e-mail:

Zaawansowane możliwości Smarty

Autor: Adam Major
Artykuł został pierwotnie opublikowany w magazynie PHP Solutions nr 1 (8/2003) Software 2.0 Extra
www.phpsolmag.org
 

Smarty jest jednym z najbardziej zaawansowanych systemów szablonów przeznaczonych dla języka PHP. Charakteryzuje się duża szybkością działania oraz rozbudowaną funkcjonalnością wyróżniającą ten produkt wśród wielu istniejących na rynku.
Systemy szablonów pozwalają na oddzielenie części prezentacyjnej aplikacji (HTML, grafika, JS, CSS) od skryptów PHP, co zapewnia nieporównywalną wygodę tworzenia i modyfikacji programów oraz ich przejrzystość. Podstawy posługiwania się Smarty oraz porównawczy test wydajnościowy konkurencyjnych systemów szablonów znajdą czytelnicy w moim artykule zamieszczonym w Software 2.0 PHP Extra! nr 1 "PHP starter kit" lub online pod adresem www.freshdata.pl/artykuly_szablony.html.

Mechanizm cache

Jest kolejną funkcją po kompilowaniu szablonów zaprojektowaną w celu zwiększenia szybkości działania serwisu oraz odciążenia serwera od zbędnej pracy. Zasada wykorzystania tego mechanizmu polega na tym, aby wygenerowane strony, których treść zmienia się stosunkowo rzadko, zapisywać do przetworzonych już wynikowych plików HTML. Tak przygotowane pliki wysyłane są do przeglądarki bez konieczności wykonywania czasochłonnych operacji: skomplikowanych obliczeń, pobierania danych z bazy SQL oraz przetwarzania szablonu.

Stosując ten mechanizm zachowujemy pełną wygodę i kontrolę nad szablonem poprzez ustawienie ważności bufora oraz możliwość jego usunięcia w wybranej przez nas chwili. Przy każdym przetwarzaniu skryptu, Smarty sprawdza czy nie został zmieniony szablon bazowy z którego utworzono plik cache oraz czy bufor istnieje i ma przyszły termin ważności, jeśli któryś z tych warunków nie jest spełniony to automatycznie jest tworzony od nowa.

Aby skorzystać z funkcji buforującej Smarty należy przygotować katalog w którym będą przechowywane przetworzone do postaci HTML pliki bufora oraz dokonać konfiguracji w pliku Smarty.class.php.
Katalog - nazwijmy go cache - najlepiej umieścić ponad DOCUMENT_ROOT serwera WWW, jeśli to nie jest możliwe np.: z powodu braku takiej opcji u firmy hostującej, warto utworzyć go w głównym katalogu naszego serwisu .
  Sposób przetwarzania szablonów Rysunek 1. Sposób przetwarzania szablonów: a) standardowy, b) z mechanizmem cache
Musimy umożliwić zapis do cache dla użytkownika na którego prawach pracuje serwer WWW (zwykle nobody lub http), jeśli mamy takie uprawnienia lub możemy poprosić administratora należy nadać prawa 770 oraz zmienić właściciela katalogu na http:http, jeśli nie to musimy zadowolić się 777.

Zanim uruchomimy pierwszy przykład wykorzystujący mechanizm należy zmienić ustawienia kilku opcji głównego pliku systemu szablonów. Położenie katalogu przeznaczonego na zbuforowane pliki ustawiamy za pomocą zmiennej $cache_dir. W zasadzie na razie możemy zakończyć konfigurację. Jednakże, jeśli PHP pracuje w trybie bezpiecznym (safe_mode) będziemy zmuszeni do wyłączenia tworzenia podkatalogów w cache i tpl_c nadając zmiennej $use_sub_dirs wartość false. Podkatalogi te, pozwalają uniknąć powstawania dużej ilości plików w jednym folderze, a tym samym przyspieszają odnalezienie pliku przez system operacyjny.

Zaczniemy od bardzo prostego przykładu, prezentującego różnicę w działaniu Smarty przy normalnym przetwarzaniu szablonu oraz z udziałem mechanizmu cache. Tworzymy nieskomplikowany szablon o nazwie cache.tpl i umieszczamy w katalogu przeznaczonym na szablony (w moim przypadku tpl).

Jeśli chcemy włączyć buforowanie danego szablonu, ustawiamy dwie zmienne obiektu $smarty, $smarty->caching na wartość 2, co spowoduje możliwość lokalnego określenia czasu wygaśnięcia bufora w sekundach (przy wartości 1 brane są ustawienia globalne z Smarty.class.php).
Drugą zmienną jest wspomniany wyżej czas życia zbuforowanego pliku w naszym przykładzie = 10. Odświeżając stronę kilkukrotnie możemy zaobserwować działanie mechanizmu, który przez 10 sekund pobiera wygenerowany plik pomijając podstawianie nowych zmiennych pod znaczniki, a po upływie ważności generowany jest nowy plik bufora.

Warto zauważyć, że skorzystanie z mechanizmu buforującego nie pociąga żadnych zmian w szablonie.
 
<html>
<body bgcolor="#FFFFFF">
Data: {$a_date}
<body>
</html>
Listing 1 Prosty szablon

<?php
include_once('Smarty.class.php');
$smarty = new Smarty;

$smarty->cache_lifetime = 10;
$smarty->caching = 2;
$smarty->assign('a_date', date('Y.m.d H:i:s'));
$smarty->display('cache.tpl');
?>
Listing 2 Zastosowanie mechanizmu cache


Buforowanie fragmentów stron

W praktyce zdarzają się sytuacje, kiedy chcemy buforować jedynie fragment strony, którego generowanie trwa stosunkowo długo lub/i jego postać zmienia się rzadko, a nie możemy cachować całej strony, ponieważ zawiera ona elementy, które muszą się zmieniać dynamicznie np. banery.
Doskonałym przykładem jest moduł ankiet, umożliwiający zbieranie opinii na przeróżne tematy od czytelników naszego serwisu oraz prezentujący wyniki. Bardzo mały odsetek osób uczestniczy w głosowaniu przez co wyniki nawet w serwisach o dużej oglądalności zmieniają się dość rzadko. Funkcjonalnie moduł taki składa się z dwóch stron: formularza z pytaniem oraz wyników głosowania.

Ważną cechą Smarty, którą zastosujemy jest możliwość generowania kilku różnych buforów z tego samego szablonu na podstawie wybranego przez nas parametru (np. identyfikatora klienta z bazy danych). Tworzymy więc jeden szablon odpowiedzialny za wyświetlanie pytania oraz wyników głosowania.

Czy wyświetlać pytanie, czy wynik głosowania skrypt będzie decydował na podstawie zawartości ciastka (cookie), które ustawiamy po oddaniu głosu. Oczywiście w przypadku ankiet poleganie wyłącznie na mechanizmie cookie nie wystarczy aby utrudnić wielokrotne głosowanie, jednak ten artykuł pokazuje jedynie uproszczoną ideę tworzenia takiego modułu.

Zadaniem kodu przedstawionego na listingu 4 jest wybór prezentowanej informacji: formularza ankiety lub jej wyników na postawie zawartości ciastka o nazwie q_ans. Następnie posługujemy się metodą is_cached(), która przyjmuje dwa argumenty: nazwę szablonu oraz identyfikator bufora. Właśnie dzięki temu identyfikatorowi możemy tworzyć wiele instancji bufora dla tego samego szablonu. W przypadku, gdy właściwy plik cache nie spełnia warunków wykorzystania: nie istnieje, stracił ważność lub zmieniono źródłowy szablon (kryterium to jest sprawdzane jeśli zmienna $compile_check na wartość true) spełniony jest warunek i dołączany plik mod_an.php. Zadaniem tego pliku jest pobranie informacji o pytaniu lub wynikach ankiety, obrobienie ich i przyporządkowanie odpowiednich wartości pod znaczniki obsługiwane przez szablon z listingu 3.

Najistotniejszymi fragmentami kodu są linie zawierające metodę fetch(), której działanie jest podobne do metody display() z tą różnicą, że przetworzony szablon nie jest wyświetlany, a zwracany. Dzięki połączeniu jej z assign() całą przetworzoną zawartość mamy przypisaną do znacznika {$mod_an}, który umieszczamy w dogodnym dla nas miejscu w głównym szablonie. Istotne jest, że dzięki zastosowaniu powyższej konstrukcji, fetch() użyje zbuforowanej wersji lub zostaną podstawione nowe wartości poprzez dołączany plik mod_an.php.
Należy pamiętać o wyłączeniu mechanizmu buforującego za pomocą ustawienia zmiennej caching na 0, co zapobiegnie buforowaniu pozostałej dynamicznej zawartości strony.

Bufory będą samoczynnie czyszczone po upływie 24 h od ich utworzenia, a proces regeneracji uruchomi się przy wejściu pierwszej osoby na stronę. Jeśli chcemy skasować cache szablonu w wybranym przez nas momencie powinniśmy posłużyć się metodą clear_cache(), która przyjmuje cztery parametry, jednak w większości przypadków wystarczą nam dwa pierwsze: nazwa szablonu oraz opcjonalny identyfikator bufora. W przykładowym module ankiet, czyszczenie cache wyników powinno się odbywać po zweryfikowaniu czy dany użytkownik może głosować: poprzez
 
