Удлиняющий Latte
Latte очень гибок и может быть расширен множеством способов: вы можете добавить пользовательские фильтры, функции, теги, загрузчики и т.д. Мы покажем вам, как это сделать.
В этой главе описаны различные способы расширения Latte. Если вы хотите повторно использовать свои изменения в различных проектах или поделиться ими с другими, вам следует создать так называемое расширение.
Сколько дорог ведет в Рим?
Поскольку некоторые из способов расширения Latte могут смешиваться, давайте сначала попробуем объяснить различия между ними. В качестве примера попробуем реализовать генератор Lorem ipsum, которому передается количество слов для генерации.
Основной конструкцией языка Latte является тег. Мы можем реализовать генератор, расширив Latte новым тегом:
{lipsum 40}
Тег будет работать отлично. Однако генератор в виде тега может оказаться недостаточно гибким, поскольку его нельзя использовать в выражении. Кстати, на практике вам редко понадобится генерировать теги; и это хорошая новость, потому что теги – это более сложный способ расширения.
Хорошо, давайте попробуем создать фильтр вместо тега:
{=40|lipsum}
Опять же, приемлемый вариант. Но фильтр должен преобразовать
переданное значение во что-то другое. Здесь мы используем значение
40
, которое указывает на количество созданных слов, как аргумент
фильтра, а не как значение, которое мы хотим преобразовать.
Поэтому давайте попробуем использовать функцию:
{lipsum(40)}
Вот и все! Для этого конкретного примера создание функции – идеальная точка расширения. Вы можете вызвать ее в любом месте, где принимается выражение, например:
{var $text = lipsum(40)}
Фильтры
Создайте фильтр, зарегистрировав его имя и любой вызываемый элемент PHP, например, функцию:
$latte = new Latte\Engine;
$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // shortens the text to 10 characters
В этом случае было бы лучше, чтобы фильтр получал дополнительный параметр:
$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len));
Мы используем его в шаблоне следующим образом:
<p>{$text|shortify}</p>
<p>{$text|shortify:100}</p>
Как видите, функция получает в качестве следующих аргументов левую
часть фильтра перед трубой |
as the first argument and the arguments passed to the filter
after :
.
Конечно, функция, представляющая фильтр, может принимать любое количество параметров, также поддерживаются переменные параметры.
Если фильтр возвращает строку в HTML, то можно пометить ее так, чтобы Latte
не делал автоматическую (и, соответственно, двойную) экранировку. Это
избавляет от необходимости указывать |noescape
в шаблоне. Самый
простой способ – обернуть строку в объект Latte\Runtime\Html
, другой
способ – Контекстные фильтры.
$latte->addFilter('money', fn(float $amount) => new Latte\Runtime\Html("<i>$amount EUR</i>"));
В этом случае фильтр должен обеспечить корректное экранирование данных.
Фильтры, использующие класс
Второй способ определить фильтр – использовать класс. Мы создаем метод с
атрибутом TemplateFilter
:
class TemplateParameters
{
public function __construct(
// parameters
) {}
#[Latte\Attributes\TemplateFilter]
public function shortify(string $s, int $len = 10): string
{
return mb_substr($s, 0, $len);
}
}
$params = new TemplateParameters(/* ... */);
$latte->render('template.latte', $params);
Загрузчик фильтров
Вместо регистрации отдельных фильтров вы можете создать так называемый загрузчик, который представляет собой функцию, вызываемую с именем фильтра в качестве аргумента и возвращающую его вызываемый PHP-файл или null.
$latte->addFilterLoader([new Filters, 'load']);
class Filters
{
public function load(string $filter): ?callable
{
if (in_array($filter, get_class_methods($this))) {
return [$this, $filter];
}
return null;
}
public function shortify($s, $len = 10)
{
return mb_substr($s, 0, $len);
}
// ...
}
Контекстные фильтры
Контекстный фильтр – это фильтр, который принимает объект Latte\Runtime\FilterInfo в качестве первого параметра, за которым следуют другие параметры, как в случае с классическими фильтрами. Он регистрируется таким же образом, Latte сам распознает, что фильтр контекстный:
use Latte\Runtime\FilterInfo;
$latte->addFilter('foo', function (FilterInfo $info, string $str): string {
// ...
});
Контекстные фильтры могут определять и изменять тип содержимого,
который они получают в переменной $info->contentType
. Если фильтр
вызывается классически через переменную (например, {$var|foo}
), то
$info->contentType
будет содержать null.
Фильтр должен сначала проверить, поддерживается ли тип содержимого входной строки. Он также может изменить его. Пример фильтра, который принимает текст (или null) и возвращает HTML:
use Latte\Runtime\FilterInfo;
$latte->addFilter('money', function (FilterInfo $info, float $amount): string {
// first we check if the input's content-type is text
if (!in_array($info->contentType, [null, ContentType::Text])) {
throw new Exception("Filter |money used in incompatible content type $info->contentType.");
}
// change content-type to HTML
$info->contentType = ContentType::Html;
return "<i>$amount EUR</i>";
});
В этом случае фильтр должен обеспечить правильную экранировку данных.
Все фильтры, которые используются поверх блоков
(например, как {block|foo}...{/block}
), должны быть контекстными.
Функции
По умолчанию все собственные функции PHP могут использоваться в Latte, если только это не отключено в песочнице. Но вы также можете определить свои собственные функции. Они могут переопределять собственные функции.
Создайте функцию, зарегистрировав ее имя и любую вызываемую функцию PHP:
$latte = new Latte\Engine;
$latte->addFunction('random', function (...$args) {
return $args[array_rand($args)];
});
После этого использование будет таким же, как и при вызове PHP-функции:
{random(apple, orange, lemon)} // prints for example: apple
Функции, использующие класс
Второй способ определить функцию – использовать класс. Мы создаем метод с
атрибутом TemplateFunction
:
class TemplateParameters
{
public function __construct(
// parameters
) {}
#[Latte\Attributes\TemplateFunction]
public function random(...$args)
{
return $args[array_rand($args)];
}
}
$params = new TemplateParameters(/* ... */);
$latte->render('template.latte', $params);
Загрузчики
Загрузчики отвечают за загрузку шаблонов из источника, например, из
файловой системы. Они устанавливаются с помощью метода
setLoader()
:
$latte->setLoader(new MyLoader);
Встроенными загрузчиками являются:
FileLoader
Загрузчик по умолчанию. Загружает шаблоны из файловой системы.
Доступ к файлам можно ограничить, задав базовый каталог:
$latte->setLoader(new Latte\Loaders\FileLoader($templateDir));
$latte->render('test.latte');
StringLoader
Загружает шаблоны из строк. Этот загрузчик очень полезен для модульного тестирования. Он также может быть использован для небольших проектов, где имеет смысл хранить все шаблоны в одном PHP-файле.
$latte->setLoader(new Latte\Loaders\StringLoader([
'main.file' => '{include other.file}',
'other.file' => '{if true} {$var} {/if}',
]));
$latte->render('main.file');
Упрощенное использование:
$template = '{if true} {$var} {/if}';
$latte->setLoader(new Latte\Loaders\StringLoader);
$latte->render($template);
Создание пользовательского загрузчика
Loader – это класс, реализующий интерфейс Latte\Loader.
Теги
Одной из самых интересных возможностей шаблонизатора является возможность определять новые языковые конструкции с помощью тегов. Это также более сложная функциональность, и вам необходимо понимать, как внутренне работает Latte.
В большинстве случаев, однако, тег не нужен:
- если он должен генерировать некоторый вывод, используйте вместо него функцию
- если нужно изменить входные данные и вернуть их, используйте filter
- если нужно отредактировать участок текста, оберните его тегом
{block}
тегом и используйте фильтр - если он не должен был ничего выводить, а только вызывать функцию,
вызовите ее с помощью
{do}
Если вы все еще хотите создать тег, отлично! Все самое необходимое можно найти в разделе Создание расширения.
Передачи компилятора
Пассы компилятора – это функции, которые изменяют AST или собирают в них информацию. В Latte, например, песочница реализована таким образом: она обходит все узлы AST, находит вызовы функций и методов и заменяет их управляемыми вызовами.
Как и в случае с тегами, это более сложная функциональность, и вам нужно понимать, как Latte работает под капотом. Все самое необходимое можно найти в главе Создание расширения.