6 Les hydrateurs¶
6.1 Au sujet des hydrateurs¶
L’hydrateur va être chargé de transformer le résultat d’une requête SQL dans les objets qui ont été configurés dans les metadata par l’intermédiaire du Repository
6.2 L’hydrateur par défaut¶
L’hydrateur par défaut va retourner pour chaque ligne un tableau associatif avec pour clé le nom de la table (ou l’alias utilisé) et pour valeur l’objet qu’il a réussi à créer par rapport aux metadata.
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();
}
// ...
La collection retournée est composée de lignes structurées ainsi :
$row =
[
'user' => User(
[id] => 3,
[name] => "Sylvain"
),
'c' => Comment(
[text] => "Bonjour tout le monde"
)
]
6.2.1 Jointure avec aucune donnée¶
Lorsque la jointure ne retourne aucune donnée, la clé “c” aura pour valeur null
6.3 L’hydrateur pour un seul objet¶
L’hydrateur par défaut est optimisé pour retourner plusieurs objets, lorsque l’on fait une requête qui n’a pour but que de retourner un objet, il n’est pas des plus appropriés.
Vous pouvez injecter CCMBenchmark\Ting\Repository\HydratorSingleObject
qui conviendra mieux à votre besoin.
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()));
}
// ...
La collection retournée est une collection d’objet User.
6.4 L’hydrateur d’aggrégation¶
Note
Disponible uniquement à partir de la version 3.3
Avertissement
Déprécié en 3.5, ce dernier à été remplacé par l”hydrateur relationnel
Celui-ci permet d’aggréger un ensemble de résultats, par exemple retourner une collection d’objet User
et que chaque objet User
ait la liste de tous les livres possédés par ce dernier.
Voici un exemple d’utilisation
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));
}
// ...
Rentrons un peu dans les détails :
$hydratorAggregator->callableIdIs(function ($result) {
return $result['user']->getId();
});
La closure injectée via callableIdIs
permet de retourner l’identifiant qui sera utilisé comme clé d’aggrégation (ici l’identifiant de l’utilisateur)
Note
Il est très important d’effectuer un tri dans votre requête SQL sur cette clé d’aggrégation sinon vous aurez des résultats partiels.
$hydratorAggregator->callableDataIs(function ($result) {
return $result['book'];
});
La closure injectée via callableDataIs
permet de retourner la donnée qui doit être aggrégée (ici un Livre)
$hydratorAggregator->callableFinalizeAggregate(function ($result, $books) {
$result['user']->setBooks($books);
return $result['user'];
});
Cette dernière partie est facultative, si elle est omise, le résultat de l’aggrégation se trouvera dans la clé aggregate
de la collection.
Elle vous permet d’effectuer une opération de finalisation et de choisir ce que vous voulez faire des données qui viennent d’être aggrégées, ici
il s’agit d’une liste de livres que nous injectons dans l’utilisateur via la méthode setBooks
La collection retournée est composée de lignes structurées ainsi :
$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 L’hydrateur relationnel¶
Note
Disponible uniquement à partir de la version 3.5
Celui-ci permet d’aggréger un ensemble de résultats sur N niveaux, par exemple retourner une collection d’objet User
et
que chaque objet User
ait la liste de tous les livres possédés par ce dernier, qui eux même ont un objet Author
.
Voici un exemple d’utilisation
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
Cet hydration va parcourir toutes les lignes retournées par votre requête et les hydrater, vous ne bénéficiez plus d’une lazy hydration.
Rentrons un peu dans les détails :
$hydratorRelational->addRelation(new Hydrator\RelationMany(
new Hydrator\AggregateFrom('book'),
new Hydrator\AggregateTo('user'),
'setBooks'
));
On ajoute une relation du type many qui consiste à injecter les objets Book
dans l’objet User
par la méthode setBooks
$hydratorRelational->callableFinalizeAggregate(function ($row) {
return $row['user'];
});
Cette dernière partie est facultative, dans cet exemple si elle est omise, le résultat sera le suivant :
$row =
[
'user' => [
0 => User(
[id] => 1,
// ...
Si cependant le callable est présent, la collection retournée sera la suivante :
$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 Données sans metadata¶
Si vous effectuez une requête qui retourne des données qui ne correspondent à aucune metadata, que ce soit une colonne
d’aggrégation comme SUM(price)
ou une colonne qui n’a pas été mappée comme my_extra_column
l’hydrateur va créer un objet
stdClass
avec des propriétés correspondant à ces colonnes.
Cet objet stdClass
est accessible dans la clé 0 du tableau retourné.
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();
}
La collection retournée est composée de lignes structurées ainsi :
$row =
[
0 => stdClass(
[total] => 43,
[my_extra_column] => 'Bic'
),
'article' => Article(
[name] => "Stylo"
)
]
6.7 Mapper des données sans metadata¶
Dans le cas d’une colonne d’aggrégation, on peut souhaiter la mapper dans un objet.
Pour mapper la colonne nb_books
dans mon model User via la méthode setNbBooks
il suffit de faire :
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));
}
// ...
La collection retournée est composée de lignes structurées ainsi :
$row =
[
'user' => User(
[name] => "name",
[nbBooks] => 3
)
]
6.8 Déserializer des données sans metadata¶
Pour rappel l’action de déserializer consiste à transformer un type de base données dans un type PHP.
Par exemple si on récupère une date qui n’est pas dans des metadata, on peut vouloir transformer la date en objet
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));
}
// ...
La collection retournée est composée de lignes structurées ainsi :
$row =
[
0 => stdClass(
[fetchedDate] => Datetime("2016-01-13 10:41:36")
),
'article' => Article(
[name] => "My Awesome Book",
)
]
6.9 Composition d’objet¶
On peut vouloir faire de la composition d’objet, injecter un objet dans un autre et ce sur plusieurs niveaux.
Pour mapper l’objet Country
(qui a l’alias co
) dans mon model City
(qui a l’alias cit
) via la méthode setCountry
il suffit de faire :
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));
}
// ...
La collection retournée est composée de lignes structurées ainsi :
$row =
[
'cit' => City(
[name] => "Palaiseau",
[country] => Country(
[name] = "France"
)
)
]