6. Rozpoczęcie prac nad skryptem
Rozpoczniemy od napisania pliku index.php, który będzie inicjował obie biblioteki oraz ładował odpowiednią akcję na podstawie danych z adresu URL. Pierwszą rzeczą będzie skonfigurowanie autoloaderów oraz ścieżek dostępu:
<?php // Inicjuj include_path i strefe czasowa set_include_path(get_include_path().PATH_SEPARATOR.'./models'. PATH_SEPARATOR.'./models/generated'.PATH_SEPARATOR.'./libs'); date_default_timezone_set('Europe/Warsaw'); try { // Inicjuj autoloader OPT require('./libs/Opl/Base.php'); Opl_Loader::setDirectory('./libs/'); Opl_Loader::register(); // Inicjuj autoloader Doctrine require('./libs/Doctrine.php'); spl_autoload_register(array('Doctrine', 'autoload'));
Na początku odpowiednio ustawiamy include_path, podając ścieżkę do katalogu z bibliotekami oraz modeli Doctrine. Następnie rozpoczynamy blok try, aby wyłapać ewentualne wyjątki i wczytujemy autoloader OPT. Tutaj kilka słów wyjaśnienia odnośnie tej biblioteki. OPT w rzeczywistości jest pierwszą z serii bibliotek określanych wspólną nazwą OPL. Aby zapewnić ich właściwą współpracę i nie duplikować tej samej funkcjonalności, wszystkie one będą korzystać ze wspólnego jądra zlokalizowanego właśnie w katalogu /libs/Opl – dostarcza ono autoloadera, systemu obsługi błędów, konfiguracji oraz mechanizmu wtyczek. Sam autoloader nie korzysta z include_path, ale za to daje użytkownikowi większą elastyczność w doborze lokalizacji poszczególnych bibliotek. My skorzystaliśmy z metody Opl_Loader::setDirectory(), aby poinformować, że wszystkie znajdują się w tym samym miejscu. Następnie metodą Opl_Loader::register() instalujemy autoloader w interpreterze. W drugiej kolejności, identycznie jak w interfejsie linii komend, inicjujemy autoloader Doctrine.
// Wlacz rozszerzone komunikaty o bledach do celow diagnostycznych Opl_Registry::setState('opl_extended_errors', true); // Wczytaj konfiguracje require('./config.php');
Przy przenoszeniu aplikacji na serwer produkcyjny opcję w drugiej linijce należy wyłączyć, gdyż w przeciwnym razie każdy wyjątek będzie powodował dodatkowo wyświetlanie informacji, które mogą zagrozić bezpieczeństwu witryny lub serwera.
Doctrine inicjujemy w sposób analogiczny, co w pliku interfejsu linii komend:
// Inicjuj Doctrine $manager = Doctrine_Manager::getInstance(); $manager->setAttribute(Doctrine::ATTR_MODEL_LOADING, Doctrine::MODEL_LOADING_CONSERVATIVE); Doctrine::loadModels('./models/'); $conn = Doctrine_Manager::connection($config['database']['dsn']); $conn->setCharset($config['database']['charset']); foreach($config['database']['attributes'] as $attributeName => $attributeValue) { $conn->setAttribute($attributeName, $attributeValue); } $conn->setAttribute(Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true); $conn->setAttribute(Doctrine::ATTR_QUOTE_IDENTIFIER, true);
Następną rzeczą jest skonfigurowanie OPT do pracy. Polega ono na utworzeniu obiektu Opt_Class i umieszczeniu w nim konfiguracji z pliku config.php:
// Inicjuj OPT $tpl = new Opt_Class; $tpl->loadConfig($config['opt']); // Zaladuj dodatkowe rzeczy require('./libs/stuff.php'); $tpl->setup();
Dodatkowo, w pliku /libs/stuff.php znajduje się kod instalacji dodatkowych komponentów, które będziemy wykorzystywać w szablonach. Wrócimy do niego w późniejszej części artykułu. Po skonfigurowaniu, należy wywołać metodę Opt_Class::setup().
Open Power Template zorganizowany jest w sposób podobny do frameworków. Nie mamy tutaj jednej klasy „do wszystkiego”, lecz szereg mniejszych i bardziej wyspecjalizowanych, lepiej odwzorowujących rzeczywistość. Podstawą pracy są tzw. widoki, czyli obiekty klasy Opt_View. Można na nie patrzeć jako na dane ze skryptu wraz z towarzyszącym im szablonem. Jeden skrypt może zarządzać wieloma widokami jednocześnie, bez konieczności konfigurowania każdego z nich z osobna oraz obaw o kolizje nazw zmiennych. Aby przetworzyć widok, potrzebny jest nam z kolei system wyjścia, czyli obiekt dowolnej klasy implementującej Opt_Output_Interface. My skorzystamy z Opt_Output_Http, który powstały z szablonu kod wynikowy wysyła bezpośrednio do przeglądarki oraz pozwala na zarządzanie nagłówkami HTTP. Nasz skrypt będzie odbierać widok z akcji i wyświetlać go przy pomocy tego systemu wyjścia:
$action = 'list'; if(isset($_GET['action']) && ctype_alpha($_GET['action'])) { $action = $_GET['action']; } require('./actions/'.$action.'.php'); $view = action(); $output = new Opt_Output_Http; $output->setContentType(Opt_Output_Http::XHTML, 'utf-8'); $output->render($view);
Jak widać, biblioteka potrafi też całkiem nieźle zarządzać nagłówkiem Content-type. W naszym przypadku wysłana zostanie informacja o dokumencie XHTML tylko wtedy, gdy przeglądarka go obsługuje. W przeciwnym wypadku otrzyma ona zwyczajny nagłówek text/html.
Musimy jeszcze zadbać o prawidłową obsługę wyjątków. OPL dostarcza gotowy interfejs, wzbogacający wyjątek o szereg informacji pomocnych w odnalezieniu przyczyny błędu. Z Doctrine niestety tak fajnie nie ma, dlatego aby się nie przemęczać, opakujemy przechwycone stamtąd wyjątki w jedną z klas OPL-a i wyświetlimy je także przy pomocy tego samego systemu:
} catch(Opt_Exception $e) { $handler = new Opt_ErrorHandler; $handler->display($e); } catch(Doctrine_Exception $e) { $wrapper = new Opl_Exception($e->getMessage()); $handler = new Opl_ErrorHandler; $handler->display($wrapper); }
Aby zakończyć tę część, musimy jeszcze dopisać do pliku config.php konfigurację OPT:
$config = array( 'database' => array( 'dsn' => 'mysql://user:password@host/dbname', 'charset' => 'utf8', 'attributes' => array( 'use_native_enum' => true ) ), 'opt' => array( 'sourceDir' => './templates/', 'compileDir' => './templates_c/', 'stripWhitespaces' => false, 'charset' => 'utf-8' ) );
Dwie pierwsze dyrektywy, sourceDir oraz compileDir są niezbędne do prawidłowej pracy, gdyż wskazują one na położenie katalogów z szablonami. StripWhitespaces, jeśli jest włączona, usuwa z kodu HTML zbędne białe znaki, jednocześnie utrudniając odczytanie kodu wynikowego osobom postronnym. Na czas tworzenia skryptu warto ją jednak wyłączyć, byśmy mogli w razie potrzeby sprawdzić, jak wygląda generowany kod. Ostatnia z dyrektyw to ustawienie kodowania dla celów nagłówków HTTP.
Przejdźmy teraz do stworzenia szkieletowego szablonu HTML, definiującego ogólną strukturę kodu wynikowego i określającą miejsce wyświetlania właściwej treści. Nasz plik zapiszemy pod nazwą /templates/layout.tpl a jego treść będzie następująca:
<?xml version="1.0" ?> <opt:root include="snippets.tpl"> <opt:prolog version="1.0" /> <opt:dtd template="xhtml10transitional" /> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pl" lang="pl"> <head> <title>{$title} - galeria zdjęć</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel="stylesheet" href="style.css" type="text/css" /> </head> <body> <div id="header"> <h1>Galeria zdjęć</h1> <h2>{$title}</h2> </div> <div id="content"> <opt:insert snippet="content"> <p>Brak treści</p> </opt:insert> </div> <div id="footer"> <p>Galeria stworzona na użytek artykułu <em>Piszemy galerię zdjęć w Doctrine i OPT</em> autorstwa Tomasza Jędrzejewskiego.</p> <p>Kod źródłowy dostępny na zasadach licencji X11</p> </div> </body> </html> </opt:root>
OPT jest de facto parserem XML i w domyślnym trybie musimy zachować maksymalną zgodność z regułami tego języka. Każdy szablon musi posiadać XML-owy prolog, który jednak nie będzie wyświetlany w przeglądarce. Potrzebujemy również głównego znacznika i jego rolę pełni tutaj opt:root. Przy okazji pozwala on ustawić dodatkowe atrybuty szablonu lub, jak w naszym przypadku, załadować inny plik, snippets.tpl. Jego przeznaczenie poznamy za chwilę.
Ogólnie, język szablonów OPT składa się z dwóch elementów:
Instrukcje – mają postać znaczników i atrybutów w przestrzeni nazw
opt. Sterują one wyświetlaniem szablonu i umożliwiają wykonywanie różnych manipulacji. Jedna instrukcja może składać się z kilku znaczników i atrybutów.Wyrażenia, np.
$a+$b– pełnią identyczną rolę, jak w PHP i buduje się je według takich samych zasad. W stosunku do PHP zmienione zostały niektóre elementy składni, aby ułatwić ich stosowanie w obrębie dokumentu XML. Wyrażeń można używać jako wartości atrybutów instrukcji/innych znaczników lub osadzać w statycznym tekście przy pomocy klamerek – ich wartość zostanie wtedy wyświetlona we wskazanym miejscu.
Dwie pierwsze instrukcje, jakie coś wyświetlają, to opt:prolog oraz opt:dtd, generujące prolog oraz DTD dokumentu wynikowego, ale już dla przeglądarki. Opt:dtd pozwala dodatkowo na proste określenie rodzaju dokumentu, bez konieczności pamiętania całego, skomplikowanego ciągu. Dalej mamy dynamicznie generowany tytuł, ładowany ze zmiennej $title. W miejscu treści znajduje się natomiast instrukcja opt:insert.
OPT, jak każdy system szablonów, umożliwia składanie dokumentu wynikowego z kilku plików tak, abyśmy nie musieli przepisywać 200 razy nagłówka i stopki strony. Jednak w przeciwieństwie np. do Smarty'ego, musimy trzymać się reguł XML-a i nie wolno nam umieścić początku znacznika <HTML> czy <BODY> w jednym pliku, a zamknięcia w innym, dlatego konieczne jest patrzenie na szablony jak na pojemniki, w których możemy osadzać inne pojemniki. Jedną z dwóch głównych technik modularyzacji jest tzw. dziedziczenie szablonów, które przywędrowało z rozwiązań stosowanych w systemach dla języka Python. Nazwa pochodzi stąd, iż jest ono dość podobne do dziedziczenia stosowanego w programowaniu obiektowym, o czym zaraz się przekonamy.
Rolę klasy pełni tutaj pojedynczy szablon, zaś rolę metod – tzw. fragmenty (ang. snippets). Są to kawałki kodu XML-owego, którym została nadana unikalna nazwa. Gdy jeden szablon rozszerza inny, może nadpisać zdefiniowane przez tamten fragmenty oraz dołożyć nowe. Podobnie jak w przeciążonej metodzie, tak i tutaj przeciążony fragment może wciąż odwołać się do wersji bazowej. Jedyną różnicę między programowaniem obiektowym, a dziedziczeniem szablonów stanowi pierwszy szablon, z którego dziedziczymy. Zamiast fragmentów, umieszczamy w nim zwykłą treść i definiujemy miejsca, w których należy konkretne fragmenty szablonów dziedziczących wyświetlić. Na takiej zasadzie działa nasz layout.tpl – jest to szablon bazowy dla dziedziczenia i poprzez instrukcję opt:insert mówi, że fragment content ma się wyświetlić w obrębie znacznika <div id="content">. Dodatkowo, możemy zdefiniować zawsze treść domyślną, która zostanie wyświetlona, gdyby żaden szablon nie definiował fragmentu o takiej nazwie.