Se connecter

Informatique

Création de sites web

Sujet : [PHP] Itération sur one-to-many
1
vava740
Niveau 10
18 août 2014 à 11:38:24

Bonjour,

Je suis confronté à un problème qui m'avait l'air trivial mais qui me pose plus de problèmes que prévu (du moins en PHP).

J'ai un jeu de résultats potentiellement très grand, provenant d'une jointure SQL one-to-many (triée par ID de la partie "one"), dans un itérateur de sorte à ce que tout ne soit pas chargé en mémoire. Un exemple de résultat : https://wall.deblan.org/x1baa/php/1/

L'idée est de créer un itérateur PHP qui (dans l'exemple) aggrège les colonnes `name` dans un tableau `names` jusqu'à un changement d'`id`.

Le résultat devrait être le suivant après un `iterator_to_array` : https://wall.deblan.org/x1bab/php/1/

J'ai implémenté ça sans problèmes avec un générateur PHP 5.5, et ça marche très bien: https://wall.deblan.org/x1bac/php/1/

Mais le serveur de production est en PHP 5.4, et faut pas compter avoir PHP 5.5 dessus avant que ce soit la version par défaut sur Debian stable. Je me retrouve donc à devoir implémenter une logique similaire dans une classe qui implémente `Iterator`, et là je bloque. Y'a beaucoup plus de contraintes "inutiles" que je n'avais pas avec le générateur, et je ne trouve pas de solution qui me semble logique et évidente.

J'ai essayé d'observer comment fait Laravel, mais il s'avère qu'ils chargent le résultat entier en mémoire pour gérer les associations : https://github.com/illuminate/database/blob/8de434dbdd352b271201eb537df5f629060f4ea7/Eloquent/Relations/BelongsToMany.php#L390-L439

J'ai aussi regardé du côté de Doctrine qui a une méthode `iterate` pour ne pas tout charger en mémoire, mais cette méthode est justement incompatible avec les jointures de ce type (voir la note en jaune sur la doc) : http://docs.doctrine-project.org/en/2.0.x/reference/batch-processing.html#iterating-results

Enfin, j'ai regardé des solutions équivalentes en JavaScript, en particulier une réponse sur StackOverflow qui monte un exemple de "simulation de yield" avec Traceur, mais le résultat compilé est incompréhensible et dépend en plus sur le runtime de Traceur : http://stackoverflow.com/a/14095322

J'ai pas l'impression que le fait d'itérer sur des relations one-to-many est quelque chose d'exceptionnel, c'est apparemment la méthode la plus efficace pour éviter le problème des N+1 requêtes : http://use-the-index-luke.com/sql/join/nested-loops-join-n1-problem

Ce qui est peut-être moins commun, c'est de factoriser la logique dans un itérateur pour rendre l'aggrégation transparente au code final qui va itérer sur les résultats.

Avez-vous déjà rencontré ce problème ? Quelles solutions, patterns, outils, librairies utiliser pour résoudre le problème sans les générateurs PHP 5.5 ?

vava740
Niveau 10
18 août 2014 à 20:14:20

Bon, j'ai réussi à m'en sortir en implémentant `Iterator`, autant le générateur m'a pris une dizaine de minutes, autant j'ai passé plusieurs heures (pas compté précisément) pour faire un truc à peu près propre et fonctionnel en itérateur. :pf:

https://github.com/valeriangalliat/php-one-to-many-iterator

Dans `src` y'a une classe pour le générateur (~50 lignes), et une classe pour l'itérateur (~150 lignes), pour donner une idée.

Need PHP 5.5 ! :(

deepblue
Niveau 13
19 août 2014 à 02:00:18

Ça fonctionne presque :o)) Si tes résultats ne sont pas rassemblés par ta `commonKey`, ton code tombe à l'eau. Il y a de fortes chances que ce soit le cas d'ailleurs. Je n'ai pas de solution par contre, sauf tout charger en mémoire (cf la conversation IRC).

vava740
Niveau 10
19 août 2014 à 09:26:54

Il faut absolument que les résultats soient triés par la `commonKey` en effet, c'est d'ailleurs précisé dans le readme. :oui:

Sachant que les résultats proviennent d'une requête SQL, ça demande juste de rajouter un `ORDER BY`.

Faudrait que je prenne le temps de faire un bench, entre 1. faire N+1 requêtes, 2. tout charger en mémoire, 3. streamer après un `ORDER BY`.

1
Sujet : [PHP] Itération sur one-to-many
   Retour haut de page
Consulter la version web de cette page