Passaggi di compilazione
I passaggi di compilazione forniscono un potente meccanismo per analizzare e modificare i template Latte dopo il loro parsing in un albero sintattico astratto (AST) e prima della generazione del codice PHP finale. Ciò consente la manipolazione avanzata dei template, ottimizzazioni, controlli di sicurezza (come la Sandbox) e la raccolta di informazioni sui template. Questa guida vi accompagnerà nella creazione dei vostri passaggi di compilazione personalizzati.
Cos'è un passaggio di compilazione?
Per comprendere il ruolo dei passaggi di compilazione, dai un'occhiata al processo di compilazione di Latte. Come puoi vedere, i passaggi di compilazione operano in una fase chiave, consentendo un intervento profondo tra il parsing iniziale e l'output finale del codice.
Nel suo nucleo, un passaggio di compilazione è semplicemente un oggetto PHP callable (come una funzione, un metodo statico
o un metodo di istanza) che accetta un singolo argomento: il nodo radice dell'AST del template, che è sempre un'istanza di
Latte\Compiler\Nodes\TemplateNode
.
L'obiettivo primario di un passaggio di compilazione è di solito uno o entrambi i seguenti:
- Analisi: Attraversare l'AST e raccogliere informazioni sul template (ad esempio, trovare tutti i blocchi definiti, controllare l'uso di tag specifici, garantire il rispetto di determinati vincoli di sicurezza).
- Modifica: Cambiare la struttura dell'AST o gli attributi dei nodi (ad esempio, aggiungere automaticamente attributi HTML, ottimizzare determinate combinazioni di tag, sostituire tag obsoleti con nuovi, implementare regole di sandbox).
Registrazione
I passaggi di compilazione vengono registrati tramite il metodo dell'estensione getPasses()
. Questo metodo
restituisce un array associativo, dove le chiavi sono nomi univoci dei passaggi (utilizzati internamente e per l'ordinamento) e
i valori sono oggetti PHP callable che implementano la logica del passaggio.
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Extension;
class MyExtension extends Extension
{
public function getPasses(): array
{
return [
'modificationPass' => $this->modifyTemplateAst(...),
// ... altri passaggi ...
];
}
public function modifyTemplateAst(TemplateNode $templateNode): void
{
// Implementazione...
}
}
I passaggi registrati dalle estensioni di base di Latte e dalle tue estensioni personalizzate vengono eseguiti in sequenza.
L'ordine può essere importante, specialmente se un passaggio dipende dai risultati o dalle modifiche di un altro. Latte fornisce
un meccanismo di aiuto per controllare questo ordine, se necessario; vedi la documentazione per Extension::getPasses()
per i dettagli.
Esempio di AST
Per avere un'idea migliore dell'AST, aggiungiamo un esempio. Questo è il template sorgente:
{foreach $category->getItems() as $item}
<li>{$item->name|upper}</li>
{else}
nessun elemento trovato
{/foreach}
E questa è la sua rappresentazione sotto forma di 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\Essential\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('nessun elemento trovato') ) ) ) )
Attraversamento dell'AST con NodeTraverser
Scrivere manualmente funzioni ricorsive per attraversare la complessa struttura dell'AST è noioso e soggetto a errori. Latte fornisce uno strumento speciale per questo scopo: Latte\Compiler\NodeTraverser. Questa classe implementa il design pattern Visitor, grazie al quale l'attraversamento dell'AST diventa sistematico e facilmente gestibile.
L'uso di base prevede la creazione di un'istanza di NodeTraverser
e la chiamata del suo metodo
traverse()
, passando il nodo radice dell'AST e uno o due “visitor” callable:
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes;
(new NodeTraverser)->traverse(
$templateNode,
// visitor 'enter': Chiamato all'ingresso nel nodo (prima dei suoi figli)
enter: function (Node $node) {
echo "Ingresso nel nodo di tipo: " . $node::class . "\n";
// Qui puoi esaminare il nodo
if ($node instanceof Nodes\TextNode) {
// echo "Trovato testo: " . $node->content . "\n";
}
},
// visitor 'leave': Chiamato all'uscita dal nodo (dopo i suoi figli)
leave: function (Node $node) {
echo "Uscita dal nodo di tipo: " . $node::class . "\n";
// Qui puoi eseguire azioni dopo l'elaborazione dei figli
},
);
Puoi fornire solo il visitor enter
, solo il visitor leave
, o entrambi, a seconda delle tue
esigenze.
enter(Node $node)
: Questa funzione viene eseguita per ogni nodo prima che l'attraversatore visiti
qualsiasi figlio di questo nodo. È utile per:
- Raccogliere informazioni durante l'attraversamento dell'albero verso il basso.
- Prendere decisioni prima dell'elaborazione dei figli (come decidere di saltarli, vedi Ottimizzazione dell'attraversamento).
- Potenziali modifiche al nodo prima della visita dei figli (meno comune).
leave(Node $node)
: Questa funzione viene eseguita per ogni nodo dopo che tutti i suoi figli (e
i loro interi sottoalberi) sono stati completamente visitati (sia l'ingresso che l'uscita). È il luogo più comune per:
Entrambi i visitor enter
e leave
possono opzionalmente restituire un valore per influenzare il
processo di attraversamento. Restituire null
(o niente) continua l'attraversamento normalmente, restituire
un'istanza di Node
sostituisce il nodo corrente, e restituire costanti speciali come
NodeTraverser::RemoveNode
o NodeTraverser::StopTraversal
modifica il flusso, come spiegato nelle sezioni
seguenti.
Come funziona l'attraversamento
NodeTraverser
utilizza internamente il metodo getIterator()
, che deve essere implementato da ogni
classe Node
(come discusso in Creazione di tag personalizzati).
Itera sui figli ottenuti tramite getIterator()
, chiama ricorsivamente traverse()
su di essi e assicura
che i visitor enter
e leave
siano chiamati nel corretto ordine depth-first per ogni nodo nell'albero
accessibile tramite gli iteratori. Questo sottolinea ancora una volta perché un getIterator()
implementato
correttamente nei tuoi nodi di tag personalizzati è assolutamente necessario per il corretto funzionamento dei passaggi di
compilazione.
Scriviamo un semplice passaggio che conta quante volte viene utilizzato il tag {do}
nel template (rappresentato da
Latte\Essential\Nodes\DoNode
).
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++;
}
},
// Il visitor 'leave' non è necessario per questo compito
);
echo "Trovato il tag {do} $count volte.\n";
}
$latte = new Latte\Engine;
$ast = $latte->parse($templateSource);
countDoTags($ast);
In questo esempio, avevamo bisogno solo del visitor enter
per controllare il tipo di ogni nodo visitato.
Successivamente, esploreremo come questi visitor modificano effettivamente l'AST.
Modifica dell'AST
Uno degli scopi principali dei passaggi di compilazione è la modifica dell'albero sintattico astratto. Ciò consente potenti
trasformazioni, ottimizzazioni o l'applicazione di regole direttamente sulla struttura del template prima della generazione del
codice PHP. NodeTraverser
fornisce diversi modi per ottenere ciò all'interno dei visitor enter
e
leave
.
Nota importante: La modifica dell'AST richiede cautela. Cambiamenti errati – come la rimozione di nodi essenziali o la sostituzione di un nodo con un tipo incompatibile – possono portare a errori durante la generazione del codice o causare comportamenti inaspettati durante l'esecuzione del programma. Testa sempre a fondo i tuoi passaggi di modifica.
Modifica degli attributi dei nodi
Il modo più semplice per modificare l'albero è cambiare direttamente le proprietà pubbliche dei nodi visitati durante l'attraversamento. Tutti i nodi memorizzano i loro argomenti parsati, contenuto o attributi in proprietà pubbliche.
Esempio: Creiamo un passaggio che trova tutti i nodi di testo statico (TextNode
, che rappresentano HTML
comune o testo al di fuori dei tag Latte) e converte il loro contenuto in maiuscolo direttamente nell'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,
// Possiamo usare 'enter', poiché TextNode non ha figli da elaborare
enter: function (Node $node) {
// Questo nodo è un blocco di testo statico?
if ($node instanceof TextNode) {
// Sì! Modifichiamo direttamente la sua proprietà pubblica 'content'.
$node->content = mb_strtoupper(html_entity_decode($node->content));
}
// Non c'è bisogno di restituire nulla; la modifica viene applicata direttamente.
},
);
}
In questo esempio, il visitor enter
controlla se $node
corrente è di tipo TextNode
. Se
sì, aggiorniamo direttamente la sua proprietà pubblica $content
usando mb_strtoupper()
. Questo cambia
direttamente il contenuto del testo statico memorizzato nell'AST prima della generazione del codice PHP. Poiché
modifichiamo l'oggetto direttamente, non dobbiamo restituire nulla dal visitor.
Effetto: Se il template conteneva <p>Hello</p>{= $var }<span>World</span>
, dopo questo
passaggio l'AST rappresenterà qualcosa come: <p>HELLO</p>{= $var }<span>WORLD</span>
. Questo
NON INFLUENZERÀ il contenuto di $var.
Sostituzione dei nodi
Una tecnica di modifica più potente è la sostituzione completa di un nodo con un altro. Questo si fa restituendo una nuova
istanza di Node
dal visitor enter
o leave
. NodeTraverser
sostituisce
quindi il nodo originale con quello restituito nella struttura del nodo genitore.
Esempio: Creiamo un passaggio che trova tutti gli usi della costante PHP_VERSION
(rappresentata da
ConstantFetchNode
) e li sostituisce direttamente con un letterale stringa (StringNode
) contenente la
versione PHP effettiva rilevata durante la compilazione. Questa è una forma di ottimizzazione al momento della
compilazione.
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' è spesso usato per la sostituzione, assicurando che i figli (se presenti)
// siano elaborati prima, anche se qui funzionerebbe anche 'enter'.
leave: function (Node $node) {
// Questo nodo è un accesso a una costante e il nome della costante è 'PHP_VERSION'?
if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') {
// Creiamo un nuovo StringNode contenente la versione PHP corrente
$newNode = new StringNode(PHP_VERSION);
// Opzionale, ma buona pratica: copiamo le informazioni sulla posizione
$newNode->position = $node->position;
// Restituiamo il nuovo StringNode. Traverser sostituirà
// l'originale ConstantFetchNode con questo $newNode.
return $newNode;
}
// Se non restituiamo un Node, l'originale $node viene mantenuto.
},
);
}
Qui il visitor leave
identifica lo specifico ConstantFetchNode
per PHP_VERSION
. Quindi
crea un StringNode
completamente nuovo contenente il valore della costante PHP_VERSION
al momento
della compilazione. Restituendo questo $newNode
, dice al traverser di sostituire l'originale
ConstantFetchNode
nell'AST.
Effetto: Se il template conteneva {= PHP_VERSION }
e la compilazione viene eseguita su PHP 8.2.1, l'AST dopo
questo passaggio rappresenterà efficacemente {= '8.2.1' }
.
Scelta tra enter
e leave
per la sostituzione:
- Usa
leave
se la creazione del nuovo nodo dipende dai risultati dell'elaborazione dei figli del vecchio nodo, o se vuoi semplicemente assicurarti che i figli siano visitati prima della sostituzione (pratica comune). - Usa
enter
se vuoi sostituire un nodo prima che i suoi figli vengano visitati.
Rimozione dei nodi
Puoi rimuovere completamente un nodo dall'AST restituendo la costante speciale NodeTraverser::RemoveNode
dal
visitor.
Esempio: Rimuoviamo tutti i commenti del template ({* ... *}
), che sono rappresentati da
CommentNode
nell'AST generato dal core di Latte (anche se tipicamente elaborati prima, questo serve come
esempio).
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Compiler\Nodes\CommentNode;
function removeCommentNodes(TemplateNode $templateNode): void
{
(new NodeTraverser)->traverse(
$templateNode,
// 'enter' va bene qui, poiché non abbiamo bisogno di informazioni sui figli per rimuovere il commento
enter: function (Node $node) {
if ($node instanceof CommentNode) {
// Segnaliamo al traverser di rimuovere questo nodo dall'AST
return NodeTraverser::RemoveNode;
}
},
);
}
Attenzione: Usa RemoveNode
con cautela. La rimozione di un nodo che contiene contenuto essenziale
o influisce sulla struttura (come la rimozione del nodo di contenuto di un ciclo) può portare a template danneggiati o codice
generato non valido. È più sicuro per nodi che sono veramente opzionali o autonomi (come commenti o tag di debug) o per nodi
strutturali vuoti (ad esempio, un FragmentNode
vuoto può essere rimosso in sicurezza in alcuni contesti da un
passaggio di pulizia).
Questi tre metodi – modifica delle proprietà, sostituzione dei nodi e rimozione dei nodi – forniscono gli strumenti essenziali per manipolare l'AST all'interno dei tuoi passaggi di compilazione.
Ottimizzazione dell'attraversamento
L'AST dei template può essere piuttosto grande, contenendo potenzialmente migliaia di nodi. Attraversare ogni singolo nodo
può essere superfluo e influire sulle prestazioni di compilazione se il tuo passaggio è interessato solo a parti specifiche
dell'albero. NodeTraverser
offre modi per ottimizzare l'attraversamento:
Saltare i figli
Se sai che una volta incontrato un certo tipo di nodo, nessuno dei suoi discendenti può contenere i nodi che stai cercando,
puoi dire al traverser di saltare la visita dei suoi figli. Questo si fa restituendo la costante
NodeTraverser::DontTraverseChildren
dal visitor enter
. Ciò omette interi rami durante
l'attraversamento, risparmiando potenzialmente tempo considerevole, specialmente nei template con espressioni PHP complesse
all'interno dei tag.
Fermare l'attraversamento
Se il tuo passaggio deve trovare solo la prima occorrenza di qualcosa (un tipo specifico di nodo, il soddisfacimento di
una condizione), puoi fermare completamente l'intero processo di attraversamento una volta trovato. Ciò si ottiene restituendo la
costante NodeTraverser::StopTraversal
dal visitor enter
o leave
. Il metodo
traverse()
smetterà di visitare qualsiasi altro nodo. Questo è altamente efficiente se hai bisogno solo della prima
corrispondenza in un albero potenzialmente molto grande.
Utile helper NodeHelpers
Mentre NodeTraverser
offre un controllo finemente graduato, Latte fornisce anche una pratica classe helper, Latte\Compiler\NodeHelpers, che incapsula
NodeTraverser
per diverse comuni attività di ricerca e analisi, richiedendo spesso meno codice boilerplate.
find (Node $startNode, callable $filter): array
Questo metodo statico trova tutti i nodi nel sottoalbero che inizia da $startNode
(incluso) che soddisfano
il callback $filter
. Restituisce un array dei nodi corrispondenti.
Esempio: Trovare tutti i nodi di variabile (VariableNode
) nell'intero 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
Simile a find
, ma interrompe l'attraversamento immediatamente dopo aver trovato il primo nodo che soddisfa
il callback $filter
. Restituisce l'oggetto Node
trovato o null
se non viene trovato alcun
nodo corrispondente. Questo è essenzialmente un comodo wrapper attorno a NodeTraverser::StopTraversal
.
Esempio: Trovare il nodo {parameters}
(uguale all'esempio manuale precedente, ma più corto).
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Essential\Nodes\ParametersNode;
function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode
{
return NodeHelpers::findFirst(
$templateNode->head, // Cerca solo nella sezione principale per efficienza
fn($node) => $node instanceof ParametersNode,
);
}
toValue (ExpressionNode $node, bool $constants = false): mixed
Questo metodo statico tenta di valutare un ExpressionNode
al momento della compilazione e restituire il suo
valore PHP corrispondente. Funziona in modo affidabile solo per nodi letterali semplici (StringNode
,
IntegerNode
, FloatNode
, BooleanNode
, NullNode
) e istanze di
ArrayNode
contenenti solo tali elementi valutabili.
Se $constants
è impostato su true
, tenterà anche di risolvere ConstantFetchNode
e
ClassConstantFetchNode
controllando defined()
e usando constant()
.
Se il nodo contiene variabili, chiamate a funzioni o altri elementi dinamici, non può essere valutato al momento della
compilazione e il metodo lancerà InvalidArgumentException
.
Caso d'uso: Ottenere il valore statico di un argomento di tag durante la compilazione per prendere decisioni al momento della compilazione.
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'argomento non era una stringa letterale statica
return null;
}
}
toText (?Node $node): ?string
Questo metodo statico è utile per estrarre il contenuto testuale semplice da nodi semplici. Funziona principalmente con:
TextNode
: Restituisce il suo$content
.FragmentNode
: Concatena il risultato ditoText()
per tutti i suoi figli. Se un figlio non è convertibile in testo (ad esempio, contiene unPrintNode
), restituiscenull
.NopNode
: Restituisce una stringa vuota.- Altri tipi di nodi: Restituisce
null
.
Caso d'uso: Ottenere il contenuto testuale statico del valore di un attributo HTML o di un semplice elemento HTML per l'analisi durante un passaggio di compilazione.
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\Html\AttributeNode;
function getStaticAttributeValue(AttributeNode $attr): ?string
{
// $attr->value è tipicamente un AreaNode (come FragmentNode o TextNode)
return NodeHelpers::toText($attr->value);
}
// Esempio di utilizzo in un passaggio:
// if ($node instanceof Html\ElementNode && $node->name === 'meta') {
// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name'));
// if ($nameAttrValue === 'description') { ... }
// }
NodeHelpers
può semplificare i tuoi passaggi di compilazione fornendo soluzioni pronte all'uso per comuni
attività di attraversamento e analisi dell'AST.
Esempi pratici
Applichiamo i concetti di attraversamento e modifica dell'AST per risolvere alcuni problemi pratici. Questi esempi dimostrano pattern comuni utilizzati nei passaggi di compilazione.
Aggiunta automatica di loading="lazy"
a <img>
I browser moderni supportano il lazy loading nativo per le immagini tramite l'attributo loading="lazy"
. Creiamo
un passaggio che aggiunge automaticamente questo attributo a tutti i tag <img>
che non hanno ancora un
attributo 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,
// Possiamo usare 'enter', poiché modifichiamo il nodo direttamente
// e non dipendiamo dai figli per questa decisione.
enter: function (Node $node) {
// È un elemento HTML con nome 'img'?
if ($node instanceof Html\ElementNode && $node->name === 'img') {
// Assicuriamoci che il nodo degli attributi esista
$node->attributes ??= new Nodes\FragmentNode;
// Controlliamo se esiste già un attributo 'loading' (indipendentemente dal case)
foreach ($node->attributes->children as $attrNode) {
if ($attrNode instanceof Html\AttributeNode
&& $attrNode->name instanceof Nodes\TextNode // Nome attributo statico
&& strtolower($attrNode->name->content) === 'loading'
) {
return;
}
}
// Aggiungiamo uno spazio se gli attributi non sono vuoti
if ($node->attributes->children) {
$node->attributes->children[] = new Nodes\TextNode(' ');
}
// Creiamo un nuovo nodo attributo: loading="lazy"
$node->attributes->children[] = new Html\AttributeNode(
name: new Nodes\TextNode('loading'),
value: new Nodes\TextNode('lazy'),
quote: '"',
);
// La modifica viene applicata direttamente nell'oggetto, non c'è bisogno di restituire nulla.
}
},
);
}
Spiegazione:
- Il visitor
enter
cerca nodiHtml\ElementNode
con nomeimg
. - Itera sugli attributi esistenti (
$node->attributes->children
) e controlla se l'attributoloading
è già presente. - Se non trovato, crea un nuovo
Html\AttributeNode
che rappresentaloading="lazy"
.
Controllo delle chiamate a funzioni
I passaggi di compilazione sono alla base della Sandbox di Latte. Anche se la Sandbox reale è sofisticata, possiamo dimostrare il principio di base del controllo delle chiamate a funzioni vietate.
Obiettivo: Impedire l'uso della funzione potenzialmente pericolosa shell_exec
all'interno delle espressioni
del 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]; // Semplice lista
$traverser = new NodeTraverser;
(new NodeTraverser)->traverse(
$templateNode,
enter: function (Node $node) use ($forbiddenFunctions) {
// È un nodo di chiamata diretta a funzione?
if ($node instanceof Php\Expression\FunctionCallNode
&& $node->name instanceof Php\NameNode
&& isset($forbiddenFunctions[strtolower((string) $node->name)])
) {
throw new SecurityViolationException(
"La funzione {$node->name}() non è consentita.",
$node->position,
);
}
},
);
}
Spiegazione:
- Definiamo un elenco di nomi di funzioni vietate.
- Il visitor
enter
controllaFunctionCallNode
. - Se il nome della funzione (
$node->name
) è unNameNode
statico, controlliamo la sua rappresentazione stringa in minuscolo rispetto al nostro elenco vietato. - Se viene trovata una funzione vietata, lanciamo
Latte\SecurityViolationException
, che indica chiaramente la violazione della regola di sicurezza e interrompe la compilazione.
Questi esempi mostrano come i passaggi di compilazione, utilizzando NodeTraverser
, possono essere sfruttati per
l'analisi, le modifiche automatiche e l'applicazione di vincoli di sicurezza interagendo direttamente con la struttura AST del
template.
Best practices
Quando scrivi passaggi di compilazione, tieni presenti queste linee guida per creare estensioni robuste, manutenibili ed efficienti:
- L'ordine è importante: Sii consapevole dell'ordine in cui vengono eseguiti i passaggi. Se il tuo passaggio dipende
dalla struttura AST creata da un altro passaggio (ad esempio, passaggi di base di Latte o un altro passaggio personalizzato),
o se altri passaggi possono dipendere dalle tue modifiche, usa il meccanismo di ordinamento fornito da
Extension::getPasses()
per definire le dipendenze (before
/after
). Vedi la documentazione perExtension::getPasses()
per i dettagli. - Singola responsabilità: Cerca di creare passaggi che eseguano un singolo compito ben definito. Per trasformazioni complesse, considera la suddivisione della logica in più passaggi – magari uno per l'analisi e un altro per la modifica basata sui risultati dell'analisi. Ciò migliora la leggibilità e la testabilità.
- Prestazioni: Ricorda che i passaggi di compilazione aggiungono tempo alla compilazione del template (anche se questo
di solito avviene solo una volta, finché il template non cambia). Evita operazioni computazionalmente costose nei tuoi passaggi,
se possibile. Utilizza ottimizzazioni dell'attraversamento come
NodeTraverser::DontTraverseChildren
eNodeTraverser::StopTraversal
ogni volta che sai di non aver bisogno di visitare determinate parti dell'AST. - Usa
NodeHelpers
: Per compiti comuni come la ricerca di nodi specifici o la valutazione statica di espressioni semplici, controlla seLatte\Compiler\NodeHelpers
offre un metodo appropriato prima di scrivere la tua logicaNodeTraverser
. Può risparmiare tempo e ridurre la quantità di codice boilerplate. - Gestione degli errori: Se il tuo passaggio rileva un errore o uno stato non valido nell'AST del template, lancia
Latte\CompileException
(oLatte\SecurityViolationException
per problemi di sicurezza) con un messaggio chiaro e l'oggettoPosition
pertinente (di solito$node->position
). Questo fornisce un feedback utile allo sviluppatore del template. - Idempotenza (se possibile): Idealmente, l'esecuzione del tuo passaggio più volte sullo stesso AST dovrebbe produrre lo stesso risultato della sua esecuzione una sola volta. Questo non è sempre fattibile, ma semplifica il debug e la riflessione sulle interazioni dei passaggi, se raggiunto. Ad esempio, assicurati che il tuo passaggio di modifica controlli se la modifica è già stata applicata prima di applicarla di nuovo.
Seguendo queste pratiche, puoi utilizzare efficacemente i passaggi di compilazione per estendere le capacità di Latte in modo potente e affidabile, contribuendo a un'elaborazione dei template più sicura, ottimizzata o funzionalmente più ricca.