Kompilační průchody
Kompilační průchody poskytují výkonný mechanismus pro analýzu a modifikaci Latte šablon po jejich parsování do abstraktního syntaktického stromu (AST) a před vygenerováním finálního PHP kódu. To umožňuje pokročilou manipulaci s šablonami, optimalizace, bezpečnostní kontroly (jako je Sandbox) a sběr informací o šablonách. Tento průvodce vás provede vytvořením vlastních kompilačních průchodů.
Co je kompilační průchod?
Pro pochopení role kompilačních průchodů se podívejte na kompilační proces Latte. Jak můžete vidět, kompilační průchody operují v klíčové fázi, umožňující hluboký zásah mezi počátečním parsováním a finálním výstupem kódu.
V jádru je kompilační průchod jednoduše PHP volatelný objekt (jako funkce, statická metoda nebo metoda instance),
který přijímá jeden argument: kořenový uzel AST šablony, což je vždy instance
Latte\Compiler\Nodes\TemplateNode
.
Primárním cílem kompilačního průchodu je obvykle jeden nebo oba z následujících:
- Analýza: Procházet AST a shromažďovat informace o šabloně (např. najít všechny definované bloky, zkontrolovat použití specifických tagů, zajistit splnění určitých bezpečnostních omezení).
- Modifikace: Změnit strukturu AST nebo atributy uzlů (např. automaticky přidat HTML atributy, optimalizovat určité kombinace tagů, nahradit zastaralé tagy novými, implementovat pravidla sandboxu).
Registrace
Kompilační průchody jsou registrovány pomocí metody rozšíření getPasses()
. Tato metoda vrací
asociativní pole, kde klíče jsou jedinečné názvy průchodů (používané interně a pro řazení) a hodnoty jsou PHP
volatelné objekty implementující logiku průchodu.
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Extension;
class MyExtension extends Extension
{
public function getPasses(): array
{
return [
'modificationPass' => $this->modifyTemplateAst(...),
// ... další průchody ...
];
}
public function modifyTemplateAst(TemplateNode $templateNode): void
{
// Implementace...
}
}
Průchody registrované základními rozšířeními Latte a vašimi vlastními rozšířeními běží sekvenčně. Pořadí
může být důležité, zejména pokud jeden průchod závisí na výsledcích nebo modifikacích jiného. Latte poskytuje
pomocný mechanismus pro kontrolu tohoto pořadí, pokud je potřeba; viz dokumentaci k Extension::getPasses()
pro podrobnosti.
Příklad AST
Pro lepší představu o AST, přidáváme ukázku. Toto je zdrojová šablona:
{foreach $category->getItems() as $item}
<li>{$item->name|upper}</li>
{else}
no items found
{/foreach}
A toto je její reprezentace ve formě 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') ) ) ) )
Procházení AST pomocí NodeTraverser
Ruční psaní rekurzivních funkcí pro procházení komplexní struktury AST je únavné a náchylné k chybám. Latte poskytuje speciální nástroj pro tento účel: Latte\Compiler\NodeTraverser. Tato třída implementuje návrhový vzor Visitor, díky kterému je procházení AST systematické a snadno zvládnutelné.
Základní použití zahrnuje vytvoření instance NodeTraverser
a volání její metody traverse()
,
předání kořenového uzlu AST a jednoho nebo dvou „visitor“ volatelných objektů:
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes;
(new NodeTraverser)->traverse(
$templateNode,
// 'enter' visitor: Volán při vstupu do uzlu (před jeho dětmi)
enter: function (Node $node) {
echo "Vstup do uzlu typu: " . $node::class . "\n";
// Zde můžete zkoumat uzel
if ($node instanceof Nodes\TextNode) {
// echo "Nalezen text: " . $node->content . "\n";
}
},
// 'leave' visitor: Volán při opuštění uzlu (po jeho dětech)
leave: function (Node $node) {
echo "Opuštění uzlu typu: " . $node::class . "\n";
// Zde můžete provádět akce po zpracování dětí
},
);
Můžete poskytnout pouze enter
visitor, pouze leave
visitor, nebo oba, v závislosti na vašich
potřebách.
enter(Node $node)
: Tato funkce je provedena pro každý uzel před tím, než procházející
navštíví kterékoliv z dětí tohoto uzlu. Je užitečná pro:
- Sbírání informací při průchodu stromem směrem dolů.
- Rozhodování před zpracováním dětí (jako rozhodnutí je přeskočit, viz Optimalizace procházení).
- Potenciální úpravy uzlu před návštěvou dětí (méně časté).
leave(Node $node)
: Tato funkce je provedena pro každý uzel po tom, co všechny jeho děti (a
jejich celé podstromy) byly plně navštíveny (jak vstup tak opuštění). Je nejčastějším místem pro:
Oba visitoři enter
a leave
mohou volitelně vracet hodnotu pro ovlivnění procesu procházení.
Vrácení null
(nebo nic) pokračuje v procházení normálně, vrácení instance Node
nahradí
aktuální uzel, a vrácení speciálních konstant jako NodeTraverser::RemoveNode
nebo
NodeTraverser::StopTraversal
modifikuje tok, jak je vysvětleno v následujících sekcích.
Jak procházení funguje
NodeTraverser
interně používá metodu getIterator()
, kterou musí implementovat každá třída
Node
(jak bylo diskutováno v Vytváření vlastních tagů).
Iteruje přes děti získané pomocí getIterator()
, rekurzivně volá traverse()
na nich a zajišťuje,
že enter
a leave
visitoři jsou voláni ve správném hloubkově-prvním pořadí pro každý uzel ve
stromě dostupný přes iterátory. To znovu zdůrazňuje, proč správně implementovaný getIterator()
ve vašich
vlastních tagových uzlech je naprosto nezbytný pro správné fungování kompilačních průchodů.
Pojďme napsat jednoduchý průchod, který počítá, kolikrát je v šabloně použit tag {do}
(reprezentovaný
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++;
}
},
// 'leave' visitor není pro tento úkol potřeba
);
echo "Nalezen tag {do} $count krát.\n";
}
$latte = new Latte\Engine;
$ast = $latte->parse($templateSource);
countDoTags($ast);
V tomto příkladu jsme potřebovali pouze visitor enter
ke kontrole typu každého navštíveného uzlu.
Dále prozkoumáme, jak tyto visitoři skutečně modifikují AST.
Modifikace AST
Jedním z hlavních účelů kompilačních průchodů je modifikace abstraktního syntaktického stromu. To umožňuje
výkonné transformace, optimalizace nebo vynucení pravidel přímo na struktuře šablony před generováním PHP kódu.
NodeTraverser
poskytuje několik způsobů, jak toho dosáhnout v rámci visitorů enter
a
leave
.
Důležitá poznámka: Modifikace AST vyžaduje opatrnost. Nesprávné změny – jako odstranění základních uzlů nebo nahrazení uzlu nekompatibilním typem – mohou vést k chybám během generování kódu nebo způsobit neočekávané chování během běhu programu. Vždy důkladně testujte své modifikační průchody.
Úprava atributů uzlů
Nejjednodušší způsob, jak modifikovat strom, je přímá změna veřejných vlastností uzlů navštívených během procházení. Všechny uzly ukládají své parsované argumenty, obsah nebo atributy ve veřejných vlastnostech.
Příklad: Vytvořme průchod, který najde všechny statické textové uzly (TextNode
, reprezentující
běžné HTML nebo text mimo Latte tagy) a převede jejich obsah na velká písmena přímo v 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,
// Můžeme použít 'enter', protože TextNode nemá žádné děti ke zpracování
enter: function (Node $node) {
// Je tento uzel statickým textovým blokem?
if ($node instanceof TextNode) {
// Ano! Přímo upravíme jeho veřejnou vlastnost 'content'.
$node->content = mb_strtoupper(html_entity_decode($node->content));
}
// Není potřeba nic vracet; změna je aplikována přímo.
},
);
}
V tomto příkladu visitor enter
kontroluje, zda je aktuální $node
typu TextNode
.
Pokud ano, přímo aktualizujeme jeho veřejnou vlastnost $content
pomocí mb_strtoupper()
. To přímo
mění obsah statického textu uloženého v AST před generováním PHP kódu. Protože modifikujeme objekt přímo,
nemusíme nic vracet z visitoru.
Efekt: Pokud šablona obsahovala <p>Hello</p>{= $var }<span>World</span>
, po tomto
průchodu bude AST reprezentovat něco jako: <p>HELLO</p>{= $var }<span>WORLD</span>
. To
NEOVLIVNÍ obsah $var.
Nahrazování uzlů
Výkonnější technikou modifikace je kompletní nahrazení uzlu jiným. To se provádí vrácením nové instance
Node
z visitoru enter
nebo leave
. NodeTraverser
pak nahradí původní
uzel vráceným ve struktuře rodičovského uzlu.
Příklad: Vytvořme průchod, který najde všechna použití konstanty PHP_VERSION
(reprezentované
ConstantFetchNode
) a nahradí je přímo řetězcovým literálem (StringNode
) obsahujícím
skutečnou verzi PHP detekovanou během kompilace. Toto je forma optimalizace v době kompilace.
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' je často používán pro nahrazení, zajišťuje, že děti (pokud existují)
// jsou zpracovány nejdříve, i když by zde fungoval i 'enter'.
leave: function (Node $node) {
// Je tento uzel přístupem ke konstantě a jméno konstanty 'PHP_VERSION'?
if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') {
// Vytvoříme nový StringNode obsahující aktuální verzi PHP
$newNode = new StringNode(PHP_VERSION);
// Volitelné, ale dobrá praxe: zkopírujeme informace o pozici
$newNode->position = $node->position;
// Vrátíme nový StringNode. Traverser nahradí
// původní ConstantFetchNode tímto $newNode.
return $newNode;
}
// Pokud nevrátíme Node, původní $node je zachován.
},
);
}
Zde visitor leave
identifikuje specifický ConstantFetchNode
pro PHP_VERSION
. Poté
vytvoří zcela nový StringNode
obsahující hodnotu konstanty PHP_VERSION
v době kompilace.
Vrácením tohoto $newNode
říká traverseru, aby nahradil původní ConstantFetchNode
v AST.
Efekt: Pokud šablona obsahovala {= PHP_VERSION }
a kompilace běží na PHP 8.2.1, AST po tomto průchodu bude
efektivně reprezentovat {= '8.2.1' }
.
Volba enter
vs. leave
pro nahrazení:
- Použijte
leave
, pokud vytvoření nového uzlu závisí na výsledcích zpracování dětí starého uzlu, nebo pokud chcete jednoduše zajistit, aby děti byly navštíveny před nahrazením (běžná praxe). - Použijte
enter
, pokud chcete nahradit uzel před tím, než jsou jeho děti vůbec navštíveny.
Odstraňování uzlů
Můžete zcela odstranit uzel z AST vrácením speciální konstanty NodeTraverser::RemoveNode
z visitoru.
Příklad: Odstraňme všechny komentáře šablony ({* ... *}
), které jsou reprezentovány
CommentNode
v AST generovaném jádrem Latte (ačkoli typicky zpracovány dříve, toto slouží jako
příklad).
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' je zde v pořádku, protože nepotřebujeme informace o dětech k odstranění komentáře
enter: function (Node $node) {
if ($node instanceof CommentNode) {
// Signalizujeme traverseru, aby odstranil tento uzel z AST
return NodeTraverser::RemoveNode;
}
},
);
}
Upozornění: Používejte RemoveNode
opatrně. Odstranění uzlu, který obsahuje základní obsah nebo
ovlivňuje strukturu (jako odstranění obsahového uzlu cyklu), může vést k poškozeným šablonám nebo neplatnému
generovanému kódu. Nejbezpečnější je pro uzly, které jsou skutečně volitelné nebo samostatné (jako komentáře nebo
ladící tagy) nebo pro prázdné strukturální uzly (např. prázdný FragmentNode
může být v některých
kontextech bezpečně odstraněn průchodem pro vyčištění).
Tyto tři metody – úprava vlastností, nahrazování uzlů a odstraňování uzlů – poskytují základní nástroje pro manipulaci s AST v rámci vašich kompilačních průchodů.
Optimalizace procházení
AST šablon může být poměrně velký, potenciálně obsahující tisíce uzlů. Procházení každého jednotlivého uzlu
může být zbytečné a ovlivnit výkon kompilace, pokud váš průchod má zájem pouze o specifické části stromu.
NodeTraverser
nabízí způsoby optimalizace procházení:
Přeskakování dětí
Pokud víte, že jakmile narazíte na určitý typ uzlu, žádný z jeho potomků nemůže obsahovat uzly, které hledáte,
můžete traverseru říct, aby přeskočil návštěvu jeho dětí. To se provádí vrácením konstanty
NodeTraverser::DontTraverseChildren
z visitoru enter
. Tím vynecháte celé větve při
procházení, což potenciálně ušetří značný čas, zejména v šablonách s komplexními PHP výrazy uvnitř tagů.
Zastavení procházení
Pokud váš průchod potřebuje najít pouze první výskyt něčeho (specifický typ uzlu, splnění podmínky),
můžete úplně zastavit celý proces procházení, jakmile to najdete. Toho je dosaženo vrácením konstanty
NodeTraverser::StopTraversal
z visitoru enter
nebo leave
. Metoda traverse()
přestane navštěvovat jakékoliv další uzly. To je vysoce efektivní, pokud potřebujete pouze první shodu v potenciálně
velmi velkém stromě.
Užitečný pomocník NodeHelpers
Zatímco NodeTraverser
nabízí jemně odstupňovanou kontrolu, Latte také poskytuje praktickou pomocnou třídu,
Latte\Compiler\NodeHelpers, která zapouzdřuje
NodeTraverser
pro několik běžných úloh vyhledávání a analýzy, často vyžadující méně
přípravného kódu.
find (Node $startNode, callable $filter): array
Tato statická metoda nachází všechny uzly v podstromu začínajícím na $startNode
(včetně), které
splňují callback $filter
. Vrací pole odpovídajících uzlů.
Příklad: Najít všechny uzly proměnných (VariableNode
) v celé š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
Podobné jako find
, ale zastaví procházení okamžitě po nalezení prvního uzlu, který splňuje
callback $filter
. Vrací nalezený objekt Node
nebo null
, pokud není nalezen žádný
odpovídající uzel. Toto je v podstatě praktický obal kolem NodeTraverser::StopTraversal
.
Příklad: Najít uzel {parameters}
(stejné jako manuální příklad předtím, ale kratší).
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Essential\Nodes\ParametersNode;
function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode
{
return NodeHelpers::findFirst(
$templateNode->head, // Hledat pouze v hlavní sekci pro efektivitu
fn($node) => $node instanceof ParametersNode,
);
}
toValue (ExpressionNode $node, bool $constants = false): mixed
Tato statická metoda se pokouší vyhodnotit ExpressionNode
v době kompilace a vrátit jeho
odpovídající PHP hodnotu. Funguje spolehlivě pouze pro jednoduché literální uzly (StringNode
,
IntegerNode
, FloatNode
, BooleanNode
, NullNode
) a instance
ArrayNode
obsahující pouze takové vyhodnotitelné položky.
Pokud je $constants
nastaveno na true
, bude se také pokoušet vyřešit
ConstantFetchNode
a ClassConstantFetchNode
kontrolou defined()
a použitím
constant()
.
Pokud uzel obsahuje proměnné, volání funkcí nebo jiné dynamické prvky, nemůže být vyhodnocen v době kompilace a
metoda vyhodí InvalidArgumentException
.
Případ použití: Získání statické hodnoty argumentu tagu během kompilace pro rozhodování v době kompilace.
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) {
// Argument nebyl statický literální řetězec
return null;
}
}
toText (?Node $node): ?string
Tato statická metoda je užitečná pro extrakci prostého textového obsahu z jednoduchých uzlů. Funguje primárně s:
TextNode
: Vrací jeho$content
.FragmentNode
: Zřetězí výsledektoText()
pro všechny jeho děti. Pokud některé dítě není převoditelné na text (např. obsahujePrintNode
), vracínull
.NopNode
: Vrací prázdný řetězec.- Ostatní typy uzlů: Vrací
null
.
Případ použití: Získání statického textového obsahu hodnoty HTML atributu nebo jednoduchého HTML elementu pro analýzu během kompilačního průchodu.
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\Html\AttributeNode;
function getStaticAttributeValue(AttributeNode $attr): ?string
{
// $attr->value je typicky AreaNode (jako FragmentNode nebo TextNode)
return NodeHelpers::toText($attr->value);
}
// Příklad použití v průchodu:
// if ($node instanceof Html\ElementNode && $node->name === 'meta') {
// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name'));
// if ($nameAttrValue === 'description') { ... }
// }
NodeHelpers
může zjednodušit vaše kompilační průchody poskytnutím hotových řešení pro běžné úlohy
procházení a analýzy AST.
Praktické příklady
Pojďme aplikovat koncepty procházení a modifikace AST k řešení některých praktických problémů. Tyto příklady demonstrují běžné vzory používané v kompilačních průchodech.
Automatické přidání loading="lazy"
k <img>
Moderní prohlížeče podporují nativní líné načítání pro obrázky pomocí atributu loading="lazy"
.
Vytvořme průchod, který automaticky přidá tento atribut ke všem tagům <img>
, které ještě nemají
atribut 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,
// Můžeme použít 'enter', protože modifikujeme uzel přímo
// a nezávisíme na dětech pro toto rozhodnutí.
enter: function (Node $node) {
// Je to HTML element s názvem 'img'?
if ($node instanceof Html\ElementNode && $node->name === 'img') {
// Zajistíme, že uzel atributů existuje
$node->attributes ??= new Nodes\FragmentNode;
// Zkontrolujeme, zda již existuje atribut 'loading' (bez ohledu na velikost písmen)
foreach ($node->attributes->children as $attrNode) {
if ($attrNode instanceof Html\AttributeNode
&& $attrNode->name instanceof Nodes\TextNode // Statický název atributu
&& strtolower($attrNode->name->content) === 'loading'
) {
return;
}
}
// Připojíme mezeru, pokud atributy nejsou prázdné
if ($node->attributes->children) {
$node->attributes->children[] = new Nodes\TextNode(' ');
}
// Vytvoříme nový uzel atributu: loading="lazy"
$node->attributes->children[] = new Html\AttributeNode(
name: new Nodes\TextNode('loading'),
value: new Nodes\TextNode('lazy'),
quote: '"',
);
// Změna je aplikována přímo v objektu, není potřeba nic vracet.
}
},
);
}
Vysvětlení:
- Visitor
enter
hledá uzlyHtml\ElementNode
s názvemimg
. - Iteruje přes existující atributy (
$node->attributes->children
) a kontroluje, zda je atributloading
již přítomen. - Pokud není nalezen, vytvoří nový
Html\AttributeNode
reprezentujícíloading="lazy"
.
Kontrola volání funkcí
Kompilační průchody jsou základem Latte Sandboxu. I když je skutečný Sandbox sofistikovaný, můžeme demonstrovat základní princip kontroly zakázaných volání funkcí.
Cíl: Zabránit použití potenciálně nebezpečné funkce shell_exec
v rámci výrazů šablony.
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]; // Jednoduchý seznam
$traverser = new NodeTraverser;
(new NodeTraverser)->traverse(
$templateNode,
enter: function (Node $node) use ($forbiddenFunctions) {
// Je to uzel přímého volání funkce?
if ($node instanceof Php\Expression\FunctionCallNode
&& $node->name instanceof Php\NameNode
&& isset($forbiddenFunctions[strtolower((string) $node->name)])
) {
throw new SecurityViolationException(
"Funkce {$node->name}() není povolena.",
$node->position,
);
}
},
);
}
Vysvětlení:
- Definujeme seznam zakázaných názvů funkcí.
- Visitor
enter
kontrolujeFunctionCallNode
. - Pokud je název funkce (
$node->name
) statickýNameNode
, kontrolujeme jeho řetězcovou reprezentaci v malých písmenech proti našemu zakázanému seznamu. - Pokud je nalezena zakázaná funkce, vyhodíme
Latte\SecurityViolationException
, která jasně indikuje porušení bezpečnostního pravidla a zastaví kompilaci.
Tyto příklady ukazují, jak mohou být kompilační průchody s použitím NodeTraverser
využity pro analýzu,
automatické modifikace a vynucení bezpečnostních omezení interakcí přímo se strukturou AST šablony.
Osvědčené postupy
Při psaní kompilačních průchodů mějte na paměti tyto směrnice pro vytváření robustních, udržovatelných a efektivních rozšíření:
- Pořadí je důležité: Buďte si vědomi pořadí, v jakém průchody běží. Pokud váš průchod závisí na
struktuře AST vytvořené jiným průchodem (např. základní průchody Latte nebo jiný vlastní průchod), nebo pokud jiné
průchody mohou záviset na vašich modifikacích, použijte mechanismus řazení poskytovaný
Extension::getPasses()
k definování závislostí (before
/after
). Viz dokumentaci kExtension::getPasses()
pro podrobnosti. - Jedna odpovědnost: Snažte se o průchody, které provádějí jednu dobře definovanou úlohu. Pro komplexní transformace zvažte rozdělení logiky do více průchodů – možná jeden pro analýzu a další pro modifikaci založenou na výsledcích analýzy. To zlepšuje přehlednost a testovatelnost.
- Výkon: Pamatujte, že kompilační průchody přidávají čas kompilace šablony (i když to obvykle nastává pouze
jednou, dokud se šablona nezmění). Vyhněte se výpočetně náročným operacím ve vašich průchodech, pokud je to možné.
Využívejte optimalizace procházení jako
NodeTraverser::DontTraverseChildren
aNodeTraverser::StopTraversal
kdykoliv víte, že nepotřebujete navštívit určité části AST. - Používejte
NodeHelpers
: Pro běžné úlohy jako hledání specifických uzlů nebo statické vyhodnocování jednoduchých výrazů, zkontrolujte, zdaLatte\Compiler\NodeHelpers
nenabízí vhodnou metodu před psaním vlastní logikyNodeTraverser
. Může to ušetřit čas a snížit množství přípravného kódu. - Zpracování chyb: Pokud váš průchod detekuje chybu nebo neplatný stav v AST šablony, vyhoďte
Latte\CompileException
(neboLatte\SecurityViolationException
pro bezpečnostní problémy) s jasnou zprávou a relevantním objektemPosition
(obvykle$node->position
). To poskytuje užitečnou zpětnou vazbu vývojáři šablony. - Idempotence (pokud možno): Ideálně by spuštění vašeho průchodu vícekrát na stejném AST mělo produkovat stejný výsledek jako jeho jednorázové spuštění. To není vždy proveditelné, ale zjednodušuje ladění a uvažování o interakcích průchodů, pokud je toho dosaženo. Například zajistěte, aby váš modifikační průchod kontroloval, zda modifikace již byla aplikována, než ji aplikuje znovu.
Dodržováním těchto postupů můžete efektivně využít kompilační průchody k rozšíření schopností Latte výkonným a spolehlivým způsobem, což přispívá k bezpečnějšímu, optimalizovanějšímu nebo funkčně bohatšímu zpracování šablon.