Table of contents

  1. Szablony
    1. Dlaczego szablony są ważne
      1. Oddzielenie logiki od prezentacji
      2. Syf w kodzie i ataki XSS
    2. Czym, a czym nie?
      1. PHP jako język szablonów
      2. Smarty, Open Power Template 1.x i dziesiątki podobnych
      3. DOM
      4. XSLT
      5. PHPTAL
      6. Inne?
    3. Przerabianie aplikacji pod szablony

Dlaczego szablony są ważne

Oddzielenie logiki od prezentacji

Fatalnie utrzymuje się i rozwija aplikacje, w których w tej samej linijce jest kod ustalający kolor pisma i zapisujący dane do bazy. Tą metodą wychodzą zlepki kodu pokroju osCommerce.

Do tego są specyficzne dla PHP problemy, jak “headers already sent” przy ustawianiu cookie, gdy został już wypluty nagłówek HTML. Wprawdzie da się to obejść ustawiając ob_start(), ale i tak pozostają problemy — np. funkcje będące później w kodzie nie mogą zmienić tytułu strony ani dodać elementów do <head>.

Oddzielenie logiki od prezentacji pozwala też mieć różne prezentacje tego samego dokumentu. Na przykład nie trzeba drugi raz klepać tych samych funkcji do użytku przez AJAX — można podmienić szablon HTMLowy na XMLowy albo zamiast systemu szablonów konwertować rezultat na JSON.

Syf w kodzie i ataki XSS

Porządny system szablonów może zapewnić poprawne escape'owanie tekstu, czyli zamieniać specyficzne dla HTML znaki w tekście na encje. Dzięki temu poprawne kodowanie <a href> (o co walidator zawsze się czepia) z niewyobrażalnie mozolnego zadania nagle staje się rzeczą, która robi się sama.
Do tego szablony mogą zapewnić, że HTML/XHTML będzie (przynajmniej składniowo) poprawny. Przy generowaniu XHTML to jest konieczność.

Poprawne kodowanie encji wszędzie jest też ważnym elementem zabezpieczania się przed atakami XSS, czyli wrabianiem aplikacji w umieszczenie na stronie JavaScript, który np. wykradnie login albo cookie z sesją. Dziurawa strona może też być wykorzystana do spamowania wyszukiwarek. Istnieje przerażająco duża ilość przebiegłych sposobów na ominięcie filtrów próbujących usunąć JavaScript z danych wejściowych, więc trzeba się tym zająć na wyjściu.

Czym, a czym nie?

Dobry system szablonów to taki, w którym łatwiej jest zrobić poprawny dokument, niż niepoprawny.

Weźmy za zadanie: wygenerować odnośnik (X)HTML, który ma opcjonalny atrybut title i ma mieć tekst opcjonalnie umieszczony w tagu <em>.

PHP jako język szablonów (odradzany)

Rozdzielenie kodu na PHP-obrabiający-dane i PHP-wyświetlający-dane to zawsze lepsze, niż nic, ale paradoksalnie PHP jest kiepski jako preprocesor hipertekstu.

Wypisanie odnośnika z tekstową etykietą wymaga strasznej kombinacji alpejskiej:

<a href="<?php echo htmlspecialchars($href);?>"<?php if ($title) { ?> title="<?php echo htmlspecialchars($title);?>"<?php }?>><?php if ($em) { ?><em><?php } echo htmlspecialchars($tytul); if ($em) ?></em><?php } ?></a>

Można by to nieco skrócić przerabiając wszystko na jedno złożone echo, ale dużo piękniejsze to się nie zrobi — domyślnie PHP nawet nie ma żadnego skrótu na wypisywanie HTML i co krok wymaga męczenia się z htmlspecialchars().

Do tego programista musi pamiętać o zamknięciu wszystkich tagów (a samego kodu PHP nie da się zwalidować, więc trzeba walidować różne rezultaty różnych warunków — nietrudno coś przegapić). Na dodatek jeśli element jest dodawany warunkowo — trzeba powtórzyć warunek przy tagu zamykającym.

Smarty, Open Power Template 1.x i dziesiątki podobnych (odradzany)

Lekka ulga dla PHPowej składni, ale problemy wszystkie te same:

<a href="{$href|escape}"{if $title} title="{$title|escape}"{/if}>{if $em}<em>{/if}{$tytul|escape}{if $em}</em>{/if}</a>

OPT 2.x to zupełnie inna bestia i zachowuje się poprawnie.

OPT 1.x można jeszcze przełączyć na bardziej XMLopodobną składnię, ale to tylko powierzchowna zmiana — mimo tego nadal nie będzie miał zielonego pojęcia o składni języka, który generuje, więc bez zająknięcia zrobi tagzupę tak samo, jak Smarty.

DOM

Generowanie kodu za pomocą DOM gwarantuje jego poprawność składniową — programista buduje elementy i atrybuty, a serializer zajmuje się przeróbką tego na tagi i encje. To jest wielką zaletą.

Natomiast wielką wadą DOM są niesamowicie długie nazwy funkcji (umyślnie długie, żeby na pewno nie kolidowały z funkcjami w jakimś języku programowania) i mozolne budowanie każdego detalu.

$doc = new DOMDocument();
$a = $doc->createElement('a'); $a->setAttribute('href',$href);
if ($title) $a->setAttribute('title',$title);
if ($em) { $temp = $doc->createElement('em'); $a->appendChild($temp); }
else $temp = $a;
$temp->appendChild($doc->createTextNode($tytul));

