Nette Framework: Chytré šablony

Ačkoliv je PHP svým způsobem šablonovací jazyk, ve své čisté podobě se pro jejich kódování úplně nehodí. Proto vznikají nejrůznější šablonovací systémy. Nepřekvapí, že jeden takový je i součástí Nette Frameworku.

Seriál: Začínáme s Nette Framework (17 dílů)

  1. Nette Framework: zvyšte svoji produktivitu 10.3.2009
  2. Nette Framework: Odvšivujeme 17.3.2009
  3. Nette Framework: MVC & MVP 24.3.2009
  4. Nette Framework: Refactoring 31.3.2009
  5. Nette Framework: Chytré šablony 7.4.2009
  6. Nette Framework: adresářová struktura aplikace 14.4.2009
  7. Nette Framework: AJAX 21.4.2009
  8. Nette Framework: AJAX (pokračování) 28.4.2009
  9. Nette Framework: AJAX (dokončení) 5.5.2009
  10. Nette Framework: Sessions 12.5.2009
  11. Nette Framework: Přihlašování uživatelů 19.5.2009
  12. Nette Framework: Ověřování oprávnění a role 26.5.2009
  13. Nette Framework: Neprůstřelné formuláře 2.6.2009
  14. Nette Framework: Neprůstřelné formuláře II 9.6.2009
  15. Nette Framework: Neprůstřelné formuláře III 16.6.2009
  16. Nette Framework: Cache 23.6.2009
  17. Nette Framework: Co se do seriálu nevešlo? 30.6.2009

Šablonovací systém je dalším příkladem jednotky Nette Frameworku, kterou můžete ve svých aplikacích používat samostatně. Nejprve si povíme o motivaci, tedy proč vůbec šablonovací systém používat.

PHP a (ne)bezpečnost

Následující odstaveček si tvůrci PHP za rámeček nejspíš nedají. Jedním z nejtriviálnějších způsobů narušení webových stránek je Cross Site Scripting (XSS). Ať už z pohledu principu útoku nebo obrany proti němu. Přesto jde dost možná o nejčastější zranitelnost. Obranou je escapování, tj. převod znaků majících v daném kontextu speciální význam na jiné odpovídající sekvence.

Na webu se nejvíce setkáváme s pěti kontexty: HTML, JavaScript, URL, CSS a XML. Funkce escapující HTML by měla patřit k nejčastěji volaným funkcím PHP, proto ji autoři dali krátké a už od pohledu srozumitelné jméno htmlspecialchars. (Ano, dopustil jsem se ironie.) Aby fungovala univerzálně, musí se volat ještě s druhým parametrem, tj.  htmlspecialchars($s, ENT_QUOTES).

Escapovací funkce pro JavaScript se v PHP objevila až na sklonku roku 2006 pod zavádějícím názvem json_encode, což svádí k úvahám, že escapování řetězců je jen její vedlejší efekt. Pro escapování v kontextu URL slouží rawurlencode a pro CSS nebo XML nenabízí PHP doposud nic. Aneb naprogramuj si sám.

Tato bezpečnostní mizérie společně s bariérou v podobě direktivy short_open_tag vede k tomu, že i primitivní šablona pro výpis prvků pole vypadá takto:

<ul>
<?php foreach ($items as $item): ?>
        <li><a href='<?php echo htmlspecialchars($item->href, ENT_QUOTES) ?>'><?php echo htmlspecialchars($item->name) ?></a></li>
<?php endforeach ?>
</ul> 

Pokusme se nyní implementovat reálný požadavek vyplývající z nedokonalosti stále hojně používaného prohlížeče IE6, konkrétně přiřadit prvnímu prvku CSS třídu first a poslednímu last. Aby to bylo použitelné pro obecný iterátor (např. nebufferovaný databázový dotaz), nelze volat count. Výsledkem je tento šíleně šílený kód:

<ul>
<?php
...inicializace pomocných proměnných...
...cyklus...
...hromada podmínek...
...nepřehledný mix PHP a HTML...
?>
</ul> 

