Nette Documentation Preview

syntax
Создание пользовательских фильтров
**********************************

.[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-объект, стоящий за фильтром, может быть замыканием (closure), статическим методом или методом экземпляра. Это позволяет фильтрам получать доступ к сервисам приложения или данным, если это необходимо, хотя их основной целью остается *преобразование входного значения*.

Latte по умолчанию предоставляет богатый набор [стандартных фильтров |filters]. Пользовательские фильтры позволяют расширить этот набор форматированием и преобразованиями, специфичными для вашего проекта.

Если вам нужно выполнять логику, основанную на *нескольких* входах, или у вас нет основного значения для преобразования, вероятно, более подходящим будет использование [пользовательской функции |custom-functions]. Если вам нужно генерировать сложную разметку или управлять потоком шаблона, рассмотрите [пользовательский тег |custom-tags].


Создание и регистрация фильтров
===============================

Существует несколько способов определения и регистрации пользовательских фильтров в Latte.


Прямая регистрация с помощью `addFilter()`
------------------------------------------

Самый простой способ добавить фильтр — использовать метод `addFilter()` непосредственно на объекте `Latte\Engine`. Вы указываете имя фильтра (как он будет использоваться в шаблоне) и соответствующий вызываемый PHP-объект.

```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 Extension]:

```php
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`.

```php
$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()`. Используйте загрузчики только если у вас есть веская, специфическая причина, связанная с проблемами производительности при инициализации фильтров, которые нельзя решить иначе.


Фильтры, использующие класс с атрибутами
----------------------------------------

Еще один элегантный способ определения фильтров — использование методов в вашем [классе параметров шаблона |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(
			"Фильтр |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-разметку.

.[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-объект, стоящий за фильтром, может быть замыканием (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-содержимого.