Разширяване на 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(
		// parameters
	) {}

	#[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);

Зареждане на филтри

Вместо да регистрирате отделни филтри, можете да създадете така наречения loader, който представлява функция, извикана с името на филтъра като аргумент и връщаща обратната връзка на 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) и връща 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(
		// parameters
	) {}

	#[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 вътрешно.

В повечето случаи обаче не е необходимо да се поставя етикет:

  • ако трябва да генерира някакъв изход, използвайте функция вместо това.
  • ако искате да промените входа и да го върнете, използвайте филтър
  • ако искате да редактирате текстова секция, обвийте я с таг {block} и използвайте филтър
  • ако не трябва да извежда нищо, а само да извика функция, извикайте я с {do}

Ако все още искате да създадете етикет, чудесно! Всичко необходимо можете да намерите в раздел Създаване на разширение.

Предавания на компилатора

Пропусканията на компилатора са функции, които модифицират AST или събират информация в тях. В Latte например пясъчникът е реализиран по следния начин: той обхожда всички AST възли, намира извиквания на функции и методи и ги заменя с контролирани извиквания.

Както и при таговете, това е по-сложна функционалност и трябва да разберете как работи Latte под капака. Основните принципи са описани в глава Създаване на разширения.

версия: 3.0