Balises dynamiques, trucage d’arguments et découpage de l’AST de SPIP

Cet article est la traduction libre d’un article de Thomas Sutton publié sur son blog passingcuriosity en mars 2009 (article original).

Attention : depuis la publication de cet article, le code de SPIP a intégré la solution proposée par T. Sutton. Se reporter à la note en fin d’article.

Il arrive parfois que les possibilités offertes par les balises dynamiques ne permettent pas de pallier un petit défaut. C’est le cas lorsque vous avez besoin d’accéder à des détails de l’AST -peut-être le nom du fichier de squelette, peut-être quelque chose d’autre- pour générer votre contenu.

Dans cet article je vais décrire l’évidente (mais mauvaise) méthode de transmission des valeurs dans une balise dynamique et une autre technique qui, elle, fonctionne réellement.

Contrairement aux balises statiques -qui sont mises en œuvre par une seule fonction- les balises dynamiques sont composées d’un ensemble de trois fonctions appelées, si nécessaire, par le moteur de SPIP.

La première de ces trois fonctions (identique à la fonction unique des balises statiques) est appelée balise_TRUC() et a pour but d’appeler les deux autres : balise_TRUC_stat() -qui effectue les calculs statiques nécessaires- et balise_TRUC_dyn() qui effectue les calculs dynamiques et renvoie le contenu final.

Exemple :
Supposons que je crée une balise #HITS, pour afficher le nombre de visites d’un article.
Ma fonction balise_HITS préparera SPIP à appeler les fonctions balise_HITS_xxxxx de la façon suivante :

  • appel de balise_HITS_stat pour déterminer les paramètres statiques de la balise (par exemple : "id_article = 36").
  • appel de balise_HITS_dyn en lui passant les paramètres statiques tels que déterminés par balise_HITS_stat
  • utilisation par balise_HITS_dyn des paramètres statiques pour interroger la base de données, etc et produire la sortie appropriée.

Le code à mettre en œuvre pour la balise #HITS pourrait ressembler à :
<?php

function balise_HITS ($p$nom 'HITS') {
  
$args = array('id_article');
  return  
calculer_balise_dynamique($p ,$nom ,$args);
}

function 
balise_HITS_stat($args$filtres) {
  return array(
$args[0]);
}

function 
balise_HITS_dyn($id_article) {
  
$maintenant date ('c');
  return 
"L'article $id_article a reçu 1 000 000 visites
          à la date du 
$maintenant !";
}

?>

Mais ceci ne fonctionnera plus si je veux, par exemple, associer des détails de mon squelette aux paramètres statiques : à l’instant où je détermine les paramètres, je n’ai plus accès au nœud de l’arbre de syntaxe abstraite passé à balise_BIDULE ; tout ce que j’ai reçu à ce moment là, est un tableau d’arguments à passer à la balise et un tableau de filtres à lui appliquer. [1]

La solution évidente (mais fausse)

La solution évidente serait d’ajouter ces valeurs au tableau $args que je passe à calculer_balise_dynamique().
Hélas, cela ne fonctionnera pas parce que $args n’est pas (en dépit de son nom) un tableau d’arguments. C’est, en fait, un tableau des noms des arguments que SPIP est en train de récupérer automatiquement avant de les passer lors de l’appel de balise_xxxxx_stat().

Ajouter le nom du fichier squelette ("squelettes/truc.html" par exemple) à $args demanderait à SPIP de rechercher une variable appelée squelettes/truc.html dans le contexte à l’intérieur duquel la balise est utilisée et de la transmettre à la fonction suivante...
Inutile de dire que cela ne fonctionne pas.

La bonne solution

La bonne solution à ce problème est de prendre des dispositions pour que SPIP ajoute les valeurs que vous voulez au tableau $arguments (ou peut-être au tableau $filtres, mais ce n’est pas forcément une bonne idée).
Ce tableau contient les valeurs que j’ai demandées dans l’appel à calculer_balise_dynamique en plus des valeurs des paramètres passés dans la balise dans le squelette.

« Si je ne peux pas utiliser la route principale, alors je prendrais un chemin de traverse » - j’ai besoin d’ajouter un autre paramètre « truqué » au nœud de l’AST avant d’appeler calculer_balise_dynamique (Oui, je suis d’accord : il s’agit d’une drôle de façon d’arriver à ses fins, mais ça se passe comme çà chez SPIP-land).

Il y a deux parties dans l’AST de SPIP qui sont pertinentes ici : le nœud « Champ » qui représente les balises, et le nœud « Texte » qui représente les chaines (entre autres).
Le $p qui est passé à mes fonctions balise_xxxxx est une instance de la class « Champ » et je vais ajouter une nouvelle instance à la class « Texte » qui sera mon nouveau paramètre « truqué ».

Créer le nouvel objet est assez simple, il suffit de le construire et de définir ses attributs type et texte. L’exemple suivant ajoute ainsi un nouveau paramètre contenant le nom du fichier squelette :
<?php

function balise_TEMPLATE ($p$nom 'TEMPLATE') {
  
$file $p->descr['sourcefile'];
  
// Créer le nouvel objet 
  
$t = new Texte;
  
$t->type 'texte'// (pas vraiment utile : 'texte' est le type par défaut-ndt)
  
$t->texte $file;
  
// S'assurer que $p-param est un tableau
  // (aux bonnes dimensions) 
  
if (!is_array($p->param)) {
    
$p->param = array(array(0=>NULL));
  }
  
// Ajouter l'objet aux paramètres de la balise
  
$p->param[0][] = array($t);
  
// Appel de la balise dynamique
  
$args = array();
  return 
calculer_balise_dynamique($p$nom$args);
}

?>

  • En premier lieu, ce code récupère le nom du fichier squelette depuis le nœud de l’AST pour la balise en cours de traitement.
  • Puis il crée un nouvel objet « Texte » avec ce nom de fichier comme valeur. Il veille à ce que l’attribut $p->param de l’objet « Champ » soit un tableau (et oui, il semble bien qu’il commence avec un NULL de sorte que nous pouvons prétendre que les tableaux démarrent à l’index 1), puis lui ajoute le nouvel objet.
  • Il ne reste plus qu’à appeler calculer_balise_dynamique comme d’habitude.
  • Ceci fait, la valeur du nouveau paramètre truqué sera évaluée et transmise, dans le tableau $args, lors de l’appel de balise_TEMPLATE_stat() puis (si c’est mon choix) lors de l’appel de balise_TEMPLATE_dyn().

Conclusion

Cette technique me semble encore un peu bizarre, mais c’est la seule façon que je vois de mettre en œuvre cet effet sans introduire de variables globales.

À mon avis, calculer_balise_dynamique devraient pouvoir prendre un tableau de valeurs comme quatrième argument facultatif, mais le besoin de faire ce genre de chose est probablement assez rare (bien que je l’ai vu dans le code pour une ou deux balises). [1]

Mais si cette technique était la « Right Way™ » pour passer des valeurs supplémentaires, alors il serait vraiment nécessaire d’avoir une fonction auxiliaire comme interprete_argument_balise plutôt que de pourrir l’AST pour chaque balise qui en a besoin.

Notes

[1Important
note du traducteur :

Depuis la publication de cet article, les balises dynamiques peuvent utiliser désormais un second argument : un tableau de valeurs qui leur permet , si besoin est, de manipuler à l’exécution des valeurs qui ne sont connues que durant la compilation.
Ce tableau est composé d’abord de cinq valeurs issues du contexte de compilation (nom du squelette, nom du fichier compilé, nom de la boucle éventuelle où figure la balise, numéro de ligne, langue) suivies éventuellement des éléments du tableau optionnel fourni comme quatrième argument à la fonction calculer_balise_dynamique().