<table width=129 cellpadding=0 cellspacing=0>
<tr><td>
{if $mc != "res"}
<form action="vote.php" method=post> 
<b>{$question}</b><br />
{section name=row loop=$row_cnt}
<input type=radio name=z value={$o_val[row]}>
{$o_name[row]}<br />
{/section}
<div align=center>
<input type=submit value="Głosuj" class=fsu>
</div>
</td></form></tr>
{else}
<b>{$question}</b><br />
<table width=116 cellpadding=1 cellspacing="0">
{section name=row loop=$row_cnt}
<tr><td>{$answers[row]}: {$scores_p[row]}</td>
</tr>
{/section}
</table></td></tr>
{/if}
</table>
Listing 3 Prosty szablon modułu ankiet

<?php
include_once('Smarty.class.php');
$smarty = new Smarty;
$q_id = 1;
$smarty->caching = 2;
$smarty->cache_lifetime = 86400;
if ($_COOKIE['q_ans'] == $q_id) {
   if(!$smarty->is_cached('mod_an.tpl','res')) 
      { 
      $mc = 'res';
      include('mod_an.php');
      }
$smarty->assign('mod_an', 
	$smarty->fetch('mod_an.tpl','res'));
} else {
   if(!$smarty->is_cached('mod_an.tpl')) 
      {
      $mc = 'questions';
      include('mod_an.php');
      }
$smarty->assign('mod_an', 
	$smarty->fetch('mod_an.tpl'));
}
$smarty->caching = 0;

$smarty->assign('a_date', date('Y.m.d H:i:s'));
$smarty->display('main.tpl');
?>
Listing 4 Sterowanie buforami modułu

<html>
<body bgcolor="#FFFFFF">
Dane dynamiczne: {$a_date}
{$mod_an}
</body>
</html>
Listing 5 Szablon główny - main.tpl

sprawdzenie obecności cookie oraz porównanie kombinacji IP i IP Proxy użytkownika z listą osób głosujących na daną ankietę w ciągu ostatnich 20 min. Jeśli warunki są spełnione należy zwiększyć stosowną wartość dla tego głosowania (w bazie danych lub w pliku), wysłać ciastko informujące o głosowaniu, skasować bufor wyników poleceniem $smarty->clear_cache('mod_an.tpl','res'); oraz przekierować przeglądarkę na stronę z szablonem głównym.

Natomiast jeśli dodamy nową ankietę musimy skasować zarówno wyniki jak i formularz głosowania, oczywiście w drugim przypadku pomijamy identyfikator bufora.

Mechanizm cache jest bardzo pożytecznym narzędziem, szczególnie jeśli prezentujemy na stronie informacje, których przygotowanie jest bardzo pracochłonne dla serwera np. analiza statystyk. Dzięki niemu generujemy stronę powiedzmy raz na godzinę w dodatku, następuje to tylko gdy jakiś użytkownik ją wywoła (i minie termin ważności bufora).

Rozszerzanie funkcjonalności

Dzięki zaimplementowaniu systemu wtyczek (plugins), Smarty można bardzo prosto wzbogacić o nowe modyfikatory, które będzie można wykorzystywać w identyczny sposób jak standardowe. W przypadku serwisów wielojęzycznych, a zwłaszcza sklepów internetowych, istotna jest prezentacja informacji o produktach w pełni dostosowana do specyfiki danego kraju. Oprócz opisów w danym języku, widocznymi różnicami jest sposób zapisu daty oraz formatowania liczb (np. separator dziesiętny). Dostosowanie tych elementów można obsłużyć bezpośrednio w skryptach PHP, jednak dużo wygodniejsze jest zrealizowanie tego zadania w szablonach.

Modyfikator formatujący datę został już umieszczony standardowo, dopiszemy więc własny realizujący formatowanie liczb w oparciu o funkcję PHP number_format(). Tworzenie modyfikatorów jest bardzo proste i sprowadza się do utworzenia w katalogu plugins (lub innym zdefiniowanym na wtyczki Smarty) pliku o nazwie modifier.nazwa_modyfikatora.php. W pliku takim tworzymy funkcję, której nazwa musi mieć postać smarty_modifier_nazwa_modyfikatora. Zwracana wartość (poprzez return) funkcji zostanie podstawiona do szablonu.

<?php
function smarty_modifier_number_format($number, $num_dec=null, $dec_sep=null,
	$t_sep=null)
{
	return number_format($number, $num_dec, $dec_sep, $t_sep);
}
?>
Listing 6 Modyfikator number_format

Modyfikator ten posiada 4 parametry, pierwszy jest zawsze dostarczany przez Smarty i przyjmuje wartość przypisaną do znacznika lub przetworzoną przez poprzednie modyfikatory, jeśli takie operowały na znaczniku. Smarty bowiem, umożliwia łączenie działania kilku modyfikatorów poprzez znak | np. {$znacznik|upper|spacify} najpierw wartość zostanie przetworzona przez modyfikator upper, następnie wynik tego działania zostanie podany do obróbki modyfikatorowi spacify.

