Rozšiřujeme Latte
Latte je mimořádně flexibilní šablonovací systém, který můžete přizpůsobit svým potřebám mnoha způsoby. Ať už potřebujete přidat vlastní filtry, funkce, značky nebo změnit způsob načítání šablon, Latte vám to umožní. Pojďme se podívat, jak na to.
Tato kapitola vás provede různými metodami rozšíření Latte. Pokud plánujete své rozšíření použít ve více projektech nebo ho sdílet s komunitou, doporučujeme vytvořit samostatné rozšíření.
Kolik cest vede do Říma?
Jelikož některé způsoby rozšíření Latte mohou být podobné, pojďme si nejprve objasnit rozdíly mezi nimi. Jako příklad implementujeme generátor textu Lorem ipsum, kterému předáme požadovaný počet slov.
Základním stavebním kamenem jazyka Latte je značka (tag). Generátor bychom mohli implementovat jako novou značku:
{lipsum 40}
Tato značka by fungovala dobře, ale nemusí být dostatečně flexibilní, protože ji nelze použít ve výrazech. Mimochodem, v praxi je potřeba vytvářet nové značky jen zřídka, což je dobrá zpráva, protože jde o složitější způsob rozšíření.
Zkusme místo značky vytvořit filtr:
{=40|lipsum}
To je také platná možnost. Avšak filtr by měl typicky transformovat předanou hodnotu. V tomto případě hodnotu
40
, která určuje počet generovaných slov, používáme jako argument filtru, nikoli jako hodnotu
k transformaci.
Pojďme tedy zkusit funkci:
{lipsum(40)}
To je ono! Pro tento konkrétní případ je vytvoření funkce ideálním způsobem rozšíření. Můžete ji volat kdekoli, kde je povolen výraz, například:
{var $text = lipsum(40)}
Tento příklad ukazuje, že výběr správného způsobu rozšíření závisí na konkrétním použití. Nyní se pojďme podívat na jednotlivé metody podrobněji.
Filtry
Filtry jsou mocné nástroje pro transformaci dat přímo v šabloně. Vytvořit vlastní filtr je jednoduché:
$latte = new Latte\Engine;
$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // zkrátí text na 10 znaků
V tomto případě by bylo užitečné, kdyby filtr přijímal další parametr:
$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len));
V šabloně se pak volá takto:
<p>{$text|shortify}</p>
<p>{$text|shortify:100}</p>
Všimněte si, že:
- První argument filtru je vždy hodnota nalevo od
|
- Další argumenty se předávají za dvojtečkou
- Filtry mohou mít libovolný počet argumentů, včetně volitelných
Pokud filtr vrací řetězec v HTML, můžete jej označit tak, aby jej Latte automaticky (a tedy dvojitě) neescapovalo. Tím
se vyhnete nutnosti používat v šabloně |noescape
. Nejjednodušší způsob je obalit řetězec do objektu
Latte\Runtime\Html
, alternativou jsou Kontextové filtry.
$latte->addFilter('money', fn(float $amount) => new Latte\Runtime\Html("<i>$amount Kč</i>"));
Filtr musí v takovém případě zajistit správné escapování dat.
Filtry pomocí třídy
Alternativním způsobem definice filtru je využití třídy. Vytvoříme
metodu s atributem TemplateFilter
:
class TemplateParameters
{
public function __construct(
// parametry
) {}
#[Latte\Attributes\TemplateFilter]
public function shortify(string $s, int $len = 10): string
{
return mb_substr($s, 0, $len) . (mb_strlen($s) > $len ? '...' : '');
}
}
$params = new TemplateParameters(/* ... */);
$latte->render('template.latte', $params);
Tento přístup je zvláště užitečný, když máte více filtrů nebo když filtry potřebují přístup ke sdíleným zdrojům.
Zavaděč filtrů
Místo registrace jednotlivých filtrů lze vytvořit tzv. zavaděč. Jde o funkci, která se volá s názvem filtru jako argumentem a vrací 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);
}
// ...
}
Tento přístup umožňuje dynamicky načítat filtry podle potřeby, což může být užitečné v rozsáhlých aplikacích.
Kontextové filtry
Kontextové filtry jsou speciální typ filtrů, které mají přístup k dodatečným informacím o kontextu, ve kterém jsou použity:
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 podporuje content-type vstupního řetězce. 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 ověří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>$amount 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 PHP, pokud to nezakáže sandbox. Zároveň si můžete definovat vlastní funkce, které mohou přepsat i nativní funkce.
Funkci vytvoříme registrací 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 při volání PHP funkce:
{random(jablko, pomeranč, citron)} // vypíše například: jablko
Funkce pomocí třídy
Alternativním způsobem definice funkce je využití třídy. Vytvoříme
metodu s atributem TemplateFunction
:
class TemplateParameters
{
public function __construct(
// parametry
) {}
#[Latte\Attributes\TemplateFunction]
public function random(...$args)
{
return $args[array_rand($args)];
}
}
$params = new TemplateParameters(/* ... */);
$latte->render('template.latte', $params);
Tento přístup je užitečný pro organizaci souvisejících funkcí a sdílení zdrojů mezi nimi.
Loadery
Loadery jsou zodpovědné za načítání šablon ze zdroje, například ze souborového systému. Nastavují se metodou
setLoader()
:
$latte->setLoader(new MyLoader);
Latte nabízí tyto vestavěné loadery:
FileLoader
Výchozí loader. Načítá šablony ze souborového systému.
Přístup k souborům lze 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 menší projekty, kde může být výhodné ukládat všechny šablony do jediného PHP souboru.
$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ší funkcionalita a vyžaduje pochopení vnitřního fungování Latte.
Ve většině případů však značka není potřeba:
- pokud má generovat nějaký výstup, použijte místo ní funkci
- pokud má upravovat nějaký vstup a vracet ho, použijte filtr
- pokud má upravovat oblast textu, obalte jej značkou
{block}
a použijte filtr - pokud nemá nic vypisovat, ale pouze volat funkci, použijte
{do}
Pokud se přesto rozhodnete vytvořit tag, skvělé! Vše potřebné najdete v kapitole Vytváříme Extension.
Průchody kompilátoru
Průchody kompilátoru jsou funkce, které modifikují AST (abstraktní syntaktický strom) nebo z něj sbírají 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 kontrolovaná volání.
Stejně jako v případě značek se jedná o složitější funkcionalitu, která vyžaduje pochopení vnitřního fungování Latte. Vše potřebné najdete v kapitole Vytváříme Extension.