Расширение Latte

Latte разработано с учетом возможности расширения. Хотя стандартный набор тегов, фильтров и функций охватывает многие случаи использования, часто возникает необходимость добавить собственную специфическую логику или вспомогательные инструменты. Эта страница предоставляет обзор способов расширения Latte, чтобы оно идеально соответствовало требованиям вашего проекта — от простых помощников до сложного нового синтаксиса.

Способы расширения Latte

Вот краткий обзор основных способов настройки и расширения Latte:

  • Пользовательские фильтры: Для форматирования или преобразования данных непосредственно в выводе шаблона (например, {$var|myFilter}). Идеально подходят для таких задач, как форматирование дат, редактирование текста или применение специфического экранирования. Вы также можете использовать их для изменения больших блоков HTML-содержимого, обернув содержимое анонимным {block} и применив к нему пользовательский фильтр.
  • Пользовательские функции: Для добавления повторно используемой логики, которую можно вызывать в выражениях шаблона (например, {myFunction($arg1, $arg2)}). Полезны для вычислений, доступа к вспомогательным функциям приложения или генерации небольших фрагментов контента.
  • Пользовательские теги: Для создания совершенно новых языковых конструкций ({mytag}...{/mytag} или n:mytag). Теги предлагают наибольшие возможности, позволяют определять собственные структуры, управлять парсингом шаблона и реализовывать сложную логику рендеринга.
  • Проходы компиляции: Функции, которые изменяют абстрактное синтаксическое дерево (AST) шаблона после парсинга, но до генерации PHP-кода. Используются для продвинутых оптимизаций, проверок безопасности (например, Sandbox) или автоматических изменений кода.
  • Пользовательские загрузчики: Для изменения способа, которым Latte ищет и загружает файлы шаблонов (например, загрузка из базы данных, зашифрованного хранилища и т. д.).

Выбор правильного метода расширения является ключевым. Прежде чем создавать сложный тег, подумайте, не будет ли достаточно более простого фильтра или функции. Давайте рассмотрим это на примере: реализация генератора Lorem ipsum, который в качестве аргумента принимает количество слов для генерации.

  • Как тег? {lipsum 40} – Возможно, но теги больше подходят для управляющих структур или генерации сложных тегов. Теги нельзя использовать непосредственно в выражениях.
  • Как фильтр? {=40|lipsum} – Технически это работает, но фильтры предназначены для преобразования входного значения. Здесь 40 — это аргумент, а не значение, которое преобразуется. Это кажется семантически неправильным.
  • Как функция? {lipsum(40)} – Это самое естественное решение! Функции принимают аргументы и возвращают значения, что идеально подходит для использования в любом выражении: {var $text = lipsum(40)}.

Общая рекомендация: Используйте функции для вычислений/генерации, фильтры для преобразования и теги для новых языковых конструкций или сложных тегов. Проходы используйте для манипуляции AST, а загрузчики для получения шаблонов.

Прямая регистрация

Для вспомогательных инструментов, специфичных для проекта, или быстрых расширений Latte позволяет напрямую регистрировать фильтры и функции в объекте Latte\Engine.

Для регистрации фильтра используйте метод addFilter(). Первым аргументом вашей функции-фильтра будет значение перед символом |, а последующие аргументы — это те, которые передаются после двоеточия :.

$latte = new Latte\Engine;

// Определение фильтра (вызываемый объект: функция, статический метод и т.д.)
$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length);

// Регистрация
$latte->addFilter('truncate', $myTruncate);

// Использование в шаблоне: {$text|truncate} или {$text|truncate:100}

Вы также можете зарегистрировать Загрузчик фильтров, функцию, которая динамически предоставляет вызываемые объекты фильтров по запрошенному имени:

$latte->addFilterLoader(fn(string $name) => /* возвращает вызываемый объект или null */);

Для регистрации функции, используемой в выражениях шаблона, используйте addFunction().

$latte = new Latte\Engine;

// Определение функции
$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6;

// Регистрация
$latte->addFunction('isWeekend', $isWeekend);

// Использование в шаблоне: {if isWeekend($myDate)}Выходной!{/if}

Больше информации вы найдете в разделах Создание пользовательских фильтров и Функций.

Надежный способ: Расширение Latte

Хотя прямая регистрация проста, стандартным и рекомендуемым способом упаковки и распространения расширений Latte является использование классов Extension. Extension служит центральной точкой конфигурации для регистрации нескольких тегов, фильтров, функций, проходов компиляции и других элементов.

Зачем использовать Extensions?

  • Организация: Сохраняет связанные расширения (теги, фильтры и т. д. для конкретной функции) вместе в одном классе.
  • Повторное использование и обмен: Легко упаковывать свои расширения для использования в других проектах или для обмена с сообществом (например, через Composer).
  • Полная мощь: Пользовательские теги и проходы компиляции можно регистрировать только через Extensions.

Регистрация Extension

Extension регистрируется в Latte с помощью метода addExtension() (или через файл конфигурации):

$latte = new Latte\Engine;
$latte->addExtension(new MyProjectExtension);

Если вы зарегистрируете несколько расширений, и они определяют теги, фильтры или функции с одинаковыми именами, приоритет будет иметь последнее добавленное расширение. Это также означает, что ваши расширения могут переопределять нативные теги/фильтры/функции.

Каждый раз, когда вы вносите изменения в класс и автоматическое обновление не отключено, Latte автоматически перекомпилирует ваши шаблоны.

Создание Extension

Для создания собственного расширения вам нужно создать класс, который наследуется от Latte\Extension. Чтобы представить, как выглядит такое расширение, посмотрите на встроенное CoreExtension.

Рассмотрим методы, которые вы можете реализовать:

beforeCompile (Latte\Engine $engine)void

Вызывается перед компиляцией шаблона. Метод можно использовать, например, для инициализаций, связанных с компиляцией.

getTags(): array

Вызывается при компиляции шаблона. Возвращает ассоциативный массив имя тега ⇒ вызываемый объект, который представляет собой функции для парсинга тегов. Подробнее.

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 xor after указывает, какие теги сортируются до или после тега.

public function getTags(): array
{
	return [
		'foo' => self::order(FooNode::create(...), before: 'bar'),
		'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']),
	];
}

getPasses(): array

Вызывается при компиляции шаблона. Возвращает ассоциативный массив имя прохода ⇒ вызываемый объект, который представляет собой функции, представляющие так называемые проходы компиляции, которые проходят и изменяют AST.

Здесь также можно использовать вспомогательный метод order(). Значение параметров before или after может быть *, что означает до/после всех.

public function getPasses(): array
{
	return [
		'optimize' => Passes::optimizePass(...),
		'sandbox' => self::order($this->sandboxPass(...), before: '*'),
		// ...
	];
}

beforeRender (Latte\Engine $engine)void

Вызывается перед каждым рендерингом шаблона. Метод может быть использован, например, для инициализации переменных, используемых во время рендеринга.

getFilters(): array

Вызывается перед рендерингом шаблона. Возвращает фильтры как ассоциативный массив имя фильтра ⇒ вызываемый объект. Подробнее.

public function getFilters(): array
{
	return [
		'batch' => $this->batchFilter(...),
		'trim' => $this->trimFilter(...),
		// ...
	];
}

getFunctions(): array

Вызывается перед рендерингом шаблона. Возвращает функции как ассоциативный массив имя функции ⇒ вызываемый объект. Подробнее.

public function getFunctions(): array
{
	return [
		'clamp' => $this->clampFunction(...),
		'divisibleBy' => $this->divisibleByFunction(...),
		// ...
	];
}

getProviders(): array

Вызывается перед рендерингом шаблона. Возвращает массив поставщиков, которые обычно являются объектами, используемыми тегами во время выполнения. Доступ к ним осуществляется через $this->global->.... Подробнее.

public function getProviders(): array
{
	return [
		'myFoo' => $this->foo,
		'myBar' => $this->bar,
		// ...
	];
}

getCacheKey (Latte\Engine $engine)mixed

Вызывается перед рендерингом шаблона. Возвращаемое значение становится частью ключа, хеш которого содержится в имени файла скомпилированного шаблона. Таким образом, для разных возвращаемых значений Latte сгенерирует разные файлы кеша.

версия: 3.0