2. Więcej o formatach danych

W całej tej zabawie znacznikami XML nie umieściliśmy żadnych technicznych detali dotyczących działania listy i jej współpracy z naszym skryptem, który jest ukryty gdzieś za szablonami. Dlatego zobaczmy teraz, jak taką sekcję napompować danymi. Tutaj zapoznamy się z koncepcją formatów danych. Formaty danych mają wiele podobieństw do typów danych stosowanych w językach programowania i mówią nam, czym dana rzecz jest w rzeczywistości. Różnica polega na tym, że formaty danych nie dotyczą jedynie zmiennych, ale w teorii każdego „dynamicznego” elementu szablonu. W OPT 2.0 poza zmiennymi, z formatów danych korzystają jedynie sekcje, lecz w kolejnych wersjach ilość współpracujących z nimi instrukcji znacznie się powiększy. Mówiąc krótko: to właśnie formaty danych zdefiniowane przez skrypt decydują o tym, jak dana sekcja będzie działać w praktyce.

Domyślny format danych o nazwie Array traktuje dane dla sekcji jako tablicę z indeksami od 0 do n-1 (n – liczba elementów do wyświetlenia), przy czym każdy element listy także może być tablicą zmiennych. Ponadto, decyduje on także, skąd sekcja ma taką tablicę wziąć. Tu sprawa jest prosta. Musimy zapakować ją do zmiennej szablonu o nazwie identycznej, jak sekcja. To wszystko. Nasz kod będzie wyglądał następująco:

$view = new Opt_View('lista.tpl');
$view->items = array(0 =>
 array('id' => 1, 'title' => 'News 1', 'date_formatted' => '15.10.2009'),
 array('id' => 2, 'title' => 'News 2', 'date_formatted' => '16.10.2009'),
 array('id' => 3, 'title' => 'News 3', 'date_formatted' => '18.10.2009'),
 array('id' => 4, 'title' => 'News 4', 'date_formatted' => '21.10.2009'),
);

Listę taką jest bardzo prostu tworzyć na podstawie danych z bazy. Poniżej pokazujemy przykład dla biblioteki PDO:

$stmt = $pdo->query('SELECT * FROM news ORDER BY `date` DESC');
$items = array();
while($row = $stmt->fetch(PDO::FETCH_ASSOC))
{
  $row['date_formatted'] = date($config->dateFormat, $row['date']);
  $items[] = $row;
}
$stmt->closeCursor();
$view->items = $items;

Tablice będą prawdopodobnie najczęściej wykorzystywanym elementem, lecz warto pamiętać, że w każdej chwili możemy sprawić, by sekcja iterowała po obiekcie:

$view = new Opt_View('list.tpl');
$list = new SplDoublyLinkedList();
$list->push(new News(1));
$list->push(new News(2));
$list->push(new News(3));
$view->items = $list;
$view->setFormat('items', 'Objective');

Tym razem nasza lista jest obiektem klasy SplDoublyLinkedList dostępnej w Standardowej Bibliotece PHP od wersji 5.3.0. W naszym kodzie pojawiło się wywołanie dodatkowej metody: setFormat(). Dzięki niej możemy ustawiać formaty danych dla różnych elementów. Format Objective sprawia, że OPT traktuje zarówno listę, jak i jej elementy jako obiekty i odpowiednio optymalizuje pod tym kątem sekcję.

Formatów danych nie można zmieniać dynamicznie. Zmiana formatu wymaga rekompilacji szablonu, którą trzeba wymusić ręcznie, kasując odpowiadający szablonowi skompilowany plik z katalogu wskazywanego przez dyrektywę compileDir.

