Poprzedni rozdział: Czym jest system szablonów?
Następny rozdział: Open Power Template

Smarty

edytuj

Nasze praktyczne zmagania z systemami szablonów rozpoczniemy od biblioteki Smarty.

Instalacja

edytuj

W przeciwieństwie do dotąd objaśnianych zestawów funkcji oraz rozszerzeń, Smarty napisany jest w całości w PHP, dlatego musimy samodzielnie przeprowadzić proces jego instalacji. Nie jest on jednak trudny. W praktyce sprowadza się on jedynie do skopiowania gdzieś plików i dołączenia do skryptu jednego z nich. Dokładna procedura opisana jest poniżej:

  1. Wchodzimy na stronę http://www.smarty.net/ i pobieramy stamtąd najnowszą dostępną wersję (w chwili pisania tego tekstu - 2.6.16).
  2. Zakładamy, że nasze skrypty są w katalogu kursphp. Tworzymy w nim nowy podkatalog, np. smarty .
  3. Otwieramy ściągnięte archiwum i kopiujemy do naszego nowo stworzonego katalogu zawartość folderu libs.
  4. Aby załadować bibliotekę do naszej aplikacji, dołączamy plik Smarty.class.php.

Zanim zaczniemy, musimy jeszcze utworzyć dwa dodatkowe katalogi:

  1. /templates - tu trzymać będziemy nasze szablony. PHP musi mieć uprawnienia do odczytu.
  2. /templates_c - aby zwiększyć wydajność, Smarty najpierw kompiluje każdy szablon do postaci kodu PHP, a dopiero później go wykonuje. Raz skompilowany kod jest przechowywany na HDD w tym właśnie katalogu. Programista nie powinien tam nic zmieniać - po prostu należy przydzielić dla PHP prawa do zapisu i nic więcej.

Pierwszy skrypt

edytuj

Szablon to nic innego, jak plik z kodem HTML, który zawiera dodatkowe znaczniki określające, gdzie mają pojawić się dane ze skryptu. Nasz pierwszy szablon (szablon1.tpl) wygląda następująco:

<html>
 <head>
  <title>Smarty: pierwszy skrypt</title>
 </head>
 <body>
   <p>Hello world! Dzisiaj jest {$data}!</p>
 </body>
</html>

{$data} - ten fragment oznacza, że w tym miejscu ma pojawić się zawartość zmiennej $data. (Możliwe jest jednak zmiana systemu oznaczenia wprowadzania zmiennych Smarty. Są to dwa pola z klasy smarty z pliku libs/Smarty.class.php. var $left_delimiter, oraz var $right_delimiter) Jednak uważaj: to, że w szablonie stosujemy taką zmienną, nie znaczy wcale, że jeżeli stworzymy w naszym skrypcie analogiczną zmienną, to ją nam w tym miejscu wyświetli. Zmienne wewnątrz szablonów i zmienne PHP są dwiema zupełnie osobnymi rzeczami. Zmienne szablonowe należy utworzyć odpowiednią funkcją i przypisać im jakąś wartość ze skryptu. Popatrzmy więc, jak się to robi:

<?php

	require('./smarty/Smarty.class.php'); // 1
	
	$tpl = new Smarty; // 2
	$tpl -> template_dir = './templates/';
	$tpl -> compile_dir = './templates_c/';

	$tpl -> assign('data', date('d.m.Y')); // 3
	$tpl -> display('szablon1.tpl'); //4

?>

Opis skryptu (szablon1.php):

  1. Na początku ładujemy bibliotekę.
  2. Smarty ma budowę obiektową. Aby rozpocząć zabawę z szablonami, musimy utworzyć obiekt klasy Smarty i go skonfigurować. Najprostsza konfiguracja polega na określeniu ścieżek do katalogów z szablonami oraz ich skompilowanymi wersjami, ale dyrektyw jest znacznie więcej.
  3. Tutaj ustawiamy, jaką wartość ma mieć szablonowa zmienna $data.
  4. Kiedy wszystkie dane przenieśliśmy już do parsera, możemy nakazać mu przetworzenie konkretnego szablonu.

