Tag finder:

A gentle introduction to Pomm

 Posted by greg on January 27, 2012 3:21 PM |  Comments are deactivated for this post
Tags : geek  computing  postgresql  dev  php  pomm 

h2. Writing PHP application with Postgresql: Pomm

It has been a long time since I wrote an article on my blog. Well, lot of things have changed in my life last couple of month and I have been quite busy working on Pomm . For those who do not know Pomm, it is a fast, lightweight, efficient non ORM.

What is a non ORM ?

Well it is a software that is different of what ORMs aimed at :

  • no database abstraction: Pomm works with Postgresql only.
  • no external agnostic schema: the database is the schema.
  • no relation guessing: do what you want and just what you need.
  • no query builder: the query language is Postgres’SQL.

In doing this, we do free ourselves from ORM’s limitations in terms of type support, speed and query possibilities. In other words, Pomm is a layer above PDO to provide some features it lacks. Here are some:

  • Pg <-> PHP type translators (Arrays, geometric, binary and some more types support like HStore, Ltree … )
  • Identity Mapper
  • Schema agnostic objects hydration
  • Lazy results fetching
  • Pagers
  • Pg Schemas support
  • Database introspection (tables and views).

Let’s go

I did really like Peter van Hardenberg’s presentation about Postgresql and the example is simple enough to think about creating a PHP application that would show servers’ states.

Bootstraping the database

We will use PHP 5.3 and postgresql 8.4 as there are the minimum requirements for Pomm. First, we have to set up the database. We will create a schema _server_status_ where the event table will be stored:

db=> CREATE SCHEMA server_stat;
CREATE SCHEMA
db=> SET search_path TO server_stat, public;
SET
db=> CREATE TABLE event AS
 SELECT
   floor(random() * 20) as app_id, 
   (ARRAY[
     'starting', 'running', 'offline'
   ])[floor(random() * 3) + 1] as state,
   generate_series(
     now() - '1 year'::interval, 
     now(), 
     '2 hour'::interval) as entered_at;
CREATE TABLE
db=> SELECT * FROM event LIMIT 5;
 app_id |  state   |          entered_at           
--------+----------+-------------------------------
     16 | offline  | 2011-01-27 12:46:30.943267+00
     11 | starting | 2011-01-27 14:46:30.943267+00
     17 | starting | 2011-01-27 16:46:30.943267+00
      1 | offline  | 2011-01-27 18:46:30.943267+00
      8 | starting | 2011-01-27 20:46:30.943267+00
(5 rows)

We now have an event table full of records to test our not yet born application.

Bootstraping the application

No need for a framework here, we will just rely on Composer’s autoloader to ease our development. Let’s start in an empty directory, we are going to set tell composer our dependencies and create some directories. Open an editor and create the composer.json file as is:

{
    "require": {
        "pomm/pomm": "master-dev"
    }
} 

Let’s invoke composer and finish the job:

$ composer.phar install
Installing from lock file
  - Package pomm/pomm (master-dev)
    Downloading
    Unpacking archive
    Cleaning up

Generating autoload files
$ mkdir {web,config,bin}

Let’s finish with writing the config/init.php file that sets up the database connection:

<?php #config/init.php

require __DIR__."/../vendor/.composer/autoload.php";

$service = new Pomm\Service(
    array(
        'default' => array(
            'dsn' => sprintf('pgsql://user/your_db_name', __DIR__)
        )
));

return $service;

Parsing this file from the command line should return you no error. If you do not know what is the DSN of your database, check pomm’s documentation about it. If really in doubt, the documentation provided with Pomm might be more up to date.

Generate the model files

File generation is not something triggered by an online call. It is something developers want from the command line. Let’s create the PHP generation script we store in the bin directory

<?php # bin/generate_model.php

$service = require(__DIR__."/../config/init.php");

$scan = new Pomm\Tools\ScanSchemaTool(array(
    'schema' => 'server_stat',
    'database' => $service->getDatabase(),
    'prefix_dir' => __DIR__."/../model",

    ));
$scan->execute();
$ php bin/generate_model.php
$ tree model
model
└── YourDbName
    └── ServerStat
        ├── Base
        │   └── EventMap.php
        ├── EventMap.php
        └── Event.php

3 directories, 3 files

If we open the EventMap.php file in the Base subdirectory, we should see something like this :

<?php # model/YourDbName/ServerStat/Base/EventMap.php

namespace YourDbName\ServerStat\Base;

use \Pomm\Object\BaseObjectMap;
use \Pomm\Exception\Exception;

abstract class EventMap extends BaseObjectMap
{
    public function initialize()
    {

        $this->object_class =  'YourDbName\ServerStat\Event';
        $this->object_name  =  'server_stat.event';

        $this->addField('app_id', 'Number');
        $this->addField('state', 'String');
        $this->addField('entered_at', 'Timestamp');

        $this->pk_fields = array('');
    }
}

The last thing we must do before going on doing real things with it is to register the model directory to the autoloader.

<?php #config/init.php

$loader = require __DIR__."/../vendor/.composer/autoload.php";
$loader->add('YourDbName', __DIR__."/../model");

...

Writing the application

Let’s start simple

The first application we will write will just show all records from the database.

<?php # web/index.php

$service = require(__DIR__."/../config/init.php");

$map = $service
    ->getDatabase()
    ->createConnection()
    ->getMapFor("YourDbName\ServerStat\Event");

?>

<html>
    <body>
    <table>
        <th>
            <td>app_id</td><td>state</td><td>At</td>
        </th>
<?php foreach ($map->findAll() as $event): ?>
        <tr>
            <td><?php echo $event['app_id'] ?></td>
            <td><?php echo $event['state'] ?></td>
            <td><?php echo $event['entered_at']->format('Y-m-d H:i:s') ?></td>
        </tr>
<?php endforeach ?>
    </table>
    </body>
</html>

A bit too simplistic

Of course, we are not interested in all the records but only the last ones and it would be nice if the results could be ordered by _entered_at_ from the most recent to the oldest. This is the job of findWhere

// SELECT app_id, state, entered_at FROM server_stat.event WHERE true ORDER BY entered_at DESC LIMIT 30
$events = $map->findWhere('true', array(), 'ORDER BY entered_at DESC LIMIT 30');

Of course the «WHERE true» is not that sexy but we will see later how to write custom nice queries. For now, a nice feature of our application would be to make the user able to click on an app_id to filter only the state changes of this particular server instance. Let’s modify a bit our script:

<?php # web/index.php

$service = require(__DIR__."/../config/init.php");

$map = $service
    ->getDatabase()
    ->createConnection()
    ->getMapFor("YourDbName\ServerStat\Event");

if (isset($_GET['app_id'])) {
    $condition = 'app_id = ?';
    $values = array($_GET['app_id']);
} else {
    $condition = 'true';
    $values = array();
}

$events = $map->findWhere($condition, $values, 'ORDER BY entered_at DESC LIMIT 30');

?>

<html>
    <body>
    <p>back to <a href="index.php">index</a></p>
    <table>
        <th>
            <td>app_id</td><td>state</td><td>At</td>
        </th>
<?php foreach ($events as $event): ?>
        <tr>
            <td><a href="?app_id=<?php echo $event['app_id'] ?>"><?php echo $event['app_id'] ?></a></td>
            <td><?php echo $event['state'] ?></td>
            <td><?php echo $event['entered_at']->format('Y-m-d H:i:s') ?></td>
        </tr>
<?php endforeach ?>
    </table>
    </body>
</html>

Pagers

One coolest thing here would be using a pager instead of truncating the results to an arbitrary 30 rows limit. Using Pomm’s Pager class is that easy:

<?php # web/index.php
$page = isset($_GET['page']) ? $_GET['page'] : 1;

$pager = $map->PaginatefindWhere($condition, $values, 'ORDER BY entered_at DESC', 30, $page);
$events = $pager->getCollection();

The Pager also contains a lot of informations that we are going to use to display kind of a ruler to navigate trough pages. Here is the whole file:

<?php # web/index.php

$service = require(__DIR__."/../config/init.php");

$map = $service
    ->getDatabase()
    ->createConnection()
    ->getMapFor("YourDbName\ServerStat\Event");

if (isset($_GET['app_id'])) {
    $condition = 'app_id = ?';
    $values = array($_GET['app_id']);
} else {
    $condition = 'true';
    $values = array();
}

$page = isset($_GET['page']) ? $_GET['page'] : 1;

$pager = $map->PaginatefindWhere($condition, $values, 'ORDER BY entered_at DESC', 30, $page);
$events = $pager->getCollection();

?>

