Wprowadzenie do OPT cz. 1

Autor: Tomasz Jędrzejewski
Data publikacji: 14.11.2006, 13:26 | Ostatnia modyfikacja: 14.11.2006, 13:26

Pierwsza część artykuÅ‚u wprowadzajÄ…cego czytelnika w Å›wiat polskiego systemu szablonów Open Power Template dla PHP5.



Idea separacji mechanizmów prezentacji i przetwarzania danych powraca wÅ›ród programistów PHP, jak bumerang. Nikt nie wÄ…tpi, że powinny być one oddzielone od siebie: programista pracuje nad swojÄ… częściÄ…, webmaster tworzy kod HTML i nikt nikomu nie wchodzi w drogÄ™, gdyż wszystko jest w osobnych plikach. Dyskusje powstajÄ… w momencie, kiedy zaczyna siÄ™ mówić o formie i wykonaniu. JednÄ… z koncepcji, przez jednych wychwalanÄ…, przez innych krytykowanÄ…, sÄ… systemy szablonów. SÄ… to specjalne biblioteki, które potrafiÄ… przetwarzać pliki HTML ze specjalnymi znacznikami mówiÄ…cymi, gdzie majÄ… siÄ™ pokazywać dane generowane przez skrypt.

Niekwestionowanym królem systemów szablonów, jeÅ›li chodzi o popularność, jest Smarty™. Rozwijany jest on od bardzo dawna, co z punktu widzenia stabilnoÅ›ci jest korzystne, lecz ma przeÅ‚ożenie na możliwoÅ›ci. Wielu programistów krytykuje go za zbytnie zbliżanie siÄ™ do jÄ™zyka programowania, "nieżyciowe" nazewnictwo metod w formie "nazwa_nazwa()", zamiast bardziej "eleganckiego" camel style, czy nawet ograniczenia samej architektury. Smarty przez dÅ‚ugi czas miaÅ‚ też problemy z dziaÅ‚aniem na PHP 5.

ArtykuÅ‚ ten koncentruje siÄ™ jednak na innym systemie szablonów, stworzonej w Polsce bibliotece Open Power Template. Na pierwszy rzut oka może siÄ™ ona wydawać klonem Smarty'ego, lecz bliższe spotkanie ujawnia mnóstwo różnic, zarówno w skÅ‚adni, możliwoÅ›ciach, jak i architekturze. Oto krótka charakterystyka biblioteki:

Open Power Template jest częściÄ… wiÄ™kszego projektu obejmujÄ…cego zarówno aplikacjÄ™ forum dyskusyjnego Open Power Board, jak i zestaw pokrewnych bibliotek (Open Power Driver - nakÅ‚adka na PHP Data Objects, Open Power Forms - dodatek do OPT koncentrujÄ…cy siÄ™ na zarzÄ…dzaniu formularzami).

Instalacja

Instalacja OPT jest bardzo prosta. Na poczÄ…tku wchodzimy na witrynÄ™ WWW projektu: opt.openpb.net i pobieramy stamtÄ…d najnowszÄ… wersjÄ™ (kolejne wydania ukazujÄ… siÄ™ na poczÄ…tku każdego miesiÄ…ca). W Å›ciÄ…gniÄ™tym archiwum znajduje siÄ™ katalog "lib", którego zawartość musimy skopiować w drzewo katalogowe naszego wÅ‚asnego projektu. JeÅ›li zamierzamy korzystać z pluginów, możemy utworzyć gdzieÅ› katalog plugins.

Kolejnym krokiem jest utworzenie dwóch katalogów na szablony: templates bÄ™dzie zawieraÅ‚ wersje źródÅ‚owe szablonów, czyli takie, które utworzysz ty sam, natomiast templates_c posÅ‚uży do przechowywania skompilowanych wersji, generowanych przez OPT. OczywiÅ›cie nazwy katalogów i ich dokÅ‚adna lokalizacja sÄ… caÅ‚kowicie dowolne. W systemach uniksowych musimy pamiÄ™tać o nadaniu skryptowi praw do zapisu dla katalogu templates_c - inaczej powstanÄ… błędy przy próbie zapisu skompilowanych szablonów. To wszystko. PozostaÅ‚o jedynie zainicjowanie skryptu.

Do góry

Pierwszy skrypt

Open Power Template został napisany z wykorzystaniem technik programowania obiektowego, dlatego od jego użytkownika wymagana jest pewna znajomość takich pojęć, jak klasa, metoda, czy obiekt i właśnie nimi będziemy operować w dalszej części tekstu.

Zaczniemy od stworzenia szablonu HTML, który umieÅ›cimy w katalogu templates. Przyjęło siÄ™ nadawanie plikom szablonów rozszerzenia .tpl.

<html>
<head>
  <title>Open Power Template: pierwszy skrypt</title>
</head>
<body>
    <p>Hello world! Dzisiaj jest {$obecna_data}</p>
