Amoureux de la nature, clickez-ici (ou scannez le QR code) obtenir une version PDF.
Scannez ce QR code pour
lire cet article sur votre mobile.
La question posée ici est : "comment peut-on mettre le contenu d'un noeud, ou d'une liste de noeuds, dans un noeud principal"... Le cas typique serait une page, avec un texte d'introduction (le noeud maître), suivi d'une liste de noeuds (dits "esclaves", résumés ou complets).
Noeuds et NID

Le stockage du noeud est assuré conjointement par la table node et node_revisions. La première contient les informations fondamentales du noeud (nid, statut, langue, et surtout type), et la seconde prend en charge les données du noeud par révision (titre, corps, etc.). La valeur du champ vid (pour version ID) de la table node pointe sur la révision en cours dans la table node_revision.
Chargement d'un noeud

Ainsi comme un type de contenu peut potentiellement se ramifier sur de nombreuses tables, il n'est pas possible de créer une requêtes SQL universelle qui remonterait toutes les données d'un coup. Heureusement Drupal nous fournit la fonction node_load qui effectue ce travail sans douleur à partir du nid du noeud.

Nous allons donc utiliser cette fameuse fonction node_load pour charger le noeud de nid 41 et toutes ses éventuelles dépendances :
$noeud_esclave=node_load(41);
Pas bien sorcier n'est-ce pas ? La fonction va ainsi charger toutes les information de la dernière révision d'un noeud, et en faire un objet PHP que nous stockons dans $noeud_esclave. Ainsi $noeud_esclave->title nous renvoie le titre du noeud, $noeud_esclave->body sont corps (tel qu'il a été saisi, et donc sans format d'entrée appliqué), etc.
Format XHTML d'un noeud
$node n'est donc pour l'instant pas formaté, il va nous falloir un peu plus pour le transformer en une version XHTML exploitable. C'est cette fois le rôle de la fonction node_view
print node_view($noeud_esclave, true, false, true);
Le premier paramètre est l'objet $noeud_esclave que nous avons obtenu de la fonction node_load. Le second paramètre true indique que nous désirons obtenir la version résumée du noeud (le "teaser"). Le troisième paramètre à false prévient la fonction que nous ne voulons pas afficher ce contenu en tant que page. Enfin, le dernier paramètre à true demande à ce que les liens additionnels soient ajoutés au contenu.
utilisation de hook_nodeapi
Nous avons maintenant la théorie, reste à voir comment concrètement intégrer cela au noeud "maître".
La meilleur des approches reste (évidemment
celle du module. Il vous suffit pour cela de créer un module basique et d'exploiter le hook_nodeapi. Comme son nom le suggère, ce hook permet d'intercepter chacune des étapes de la vie d'un noeud, y compris son affichage.

Comme nous le disions, le hook node_api est invoqué à chaque opération (ajout, mise à jour, suppression, impression, etc.) sur un noeud. Le noeud en question est passé comme premier paramètre du hook ($node). Notez au passage qu'il s'agit là d'un passage par référence induisant que nous pouvons modifier le contenu de noeud. Le second paramètre, $op indique l'opération effectuée. Ici nous nous intéressons uniquement à $op=='view' correspondant à l'étape finale de visualisation du noeud.function mon_module_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'view' : {
if ($node->nid == 42 && !$teaser) {
...
}
}
}
}Implémentation de hook_nodeapi
Le paramètre $teaser nous indique quant à lui si l'opération vise un affichage résumé ou complet. Nous ciblons donc notre action sur un noeud de nid 42 en affichage complet (!$teaser).
Agrégation de fragments XHTML
Pour bien comprendre ce qui suit, il faut savoir que lorsque Drupal prépare un noeud pour l'affichage, il lui ajoute un champ content qui est un tableau associatif. Ce champ va contenir des portions de code XHTML qui à eux tous vont constituer le contenu à afficher.
De manière standard, ce champ ne contient qu'une clef body (ou teaser) avec comme valeur un tableau indexé. Ce tableau a deux clefs, #value contenant le résultat formaté XHTML (en fonction du format d'entrée) de $node->body (ou node->teaser), et #weight qui contient la position de ce morceau de XHTML par rapport à d'éventuels autres.
node : Object (
'title' => 'NID:41 - Un noeud esclave',
'body' => 'consectetur adipiscing elit.
Aliquam vel tristique sapien.',
...
'content' => array(
'body' => array (
'#value'=>'consectetur adipiscing elit.Aliquam vel tristique sapien.',
'#weight'=>0))Vue interne de l'objet $node
Pour créer le $content du modèle node.tpl.php, Drupal n'a alors plus qu'à agréger tous les éléments #value dans l'ordre des #weight.
Mise en oeuvre finale
C'est donc cette caractéristique que nous allons exploiter pour ajouter notre propre fragment de XHTML et utilisant le résultat du node_view.
function mon_module_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'view' : {
if ($node->nid == 42 && !$teaser) {
$noeud_esclave = node_load(41);
$node->content['noeud_esclave'] = array (
'#value' => node_view($noeud_esclave, true, false, true),
'#weight' => 1);
}
}
}
}Implémentation finale de hook_nodeapi
Le champ #weight prend pour valeur 1, ce qui le placera au dessous du contenu du noeud maître. Une valeur négative inverserait simplement ce comportement.