Posługiwanie się nowym modyfikatorem przebiega tak samo jak standardowymi, poprzez wywołanie {$znacznik|number_format} uzyskujemy obcięcie części dziesiętnej liczby. Parametry modyfikatora podajemy oddzielając je za pomocą znaku :. Formatowanie stosowane w Polsce uzyskujemy poprzez użycie w szablonie konstrukcji {$znacznik|number_format:2:',':' '}, natomiast anglosaskie {$znacznik|number_format:2:'.':','}.

Osoby preferujące całkowite oddzielenie standardowych wtyczek systemu od własnych rozszerzeń, mogą składować je w osobnym katalogu o nazwie np. new_plugins. W tym celu należy dopisać w pliku Smarty.class.php w tabeli $plugins_dir nową lokalizację katalogu z wtyczkami.

Filtry

Smarty wyposażono w system filtrów za pomocą, których możemy wpływać na szablon przed jego przetwarzaniem (prefilter) lub oddziaływać na jego wynik przed kompilacją szablonu (postfilter) oraz w chwili wysyłania ostatecznego rezultatu - filtry wyjścia (output filters). Filtry mogą być umieszczane podobnie jak modyfikatory w katalogu przeznaczonym na wtyczki, w takim przypadku pliki muszą mieć nazwę rodzaj.nazwa_filtra.php , gdzie rodzaj może przyjąć wartość prefilter, postfilter, outputfilter. Natomiast funkcje odpowiednio nazwy smarty_rodzaj_nazwa_filtra. Istnieje także możliwość deklaracji funkcji filtra bezpośrednio w skrypcie PHP i rejestracji jej poprzez jedną z metod register_*().

Zastosowanie filtrów może być bardzo różnorakie od usuwania z szablonów komentarzy HTML, otaczanie sekcji JavaScript i CSS w razie konieczności znacznikami {ldelim}literal{rdelim} {ldelim}/literal{rdelim} poprzez modyfikację wyniku przetwarzania, polegającą np. na zamianie znaku @ na (at) w adresach email.

Stworzymy prefilter, którego zadaniem będzie usuwanie komentarzy HTML z szablonów przed ich przetwarzaniem. W katalogu plugins tworzymy plik prefilter.remove_comments.php, w którym zamieszczamy funkcję przedstawioną na listingu 7.

Aby zweryfikować działanie prefilter'a użyjemy skryptu, którego zadaniem będzie podstawienie pod znacznik {$komentarz} łańcuch znaków reprezentujący HTMLowy komentarz. Co pozwoli nam wykazać, że ten typ filtrów rzeczywiście wykonywany jest przed rozpoczęciem przetwarzania szablonu, a tym samym przed podstawianiem wartości pod znaczniki.

Załadowanie filtra odbywa się za pomocą metody load_filter(), która oczekuje dwóch parametrów: pierwszy określa typ filtra, może przyjmować wartość: pre, post i output, natomiast drugi to jego nazwa. W wyniku zadziałania tego skryptu otrzymamy stronę z komentarzem ustawionym przez metodę assign(), natomiast zdefiniowane bezpośrednio w szablonie komentarze zostaną usunięte.

Warto zauważyć, że filtry nie oddziałują bezpośrednio na pliki z szablonami, a jedynie na postać skompilowaną lub
 
<?php
function smarty_prefilter_remove
	_comments($source, &$smarty)
 {
 return preg_replace('/<--.*-->/', '', $source);
 }
?>
Listing 7 Prefilter remove_comments

<html>
<body>
<!-- komentarz 1 //-->
{$komentarz}
<!-- komentarz 2 //-->
</body>
<html>		
Listing 8 Szablon prezentujący działanie prefilter'a

<?php
include_once('Smarty.class.php');
$smarty = new Smarty;

$smarty->assign('komentarz', '');
$smarty->load_filter('pre', 'remove_comments');
$smarty->display('prefiler.tpl');
?>
Listing 9 Uruchomienie prefilter'a

zmiany są dokonywane tuż przed samym wyświetleniem strony.

Podsumowanie

Smarty dysponuje bardzo dużą funkcjonalnością, a dzięki technice dołączenia własnych wtyczek, potrafi sprostać rozwijającym się potrzebom. Dodatkowo system ten przy zapewnieniu odpowiednich warunków jest jednym z najszybszych silników szablonów dla PHP. Cechy te, a także stały rozwój Smarty sprawia, że może on być stosowany w dużych wymagających serwisach.

Mam nadzieję, że ten artykuł oraz jego pierwsza część, zachęciły czytelników do zastosowania Smarty w swoich projektach, a Tych z Państwa, co używają innych rozwiązań szablonowych do przejścia na Smarty.

szablony2.zip (65 KB) - Listingi do artykułu