Omlouvám se laskavému čtenáři, nenašel jsem v sobě sílu zadání naprogramovat. Zkuste si to schválně jako domácí cvičení. S výsledkem se pak můžete pochlubit v komentářích. A nezapomeňte algoritmus vyzkoušet i pro krajní stavy, jako je prázdné nebo jednopoložkové pole ( <li class="first last">...</li>).

Šablony a la Nette Framework

Nette Framework přichází s alternativní formou zápisu. Výpis prvků pole zjednoduší do této podoby:

<ul>
{foreach $items as $item}
        <li><a href='{$item->href}'>{$item->name}</a></li>
{/foreach}
</ul> 

Syntax vám může být povědomá, podobnou používá řada šablonovacích systémů, z těch nejznámějších například Smarty. Všimněte si důležité věci: proměnné se automaticky escapují. Pokud by proměnná obsahovala HTML kód a chtěli bychom ji vypsat bez jakékoliv transformace, stačí přidat vykřičník:  {!$item->name}.

Poprvé se tak setkáváme s principem „less code, more security“, kdy méně psaní vede k více zabezpečenému kódu. Chcete vypnout escapování? Pak přidejte vykřičník. Nebo naopak, pokud zapomenete vykřičník, nedopustíte se bezpečnostní chyby.

Syntaxe nabízí celou řadu dalších konstrukcí, jako jsou třeba podmínky:

{if $stock} Skladem {elseif $onWay} Na cestě {else} Není dostupné {/if} 

Dále cykly {for ...} ... {/for} nebo {while ...} ... {/while} a řadu užitečných maker, jako například:

{include 'paginator.phtml'} 

pro vkládání podšablony z jiného souboru nebo pomocník pro generování HTML atributů  {attr ...}.

Vraťme se k iteračnímu cyklu {foreach ...}. Uvnitř něj se automaticky inicializuje „magická“ proměnná $iterator, pomocí které můžeme zkoumat právě probíhající cyklus. Její API disponuje čítačem průchodů nebo metodami zjišťujícími, zda je aktuální průchod sudý, lichý, první nebo poslední. Váš domácí úkol, tedy označení prvního prvku CSS třídou first a posledního last, lze v Nette Frameworku napsat takto:

<ul>
{foreach $items as $item}
        <li{attr class('first', $iterator->first) class('last', $iterator->last)}><a href='{$item->href}'>{$item->name}</a></li>
{/foreach}
</ul> 

Stručné, přehledné, srozumitelné – jedním slovem kouzelné!

Další šikovnou vlastností je kontextově-sensitivní escapování. Mějme šablonu obsahující kód v HTML a JavaScriptu:

<p>{$movies}</p>

<script>alert({$movies});</script> 

Pokud bude proměnná $movies 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.

<p>Amarcord &amp; 8 1/2</p>

<script>alert("Amarcord & 8 1/2");</script> 

Pohled pod kapotu

Šablonovací jednotka je umístěna ve jmenném prostoru NetteTemplates, jehož jádrem je třída NetteTemplatesTemplate. Jak s ní zacházet nejlépe, ukáže příklad.

$template = new Template;
$template->setFile('template.phtml'); // specifikuje soubor se šablonou
$template->firstName = 'John'; // vložení parametru
$template->lastName = 'Doe';
$template->render(); // vykreslí šablonu 

Metoda render() vykoná PHP skript uložený v souboru template.phtml, přičemž parametry budou k dispozici v lokálních proměnných $firstName a $lastName. Navíc v proměnné $template bude samotný objekt šablony.

Filtry šablon

Zatím ale nelze použít výše uvedenou alternativní formu zápisu se složenými závorkami. Tu je třeba aktivovat skrze tzv. filtr. Jde o běžnou funkci, která na vstupu dostane text šablony a vrátí jej nějak upravený. Framework je už s několika filtry dodáván, z nichž nejzajímavajší je CurlyBracketsFilter přidávající podporu onu šikovnější syntax.

$template->registerFilter('NetteTemplatesCurlyBracketsFilter::invoke'); 

Filtr nahradí v šabloně sekvence {...} za odpovídající PHP kód. 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.

Z uvedeného vyplývají dvě podstatné skutečnosti. Jednak je šablonovací systém extrémně výkonný a plně využívá opcode akcelerace a cache. A za druhé – šablony lze pohodlně ladit, krokovat, umisťovat do nich break pointy, logovat výjimky a podobně.

