PHP: Systemy szablonów

Kod PHP przeplatający się z tagami HTML wygląda bardzo nieczytelnie. Jest wiele metod na odseparowanie kodu PHP od kodu HTML. Jedną z takich metod są systemy szablonów.

Separować kod od treści można na przykład tak:

<?php
function title() {
    echo 'tytuł';
}

function body() {
    table();
}

function table() {
    echo '<table>';
    while (coś) {
        row(dane);
    }
    echo '</table>';
}

function row($dane) {
    echo '<tr>';
    echo '<td>';
    echo $dane;
    echo '</td>';
    echo '</tr>';
}
?>
<html>
 <head>
  <title><?php title() ?></title>
 </head>
 <body>
  <?php body() ?>
 </body>
</html>

Mimo wszystko nie wygląda to najlepiej. Najepszym rozwiązaniem jest system template’ów (wzorców). W systemie takim tworzone są osobne pliki zawierające kod PHP i osobne zawierające kod HTML, zawierające jednak specjalne oznaczenia, gdzie należy wstawić dane przekazane przez PHP. Taki plik może przykładowo wyglądać tak:

index.tpl

<html>
 <head>
  <title>{title}</title>
  <meta name="description" content="{description}"/>
 </head>
 <body>
  <table>
   <tr>
    <th>ID</th>
    <th>Autor</th>
    <th>Tytuł</th>
    <th>Wydawnictwo</th>
   </tr>
   {table}
  </table>
 </body>
</html>

table.tpl

   <tr>
    <td>
     {id}.
    </td>
    <td>
     {autor}
    </td>
    <td>
     {tytul}
    </td>
    <td>
     {wydawnictwo}
    </td>
   </tr>

Na początek trzeba stworzyć klasę, która będzie przetwarzała wzorce. Trzeba się zastanowić jakich metod i pól klasa będzie potrzebowała. Najwygodniej będzie, jeśli konstruktor klasy będzie jako parametr przyjmował nazwę pliku wzorca. Niezbędna będzie metoda dodająca zmienną do podstawienia (a co za tym idzie także pole, w którym zmienne te będą przechowywane) oraz metoda zwracająca przetworzony wzorzec. Dla wygody można także dodać metodę, która od razu będzie wyświetlała wzorzec.

Tak więc na początek pola:

<?php
class Template {
    var $tmpl;
    var $dane;
}
?>

Konstruktor ma za zadanie wczytać plik ze wzorcem.

<?php
function Template ($name)
{
    $this->tmpl = file_get_contents($name)); // Funkcja file_get_contenst dostępna jest
                                            // w php od PHP 4.3.0
    $this->dane = Array();
}
?>

Funkcja dodająca dane do wzorca powinna przyjmować dane w dwóch postaciach: albo dwa parametry – nazwa i wartość, albo jeden parametr – tablica, w której nazwy zmiennych zapisane są jako klucze.

<?php
function add($name, $value = '')
{
    if (is_array($name)) {
        $this->dane = array_merge($this->dane, $name);
    } else if (!empty($value)) {
        $this->dane[$name] = $value;
    }
}
?>

Powyższa metoda sprawdza, czy pierwszy z parametrów jest tablicą. Jeśli tak, zostaje ona dołączona do istniejących danych przy pomocy funkcji array_merge(). Podawanie do metody tablicy jest bardzo wygodne – umożliwia to bezpośrednie przekazanie wiersza odczytanego z bazy danych. Jeśli natomiast podane zostały dwa parametry, pierwszy z nich zostanie użyty jako klucz a drugi jako wartość tablicy z danymi.

Skoro są już dane i jest wzorzec, trzeba to połączyć, czyli stworzyć metodę wstawiającą dane do wzorca. Można to zrobić na wiele sposobów. Seria wywołań funkcji str_replace jest nieefektywna, gdyż dla każdego wywołania wzorzec musi być przeszukany od początku – dużo lepiej jest zrobić to przy pomocy wyrażeń regularnych. Przy pomocy funkcji preg_replace() można podmienić każde napotkane wyrażenie {zmienna} na zawartość tablicy o kluczu podanym w wyrażeniu.

<?php
function execute() {
    return preg_replace('/{([^}]+)}/e', '$this->dane["\1"]', $this->tmpl);
}
?>

