6   About the hydrators

6.1   About hydrators

The hydrator handles the sql query transformation into the objects that were configured in the metadata through the Repository

6.2   Default hydrator

The default hydrator will use the metadata to create the right object and will fill an associative array with, as a key, the table name (or alias used) and the object for as a value

use CCMBenchmark\Ting\Repository\Repository;
use CCMBenchmark\Ting\Repository\MetadataInitializer;

class SampleRepository extends Repository implements MetadataInitializer
{
    /**
     * @return CollectionInterface
     */
    public function getCommentForSylvain()
    {
        $query = $this->getQuery('SELECT id, name, c.text FROM user LEFT JOIN comment c ON (c.user_id = user.id) WHERE name = :name');
        $query->setParams(['name' => 'Sylvain']);

        return $query->query();
    }

    // ...

The returned collection is composed of rows:

$row =
  [
    'user' => User(
      [id] => 3,
      [name] => "Sylvain"
    ),
    'c' => Comment(
      [text] => "Bonjour tout le monde"
    )
  ]

6.2.1   SQL join with no data

When the join returns no data, the key ‘c’ will be null

6.3   The hydrator for a single object

The default hydrator is optimized to return multiple objects when we do a query that is intended to return only one item, it is not the best choice.

You can inject CCMBenchmark\Ting\Repository\HydratorSingleObject, to suit your needs

use CCMBenchmark\Ting\Repository\Repository;
use CCMBenchmark\Ting\Repository\MetadataInitializer;
use CCMBenchmark\Ting\Repository\CollectionInterface;

class SampleRepository extends Repository implements MetadataInitializer
{
    /**
     * @return CollectionInterface
     */
    public function getUserSylvain()
    {
        $query = $this->getQuery('SELECT id, name, FROM user WHERE name = :name');
        $query->setParams(['name' => 'Sylvain']);

        return $query->query($this->getCollection(new CCMBenchmark\Ting\Repository\HydratorSingleObject()));
    }

