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 leave si 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 enter si 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 de toText() pour tous ses enfants. Si un enfant n'est pas convertible en texte (parce qu'il contient un PrintNode, par exemple), elle retourne null.
  • 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 enter cherche les nœuds Html\ElementNode nommés img.
  • Il parcourt les attributs existants ($node->attributes->children) pour vérifier si un attribut loading est déjà présent.
  • S'il n'en trouve pas, il crée un nouveau Html\AttributeNode représentant loading="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 enter recherche les FunctionCallNode.
  • Si le nom de la fonction ($node->name) est un NameNode statique, 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 de Extension::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::DontTraverseChildren et NodeTraverser::StopTraversal chaque 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 si Latte\Compiler\NodeHelpers propose une méthode adaptée avant d'écrire votre propre logique avec NodeTraverser. 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 une Latte\SecurityViolationException pour les problèmes de sécurité) avec un message clair et l'objet Position pertinent (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.

version: 3.x