<commit_before># Pomm ModelManager

[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/pomm-project/ModelManager/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/pomm-project/ModelManager/?branch=master)

Pomm's ModelManager package is the common model layer built upon the Foundation package. It makes developers able to query the database and get object oriented entities back.

This package is still in early development stage and should be available soon. If you want to use Pomm in production now, look at [Pomm 1.x](http://www.pomm-project.org).
<commit_msg>README first part.<commit_after># Pomm ModelManager

[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/pomm-project/ModelManager/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/pomm-project/ModelManager/?branch=master)

Pomm's ModelManager package is the common model layer built upon the Foundation package. It makes developers able to create models against the database and get object oriented entities back. **It is not an ORM** it grants developers with the ability to perform native queries and use almost all types of Postgresql. This makes model layer to meet with performances while staying lean.

This package is still in early development stage and should be available soon. If you want to use Pomm in production now, look at [Pomm 1.x](http://www.pomm-project.org).

## Introduction

The model manager links classes with database relation through structure (relation physical ) and projection (fields returned to the application) to hydrate entity instances manipulated by web applications.

![Diffrent classes involved](http://yuml.me/816d261c)

FlexibleEntity, Structure and Model classes can be generated by the [Cli package tool](https://github.com/pomm-project/Cli) by inspecting the database. It is also possible to map to virtual objects like `VALUES` sets or functions results by creating custom classes.

## Structure file

Structure class is a very simple component, it binds fields with types. They are used by write queries by the projection. Here is a code example of a simple Structure class code:

```php
class DocumentStructure extends RowStructure
{
    /**
     * initialize
     *
     * @see RowStructure
     */
    protected function initialize()
    {
        $this
            ->setRelation('document')
            ->setPrimaryKey(['document_id'])
            ->addField('document_id', 'uuid')
            ->addField('title', 'varchar')
            ->addField('updated_at', 'timestamp')
            ->addField('creator_id', 'uuid')
            ;
    }
}
```

## Model and queries

Model classes are are the main point of Pomm model manager. They are bound with a flexible entity class thus responsible of issuing queries and returning hydrated instances of this type:

```php
class DocumentModel extends Model
{
    use WriteQueries;

    protected function __construct()
    {
        $this->structure = new DocumentStructure;
        $this->flexible_entity_class = "\Model\PommTest\PyloneSchema\Document";
    }
}
```

The class shown above uses the `DocumentStructure` seen before and a `Document` entity. As the `document` relation is a table, the class imports the `WriteQueries` trait that contains read and write predefined queries like `findWhere()`, `createAndSave()` and many more methods. As soon as this file is available in the project, it is possible to use the model layer from a controller using the `Model client pooler` (see the [Foundation package](https://github.com/pomm-project/Foundation)):

```php
function touchDocumentController($document_id)
{
    $document = $this->container->getService('pomm')['db_name']
        ->getModel('\Database\Schema\DocumentModel')
        ->updateByPK([$document_id], ['updated_at' => new \DateTime()])
        ;

    if (!$document) {
        throw new NotFound404Exception(
            sprintf("Could not find document '%d'.", $document_id)
            );
    }

    return json_encode($document->extract());
}
```

Here are the different methods loaded by the `ReadQueries` and `WriteQueries` traits:

 * `findAll()`
 * `findWhere(Where)`
 * `countWhere(Where)`
 * `findByPK([primary_key])`

 * `insertOne(FlexibleEntity)`
 * `updateOne(FlexibleEntity, [changes])`
 * `deleteOne(FlexibleEntity)`
 * `createAndSave([fields])`
 * `updateByPK([primary_key], [changes])`
 * `deleteByPK([primary_key], [changes])`

All the read and write queries either return an instance of the updated (or deleted) entity or update the entity by reference with values returned from the database.

### Composing conditions

Some of the methods shown above can take a `Where` arguments. This is a condition builder allowing composition of conditions.

```php
class DocumentModel extends Model
{
    use WriteQueries;

    protected function __construct()
    {
        $this->structure = new DocumentStructure;
        $this->flexible_entity_class = "\Model\Database\Schema\Document";
    }

    public function findSecret($secret_level, $where = null)
    {
        Where::create('secret_level > $*', [$secret_level])
            ->andWhere($where)
            ;

        return $this->findWhere($where);
    }

    public function findPersonnalSecretFile(SecretAgent $agent, Where $where = null)
    {
        Where::create('agent_id = $*', [$agent['agent_id']])
            ->andWhere($where)
            ;

        return $this->findSecret($agent['secret_level'], $where);
    }
```

### Projection

Projection is the big difference between Pomm and an ORM. ORM define the relation through a static class definition whereas Pomm defines a [projection](http://en.wikipedia.org/wiki/Relational_algebra#Projection_.28.CF.80.29) (ie the fields list of a select or returning) from a database relation to a flexible instance.

By default, a `Model` instance takes all the fields of its relation so `$model->findByPK(['document_id' => 2])` is equivalent to `select * from myschema.document where document_id = 2`. But it is possible to tune this projection in overloading the `createProjection()` method.

```php
function createProjection()
{
    return parent::createProjection()
        ->unsetField('unnecessary_field')
        ->setField('quality_score', '%page / (%modification + 1)', 'float4')
        ;
}
```

Now, calling `findByPK` will issue a query like `select …, d.page / (d.modification + 1) as "quality_score", … from document d where …`. It is important to note that all queries are using the default projection so modifying it will change the values the entities are hydrated with. The third argument is the type associated with the added field. It makes the converter system to know how to convert the new value from its database representation to a usable PHP value.


### Custom queries

Even though the queries coming with the traits cover a broad range of what can be done on a relation, Pomm incites developers to write custom queries using the rich [Postgres's SQL language](http://www.postgresql.org/docs/9.4/static/sql.html). Since Pomm is not an ORM, it will never generate queries to fetch foreign information letting the developers to write neats queries and add the according indexes in the database. Let's see a simple custom query:

```php
class DocumentModel extends Model
{
    use WriteQueries;

    protected function __construct()
    {
        $this->structure = new DocumentStructure;
        $this->flexible_entity_class = "\Model\Database\Schema\Document";
    }

    public function findWithAttachmentCountWhere(Where $where)
    {
        // 1.define SQL query using placeholders
        $sql = <<<SQL
select
    :projection
from
    :document_table d
        join :attachment_table a using (document_id)
where :condition
SQL;
        // 2.define the projection
        $projection = $this
            ->createProjection()
            ->setField('count(a.*)', 'int4')
            ;

        // 3.replace placeholders
        $sql = strtr($sql,
            [
                ':projection'       => $projection ->formatFieldsWithFieldAlias('d'),
                ':document_table'   => $this->getStructure()->getRelation(),
                ':attachment_table' => $this
                                        ->getSession()
                                        ->getModel('\Model\Database\Schema\AttachementModel')
                                        ->getStructure()
                                        ->getRelation(),
                ':condition'        => $where
            ]
        );

        // 4.issue the query
        return $this->query($sql, $where->getValues(), $projection)
    }
}
```

This way to issue write queries allow developers to focus on what the query actually does instead of managing list of fields and aliases. All the goodness of Postgres'SQL like window functions, CTE etc. are usable this way.

## Collection & flexible entities

`findAll()` and `findWhere()` methods return more than one entity, they return an iterator on result: a `CollectionIterator`. The easiest way to fetch entities from this iterator is to traverse it:

```php
$iterator = $pomm['my_database']
    ->getModel('\Model\Database\Schema\Document')
    ->findSecret()
    ;

if ($iterator->isEmpty()) {
    printf("No document found.\n");
} else {
    foreach ($iterator as $document) {
        printf("Document '%s' is secret.\n", $document['title']);
    }
}
```


## Model layer

## Testing

{'fraction_non_alphanumeric': 0.1024390243902439, 'fraction_numerical': 0.0030752916224814422, 'mean_word_length': 3.316247139588101, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 48, 'https://': 6, 'lorem ipsum': 0, 'www.': 3, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4240208', 'n_tokens_mistral': 2509, 'n_tokens_neox': 2430, 'n_words': 938}