Latte je šablonovací systém pro PHP, který vám 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í filtrů
  • 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!

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="…"

Instalace a použití

Nejlepší způsob, jak nainstalovat Latte je stáhnout nejnovější balíček nebo použít Composer:

composer require latte/latte

Latte vyžaduje PHP verze 5.4.4 nebo novější (je kompatibilní s PHP 7.0 a 7.1).

Jak vykreslit šablonu? Stačí spustit tento kód:

$latte = new Latte\Engine;

$latte->setTempDirectory('/path/to/tempdir');

$parameters['items'] = ['one', 'two', 'three'];

// kresli na výstup
$latte->render('template.latte', $parameters);
// kresli do řetězce
$html = $latte->renderToString('template.latte', $parameters);

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. filter, 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:

  1. pole můžeme zapisovat jako [1, 2, 3], což odpovídá [1, 2, 3]
  2. můžeme vynechat uvozovky kolem řetězce z písmen, číslic a pomlček
  3. 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.

Filtry

Podívejte se na přehled standardních filtrů.

Filtry se zapisují za svislítko (může být před ním mezera) do tzv. modifikátoru:

<h1>{$heading|upper}</h1>

Filtry (ve starších verzích helpery) 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 filtru oddělené dvojtečkami nebo čárkami:

<h1>{$heading|truncate:20,''}</h1>

Filtry lze aplikovat i na výraz:

{var $name = ($title|upper) . ($subtitle|lower)}</h1>

Vlastní filter

Jako filtr lze do šablony zaregistrovat libovolný callback:

$latte = new Latte\Engine;
$latte->addFilter('shortify', function ($s) {
    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 parameter:

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

V šabloně se potom volá takto:

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

Je doporučeno registrovat vlastní filtr vždy v tom presenteru, ve kterém ho využijete.

Univerzální filter

Manuální registraci více filterů lze nahradit registrací jednoho univerzálního:

$latte->addFilter(null, 'Filters::common');

Ten dostane jako parametr název požadovaného filtru:

class Filters
{
    public static function common($filter, $value)
    {
        if (method_exists(__CLASS__, $filter)) {
            $args = func_get_args();
            array_shift($args);
            return call_user_func_array([__CLASS__, $filter], $args);
        }
    }

    public static function shortify($s, $len = 10)
    {
        return mb_substr($s, 0, $len);
    }
}

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(&quot;Amarcord &amp; 8 1\/2&quot;)">Amarcord &amp; 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!

JavaScript

Řetězec vkládaný do JavaScriptu se escapuje včetně uvozovek. Pokud jej chcete vložit do jiného řetězce, jednoduše je spojte:

<script>
    alert('Hello ' + {$name} + '!');  # správně
    alert('Hello {$name} !');  # špatně!
</script>

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 Latte\Engine;

// vytvoříme si sadu
$set = new Latte\Macros\MacroSet($latte->getCompiler());

// 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');

Načítání šablon z řetězce

Pokud nemáme naši šablonu uloženou v souboru, ale pouze v proměnných, musíme nastavit loader na Latte\Loaders\StringLoader.

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

$latte->render('main', $parameters);