Ještě je nutné určit adresář, do kterého se budou soubory cache ukládat:

// pro dočasné soubory použijeme podadresář tmp (musí mít práva zápisu)
Environment::setVariable('tempDir', dirname(__FILE__) . '/tmp'); 

Helpery

Vedle filtrů je možné do šablon registrovat i tzv. helpery. Opět jde o libovolnou funkci transformující vstupní proměnnou do výstupní podoby. Helperům však není předkládán obsah celé šablony, nýbrž si je kodér volá sám podle potřeby. Příkladem můze být třeba helper pro zobrazení data v národním formátu.

/**
 * České formátování data.
 * @param  int  timestamp
 * @return string
 */
function czechDateHelper($value)
{
        return date('j. n. Y', $value);
}

$template->registerHelper('date', 'czechDateHelper');
// nyní lze helper volat jako $template->date(...) 

Filtr CurlyBracketsFilter podporuje volání helperů přes svislítko:

Timestamp ve zdrojové podobě: {$timestamp}
Timestamp zformátovaný helperem date: {$timestamp|date} 

Helpery je možné zřetězit, např. {$title |lower|capitalize}, lze jim také předávat parametry, např. {$title |truncate:30}. Syntaxe bude opět blízká uživatelům Smarty.

Propojení s presenterem

Jak bylo řečeno výše, šablonovací engine je použitelný samostatně, tedy nejen v aplikacích postavených na bázi Model-View-Presenter. Nicméně platí to i naopak – tj. presentery nemusí používat šablonovací systém Nette Frameworku, ale jakýkoliv jiný. Stačí, aby implementoval rozhraní NetteTemplatesITemplate nebo NetteTemplatesIFileTemplate. Takže lze napsat obálku například nad Smarty implementující uvedené rozhraní a poté přepsat metodu presenteru createTemplate() tak, aby vracela objekt vlastní šablony. Integrovaný šablonovací systém je natolik výkonný a flexibilní, že nahrazovat jej jiným obvykle není třeba.

Vraťme se k našemu Automatu na kávu (viz díly MVC & MVP, Refactoring). Dalším logickým krokem bude zaregistrování filtru CurlyBracketsFilter a přepsání šablon do nové srozumitelnější syntaxe. Samotnou registraci je vhodné provést v metodě beforeRender(), která spadá do životního cyklu presenteru:

class MachinePresenter extends Presenter
{
        ...

        public function beforeRender()
        {
            $this->template->registerFilter('NetteTemplatesCurlyBracketsFilter::invoke');
        }

        ... 

Upravená šablona Machine.defau­lt.phtml:

<div id="machine">
  <p id="display">{$display}</p>

