Компилационни проходи

Компилационните проходи предоставят мощен механизъм за анализ и модификация на Latte шаблони след тяхното парсване в абстрактно синтактично дърво (AST) и преди генерирането на финалния PHP код. Това позволява напреднала манипулация на шаблони, оптимизации, проверки за сигурност (като Sandbox) и събиране на информация за шаблоните. Този ръководство ще ви преведе през създаването на собствени компилационни проходи.

Какво е компилационен проход?

За да разберете ролята на компилационните проходи, погледнете процеса на компилация на Latte. Както можете да видите, компилационните проходи оперират в ключова фаза, позволявайки дълбока намеса между първоначалното парсване и финалния изход на кода.

В основата си, компилационен проход е просто PHP callable обект (като функция, статичен метод или метод на инстанция), който приема един аргумент: коренния възел на AST на шаблона, който винаги е инстанция на Latte\Compiler\Nodes\TemplateNode.

Основната цел на компилационния проход обикновено е една или и двете от следните:

  • Анализ: Обхождане на AST и събиране на информация за шаблона (напр. намиране на всички дефинирани блокове, проверка на използването на специфични тагове, осигуряване на спазването на определени ограничения за сигурност).
  • Модификация: Промяна на структурата на AST или атрибутите на възлите (напр. автоматично добавяне на HTML атрибути, оптимизиране на определени комбинации от тагове, замяна на остарели тагове с нови, прилагане на правила на sandbox).

Регистрация

Компилационните проходи се регистрират с помощта на метода разширение getPasses(). Този метод връща асоциативен масив, където ключовете са уникални имена на проходите (използвани вътрешно и за сортиране), а стойностите са PHP callable обекти, имплементиращи логиката на прохода.

use Latte\Compiler\Nodes\TemplateNode;
use Latte\Extension;

class MyExtension extends Extension
{
	public function getPasses(): array
	{
		return [
			'modificationPass' => $this->modifyTemplateAst(...),
			// ... други проходи ...
		];
	}

	public function modifyTemplateAst(TemplateNode $templateNode): void
	{
		// Имплементация...
	}
}

Проходите, регистрирани от основните разширения на Latte и вашите собствени разширения, се изпълняват последователно. Редът може да бъде важен, особено ако един проход зависи от резултатите или модификациите на друг. Latte предоставя помощен механизъм за контрол на този ред, ако е необходимо; вижте документацията за Extension::getPasses() за подробности.

Пример за AST

За по-добра представа за AST, добавяме пример. Това е изходният шаблон:

{foreach $category->getItems() as $item}
	<li>{$item->name|upper}</li>
	{else}
	no items found
{/foreach}

А това е неговото представяне под формата на 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')
            )
        )
   )
)

Обхождане на AST с помощта на NodeTraverser

Ръчното писане на рекурсивни функции за обхождане на сложната структура на AST е уморително и податливо на грешки. Latte предоставя специален инструмент за тази цел: Latte\Compiler\NodeTraverser. Този клас имплементира дизайн патърна Visitor, благодарение на който обхождането на AST става систематично и лесно управляемо.

Основното използване включва създаване на инстанция на NodeTraverser и извикване на нейния метод traverse(), като се предаде коренният възел на AST и един или два “visitor” callable обекта:

use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes;

(new NodeTraverser)->traverse(
	$templateNode,

	// 'enter' visitor: Извиква се при влизане във възел (преди неговите деца)
	enter: function (Node $node) {
		echo "Влизане във възел от тип: " . $node::class . "\n";
		// Тук можете да изследвате възела
		if ($node instanceof Nodes\TextNode) {
			// echo "Намерен текст: " . $node->content . "\n";
		}
	},

	// 'leave' visitor: Извиква се при напускане на възел (след неговите деца)
	leave: function (Node $node) {
		echo "Напускане на възел от тип: " . $node::class . "\n";
		// Тук можете да извършвате действия след обработка на децата
	},
);

Можете да предоставите само enter visitor, само leave visitor, или и двата, в зависимост от вашите нужди.

enter(Node $node): Тази функция се изпълнява за всеки възел преди обхождащият да посети което и да е от децата на този възел. Полезна е за:

  • Събиране на информация при обхождане на дървото надолу.
  • Вземане на решения преди обработката на децата (като решение за тяхното пропускане, вижте Оптимизиране на обхождането).
  • Потенциални корекции на възела преди посещение на децата (по-рядко).