</body>
</html>

Tak, jak wspominaliÅ›my, system szablonów pobiera z szablonu kod HTML i odnajduje w nim specjalne znaczniki, które nastÄ™pnie zamieniane sÄ… na dane ze skryptu. W powyższym przykÅ‚adzie takim znacznikiem jest napisana pogrubionÄ… czcionkÄ… {$obecna_data}. Klamry ograniczajÄ… zasiÄ™g znacznika. $obecna_data to tzw. blok, reprezentujÄ…cy miejsce, w którym umieszczamy dane ze skryptu. Napiszmy teraz skrypt, który bÄ™dzie nakazywać przetworzenie naszego szablonu:

<?php
    define('OPT_DIR', './lib/');
    require('./lib/opt.class.php'); // 1
 
    try
    { 
        $tpl = new optClass; // 2
        $tpl -> root = './templates/';
        $tpl -> compile = './templates_c/';
        $tpl -> gzipCompression = true;
        $tpl -> httpHeaders(OPT_HTML); // 3
 
        $tpl -> assign('obecna_data', date('d.m.Y, H:i')); // 4
        $tpl -> parse('szablon1.tpl'); // 5
    }
    catch(optException $exception)
    {
        optErrorHandler($exception); // 6
    }
?>

Przyjrzyjmy siÄ™ szczegóÅ‚owo skryptowi:

  1. Na poczÄ…tku musimy dołączyć do skryptu plik opt.class.php, przedtem definiujÄ…c stałą OPT_DIR zawierajÄ…cÄ… Å›cieżkÄ™ do plików biblioteki. Zaleca siÄ™ podawanie jej wÅ‚aÅ›nie w takiej postaci, jak na przykÅ‚adzie, tj. zaczÄ™tej od ./ - dziÄ™ki temu PHP nie bÄ™dzie przeszukiwać kilkunastu domyÅ›lnych Å›cieżek w dyrektywie include_path, co negatywnie odbija siÄ™ na wydajnoÅ›ci.
  2. Tutaj tworzymy obiekt klasy optClass, czyli samego parsera szablonów.
  3. Ta bardzo ciekawa metoda automatycznie wysyÅ‚a nagÅ‚ówki HTTP dla ustalonego typu zawartoÅ›ci. JeÅ›li dodatkowo w konfiguracji ustawimy dyrektywÄ™ charset, wyÅ›le automatycznie informacje o kodowaniu (jest to szczególnie ważne, gdy chcemy stworzyć witrynÄ™ wykorzystujÄ…cÄ… UTF-8).
  4. Pomostem między skryptem, a szablonem, jest metoda assign(). To za jej pomocą powiadamiamy OPT, jakie dane mamy zamiar umieścić w szablonie oraz pod jakimi nazwami będą one dostępne. W tym wypadku zapisujemy pod nazwą obecna_data aktualną datę.
  5. Ostatnim krokiem jest wywołanie naszego szablonu. OPT wczyta go i połączy z danymi, a gotowy kod HTML automatycznie wyśle do przeglądarki internauty.
  6. Ewentualne błędy sÄ… zgÅ‚aszane jako wyjÄ…tki. W tym miejscu musimy je przechwycić. JeÅ›li nie mamy wÅ‚asnego handlera, możemy skorzystać z gotowego optErrorHandler(), który automatycznie sformatuje komunikat.

Gratulacje, wÅ‚aÅ›nie napisaÅ‚eÅ› swój pierwszy skrypt wykorzystujÄ…cy OPT!

Do góry

Listy

Open Power Template to nie tylko prymitywne umieszczanie danych w szablonie. Biblioteka udostÄ™pnia szereg narzÄ™dzi pozwalajÄ…cych na zaawansowanÄ… manipulacjÄ™ informacjami. W praktyce po stronie szablonu mamy do swej dyspozycji caÅ‚kiem niezÅ‚y jÄ™zyk programowania, z obsÅ‚ugÄ… zmiennych, wyrażeÅ„, pÄ™tli oraz instrukcji warunkowych. Jednak należy to traktować bardziej jako ciekawostkÄ™, ponieważ wraz z nim dostajemy również instrukcje pozwalajÄ…ce choćby częściowo o programowaniu zapomnieć, które zautomatyzujÄ… wiele czynnoÅ›ci. JednÄ… z nich sÄ… sekcje. SÅ‚użą one do generowania wszelkiego rodzaju list za pomocÄ… niezwykle prostej skÅ‚adni. Poniższy przykÅ‚ad pokazuje, jak stworzyć listÄ™ albumów muzycznych.

Zaczynamy od szablonu. Stworzenie sekcji polega po prostu na określeniu wyglądu pojedynczego elementu listy - resztą zajmuje się OPT:

<html>
<head>
  <title>Open Power Template: albumy muzyczne</title>