  <a href="{link buy!}"><img id="button" src="images/button.png" alt="Kup kávu" /></a>
</div>

<ul id="coins">
  <li><a href="{link insert!, 1}"><img src="images/coin-1.png" alt="Vhoď 1 Kč" /></a></li>
  <li><a href="{link insert!, 2}"><img src="images/coin-2.png" alt="Vhoď 2 Kč" /></a></li>
  <li><a href="{link insert!, 5}"><img src="images/coin-5.png" alt="Vhoď 5 Kč" /></a></li>
  <li><a href="{link insert!, 10}"><img src="images/coin-10.png" alt="Vhoď 10 Kč" /></a></li>
</ul> 

Zde vidíte použití makra {link ...}, které nahrazuje původní <?php echo $presenter->link(...) ?>. Nový kód automatu si můžete opět stáhnout na konci článku.

Šablonovací logika

Šablony představují osvědčenou a mnohdy podstatnou součást vrsty view. Někdy to může svádět k dojmu, že šablona = view. Taková představa je však mylná a vede ke špatnému návrhu aplikací. Například snaze umístit do šablony celou zobrazovací logiku. Toho se vyvarujte – do šablony patří jen podmnožina zobrazovací logiky, šablonovací logika.

Co to v praxi znamená? Vždy zvažte, jestli je šablona srozumitelná z pozice kodéra. Příklad: chcete v šabloně zobrazit stav a vytížení serveru a případný problém signalizovat barvou. Špatným rešením je provést analýzu přímo v šabloně, správným řešením je tak učinit v presenteru, například v metodě renderDefault() a teprve závěry předat parametrem šabloně. Na šablonovací logice pak leží jen úkol zjištěný závěr odprezentovat.

Co se nevešlo?

O šablonách by bylo možné psát ještě dlouho, nestlihl jsem představit všechny vestavěné filtry a helpery nebo nový koncept dědění šablon. Článek by se stal neúměrně dlouhým, takže zájemce odkazuji na oficiální dokumentaci.

Příště se opět vrátíme k aplikaci Automat na kávu, kde je třeba dohnat několik restů. Jako bonus se dozvíte, jak dát navždy sbohem konstrukci require_once a zase o něco zjednodušit nelehký život webového vývojáře.

Zdrojový kód ukázek je k dispozici ke stažení.

Autor článku je vývojář na volné noze, specializuje se na návrh a programování moderních webových aplikací. Pravidelně pořádá školení pro tvůrce webových aplikací, vyvíjí open-source knihovny Texy, dibi a Nette Framework.

Používáte šablonovací systém?

David Grudl školí, je autorem PHP knihoven Nette Framework, databázové vrstvy dibi a formátovače HTML kódu Texy!.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 48

Přehled komentářů

Mastodont Syntaxe
v6ak Re: Syntaxe
RuDa Re: Syntaxe
Ped Re: Syntaxe
romansklenar Re: Syntaxe
KLoK Re: Syntaxe
Jarda Re: Syntaxe
v6ak Re: Syntaxe
Jarda Re: Syntaxe
Mastodont Re: Syntaxe
Jarda Re: Syntaxe
Anonym Re: Syntaxe
kulda Re: Syntaxe
yossarian RE: Nette Framework: Chytré šablony
KLoK RE: Nette Framework: Chytré šablony
David Grudl RE: Nette Framework: Chytré šablony
yossarian RE: Nette Framework: Chytré šablony
Jarda RE: Nette Framework: Chytré šablony
Borelioza RE: Nette Framework: Chytré šablony
Jarda RE: Nette Framework: Chytré šablony
David Grudl RE: Nette Framework: Chytré šablony
v6ak RE: Nette Framework: Chytré šablony
Jakub Vrána Kontextově závislé escapování
David Grudl Re: Kontextově závislé escapování
David Grudl Re: Kontextově závislé escapování
Jakub Vrána Re: Kontextově závislé escapování
David Grudl Re: Kontextově závislé escapování
danaketh RE: Nette Framework: Chytré šablony
Martin Hassman RE: Nette Framework: Chytré šablony
danaketh RE: Nette Framework: Chytré šablony
v6ak Znalost XSS autorem šablon
Anonym Vyvijeno na Windows == Nizsi duveryhodnost
David Grudl Re: Vyvijeno na Windows == Nizsi duveryhodnost
Anonym Re: Vyvijeno na Windows == Nizsi duveryhodnost
David Grudl Re: Vyvijeno na Windows == Nizsi duveryhodnost
Anonym Re: Vyvijeno na Windows == Nizsi duveryhodnost
tomik.vitek Re: Vyvijeno na Windows == Nizsi duveryhodnost
Martin Hassman Re: Vyvijeno na Windows == Nizsi duveryhodnost
Anonym Re: Vyvijeno na Windows == Nizsi duveryhodnost
Martin Hassman Re: Vyvijeno na Windows == Nizsi duveryhodnost
KLoK Teď jsem se ztratil
Anonym Re: Vyvijeno na Windows == Nizsi duveryhodnost
tomik.vitek Re: Vyvijeno na Windows == Nizsi duveryhodnost
David Grudl Re: Vyvijeno na Windows == Nizsi duveryhodnost
Martin Hassman Re: Vyvijeno na Windows == Nizsi duveryhodnost
Karel Hák Hezká URL v Coffe Machine
kamil Šablonovací systém Grudly
David Žaba Sablona COFFEE nefunkcni
Zdroj: https://www.zdrojak.cz/?p=2983