Pascal MARTIN m’a souvent dit « les générateurs c’est fantastique ». J’ai donc voulu essayer.
Les générateurs sont apparus en 2013 avec PHP 5.5. Le concept et son utilisation peuvent paraître un peu abstrait. Je vais donc essayer de vous donner une idée d’utilisations avec les générateurs.
Je vais vous présenter une utilisation que j’ai pu trouver de conversion « à la volée » pendant un export. J’ai utilisé cette solution dans un projet réel mais cet article ne sera qu’une illustration simple.
Présentation
J’ai créé un petit projet pour illustrer cet article : GeneratorExample. Ce projet permet l’export en CSV d’une liste de Person
. Pour cet export nous utilisons la méthode fputcsv
. Nous avons donc besoin de convertir les objets Person
en tableau.
Voici la classe Person
:
Et la classe PersonRepository
:
Cette classe est une version très allégée (pour l’exemple) d’un Repository Doctrine avec la méthode findAll()
qui nous retourne toutes les personnes. Dans une version réelle les données viendraient généralement d’une base de données.
Pour la conversion de la classe Person
en tableau, j’ai créé une methode findAllAsArray()
. Cette méthode à pour but de prendre les personnes retournées par findAll()
et de convertir chaque objet Person
en un tableau.
Nous allons voir dans un premier temps l’utilisation d’un tableau pour stocker les conversions des personnes. Ensuite nous verrons la conversion avec l’utilisation d’un générateur.
Une autre solution aurait été d’utiliser un Iterator
mais le générateur est Iterator
avec une écriture très allégée donc nous ne la présenterons pas.
Utilisation des tableaux
Nous avons vu que nous pouvons effectuer notre conversion en stockant les résultats dans un tableau à la manière suivante :
Cette méthode est très simple et très lisible. Le problème de cette solution est que l’on utilise beaucoup de mémoire si la fonction findAll()
retourne beaucoup de personnes. Dans le cas où findAll()
utilise un Traversable
nous perdons le chargement « au besoin ».
C’est là que les générateurs vont être utiles.
Utilisation des générateurs
Nous pouvons donc utiliser un générateur comme dans la méthode suivante :
Le code reste très lisible et nous gagnons en preformance.
Bench
J’ai créé le script command.php pour pouvoir réaliser des tests comparatifs entre les deux méthodes. Si on utilise l’argument array
on utilise les conversions stockées dans un tableau, dans les autres cas nous utilisons la conversion via le générateur. J’ai utilisé une table de données un peu importante pour avoir du contenu et permettre une meilleure comparaison.
Nous pouvons voir qu’avec l’utilisation des générateurs nous avons un gain d’au moins 90% de mémoire, dans ce cas, par rapport à l’utilisation des tableaux. Ce gain dépend, bien sur, du volume de données à traiter.
Cerise sur le gateau nous pouvons également voir que le temps d’éxécution est (un peu) plus court avec les générateurs.
Conclusion
Nous avons pu voir une utilisation des générateurs pour convertir des données. La solution n’est pas plus compliquée à écrire que le fait d’utiliser un tableau.
Nous aurions pu jouer la conversion des données au moment où nous utilisions la fonction fputcsv
le problème est que nous perdons le principe de ‘Responsabilité unique’ (Single responsibility) conseillé dans le développement SOLID.
En parlant de SOLID nous aurions dû utiliser une classe permettant la conversion en dehors du Repository mais cela aurait plus complexifié l’exemple.
Un autre gros avantage des générateurs c’est que nous pouvons les chainer, comme avec le script chained_generators dont voici le résultat :
Nous avons donc des conversions successives qui se font en consommant une quantité réduite de ressources.
Merci de votre lecture.
Merci à Pascal MARTIN sans qui cet article n’aurait jamais vu le jour, ainsi que pour sa relecture.
Je connais pas bien les générateurs mais ça m’intrigue ton return :
« `
if (empty($persons)) {
return;
}
« `
Quel est l’effet si il n’y a eu aucun yield ?
Bah écoute, tu m’as mis le doute donc j’ai refais un test et il se passe rien de mal.
C’est comme si tu rentrais dans un foreach mais avec un tableau vide.