leave(Node $node): Тази функция се изпълнява за всеки възел след като всички негови деца (и техните цели поддървета) са напълно посетени (както влизане, така и напускане). Това е най-честото място за:

И двата визитора enter и leave могат опционално да връщат стойност, за да повлияят на процеса на обхождане. Връщането на null (или нищо) продължава обхождането нормално, връщането на инстанция на Node замества текущия възел, а връщането на специални константи като NodeTraverser::RemoveNode или NodeTraverser::StopTraversal модифицира потока, както е обяснено в следващите секции.

Как работи обхождането

NodeTraverser вътрешно използва метода getIterator(), който трябва да бъде имплементиран от всеки клас Node (както беше обсъдено в Създаване на собствени тагове). Итерира през децата, получени с помощта на getIterator(), рекурсивно извиква traverse() върху тях и гарантира, че enter и leave визиторите се извикват в правилния ред „първо в дълбочина“ за всеки възел в дървото, достъпен чрез итератори. Това отново подчертава защо правилно имплементираният getIterator() във вашите собствени тагови възли е абсолютно необходим за правилното функциониране на компилационните проходи.

Нека напишем прост проход, който брои колко пъти в шаблона е използван тагът {do} (представен от 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 не е необходим за тази задача
	);

	echo "Намерен таг {do} $count пъти.\n";
}

$latte = new Latte\Engine;
$ast = $latte->parse($templateSource);
countDoTags($ast);

В този пример ни беше необходим само visitor enter, за да проверим типа на всеки посетен възел.

След това ще разгледаме как тези визитори действително модифицират AST.

Модификация на AST

Една от основните цели на компилационните проходи е модификацията на абстрактното синтактично дърво. Това позволява мощни трансформации, оптимизации или налагане на правила директно върху структурата на шаблона преди генерирането на PHP код. NodeTraverser предоставя няколко начина за постигане на това в рамките на визиторите enter и leave.

Важна забележка: Модификацията на AST изисква внимание. Неправилните промени – като премахване на основни възли или замяна на възел с несъвместим тип – могат да доведат до грешки по време на генерирането на код или да причинят неочаквано поведение по време на изпълнение на програмата. Винаги тествайте обстойно вашите модификационни проходи.

Промяна на атрибутите на възлите

Най-простият начин за модифициране на дървото е директната промяна на публичните свойства на възлите, посетени по време на обхождането. Всички възли съхраняват своите парснати аргументи, съдържание или атрибути в публични свойства.

Пример: Нека създадем проход, който намира всички статични текстови възли (TextNode, представляващи обикновен HTML или текст извън Latte тагове) и преобразува тяхното съдържание на главни букви директно в 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,
		// Можем да използваме 'enter', тъй като TextNode няма деца за обработка
		enter: function (Node $node) {
			// Този възел статичен текстов блок ли е?
			if ($node instanceof TextNode) {
				// Да! Директно ще променим неговото публично свойство 'content'.
				$node->content = mb_strtoupper(html_entity_decode($node->content));
			}
			// Не е необходимо нищо да се връща; промяната е приложена директно.
		},
	);
}

В този пример visitor enter проверява дали текущият $node е от тип TextNode. Ако е така, директно актуализираме неговото публично свойство $content с помощта на mb_strtoupper(). Това директно променя съдържанието на статичния текст, съхранен в AST преди генерирането на PHP код. Тъй като модифицираме обекта директно, не е необходимо да връщаме нищо от визитора.

Ефект: Ако шаблонът съдържаше <p>Hello</p>{= $var }<span>World</span>, след този проход AST ще представя нещо като: <p>HELLO</p>{= $var }<span>WORLD</span>. Това НЕ ВЛИЯЕ на съдържанието на $var.

Замяна на възли

По-мощна техника за модификация е пълната замяна на възел с друг. Това се извършва чрез връщане на нова инстанция на Node от визитора enter или leave. NodeTraverser след това замества оригиналния възел с върнатия в структурата на родителския възел.