Po uruchomieniu powyższego skryptu zobaczysz, że na ekranie przeglądarki pojawił się napis "Hello world! Dzisiaj jest (tu aktualna data)!"

Więcej o zmiennych

edytuj

Smarty to coś więcej, niż zwykłe umieszczanie danych w kodzie HTML. W zasadzie po stronie szablonów mamy do dyspozycji całkiem rozbudowany język programowania obsługujący m.in. tablice i obiekty z PHP, a także rozmaite formy manipulacji danymi. Załóżmy, że mamy jakąś listę wiadomości, jednak z oczywistych przyczyn na stronie głównej pragniemy wyświetlić jedynie nagłówki i kilkanaście początkowych wyrazów. Możemy cały mechanizm przetwarzania zrealizować po stronie PHP, lecz z tym różnie bywa, zwłaszcza gdy kod PHP do pobierania listy wykorzystywany jest jeszcze w paru innych miejscach, gdzie taka właściwość jest niewskazana. To jednak nie problem, ponieważ możemy odpowiednie przycinanie dodać bezpośrednio w szablonie, który tego wymaga.

<?php

	require('./smarty/Smarty.class.php');
	
	$tpl = new Smarty;
	$tpl -> template_dir = './templates/';
	$tpl -> compile_dir = './templates_c/';

	$tpl -> assign('wiadomosc', array(
		'tytul' => 'Premier podaje się do dymisji!',
		'data' => date('d.m.Y'),
		'autor' => 'Jan Nowak',
		'tresc' => 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
		Cras nec diam. In hac habitasse platea dictumst. Donec id leo. Ut
		feugiat augue at metus. In hac habitasse platea dictumst. Donec
		pulvinar sollicitudin tellus. Quisque mattis faucibus nulla. Praesent
		in mauris. Maecenas erat nisi, laoreet in, porta nec, varius ut, turpis.
		Suspendisse pretium nibh at tellus placerat venenatis. Vestibulum
		ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia
		Curae; Etiam felis arcu, ringilla a, commodo quis, blandit id, est.
		Fusce nec sapien nec libero dignissim volutpat.'		
		));
	$tpl -> display('szablon2.tpl');

?>

Od strony skryptu za wiele nowego nie ma, poza faktem przypisywania do zmiennej szablonowej całej tablicy z danymi. Zwróćmy uwagę na długość oryginalnej wiadomości.

<html>
 <head>
  <title>SmartyNews!</title>
 </head>
 <body>
  <h3>{$wiadomosc.tytul}</h3>
  <p>Napisał {$wiadomosc.autor} dnia {$wiadomosc.data}</p>
  <p>{$wiadomosc.tresc|truncate:200:"..."}</p> 

 </body>
</html>

W tym przykładzie odwołujemy się do poszczególnych wartości w tablicy za pomocą kropki, po której podajemy nazwę indeksu. Smarty oferuje także alternatywną składnię, zbliżoną bardziej do PHP: $wiadomosc[tytul]. W ostatniej zmiennej, pojawiają się jeszcze dodatkowe znaki. Jest to tzw. modyfikator i służy, jak nazwa wskazuje, do końcowej obróbki danych, najczęściej związanej bezpośrednio z procesem wyświetlania. Tutaj nakazujemy przyciąć długość wiadomości do 200 znaków z zaokrągleniem do pełnych słów. Jeżeli wiadomość faktycznie była dłuższa, niż nakazujemy, na jej końcu mają być wstawione trzy kropki.

Sekcje

edytuj

W systemie Smarty sekcja jest jednym z rodzajów pętli. Najczęściej wykorzystuje się ją do tworzenia wszelkiego rodzaju list. Pokażemy teraz, jak wykorzystać omawianą bibliotekę do wygenerowania listy newsów, a następnie jak połączyć system szablonów oraz bazy danych, w których wykorzystujemy relację jeden do wielu.

Zacznijmy od listy. Tak wygląda szablon HTML listy newsów:

<html>
 <head>
  <title>SmartyNews!</title>
 </head>
 <body>
  {section name=i loop=$newsy}
  <h3>{$newsy[i].tytul}</h3>
  <p>Dnia {$newsy[i].data}</p>
  <p>{$newsy[i].tresc|truncate:200:"..."}</p> 
  {/section}
 </body>
</html>

Zwróćmy uwagę na dwa znaczniki: otwierający {section name=i loop=$newsy} oraz zamykający {/section}. Cały kod HTML między nimi jest powtarzany w kółko i służy za szablon pojedynczego newsa. W znaczniku otwierającym sekcję podajemy dwa parametry. Pierwszy z nich, name, określa nazwę sekcji. Identycznie będzie też nazywać się jej iterator wskazujący, na którym elemencie aktualnie jesteśmy. Drugi parametr to loop. Podajemy w nim nazwę zmiennej, w której mieści się tablica z danymi (możemy też podać cyfrę, np. loop=10 będzie oznaczało, że pętla wykona się 10 razy). Aby odwołać się np. do tytułu newsa w obrębie sekcji, stosujemy składnię {$newsy[i].tytul}, czyli najpierw nazwa zmiennej, w nawiasie kwadratowym podajemy iterator i po kropce dopiero nazwę zmiennej wewnętrznej, której wartość jest unikalna dla każdego elementu listy.

Tablica z danymi musi być w odpowiedni sposób wygenerowana po stronie PHP. Zobaczymy teraz skrypt, który się tym zajmuje.

<?php

	require('./smarty/Smarty.class.php');
	
	$tpl = new Smarty;
	$tpl -> template_dir = './templates/';
	$tpl -> compile_dir = './templates_c/';

	$newsy = array();

	for($i = 0; $i < 5; $i++)
	{
		$newsy[] = array(
				'tytul' => 'Tytuł wiadomości',
				'data' => date('d.m.Y'),
				'tresc' => 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
				Cras nec diam. In hac habitasse platea dictumst. Donec id leo. Ut
				feugiat augue at metus. In hac habitasse platea dictumst. Donec
				pulvinar sollicitudin tellus. Quisque mattis faucibus nulla. Praesent
				in mauris. Maecenas erat nisi, laoreet in, porta nec, varius ut, turpis.
				Suspendisse pretium nibh at tellus placerat venenatis. Vestibulum
				ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia
				Curae; Etiam felis arcu, ringilla a, commodo quis, blandit id, est.
				Fusce nec sapien nec libero dignissim volutpat.'	
			);
	}

	$tpl -> assign('newsy', $newsy);
	$tpl -> display('szablon3.tpl');

?>

Widzimy tutaj, że tablica $newsy zawiera mniejsze tablice asocjacyjne przypisujące konkretnym zmiennym wewnętrznym odpowiednie wartości. Następnie przekazujemy ją do parsera metodą assign(). Jako ćwiczenie spróbuj utworzyć taką tablicę, pobierając jej zawartość z bazy danych.

Sekcje można zagnieżdżać, dzięki czemu możliwe jest wyświetlanie danych w określonym porządku, np. kategorii oraz przypisanych do każdej z nich produktów. Wróćmy się do naszej bazy danych biblioteki z rozdziału o PHP Data Objects. Spróbujemy wyświetlić jej zawartość na ekranie, korzystając z pakietu Smarty. Jest to nieco trudniejsze, niż w wypadku zwykłych, płaskich list, ale również wykonalne. Rozpocznijmy od szablonu:

<html>
 <head>
  <title>Biblioteka</title>
 </head>
 <body>
  <ul>
  {section name=i loop=$kategorie}
   <li>{$kategorie[i].nazwa} <ul>
     {section name=j loop=$ksiazki[i]}
     <li>{$ksiazki[i][j].nazwa}</li>
     {/section}
   </ul></li>
  {/section}
  </ul>
 </body>
</html>

Zwróć uwagę, że w podrzędnej sekcji dotyczącej książek, musimy "podpiąć się" pod sekcję nadrzędną, aby Smarty wiedział, w jaki sposób powiązane są ich elementy. Odwołując się do rekordów książek, musimy podać w nawiasach kwadratowych najpierw iterator kategorii, a później książek.

