Trecerea de compilare
Trecerea de compilare oferă un mecanism puternic pentru analiza și modificarea șabloanelor Latte după ce acestea sunt parsate într-un arbore sintactic abstract (AST) și înainte de generarea codului PHP final. Acest lucru permite manipularea avansată a șabloanelor, optimizări, verificări de securitate (cum ar fi Sandbox) și colectarea de informații despre șabloane. Acest ghid vă va îndruma în crearea propriilor treceri de compilare.
Ce este o trecere de compilare?
Pentru a înțelege rolul trecerilor de compilare, consultați procesul de compilare Latte. După cum puteți vedea, trecerile de compilare operează într-o fază cheie, permițând o intervenție profundă între parsarea inițială și ieșirea finală a codului.
În esență, o trecere de compilare este pur și simplu un obiect PHP apelabil (cum ar fi o funcție, o metodă statică
sau o metodă de instanță) care acceptă un singur argument: nodul rădăcină al AST-ului șablonului, care este întotdeauna
o instanță a Latte\Compiler\Nodes\TemplateNode
.
Scopul principal al unei treceri de compilare este de obicei unul sau ambele dintre următoarele:
- Analiză: Parcurgerea AST-ului și colectarea de informații despre șablon (de exemplu, găsirea tuturor blocurilor definite, verificarea utilizării unor tag-uri specifice, asigurarea îndeplinirii anumitor restricții de securitate).
- Modificare: Schimbarea structurii AST-ului sau a atributelor nodurilor (de exemplu, adăugarea automată a atributelor HTML, optimizarea anumitor combinații de tag-uri, înlocuirea tag-urilor depreciate cu altele noi, implementarea regulilor sandbox).
Înregistrare
Trecerea de compilare este înregistrată folosind metoda extensiei getPasses()
. Această metodă
returnează un array asociativ unde cheile sunt nume unice ale trecerilor (utilizate intern și pentru sortare) iar valorile sunt
obiecte PHP apelabile care implementează logica trecerii.
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Extension;
class MyExtension extends Extension
{
public function getPasses(): array
{
return [
'modificationPass' => $this->modifyTemplateAst(...),
// ... alte treceri ...
];
}
public function modifyTemplateAst(TemplateNode $templateNode): void
{
// Implementare...
}
}
Trecerea înregistrată de extensiile de bază Latte și extensiile dvs. proprii rulează secvențial. Ordinea poate fi
importantă, mai ales dacă o trecere depinde de rezultatele sau modificările alteia. Latte oferă un mecanism auxiliar pentru
controlul acestei ordini, dacă este necesar; consultați documentația pentru Extension::getPasses()
pentru detalii.
Exemplu AST
Pentru o mai bună înțelegere a AST-ului, adăugăm un exemplu. Acesta este șablonul sursă:
{foreach $category->getItems() as $item}
<li>{$item->name|upper}</li>
{else}
no items found
{/foreach}
Și aceasta este reprezentarea sa sub formă de 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('no items found') ) ) ) )
Parcurgerea AST folosind NodeTraverser
Scrierea manuală a funcțiilor recursive pentru parcurgerea structurii complexe a AST-ului este obositoare și predispusă la erori. Latte oferă un instrument special pentru acest scop: Latte\Compiler\NodeTraverser. Această clasă implementează pattern-ul de design Visitor, datorită căruia parcurgerea AST-ului este sistematică și ușor de gestionat.
Utilizarea de bază implică crearea unei instanțe NodeTraverser
și apelarea metodei sale
traverse()
, transmițând nodul rădăcină al AST-ului și unul sau doi „visitor” apelabili:
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes;
(new NodeTraverser)->traverse(
$templateNode,
// 'enter' visitor: Apelat la intrarea în nod (înainte de copiii săi)
enter: function (Node $node) {
echo "Intrare în nod de tip: " . $node::class . "\n";
// Aici puteți examina nodul
if ($node instanceof Nodes\TextNode) {
// echo "Text găsit: " . $node->content . "\n";
}
},
// 'leave' visitor: Apelat la părăsirea nodului (după copiii săi)
leave: function (Node $node) {
echo "Părăsire nod de tip: " . $node::class . "\n";
// Aici puteți efectua acțiuni după procesarea copiilor
},
);
Puteți furniza doar visitor-ul enter
, doar visitor-ul leave
, sau ambii, în funcție de
nevoile dvs.
enter(Node $node)
: Această funcție este executată pentru fiecare nod înainte ca traverser-ul să
viziteze oricare dintre copiii acestui nod. Este utilă pentru:
- Colectarea informațiilor în timpul parcurgerii arborelui în jos.
- Luarea deciziilor înainte de procesarea copiilor (cum ar fi decizia de a-i sări, vezi Optimizarea parcurgerii).
- Modificări potențiale ale nodului înainte de vizitarea copiilor (mai puțin frecvent).
leave(Node $node)
: Această funcție este executată pentru fiecare nod după ce toți copiii săi
(și subarborii lor întregi) au fost complet vizitați (atât intrare cât și părăsire). Este cel mai frecvent loc pentru:
Ambii vizitatori enter
și leave
pot returna opțional o valoare pentru a influența procesul de
parcurgere. Returnarea null
(sau nimic) continuă parcurgerea normal, returnarea unei instanțe Node
înlocuiește nodul curent, iar returnarea constantelor speciale precum NodeTraverser::RemoveNode
sau
NodeTraverser::StopTraversal
modifică fluxul, așa cum este explicat în secțiunile următoare.
Cum funcționează parcurgerea
NodeTraverser
utilizează intern metoda getIterator()
, pe care trebuie să o implementeze fiecare
clasă Node
(așa cum s-a discutat în Crearea tag-urilor personalizate).
Iterează peste copiii obținuți prin getIterator()
, apelează recursiv traverse()
pe ei și asigură
că vizitatorii enter
și leave
sunt apelați în ordinea corectă de parcurgere în adâncime pentru
fiecare nod din arbore accesibil prin iteratori. Acest lucru subliniază din nou de ce un getIterator()
implementat
corect în nodurile dvs. de tag-uri personalizate este absolut necesar pentru funcționarea corectă a trecerilor de
compilare.
Să scriem o trecere simplă care numără de câte ori este utilizat tag-ul {do}
(reprezentat de
Latte\Essential\Nodes\DoNode
) în șablon.
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++;
}
},
// visitor-ul 'leave' nu este necesar pentru această sarcină
);
echo "Tag-ul {do} a fost găsit de $count ori.\n";
}
$latte = new Latte\Engine;
$ast = $latte->parse($templateSource);
countDoTags($ast);
În acest exemplu, am avut nevoie doar de visitor-ul enter
pentru a verifica tipul fiecărui nod vizitat.
În continuare, vom explora cum acești vizitatori modifică efectiv AST-ul.
Modificarea AST-ului
Unul dintre scopurile principale ale trecerilor de compilare este modificarea arborelui sintactic abstract. Acest lucru permite
transformări puternice, optimizări sau impunerea regulilor direct asupra structurii șablonului înainte de generarea codului
PHP. NodeTraverser
oferă mai multe moduri de a realiza acest lucru în cadrul vizitatorilor enter
și
leave
.
Notă importantă: Modificarea AST-ului necesită prudență. Modificările incorecte – cum ar fi eliminarea nodurilor esențiale sau înlocuirea unui nod cu un tip incompatibil – pot duce la erori în timpul generării codului sau pot cauza un comportament neașteptat în timpul rulării programului. Testați întotdeauna temeinic trecerile dvs. de modificare.
Modificarea atributelor nodurilor
Cel mai simplu mod de a modifica arborele este schimbarea directă a proprietăților publice ale nodurilor vizitate în timpul parcurgerii. Toate nodurile stochează argumentele, conținutul sau atributele parsate în proprietăți publice.
Exemplu: Să creăm o trecere care găsește toate nodurile de text static (TextNode
, reprezentând HTML
obișnuit sau text în afara tag-urilor Latte) și convertește conținutul lor în majuscule direct în 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,
// Putem folosi 'enter', deoarece TextNode nu are copii de procesat
enter: function (Node $node) {
// Este acest nod un bloc de text static?
if ($node instanceof TextNode) {
// Da! Modificăm direct proprietatea sa publică 'content'.
$node->content = mb_strtoupper(html_entity_decode($node->content));
}
// Nu este nevoie să returnăm nimic; modificarea este aplicată direct.
},
);
}
În acest exemplu, visitor-ul enter
verifică dacă $node
-ul curent este de tip
TextNode
. Dacă da, actualizăm direct proprietatea sa publică $content
folosind
mb_strtoupper()
. Acest lucru modifică direct conținutul textului static stocat în AST înainte de generarea
codului PHP. Deoarece modificăm obiectul direct, nu trebuie să returnăm nimic din visitor.
Efect: Dacă șablonul conținea <p>Hello</p>{= $var }<span>World</span>
, după această
trecere, AST-ul va reprezenta ceva de genul: <p>HELLO</p>{= $var }<span>WORLD</span>
. Acest
lucru NU VA AFECTA conținutul $var.
Înlocuirea nodurilor
O tehnică de modificare mai puternică este înlocuirea completă a unui nod cu altul. Acest lucru se realizează prin
returnarea unei noi instanțe Node
din visitor-ul enter
sau leave
.
NodeTraverser
înlocuiește apoi nodul original cu cel returnat în structura nodului părinte.
Exemplu: Să creăm o trecere care găsește toate utilizările constantei PHP_VERSION
(reprezentate de
ConstantFetchNode
) și le înlocuiește direct cu un literal de șir (StringNode
) conținând versiunea
PHP reală detectată în timpul compilării. Aceasta este o formă de optimizare în timpul compilării.
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' este adesea folosit pentru înlocuire, asigurând că copiii (dacă există)
// sunt procesați mai întâi, deși și 'enter' ar funcționa aici.
leave: function (Node $node) {
// Este acest nod un acces la constantă și numele constantei este 'PHP_VERSION'?
if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') {
// Creăm un nou StringNode conținând versiunea PHP curentă
$newNode = new StringNode(PHP_VERSION);
// Opțional, dar bună practică: copiem informațiile despre poziție
$newNode->position = $node->position;
// Returnăm noul StringNode. Traverser-ul va înlocui
// ConstantFetchNode original cu acest $newNode.
return $newNode;
}
// Dacă nu returnăm Node, $node-ul original este păstrat.
},
);
}
Aici, visitor-ul leave
identifică ConstantFetchNode
specific pentru PHP_VERSION
. Apoi
creează un StringNode
complet nou conținând valoarea constantei PHP_VERSION
în timpul
compilării. Returnând acest $newNode
, îi spune traverser-ului să înlocuiască
ConstantFetchNode
original în AST.
Efect: Dacă șablonul conținea {= PHP_VERSION }
și compilarea rulează pe PHP 8.2.1, AST-ul după această
trecere va reprezenta efectiv {= '8.2.1' }
.
Alegerea enter
vs. leave
pentru înlocuire:
- Utilizați
leave
dacă crearea noului nod depinde de rezultatele procesării copiilor nodului vechi, sau dacă doriți pur și simplu să vă asigurați că copiii sunt vizitați înainte de înlocuire (practică comună). - Utilizați
enter
dacă doriți să înlocuiți un nod înainte ca copiii săi să fie vizitați.
Eliminarea nodurilor
Puteți elimina complet un nod din AST returnând constanta specială NodeTraverser::RemoveNode
din visitor.
Exemplu: Să eliminăm toate comentariile șablonului ({* ... *}
), care sunt reprezentate de
CommentNode
în AST-ul generat de nucleul Latte (deși de obicei procesate mai devreme, acest lucru servește ca
exemplu).
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' este în regulă aici, deoarece nu avem nevoie de informații despre copii pentru a elimina comentariul
enter: function (Node $node) {
if ($node instanceof CommentNode) {
// Semnalăm traverser-ului să elimine acest nod din AST
return NodeTraverser::RemoveNode;
}
},
);
}
Atenție: Utilizați RemoveNode
cu prudență. Eliminarea unui nod care conține conținut esențial sau
afectează structura (cum ar fi eliminarea nodului de conținut al unui ciclu) poate duce la șabloane corupte sau cod generat
invalid. Este cel mai sigur pentru nodurile care sunt cu adevărat opționale sau autonome (cum ar fi comentariile sau tag-urile
de depanare) sau pentru nodurile structurale goale (de exemplu, un FragmentNode
gol poate fi eliminat în siguranță
în unele contexte printr-o trecere de curățare).
Aceste trei metode – modificarea proprietăților, înlocuirea nodurilor și eliminarea nodurilor – oferă instrumentele de bază pentru manipularea AST-ului în cadrul trecerilor dvs. de compilare.
Optimizarea parcurgerii
AST-ul șabloanelor poate fi destul de mare, conținând potențial mii de noduri. Parcurgerea fiecărui nod individual poate
fi inutilă și poate afecta performanța compilării dacă trecerea dvs. este interesată doar de părți specifice ale
arborelui. NodeTraverser
oferă modalități de optimizare a parcurgerii:
Sărirea peste copii
Dacă știți că, odată ce întâlniți un anumit tip de nod, niciunul dintre descendenții săi nu poate conține nodurile
pe care le căutați, puteți spune traverser-ului să sară peste vizitarea copiilor săi. Acest lucru se realizează returnând
constanta NodeTraverser::DontTraverseChildren
din visitor-ul enter
. Astfel, veți omite ramuri
întregi în timpul parcurgerii, economisind potențial timp considerabil, în special în șabloanele cu expresii PHP complexe
în interiorul tag-urilor.
Oprirea parcurgerii
Dacă trecerea dvs. trebuie să găsească doar prima apariție a ceva (un tip specific de nod, îndeplinirea unei
condiții), puteți opri complet întregul proces de parcurgere odată ce ați găsit-o. Acest lucru se realizează returnând
constanta NodeTraverser::StopTraversal
din visitor-ul enter
sau leave
. Metoda
traverse()
va înceta să viziteze orice alte noduri. Acest lucru este extrem de eficient dacă aveți nevoie doar de
prima potrivire într-un arbore potențial foarte mare.
Ajutor util NodeHelpers
În timp ce NodeTraverser
oferă un control fin, Latte oferă, de asemenea, o clasă ajutătoare practică, Latte\Compiler\NodeHelpers, care încapsulează
NodeTraverser
pentru mai multe sarcini comune de căutare și analiză, necesitând adesea mai puțin cod de
pregătire.
find (Node $startNode, callable $filter): array
Această metodă statică găsește toate nodurile din subarborele care începe la $startNode
(inclusiv),
care îndeplinesc callback-ul $filter
. Returnează un array de noduri potrivite.
Exemplu: Găsiți toate nodurile de variabile (VariableNode
) din întregul șablon.
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
Similar cu find
, dar oprește parcurgerea imediat după găsirea primului nod care îndeplinește
callback-ul $filter
. Returnează obiectul Node
găsit sau null
dacă nu este găsit niciun
nod potrivit. Acesta este în esență un wrapper practic în jurul NodeTraverser::StopTraversal
.
Exemplu: Găsiți nodul {parameters}
(la fel ca exemplul manual anterior, dar mai scurt).
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Essential\Nodes\ParametersNode;
function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode
{
return NodeHelpers::findFirst(
$templateNode->head, // Căutați doar în secțiunea principală pentru eficiență
fn($node) => $node instanceof ParametersNode,
);
}
toValue (ExpressionNode $node, bool $constants = false): mixed
Această metodă statică încearcă să evalueze ExpressionNode
în timpul compilării și să returneze
valoarea sa PHP corespunzătoare. Funcționează fiabil doar pentru noduri literale simple (StringNode
,
IntegerNode
, FloatNode
, BooleanNode
, NullNode
) și instanțe
ArrayNode
care conțin doar astfel de elemente evaluabile.
Dacă $constants
este setat la true
, va încerca, de asemenea, să rezolve
ConstantFetchNode
și ClassConstantFetchNode
verificând defined()
și folosind
constant()
.
Dacă nodul conține variabile, apeluri de funcții sau alte elemente dinamice, nu poate fi evaluat în timpul compilării și
metoda va arunca InvalidArgumentException
.
Caz de utilizare: Obținerea valorii statice a unui argument de tag în timpul compilării pentru luarea deciziilor în timpul compilării.
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) {
// Argumentul nu a fost un șir literal static
return null;
}
}
toText (?Node $node): ?string
Această metodă statică este utilă pentru extragerea conținutului text simplu din noduri simple. Funcționează în principal cu:
TextNode
: Returnează$content
-ul său.FragmentNode
: Concatenează rezultatultoText()
pentru toți copiii săi. Dacă un copil nu este convertibil în text (de exemplu, conținePrintNode
), returneazănull
.NopNode
: Returnează un șir gol.- Alte tipuri de noduri: Returnează
null
.
Caz de utilizare: Obținerea conținutului text static al valorii unui atribut HTML sau al unui element HTML simplu pentru analiză în timpul unei treceri de compilare.
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\Html\AttributeNode;
function getStaticAttributeValue(AttributeNode $attr): ?string
{
// $attr->value este de obicei AreaNode (cum ar fi FragmentNode sau TextNode)
return NodeHelpers::toText($attr->value);
}
// Exemplu de utilizare într-o trecere:
// if ($node instanceof Html\ElementNode && $node->name === 'meta') {
// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name'));
// if ($nameAttrValue === 'description') { ... }
// }
NodeHelpers
poate simplifica trecerile dvs. de compilare oferind soluții gata făcute pentru sarcini comune de
parcurgere și analiză AST.
Exemple practice
Să aplicăm conceptele de parcurgere și modificare AST pentru a rezolva câteva probleme practice. Aceste exemple demonstrează pattern-uri comune utilizate în trecerile de compilare.
Adăugarea automată a loading="lazy"
la <img>
Browserele moderne suportă încărcarea leneșă nativă pentru imagini folosind atributul loading="lazy"
. Să
creăm o trecere care adaugă automat acest atribut la toate tag-urile <img>
care nu au încă atributul
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,
// Putem folosi 'enter', deoarece modificăm nodul direct
// și nu depindem de copii pentru această decizie.
enter: function (Node $node) {
// Este un element HTML cu numele 'img'?
if ($node instanceof Html\ElementNode && $node->name === 'img') {
// Asigurăm că nodul de atribute există
$node->attributes ??= new Nodes\FragmentNode;
// Verificăm dacă există deja un atribut 'loading' (indiferent de majuscule/minuscule)
foreach ($node->attributes->children as $attrNode) {
if ($attrNode instanceof Html\AttributeNode
&& $attrNode->name instanceof Nodes\TextNode // Nume static de atribut
&& strtolower($attrNode->name->content) === 'loading'
) {
return;
}
}
// Adăugăm un spațiu dacă atributele nu sunt goale
if ($node->attributes->children) {
$node->attributes->children[] = new Nodes\TextNode(' ');
}
// Creăm un nou nod de atribut: loading="lazy"
$node->attributes->children[] = new Html\AttributeNode(
name: new Nodes\TextNode('loading'),
value: new Nodes\TextNode('lazy'),
quote: '"',
);
// Modificarea este aplicată direct în obiect, nu este nevoie să returnăm nimic.
}
},
);
}
Explicație:
- Visitor-ul
enter
caută noduriHtml\ElementNode
cu numeleimg
. - Iterează peste atributele existente (
$node->attributes->children
) și verifică dacă atributulloading
este deja prezent. - Dacă nu este găsit, creează un nou
Html\AttributeNode
reprezentândloading="lazy"
.
Verificarea apelurilor de funcții
Trecerea de compilare stă la baza Latte Sandbox. Deși Sandbox-ul real este sofisticat, putem demonstra principiul de bază al verificării apelurilor de funcții interzise.
Scop: Prevenirea utilizării funcției potențial periculoase shell_exec
în cadrul expresiilor
șablonului.
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]; // Listă simplă
$traverser = new NodeTraverser;
(new NodeTraverser)->traverse(
$templateNode,
enter: function (Node $node) use ($forbiddenFunctions) {
// Este un nod de apel direct de funcție?
if ($node instanceof Php\Expression\FunctionCallNode
&& $node->name instanceof Php\NameNode
&& isset($forbiddenFunctions[strtolower((string) $node->name)])
) {
throw new SecurityViolationException(
"Funcția {$node->name}() nu este permisă.",
$node->position,
);
}
},
);
}
Explicație:
- Definim o listă de nume de funcții interzise.
- Visitor-ul
enter
verificăFunctionCallNode
. - Dacă numele funcției (
$node->name
) este unNameNode
static, verificăm reprezentarea sa de șir în minuscule față de lista noastră interzisă. - Dacă este găsită o funcție interzisă, aruncăm
Latte\SecurityViolationException
, care indică clar încălcarea regulii de securitate și oprește compilarea.
Aceste exemple arată cum trecerile de compilare, folosind NodeTraverser
, pot fi utilizate pentru analiză,
modificări automate și impunerea restricțiilor de securitate prin interacțiunea directă cu structura AST a șablonului.
Cele mai bune practici
La scrierea trecerilor de compilare, țineți cont de aceste linii directoare pentru a crea extensii robuste, mentenabile și eficiente:
- Ordinea contează: Fiți conștienți de ordinea în care rulează trecerile. Dacă trecerea dvs. depinde de structura
AST creată de o altă trecere (de exemplu, trecerile de bază Latte sau o altă trecere personalizată), sau dacă alte treceri
pot depinde de modificările dvs., utilizați mecanismul de sortare furnizat de
Extension::getPasses()
pentru a defini dependențele (before
/after
). Consultați documentația pentruExtension::getPasses()
pentru detalii. - O singură responsabilitate: Încercați să aveți treceri care efectuează o singură sarcină bine definită. Pentru transformări complexe, luați în considerare împărțirea logicii în mai multe treceri – poate una pentru analiză și alta pentru modificare bazată pe rezultatele analizei. Acest lucru îmbunătățește claritatea și testabilitatea.
- Performanță: Amintiți-vă că trecerile de compilare adaugă timp la compilarea șablonului (deși acest lucru se
întâmplă de obicei o singură dată, până când șablonul se schimbă). Evitați operațiunile costisitoare din punct de
vedere computațional în trecerile dvs., dacă este posibil. Utilizați optimizări de parcurgere precum
NodeTraverser::DontTraverseChildren
șiNodeTraverser::StopTraversal
ori de câte ori știți că nu trebuie să vizitați anumite părți ale AST-ului. - Utilizați
NodeHelpers
: Pentru sarcini comune precum găsirea unor noduri specifice sau evaluarea statică a expresiilor simple, verificați dacăLatte\Compiler\NodeHelpers
nu oferă o metodă potrivită înainte de a scrie propria logicăNodeTraverser
. Acest lucru poate economisi timp și reduce cantitatea de cod de pregătire. - Gestionarea erorilor: Dacă trecerea dvs. detectează o eroare sau o stare invalidă în AST-ul șablonului,
aruncați
Latte\CompileException
(sauLatte\SecurityViolationException
pentru probleme de securitate) cu un mesaj clar și obiectulPosition
relevant (de obicei$node->position
). Acest lucru oferă feedback util dezvoltatorului șablonului. - Idempotență (dacă este posibil): Ideal, rularea trecerii dvs. de mai multe ori pe același AST ar trebui să producă același rezultat ca și rularea sa o singură dată. Acest lucru nu este întotdeauna fezabil, dar simplifică depanarea și raționamentul despre interacțiunile trecerilor, dacă este realizat. De exemplu, asigurați-vă că trecerea dvs. de modificare verifică dacă modificarea a fost deja aplicată înainte de a o aplica din nou.
Urmând aceste practici, puteți utiliza eficient trecerile de compilare pentru a extinde capacitățile Latte într-un mod puternic și fiabil, contribuind la o procesare a șabloanelor mai sigură, optimizată sau mai bogată funcțional.