Създаване на персонализирани филтри

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

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\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(). Това е единствено callable, което Latte ще поиска за всяко непознато име на филтър по време на компилация. Зареждащото устройство връща PHP callable на филтъра или null.

$latte = new Latte\Engine;

// Зареждащото устройство може динамично да създава/получава callable филтри
$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(
			"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 съдържание.

версия: 3.0