Šablony
Šablony jsou náramnou předností Nette Frameworku, ušetří a zpříjemní vám práci a zabezpečí výstup před zranitelnostmi jako je XSS.
- naučíte se používat makra
- bloky a dědičnost
- vytváření a používání helperů
- vytváření vlastních maker
- vyhnutí se bezpečnostním zranitelnostem
Ačkoliv je PHP původem šablonovací jazyk, k jejich kódování se příliš nehodí. Podívejme se, jak v šablonovacím
PHP vypsat pole prvků $items
jako seznam:
<?php if ($items): ?>
<?php $counter = 1 ?>
<ul>
<?php foreach ($items as $item): ?>
<li id="item-<?php echo $counter++ ?>"><?php
echo htmlSpecialChars(mb_convert_case($item, MB_CASE_TITLE)) ?>
</li>
<?php endforeach ?>
</ul>
<?php endif?>
Zapis je poměrně nepřehledný a nesmí se zapomínat na volání htmlSpecialChars
. Proto vznikají v PHP
nejrůznější šablonovací systémy. Jeden z nejskvělejších šablonovacích systémů je součástí Nette Framework a nese
název Latte. Budete ho milovat!
Latte
V Latte se stejná šablona napíše řádově jednodušeji:
<ul n:if="$items">
{foreach $items as $item}
<li id="item-{$iterator->counter}">{$item|capitalize}</li>
{/foreach}
</ul>
Jak vidno, v Latte se používají dva druhy speciálních značek:
- makra ve složených závorkách, například
{foreach …}
- n:makra, například
n:if="…"
Makra
Popis všech výchozích maker najdete na samostatné stránce. Krom toho můžete vytvářet i vlastní makra.
Každé párové makro, například {if} … {/if}
, operující nad jedním HTML elementem, se dá přepsat do
podoby n:makra. Takto by bylo možné zapsat například i {foreach}
<ul n:if="$items">
<li n:foreach="$items as $item">{$item|capitalize}</li>
</ul>
A to se s n:makry dají dělat ještě daleko zajímavější kousky, jak se za chvíli dozvíte.
Makro {$item|capitalize}
, které slouží k vypsání proměnné $item
, obsahuje tzv. helper, v tomto případě capitalize
, který převede první písmenko každého
slova na velké.
Velmi důležité je, že Latte vypisované proměnné automaticky escapuje. Vypsání proměnné totiž vyžaduje escapování, tj. převedení znaků majících v HTML speciální význam na jiné odpovídající sekvence. Opomenutí by vedlo ke vzniku závažné bezpečnostní díry Cross Site Scripting (XSS).
Protože v různých dokumentech a na různých místech stránky se používají jiné escapovací funkce, disponuje Latte zcela unikátní technologií Context-Aware Escaping, která rozezná, ve které části dokumentu se makro nachází a podle toho zvolí správné escapování. Nemusíte se proto bát, že váš kodér na escapování zapomene a způsobí vám velké starosti kvůli bezpečnostní díře. Což je skvělé!
Pokud by proměnná $item
obsahovala HTML kód a chtěli bychom ji vypsat bez jakékoliv
transformace, stačí přidat modifikátor noescape: {$item|noescape}
. Opomenutí přitom nezpůsobí bezpečností
díru, dle principu „less code, more security“.
Uvnitř maker přitom můžeme používat PHP takové, jaké ho známe. Dokonce včetně komentářů. Latte navíc syntaxi PHP rozšiřuje o tři příjemné novinky:
- pole můžeme zapisovat jako
[1, 2, 3]
, což odpovídáarray(1, 2, 3)
- můžeme vynechat uvozovky kolem řetězce z písmen, číslic a pomlček
- stručnější zápis podmínek
$a ? 'b'
odpovídající$a ? 'b' : null
Příklad:
{foreach [a, b, c] as $id} ... {/foreach}
{$cond ? hello} // vypíše 'hello' pokud je $cond truthy
Komentáře se zapisují {* tímto způsobem *}
a do výstupu se nedostanou.
n:makra
Ukazovali jsme si, že n:makra se zapisují přímo do HTML značek jako jejich speciální atributy. A také si říkali, že
každé párové makro (například {if} … {/if}
) se dá přepsat do podoby n:makra. Makro se pak vztahuje na HTML
element, do něhož je umístěno:
{var $items = ['I', '♥', 'Nette Framework']}
<p n:foreach="$items as $item">{$item}</p>
vypíše
<p>I</p>
<p>♥</p>
<p>Nette Framework</p>
Pomocí prefixu inner-
můžeme chování poupravit tak, aby se vztahovalo jen na vnitřní část elementu:
<div n:inner-foreach="$items as $item">
<p>{$item}</p>
<hr>
</div>
Vypíše se:
<div>
<p>I</p>
<hr>
<p>♥</p>
<hr>
<p>Nette Framework</p>
<hr>
</div>
Nebo pomocí prefixu tag-
aplikujeme makro jen na samotné HTML značky:
<p><a href="{$url}" n:tag-if="$url">Title</a></p>
Což vypíše v závislosti na proměnné $url
:
{* když je $url prázdné *}
<p>Title</p>
{* když $url obsahuje 'https://nette.org' *}
<p><a href="https://nette.org">Title</a></p>
Avšak n:makra nejsou jen zkratkou pro párová makra, existují i ryzí n:makra, jako třeba n:href nebo velešikovný pomocník kodéra n:class.
Helpery v Latte
Latte umožňuje snadné volání helperů za použití zápisu se svislítkem (před ním může být mezera):
<h1>{$heading|upper}</h1>
Helpery (někdy též modifikátory) lze zřetězit a poté se aplikují v pořadí od levého k pravému:
<h1>{$heading|lower|capitalize}</h1>
Parametry se zadávají za jménem helperu oddělené dvojtečkami nebo čárkami:
<h1>{$heading|truncate:20,''}</h1>
Podívejte se také na přehled standardních helperů a způsob, jak si vytvořit helpery vlastní.
Rychlost
Latte je rychlé. Překládá totiž šablony do nativního PHP kódu a ukládá do cache na disk. Díky tomu mají stejný výkon, jako bychom psali šablony v čistém PHP. Nicméně v přehlednosti, bezpečnosti a efektivitě jsou o několik řádů dál.
Šablona se automaticky regeneruje pokaždé, když změníme zdrojový soubor. Během vývoje si tedy pohodlně editujeme šablony v Latte a změny okamžitě vidíme v prohlížeči.
Debuggování
O jakékoliv chybě v šabloně nebo překlepu vás bude informovat Laděnka s plnou parádou. Zobrazí zdrojový kód šablony a červeně označí řádek, na kterém je chyba. Společně s výstižnou chybovou zprávou. Jedním kliknutím pak otevřeme šablonu ve svém oblíbeném editoru a chybu můžeme okamžitě opravit. Snadnější to už být nemůže. Leda, že by se chyby opravovaly samy (inspirace pro budoucí verze? ;-) )
Pokud používáte IDE s možností krokování kódu, můžete takto procházet i vygenerovaný PHP kód šablon.
Použitelnost
Syntax Latte nebyla vymyšlena inženýry od stolu, ale vzešla z ryze praktických požadavků webdesignerů. Hledali jsme tu nejpřívětivější syntax, se kterou elegantně zapíšete i konstrukce, které jinak představují skutečný oříšek. Budete překvapeni, jak moc vám Latte zjednoduší práci.
Najdete tu makra pro pokročilou tvorbu layoutů, pro tzv. dědičnost šablon, vnořené bloky a podobně. Přitom syntaxe vychází přímo z PHP, nebudete se tedy muset učit něco zcela nového a zúročíte své knowhow.
Context-Aware Escaping
Byť je Cross Site Scripting (XSS) jedním z nejtriviálnějších způsobů narušení webových stránek, jde o zranitelnost nejčastější. Přitom velmi závažnou, protože může vést k zcizení identity a podobně. Obranou je důsledné escapování vypisovaných dat, tj. převod znaků majících v daném kontextu speciální význam na jiné odpovídající sekvence.
Pokud kodér na escapování zapomene, vznikne v aplikaci bezpečnostní díra. Proto šablonovací systémy začínají přicházet s automatickým escapováním. Problém je ale v tom, že na webové stránce existují různé kontexty a v každém je potřeba ošetřit vypisované proměnné trošku jinak. Může tak vzniknou bezpečnostní díra i kvůli špatně zvolené escapovací funkci.
Latte je však dál. Disponuje unikátní technologií Context-Aware Escaping, která rozezná, ve které části dokumentu se makro nachází a podle toho zvolí správné escapování. Co to znamená?
Že v Latte není potřeba nic manuálně ošetřovat. Vše se děje automaticky, správně a důsledně. Nemusíte se bát bezpečnostní děr.
Podívejme se, jak to funguje. Vytvoříme si šablonu:
<p onclick="alert({$movie})">{$movie}</p>
<script>var movie = {$movie};</script>
Pokud bude proměnná $movie
obsahovat řetězec 'Amarcord & 8 1/2'
, vygeneruje se následujíci
výstup. Všimněte si, že uvnitř HTML se použije jiné escapování, než uvnitř JavaScriptu a ještě jiné v atributu
onclick
:
<p onclick="alert("Amarcord & 8 1\/2")">Amarcord & 8 1/2</p>
<script>var movie = "Amarcord & 8 1\/2";</script>
Díky Context-Aware Escaping je šablona jednoduchá a vaše aplikace přitom bude perfektně zabezpečená proti Cross Site Scripting. Dokonce je možné zcela nativně používat PHP proměnné uvnitř JavaScriptu!
Hezký výstup
Puntičkáře potěší podoba HTML výstupu, který Latte generuje. Všechny značky budou odsazeny přesně tak, jak jsme zamýšleli. Kód bude vypadat, jako by byl zpracován nějakým HTML code beautifierem :-)
Vlastní makra
Latte poskytuje API pro tvorbu vlastních maker. Není to nic složitého. Makra přidáváme v sadách, přičemž sadu může tvořit i jediné makro.
$latte = new Nette\Latte\Engine;
// vytvoříme si sadu
$set = new Nette\Latte\Macros\MacroSet($latte->compiler);
// do sady přidáme párové makro {try} ... {/try}
$set->addMacro(
'try', // název makra
'try {', // PHP kód nahrazující otevírací značku
'} catch (\Exception $e) {}' // kód nahrazující uzavírací značku
);
Pokud makro není párové, třetí parametr metody addMacro()
vynecháme.
PHP kód uváděný ve druhém a třetím parametru může obsahovat zástupné symboly:
%node.word
– vloží první argument makra%node.array
– vloží argumenty makra naformátované jako PHP pole%node.args
– vloží argumenty makra naformátované jako PHP kód%escape(...)
– nahradí za aktuální escapovací funkcí%modify(...)
– nahradí sérií modifikátorů
Příklad:
$set->addMacro('if', 'if (%node.args):', 'endif');
Pokud je logika makra ještě složitější, můžeme místo řetězců uvést callbacky či lambda funkce. Jako první parameter dostanou objekt MacroNode reprezentující aktuální makro, druhým parameterem je objekt PhpWriter, který usnadňuje generování výstupního kódu.
$set->addMacro('if', function($node, $writer) {
return $writer->write('if (%node.args):');
}, 'endif');
Helpery
V šablonách můžeme používat funkce, které pomáhají upravit nebo přeformátovat data do výsledné podoby. Říkáme jim helpery. Podívejte se na přehled všech standardních helperů.
Jako helper lze do šablony zaregistrovat libovolný callback nebo anonymní funkci, např. v BasePresenter
:
protected function createTemplate($class = NULL)
{
$template = parent::createTemplate($class);
$template->registerHelper('shortify', function ($s) {
return mb_substr($s, 0, 10); // zkrátí text na 10 písmen
});
return $template;
}
V tomto případě by bylo šikovnější, kdyby helper přijímal parameter:
$template->registerHelper('shortify', function ($s, $len = 10) {
return mb_substr($s, 0, $len);
});
V šabloně se potom volá takto:
<p><?php echo $template->shortify($text, 100); ?></p>
Latte zápis zjednodušuje, helpery se uvádějí za svislítkem, lze je zřetězit a aplikují se v pořadí od levého k pravému. Parametry se zadávají oddělené dvojtečkou nebo čárkou:
<p>{$text|shortify:100}</p>
Helper loader
Manuální registraci více helperů lze nahradit registrací jednoho či více helper loaderů. Jako helper loader lze zaregistrovat libovolný callback nebo anonymní funkci.
$template->registerHelperLoader('Helpers::loader');
Helper loader dostane jako parametr název požadovaného helperu a vrací jeho callback nebo NULL
v případě,
že helper není schopen dodat.
class Helpers
{
public static function loader($helper)
{
if (method_exists(__CLASS__, $helper)) {
return array(__CLASS__, $helper);
}
}
public static function shortify($s, $len = 10)
{
return mb_substr($s, 0, $len);
}
}
Samostatné použití šablon
Při používání presenterů obvykle nepotřebujeme udělat nic víc, než napsat kód šablony. Vše ostatní udělá framework za nás. Nicméně šablony můžeme používat i samostatně, třeba v již existující aplikaci nepostavené na Nette.
Šablonovací třídy se nacházejí ve jmenném prostoru Nette\Templating
, jehož jádrem je třída Nette\Templating\Template. Jak s ní zacházet nejlépe
ukáže příklad:
use Nette\Templating\Template;
$template = new Template;
// předáme šabloně parametry:
$template->firstName = 'John';
$template->lastName = 'Doe';
$template->setSource('
<em><?php echo htmlspecialchars($firstName) ?></em>
<strong><?php echo htmlspecialchars($lastName) ?></strong>
');
$template->render();
Metoda render()
vykreslí šablonu, tj. PHP skript, který jsme předali jako argument. Přičemž parametry budou
k dispozici v lokálních proměnných $firstName
a $lastName
. Navíc v proměnné
$template
bude k dispozici samotný objekt šablony.
Pokud máme šablonu uloženou v souboru, bude vhodnější použít třídu FileTemplate:
use Nette\Templating\FileTemplate;
$template = new FileTemplate('template.latte'); // soubor se šablonou
// $template->setFile('template.latte'); // název souboru lze určit i později
$template->firstName = 'John';
$template->lastName = 'Doe';
$template->render(); // vykreslí šablonu
Abychom mohli v šabloně používat helpery, musíme je zaregistrovat. Výchozí helpery přidáme tímto způsobem:
$template->registerHelperLoader('Nette\Templating\Helpers::loader');
Šablonu je možné předzpracovat pomocí jednoho či více filtrů, což jsou funkce, které dostanou jako parametr obsah šablony a vrátí ho v upraveném stavu. Jako filtr lze zaregistrovat libovolný callback nebo anonymní funkci.
// filtr, který nahradí v textu šablony slova 'apple' slovem 'pizza'
$template->registerFilter(function ($s) {
return str_replace('apple', 'pizza', $s);
});
V presenterech a komponentách je nejvhodnějším místem pro registraci přepsání metody
templatePrepareFilters
.
Nejkomplexnějším filtrem je Latte:
$template->registerFilter(new Nette\Latte\Engine);
Po aplikaci všech zaregistrovaných filtrů získáme nativní PHP šablonu. Ta se uloží do cache, takže aplikace fitrů se
provádí jen jednou. Cache se invaliduje automaticky při změně zdrojového souboru a je ukládána ve zvláštním formátu,
který dovoluje soubory načítat konstrukcí include
. Proto je nutné ještě určit úložiště, do kterého se
budou soubory cache zapisovat:
$template->setCacheStorage(new Nette\Caching\Storages\PhpFileStorage('temp'));
Když se filtrované šablony ukládají do cache, je zbytečné pokaždé vytvářet a registrovat filtr Latte. Proto jeho
registraci přidáme do události onPrepareFilters
:
$template->onPrepareFilters[] = function($template) {
$template->registerFilter(new Nette\Latte\Engine);
};
Snazší použití šablon v presenteru či komponentách
Chceme-li např. odeslat pěkně naformátovaný email, můžeme místo
new Nette\Templating\FileTemplate
využít aktuální presenter a jeho metodu createTemplate()
, která
za nás zaregistruje všechna důležitá makra a helpery.
$template = $this->createTemplate();
$template->setFile('template.latte');
A nyní už víte o šablonách úplně vše.