Расширение Latte
Latte спроектирован с прицелом на расширяемость. Хотя его стандартный набор тегов, фильтров и функций покрывает множество сценариев, часто нужно добавить собственную логику или помощники. Эта страница даёт обзор того, как расширить Latte, чтобы он идеально подошёл требованиям вашего проекта – от простых помощников до сложного нового синтаксиса.
Способы расширения Latte
Вот краткий обзор основных способов настроить и расширить Latte:
- Пользовательские фильтры: для
форматирования или преобразования данных прямо в выводе шаблона
(например,
{$var|myFilter}). Идеально подходят для форматирования дат, работы с текстом или особого экранирования. С их помощью можно изменять и крупные блоки HTML-содержимого, обернув содержимое в анонимный{block}и применив к нему свой фильтр. - Пользовательские функции: для
добавления переиспользуемой логики, которую можно вызывать в
выражениях шаблона (например,
{myFunction($arg1, $arg2)}). Полезны для вычислений, обращения к помощникам приложения или генерации небольших кусочков содержимого. - Пользовательские теги: для создания
совершенно новых языковых конструкций (
{mytag}...{/mytag}илиn:mytag). Теги дают больше всего возможностей: они позволяют определять собственные структуры, управлять разбором шаблона и реализовывать сложную логику отрисовки. - Проходы компилятора: функции, которые изменяют абстрактное синтаксическое дерево (AST) шаблона после разбора, но до генерации PHP-кода. Применяются для продвинутых оптимизаций, проверок безопасности (как песочница) или автоматических правок кода.
- Пользовательские загрузчики: для изменения того, как Latte находит и загружает файлы шаблонов (например, загрузка из базы данных, из зашифрованного хранилища и так далее).
Выбор правильного способа расширения принципиально важен. Прежде чем создавать сложный тег, подумайте, не хватит ли более простого фильтра или функции. Проиллюстрируем это примером: реализуем генератор Lorem ipsum, который принимает число слов для генерации в качестве аргумента.
- Как тег?
{lipsum 40}– возможно, но теги лучше подходят для управляющих конструкций или генерации сложной разметки. Теги нельзя использовать прямо внутри выражений. - Как фильтр?
{=40|lipsum}– технически работает, но фильтры предназначены для преобразования входного значения. Здесь40– это аргумент, а не преобразуемое значение. Семантически это выглядит неправильно. - Как функция?
{lipsum(40)}– вот самый естественный вариант! Функции принимают аргументы и возвращают значения, поэтому идеально подходят для использования в любом выражении:{var $text = lipsum(40)}.
Общая рекомендация: используйте функции для вычислений и генерации, фильтры для преобразования, а теги для новых языковых структур или сложной разметки. Проходы применяйте для работы с AST, а загрузчики для получения шаблонов.
Прямая регистрация
Для помощников, нужных лишь одному проекту, или для быстрых
дополнений Latte позволяет регистрировать фильтры и функции прямо на
объекте Latte\Engine.
Для регистрации фильтра используйте addFilter(). Первым аргументом
вашей функции-фильтра будет значение перед вертикальной чертой
|, а последующими – те, что переданы после двоеточия :.
$latte = new Latte\Engine;
// Определение фильтра (callable: функция, статический метод и т. д.)
$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length);
// Регистрируем его
$latte->addFilter('truncate', $myTruncate);
// Использование в шаблоне: {$text|truncate} или {$text|truncate:100}
Для регистрации функции, доступной в выражениях шаблона, используйте
addFunction().
$latte = new Latte\Engine;
// Определение функции
$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6;
// Регистрируем её
$latte->addFunction('isWeekend', $isWeekend);
// Использование в шаблоне: {if isWeekend($myDate)}Weekend!{/if}
Подробности см. в разделах Создание пользовательских фильтров и Функции.
Надёжный путь: расширение Latte
Прямая регистрация проста, но стандартный и рекомендуемый способ собрать и распространять доработки Latte – это классы расширений. Расширение служит центральной точкой настройки, где регистрируются несколько тегов, фильтров, функций, проходов компилятора и прочего.
Зачем нужны расширения?
- Организация: держит связанные доработки (теги, фильтры и так далее для одной возможности) вместе в одном классе.
- Переиспользование и распространение: легко упаковать расширение для использования в других проектах или для передачи сообществу (например, через Composer).
- Полная мощь: пользовательские теги и проходы компилятора можно зарегистрировать только через расширения.
Регистрация расширения
Расширение регистрируется в Latte методом addExtension() (или через конфигурационный файл):
$latte = new Latte\Engine;
$latte->addExtension(new MyProjectExtension);
Если вы регистрируете несколько расширений и они определяют одноимённые теги, фильтры или функции, побеждает добавленное последним. Отсюда же следует, что ваши расширения могут переопределять встроенные теги, фильтры и функции.
Всякий раз, когда вы меняете класс и автообновление не отключено, Latte автоматически перекомпилирует ваши шаблоны.
Создание расширения
Чтобы создать собственное расширение, нужно создать класс, наследующий от Latte\Extension. Чтобы представить себе, как выглядит расширение, посмотрите на встроенное CoreExtension.
Посмотрим, какие методы можно реализовать:
beforeCompile (Latte\Engine $engine): void
Вызывается перед компиляцией шаблона. Метод можно использовать, например, для инициализаций, связанных с компиляцией.
getTags(): array
Вызывается при компиляции шаблона. Возвращает ассоциативный массив имя тега ⇒ callable, где значения – функции разбора тегов. Подробнее.
public function getTags(): array
{
return [
'foo' => FooNode::create(...),
'bar' => BarNode::create(...),
'n:baz' => NBazNode::create(...),
// ...
];
}
Тег n:baz представляет собой чистый n:атрибут, то есть тег, который можно
записать только как атрибут.
В случае тегов foo и bar Latte автоматически распознает,
парные ли они, и если да, их можно автоматически записывать в виде
n:атрибутов, включая варианты с префиксами n:inner-foo и
n:tag-foo.
Порядок выполнения таких n:атрибутов определяется их порядком в
массиве, который возвращает getTags(). Поэтому n:foo всегда
выполняется раньше n:bar, даже если в HTML-теге атрибуты перечислены
в обратном порядке как <div n:bar="..." n:foo="...">.
Если вам нужно задать порядок n:атрибутов между несколькими
расширениями, используйте вспомогательный метод order(), где
параметр before и/или after определяет, какие теги идут до или
после данного тега.
public function getTags(): array
{
return [
'foo' => self::order(FooNode::create(...), before: 'bar'),
'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']),
];
}
getPasses(): array
Вызывается при компиляции шаблона. Возвращает ассоциативный массив имя прохода ⇒ callable, где значения – функции, представляющие так называемые проходы компилятора, которые обходят и изменяют AST.
Здесь тоже можно использовать вспомогательный метод order().
Значением параметров before или after может быть * со
смыслом “перед всеми” или “после всех”.
public function getPasses(): array
{
return [
'optimize' => Passes::optimizePass(...),
'sandbox' => self::order($this->sandboxPass(...), before: '*'),
// ...
];
}
beforeRender (Latte\Runtime\Template $template): void
Вызывается перед каждой отрисовкой шаблона. Метод можно использовать, например, для инициализации переменных, применяемых при отрисовке.
afterRender (Latte\Runtime\Template $template): void
Вызывается после каждой отрисовки шаблона. Он выполняется, даже если
отрисовка завершилась досрочно через {exitIf} или была прервана
исключением, поэтому это подходящее место для уборки или измерений.
getFilters(): array
Вызывается при регистрации расширения методом addExtension().
Возвращает фильтры в виде ассоциативного массива имя фильтра ⇒
callable. Подробнее.
public function getFilters(): array
{
return [
'batch' => $this->batchFilter(...),
'trim' => $this->trimFilter(...),
// ...
];
}
getFunctions(): array
Вызывается при регистрации расширения методом addExtension().
Возвращает функции в виде ассоциативного массива имя функции ⇒
callable. Подробнее.
public function getFunctions(): array
{
return [
'clamp' => $this->clampFunction(...),
'divisibleBy' => $this->divisibleByFunction(...),
// ...
];
}
getProviders(): array
Вызывается при регистрации расширения методом addExtension().
Возвращает массив провайдеров, которыми обычно служат объекты,
используемые тегами во время выполнения. Обращение к ним идёт через
$this->global->.... Подробнее.
public function getProviders(): array
{
return [
'myFoo' => $this->foo,
'myBar' => $this->bar,
// ...
];
}
getCacheKey (Latte\Engine $engine): mixed
Вызывается перед отрисовкой шаблона. Возвращаемое значение становится частью ключа, хеш которого входит в имя файла скомпилированного шаблона. Таким образом, для разных возвращаемых значений Latte создаст разные файлы кеша.