Création de passes de compilation
Les passes de compilation offrent un mécanisme puissant pour analyser et modifier les templates Latte après leur analyse en arbre syntaxique abstrait (AST) et avant la génération du code PHP final. Cela permet une manipulation avancée des templates, des optimisations, des contrôles de sécurité (comme le Sandbox) et la collecte d'informations sur les templates. Ce guide vous accompagne dans la création de vos propres passes de compilation.
Qu'est-ce qu'une passe de compilation ?
Pour comprendre le rôle des passes de compilation, voyez le processus de compilation de Latte. Comme vous le constatez, les passes de compilation interviennent à une étape charnière, ce qui permet une intervention en profondeur entre l'analyse initiale et la production du code final.
Fondamentalement, une passe de compilation est simplement un callable PHP (fonction, méthode statique ou méthode d'instance)
qui accepte un argument : le nœud racine de l'AST du template, toujours une instance de
Latte\Compiler\Nodes\TemplateNode.
L'objectif principal d'une passe de compilation est généralement l'un des deux suivants, ou les deux :
- Analyse : parcourir l'AST et recueillir des informations sur le template (trouver tous les blocs définis, vérifier l'usage de certaines balises, s'assurer que des contraintes de sécurité sont respectées).
- Modification : changer la structure de l'AST ou les propriétés des nœuds (ajouter automatiquement des attributs HTML, optimiser certaines combinaisons de balises, remplacer des balises obsolètes par de nouvelles, implémenter les règles du sandbox).
Enregistrement
Les passes de compilation s'enregistrent via la méthode getPasses() d'une Extension. Cette méthode retourne un tableau associatif dont
les clés sont les noms uniques des passes (utilisés en interne et pour l'ordonnancement) et les valeurs les callables PHP qui
implémentent la logique de la passe.
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Extension;
class MyExtension extends Extension
{
public function getPasses(): array
{
return [
'modificationPass' => $this->modifyTemplateAst(...),
// ... autres passes ...
];
}
public function modifyTemplateAst(TemplateNode $templateNode): void
{
// Implémentation...
}
}
Les passes enregistrées par les extensions du cœur de Latte et par vos extensions personnalisées s'exécutent
séquentiellement. L'ordre peut compter, en particulier si une passe s'appuie sur les résultats ou les modifications d'une autre.
Latte fournit un mécanisme d'aide pour contrôler cet ordre si besoin ; voyez la documentation de Extension::getPasses() pour les détails.
Exemple d'AST
Pour vous donner une meilleure idée de l'AST, voici un échantillon. Voici le template source :
{foreach $category->getItems() as $item}
<li>{$item->name|upper}</li>
{else}
no items found
{/foreach}
Et voici sa représentation sous forme d'AST :
Latte\Compiler\Nodes\TemplateNode(
Latte\Compiler\Nodes\FragmentNode(
- Latte\Essential\Nodes\ForeachNode(
expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode(
object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category')
name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems')
)
value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item')
content: Latte\Compiler\Nodes\FragmentNode(
- Latte\Compiler\Nodes\TextNode(' ')
- Latte\Compiler\Nodes\Html\ElementNode('li')(
content: Latte\Compiler\Nodes\PrintNode(
expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode(
object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item')
name: Latte\Compiler\Nodes\Php\IdentifierNode('name')
)
modifier: Latte\Compiler\Nodes\Php\ModifierNode(
filters:
- Latte\Compiler\Nodes\Php\FilterNode('upper')
)
)
)
)
else: Latte\Compiler\Nodes\FragmentNode(
- Latte\Compiler\Nodes\TextNode('no items found')
)
)
)
)
Parcourir l'AST avec NodeTraverser
Écrire manuellement des fonctions récursives pour parcourir la structure complexe de l'AST est fastidieux et sujet aux erreurs. Latte fournit un outil dédié à cet effet : Latte\Compiler\NodeTraverser. Cette classe implémente le patron de conception Visiteur, ce qui rend le parcours de l'AST systématique et facile à gérer.
L'utilisation de base consiste à créer une instance de NodeTraverser et à appeler sa méthode
traverse(), en lui passant le nœud racine de l'AST et un ou deux callables “visiteurs” :
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes;
(new NodeTraverser)->traverse(
$templateNode,
// Visiteur 'enter' : appelé à l'entrée d'un nœud (avant ses enfants)
enter: function (Node $node) {
echo "Entering node of type: " . $node::class . "\n";
// Ici, vous pouvez examiner le nœud
if ($node instanceof Nodes\TextNode) {
// echo "Found text: " . $node->content . "\n";
}
},
// Visiteur 'leave' : appelé à la sortie d'un nœud (après ses enfants)
leave: function (Node $node) {
echo "Leaving node of type: " . $node::class . "\n";
// Ici, vous pouvez agir une fois les enfants traités
},
);
Vous pouvez fournir uniquement le visiteur enter, uniquement le visiteur leave, ou les deux, selon
vos besoins.
enter(Node $node) : cette fonction est exécutée pour chaque nœud avant que le traverseur ne
visite l'un de ses enfants. Elle est utile pour :
- collecter des informations lors de la descente dans l'arbre ;
- prendre des décisions avant de traiter les enfants (comme décider de les ignorer, voir Optimisation du parcours) ;
- éventuellement modifier le nœud avant que ses enfants ne soient visités (plus rare).
leave(Node $node) : cette fonction est exécutée pour chaque nœud après que tous ses enfants (et
leurs sous-arbres entiers) ont été entièrement visités (entrée et sortie). C'est l'endroit le plus courant pour :
- remplacer un nœud une fois ses enfants traités ;
- supprimer des nœuds de l'AST ;
- agréger les informations recueillies dans tout le sous-arbre.
Les visiteurs enter et leave peuvent tous deux retourner une valeur pour influencer le déroulement
du parcours. Retourner null (ou rien) poursuit le parcours normalement, retourner une instance de Node
remplace le nœud courant, et retourner des constantes spéciales comme NodeTraverser::RemoveNode ou
NodeTraverser::StopTraversal modifie le flux, comme l'expliquent les sections suivantes.
Comment fonctionne le parcours
NodeTraverser utilise en interne la méthode getIterator() que chaque classe Node doit
implémenter (comme évoqué dans Création de balises
personnalisées). Il itère sur les enfants fournis par getIterator(), appelle récursivement
traverse() sur eux et s'assure que les visiteurs enter et leave sont appelés dans le bon
ordre, en profondeur d'abord, pour chaque nœud de l'arbre accessible via les itérateurs. Cela souligne une fois de plus pourquoi
un getIterator() correctement implémenté dans vos nœuds de balises personnalisées est absolument indispensable au
bon fonctionnement des passes de compilation.
Écrivons une passe simple qui compte combien de fois la balise {do} (représentée par
Latte\Essential\Nodes\DoNode) est utilisée dans le template.
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Essential\Nodes\DoNode;
function countDoTags(TemplateNode $templateNode): void
{
$count = 0;
(new NodeTraverser)->traverse(
$templateNode,
enter: function (Node $node) use (&$count): void {
if ($node instanceof DoNode) {
$count++;
}
},
// le visiteur 'leave' n'est pas nécessaire ici
);
echo "Found {do} tag $count times.\n";
}
$latte = new Latte\Engine;
$ast = $latte->parse($templateSource);
countDoTags($ast);
Dans cet exemple, seul le visiteur enter était nécessaire pour contrôler le type de chaque nœud
rencontré.
Voyons maintenant comment utiliser ces visiteurs pour modifier réellement l'AST.
Modification de l'AST
L'un des principaux usages des passes de compilation est de modifier l'arbre syntaxique abstrait. Cela permet des
transformations puissantes, des optimisations ou l'application de règles directement sur la structure du template, avant que le
code PHP ne soit généré. NodeTraverser offre plusieurs moyens d'y parvenir depuis les visiteurs enter
et leave.
Remarque importante : modifier l'AST demande de la prudence. Des changements incorrects, comme supprimer des nœuds essentiels ou remplacer un nœud par un type incompatible, peuvent provoquer des erreurs à la génération du code ou un comportement inattendu à l'exécution. Testez toujours soigneusement vos passes de modification.
Modification des propriétés des nœuds
La façon la plus simple de modifier l'arbre est de changer directement les propriétés publiques des nœuds rencontrés pendant le parcours. Tous les nœuds stockent leurs arguments analysés, leur contenu ou leurs attributs dans des propriétés publiques.
Exemple : créons une passe qui trouve tous les nœuds de texte statique (TextNode, représentant du HTML
ou du texte hors des balises Latte) et convertit leur contenu en majuscules directement dans l'AST.
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Compiler\Nodes\TextNode;
function uppercaseStaticText(TemplateNode $templateNode): void
{
(new NodeTraverser)->traverse(
$templateNode,
// Nous pouvons utiliser 'enter', car TextNode n'a pas d'enfants à traiter d'abord
enter: function (Node $node) {
// Ce nœud est-il un bloc de texte statique ?
if ($node instanceof TextNode) {
// Oui ! Modifions directement sa propriété publique 'content'.
$node->content = mb_strtoupper(html_entity_decode($node->content));
}
// Rien à retourner ; la modification se fait sur place.
},
);
}
Dans cet exemple, le visiteur enter vérifie si le $node courant est un TextNode. Si
c'est le cas, nous mettons directement à jour sa propriété publique $content avec mb_strtoupper().
Cela change directement le contenu du texte statique stocké dans l'AST avant la génération du code PHP. Comme nous
modifions l'objet directement, le visiteur n'a rien à retourner.
Effet : si le template contenait <p>Hello</p>{= $var }<span>World</span>, après cette
passe l'AST représentera quelque chose comme <p>HELLO</p>{= $var }<span>WORLD</span>. Cela
n'affecte PAS le contenu de $var.
Remplacement des nœuds
Une technique de modification plus puissante consiste à remplacer complètement un nœud par un autre. Cela se fait en
retournant la nouvelle instance de Node depuis le visiteur enter ou leave. Le
NodeTraverser substituera alors le nœud d'origine par celui retourné dans la structure du nœud parent.
Exemple : créons une passe qui trouve tous les usages de la constante PHP_VERSION (représentée par
ConstantFetchNode) et les remplace directement par une chaîne littérale (StringNode) contenant la
version réelle de PHP détectée pendant la compilation. C'est une forme d'optimisation à la compilation.
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode;
use Latte\Compiler\Nodes\Php\Scalar\StringNode;
function inlinePhpVersion(TemplateNode $templateNode): void
{
(new NodeTraverser)->traverse(
$templateNode,
// 'leave' est souvent utilisé pour les remplacements, ce qui garantit que
// les enfants (s'il y en a) sont traités d'abord ; 'enter' marcherait aussi ici.
leave: function (Node $node) {
// Est-ce un accès à une constante nommée 'PHP_VERSION' ?
if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') {
// Créons un nouveau StringNode contenant la version actuelle de PHP
$newNode = new StringNode(PHP_VERSION);
// Facultatif, mais recommandé : copier l'information de position
$newNode->position = $node->position;
// Retournons le nouveau StringNode. Le traverseur remplacera
// le ConstantFetchNode d'origine par ce $newNode.
return $newNode;
}
// Si nous ne retournons pas de Node, le $node d'origine est conservé.
},
);
}
Ici, le visiteur leave identifie le ConstantFetchNode correspondant à PHP_VERSION. Il
crée ensuite un StringNode entièrement nouveau contenant la valeur de la constante PHP_VERSION au
moment de la compilation. En retournant ce $newNode, il indique au traverseur de remplacer le
ConstantFetchNode d'origine dans l'AST.
Effet : si le template contenait {= PHP_VERSION } et que la compilation tourne sur PHP 8.2.1, l'AST après cette
passe représentera en pratique {= '8.2.1' }.
Choisir enter ou leave pour un remplacement :
- Utilisez
leavesi la création du nouveau nœud dépend du traitement des enfants de l'ancien, ou si vous voulez simplement garantir que les enfants sont visités avant le remplacement (pratique courante). - Utilisez
entersi vous voulez remplacer un nœud avant même que ses enfants ne soient visités.
Suppression des nœuds
Vous pouvez supprimer entièrement un nœud de l'AST en retournant la constante spéciale
NodeTraverser::RemoveNode depuis un visiteur.
Exemple : supprimons de la sortie tous les commentaires HTML (<!-- ... -->). Les commentaires Latte
{* ... *} ne peuvent pas être visés de cette façon, car le parser jette leur contenu et les remplace par un
NopNode vide plutôt que par un nœud de commentaire dédié ; les commentaires HTML, en revanche, sont conservés
comme nœuds Html\CommentNode, nous pouvons donc les retirer ici.
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Compiler\Nodes\Html\CommentNode;
function removeHtmlComments(TemplateNode $templateNode): void
{
(new NodeTraverser)->traverse(
$templateNode,
// 'enter' convient ici, car nous n'avons pas besoin des enfants pour supprimer un commentaire
enter: function (Node $node) {
if ($node instanceof CommentNode) {
// Indiquons au traverseur de retirer ce nœud de l'AST
return NodeTraverser::RemoveNode;
}
},
);
}
Attention : utilisez RemoveNode avec précaution. Supprimer un nœud qui porte un contenu essentiel ou qui
influe sur la structure (comme le nœud de contenu d'une boucle) peut casser les templates ou produire du code invalide. C'est le
plus sûr pour des nœuds réellement facultatifs ou autonomes (commentaires, balises de débogage) ou pour des nœuds structurels
vides (un FragmentNode vide peut par exemple être supprimé sans risque dans certains contextes par une passe de
nettoyage).
Ces trois méthodes – modifier les propriétés, remplacer des nœuds et supprimer des nœuds – constituent les outils fondamentaux pour manipuler l'AST dans vos passes de compilation.
Optimisation du parcours
Les AST de templates peuvent devenir assez volumineux, avec parfois des milliers de nœuds. Parcourir chaque nœud peut être
inutile et peser sur le temps de compilation si votre passe ne s'intéresse qu'à certaines parties de l'arbre.
NodeTraverser propose des moyens d'optimiser le parcours :
Ignorer les enfants
Si vous savez qu'une fois un certain type de nœud rencontré, aucun de ses descendants ne peut contenir les nœuds que vous
cherchez, vous pouvez dire au traverseur de ne pas visiter ses enfants. Cela se fait en retournant la constante
NodeTraverser::DontTraverseChildren depuis le visiteur enter. Vous élaguez ainsi des branches
entières du parcours, ce qui peut faire gagner un temps notable, surtout dans des templates aux expressions PHP complexes à
l'intérieur des balises.
Arrêter le parcours
Si votre passe n'a besoin de trouver que la première occurrence de quelque chose (un type de nœud donné, une
condition remplie), vous pouvez arrêter complètement le parcours dès que vous l'avez trouvée. Il suffit de retourner la
constante NodeTraverser::StopTraversal depuis le visiteur enter ou leave. La méthode
traverse() cesse alors de visiter d'autres nœuds. C'est très efficace si vous n'avez besoin que de la première
correspondance dans un arbre potentiellement très grand.
L'utile classe NodeHelpers
Là où NodeTraverser offre un contrôle fin, Latte fournit aussi une classe utilitaire pratique, Latte\Compiler\NodeHelpers, qui enveloppe
NodeTraverser pour plusieurs tâches courantes de recherche et d'analyse, souvent avec beaucoup moins de code
répétitif.
find (Node $startNode, callable $filter): array
Cette méthode statique trouve tous les nœuds du sous-arbre commençant à $startNode (inclus) qui
satisfont le callback $filter. Elle retourne un tableau des nœuds correspondants.
Exemple : trouver tous les nœuds de variables (VariableNode) dans tout le template.
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\Php\Expression\VariableNode;
use Latte\Compiler\Nodes\TemplateNode;
function findAllVariables(TemplateNode $templateNode): array
{
return NodeHelpers::find(
$templateNode,
fn($node) => $node instanceof VariableNode,
);
}
findFirst (Node $startNode, callable $filter): ?Node
Semblable à find, mais arrête le parcours dès qu'elle a trouvé le premier nœud satisfaisant le
callback $filter. Elle retourne l'objet Node trouvé, ou null si aucun nœud ne correspond.
C'est essentiellement une enveloppe pratique autour de NodeTraverser::StopTraversal.
Exemple : trouver le nœud {parameters}.
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Essential\Nodes\ParametersNode;
function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode
{
return NodeHelpers::findFirst(
$templateNode->head, // Chercher seulement dans la section head, par efficacité
fn($node) => $node instanceof ParametersNode,
);
}
clone (Latte\Compiler\Node $node): Node
Cette méthode statique crée une copie profonde d'un nœud et de tout son sous-arbre. C'est utile quand vous devez dupliquer une branche de l'AST, par exemple pour insérer une copie modifiée d'un nœud tout en laissant l'original intact.
use Latte\Compiler\NodeHelpers;
$copy = NodeHelpers::clone($node);
toValue (ExpressionNode $node, bool $constants = false): mixed
Cette méthode statique tente d'évaluer un ExpressionNode à la compilation et de retourner la valeur PHP
correspondante. Elle ne fonctionne de façon fiable que pour les nœuds littéraux simples (StringNode,
IntegerNode, FloatNode, BooleanNode, NullNode) et pour les instances de
ArrayNode ne contenant que de tels éléments évaluables.
Si $constants vaut true, elle tentera aussi de résoudre ConstantFetchNode et
ClassConstantFetchNode en vérifiant defined() et en utilisant constant().
Si le nœud contient des variables, des appels de fonctions ou d'autres éléments dynamiques, il ne peut pas être évalué à
la compilation et la méthode lèvera une InvalidArgumentException.
Cas d'usage : obtenir la valeur statique d'un argument de balise pendant la compilation pour prendre des décisions à la compilation.
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\Php\ExpressionNode;
function getStaticStringArgument(ExpressionNode $argumentNode): ?string
{
try {
$value = NodeHelpers::toValue($argumentNode);
return is_string($value) ? $value : null;
} catch (\InvalidArgumentException $e) {
// L'argument n'était pas une chaîne littérale statique
return null;
}
}
toText (?Node $node): ?string
Cette méthode statique est utile pour extraire le contenu textuel brut de nœuds simples. Elle fonctionne principalement avec :
TextNode: retourne son$content.FragmentNode: concatène le résultat detoText()pour tous ses enfants. Si un enfant n'est pas convertible en texte (parce qu'il contient unPrintNode, par exemple), elle retournenull.NopNode: retourne une chaîne vide.- Autres types de nœuds : retourne
null.
Cas d'usage : obtenir le contenu textuel statique de la valeur d'un attribut HTML ou d'un élément HTML simple, pour l'analyser dans une passe de compilation.
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\Html\AttributeNode;
function getStaticAttributeValue(AttributeNode $attr): ?string
{
// $attr->value est en général un AreaNode (comme FragmentNode ou TextNode)
return NodeHelpers::toText($attr->value);
}
// Exemple d'utilisation dans une passe :
// if ($node instanceof Html\ElementNode && $node->name === 'meta') {
// $nameAttrValue = $node->getAttribute('name');
// if ($nameAttrValue === 'description') { ... }
// }
NodeHelpers peut simplifier vos passes de compilation en offrant des solutions toutes faites pour les tâches
courantes de parcours et d'analyse de l'AST.
Exemples pratiques
Appliquons les notions de parcours et de modification de l'AST à quelques problèmes concrets. Ces exemples illustrent des motifs courants dans les passes de compilation.
Ajout automatique de loading="lazy" à
<img>
Les navigateurs modernes prennent en charge le chargement différé natif des images via l'attribut
loading="lazy". Créons une passe qui ajoute automatiquement cet attribut à toutes les balises
<img> qui n'ont pas déjà d'attribut loading.
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes;
use Latte\Compiler\Nodes\Html;
function addLazyLoading(Nodes\TemplateNode $templateNode): void
{
(new NodeTraverser)->traverse(
$templateNode,
// Nous pouvons utiliser 'enter', car nous modifions le nœud directement
// et cette décision ne dépend pas des enfants.
enter: function (Node $node) {
// Est-ce un élément HTML nommé 'img' ?
if ($node instanceof Html\ElementNode && $node->name === 'img') {
// L'attribut 'loading' existe-t-il déjà (sans distinction de casse) ?
foreach ($node->attributes->children as $attrNode) {
if ($attrNode instanceof Html\AttributeNode
&& $attrNode->name instanceof Nodes\TextNode // Nom d'attribut statique
&& strtolower($attrNode->name->content) === 'loading'
) {
return; // Déjà présent, ne rien faire
}
}
// Ajouter une espace devant si les attributs ne sont pas vides
if ($node->attributes->children) {
$node->attributes->children[] = new Nodes\TextNode(' ');
}
// Créer le nouveau nœud d'attribut : loading="lazy"
$node->attributes->children[] = new Html\AttributeNode(
name: new Nodes\TextNode('loading'),
value: new Nodes\TextNode('lazy'),
quote: '"',
);
// Modification faite sur place, rien à retourner.
}
},
);
}
Explication :
- Le visiteur
entercherche les nœudsHtml\ElementNodenommésimg. - Il parcourt les attributs existants (
$node->attributes->children) pour vérifier si un attributloadingest déjà présent. - S'il n'en trouve pas, il crée un nouveau
Html\AttributeNodereprésentantloading="lazy"et l'ajoute (précédé d'une espace si nécessaire).
Vérification des appels de fonctions
Les passes de compilation sont le fondement du Sandbox de Latte. Le vrai Sandbox est sophistiqué, mais nous pouvons montrer le principe de base du contrôle des appels de fonctions interdites.
Objectif : empêcher l'usage de la fonction potentiellement dangereuse shell_exec dans les expressions du
template.
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes;
use Latte\Compiler\Nodes\Php;
use Latte\SecurityViolationException;
function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void
{
$forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Liste simple
(new NodeTraverser)->traverse(
$templateNode,
enter: function (Node $node) use ($forbiddenFunctions) {
// Est-ce un nœud d'appel de fonction direct ?
if ($node instanceof Php\Expression\FunctionCallNode
&& $node->name instanceof Php\NameNode
&& isset($forbiddenFunctions[strtolower((string) $node->name)])
) {
throw new SecurityViolationException(
"Function {$node->name}() is not allowed.",
$node->position,
);
}
},
);
}
Explication :
- Nous définissons une liste de noms de fonctions interdites.
- Le visiteur
enterrecherche lesFunctionCallNode. - Si le nom de la fonction (
$node->name) est unNameNodestatique, nous comparons sa représentation en minuscules à notre liste d'interdits. - Si une fonction interdite est trouvée, nous levons une
Latte\SecurityViolationException, qui signale clairement une violation d'une règle de sécurité et interrompt la compilation.
Ces exemples montrent comment les passes de compilation, à l'aide de NodeTraverser, peuvent servir à l'analyse,
aux modifications automatiques et à l'application de contraintes de sécurité, en interagissant directement avec la structure de
l'AST du template.
Bonnes pratiques
Quand vous écrivez des passes de compilation, gardez ces principes en tête pour créer des extensions robustes, maintenables et efficaces :
- L'ordre compte : soyez attentif à l'ordre d'exécution des passes. Si votre passe s'appuie sur une structure d'AST
créée par une autre passe (passes du cœur de Latte ou autre passe personnalisée), ou si d'autres passes peuvent dépendre de
vos modifications, utilisez le mécanisme d'ordonnancement fourni par
Extension::getPasses()pour définir les dépendances (before/after). Voyez la documentation deExtension::getPasses()pour les détails. - Responsabilité unique : visez des passes qui accomplissent une seule tâche bien définie. Pour des transformations complexes, envisagez de découper la logique en plusieurs passes, par exemple une pour l'analyse et une autre pour la modification fondée sur les résultats de l'analyse. Cela améliore la clarté et la testabilité.
- Performance : n'oubliez pas que les passes de compilation allongent le temps de compilation des templates (même si
cela ne se produit généralement qu'une fois, jusqu'à la modification du template). Évitez si possible les opérations
coûteuses en calcul dans vos passes. Tirez parti des optimisations de parcours comme
NodeTraverser::DontTraverseChildrenetNodeTraverser::StopTraversalchaque fois que vous savez que certaines parties de l'AST ne vous intéressent pas. - Utilisez
NodeHelpers: pour les tâches courantes, comme trouver des nœuds précis ou évaluer statiquement des expressions simples, regardez siLatte\Compiler\NodeHelperspropose une méthode adaptée avant d'écrire votre propre logique avecNodeTraverser. Cela fait gagner du temps et réduit le code répétitif. - Gestion des erreurs : si votre passe détecte une erreur ou un état invalide dans l'AST du template, levez une
Latte\CompileException(ou uneLatte\SecurityViolationExceptionpour les problèmes de sécurité) avec un message clair et l'objetPositionpertinent (en général$node->position). Cela donne un retour utile au développeur du template. - Idempotence (si possible) : idéalement, exécuter votre passe plusieurs fois sur le même AST devrait produire le même résultat qu'une seule exécution. Ce n'est pas toujours réalisable, mais cela simplifie le débogage et le raisonnement sur les interactions entre passes. Veillez par exemple à ce que votre passe de modification vérifie si la modification a déjà été appliquée avant de l'appliquer de nouveau.
En respectant ces pratiques, vous pouvez tirer pleinement parti des passes de compilation pour étendre les capacités de Latte de façon puissante et fiable, et contribuer à un traitement des templates plus sûr, mieux optimisé ou plus riche en fonctionnalités.