Пример: Нека създадем проход, който намира всички употреби на константата PHP_VERSION (представена от ConstantFetchNode) и ги заменя директно с низов литерал (StringNode), съдържащ действителната версия на PHP, открита по време на компилация. Това е форма на оптимизация по време на компилация.

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' често се използва за замяна, като гарантира, че децата (ако има такива)
		// се обработват първо, въпреки че 'enter' също би работил тук.
		leave: function (Node $node) {
			// Този възел достъп до константа ли е и името на константата 'PHP_VERSION' ли е?
			if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') {
				// Създаваме нов StringNode, съдържащ текущата версия на PHP
				$newNode = new StringNode(PHP_VERSION);

				// Незадължително, но добра практика: копираме информацията за позицията
				$newNode->position = $node->position;

				// Връщаме новия StringNode. Traverser ще замени
				// оригиналния ConstantFetchNode с този $newNode.
				return $newNode;
			}
			// Ако не върнем Node, оригиналният $node се запазва.
		},
	);
}

Тук visitor leave идентифицира специфичния ConstantFetchNode за PHP_VERSION. След това създава изцяло нов StringNode, съдържащ стойността на константата PHP_VERSION по време на компилация. Връщайки този $newNode, той казва на обхождащия да замени оригиналния ConstantFetchNode в AST.

Ефект: Ако шаблонът съдържаше {= PHP_VERSION } и компилацията се изпълнява на PHP 8.2.1, AST след този проход ефективно ще представя {= '8.2.1' }.

Избор на enter срещу leave за замяна:

  • Използвайте leave, ако създаването на новия възел зависи от резултатите от обработката на децата на стария възел, или ако просто искате да гарантирате, че децата са посетени преди замяната (често срещана практика).
  • Използвайте enter, ако искате да замените възел преди неговите деца изобщо да бъдат посетени.

Премахване на възли

Можете напълно да премахнете възел от AST, като върнете специалната константа NodeTraverser::RemoveNode от визитора.

Пример: Нека премахнем всички коментари на шаблона ({* ... *}), които са представени от CommentNode в AST, генериран от ядрото на Latte (въпреки че обикновено се обработват по-рано, това служи като пример).

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' тук е добре, тъй като не се нуждаем от информация за децата, за да премахнем коментара
		enter: function (Node $node) {
			if ($node instanceof CommentNode) {
				// Сигнализираме на обхождащия да премахне този възел от AST
				return NodeTraverser::RemoveNode;
			}
		},
	);
}

Внимание: Използвайте RemoveNode внимателно. Премахването на възел, който съдържа основно съдържание или влияе на структурата (като премахване на съдържателния възел на цикъл), може да доведе до повредени шаблони или невалиден генериран код. Най-безопасно е за възли, които са наистина незадължителни или самостоятелни (като коментари или дебъгващи тагове) или за празни структурни възли (напр. празен FragmentNode може да бъде безопасно премахнат в някои контексти чрез проход за почистване).

Тези три метода – промяна на свойства, замяна на възли и премахване на възли – предоставят основните инструменти за манипулиране на AST в рамките на вашите компилационни проходи.

Оптимизиране на обхождането

AST на шаблоните може да бъде доста голям, потенциално съдържащ хиляди възли. Обхождането на всеки отделен възел може да бъде ненужно и да повлияе на производителността на компилацията, ако вашият проход се интересува само от специфични части на дървото. NodeTraverser предлага начини за оптимизиране на обхождането:

Пропускане на деца

Ако знаете, че щом срещнете определен тип възел, нито един от неговите потомци не може да съдържа възли, които търсите, можете да кажете на обхождащия да пропусне посещението на неговите деца. Това се извършва чрез връщане на константата NodeTraverser::DontTraverseChildren от визитора enter. По този начин пропускате цели клонове при обхождането, което потенциално спестява значително време, особено в шаблони със сложни PHP изрази вътре в тагове.

Спиране на обхождането

Ако вашият проход трябва да намери само първото срещане на нещо (специфичен тип възел, изпълнение на условие), можете напълно да спрете целия процес на обхождане, щом го намерите. Това се постига чрез връщане на константата NodeTraverser::StopTraversal от визитора enter или leave. Методът traverse() спира да посещава всякакви други възли. Това е изключително ефективно, ако се нуждаете само от първото съвпадение в потенциално много голямо дърво.

Полезен помощник NodeHelpers

Докато NodeTraverser предлага фино настроен контрол, Latte също предоставя практичен помощен клас, Latte\Compiler\NodeHelpers, който капсулира NodeTraverser за няколко често срещани задачи за търсене и анализ, често изискващи по-малко подготвителен код.

find (Node $startNode, callable $filter)array

Този статичен метод намира всички възли в поддървото, започващо от $startNode (включително), които отговарят на callback $filter. Връща масив от съответстващи възли.