Les Worst Practices...
Views et Panels dans un bâteau...

Je n'en fais pas un mystère, je ne suis pas un Viewsonados mais il faut tout de même être honnête, la modulo-mania appliquée peut amener à des trucs assez géants avec Views...
Je suis ainsi tombé (j'ai encore un peu mal
, sur un exemple assez magnifique pondu par une graaaande WebAgency spécialisée Drupal, tout ça... Pour répondre à une partie de notre besoin, leur idée a été d'utiliser le module Panels et de créer un "Page Panel". De là, ils collent une vue "Views" qui ne pond qu'un seul noeud sur la partie haute, et pour celle du bas, une seconde vue "Views" qui cette fois sort une liste de noeuds... Je vous laisse imaginer la simplicité de debuggage et les performances d'un tel assemblage...
Le coup du filtre PHP Code
Il existe plusieurs autres "techniques" que j'ai croisées à droite à gauche (y compris chez moi, à mes débuts sur Drupal) qui ne valent pas, loin de là, celle du nodeapi. La plus courante et bien évidement la pire, consiste à utiliser l'horrible format d'entrée code PHP :
insérer du code dans un noeud":
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel tristique sapien.
<?php
$node=node_load(41);
print node_view($node, true, false, true);
?>
Alors ça, c'est comme croiser les effluves, c'est MAL. A chaque fois que vous vient l'envie de faire un chose pareil, ayez une pensée compatissante pour le pauvre bougre qui passera dans 10 mois sur votre projet en se demandant d'où vient le comportement extraordinaire de ce contenu. En plus, mettre du code dans une base de donnée (car cela revient à cela), au delà d'être très sale, est une calamité lorsque viendra le temps de debugger.
Dans un domaine connexe mais issues ici aussi de la fameuse WebAgency dont je parlais plus haut (mais j'imagine qu'ils ne sont pas les seuls), le pompon de l'utilisation de cette "technique" a été de coller 200 lignes de code PHP dans un noeud, et d'associer le chemin de ce noeud (node/XXX) à un alias (chemin/vers/ma/fonction). Tout cela pour créer une page de fonctionnalités complexe alors qu'il aurait été si simple de mettre ce code dans une fonction, cette fonction dans un module, et d'ajouter le chemin dans un hook_menu...
Du fonctionnel dans la présentation...
L'autre (mauvaise) possibilité consiste à utiliser la fonction phptemplate_preprocess_node d'un thème (généralement dans template.php) et à ajouter le contenu XHTML du noeud esclave à la variable $vars['content'] juste avant qu'elle ne devienne le $content de node.tpl.php.
Alors ça marche mais ce n'est pas terrible. En effet, la beauté du système de thème de Drupal tient justement à ce qu'il permet une stricte séparation entre les fonctionalités d'un côté et leur présentation de l'autre. Ce que nous faisons ici relève du domaine du fonctionnel et n'a donc rien à faire dans le thème. Une bonne manière de savoir si telle ou telle chose va dans un thème ou dans un module, est de ce demander si cette chose doit survivre à un changement brutale de thème. Si c'est le cas, vous n'avez d'autre choix que le module.
De manière plus dégradée, évitez aussi de coller ce type de code, ou tout autre code d'ailleurs, directement dans node.tpl.php. En faisant cela vous commettez la même erreur que plus haut avec en prime le risque de rendre vos modèles totalement illisibles.
D'une manière générale, une petite règle qui vaut la peine d'être rappelée : Drupal est suffisamment bien conçu pour qu'un modèle ne doive contenir que les fonctions PHP if/else et print. Tout le reste doit être dans un module, ou, le cas échéant, dans template.php.
Conclusion
Nous avons vu ici qu'il est relativement simple d'intégrer proprement un noeud dans un autre, avec en prime la possibilité d'exploiter sans soucis le module Printer pour générer une version imprimable ou PDF de l'ensemble.

Encore une fois, merci pour cet article détaillé ! J'ai surtout les sections "bonnes et mauvaises pratiques". Ca permet de progresser.
opi
Ah mais c'est que c'est du vécu
Et il ne faut hésiter à m'en filer d'autres, je suis un grand collectionneur 
Article sympa, comme d'hab je dirais !
Quand tu as une fonction assez générique qui est utilisée à plusieurs niveaux, tu passes par un module "générique" où tu colles un peu tout ce qui est "de haut niveau" ou tu mets ça dans template.php ?
Merci pour le point de vue
Je suppose que dans le serie mauvaises pratiques, tu ne dois pas etre bien fan de Computed field nan ?
Ben c'est comme tout, je ne suis à priori contre rien. Mais tous ces modules peuvent être "a curse and a bless" comme dise nos amis britanniques. Et dés lors qu'il y a "code dans base de données", toute personne sensée devrait voir s'allumer un gros warning. Car ok, on va gagner du temps à la construction par rapport à un module qui ferait strictement la même chose, mais côté maintenance et debuggage c'est juste l'enfer. Du code, tout le monde peut le lire, chercher dedans, comprendre d'où vient telle ou telle fonctionnalité, etc. Là, le projet sur lequel j'interviens en ce moment, l'enfer est que pour chaque page, je dois passer un temps de malade pour comprendre quelle technique a été inventée pour la générer. Je dépense ainsi à l'aise 50% de ma charge à jouer les sherlockhomes. Alors peut-être ce client a eu son site vite et pas cher, mais aujourd'hui cela se paye... Pour te donner une idée, sur ce projet là, un outil comme sitedoc explose avant de pouvoir fournir le moindre rapport...
Pas mal du tout l'article.
Bravo et merci ! Certes ton article me fait rougir... Car il m'a semblé que tu parlais de moi parfois
Mais c'est ainsi : on apprend chaque jour.
Il m'arrive d'utiliser le modules insert_views. Je n'ai jamais pris le temps d'aller lire le code. Est-ce une "pas trop mauvaise chose" ou est-ce également à bannir ?
Personnellement, je suis un utilisateur acharné de views. En revanche, si j'ai été tenté par Panels à une époque, ce que j'ai lu à son sujet a suffit à m'en dissuader.
Merci encore.
En conclusion ce que tu décris dans les 'Worst Practices' sont des non confomités au modèle MVC que l'on recontre souvent dans de nombreux programmes PHP (mélange du code, des données et de la présentation). En gros ne jamais mélanger les données (M) issues de base, fichiers ... des vues (sorties html, pdf, email ...) des controleurs gérants les évenements (code métier). Le simple fait de formatter les données en dehors de la vue rend l'application imcompatible avec le modèle MVC. Attention je ne dis pas non plus que MVC soit la panacée mais cela facilite grandement les choses quant à la flexibilité et lors du debug d'une appli. J'ajouterais q'une batterie de tests permettront de rendre l'application viable dans le temps. (tests de non regression)
Oui c'est un peu cela, même si je n'envisage pas réellement un MVC sans une approche objet, ce qui manque (pour le moins !) à Drupal aujourd'hui. Donc sans être réellement du MVC, il s'agit d'opérer une bonne séparation entre présentation et traitement.
Publier un nouveau commentaire