Od strony PHP musimy przygotować dwie listy: po jednej dla kategorii i książek, z tym że druga musi uwzględniać istnienie dwóch indeksów identyfikujących rekordy. Przypomnij sobie, w jaki sposób wiązaliśmy te elementy w rozdziale poświęconym PDO - wykorzystaliśmy tam po prostu ID kategorii jako element wiążący, jednak w tym przypadku jest to niemożliwe. Gdybyśmy bowiem skasowali jakiś rekord, powstałaby dziura w numeracji, w którą sekcja z pewnością by się zaplątała, wyświetlając w tym miejscu pusty rekord. Musimy zatem użyć innej techniki. Zaproponujemy teraz nieco inne rozwiązanie tego problemu. Dane dla kategorii będziemy pobierać tak, jak poprzednio, natomiast dane książek posortujemy w pierwszej kolejności według nazw kategorii, a dopiero później według ich własnych tytułów. Zauważmy, że dzięki temu mamy zagwarantowane, że książki znajdujące się w tej samej kategorii znajdą się na liście wyników obok siebie. Teraz wystarczy podczas pobierania utrzymywać dwie zmienne: $i jako iterator oraz $kid - ID ostatniej znanej kategorii. Zanim wprowadzimy dane rekordu do tablicy, sprawdzamy prosty warunek: jeżeli $kid jest różne od identyfikatora kategorii, do której należy dana książka, to znaczy, że przeszliśmy już do książek z następnej kategorii i musimy w tym celu zwiększyć $i o 1, tak aby dane te trafiły do kolejnego rekordu. Dodatkowo zapamiętujemy ID nowej kategorii. Oto ilustracja tego algorytmu w kodzie PHP:

<?php

	require('./smarty/Smarty.class.php');
	
	$tpl = new Smarty;
	$tpl -> template_dir = './templates/';
	$tpl -> compile_dir = './templates_c/';

	$pdo = new PDO('mysql:host=localhost;dbname=podrecznik_php', 'root', 'root');

	$kategorie = array();
	$ksiazki = array();

	$stmt = $pdo -> query('SELECT id, nazwa, il_ksiazek FROM kategorie ORDER BY nazwa');
	while($row = $stmt -> fetch())
	{
		$kategorie[] = $row;
	}
	$stmt -> closeCursor();
	unset($stmt);

	$stmt = $pdo -> query('SELECT x.id, x.nazwa, k.id AS `kategoria_id` FROM ksiazki x, kategorie k
		WHERE k.id = x.kategoria_id ORDER BY k.nazwa, x.nazwa');
	$i = -1;
	$kid = 0;
	while($row = $stmt -> fetch())
	{
		if($row['kategoria_id'] != $kid)
		{
			$i++;
			$kid = $row['kategoria_id'];
		}

		$ksiazki[$i][] = $row;
	}
	$stmt -> closeCursor();

	$tpl -> assign('kategorie', $kategorie);
	$tpl -> assign('ksiazki', $ksiazki);
	$tpl -> display('szablon4.tpl');

?>

Sekcje w systemie Smarty mają duże możliwości i poznanie ich wszystkich wykracza poza ramy tego podręcznika. Więcej przykładów ich wykorzystania można znaleźć w dokumentacji biblioteki.

Instrukcje warunkowe

edytuj

Smarty posiada także instrukcję warunkową if, dzięki której można testować różne warunki i wyświetlać fragmenty kodu HTML warunkowo. Wróćmy do naszej listy newsów. Dodamy do każdego jej elementu nową zmienną: news_dnia. Jeżeli będzie ona ustawiona na 1, oznacza to, że mamy do czynienia z newsem dnia i wypadałoby to jakoś specjalnie zaznaczyć. Aby nie komplikować kodu, przyjmiemy w przykładzie, że newsem dnia jest pierwszy z elementów:

<?php

	require('./smarty/Smarty.class.php');
	
	$tpl = new Smarty;
	$tpl -> template_dir = './templates/';
	$tpl -> compile_dir = './templates_c/';

	$newsy = array();

	for($i = 0; $i < 5; $i++)
	{
		$newsy[] = array(
				'tytul' => 'Tytuł wiadomości',
				'data' => date('d.m.Y'),
				'tresc' => 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
				Cras nec diam. In hac habitasse platea dictumst. Donec id leo. Ut
				feugiat augue at metus. In hac habitasse platea dictumst. Donec
				pulvinar sollicitudin tellus. Quisque mattis faucibus nulla. Praesent
				in mauris. Maecenas erat nisi, laoreet in, porta nec, varius ut, turpis.
				Suspendisse pretium nibh at tellus placerat venenatis. Vestibulum
				ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia
				Curae; Etiam felis arcu, ringilla a, commodo quis, blandit id, est.
				Fusce nec sapien nec libero dignissim volutpat.',
				'news_dnia' => ($i == 0 ? 1 : 0)
			);
	}

	$tpl -> assign('newsy', $newsy);
	$tpl -> display('szablon5.tpl');

?>

I czas na szablon:

<html>
 <head>
  <title>SmartyNews!</title>
 </head>
 <body>
  {section name=i loop=$newsy}
  <h3>{$newsy[i].tytul}</h3>
  {if $newsy[i].news_dnia eq 1}
   <p><strong>News dnia!</strong></p>
  {/if}
  <p>Dnia {$newsy[i].data}</p>
  <p>{$newsy[i].tresc|truncate:200:"..."}</p> 
  {/section}
 </body>
</html>

Instrukcję warunkową tworzą znaczniki {if warunek} ... {/if}. Za warunek uznawane jest dowolne poprawne wyrażenie - jeżeli idzie Ci układanie takowych po stronie PHP, również w Smartym nie będziesz mieć z nimi problemów. Jedynie trzeba przyzwyczaić się do nowych operatorów. Biblioteka preferuje użycie tekstowych zamienników, aczkolwiek symboliczny zapis z PHP także jest dozwolony. Oto lista najważniejszych operatorów:

Smarty PHP
eq ==
neq, ne !=
gt >
lt <
ge, gte >=
le, lte <=
not !

Dostęp do zmiennych sesyjnych

edytuj

Z poziomu Smarty możemy mieć dostęp do zmiennych sesyjnych, które ustawiliśmy sobie z poziomu skryptów PHP. Dostęp do tych zmiennych odbywa się przez odwołanie do tablicy $smarty.session, np. $smarty.session.login_name.

Jak można to praktycznie wykorzystać, to już zupełnie inna sprawa. Przykładem może być tu proces logowania i nadawania uprawnień, tj. podczas logowania tworzymy w zmiennej sesyjnej obraz uprawnień nadanych użytkownikowi, a potem już z poziomu szablonu, bez konieczności przekazywania za każdym razem przez metodę assign(), możemy sterować tym, co pokazujemy użytkownikowi:

<?php
	// to jest power-user ustawiamy to
	$_SESSION['prawa_zapisu'] = 1;
?>

a potem w szablonie

{if $smarty.session.prawa_zapisu}
kod tylko dla power-usera
{/if}

W analogiczny sposób można odwoływać się do innych rodzajów danych wejściowych. Wartości pól formularza zapisane są w $smarty.post, z adresu URL w $smarty.get. Istnieje też możliwość dostania się do ciasteczek: $smarty.cookie. Znowu jednak sprawy bezpieczeństwa ... pod żadnym pozorem nie wolno na produkcyjnych, publicznych serwerach używać odwołań do typu $smarty.post czy $smarty.get ponieważ to zaproszenie do ataków XSS.

Zakończenie

edytuj

Biblioteka Smarty ma dużo większe możliwości, niż są w stanie pomieścić założenia tego podręcznika. W sieci znaleźć można dużo artykułów na temat tego systemu szablonów, także w języku polskim. Oprócz przetwarzania szablonów, Smarty posiada również zaawansowany moduł cache'owania wyników, szczególnie przydatny na stronach z dużym ruchem.

Przejdziemy teraz do omówienia alternatywnej biblioteki Open Power Template.