Пример: Намиране на всички възли на променливи (VariableNode) в целия шаблон.

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

Подобно на find, но спира обхождането незабавно след намиране на първия възел, който отговаря на callback $filter. Връща намерения обект Node или null, ако не е намерен съответстващ възел. Това е по същество практична обвивка около NodeTraverser::StopTraversal.

Пример: Намиране на възела {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, // Търсене само в главната секция за ефективност
		fn($node) => $node instanceof ParametersNode,
	);
}

toValue (ExpressionNode $node, bool $constants = false)mixed

Този статичен метод се опитва да изчисли ExpressionNode по време на компилация и да върне неговата съответстваща PHP стойност. Работи надеждно само за прости литерални възли (StringNode, IntegerNode, FloatNode, BooleanNode, NullNode) и инстанции на ArrayNode, съдържащи само такива изчислими елементи.

Ако $constants е зададено на true, той също ще се опита да разреши ConstantFetchNode и ClassConstantFetchNode чрез проверка с defined() и използване на constant().

Ако възелът съдържа променливи, извиквания на функции или други динамични елементи, той не може да бъде изчислен по време на компилация и методът ще хвърли InvalidArgumentException.

Случай на употреба: Получаване на статичната стойност на аргумент на таг по време на компилация за вземане на решения по време на компилация.

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) {
		// Аргументът не беше статичен литерален низ
		return null;
	}
}

toText (?Node $node): ?string

Този статичен метод е полезен за извличане на чисто текстово съдържание от прости възли. Работи предимно с:

  • TextNode: Връща неговото $content.
  • FragmentNode: Конкатенира резултата от toText() за всички негови деца. Ако някое дете не може да се преобразува в текст (напр. съдържа PrintNode), връща null.
  • NopNode: Връща празен низ.
  • Други типове възли: Връща null.

Случай на употреба: Получаване на статичното текстово съдържание на стойността на HTML атрибут или прост HTML елемент за анализ по време на компилационен проход.

use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\Html\AttributeNode;

function getStaticAttributeValue(AttributeNode $attr): ?string
{
	// $attr->value обикновено е AreaNode (като FragmentNode или TextNode)
	return NodeHelpers::toText($attr->value);
}

// Пример за използване в проход:
// if ($node instanceof Html\ElementNode && $node->name === 'meta') {
//     $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name'));
//     if ($nameAttrValue === 'description') { ... }
// }

NodeHelpers може да опрости вашите компилационни проходи, като предостави готови решения за често срещани задачи за обхождане и анализ на AST.

Практически примери

Нека приложим концепциите за обхождане и модификация на AST за решаване на някои практически проблеми. Тези примери демонстрират често срещани модели, използвани в компилационните проходи.

Автоматично добавяне на loading="lazy" към <img>

Съвременните браузъри поддържат нативно мързеливо зареждане за изображения с помощта на атрибута loading="lazy". Нека създадем проход, който автоматично добавя този атрибут към всички тагове <img>, които все още нямат атрибут 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,
		// Можем да използваме 'enter', тъй като модифицираме възела директно
		// и не зависим от децата за това решение.
		enter: function (Node $node) {
			// Това HTML елемент с име 'img' ли е?
			if ($node instanceof Html\ElementNode && $node->name === 'img') {
				// Гарантираме, че възелът на атрибутите съществува
				$node->attributes ??= new Nodes\FragmentNode;

				// Проверяваме дали вече съществува атрибут 'loading' (без значение от регистъра)
				foreach ($node->attributes->children as $attrNode) {
					if ($attrNode instanceof Html\AttributeNode
						&& $attrNode->name instanceof Nodes\TextNode // Статично име на атрибут
						&& strtolower($attrNode->name->content) === 'loading'
					) {
						return;
					}
				}

				// Добавяме интервал, ако атрибутите не са празни
				if ($node->attributes->children) {
					$node->attributes->children[] = new Nodes\TextNode(' ');
				}

				// Създаваме нов възел на атрибута: loading="lazy"
				$node->attributes->children[] = new Html\AttributeNode(
					name: new Nodes\TextNode('loading'),
					value: new Nodes\TextNode('lazy'),
					quote: '"',
				);
				// Промяната се прилага директно в обекта, не е необходимо нищо да се връща.
			}
		},
	);
}

Обяснение:

  • Visitor enter търси възли Html\ElementNode с име img.
  • Итерира през съществуващите атрибути ($node->attributes->children) и проверява дали атрибутът loading вече присъства.
  • Ако не е намерен, създава нов Html\AttributeNode, представляващ loading="lazy".

Проверка на извиквания на функции

Компилационните проходи са основата на Latte Sandbox. Въпреки че истинският Sandbox е сложен, можем да демонстрираме основния принцип на проверка за забранени извиквания на функции.

Цел: Предотвратяване на използването на потенциално опасната функция shell_exec в рамките на изрази в шаблона.

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]; // Прост списък

	$traverser = new NodeTraverser;
	(new NodeTraverser)->traverse(
		$templateNode,
		enter: function (Node $node) use ($forbiddenFunctions) {
			// Това възел на директно извикване на функция ли е?
			if ($node instanceof Php\Expression\FunctionCallNode
				&& $node->name instanceof Php\NameNode
				&& isset($forbiddenFunctions[strtolower((string) $node->name)])
			) {
				throw new SecurityViolationException(
					"Функцията {$node->name}() не е разрешена.",
					$node->position,
				);
			}
		},
	);
}

Обяснение:

  • Дефинираме списък със забранени имена на функции.
  • Visitor enter проверява FunctionCallNode.
  • Ако името на функцията ($node->name) е статичен NameNode, проверяваме неговото представяне като низ с малки букви спрямо нашия забранен списък.
  • Ако е намерена забранена функция, хвърляме Latte\SecurityViolationException, която ясно показва нарушение на правилото за сигурност и спира компилацията.

Тези примери показват как компилационните проходи с използването на NodeTraverser могат да бъдат използвани за анализ, автоматични модификации и налагане на ограничения за сигурност чрез директно взаимодействие със структурата на AST на шаблона.

Добри практики

При писане на компилационни проходи имайте предвид тези насоки за създаване на стабилни, поддържаеми и ефективни разширения:

  • Редът е важен: Бъдете наясно с реда, в който се изпълняват проходите. Ако вашият проход зависи от структурата на AST, създадена от друг проход (напр. основни проходи на Latte или друг персонализиран проход), или ако други проходи могат да зависят от вашите модификации, използвайте механизма за сортиране, предоставен от Extension::getPasses(), за да дефинирате зависимости (before/after). Вижте документацията за Extension::getPasses() за подробности.
  • Една отговорност: Стремете се към проходи, които изпълняват една добре дефинирана задача. За сложни трансформации обмислете разделянето на логиката на няколко прохода – може би един за анализ и друг за модификация, базирана на резултатите от анализа. Това подобрява прегледността и тестваемостта.
  • Производителност: Помнете, че компилационните проходи добавят време към компилацията на шаблона (въпреки че това обикновено се случва само веднъж, докато шаблонът не се промени). Избягвайте изчислително скъпи операции във вашите проходи, ако е възможно. Използвайте оптимизации на обхождането като NodeTraverser::DontTraverseChildren и NodeTraverser::StopTraversal винаги, когато знаете, че не е необходимо да посещавате определени части от AST.
  • Използвайте NodeHelpers: За често срещани задачи като търсене на специфични възли или статично изчисляване на прости изрази, проверете дали Latte\Compiler\NodeHelpers не предлага подходящ метод, преди да пишете собствена логика с NodeTraverser. Това може да спести време и да намали количеството подготвителен код.
  • Обработка на грешки: Ако вашият проход открие грешка или невалидно състояние в AST на шаблона, хвърлете Latte\CompileException (или Latte\SecurityViolationException за проблеми със сигурността) с ясно съобщение и релевантен обект Position (обикновено $node->position). Това предоставя полезна обратна връзка на разработчика на шаблона.
  • Идемпотентност (ако е възможно): В идеалния случай, изпълнението на вашия проход няколко пъти върху същия AST трябва да произведе същия резултат като еднократното му изпълнение. Това не винаги е изпълнимо, но опростява отстраняването на грешки и разсъжденията за взаимодействията на проходите, ако бъде постигнато. Например, уверете се, че вашият модификационен проход проверява дали модификацията вече е приложена, преди да я приложи отново.

Следвайки тези практики, можете ефективно да използвате компилационните проходи, за да разширите възможностите на Latte по мощен и надежден начин, допринасяйки за по-безопасна, по-оптимизирана или функционално по-богата обработка на шаблони.

версия: 3.0