Подовжуючий Latte

Latte дуже гнучкий і може бути розширений безліччю способів: ви можете додати користувацькі фільтри, функції, теги, завантажувачі тощо. Ми покажемо вам, як це зробити.

У цьому розділі описано різні способи розширення Latte. Якщо ви хочете повторно використовувати свої зміни в різних проєктах або поділитися ними з іншими, вам слід створити так зване розширення.

Скільки доріг веде до Риму?

Оскільки деякі зі способів розширення Latte можуть змішуватися, давайте спочатку спробуємо пояснити відмінності між ними. Як приклад спробуємо реалізувати генератор Lorem ipsum, якому передається кількість слів для генерації.

Основною конструкцією мови Latte є тег. Ми можемо реалізувати генератор, розширивши Latte новим тегом:

{lipsum 40}

Тег працюватиме чудово. Однак генератор у вигляді тега може виявитися недостатньо гнучким, оскільки його не можна використовувати у виразі. До речі, на практиці вам рідко знадобиться генерувати теги; і це хороша новина, тому що теги – це складніший спосіб розширення.

Добре, давайте спробуємо створити фільтр замість тега:

{=40|lipsum}

Знову ж таки, прийнятний варіант. Але фільтр повинен перетворити передане значення на щось інше. Тут ми використовуємо значення 40, яке вказує на кількість створених слів, як аргумент фільтра, а не як значення, яке ми хочемо перетворити.

Тому давайте спробуємо використати функцію:

{lipsum(40)}

Ось і все! Для цього конкретного прикладу створення функції – ідеальна точка розширення. Ви можете викликати її в будь-якому місці, де приймається вираз, наприклад:

{var $text = lipsum(40)}

Фільтри

Створіть фільтр, зареєструвавши його ім'я і будь-який елемент PHP, що викликається, наприклад, функцію:

$latte = new Latte\Engine;
$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // скорочує текст до 10 символів

У цьому випадку було б краще, щоб фільтр отримував додатковий параметр:

$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len));

Ми використовуємо його в шаблоні таким чином:

<p>{$text|shortify}</p>
<p>{$text|shortify:100}</p>

Як бачите, функція отримує як такі аргументи ліву частину фільтра перед трубою | as the first argument and the arguments passed to the filter after :.

Звичайно, функція, що представляє фільтр, може приймати будь-яку кількість параметрів, також підтримуються змінні параметри.

Якщо фільтр повертає рядок у форматі HTML, ви можете позначити його, щоб Latte не використовував автоматичне (і, відповідно, подвійне) екранування. Це дозволяє уникнути необхідності вказувати |noescape у шаблоні. Найпростіший спосіб – обернути рядок в об'єкт Latte\Runtime\Html, інший спосіб – контекстні фільтри.

$latte->addFilter('money', fn(float $amount) => new Latte\Runtime\Html("<i>$amount EUR</i>"));

У цьому випадку фільтр повинен забезпечити коректне екранування даних.

Фільтри, що використовують клас

Другий спосіб визначити фільтр – використовувати клас. Ми створюємо метод з атрибутом TemplateFilter:

class TemplateParameters
{
	public function __construct(
		// параметри
	) {}

	#[Latte\Attributes\TemplateFilter]
	public function shortify(string $s, int $len = 10): string
	{
		return mb_substr($s, 0, $len);
	}
}

$params = new TemplateParameters(/* ... */);
$latte->render('template.latte', $params);

Завантажувач фільтрів

Замість реєстрації окремих фільтрів ви можете створити так званий завантажувач, який являє собою функцію, що викликається з ім'ям фільтра як аргумент і повертає його викликаний PHP-файл або null.

$latte->addFilterLoader([new Filters, 'load']);


class Filters
{
	public function load(string $filter): ?callable
	{
		if (in_array($filter, get_class_methods($this))) {
			return [$this, $filter];
		}
		return null;
	}

	public function shortify($s, $len = 10)
	{
		return mb_substr($s, 0, $len);
	}

	// ...
}

Контекстні фільтри

Контекстний фільтр – це фільтр, який приймає об'єкт Latte\Runtime\FilterInfo як перший параметр, за яким слідують інші параметри, як у випадку з класичними фільтрами. Він реєструється так само, Latte сам розпізнає, що фільтр контекстний:

use Latte\Runtime\FilterInfo;

$latte->addFilter('foo', function (FilterInfo $info, string $str): string {
	// ...
});