</head>
<body>
    <h3>Moja lista albumów</h3>
    
    <ul>
    {section=album}
    <li>{$album.tytul} - {$album.zespol} ({$album.rok})</li>    
    {/section}
    </ul>
</body>
</html>

Nasz element ograniczony jest znacznikami {section=album} (tu okreÅ›lamy też nazwÄ™ sekcji - musi być ona unikalna, abyÅ›my mogli podpiąć do niej później dane listy) oraz zamykajÄ…cym {/section}. Aby wstawić w jakimÅ› miejscu dane elementu, używamy specjalnego bloku sekcji, np. {$album.tytul}. Jak widać, zbudowany jest on z dwóch części oddzielonych kropkÄ…. Pierwsza to nazwa naszej sekcji, druga to nazwa jednego z bloków niesionych przez dany element.

Przejdźmy teraz do skryptu. Zaczniemy od utworzenia tabelki w bazie danych:

CREATE TABLE `albumy` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`tytul` VARCHAR( 50 ) NOT NULL ,
`zespol` VARCHAR( 50 ) NOT NULL ,
`rok` SMALLINT NOT NULL 
) ENGINE = MYISAM;
 
INSERT INTO `albumy` VALUES (1, 'Nightflight to Venus', 'Boney M.', 1978);
INSERT INTO `albumy` VALUES (2, 'Take The Heat Off Me', 'Boney M.', 1976);
INSERT INTO `albumy` VALUES (3, 'Spirits Having Flown', 'Bee Gees', 1979);
INSERT INTO `albumy` VALUES (4, 'Saturday Night Fever', 'Bee Gees', 1977);

Kolejnym krokiem jest napisanie samego skryptu. Dane dla listy dostarczane sÄ… w postaci tablicy zawierajÄ…cej poszczególne jej elementy. SÄ… one również tablicami zawierajÄ…cymi pojedyncze informacje, np. tytul czy zespol. Tak wiÄ™c takie coÅ› musimy generować.

<?php
    define('OPT_DIR', './lib/');
    require('./lib/opt.class.php');
 
    try
    {    
        $tpl = new optClass;
        $tpl -> root = './templates/';
        $tpl -> compile = './templates_c/';
        $tpl -> gzipCompression = true;
        $tpl -> httpHeaders(OPT_HTML);
        
        $pdo = new PDO('mysql:host=localhost;port=3305;dbname=test',
         'root', 'root'); // 1
        
        $stmt = $pdo -> query('SELECT * FROM albumy ORDER BY rok');
        $result = array(); // 2
        
        while($row = $stmt -> fetch())
        {
            $result[] = array( // 3
                'tytul' => $row['tytul'],
                'zespol' => $row['zespol'],
                'rok' => $row['rok']            
            );
        }
        $stmt -> closeCursor();
        
        $tpl -> assign('album', $result); // 4    
        
        $tpl -> parse('szablon4.tpl'); 
    }
    catch(optException $exception)
    { 
        optErrorHandler($exception); 
    }
?>

Jego działanie jest następujące:

  1. Najpierw łączymy się z MySQL-em za pomocą biblioteki PHP Data Objects.
  2. Przygotowujemy pustÄ… tablicÄ™.
  3. Zapisujemy tablicę z danymi o każdym albumie do naszego zbiornika na elementy: $result jako kolejny jego element.
  4. Korzystamy z metody assign(), aby przypisać wygenerowaną tablicę do naszej sekcji album.

To wszystko. Po uruchomieniu tego skryptu zobaczysz, że OPT wygenerowaÅ‚ nam Å‚adnÄ… listÄ™ albumów. Pójdźmy jednak dalej od strony szablonu, aby przekonać siÄ™ nieco o możliwoÅ›ciach biblioteki. Na poczÄ…tek zastanówmy siÄ™, co bÄ™dzie, jeÅ›li w bazie albumów niczego nie bÄ™dzie. Odpowiedź jest prosta: dostaniemy w kodzie puste znaczniki <ul></ul> i nic wiÄ™cej. Istnieje kilka sposobów na powiadomienie internauty o kÅ‚opocie - co ważniejsze, żaden z nich nie wymaga zmieniania czegokolwiek w kodzie PHP! Możemy sprawdzić instrukcjÄ… warunkowÄ…, czy sekcja zawiera jakieÅ› elementy, a jeÅ›li nie, powiadomić go o tym:

<html>
<head>
  <title>Open Power Template: albumy muzyczne</title>
</head>
<body>
    <h3>Moja lista albumów</h3>
    
    {if count($album) > 0}
    <ul>
    {section=album}
    <li>{$album.tytul} - {$album.zespol} ({$album.rok})</li>
    {/section}
    </ul>
    {else}
    <p>Brak albumów!</p>
    {/if}
</body>
</html>