Teraz wystarczy posklejać wszystko w całość.

<?php
class Template {
    var $tmpl;
    var $dane;

    function Template ($name)
    {
        $this->tmpl = file_get_contents($name);
        $this->dane = Array();
    }

    function add($name, $value = '')
    {
        if (is_array($name)) {
            $this->dane = array_merge($this->dane, $name);
        } else if (!empty($value)) {
            $this->dane[$name] = $value;
        }
    }

    function execute() {
        return preg_replace('/{([^}]+)}/e', '$this->dane["\1"]',
                $this->tmpl);
    }

}
?>

Teraz może mały przykład jak to wykorzystać. Zakładając, że klasa Template znajduje się w pliku template.inc.php:

test.php

<?php
include 'template.inc.php';

$tmpl = new Template('test.tmpl');
$tmpl->add('title', 'strona testowa');
$tmpl->add('autor', 'Leszek');
$tmpl->add('charset', 'iso-8859-2');
$dane = Array('imie'=> 'Franek', 'podpis'=>'sincerly yours');
$tmpl->add($dane);
echo $tmpl->execute();

?>

test.tmpl

<html>
 <head>
  <title>{title}</title>
  <meta http-equiv="Content-type" content="text/html; charset={charset}" />
 </head>
 <body>
  Cześć, nazywam się {autor}
  To jest indywidualna strona stworzona tylko dla Ciebie, {imie}
  {podpis}
  <p style="text-indent: 30ex">{autor}
 </body>
</html>

System taki można także zagnieżdżać, aby wyświetlać dane z tabeli.

test.php

<?php
include 'template.inc.php';

$res = mysql_query('select * from data');
while($row = mysql_fetch_array($res)) {
    $rows = new Template('rows.tmpl');
    $rows->add($row);
    $table .= $rows->execute();
}


$tmpl = new Template('test.tmpl');
$tmpl->add('title', 'strona testowa');
$tmpl->add('charset', 'iso-8859-2');
$tmpl->add('table', $table);
echo $tmpl->execute();

?>

test.tmpl

<html>
 <head>
  <title>{title}</title>
  <meta http-equiv="Content-type" content="text/html; charset={charset}" />
 </head>
 <body>
  <table>
   {table}
  </table>
 </body>
</html>

rows.tmpl

<tr>
 <td>
  {imie}
 </td>
 <td>
  {nazwisko}
 </td>
 <td>
  {adres}
 </td>
</tr>

Przy użyciu tego systemu wzorców zmiana sposobu wyświetlania danych z bazy danych z tabelarycznego na rekordowy to kwestia usunięcia otwarcia tabeli z głównego wzorca i zmiany wzorca wyświetlającego dane.

Rozwiązanie to jest bardzo proste, ale wystarcza dla wielu celów. Zaawansowane systemy wzorców, takie jak na przykład Smarty, umożliwiają umieszczenie we wzorcach pętli. Dodanie takich opcji wymaga już innej konstrukcji funkcji podstawiającej dane do wzorca oraz samego wzorca. Niezbędne jest określenie bloku, który będzie podlegał pętli, oraz danych – najłatwiej podać je w postaci tablicy. Jak to zrealizować – najepiej podejrzeć jak jest to zrealizowane w innych systemach.

Jeśli potrzebny jest system wzorców oferujący większe możliwości, można skorzystać z gotowych rozwiązań. Jednym z najlepszych pakietów jest Smarty Templates, dostępny pod adresem http://smarty.php.net/

Podstawianie danych do wzorca i wyświetlanie go za każdym żądaniem od klienta jest nieefektywne – operacja podstawiania jest stosunkowo długotrwała a potrzeba ponownego generowania strony zachodzi tylko w dwóch przypadkach: kiedy zmienia się wzorzec albo zmieniają się dane. Dobrym rozwiązaniem jest zastosowanie klas typu Cache – jednej z dostępnych (np. zawartej w repozytorium PEAR – http://pear.php.net) lub napisanie własnej (o tym w osobnym artykule).

Autor: leafnode

Architekt oprogramowania webowego, programista, analityk bezpieczeństwa serwisów internetowych, speaker, konsultant. Potrzebujesz pomocy? Skontaktuj się ze mną!

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *