Удлиняющий 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 работает под капотом. Все самое необходимое можно найти в главе Создание расширения.

версия: 3.0