Jednak w tym przypadku zbliżamy siÄ™ za bardzo do programowania. OPT udostÄ™pnia lepszy oraz nieco szybszy sposób polegajÄ…cy na rozbudowaniu sekcji o dodatkowe znaczniki: {show} oraz {/show}. Jeżeli zdecydujemy siÄ™ na ich zastosowanie, to wÅ‚aÅ›nie w nich ustalamy parametry sekcji, natomiast znacznik {section} pozostaje pusty:

<html>
<head>
  <title>Open Power Template: albumy muzyczne</title>
</head>
<body>
    <h3>Moja lista albumów</h3>
    
    {show=album}
        <ul>
        {section}
        <li>{$album.tytul} - {$album.zespol} ({$album.rok})</li>
        {/section}
        </ul>
    {showelse}
        <p>Brak albumów!</p>
    {/show}
</body>
</html>

Teraz OPT ma jasno okreÅ›lone: to jest otoczenie listy bÄ™dÄ…ce jej integralnÄ… częściÄ…; to jest wyglÄ…d pojedynczego elementu listy; to jest tekst alternatywny, jeÅ›li lista nie posiada elementów.

Sekcja umożliwia wykonywanie prostych manipulacji na danych. Spróbujmy teraz wyÅ›wietlić jÄ… w odwrotnej kolejnoÅ›ci (nadal nie ruszajÄ…c nic w kodzie PHP!) oraz jakoÅ› wyróżnić pierwszy element:

<html>
<head>
  <title>Open Power Template: albumy muzyczne</title>
</head>
<body>
    <h3>Moja lista albumów</h3>
    
    {show=album; reversed}
        <ul>
        {section}
        {if $opt.section.album.first}
        <li><strong>{$album.tytul}</strong> - 
            {$album.zespol} ({$album.rok})</li>
        {else}
        <li>{$album.tytul} - {$album.zespol} ({$album.rok})</li>
        {/if}
        {/section}
        </ul>
    {showelse}
        <p>Brak albumów!</p>
    {/show}
</body>
</html>

JeÅ›li chodzi o odwracanie kolejnoÅ›ci, wystarczy w parametrach sekcji dopisać sÅ‚owo "reversed". Wyróżnienie pierwszego elementu to sprawa nieco bardziej skomplikowana. Musimy skorzystać koniecznie z pomocy instrukcji warunkowej, lecz OPT w tym momencie pomaga automatycznie stworzyć odpowiedni warunek. Blok specjalny $opt daje dostÄ™p do różnych ciekawych danych - w tym wypadku w postaci $opt.section.album.first zwraca on true, jeżeli aktualnie przetwarzany element sekcji album jest jej pierwszym elementem. Możemy z tego skorzystać do zdefiniowania dla niego alternatywnego wyÅ›wietlania.

Do góry

Wielopoziomowe listy

OPT zezwala na zagnieżdżanie sekcji, co umożliwia tworzenie wielopoziomowych list. Od strony szablonu sprawa jest oczywista - umieszczamy jedną sekcję w drugiej i nie martwimy się o resztę. Parę ważnych decyzji czeka nas natomiast od strony kodu PHP. Musimy zdecydować się na to, w jakim formacie dostarczymy dane do sekcji zagnieżdżonych. Mamy dwa wyjścia:

Wyboru najlepiej dokonać przed rozpoczęciem pisania aplikacji, choć warto też dodać, że gdy już się zdecydujemy, wcale nie jesteśmy skazani do samego końca na korzystanie z niego. Zawsze istnieje możliwość skorzystania z drugiego wariantu, lecz wymaga to poczynienia pewnych zmian po stronie szablonu.

Ale przejdźmy do rzeczy. Do demonstracji zagadnienia wykorzystamy tym razem tabele SQL dołączone do przykÅ‚adów OPT. Odpowiedni plik znajduje siÄ™ w katalogu /examples/samples.sql i jest dołączony do każdego wydania biblioteki. Po jego wgraniu pojawiÄ… nam siÄ™ tabelki "products" oraz "categories" połączone prostÄ… relacjÄ… jeden-do-wielu. Poniżej podam teraz szablon z zagnieżdżonymi sekcjami, który umożliwi nam wyÅ›wietlenie naszych dwóch tabelek. NastÄ™pnie zaprezentowane zostanÄ… dwa wykorzystujÄ…ce go skrypty demonstrujÄ…ce użycie każdego z formatów danych. OczywiÅ›cie nie bÄ™dziemy w nich wykonywać zapytaÅ„ rekurencyjnych; i tak nam nie pozwoli na to biblioteka PDO użyta do komunikacji z bazÄ…. Z tego powodu trzeba jeszcze zadbać o mechanizm pamiÄ™tajÄ…cy, jaki ID kategorii powiÄ…zany jest z jakim indeksem tablicy $kategorie; nie możemy w tym celu wykorzystać tego samego ID, ponieważ OPT wymaga ciÄ…gÅ‚ej numeracji zaczynajÄ…cej siÄ™ od 0, podczas gdy identyfikatory bazy danych nie speÅ‚niajÄ… ani jednego, ani drugiego warunku.

