Създаване на разширение
Разширението е клас за многократна употреба, който може да дефинира персонализирани тагове, филтри, функции, доставчици и т.н.
Създаваме разширения, когато искаме да използваме повторно настройките на Latte в различни проекти или да ги споделим с други хора. Полезно е също така да създадете разширение за всеки уеб проект, което да съдържа всички специфични тагове и филтри, които искате да използвате в шаблоните на проектите си.
Клас за разширение
Extension е клас, наследен от Latte\Extension. Регистрира се в Latte с
помощта на addExtension()
(или чрез конфигурационен файл):
Ако регистрирате няколко разширения и те дефинират идентични по име тагове, филтри или функции, печели последното добавено разширение. Това означава също, че разширенията ви могат да отменят собствените си тагове/филтри/функции.
Когато направите промени в клас и автоматичното обновяване не е изключено, Latte автоматично ще прекомпилира вашите шаблони.
Класът може да реализира някой от следните методи:
За да получите представа как изглежда разширението, разгледайте вграденото разширение „CoreExtension:https://github.com/…xtension.php“.
beforeCompile(Latte\Engine $engine): void
Извиква се преди компилирането на шаблона. Методът може да се използва например за инициализация, свързана с компилирането.
getTags(): array
Извиква се при компилиране на шаблон. Връща асоциативен масив име на таг ⇒ callable, които са функции за парсване на тагове.
Тагът n:baz
е чист n:атрибут, т.е. това е таг, който може да бъде
записан само като атрибут.
В случая с таговете foo
и bar
Latte автоматично разпознава
дали са двойки и ако е така, те могат да бъдат автоматично записани с
помощта на n:attributes, включително варианти, предхождани от n:inner-foo
и
n:tag-foo
.
Редът, по който се изпълняват тези n:атрибути, се определя от реда им в
масива, върнат от getTags()
. По този начин n:foo
се изпълнява
винаги преди n:bar
, дори ако атрибутите са изброени в обратен ред в
HTML тага като <div n:bar="..." n:foo="...">
.
Ако трябва да определите реда на изпълнение на n:атрибути за няколко
разширения, използвайте помощния метод order()
, където параметърът
before
xor after
определя кои тагове ще бъдат подредени преди
или след тага .
getPasses(): array
Извиква се при компилиране на шаблона. Връща асоциативен масив name pass ⇒ callable, които са функции, представляващи така наречените passes на компилатора, които заобикалят и модифицират AST.
Отново може да се използва спомагателен метод order()
. Стойността
на параметъра before
или after
може да бъде *
със
стойност преди/след всички.
beforeRender(Latte\Engine $engine): void
Извиква се преди всяко визуализиране на шаблона. Методът може да се използва например за инициализиране на променливи, използвани по време на рендирането.
getFilters(): array
Извиква се преди шаблонът да бъде визуализиран. Връща филтрите като асоциативен масив име на филтър ⇒ извикващ се.
getFunctions(): array
Извиква се преди шаблонът да бъде визуализиран. Връща функциите като асоциативен масив име на функция ⇒ callable.
getProviders(): array
Извиква се преди шаблонът да бъде визуализиран. Връща масив от
доставчици, които обикновено са маркирани по време на изпълнение
обекти. Достъпът до тях се осъществява чрез $this->global->...
.
getCacheKey(Latte\Engine $engine): mixed
Извиква се преди шаблонът да бъде визуализиран. Върнатата стойност става част от ключа, чийто хеш се съдържа в името на файла на компилирания шаблон. По този начин Latte ще генерира различни кеш файлове за различните върнати стойности.
Как работи Latte?
За да разберете как да дефинирате потребителски тагове или пропуски на компилатора, трябва да разберете как работи Latte под капака.
Съставянето на модели в Latte опростено работи по следния начин:
- Първо, parser разбива изходния код на шаблона на малки фрагменти (токени) за по-лесна обработка.
- След това парсерът преобразува потока от символи в смислено дърво от възли (Abstract Syntax Tree, AST).
- Накрая компилаторът генерира PHP клас от AST, който съпоставя шаблона, и го съхранява в кеша си.
Всъщност съставянето е малко по-сложно. Latte има два лексикатора и анализатора: един за HTML шаблона и един за PHP-подобния код вътре в таговете. Освен това парсингът не се извършва след токенизацията, а лексикаторът и парсерът работят паралелно в две „нишки“ и са координирани. Това е ракетна наука :-)
Освен това всички тагове имат свои собствени процедури за парсване. Когато парсерът срещне таг, той извиква своята функция за парсиране (тя връща Extension::getTags()). Задачата му е да анализира аргументите на тага и, в случай на сдвоени тагове, вътрешното съдържание. Той връща възел, който става част от AST. За повече информация вижте раздел Разработване на етикети.
Когато анализаторът завърши работата си, получаваме пълния AST,
представящ модела. Коренният възел е Latte\Compiler\Nodes\TemplateNode
.
Отделните възли в дървото представляват не само таговете, но и HTML
елементите, техните атрибути, всички изрази, използвани в таговете,
и т.н.
След това се появяват т.нар. passes на компилатора, които представляват функции (връщани от Extension::getPasses()), които модифицират AST.
Целият процес – от зареждането на съдържанието на шаблона, през парсирането, до генерирането на получения файл – може да бъде оптимизиран с този код, с който можете да експериментирате и да изхвърляте междинни резултати:
Пример за AST
За да получите по-добра представа за AST, ще добавим пример. Това е оригиналният шаблон:
И това е нейното представяне като 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') ) ) ) )
Потребителски етикети
За дефинирането на нов етикет са необходими три стъпки:
- дефиниране на функция за разбор на таг (отговорна за разбора на тага във възел)
- създаване на клас за възли (отговарящ за генерирането на PHP код и обхождането на AST)
- регистриране на тага с Extension::getTags()
Функция за парсване на етикети
Таговете се анализират от функцията за анализ (тази, която се връща от
Extension::getTags()). Неговата задача е да анализира и проверява
всички аргументи в даден таг (за целта използва TagParser). Също така, ако
тагът е двойка, той ще поиска от TemplateParser да анализира и върне
вътрешното съдържание. Функцията създава и връща възел, който
обикновено е дъщерен възел Latte\Compiler\Nodes\StatementNode
, и той става част
от AST.
Създаваме клас за всеки възел, което ще направим сега, и елегантно
поставяме функцията за парсинг в него като статична фабрика. Като
пример, нека се опитаме да създадем познатия таг {foreach}
:
На функцията за парсинг create()
се предава обект Latte\Compiler\Tag, който носи основна
информация за тага (дали е класически таг или n:атрибут, на кой ред се
намира и т.н.) и основно се отнася до обекта Latte\Compiler\TagParser в
$tag->parser
.
Ако даден таг трябва да има аргументи, проверете за тях, като извикате
$tag->expectArguments()
. Методите на обекта $tag->parser
са на
разположение за анализирането им :
parseExpression(): ExpressionNode
за израз, подобен на PHP (например10 + 3
).parseUnquotedStringOrExpression(): ExpressionNode
за израз или низ без кавички- Съдържание на масива
parseArguments(): ArrayNode
(напр.10, true, foo => bar
) parseModifier(): ModifierNode
за модификатор (напр.|upper|truncate:10
)parseType(): expressionNode
за подсказка за типа (напримерint|string
илиFoo\Bar[]
)
и на ниско ниво Latte\Compiler\TokenStream, работещи директно с токени:
$tag->parser->stream->consume(...): Token
$tag->parser->stream->tryConsume(...): ?Token
Latte разширява синтаксиса на PHP по малки начини, например чрез добавяне
на модификатори, съкращаване на тернарни оператори или позволяване на
запис на прости буквено-цифрови низове без кавички. Ето защо
използваме термина PHP-like вместо PHP. Например, методът
parseExpression()
анализира foo
като 'foo'
. Също така
нецитиран низ е специален случай на низ, който също не се нуждае от
кавички, но в същото време не е задължително да бъде буквено-цифров.
Например, това е пътят до файла в тага {include ../file.latte}
. За
анализирането му се използва методът parseUnquotedStringOrExpression()
.
Изучаването на класовете възли, които съставляват Latte, е най-добрият начин да научите тънкостите на процеса на разбор.
Нека се върнем към етикета {foreach}
. В него очакваме аргументи с
формата expression + 'as' + second expression
, които анализираме по
следния начин:
Изразите, които сме записали в променливите $expression
и
$value
, са вложени възли.
Дефинирайте променливите на подвъзела като публични, за да могат да бъдат променяни на по-късни етапи от обработката, ако е необходимо. Също така трябва да ги направите достъпни за обхождане.
За сдвоени тагове като нашия методът трябва да позволява на TemplateParser
да анализира вътрешното съдържание на тага. Това се обработва от
yield
, който връща двойката вътрешно съдържание, краен таг].
Съхраняваме вътрешното съдържание в променлива $node->content
.
Ключовата дума yield
води до прекратяване на метода create()
,
като връща управлението обратно към TemplateParser, който продължава да
анализира съдържанието, докато достигне тага end. След това той предава
управлението обратно на метода create()
, който продължава оттам,
където е спрял. Използването на метода yield
, автоматично връща
Generator
.
Можете също така да подадете масив от имена на тагове на yield
, за
които искате да спрете парсирането, ако се появят преди крайния таг.
Това ни помага да прилагаме {foreach}...{else}...{/foreach}
конструкция. Ако
срещнем {else}
, анализираме съдържанието след него до
$node->elseContent
:
Връщането на възела завършва разбора на тага.
Генериране на PHP код
Всеки възел трябва да прилага метода print()
. Връща код на PHP,
който визуализира дадената част от шаблона (код за изпълнение). Като
параметър той се предава на обекта Latte\Compiler\PrintContext, който има
полезен метод format()
, улесняващ сглобяването на получения код.
Методът format(string $mask, ...$args)
приема следните заместители
в маска:
%node
отпечатва възела%dump
експортира стойността в PHP%raw
вмъква текст директно, без преобразувания%args
отпечатва ArrayNode като аргументи за извикване на функция%line
отпечатва коментар с номер на реда%escape(...)
избягва съдържанието%modify(...)
прилага модификатор%modifyContent(...)
прилага модификатор към блокове
Нашата функция print()
може да изглежда по следния начин (за
улеснение пренебрегваме клона else
)
Променливата $this->position
вече е дефинирана от класа Latte\Compiler\Node и се задава от
анализатора. Той съдържа обект Latte\Compiler\Position с позицията на
тага в изходния код като номер на ред и колона.
Кодът по време на изпълнение може да използва спомагателни
променливи. За да се избегне сблъсък с променливи, използвани от самия
шаблон, е прието те да се предхождат от $ʟ__
.
Времето за изпълнение може също така да използва произволни
стойности, които се предават на шаблона като доставчици чрез метода Extension::getProviders(). Достъпът до тях се осъществява чрез
$this->global->...
.
Байпас AST
За да може да се прегледа дървото AST в дълбочина, трябва да се приложи
методът getIterator()
. Това ще осигури достъп до вложените възли:
Обърнете внимание, че getIterator()
връща връзка. Това позволява на
посетителите на възела да заменят отделни възли с други възли.
Ако даден възел има подвъзли, е необходимо да се приложи този метод и да се осигури достъп до всички подвъзли. В противен случай може да се стигне до пробив в сигурността. Например режимът „пясъчник“ няма да може да контролира подвъзлите и да гарантира, че в тях няма да бъдат извикани неразрешени проекти.
Тъй като ключовата дума yield
трябва да присъства в тялото на
метода, дори ако той няма подчинени възли, запишете я по
следния начин:
AuxiliaryNode
Ако създавате нов таг за Latte, препоръчително е да създадете за него
специален клас възел, който ще го представя в дървото AST (вж. класа
ForeachNode
в примера по-горе). В някои случаи може да ви бъде полезен
тривиалният помощен клас AuxiliaryNode, който ви
позволява да предадете тялото на метода print()
и списъка на
възлите, направени достъпни от метода getIterator()
, като параметри на
конструктора:
Компилаторът предава
Пропусканията на компилатора са функции, които модифицират AST или събират информация в тях. Те се връщат чрез метода Extension::getPasses().
Обхождане на възли
Най-разпространеният начин за работа с AST е да се използва Latte\Compiler\NodeTraverser:
Функцията вход (т.е. посетител) се извиква, когато даден възел се срещне за първи път, преди да бъдат обработени неговите подвъзели. Функцията leave се извиква, след като са посетени всички възли. Често срещан модел е, че enter се използва за събиране на някаква информация, а след това leave извършва модификации въз основа на тази информация. В момента, в който се извика leave, всички кодове във възела ще са посетени и ще е събрана необходимата информация.
Как да променя AST? Най-лесният начин е просто да промените свойствата
на възела. Вторият начин е да замените изцяло възела, като върнете нов
възел. Пример: Следният код ще промени всички цели числа в AST в низове
(например 42 ще бъде заменено с '42'
).
Един AST може да съдържа хиляди възли и преминаването през всички възли може да бъде бавно. В някои случаи може да се избегне цялостно обхождане.
Ако търсите всички Html\ElementNode
в дървото, знаете, че след като
разгледате Php\ExpressionNode
, няма смисъл да проверявате всички негови
дъщерни възли, защото HTML не може да бъде вътре в изразите. В този случай
можете да кажете на байпасния модул да не отива във възела на класа:
Ако търсите само един конкретен възел, можете също така напълно да прекъснете обхождането, след като го намерите.
Асистенти на възли
Класът Latte\Compiler\NodeHelpers предоставя няколко метода, чрез които могат да се намерят AST възли, които отговарят на определено обратно повикване, и т.н. Показани са няколко примера: