Optimiser les Nested Set Doctrine quand i18n est activé
Je ne dois pas être le seul à utiliser les Nested Set de Doctrine dans mes projets Symfony pour réaliser les menus d’un site. Mais jusqu’ici, je n’avais pas vraiment prêté attention aux requêtes effectuées pour afficher un menu racine et ses descendants quand l’internationalisation (i18n) est activée. Depuis, je suis passé de 10 requêtes à … 3. Petit tour d’horizon.
Mes fichiers avant modification
Premièrement mon action ressemblait tout simplement à ce qui va suivre, à savoir la récupération de tous les menus racines, rien de bien original. Il faut quand même garder à l’esprit que ce que je vous présente ici, n’a réellement d’intérêt que si votre table est multilingue ou a une relation avec une autre table.
public function executeList(sfWebRequest $request)
{
$this->roots = Doctrine::getTable('Menu')->getTree()->fetchRoots();
}
Ensuite dans mon premier partial (_list), j’avais uncode qui récupère les enfants du nœud racine :
foreach ($roots as $root) {
echo $root->name . '<br />';
echo '<ul id="order'. $root->id . '">';
include_partial('doMenu/children',array(
'children' => $root->getNode()->getChildren()
));
echo '</ul>';
}
Et dans mon partial _children un simple affichage de chaque enfant :
<?php
foreach ($children as $child) {
echo '<li id="menu_' . $child->id . '" class="sortable"><a href="' . url_for('doMenu/edit?id=' . $child->id ). '"/>' . $child . '</a></li>';
}
Sauf que ça a beau être basique, il nous faut une requête pour l’action qui récupère l’arbre, ensuite 1 requête pour récupérer la traduction du Menu dans le partial _list (actAs: i18N pour mon modèle Menu), 2 autres dans _list pour avoir les descendants du menu racine, et pour chaque descendant, une autre requête pour récupérer la traduction … ouf !
Soit pour moi un beau total de 10 requêtes (les plus aguerris d’entre vous aurons noté que dans mon cas j’ai 6 descendants
)
Alors évidemment on va nettoyer un peu tout ça et on va éliminer les requêtes de traduction qui ralentissent le tout …
Création de la requête à la main
Au lieu de passer par la méthode Doctrine::getTable, nous allons écrire notre requêtes à la main, ce qui va nous permettre d’inclure directement les traductions. Nous allons procéder comme expliqué dans la doc Doctrine pour récupérer un arbre et ses relations.
public function executeList(sfWebRequest $request)
{
$q = Doctrine_Query::create()
->select('m.id, t.name')
->from('Menu m')
->leftJoin('m.Translation t');
$treeObject = Doctrine::getTable('Menu')->getTree();
$treeObject->setBaseQuery($q);
$this->roots = $treeObject->fetchRoots();
$treeObject->resetBaseQuery();
}
Nous passons ici de 10 à 9 requêtes. Tout ça pour ça ? Eh oui, la seule requête de traduction qui a été enlevée est la première qui était effectuée dans _list pour le menu racine, les autres (pour les enfants) sont toujours là. Pourquoi ? Tout simplement à cause du resetBaseQuery qui réinitialise la requête et nous enlève notre join sur la table translation. Même si il est conseillé de le faire dans la doc Doctrine, ce n’est pas le cas pour nous ici, nous voulons garder la même requête de l’arbre pour les enfants. Ce qui nous donne ceci :
public function executeList(sfWebRequest $request)
{
$q = Doctrine_Query::create()
->select('m.id, t.name')
->from('Menu m')
->leftJoin('m.Translation t');
$treeObject = Doctrine::getTable('Menu')->getTree();
$treeObject->setBaseQuery($q);
$this->roots = $treeObject->fetchRoots();
}
Nous nous retrouvons maintenant avec uniquement 3 requêtes, et surtout avec un nombre de requêtes qui n’augmentera plus avec le nombre de descendants du nœud racine.
Conclusion
Même si on a tendance à l’oublier, il faut toujours garder un œil sur le nombre de requêtes que Doctrine peut générer. Comme il est répété dans la documentation de Doctrine, il vaut mieux toujours créer sa requête « à la main » en ne récupérant que les informations dont on a besoin, et surtout toutes les informations dont on a besoin, au risque que Doctrine aille les chercher via des requêtes supplémentaires.

C’est exactement ce que je cherchais à mettre en application. Merci pour cet excellent article