Создание пользовательских фильтров
Фильтры — это мощные инструменты для форматирования и
изменения данных непосредственно в шаблонах Latte. Они предлагают чистый
синтаксис с использованием символа пайпа (|
) для преобразования
переменных или результатов выражений в желаемый выходной формат.
Что такое фильтры?
Фильтры в Latte — это, по сути, PHP-функции, разработанные специально
для преобразования входного значения в выходное. Они применяются с
помощью записи с пайпом (|
) внутри выражений шаблона
({...}
).
Удобство: Фильтры позволяют инкапсулировать распространенные задачи форматирования (например, форматирование дат, изменение регистра, усечение) или манипуляции с данными в повторно используемые единицы. Вместо повторения сложного PHP-кода в ваших шаблонах вы можете просто применить фильтр:
{* Вместо сложного PHP для усечения: *}
{$article->text|truncate:100}
{* Вместо кода для форматирования даты: *}
{$event->startTime|date:'Y-m-d H:i'}
{* Применение нескольких преобразований: *}
{$product->name|lower|capitalize}
Читаемость: Использование фильтров делает шаблоны более понятными и более ориентированными на представление, поскольку логика преобразования перемещается в определение фильтра.
Контекстная чувствительность: Ключевым преимуществом фильтров в Latte является их способность быть контекстно-зависимыми. Это означает, что фильтр может распознавать тип содержимого, с которым он работает (HTML, JavaScript, простой текст и т. д.), и применять соответствующую логику или экранирование, что критически важно для безопасности и правильности, особенно при генерации HTML.
Интеграция с логикой приложения: Как и пользовательские функции, вызываемый PHP-объект, стоящий за фильтром, может быть замыканием (closure), статическим методом или методом экземпляра. Это позволяет фильтрам получать доступ к сервисам приложения или данным, если это необходимо, хотя их основной целью остается преобразование входного значения.
Latte по умолчанию предоставляет богатый набор стандартных фильтров. Пользовательские фильтры позволяют расширить этот набор форматированием и преобразованиями, специфичными для вашего проекта.
Если вам нужно выполнять логику, основанную на нескольких входах, или у вас нет основного значения для преобразования, вероятно, более подходящим будет использование пользовательской функции. Если вам нужно генерировать сложную разметку или управлять потоком шаблона, рассмотрите пользовательский тег.
Создание и регистрация фильтров
Существует несколько способов определения и регистрации пользовательских фильтров в Latte.
Прямая регистрация с помощью
addFilter()
Самый простой способ добавить фильтр — использовать метод
addFilter()
непосредственно на объекте Latte\Engine
. Вы указываете
имя фильтра (как он будет использоваться в шаблоне) и соответствующий
вызываемый PHP-объект.
$latte = new Latte\Engine;
// Простой фильтр без аргументов
$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.');
// Фильтр с необязательным аргументом
$latte->addFilter('shortify', function (string $s, int $len = 10): string {
return mb_substr($s, 0, $len);
});
// Фильтр, обрабатывающий массив
$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers));
Использование в шаблоне:
{$name|initial} {* Выведет 'J.' если $name 'John' *}
{$description|shortify} {* Использует длину по умолчанию 10 *}
{$description|shortify:50} {* Использует длину 50 *}
{$prices|sum} {* Выведет сумму элементов в массиве $prices *}
Передача аргументов:
Значение слева от пайпа (|
) всегда передается как первый
аргумент функции фильтра. Любые параметры, указанные после двоеточия
(:
) в шаблоне, передаются как следующие аргументы.
{$text|shortify:30}
// Вызывает PHP-функцию shortify($text, 30)
Регистрация с помощью расширения
Для лучшей организации, особенно при создании повторно используемых наборов фильтров или их распространении в виде пакетов, рекомендуемым способом является их регистрация в рамках расширения Latte:
namespace App\Latte;
use Latte\Extension;
class MyLatteExtension extends Extension
{
public function getFilters(): array
{
return [
'initial' => $this->initial(...),
'shortify' => $this->shortify(...),
];
}
public function initial(string $s): string
{
return mb_substr($s, 0, 1) . '.';
}
public function shortify(string $s, int $len = 10): string
{
return mb_substr($s, 0, $len);
}
}
// Регистрация
$latte = new Latte\Engine;
$latte->addExtension(new App\Latte\MyLatteExtension);
Этот подход сохраняет логику вашего фильтра инкапсулированной и упрощает регистрацию.
Использование загрузчика фильтров
Latte позволяет регистрировать загрузчик фильтров с помощью
addFilterLoader()
. Это единственный вызываемый объект, который Latte
запросит для любого неизвестного имени фильтра во время компиляции.
Загрузчик возвращает вызываемый PHP-объект фильтра или null
.
$latte = new Latte\Engine;
// Загрузчик может динамически создавать/получать вызываемые объекты фильтров
$latte->addFilterLoader(function (string $name): ?callable {
if ($name === 'myLazyFilter') {
// Представьте здесь сложную инициализацию...
$service = get_some_expensive_service();
return fn($value) => $service->process($value);
}
return null;
});
Этот метод был в основном предназначен для ленивой загрузки фильтров с очень сложной инициализацией. Однако современные практики внедрения зависимостей (dependency injection) обычно справляются с ленивыми сервисами более эффективно.
Загрузчики фильтров добавляют сложности и, как правило, не
рекомендуются в пользу прямой регистрации с помощью addFilter()
или
в рамках расширения с помощью getFilters()
. Используйте загрузчики
только если у вас есть веская, специфическая причина, связанная с
проблемами производительности при инициализации фильтров, которые
нельзя решить иначе.
Фильтры, использующие класс с атрибутами
Еще один элегантный способ определения фильтров — использование
методов в вашем классе
параметров шаблона. Просто добавьте атрибут
#[Latte\Attributes\TemplateFilter]
к методу.
use Latte\Attributes\TemplateFilter;
class TemplateParameters
{
public function __construct(
public string $description,
// другие параметры...
) {}
#[TemplateFilter]
public function shortify(string $s, int $len = 10): string
{
return mb_substr($s, 0, $len);
}
}
// Передача объекта в шаблон
$params = new TemplateParameters(description: '...');
$latte->render('template.latte', $params);
Latte автоматически распознает и зарегистрирует методы, помеченные
этим атрибутом, когда объект TemplateParameters
передается в шаблон. Имя
фильтра в шаблоне будет таким же, как имя метода (shortify
в данном
случае).
{* Использование фильтра, определенного в классе параметров *}
{$description|shortify:50}
Контекстные фильтры
Иногда фильтру требуется больше информации, чем просто входное значение. Ему может потребоваться знать тип содержимого строки, с которой он работает (например, HTML, JavaScript, простой текст), или даже изменить его. Это ситуация для контекстных фильтров.
Контекстный фильтр определяется так же, как и обычный фильтр, но его
первый параметр должен быть типизирован как Latte\Runtime\FilterInfo
.
Latte автоматически распознает эту сигнатуру и при вызове фильтра
передаст объект FilterInfo
. Следующие параметры получат аргументы
фильтра как обычно.
use Latte\Runtime\FilterInfo;
use Latte\ContentType;
$latte->addFilter('money', function (FilterInfo $info, float $amount): string {
// 1. Проверьте тип входного содержимого (необязательно, но рекомендуется)
// Разрешите null (переменный ввод) или простой текст. Отклоните, если применен к HTML и т. д.
if (!in_array($info->contentType, [null, ContentType::Text], true)) {
$actualType = $info->contentType ?? 'mixed';
throw new \RuntimeException(
"Фильтр |money используется в несовместимом типе контента $actualType. Ожидался text или null."
);
}
// 2. Выполните преобразование
$formatted = number_format($amount, 2, '.', ',') . ' EUR';
$htmlOutput = '<i>' . htmlspecialchars($formatted) . '</i>'; // Обеспечьте правильное экранирование!
// 3. Объявите тип выходного содержимого
$info->contentType = ContentType::Html;
// 4. Верните результат
return $htmlOutput;
});
$info->contentType
— это строковая константа из Latte\ContentType
(например, ContentType::Html
, ContentType::Text
, ContentType::JavaScript
и т.
д.) или null
, если фильтр применяется к переменной ({$var|filter}
).
Вы можете читать это значение, чтобы проверить входной контекст, и
записывать в него, чтобы объявить тип выходного контекста.
Устанавливая тип содержимого в HTML, вы сообщаете Latte, что строка, возвращаемая вашим фильтром, является безопасным HTML. Latte тогда не будет применять к этому результату свое стандартное автоматическое экранирование. Это критически важно, если ваш фильтр генерирует HTML-разметку.
Если ваш фильтр генерирует HTML, вы несете ответственность
за правильное экранирование любых входных данных, используемых в
этом HTML (как в случае вызова htmlspecialchars($formatted)
выше). Пренебрежение
этим может создать уязвимости XSS. Если ваш фильтр возвращает только
простой текст, вам не нужно устанавливать $info->contentType
.
Фильтры на блоках
Все фильтры, применяемые к блокам, должны быть контекстными. Это потому, что содержимое блока имеет определенный тип содержимого (обычно HTML), о котором фильтр должен знать.
{block heading|money}1000{/block}
{* Фильтр 'money' получит '1000' как второй аргумент
и $info->contentType будет ContentType::Html *}
Контекстные фильтры предоставляют мощный контроль над тем, как данные обрабатываются на основе их контекста, позволяют реализовать продвинутые функции и обеспечивают правильное поведение экранирования, особенно при генерации HTML-содержимого.