Контекстні фільтри можуть визначати і змінювати тип вмісту, який вони отримують у змінній $info->contentType. Якщо фільтр викликається класично через змінну (наприклад, {$var|foo}), то $info->contentType буде містити null.

Фільтр повинен спочатку перевірити, чи підтримується тип вмісту вхідного рядка. Він також може змінити його. Приклад фільтра, який приймає текст (або null) і повертає HTML:

use Latte\Runtime\FilterInfo;

$latte->addFilter('money', function (FilterInfo $info, float $amount): string { })
	// спочатку перевіряємо, чи тип вводу текстовий
	if (!in_array($info->contentType, [null, ContentType::Text])) {
		throw new Exception("Filter |money used in incompatible content type $info->contentType.");
	}

	// змінюємо тип контенту на HTML
	$info->contentType = ContentType::Html;
	return "<i>$amount EUR</i>";
});

У цьому випадку фільтр повинен забезпечити правильне екранування даних.

Усі фільтри, які використовуються поверх блоків (наприклад, як {block|foo}...{/block}), мають бути контекстними.

Функції

За замовчуванням усі власні функції PHP можуть використовуватися в Latte, якщо тільки це не вимкнено в пісочниці. Але ви також можете визначити свої власні функції. Вони можуть перевизначати власні функції.

Створіть функцію, зареєструвавши її ім'я та будь-яку викликану функцію PHP:

$latte = new Latte\Engine;
$latte->addFunction('random', function (...$args) {
	return $args[array_rand($args)];
});

Після цього використання буде таким же, як і при виклику PHP-функції:

{random(apple, orange, lemon)} // prints for example: apple

Функції, що використовують клас

Другий спосіб визначити функцію – використовувати клас. Ми створюємо метод з атрибутом TemplateFunction:

class TemplateParameters
{
	public function __construct(
		// параметри
	) {}

	#[Latte\Attributes\TemplateFunction]
	public function random(...$args)
	{
		return $args[array_rand($args)];
	}
}

$params = new TemplateParameters(/* ... */);
$latte->render('template.latte', $params);

Завантажувачі

Завантажувачі відповідають за завантаження шаблонів із джерела, наприклад, із файлової системи. Вони встановлюються за допомогою методу setLoader():

$latte->setLoader(new MyLoader);

Вбудованими завантажувачами є:

FileLoader

Завантажувач за замовчуванням. Завантажує шаблони з файлової системи.

Доступ до файлів можна обмежити, задавши базовий каталог:

$latte->setLoader(new Latte\Loaders\FileLoader($templateDir));
$latte->render('test.latte');

StringLoader

Завантажує шаблони з рядків. Цей завантажувач дуже корисний для модульного тестування. Він також може бути використаний для невеликих проектів, де має сенс зберігати всі шаблони в одному PHP-файлі.

$latte->setLoader(new Latte\Loaders\StringLoader([
	'main.file' => '{include other.file}',
	'other.file' => '{if true} {$var} {/if}',
]));

$latte->render('main.file');

Спрощене використання:

$template = '{if true} {$var} {/if}';
$latte->setLoader(new Latte\Loaders\StringLoader);
$latte->render($template);

Створення користувацького завантажувача

Loader – це клас, що реалізує інтерфейс Latte\Loader.

Теги

Однією з найцікавіших можливостей шаблонізатора є можливість визначати нові мовні конструкції за допомогою тегів. Це також складніша функціональність, і вам необхідно розуміти, як внутрішньо працює Latte.

У більшості випадків, однак, тег не потрібен:

  • якщо він має генерувати певний висновок, використовуйте замість нього функцію
  • якщо потрібно змінити вхідні дані та повернути їх, використовуйте filter
  • якщо потрібно відредагувати ділянку тексту, оберніть її тегом {block} тегом і використовуйте фільтр
  • якщо він не повинен був нічого виводити, а тільки викликати функцію, викличте її за допомогою {do}

Якщо ви все ще хочете створити тег, чудово! Все найнеобхідніше можна знайти в розділі Створення розширення.

Передачі компілятора

Паси компілятора – це функції, які змінюють AST або збирають у них інформацію. У Latte, наприклад, пісочниця реалізована таким чином: вона обходить усі вузли AST, знаходить виклики функцій і методів і замінює їх керованими викликами.

Як і у випадку з тегами, це складніша функціональність, і вам потрібно розуміти, як Latte працює під капотом. Усе найнеобхідніше можна знайти в розділі Створення розширення.

версію: 3.0