Создание пользовательских фильтров
**********************************
.[perex]
Фильтры - мощный инструмент для форматирования и изменения данных прямо в шаблонах Latte. Они предлагают чистый синтаксис с символом вертикальной черты (`|`), позволяющий преобразовать переменную или результат выражения в нужный формат вывода.
Что такое фильтры?
==================
Фильтры в Latte - это по сути **функции PHP, предназначенные специально для преобразования входного значения в выходное**. Они применяются с помощью записи через вертикальную черту (`|`) в выражениях шаблона (`{...}`).
**Удобство:** фильтры позволяют упаковать типовые задачи форматирования (форматирование даты, изменение регистра, обрезка) или обработку данных в переиспользуемые единицы. Вместо того чтобы повторять в шаблонах сложный PHP-код, вы просто применяете фильтр:
```latte
{* Вместо сложного PHP для обрезки: *}
{$article->text|truncate:100}
{* Вместо кода форматирования даты: *}
{$event->startTime|date:'Y-m-d H:i'}
{* Применение нескольких преобразований: *}
{$product->name|lower|capitalize}
```
**Читаемость:** использование фильтров делает шаблоны чище и сосредоточеннее на представлении, перенося логику преобразования в определение фильтра.
**Учёт контекста:** ключевая сильная сторона фильтров Latte - способность [учитывать контекст |#Контекстные фильтры]. Это значит, что фильтр может понимать, с содержимым какого типа он работает (HTML, JavaScript, простой текст и так далее), и применять подходящую логику или экранирование, что критично для безопасности и корректности, особенно при генерации HTML.
**Интеграция с логикой приложения:** как и у пользовательских функций, PHP-callable за фильтром может быть замыканием, статическим методом или методом экземпляра. Это позволяет фильтрам при необходимости обращаться к сервисам или данным приложения, хотя их основное назначение остаётся прежним - *преобразовать входное значение*.
По умолчанию Latte предоставляет богатый набор [стандартных фильтров|filters]. Пользовательские фильтры позволяют расширить этот набор форматированием и преобразованиями, нужными именно вашему проекту.
Если вам нужна логика, опирающаяся на *несколько* входных значений, или у вас нет основного значения для преобразования, скорее всего, лучше подойдёт [пользовательская функция|custom functions]. Если нужно сгенерировать сложную разметку или управлять ходом шаблона, подумайте о [пользовательском теге|custom tags].
Создание и регистрация фильтров
===============================
Есть несколько способов определить и зарегистрировать пользовательские фильтры в Latte.
Прямая регистрация через `addFilter()`
--------------------------------------
Самый простой способ добавить фильтр - использовать метод `addFilter()` прямо на объекте `Latte\Engine`. Вы указываете имя фильтра (как он будет использоваться в шаблоне) и соответствующий PHP-callable.
```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));
```
**Использование в шаблоне:**
```latte
{$name|initial} {* Выводит 'J.', если $name равно 'John' *}
{$description|shortify} {* Использует длину по умолчанию 10 *}
{$description|shortify:50} {* Использует длину 50 *}
{$prices|sum} {* Выводит сумму элементов массива $prices *}
```
**Передача аргументов:**
Значение слева от вертикальной черты (`|`) всегда передаётся в функцию фильтра как *первый* аргумент. Все параметры, указанные после двоеточия (`:`) в шаблоне, передаются как последующие аргументы.
```latte
{$text|shortify:30}
// Вызывает функцию PHP shortify($text, 30)
```
Регистрация через расширение
----------------------------
Для лучшей организации, особенно когда вы создаёте переиспользуемые наборы фильтров или распространяете их в виде пакетов, рекомендуется регистрировать их внутри [расширения Latte |extending-latte#Расширение Latte]:
```php
namespace App\Templating;
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 MyLatteExtension);
```
Такой подход держит логику фильтров упакованной, а регистрацию делает простой.
Фильтры через класс с атрибутами .{toc: Фильтры через класс}
------------------------------------------------------------
Ещё один изящный способ определить фильтры - использовать методы вашего [класса параметров шаблона |develop#Параметры как класс]. Достаточно добавить к методу атрибут `#[Latte\Attributes\TemplateFilter]`.
```php
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`).
```latte
{* Используем фильтр, определённый в классе параметров *}
{$description|shortify:50}
```
Контекстные фильтры
===================
Иногда фильтру нужно больше сведений, чем просто входное значение. Ему может понадобиться знать **тип содержимого** обрабатываемой строки (например, HTML, JavaScript, простой текст) или даже изменить его. Вот тут и пригодятся контекстные фильтры.
Контекстный фильтр определяется так же, как обычный, но его **первый параметр должен быть** объявлен с типом `Latte\Runtime\FilterInfo`. Latte автоматически распознаёт такую сигнатуру и при вызове фильтра передаёт объект `FilterInfo`. Последующие параметры получают аргументы фильтра как обычно.
```php
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(
"Filter |money used in incompatible content type $actualType. Expected text or 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-разметку.
.[warning]
Если ваш фильтр генерирует HTML, **вы отвечаете за правильное экранирование всех входных данных**, используемых внутри этого HTML (как в вызове `htmlspecialchars($formatted)` выше). Иначе можно создать XSS-уязвимость. Если фильтр возвращает только простой текст, устанавливать `$info->contentType` не нужно.
Фильтры на блоках
-----------------
Фильтры, применяемые к [блокам |tags#{block}] с типом содержимого, отличным от текста (обычно HTML), *должны* быть контекстными. Дело в том, что у содержимого блока есть определённый тип, о котором фильтр должен знать. Обычный, неконтекстный фильтр можно применить только к блоку, содержимое которого - простой текст.
```latte
{block heading|money}1000{/block}
{* Фильтр 'money' получает '1000' как второй аргумент,
а $info->contentType будет ContentType::Html *}
```
Контекстные фильтры дают мощный контроль над тем, как обрабатываются данные в зависимости от контекста, открывают доступ к продвинутым возможностям и обеспечивают правильное экранирование, особенно при генерации HTML-содержимого.
Создание пользовательских фильтров
Фильтры – мощный инструмент для форматирования и изменения
данных прямо в шаблонах 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-callable за фильтром может быть замыканием, статическим методом
или методом экземпляра. Это позволяет фильтрам при необходимости
обращаться к сервисам или данным приложения, хотя их основное
назначение остаётся прежним – преобразовать входное значение.
По умолчанию Latte предоставляет богатый набор стандартных фильтров. Пользовательские
фильтры позволяют расширить этот набор форматированием и
преобразованиями, нужными именно вашему проекту.
Если вам нужна логика, опирающаяся на несколько входных значений,
или у вас нет основного значения для преобразования, скорее всего,
лучше подойдёт пользовательская
функция. Если нужно сгенерировать сложную разметку или управлять
ходом шаблона, подумайте о пользовательском
теге.
Создание и регистрация фильтров
Есть несколько способов определить и зарегистрировать
пользовательские фильтры в Latte.
Прямая регистрация через addFilter()
Самый простой способ добавить фильтр – использовать метод
addFilter() прямо на объекте Latte\Engine. Вы указываете имя фильтра
(как он будет использоваться в шаблоне) и соответствующий PHP-callable.
$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\Templating;
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 MyLatteExtension);
Такой подход держит логику фильтров упакованной, а регистрацию
делает простой.
Фильтры через класс с атрибутами
Ещё один изящный способ определить фильтры – использовать методы
вашего класса параметров
шаблона. Достаточно добавить к методу атрибут
#[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(
"Filter |money used in incompatible content type $actualType. Expected text or 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-содержимого.