    // ...

The returned collection is a collection of object User.

6.4   The aggregator hydrator

Note

Available since version 3.3

Warning

Deprecated in 3.5, replaced by the relational hydrator

This one allows us to aggregate a set of results, for example, we can return a collection of users and for each User object we can have its list of books.

Here is an example of use

use CCMBenchmark\Ting\Repository\Repository;
use CCMBenchmark\Ting\Repository\MetadataInitializer;
use CCMBenchmark\Ting\Repository\CollectionInterface;

class SampleRepository extends Repository implements MetadataInitializer
{
    /**
     * @return CollectionInterface
     */
    public function getUsersWithBooks()
    {
        $query = $this->getQuery('SELECT user.id, user.name, book.id, book.name
                FROM user
                INNER JOIN book ON (book.user_id = user.id)
                ORDER BY user.id');

        $hydratorAggregator = new \CCMBenchmark\Ting\Repository\HydratorAggregator();
        $hydratorAggregator->callableIdIs(function ($result) {
            return $result['user']->getId();
        });

        $hydratorAggregator->callableDataIs(function ($result) {
            return $result['book'];
        });

        $hydratorAggregator->callableFinalizeAggregate(function ($result, $books) {
            $result['user']->setBooks($books);
            return $result['user'];
        });

        return $query->query($this->getCollection($hydratorAggregator));
    }

    // ...

If we take a deeper look

$hydratorAggregator->callableIdIs(function ($result) {
    return $result['user']->getId();
});

The closure injected through callableIdIs allows you to return the identifier we’re going to use as aggregate key (user’s identifier here)

Note

It’s very important to sort your data on this aggregate key in your SQL query otherwise you will have partial result.

$hydratorAggregator->callableDataIs(function ($result) {
    return $result['book'];
});

The closure injected through callableDataIs allows you to return the data that should be aggregated (here a book)

$hydratorAggregator->callableFinalizeAggregate(function ($result, $books) {
    $result['user']->setBooks($books);
    return $result['user'];
});

This last part is optional, when you ommit it, the aggregated result will be put into aggregate key of the collection. You can finalize the aggregate with this closure and choose what to do with the data aggregated, here we have a list of book and we inject them into user through setBooks

The returned collection is composed of rows:

$row =
  [
    0 => User(
      [id] => 1,
      [name] => 'Sylvain'
      [books] => [
        Book(
          [id] => 1
          [name] => "Tintin au tibet"
        ),
        Book(
          [id] => 2
          [name] => "L'Oreille cassée"
        )
      ]
    )
  ]

6.5   The relational hydrator

Note

Available since version 3.5

This one allows us to aggregate a set of results with N depths, for example, we can return a collection of users and for each User object we can have its list of books, which themselves have an Author.

Here is an example of use

use CCMBenchmark\Ting\Repository\Hydrator;
use CCMBenchmark\Ting\Repository\HydratorRelational;
use CCMBenchmark\Ting\Repository\Repository;
use CCMBenchmark\Ting\Repository\MetadataInitializer;
use CCMBenchmark\Ting\Repository\CollectionInterface;

class SampleRepository extends Repository implements MetadataInitializer
{
    /**
     * @return CollectionInterface
     */
    public function getUsersWithBooksAndAuthor()
    {
        $query = $this->getQuery('SELECT user.id, user.name, book.id, book.name, author.id, author.name
                FROM user
                INNER JOIN book ON (book.user_id = user.id)
                INNER JOIN author ON (author.id = book.author_id)
                ORDER BY user.id');

        $hydratorRelational = new HydratorRelational();
        $hydratorRelational->addRelation(new Hydrator\RelationMany(
            new Hydrator\AggregateFrom('book'),
            new Hydrator\AggregateTo('user'),
            'setBooks'
        ));

        $hydratorRelational->addRelation(new Hydrator\RelationOne(
            new Hydrator\AggregateFrom('author'),
            new Hydrator\AggregateTo('book'),
            'setAuthor'
        ));

        $hydratorRelational->callableFinalizeAggregate(function ($row) {
            return $row['user'];
        });

        return $query->query($this->getCollection($hydratorRelational));
    }

    // ...

Note

This hydrator going to fetch every rows and hydrate them, you’re no longer in a lazy hydration.

If we take a deeper look

$hydratorRelational->addRelation(new Hydrator\RelationMany(
    new Hydrator\AggregateFrom('book'),
    new Hydrator\AggregateTo('user'),
    'setBooks'
));

We add the relation many which going to say to push Book within the object User through the method setBooks

$hydratorRelational->callableFinalizeAggregate(function ($row) {
    return $row['user'];
});

This last part is optional, when you ommit it, the result will be:

$row =
  [
    'user' => [
      0 => User(
        [id] => 1,
        // ...

However if the closure is there, the result will be:

$row =
  [
    0 => User(
      [id] => 1,
      [name] => 'Sylvain'
      [books] => [
        Book(
          [id] => 1
          [name] => "Tintin au tibet",
          [author] =>
            Author(
              [id] => 1
              [name] => "Hergé"
            )
        ),
        Book(
          [id] => 2
          [name] => "L'Oreille cassée",
          [author] =>
            Author(
              [id] => 1
              [name] => "Hergé"
            )
        )
      ]
    )
  ]

6.6   Data without metadata

If you perform a query that returns data that do not match any metadata, whether an aggregation column as SUM(price) or a column that has not been mapped as my_extra_column the hydrator will create a stdClass object with properties corresponding to those columns.

This stdClass object is accessible in the key 0 of the returned array.

use CCMBenchmark\Ting\Repository\Repository;
use CCMBenchmark\Ting\Repository\MetadataInitializer;
use CCMBenchmark\Ting\Repository\CollectionInterface;

class SampleRepository extends Repository implements MetadataInitializer
{
    /**
     * @return CollectionInterface
     */
    public function getArticles()
    {
        $query = $this->getQuery('SELECT name, my_extra_column, SUM(price) as total FROM article');

        return $query->query();
    }

The returned collection is composed of rows:

$row =
  [
    0 => stdClass(
      [total] => 43,
      [my_extra_column] => 'Bic'
    ),
    'article' => Article(
      [name] => "Stylo"
    )
  ]

6.7   Mapping data without metadata

When you have an aggregate column you might want to mapped it to an object. To map the column nb_books into my model User through the method setNbBooks you can do that:

use CCMBenchmark\Ting\Repository\Repository;
use CCMBenchmark\Ting\Repository\MetadataInitializer;
use CCMBenchmark\Ting\Repository\CollectionInterface;
use CCMBenchmark\Ting\Repository\Hydrator;

class SampleRepository extends Repository implements MetadataInitializer
{
    /**
     * @return CollectionInterface
     */
    public function getUsersWithNbBooks()
    {
        $query = $this->getQuery('SELECT name, SUM(has_book.id) as nb_books FROM user INNER JOIN has_book ON (user.id = has_book.user_id)');
        $hydrator = new Hydrator();
        $hydrator->mapAliasTo('nb_books', 'user', 'setNbBooks')

        return $query->query($this->getCollection($hydrator));
    }

    // ...

The returned collection is composed of rows:

$row =
  [
    'user' => User(
      [name] => "name",
      [nbBooks] => 3
    )
  ]

6.8   Deserialize data without metadata

As a reminder, transform a database type into a PHP type is to deserializing. For example when you retrieve a date which is not in metadata, we may want to transform it to object Datetime.

use CCMBenchmark\Ting\Repository\Repository;
use CCMBenchmark\Ting\Repository\MetadataInitializer;
use CCMBenchmark\Ting\Repository\CollectionInterface;
use CCMBenchmark\Ting\Repository\Hydrator;
use CCMBenchmark\Ting\Serializer;

class SampleRepository extends Repository implements MetadataInitializer
{
    /**
     * @return CollectionInterface
     */
    public function getArticlesWithFetchedDate()
    {
        $query = $this->getQuery('SELECT title, NOW() as fetchedDate FROM article');
        $hydrator = new Hydrator();
        $hydrator->unserializeAliasWith('fetchedDate', $services->get('SerializerFactory')->get(Serializer\Datetime::class))

        return $query->query($this->getCollection($hydrator));
    }

    // ...

The returned collection is composed of rows:

$row =
  [
    0 => stdClass(
      [fetchedDate] => Datetime("2016-01-13 10:41:36")
    ),
    'article' => Article(
      [name] => "My Awesome Book",
    )
  ]

6.9   Object composition

We may want to do object compositions, injecting one object into one other on many deep levels. To map object Country (which has alias co) into my model City (which has alias cit) through the method setCountry you can do that:

use CCMBenchmark\Ting\Repository\Repository;
use CCMBenchmark\Ting\Repository\MetadataInitializer;
use CCMBenchmark\Ting\Repository\CollectionInterface;
use CCMBenchmark\Ting\Repository\Hydrator;

class SampleRepository extends Repository implements MetadataInitializer
{
    /**
     * @return CollectionInterface
     */
    public function getCityWithCountry()
    {
        $query = $this->getQuery('SELECT cit.name, co.cou_name FROM city cit INNER JOIN t_country_cou co ON (c.cou_code = co.cou_code)');
        $hydrator = new Hydrator();
        $hydrator->mapObjectTo('co', 'cit', 'setCountry')

        return $query->query($this->getCollection($hydrator));
    }

    // ...

The returned collection is composed of rows:

$row =
  [
    'cit' => City(
      [name] => "Palaiseau",
      [country] => Country(
        [name] = "France"
      )
    )
  ]