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"
)
)
]