DOM staje się znośny dopiero, gdy napisze się kilka funkcji pomocniczych, które np. przerabiają tablicę asocjacyjną na element z atrybutami.

XSLT (zalecany)

XSLT wymaga danych wejściowych jako drzewo DOM, więc nie można się kompletnie pozbyć DOM, ale na dłuższą metę jest mniej roboty, bo wszystkie “dekoracyjne” elementy i atrybuty może dodać XSLT.

<x:template match="a">
<a href="{href}"><x:if test="string(title)"><x:attribute name="title"><x:value-of select="title"/></x:attribute></x:if><x:value-of select="tytul"/></a>
</x:template>

XSLT działa w unikalny sposób — bardziej jak CSS, niż imperatywny język programowania. Deklaruje się szablony, które za pomocą selektorów XPath same “złapią” i przerobią odpowiednie elmenty w wejściowym dokumencie. Dzięki temu łatwo jest upewnić się, że np. każdy adres e-mail jaki przekażesz do szablonu będzie zawsze miał odpowiedni kod HTML (nadaną klasę albo ukrywanie przed spambotami) i nie trzeba o tym za każdym razem pamiętać. Oczywiście na początku trudno przestawić się na taką filozofię.

XSLT może generować zarówno HTML, jak i XHTML. Stety lub niestety, XHTML który generuje XSLT jest bezlitośnie XMLowy i dla XSLT nie ma różnicy między notacją <img/>, <img></img> ani <xhtml:img xmlns:xhtml=­"http://www.w3.org/1999/xhtml"/>. Trudno jest to kontrolować, więc zapomnij o serwowaniu pseudoXHTML “działającego” w Internet Explorerze i Google (to mała strata).

Następną zaleto-wadą jest konieczność podania wszystkich danych do szablonu. Wada, bo aplikacja musi przewidzieć wszystko, co będzie na stronie i co trzeba dla szablonu przygotować, bo bez hacków szablon nie ma jak zażądać dodatkowych danych. Zaleta, bo nie da się do szablonu wpakować za dużo logiki aplikacji (inaczej rozdzielenie logiki i prezentacji zamieniło by się na przeprowadzkę do innego języka programowania).

Zdecydowaną wadą XSLT jest składnia. Niektóre rzeczy wymagają dużo pisania. Niektóre strasznie dużo pisania. Bez edytora z makrami i auto-uzupełnianiem kodu będzie męczący.

PHPTAL (zalecany) (zalecany)

PHPTAL (polska strona) jest klonem systemu szablonów ZPT (składającego się z TAL, TALES i METAL) z pythonowego CMS-u Zope. Dużo nazw, jak na jeden produkt :)

<a href="${href}" tal:attributes="title title | nothing"><em tal:omit-tag="not:em" tal:content="tytul" /></a>

PHPTAL rozwiązuje bolączki każdego z konkurentów:

Ale jak wszystko ma wady:

Inne?

XT

Nie testowałem jak to działa w praktyce.

XT ma radykalnie inne podejście do szablonów — zamiast używać rozkazów w szablonach, które wyciągają dane z programu, to używa selektorów CSS/XPath do wypełniania istniejącego dokumentu danymi.

patTemplate

Nie testowałem.

patTemplate jest XMLowym systemem szablonów, który stara się być zupełnie deklaratywnym. Nie ma if/else, nie ma pętli — szablony są dopasowywane do danych wejściowych i automatycznie powielane przy wyświetlaniu tablic.

Więcej?

Jak znasz jakieś inne systemy szablonów, które nie są zlepiaczką skrawków tekstu, ani nie są porzuone od kilku lat, to daj znać.

Forum dyskusyjne o szablonach.

Przerabianie aplikacji pod szablony

Do wykorzystania szablonów trzeba zmienić nieco architekturę aplikacji — funkcje zamiast wyrzucać swój rezultat prosto do użytkownika, powinny zwracać dane. Do tego wystarczy stary, dobry return i tablice (asocjacyjne).

zmiana:

echo "<h1>$foo</h1>";
echo "<ul>";
foreach(wczytaj_dane_z_bazy() as $costam)
{ echo "<li>$costam</li>"; }
echo "</ul>";

na:

return array(
'foo'=>$foo,
'cosie'=>wczytaj_dane_z_bazy()
);

i gdzieśtam później w szablonie:

<h1 tal:content="foo" />
<ul><li tal:repeat="cos cosie" tal:content="cos" /></ul>

Do tworzenia bardziej złożonych tablic w PHP wypada znać ich składnię, a w szczególności najprostszy sposób dodawania elementów (coby się z indeksami ani array_push nie wygłupiać). W praktyce w kodzie mogą często pojawiać się podobne konstrukcje:

$rezultat = array();
foreach($dane_wejsciowe as $d)
{
$rezultat[] = array(
'bla'=>zrob_bla($d),
'baz'=>$d['baz'],
'quz'=>'quz' . $d['quz1'] . $d['quz2'],
);
}
return array(
'to'=>"to i owo",
'rezultat'=>$rezultat,
);

A główna część aplikacji może wyglądać tak:

$strona = wykombinuj_dane_strony();
$tpl = new PHPTAL($strona['szablon']);
$tpl->strona = $strona;
$tpl->uzyszkodnik = znajdz_kto_zalogowany();
$tpl->menu = wyczaruj_menu();
echo $tpl->execute(); // jedyne echo w całej aplikacji!