Przejdźmy do bardziej zaawansowanych przykładów. Formaty danych dla sekcji mogą kontrolować m.in. skąd mają one brać listę. Domyślnie poszukiwana jest po prostu zmienna o nazwie identycznej, jak nazwa sekcji, lecz nie jest to regułą. Przypuśćmy, że tworzymy system CMS z wymiennymi zestawami skórek. Skrypt przygotowuje dla szablonów odpowiednie listy pełne rozmaitych danych do prezentacji (lista najnowszych komentarzy, polecane odnośniki, bannery itd.), lecz skórki nie muszą korzystać ze wszystkich z nich. Chcielibyśmy zoptymalizować skrypt tak, aby nie tracił czasu na pobieranie i obróbkę tych danych, które i tak nie będą wyświetlane. Tutaj przyda się nam format danych StaticGenerator, gdzie zamiast danych sekcja otrzymuje jedynie obiekt, który wie, jak je pozyskać. Jeśli korzystamy z wzorca MVC, wystarczy wyposażyć nasze modele w odpowiedni interfejs i od razu będą one mogły współpracować z OPT:

class News_Model implements Opt_Generator_Interface
{
    /**
     * Generuj dane na zyczenie OPT.
     *
     * @param string $what Nazwa sekcji
     * @return array
     */
    public function generate($what)
    {
       $db = Registry::get('db');
       $stmt = $db->query('SELECT * FROM `news` ORDER BY `date` DESC');
       $list = array();
       while($row = $stmt->fetch(PDO::FETCH_ASSOC))
       {
            $row['date_formatted'] = date($config->dateFormat, $row['date']);
            $list[] = $row;
       }
       $stmt->closeCursor();
       return $list;
    } // end generate();
} // end News_Model;
 
$view = new Opt_View('list.tpl');
$view->items = new News_Model;
$view->setFormat('items', 'StaticGenerator/Array');

W momencie gdy szablon po raz pierwszy odwoła się do sekcji, OPT wykona metodę generate(), pobierając dla niej dane z bazy. Gdy skórka nie wykorzystuje danej sekcji, nie dzieje się nic, oszczędzając nam sporo zasobów. Zwróćmy uwagę na sposób ustawienia formatu danych. Oprócz nazwy, znajduje się tam ukośnik oraz kolejny format danych. Formaty danych w OPT implementują wzorzec „dekorator”, dzięki czemu można prosto tworzyć ich kompozycje. Zauważmy, że StaticGenerator nie zadziała samotnie, gdyż zmienia jedynie sposób pobierania danych. Tymczasem metoda generate() te dane w jakiejś postaci musi zwrócić, np. tablicy i dlatego musimy wszystko udekorować, aby OPT wiedział, co dalej z nimi zrobić.

Dekorację można stosować nie tylko przy generatorach. Przypuśćmy, że nasza lista jest tablicą, ale każdy element jest już obiektem reprezentującym pojedynczy wiersz w bazie (dane takie może zwrócić np. ORM). Ustawiamy wtedy format Array/Objective, co dla OPT jest sygnałem, że po liście ma iterować, jak po tablicy, a elementy traktować jak obiekty. Zmiana kolejności spowoduje, że jako obiekt potraktowana zostanie lista, a elementy jak tablice. Polecamy w ramach ćwiczenia zastanowić się, co oznacza format StaticGenerator/Objective/Array.

Pora na podsumowanie, czego nauczyliśmy się z pierwszej części tekstu. Sekcje służą do wyświetlania list, lecz w przeciwieństwie do zwykłych pętli ukrywają przed twórcą szablonu wszystkie zbędne szczegóły, dzięki czemu może on skupić się na swoim zadaniu bez przejmowania się technicznymi aspektami skryptu, który z szablonu będzie korzystał. Reszta precyzowana jest przez wybór jednego z kilku dostępnych formatów danych przez skrypt, którego twórca najlepiej wie, jakie dane dostarcza i w jakiej postaci. Temat do zastanowienia się: jakie są zalety usunięcia z szablonu elementów zależnych od skryptu takich, jak rodzaj dostarczanych danych (tablice, obiekty) i przerzucenie zadania ich określenia na skrypt? Odpowiedź znajduje się na końcu artykułu.