Pomm 2.0 in few steps

Quick Pomm2 setup

Requirements:
➥ PHP >= 5.4.4
➥ A working Postgresql database

Step 1: Install Pomm

Installing Pomm 2 is as simple as adding the following packages in a composer.json file:

{
    "require": {
        "pomm-project/cli": "2.0.*@dev",
        "pomm-project/model-manager": "2.0.*@dev",
        "pomm-project/foundation": "2.0.*@dev"
    }
}

Launch composer.phar install and it will install Pomm2 and sets the autoloader accordingly.

Now it is time to configure the database connections. The CLI expects to get a configured Pomm instance from requiring the file .pomm_cli_bootstrap.phpin the project root directory. Let's fill it with database configuration:

<?php // .pomm_cli_bootstrap.php

use \PommProject\Foundation\Pomm;

$loader = require __DIR__.'/vendor/autoload.php';
$loader->add(null, __DIR__);

return new Pomm(['my_db' =>
    [
        'dsn' => 'pgsql://user:pass@host:port/my_db_name',
        'class:session_builder' => '\PommProject\ModelManager\SessionBuilder',
    ]
]);
The name of the database is important since it will be used in the namespace for generated classes.

At this point, the CLI tool should be functional, just enter ./vendor/bin/pomm.php in a terminal, it should greet you with a help message. Try to launch a pomm:inspect:database command, it should output something similar to:

$ php vendor/bin/pomm.php pomm:inspect:database my_db
Found 1 schemas in database.
+-----------+--------+-----------+-------------------------+
| name      | oid    | relations | comment                 |
+-----------+--------+-----------+-------------------------+
| public    | 2200   | 11        | standard public schema  |
+-----------+--------+-----------+-------------------------+

Note to Windows users:
  The script vendor\bin\pomm.php is actually a batch generated by composer to launch vendor\pomm-project\cli\bin\pomm.php, it will not work when called with PHP’s CLI directly.

Step 2: Generate model classes

The CLI can also be used to generate model files. If you want to generate model files for all the relations in a schema, just enter:

$ php vendor/bin/pomm.php pomm:generate:schema-all -d sources/lib/Model -a 'Vendor\Project\Model' --psr4 my_db public
 ✓  Creating file 'sources/lib/Model/MyDb/PublicSchema/AutoStructure/Computer.php'.
 ✓  Creating file 'sources/lib/Model/MyDb/PublicSchema/ComputerModel.php'.
 ✓  Creating file 'sources/lib/Model/MyDb/PublicSchema/Computer.php'.
…
  • The class in AutoStructure/Entity.php owns the structure dependent code introspected from the database.
  • The class in EntityModel.php is where the custom queries for the according entity will take place.
  • The class in Entity.php is the flexible entity code. Getter and setters overloads will go there.

Step 3: tune the projection

Let's consider the following script that uses the ComputerModel:

<?php

$pomm = require ".pomm_cli_bootstrap.php";

$computers = $pomm['my_db']
    ->getModel('\MyDb\PublicSchema\ComputerModel')
    ->findWhere('$* >> any (interfaces)', ['192.168.0.0/20'])
    ;

if ($computers->isEmpty()) {
    printf("No computers in database.\n");
} else {
    foreach ($computers as $computer) {
        printf(
            "Computer id='%s', interfaces = [%s].\n",
            $computer['computer_id'],
            join(', ', $computer['interfaces'])
        );
    }
}
What it does is returning all computers with an interface in the 192.168.0.0/20 network:
select
    c."computer_id" as "computer_id",
    …,
    c."interfaces" as "interfaces"
from
    public.computer c
where
    $1 >> any (interfaces)
It will display all the computers matching the given condition and print out the list of their interfaces on the same line.

For the example's sake, let's say we always want to know if a computer has a public IP address (a "public" computer). It is possible to modify the projection to add such information in the select part of SQL queries that fetches computers:

<?php // MyDB/PublicSchema/ComputerModel.php
// …
    public function createProjection()
    {
        return parent::createProjection()
            ->setField(
                'is_public',
                "not '192.168.0.0/16'::inet >> all(%:interfaces:%)",
                'bool'
            );
    }
//…
The SQL queries issued by script.php turns to:
select
    c."computer_id" as "computer_id",
    …,
    c."interfaces" as "interfaces",
    not '192.168.0.0/12'::inet >> all(c."interfaces") as "is_public"
from
  public.computer c
where
The Computer instances now have a new attribute named is_public with boolean value. This field can be accessed like any other field but it can however not be saved.

Step 4: custom queries

All the model's built-in methods issue queries regarding a given relation. This covers a broad range of queries but most of the time, queries join relations to consolidate data. Let's assume we want to know the number of registered applications running on each machine. We need to write the SQL for that query. Query writing respects almost always the same steps:

➥ step 1: write the SQL.
➥ step 2: create a custom projection if needed.
➥ step 3: expand the query.
➥ step 4: execute the query.
<?php // MyDb/PublicSchema/ComputerModel.php
// …
    public function findWithSoftCountWhere(Where $where)
    {
        // step 1
        $sql = <<<SQL
select
  :projection
from
  :relation
    left join :installed_application_relation soft using (computer_id)
where
    :condition
group by computer_id
SQL;
        // step 2
        $projection = $this->createProjection()
            ->setField('soft_count', 'count(soft)', 'int4')
            ;

        // step 3
        $sql = strtr($sql,
            [
                ':projection' => $projection,
                ':relation'   => $this->getStructure()->getRelation(),
                ':installed_application_relation' => $this->getSession()
                    ->getModel('\MyDb\PublicSchema\InstalledApplicationModel')
                    ->getStructure()
                    ->getRelation(),
                ':condition' => $where,
            ]
        );

        // step 4
        return $this->query($sql, $where->getValues(), $projection);
    }

Step 1, since the query uses the projection system, there is no need to deal with a tedious and hard to maintain list of fields. Same for the relations names. The query is expressed as its simplest form: what is needed ?

Step 2, default projection is augmented with this query specific fields. The soft_count will be converted to integer and accessed like any other fields.

Step 3, the query parameters are expanded.

Step 4, the query is issued, thanks to the Where instance.

With this method, the script.php can be rewritten as:

<?php

use PommProject\Foundation\Where;

$pomm = require ".pomm_cli_bootstrap.php";

$computers = $pomm['my_db']
    ->getModel('\MyDb\PublicSchema\ComputerModel')
    ->findWithSoftCountWhere(new Where('$* >> any (interfaces)', ['192.168.0.0/20']))
    ;

echo json_encode($computers->extract());

Step 5: Read the documentation