<html>
    <body>
    <p>back to <a href="index.php">index</a></p>
    <table>
        <th>
            <td>app_id</td><td>state</td><td>At</td>
        </th>
<?php foreach ($events as $event): ?>
        <tr>
            <td><a href="?app_id=<?php echo $event['app_id'] ?>"><?php echo $event['app_id'] ?></a></td>
            <td><?php echo $event['state'] ?></td>
            <td><?php echo $event['entered_at']->format('Y-m-d H:i:s') ?></td>
        </tr>
<?php endforeach ?>
    </table>
<?php if ($pager->getLastPage() > 1): ?>
<div>
  <a href="?page=1">First</a>&nbsp;
  <?php if ($pager->isPreviousPage()): ?><a href="?page=<?php echo $pager->getPage() - 1 ?>">Previous</a><?php endif ?>
    Page <?php echo $pager->getPage() ?> / <?php echo $pager->getLastPage() ?> 
    <?php if ($pager->isNextPage()): ?><a href="?page=<?php echo $pager->getPage() + 1 ?>">Next</a>&nbsp;<?php endif ?>
<a href="?page=<?php echo $pager->getLastPage() ?>">Last</a>
</div>
<?php endif ?>
    </body>
</html>

And after ?

Of course the example displayed below is simple and only aimed at showing how simple it is to use Pomm’s basics. If you went to the end of Hardenberg’s presentation you might be tempted to create a new page and display server’s uptime with the impressive query he showed in his slides. That is as easy as creating a VIEW with this query and ask Pomm to generate the model again.

db=> CREATE VIEW server_uptime AS
WITH
  transitions AS (SELECT
    (event) as current,
      lead((event), 1) over (
          partition by app_id 
              order by entered_at) as next
                FROM event),
  durations AS (
    SELECT *, 
      (next).entered_at - 
        (current).entered_at as duration
    FROM transitions
  ),
  interesting_transitions AS (
    SELECT * FROM durations
      WHERE (current).state = 'running'
        AND (next).state = 'offline')
SELECT (current).app_id,
       justify_hours(sum(duration))
         as running_for
FROM interesting_transitions
GROUP BY (current).app_id
;
CREATE VIEW
db=> SELECT * FROM server_uptime;
 app_id |   running_for    
--------+------------------
      0 | 41 days 22:00:00
     10 | 45 days 14:00:00
     17 | 39 days 06:00:00
     12 | 37 days 18:00:00
     13 | 30 days
     15 | 27 days 06:00:00
      5 | 28 days
     16 | 29 days 12:00:00
      2 | 37 days 14:00:00
      7 | 47 days 02:00:00
     11 | 40 days 20:00:00
      9 | 24 days 20:00:00
      1 | 49 days 14:00:00
      3 | 27 days 06:00:00
      6 | 32 days 14:00:00
      8 | 53 days 14:00:00
     19 | 62 days 10:00:00
     14 | 57 days
      4 | 44 days 12:00:00
     18 | 38 days 18:00:00
(20 rows)

That’s it for today, next time we will go trough the converter system. Take care.

Recent Links 

 sulzer young regulator

Bookmarked by greg on February 21, 2013 1:55 PM..
Tags: geek electronic

Follow link

 Méthode musique

Bookmarked by greg on February 2, 2013 3:00 PM..
Tags: geek

Follow link

 Pg modeler

Bookmarked by greg on September 29, 2012 6:28 AM..
Tags: geek computing postgresql

Follow link

 Parsing binary strings with PHP

Bookmarked by greg on September 24, 2012 3:29 PM..
Tags: geek computing dev php

Follow link

 Sprite map packager

Bookmarked by greg on September 24, 2012 2:24 PM..
Tags: geek computing dev

Follow link

 Rasp. Pi supercompteur

Bookmarked by greg on September 13, 2012 11:54 AM..
Tags: geek computing

Follow link

 Crew - code review for Git projects.

Bookmarked by greg on May 6, 2012 7:57 AM..
Tags: geek computing git

Follow link

 responsive grid system

Bookmarked by greg on May 5, 2012 12:38 PM..
Tags: geek computing dev

Follow link

 Vim "improved

Bookmarked by greg on March 8, 2012 9:26 AM..
Tags: geek computing vim

Follow link

 Highly scalable NoSql blog

Bookmarked by greg on March 3, 2012 9:12 AM..
Tags: geek computing dev

Follow link