<html>
<head>
  <title>Open Power Template: lista produktów</title>
</head>
<body>
    <h3>Moja lista produktów</h3>
    
    <ul>
    {section=kategoria}
    <li><i>{$kategoria.nazwa}</i><br/>
    <table width="60%" border="1">
     <tr>
      <td width="30"><b>#</b></td>
      <td width="20%"><b>Nazwa</b></td>
      <td width="*"><b>Opis</b></td> 
     </tr>
     {section=produkt}
     <tr>
      <td width="30">{$produkt.id}</td>
      <td width="20%">{$produkt.nazwa}</td>
      <td width="*">{$produkt.opis}</td>
     </tr>
     {/section}
    </table>
    </li>
    {/section}
    </ul>
</body>
</html>

Tak jak mówiÅ‚em, szablon nie jest szczytem wyrafinowania. Przyjrzyjmy siÄ™ zatem pierwszemu skryptowi, który potrafi generować dane na jego użytek.

<?php
    define('OPT_DIR', './lib/');
    require('./lib/opt.class.php');
 
    try
    {    
        $tpl = new optClass;
        $tpl -> root = './templates/';
        $tpl -> compile = './templates_c/';
        $tpl -> gzipCompression = true;
        $tpl -> httpHeaders(OPT_HTML);
        
        $pdo = new PDO('mysql:host=localhost;port=3305;dbname=test',
             'root', 'root');
 
        $stmt = $pdo -> query('SELECT id, name FROM
            categories ORDER BY id');
        $categories = array();
        $categoryMatch = array();
        $i = 0;
        while($row = $stmt -> fetch())
        {
            $categories[$i] = array(
                'id' => $row['id'],
                'nazwa' => $row['name']            
            );
            $categoryMatch[$row['id']] = $i; // 1
            $i++;        
        }
        $stmt -> closeCursor();
        unset($stmt);
 
        $stmt = $pdo -> query('SELECT id, name, description,
            category FROM products ORDER BY category, id');
        $products = array();
        
        while($row = $stmt -> fetch())
        {
            $products[$categoryMatch[$row['category']]][] = array( // 2
                'id' => $row['id'],
                'nazwa' => $row['name'],
                'opis' => $row['description']
            );
        }
        $stmt -> closeCursor();
        
        $tpl -> assign('kategoria', $categories); // 3
        $tpl -> assign('produkt', $products);
        
        $tpl -> parse('szablon5.tpl'); 
    }
    catch(optException $exception)
    { 
        optErrorHandler($exception); 
    }
?>

Oto opis jego działania:

  1. $categoryMatch jest tablicÄ… pamiÄ™tajÄ…cÄ…, jaki ID kategorii znajduje siÄ™ w którym elemencie listy. BÄ™dziemy jÄ… potem wykorzystywać, aby połączyć produkty z kategoriami.
  2. Tutaj musimy skonstruować nieco wiÄ™kszÄ… tablicÄ™ produktów uwzglÄ™dniajÄ…cÄ… fakt, iż te sÄ… zagnieżdżone wewnÄ…trz kategorii. Posiada ona dwa indeksy - pierwszy pokazuje, do jakiej kategorii należy produkt (tu wstawiamy wynik z tablicy $categoryMatch), a drugi jest indeksem produktu w obrÄ™bie tej kategorii.
  3. Do szablonu przekazujemy dwie tablice, osobno dla każdej z sekcji.

Jak wspomniaÅ‚em, jest to domyÅ›lny sposób przekazywania danych do sekcji zagnieżdżonych, lecz OPT posiada jeszcze jeden. Należy go rÄ™cznie włączyć, ustawiajÄ…c dyrektywÄ™ sectionStructure na OPT_SECTION_SINGLE. Wtedy zamiast dwóch tablic, bÄ™dziemy generowali tylko jednÄ… - dla sekcji nadrzÄ™dnej. Dane sekcji podrzÄ™dnych zostanÄ… w niej umieszczone jako poszczególne bloki. Od strony szablonu nic siÄ™ nie zmienia. Od strony PHP, musimy zmienić trzy linijki powyższego skryptu. Na poczÄ…tek dodaj w części inicjacyjnej informacjÄ™, że używamy innej struktury sekcji:

$tpl -> sectionStructure = OPT_SECTION_SINGLE;

NastÄ™pnie w pobieraniu produktów zamieÅ„ linijkÄ™:

$products[$categoryMatch[$row['category']]][] = array(

na:

$categories[$categoryMatch[$row['category']]]['produkt'][] = array(

Zauważ, że teraz w obrębie kategorii tworzymy blok "produkt", lecz zamiast tekstu czy liczby, umieszczamy w nim dane dla sekcji produkt powiązane z aktualnie przetwarzaną kategorią.

Ponieważ nie tworzymy już tabeli $products, poniższa linijka jest nam zbędna i należy ją usunąć:

$tpl -> assign('produkt', $products);

UsuÅ„ z /templates_c skompilowanÄ… wersjÄ™ dotychczasowego szablonu i odpal zmodyfikowany skrypt. To wszystko, wÅ‚aÅ›nie poznaÅ‚eÅ› dwie metody tworzenia zagnieżdżonych sekcji. PamiÄ™taj, że powinieneÅ› na poczÄ…tku prac zdecydować, którego z nich bÄ™dziesz używać, jednak jeżeli nagle przyjdzie Ci użyć drugiego, nie panikuj - wprawdzie wymaga to pewnych modyfikacji po stronie szablonów, ale jest wykonalne. Sekcje posiadajÄ… dodatkowy parametr, datasource, za pomocÄ… których możesz powiadomić kompilator, gdzie dokÅ‚adnie znajdujÄ… siÄ™ dane dla nich. I tak: jeżeli korzystasz z pierwszego sposobu, a potrzebny Ci jest drugi, powinieneÅ› połączyć sekcjÄ™ produktów z kategoriami w nastÄ™pujÄ…cy sposób:

{section name="produkt" datasource="$kategoria.produkt"}

W przeciwną stronę, musimy zrobić tak:

{section name="produkt" datasource="$produkt"}

OsobnÄ… sprawÄ… jest wyÅ›wietlanie zawartoÅ›ci drzew z użyciem OPT. W wersjach 1.0.x wymaga to sporego nakÅ‚adu pracy i wyposażenia sekcji w caÅ‚y zestaw instrukcji warunkowych, lecz podczÄ…wszy od wersji 1.1.x biblioteka ma już posiadać specjalne instrukcje realizujÄ…ce to zadanie schludnie i elegancko. Pokażę zatem tutaj sposób uzyskania drzewka dla wersji 1.0.x. ZakÅ‚adam, że generujesz je algorytmem "modified preorder tree traversal", który generuje liniowÄ… listÄ™ elementów z dołączonym do nich parametrem depth okreÅ›lajÄ…cym ich głębokość. Zanim przeÅ›lemy takie drzewo do OPT, musimy przepuÅ›cić je przez dodatkowÄ… funkcjÄ™:

<?php
    function prepareTree($tree)
    {
        foreach($tree as $id => &$item)
        {
            if($item['depth'] == @$tree[$id+1]['depth'])
            {
                $item['leaf'] = 1;
            }
            if($item['depth'] < @$tree[$id+1]['depth'])
            {
                $item['opening'] = 1;
            }
            if($item['depth'] > @$tree[$id+1]['depth'])
            {
                $item['closing'] = 1;
                if(isset($tree[$id+1]))
                {
                    $item['toclose'] = ($item['depth']
                        - @$tree[$id+1]['depth']);
                }
            }
        }
        return $tree;
    } // end prepareTree();
?>

Umieści ona w każdym elemencie drzewa następujące bloki:

opening - po aktualnym elemencie należy otworzyć nową listę dla jego dzieci.

Teraz przechodzimy do strony szablonu. Tutaj będziemy musieli pobawić się nieco instrukcjami warunkowymi i pętlami, aby algorytm zadziałał prawidłowo. Oto i on:

<ol>
    {section=tree}
        {if $tree.leaf} {* 1 *}
        <li>{$tree.title}</li>
        {/if}
        {if $tree.opening} {* 2 *}
        <li>{$tree.title}<ol>
        {/if}
        {if $tree.closing} {* 3 *}
        <li>{$tree.title}</a>
            {for=@i is 0; @i < $tree.toclose; @i is @i+1} {* 4 *}
            </li></ol>
            {/for}
        </li>
        {/if}
    {/section}
<ol>

Znaczenie poszczególnych fragmentów:

  1. Wygląd liścia drzewa.
  2. WyglÄ…d elementu otwierajÄ…cego nowy poziom listy.
  3. WyglÄ…d elementu zamykajÄ…cego aktualny poziom listy.
  4. Pętla zamykająca listy do nowego poziomu.

Teraz jesteśmy już w stanie wyświetlać struktury drzewiaste za pomocą Open Power Template.

Do góry

Interfejs wielojęzyczny

Przejdźmy teraz do nowego zagadnienia. Przypuśćmy, że tworzysz duży projekt skierowany nie tylko na rynek polski, ale i klientów zagranicznych. Niestety nasz rodzimy jÄ™zyk jest dopiero na poczÄ…tku realizacji planu zawÅ‚adniÄ™cia Å›wiatem, dlatego lepiej jest zaÅ‚ożyć, że taki Brytyjczyk lub Niemiec nie potrafi siÄ™ nim jeszcze posÅ‚ugiwać. Wynika z tego, iż nasza witryna musi posiadać wielojÄ™zyczny interfejs: wszystkie komunikaty i napisy zmieniajÄ… siÄ™ w zależnoÅ›ci od tego, jakÄ… wersjÄ™ jÄ™zykowÄ… wybraÅ‚ sobie internauta. Do normalnego systemu szablonów musielibyÅ›my przekazywać tony informacji lub wykorzystywać w szablonach bezpoÅ›rednio elementy silnika PHP witryny, aby nieco uÅ‚atwić ten proces. OPT normalnym systemem szablonów jednak nie jest i posiada specjalny mechanizm uÅ‚atwiajÄ…cy tworzenie wielojÄ™zycznych interfejsów. Tradycyjnie, zacznijmy jednak od szablonu:

<html>
<head>
  <title>Open Power Template: i18n</title>
</head>
<body>
<p>{$page1@intro}</p>
<p>{$page1@preview}</p>
<ul>
    <li>{literal}{$global@yes}{/literal}: {$global@yes}</li>
    <li>{literal}{$global@no}{/literal}: {$global@no}</li>
    <li>{literal}{$global@maybe}{/literal}: {$global@maybe}</li>
</ul>
{apply($page1@counter, 1)}
<p>{$page1@counter}</p>
{apply($page1@counter, 2)}
<p>{$page1@counter}</p>
</body>
</html>

W kodzie poumieszczane sÄ… nowe, niespotykane dotÄ…d rodzaje bloków majÄ…ce postać {$grupa@nazwa}. SÄ… to tzw. bloki jÄ™zykowe, które zaznaczajÄ… miejsca, w których majÄ… pojawić siÄ™ jakieÅ› komunikaty interfejsu. OPT, napotykajÄ…c taki blok, odczytuje sobie z niego ID komunikatu oraz grupy, do której należy, po czym samodzielnie pobiera go sobie ze zdefiniowanego wczeÅ›niej systemu i18n. Dodam jeszcze, że użyta w szablonie instrukcja {literal} powoduje wyÅ›wietlanie znajdujÄ…cych siÄ™ wewnÄ…trz niej tagów OPT, zamiast ich przetwarzania. ZastosowaÅ‚em jÄ… tutaj, aby wyÅ›wietlić nazwy wykorzystywanych bloków jÄ™zykowych.

Kolejnym ciekawym elementem jest funkcja apply(). Jej zadanie można wytÅ‚umaczyć nastÄ™pujÄ…co: czasami niektóre dane muszÄ… być umieszczone wewnÄ…trz komunikatu, co ma miejsce np. w takim przypadku: "Na stronie jest 17 użytkowników on-line". Widzimy, że "17" jest wartoÅ›ciÄ… dynamicznÄ…, która musi być zassana z jakiegoÅ› bloku. Mamy dwa wyjÅ›cia: albo rozbijemy komunikat na dwa, np. $stats@users_online1 i $stats@users_online2, a miÄ™dzy nimi umieÅ›cimy blok, lub... zastosujemy funkcjÄ™ apply(), która odnajdzie w tekÅ›cie specjalny znacznik (%s) i wstawi w jego miejsce jakÄ…Å› wartość dynamicznÄ…. WÅ‚aÅ›nie tak robimy powyżej: mamy dynamiczny blok jÄ™zykowy $page@counter, który ma treść "Licznik: %s.". OdpowiedniÄ… liczbÄ™ umieszczamy na miejscu %s wÅ‚aÅ›nie za pomocÄ… funkcji apply().

Aby używać wielojÄ™zycznych interfejsów, musimy w kodzie PHP połączyć OPT z jednym z nich. Biblioteka zezwala na wykorzystanie dwóch rodzajów interfejsu:

  1. Proceduralny - do OPT podpinamy dwuwymiarowÄ… tablicÄ™ asocjacyjnÄ… wiążącÄ… ID komunikatów z ich treÅ›ciÄ….
  2. Obiektowy - do OPT podpinamy obiekt klasy implementujÄ…cej interfejs ioptI18n - klasa ta ma za zadanie udostÄ™pniać żądane komunikaty za pomocÄ… odpowiednich metod. OczywiÅ›cie daje nam to znacznie wiÄ™ksze pole do popisu - możemy wtedy zaprogramować np. automatyczne wczytywanie grup komunikatów, gdy sÄ… używane po raz pierwszy.

Zajmiemy siÄ™ najpierw pierwszym z nich. Skrypt jest tutaj niezwykle prosty: tworzymy sobie tablicÄ™ z wszystkimi tekstami, po czym podpinamy jÄ… do OPT metodÄ… optClass::setDefaultI18n():

<?php
    define('OPT_DIR', './lib/');
    require('./lib/opt.class.php');
    
    $daneJezykowe = array(
        'global' => array(
            'yes' => 'Tak',
            'no' => 'Nie',
            'maybe' => 'Być może'        
        ),
        'page1' => array(
            'intro' => 'Witaj w testowym projekcie opartym
                o Open Power Template!',
            'preview' => 'Podgląd możliwości i18n',
            'counter' => 'Licznik: %s'
        )
    );
 
    try
    { 
        $tpl = new optClass;
        $tpl -> root = './templates/';
        $tpl -> compile = './templates_c/';
        $tpl -> gzipCompression = true;
        $tpl -> httpHeaders(OPT_HTML);
        
        $tpl -> setDefaultI18n($daneJezykowe);
        
        $tpl -> parse('szablon3.tpl'); 
    }
    catch(optException $exception)
    { 
        optErrorHandler($exception); 
    }
?>

Uruchamiamy skrypt i niby wszystko dziaÅ‚a, ale jednak coÅ› jest nie tak. Spójrzmy na nasz licznik. Drugie wywoÅ‚anie funkcji apply() wcale nie zaktualizowaÅ‚o licznika! Powód jest prosty - rezultat jej dziaÅ‚ania nadpisaÅ‚ oryginalny tekst, kasujÄ…c znacznik. W koÅ„cu gdzieÅ› musiaÅ‚ on trafić, a skoro do dyspozycji jest tylko jedna tablica... Jest jednak sposób, aby temu zaradzić, a mianowicie napisać wÅ‚asny, obiektowy interfejs i18n.

<?php
    define('OPT_DIR', './lib/');
    require('./lib/opt.class.php');
    
    class i18n implements ioptI18n
    {
        private $tpl;
        private $replacements = array();
        private $data = array(
            'global' => array(
                'yes' => 'Tak',
                'no' => 'Nie',
                'maybe' => 'Być może'        
            ),
            'page1' => array(
                'intro' => 'Witaj w testowym projekcie opartym
                    o Open Power Template!',
                'preview' => 'Podgląd możliwości i18n',
                'counter' => 'Licznik: %s'
            )
        );
        
        static private $instance;
        
        private function __construct(){ }
        
        public function setOptInstance(optClass $tpl)
        {
            $this -> tpl = $tpl;
        } // end setOptInstance();
        
        static public function getInstance()
        {
            if(!is_object(self::$instance))
            {
                self::$instance = new i18n;
            }
            return self::$instance;
        } // end getInstance();  
  
         public function put($group, $text_id)
         {
            if(isset($this -> replacements[$group][$text_id]))
            {
                return $this -> replacements[$group][$text_id];
            }
            return $this -> data[$group][$text_id];     
        } // end put();
 
        public function apply($group, $text_id)
        {
            $args = func_get_args();
            unset($args[0]);
            unset($args[1]);
            $this -> replacements[$group][$text_id]
                = vsprintf($this -> data[$group][$text_id], $args);
        } // end apply();
        
        public function putApply($group, $text_id)
        {
            $args = func_get_args();
            unset($args[0]);
            unset($args[1]);
            return vsprintf($this -> data[$group][$text_id], $args);
        } // end apply();    
    }
 
    try
    { 
        $tpl = new optClass;
        $tpl -> root = './templates/';
        $tpl -> compile = './templates_c/';
        $tpl -> gzipCompression = true;
        $tpl -> httpHeaders(OPT_HTML);
        
        $tpl -> setObjectI18n(i18n::getInstance());
        
        $tpl -> parse('szablon3.tpl'); 
    }
    catch(optException $exception)
    { 
        optErrorHandler($exception); 
    }
?>

Przyjrzyjmy się dokładniej klasie i18n. Po interfejsie ioptI18n implementuje ona następujące metody:

Podana powyżej implementacja wykorzystuje wzorzec singleton, aby mieć pewność, że po skrypcie nie bÄ™dzie siÄ™ pałętać 10 obiektów interfejsu jÄ™zykowego. Oryginalne komunikaty zapisane sÄ… w tablicy $replacements, a teksty zmodyfikowane przez apply() trafiajÄ… do tablicy $data - dziÄ™ki takiemu podziaÅ‚owi nasza klasa prawidÅ‚owo obsÅ‚uży podany wyżej szablon; oryginalny komunikat źródÅ‚owy nigdy nie zostanie nadpisany.

Do góry

Koniec części 1

PoznaliÅ›my wÅ‚aÅ›nie podstawowe zasady i mechanizmy dostÄ™pne w OPT. Część druga tego artykuÅ‚u jest w caÅ‚oÅ›ci poÅ›wiÄ™cona systemowi komponentów. Nie tylko nauczymy siÄ™ z nich korzystać, ale też napiszemy wÅ‚asny. Zapraszam do lektury części drugiej.

Aktualna wersja artykułu zawsze na stronie WWW autora, www.zyxist.com.

Autor: Tomasz "Zyx" Jędrzejewski, www.zyxist.com

Do góry

Waszym zdaniem:

Nikt jeszcze nie dodał swojego komentarza. Możesz być pierwszy!


Twoim zdaniem:

Reklama

banner

Partnerzy

CityDesign.pl
phpSolutions