Rozšiřujeme Latte

Latte je velmi flexibilní a lze jej rozšířit mnoha způsoby: můžete přidat vlastní filtry, funkce, značky, loadery atd. Ukážeme si jak na to.

Tato kapitola popisuje jednotlivé cesty rozšiřování Latte. Pokud chcete své úpravy znovu použít v různých projektech nebo je sdílet s ostatními, měli byste vytvořit tzv. rozšíření.

Kolik cest vede do Říma?

Protože některé způsoby rozšíření Latte mohou splývat, zkusíme si nejprve vysvětlit rozdíly mezi nimi. Jako příklad se pokusíme implementovat generátor Lorem ipsum, kterému předáme počet slov, jenž má vygenerovat.

Hlavní konstrukcí jazyka Latte je značka (tag). Generátor můžeme implementovat rozšířením jazyka Latte o nový tag:

{lipsum 40}

Tag bude skvěle fungovat. Nicméně generátor v podobě tagu nemusí být dostatečně flexibilní, protože jej nelze použít ve výrazu. Mimochodem v praxi potřebujete tagy vytvářet jen zřídka; a to je dobrá zpráva, protože tagy jsou složitějším způsobem rozšíření.

Dobrá, zkusme místo tagu vytvořit filtr:

{=40|lipsum}

Opět validní možnost. Ale filtr by měl předanou hodnotu transformovat na něco jiného. Zde hodnotu 40, která udává počet vygenerovaných slov, používáme jako argument filtru, nikoli jako hodnotu, kterou chceme transformovat.

Tak zkusíme použít funkci:

{lipsum(40)}

To je ono! Pro tento konkrétní příklad je vytvoření funkce ideálním způsobem rozšíření. Můžete ji volat kdekoli, kde je akceptován výraz, například:

{var $text = lipsum(40)}

Filtry

Filtr vytvoříme zaregistrováním jeho názvu a libovolného PHP callable, třeba funkce:

$latte = new Latte\Engine;
$latte->addFilter('shortify', function (string $s): string {
	return mb_substr($s, 0, 10); // zkrátí text na 10 písmen
});

V tomto případě by bylo šikovnější, kdyby filtr přijímal další parametr:

$latte->addFilter('shortify', function (string $s, int $len = 10): string {
	return mb_substr($s, 0, $len);
});

V šabloně se potom volá takto:

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

Jak vidíte, funkce obdrží levou stranu filtru před pipe | jako první argument a argumenty předané filtru za : jako další argumenty.

Funkce představující filtr může samozřejmě přijímat libovolný počet parametrů, podporovány jsou i variadic parametry.

Filtry pomocí třídy

Druhým způsobem definice filtru je využití třídy. Vytvoříme metodu s atributem 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);

Pokud používáte PHP 7.x a Latte 2.x, místo atributu uveďte anotaci /** @filter */.

Zavaděč filtrů

Místo registrace jednotlivých filtrů lze vytvořit tzv. zavaděč, což je funkce, které se zavolá s názvem filtru jako argumentem a vrátí jeho PHP callable, nebo 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);
	}

	// ...
}

Kontextové filtry

Kontextový filtr je takový, který v prvním parametru přijímá objekt Latte\Runtime\FilterInfo a teprve po něm následují další parametry jako u klasických filtrů. Registruje se stejný způsobem, Latte samo rozpozná, že filtr je kontextový:

use Latte\Runtime\FilterInfo;

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

Kontextové filtry mohou zjišťovat a měnit content-type, který obdrží v proměnné $info->contentType. Pokud se filtr volá klasicky nad proměnnou (např. {$var|foo}), bude $info->contentType obsahovat null.

Filtr by měl nejprve ověřit, zda content-type vstupního řetězce podporuje. A může ho také změnit. Příklad filtru, který přijímá text (nebo null) a vrací HTML:

use Latte\Runtime\FilterInfo;

$latte->addFilter('money', function (FilterInfo $info, float $amount): string {
	// nejprve oveříme, zda je vstupem content-type text
	if (!in_array($info->contentType, [null, ContentType::Text])) {
		throw new Exception("Filter |money used in incompatible content type $info->contentType.");
	}

	// změníme content-type na HTML
	$info->contentType = ContentType::Html;
	return "<i>$num Kč</i>";
});

Filtr musí v takovém případě zajistit správné escapování dat.

Všechny filtry, které se používají nad bloky (např. jako {block|foo}...{/block}), musí být kontextové.

Funkce

V Latte lze standardně používat všechny nativní funkce z PHP, pokud to nezakáže sandbox. Ale zároveň si můžete definovat funkce vlastní. Mohou přepsat funkce nativní.

Funkci vytvoříme zaregistrováním jejího názvu a libovolného PHP callable:

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

Použití je pak stejné, jako když voláte PHP funkci:

{random(jablko, pomeranč, citron)} // vypíše například: jablko

Funkce pomocí třídy

Druhým způsobem definice funkce je využití třídy. Vytvoříme metodu s atributem 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);

Pokud používáte PHP 7.x a Latte 2.x, místo atributu uveďte anotaci /** @function */.

Loadery

Loadery jsou zodpovědné za načítání šablon ze zdroje, například ze souborového systému. Nastaví se metodou setLoader():

$latte->setLoader(new MyLoader);

Vestavěné loadery jsou tyto:

FileLoader

Výchozí loader. Načítá šablony ze souborového systému.

Přístup k souborům je možné omezit nastavením základního adresáře:

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

StringLoader

Načítá šablony z řetězců. Tento loader je velmi užitečný pro testování. Lze jej také použít pro malé projekty, kde může mít smysl ukládat všechny šablony do jediného souboru PHP.

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

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

Zjednodušené použití:

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

Vytvoření vlastního loaderu

Loader je třída, která implementuje rozhraní Latte\Loader.

Tagy (makra)

Jednou z nejzajímavějších funkcí šablonovacího jádra je možnost definovat nové jazykové konstrukce pomocí značek. Je to také složitější funkčnost a je třeba pochopit, jak Latte interně funguje.

Většinou však značka není potřeba:

  • pokud by měla generovat nějaký výstup, použijte místo ní funkci
  • pokud měla upravovat nějaký vstup a vracet ho, použijte místo toho filtr
  • pokud měla upravovat oblast textu, obalte jej značkou {block} a použijte filtr
  • pokud neměla nic vypisovat, ale pouze volat funkci, zavolejte ji pomocí {do}

Pokud přesto chcete vytvořit tag, skvěle! Vše podstatné najdete v kapitole Vytváříme Extension (týká se Latte 3.x; viz také podrobný návod pro Latte 2.x).

Průchody kompilátoru

Průchody kompilátoru jsou funkce, které modifikují AST nebo sbírají v nich informace. V Latte je takto implementován například sandbox: projde všechny uzly AST, najde volání funkcí a metod a nahradí je za jím kontrolované volání.

Stejně jako v případě značek se jedná o složitější funkcionalitu a je potřeba pochopit, jak funguje Latte pod kapotou. Vše podstatné najdete v kapitole Vytváříme Extension.