Dzięki książce "PEAR. Programowanie w PHP" nauczysz się wykonywać codzienne zadania programistyczne przy użyci
236 122 5MB
Polish Pages 304 [298] Year 2013
Table of contents :
Spis treści
O autorach
O redaktorach
Przedmowa
Co można znaleźć w tej książce
Konwencje
Rozdział 1. MDB2
Krótka historia MDB2
Warstwy abstrakcji
Warstwa abstrakcji dla interfejsu bazy danych
Warstwa abstrakcji dla kodu SQL
Warstwa abstrakcji dla typów danych
Uwarunkowania związane z prędkością
Konstrukcja pakietu MDB2
Zaczynamy pracę z MDB2
Instalowanie MDB2
Łączenie się z bazą danych
Tworzenie instancji obiektu MDB2
Opcje
Definiowanie trybu pobierania danych
Rozłączanie się z bazą danych
Korzystanie z MDB2
Przykład
Wykonywanie zapytań
Pobieranie danych
Skróty ułatwiające pobieranie danych
Skróty metod query*()
Skróty metod get*()
Typy danych
Ujmowanie wartości i identyfikatorów w cudzysłowy
Iteratory
Wyszukiwanie błędów
Warstwa abstrakcji kodu SQL w MDB2
Sekwencje
Określanie limitów zapytań
Zastępowanie zapytań
Obsługa subselektów
Instrukcje preparowane
Transakcje
Moduły MDB2
Moduł Manager
Moduł Function
Moduł Reverse
Własne rozszerzenia pakietu MDB2
Własny mechanizm rejestracji w dzienniku
Własne klasy pobierające dane
Własne klasy wyników
Własne iteratory
Własne moduły
Pakiet MDB2_Schema
Instalowanie i tworzenie instancji
Tworzenie kopii bazy danych
Zmienianie bazy danych
Podsumowanie
Rozdział 2. Wyświetlanie danych
Tabele HTML
Format tabel HTML
Tworzenie prostego kalendarza za pomocą HTML_Table
Pakiet HTML_Table_Matrix rozszerzający możliwości pakietu HTML_Table
Arkusze kalkulacyjne Excela
Format Excela
Nasz pierwszy arkusz kalkulacyjny
Słowo o komórkach
Przygotowywanie strony do wyświetlenia
Dodawanie formatowania
Kolory
Wypełnianie barwnym deseniem
Formatowanie liczb
Formuły
Wiele arkuszy kalkulacyjnych, obramowania, obrazki
Inne techniki tworzenia arkuszy kalkulacyjnych
Komponent siatki danych DataGrid
Źródła danych DataSource
Renderery
Prosta siatka danych DataGrid
Stronicowanie wyników
Korzystanie ze źródła danych DataSource
Korzystanie z renderera
Estetyczne formatowanie siatki danych
Poszerzanie możliwości DataGrid
Dodawanie kolumn
Generowanie plików PDF
Kolory
Czcionki
Komórki
Tworzenie nagłówków i stopek
Podsumowanie
Rozdział 3. Praca z formatem XML
Pakiety PEAR wspomagające pracę z XML
Tworzenie dokumentów XML
Tworzenie obiektów przechowujących informacje o nagraniach
Tworzenie dokumentów XML za pomocą klasy XML_Util
Tworzenie dokumentów XML za pomocą pakietu XML_FastCreate
Tworzenie dokumentów XML za pomocą pakietu XML_Serializer
Tworzenie aplikacji Mozilli za pomocą pakietu XML_XUL
Przetwarzanie dokumentów XML
Analizowanie danych XML za pomocą pakietu XML_Parser
Przetwarzanie kodu XML za pomocą pakietu XML_Unserializer
Analizowanie danych RSS za pomocą pakietu XML_RSS
Podsumowanie
Rozdział 4. Usługi WWW
Korzystanie z usług WWW
Korzystanie z usług WWW opartych na XML-RPC
Sięganie do interfejsu API Google
Korzystanie z usług WWW opartych na REST
Tworzenie własnych usług WWW opartych na REST
Oferowanie usług WWW
Oferowanie usług WWW opartych na protokole XML-RPC
Oferowanie usług WWW opartych na protokole SOAP
Oferowanie usług opartych na protokole REST za pomocą pakietu XML_Serializer
Podsumowanie
Rozdział 5. Praca z datami
Praca z pakietem Date
Pakiet Date
Obsługa stref czasowych za pomocą klasy Date_Timezone
Pakiet PEAR::Date — podsumowanie
Pakiet Date_Holidays
Wyliczanie świąt
Czy dziś mamy święto?
Tłumaczenie nazw świąt na inne języki
Pakiet Date_Holidays — podsumowanie
Praca z pakietem Calendar
Podstawowe klasy i pojęcia związane z pakietem Calendar
Tworzenie obiektów
Sprawdzanie poprawności obiektów kalendarza
Modyfikowanie działania standardowych klas
Generowanie danych w formie graficznej
Podsumowanie
Skorowidz
Tytuł oryginału: PHP Programming with PEAR Tłumaczenie: Sławomir Dzieniszewski ISBN: 978-83-246-6934-9 Copyright © Packt Publishing 2006. First published in the English language under the title „PHP Programming with PEAR” Polish language edition published by Wydawnictwo Helion S.A. Copyright © 2007 All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 032 231 22 19, 032 230 98 63 e-mail: [email protected] WWW: http://helion.pl (księgarnia internetowa, katalog książek) Pliki z przykładami omawianymi w książce można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/pearph.zip Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie?pearph_ebook Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Printed in Poland. • Poleć książkę na Facebook.com
• Księgarnia internetowa
• Kup w wersji papierowej
• Lubię to! » Nasza społeczność
• Oceń książkę
Spis treści O autorach
7
Przedmowa
11
Rozdział 1. MDB2
15
Krótka historia MDB2 Warstwy abstrakcji Warstwa abstrakcji dla interfejsu bazy danych Warstwa abstrakcji dla kodu SQL Warstwa abstrakcji dla typów danych Uwarunkowania związane z prędkością Konstrukcja pakietu MDB2 Zaczynamy pracę z MDB2 Instalowanie MDB2 Łączenie się z bazą danych Tworzenie instancji obiektu MDB2 Opcje Definiowanie trybu pobierania danych Rozłączanie się z bazą danych Korzystanie z MDB2 Przykład Wykonywanie zapytań Pobieranie danych Skróty ułatwiające pobieranie danych Skróty metod query*() Skróty metod get*() Typy danych Ujmowanie wartości i identyfikatorów w cudzysłowy Iteratory Wyszukiwanie błędów
16 16 16 17 17 17 18 19 19 20 21 21 23 23 24 24 25 25 26 26 27 29 31 32 33
PEAR. Programowanie w PHP
Warstwa abstrakcji kodu SQL w MDB2 Sekwencje Określanie limitów zapytań Zastępowanie zapytań Obsługa subselektów Instrukcje preparowane Transakcje Moduły MDB2 Moduł Manager Moduł Function Moduł Reverse Własne rozszerzenia pakietu MDB2 Własny mechanizm rejestracji w dzienniku Własne klasy pobierające dane Własne klasy wyników Własne iteratory Własne moduły Pakiet MDB2_Schema Instalowanie i tworzenie instancji Tworzenie kopii bazy danych Zmienianie bazy danych Podsumowanie
Rozdział 2. Wyświetlanie danych Tabele HTML Format tabel HTML Tworzenie prostego kalendarza za pomocą HTML_Table Pakiet HTML_Table_Matrix rozszerzający możliwości pakietu HTML_Table Arkusze kalkulacyjne Excela Format Excela Nasz pierwszy arkusz kalkulacyjny Słowo o komórkach Przygotowywanie strony do wyświetlenia Dodawanie formatowania Kolory Wypełnianie barwnym deseniem Formatowanie liczb Formuły Wiele arkuszy kalkulacyjnych, obramowania, obrazki Inne techniki tworzenia arkuszy kalkulacyjnych Komponent siatki danych DataGrid Źródła danych DataSource Renderery Prosta siatka danych DataGrid Stronicowanie wyników Korzystanie ze źródła danych DataSource Korzystanie z renderera
4
34 34 35 36 36 37 41 42 43 46 47 49 49 51 52 55 56 57 57 58 61 61
63 64 64 65 69 71 71 72 73 74 74 75 77 77 79 80 83 84 85 85 86 87 87 88
Spis treści
Estetyczne formatowanie siatki danych Poszerzanie możliwości DataGrid Dodawanie kolumn Generowanie plików PDF Kolory Czcionki Komórki Tworzenie nagłówków i stopek Podsumowanie
89 90 91 92 95 96 96 97 98
Rozdział 3. Praca z formatem XML Pakiety PEAR wspomagające pracę z XML Tworzenie dokumentów XML Tworzenie obiektów przechowujących informacje o nagraniach Tworzenie dokumentów XML za pomocą klasy XML_Util Tworzenie dokumentów XML za pomocą pakietu XML_FastCreate Tworzenie dokumentów XML za pomocą pakietu XML_Serializer Tworzenie aplikacji Mozilli za pomocą pakietu XML_XUL Przetwarzanie dokumentów XML Analizowanie danych XML za pomocą pakietu XML_Parser Przetwarzanie kodu XML za pomocą pakietu XML_Unserializer Analizowanie danych RSS za pomocą pakietu XML_RSS Podsumowanie
Rozdział 4. Usługi WWW Korzystanie z usług WWW Korzystanie z usług WWW opartych na XML-RPC Sięganie do interfejsu API Google Korzystanie z usług WWW opartych na REST Tworzenie własnych usług WWW opartych na REST Oferowanie usług WWW Oferowanie usług WWW opartych na protokole XML-RPC Oferowanie usług WWW opartych na protokole SOAP Oferowanie usług opartych na protokole REST za pomocą pakietu XML_Serializer Podsumowanie
Rozdział 5. Praca z datami Praca z pakietem Date Pakiet Date Obsługa stref czasowych za pomocą klasy Date_Timezone Pakiet PEAR::Date — podsumowanie Pakiet Date_Holidays Wyliczanie świąt Czy dziś mamy święto? Tłumaczenie nazw świąt na inne języki Pakiet Date_Holidays — podsumowanie
99 100 101 102 106 110 118 133 142 143 155 169 173
175 176 177 182 185 199 208 208 216 223 232
235 235 236 246 250 250 254 258 259 264
5
PEAR. Programowanie w PHP
Praca z pakietem Calendar Podstawowe klasy i pojęcia związane z pakietem Calendar Tworzenie obiektów Sprawdzanie poprawności obiektów kalendarza Modyfikowanie działania standardowych klas Generowanie danych w formie graficznej Podsumowanie
Skorowidz
6
264 265 268 272 274 275 282
283
X O autorach Stephan Schmidt pracuje dla firmy 1&1 Internet, jednego z największych na świecie dostawców internetu, działającego w Karlsruhe (Niemcy). Kieruje zespołem programistów języków PHP i Java oraz specjalizuje się w programowaniu witryn WWW i internetowych systemów składania zamówień dla firmy 1&1 Internet. Od 2001 roku aktywnie współpracuje przy projekcie otwartego kodu źródłowego języka PHP. Wówczas to wraz z przyjaciółmi założył witrynę internetową PHP Application Tools (http://www.php-tools.net), która obecnie jest jednym z najstarszych projektów PHP OSS. Ponadto pracuje nad ponad 15 pakietami PEAR (koncentrując się na języku XML i usługach WWW), jak również nad rozszerzeniem id3. Ostatnio rozpoczął jeszcze projekt XJConf (http://www.xjconf.net) oraz udziela się w społeczności programistów języka Java. Jest autorem (napisanej oryginalnie w języku niemieckim) książki PHP Design Patterns (O’Reilly Verlag, ISBN 3-89721-442-3), jak również współautorem kilku innych publikacji poświęconych językowi PHP; ponadto pisał artykuły dla kilku czasopism informatycznych. Występował również jako prelegent na różnych konferencjach poświęconych projektom otwartego kodu źródłowego (ang. open source), organizowanych na całym świecie. Wolny czas poświęca kolekcjonowaniu amerykańskich komiksów o superbohaterach ze złotego okresu lat 50. Carsten Lucke studiował informatykę na Uniwersytecie Nauk Stosowanych w Brandenburgu (Fachhochshule Brandenburg) w Niemczech. Obecnie pracuje jako programista dla firmy software design and management AG (sd&m AG) w Monachium. W wolnym czasie pisze artykuły dla różnych czasopism i wspiera środowisko programistów open source (szczególnie programistów języka PHP). Jest autorem bardzo pomocnych pakietów PEAR/PECL, twórcą projektu serwera kanałów 3rdPEARty (3rdpearty.net) oraz projektu tool-garage.de oferującego darmowe oprogramowanie open source.
PEAR. Programowanie w PHP
Chciałbym przede wszystkim podziękować Tomowi Kouri oraz zespołowi High-Touch Communications z Montrealu. Szczególne wyrazy wdzięczności kieruję też do Dereka Fonga za wprowadzenie mnie w PEAR i do Michaela Caplana za to, że zawsze dotrzymuje kroku nowinkom wprowadzanym w PEAR.
Aaron Wormus jest niezależnym konsultantem pracującym w okolicach Frankfurtu w Niemczech. Specjalizuje się w programowaniu architektury klient – serwer oraz infrastruktury intranetowej. W pracy korzysta głównie z języka PHP i narzędzi open source. Aaron regularnie publikuje również artykuły w magazynach „PHPMagazine”, „PHPArchitect” i „PHPSolutions”. Są to głównie teksty poświęcone pakietom PEAR, programowaniu w PHP i metodologii programowania. Jest również zapalonym blogerem, dbającym o to, by jego osobisty blog był zawsze aktualny, pełen świeżych technicznych wskazówek, komentarzy politycznych i najnowszych wieści na temat tej cudownej a zarazem tajemniczej rzeczy, jaką jest internet. Chwile, których nie spędza przed komputerem, poświęca zazwyczaj zabawie ze swoimi dwiema córeczkami lub goszcząc w kuluarach jakiejś konferencji poświęconej nowinkom ze świata programowania.
O redaktorach Lukas Kahwe Smith programuje w języku PHP już od roku 2000, a od 2001 roku publikuje w repozytorium kodu PEAR. Od tego czasu zaprogramował i przygotował wiele pakietów PEAR, z których najbardziej znane to MDB2 i LiveUser; miał także istotny wpływ na sam kierunek projektu PEAR, będąc jednym z członków założycieli komitetu zarządzającego PEAR Group (PEAR Group steering committe) oraz głównego zespołu QA. Oprócz licznych publikacji w kilku magazynach branżowych jest też częstym prelegentem na wielu międzynarodowych konferencjach poświęconych językowi PHP. Shu-Wai Chow działa w branży komputerowej i programistycznej od ośmiu lat. Zaczynał karierę w Sacramento, stolicy stanu Kalifornia, przez cztery lata pracując jako administrator sieci w firmach Educaid i First Union, a przez kolejne cztery — jako projektant aplikacji w Vision Service Plan. W tym czasie zgłębił takie technologie, jak Java, JSP, PHP, ColdFusion, ASP, LDAP, XSLT i XSL-FO. Shu działa też społecznie jako administrator sieci i doradca w sprawach adopcji kotów dla kilku organizacji opieki nad zwierzętami w okolicach Sacramento. Obecnie pracuje jako programista w firmie Antenna Software w Jersey City w stanie New Jersey.
8
O autorach
Mimo iż urodzony w byłej brytyjskiej kolonii Hongkongu, większość swego dzieciństwa spędził w Palo Alto w Kalifornii. Studiował antropologię i ekonomię na Uniwersytecie Stanu Kalifornia w Sacramento. Obecnie mieszka na wybrzeżu Atlantyku w stanie New Jersey wraz z siedmiorgiem bardzo wymagających kotów, trzema podejrzanie inteligentnymi ptakami, umiłowaną gitarą elektryczną Fender Stratocaster i swoją ukochaną dziewczyną o anielskiej naturze. Arnaud Limburg programuje w języku PHP już od czterech lat. Uczestniczy w projekcie PEAR jako programista odpowiedzialny za kontrolę jakości i jeden z programistów odpowiedzialnych za pakiet LiveUser. Obecnie pracuje dla firmy telekomunikacyjnej jako programista VoIP.
9
PEAR. Programowanie w PHP
10
X Przedmowa Skrót PEAR wywodzi się od słów: PHP Extension and Application Repository (Repozytorium Rozszerzeń i Aplikacji PHP), i oznacza ramę projektową a zarazem system dystrybucji wszechstronnie sprawdzonych, przeznaczonych do wielokrotnego użytku komponentów PHP, dostępnych w formie „pakietów” oprogramowania. Witryna projektu PEAR to pear.php.net, w której można znaleźć, a następnie pobrać najróżniejsze użyteczne pakiety oprogramowania napisane w języku PHP. W witrynie tej programiści znajdą pakiety oprogramowania PHP pomocne w rozwiązywaniu prawie każdego problemu, z którym mogą się zetknąć w codziennej pracy. Dodatkowo oprócz funkcji i możliwości oferowanych przez udostępniane w witrynie pakiety, kod publikowany w repozytorium PEAR podlega ścisłym zaleceniom, dzięki którym kod PHP zyskuje na spójności. W naszej książce pokażemy, jak korzystać z kilku najbardziej użytecznych pakietów PEAR, które pozwolą czytelnikom znacznie poprawić produktywność w trakcie programowania. Koncentrując się na pakietach kluczowych dla przebiegu programowania, książka ta jest znakomitym przewodnikiem po jednej z najbardziej użytecznych składnic gotowego kodu, dostępnych w internecie.
Co można znaleźć w tej książce Rozdział 1. jest wprowadzeniem do warstwy abstrakcji bazy danych MDB2. Czytelnicy dowiedzą się z niego, w jaki sposób łączyć się z bazą danych, dokonywać instancjacji (tworzyć) obiektu MDB2. Pakiet MDB2 znakomicie wywiązuje się z postawionego przed nim zadania, którym jest obudowanie i ukrycie różnic między systemami baz danych i dostarczenie programiście uniwersalnego interfejsu umożliwiającego dostęp do ich funkcji. Dzięki temu programista nie musi przejmować się szczegółami implementowania różnych funkcji w zależności od systemu bazy danych. Opowiemy, jak korzystać z tej funkcji dokonującej abstrakcji
PEAR. Programowanie w PHP
kodu SQL, by za jej pomocą oferować użytkownikom pola z automatycznie zwiększanymi wartościami (ang. auto-increment fields), wykonywać zapytania zmieniające zawartość rekordów w bazie danych lub też wstawiające nowe rekordy. Powiemy również, jak używać już przygotowanych, standardowych instrukcji SQL oraz jak korzystać z metod zapisywania danych w bazie, które uwzględniać będą wymogi bezpieczeństwa. Czytelnik dowie się również o różnych pakietach MDB2 oraz jak dodawać do pakietu MDB2 nowe klasy pobierające wyniki, nowe iteratory i moduły. Gdy już pobierzemy dane z bazy, trzeba je będzie wyświetlić na stronie. Rozdział 2. omawia kilka popularnych pakietów PEAR wykorzystywanych do prezentowania danych w różnych formatach. Dowiemy się na przykład, jak za pomocą modułów HTML_Table i HTML_Table_Matrix tworzyć i formatować tabele. Jak przy użyciu pakietu Spreadsheet_ Excel_Writer tworzyć i generować arkusze kalkulacyjne Excela. Jak tworzyć wszechstronne i wygodnie dzielone na strony „siatki danych” za pomocą modułu Structures_DataGrid oraz jak na poczekaniu generować dokumenty PDF z wykorzystaniem modułu File_PDF. Kolejnym często używanym formatem w pracy z danymi jest format XML — repozytorium PEAR oferuje oczywiście odpowiednie moduły wspomagające ten format. W rozdziale 3. opowiemy dokładnie, jak repozytorium PEAR ułatwia pracę z formatem XML. Rozdział ten omawiać będzie między innymi tworzenie dokumentów XML za pomocą pakietów XML_Util, XML_FastCreate, XML_Serializer i XML_XUL. Ponadto opisuje, w jaki sposób odczytywać dokumenty XML korzystając z parsera (analizatora) opartego na SAX oraz jak przekształcać obiekty PHP na kod XML (i odwrotnie!) za pomocą modułów XML_Serializer i XML_Unserializer. Rozdział 4. opowiada o pakietach PEAR wspomagających usługi WWW i interfejsy API wykorzystywane w sieci WWW. Dowiemy się między innym, jak korzystać z usług SOAP i XMLRPC, jak sięgać do interfejsu API Google, jak przeszukiwać pozycje w blogach za pomocą pakietu Services_Technorati, jak korzystać z usługi WWW księgarni Amazon, jak sięgać do interfejsu API Yahoo. Dowiemy się również, jak oferować nowe usługi WWW oparte na XML-RPC lub SOAP. Ponadto damy czytelnikowi przedsmak tego, jak może wyglądać usługa WWW oparta na REST, przygotowana za pomocą modułu XML_Serializer. Rozdział 5. jest poświęcony funkcjom daty i czasu oferowanym przez moduły PEAR::Calendar i PEAR::Date z repozytorium PEAR. Dowiemy się, jakie zalety mają te moduły w stosunku do standardowych funkcji daty i czasu języka PHP. Następnie pokażemy, jak tworzyć, zmieniać i porównywać ze sobą obiekty daty Date korzystając z modułu Date_Span. Jak obsługiwać strefy czasowe i uwzględniać święta za pomocą modułów Date_Holidays oraz jak korzystać z klasy Calendar, by za jej pomocą wyświetlać kalendarz HTML.
12
Przedmowa
Konwencje W tej książce znajdziemy wiele stylów tekstu, wykorzystywanych do oznaczania różnych rodzajów informacji. Oto kilka przykładów używanych stylów, wraz z objaśnieniem ich znaczenia. W kodach programów zasadniczo wykorzystujemy trzy różne style. Fragmenty kodu pojawiające się w akapitach zwykłego tekstu formatowane są tak jak w tym przykładzie: „Również ta klasa oferuje metodę setId(), która przywoływana jest przez obiekt Label, gdy do listy artystów dodawany jest kolejny”. Blok kodu jest natomiast formatowany w następujący sposób: function getDGInstance($type) { if (class_exists($type)) { $datagrid =& new $type; return $datagrid; } else { return false; } }
Kiedy natomiast zechcemy zwrócić uwagę czytelnika na jakiś fragment kodu, określone wiersze lub fragmenty kodu zostaną wytłuszczone: $driver = Date_Holidays::factory($driverId, $year); $InternalNames = $driver->getInternalHolidayNames();
Wszelkie polecenia i dane wprowadzane lub zwracane w wierszu poleceń będą natomiast prezentowane takim stylem: $ pear-dh-compile-translationfile --help
Nowe terminy i ważne słowa będą oznaczane kursywą. Podobnie polecenia pojawiające się w menu, na przyciskach lub w oknach dialogowych. Na przykład: „kliknięcie przycisku Next (Dalej) przeniesie nas do kolejnego ekranu”. Ostrzeżenia, wskazówki, porady i ważne uwagi umieszczane będą w takich ramkach.
Pliki z przykładami do pobrania znajdują się pod adresem: ftp://ftp.helion.pl/przyklady/pearph.zip
13
PEAR. Programowanie w PHP
14
1 MDB2 W ciągu ostatnich dziesięciu lat sieć WWW bardzo się rozrosła, jak również dojrzała i sprofesjonalizowała, w związku z czym pojawiło się zapotrzebowanie na coraz bardziej złożone i dynamiczne witryny WWW. Dawniej zupełnie wystarczało przechowywanie informacji w pliku tekstowym lub prostej bazie danych, obecnie jednak każdy programista piszący profesjonalną aplikację WWW musi posiadać rzetelną wiedzę na temat tego, jak komunikować się z profesjonalnymi relacyjnymi bazami danych. Począwszy od najwcześniejszych wersji język PHP zawsze służył programistom solidnym wsparciem w kontaktach z bazami danych. Niemniej do czasu wprowadzenia rozszerzenia PDO (PHP Data Objects, obiekty danych PHP) nie istniał żaden standardowy sposób korzystania z najróżniejszych sterowników baz danych dodawanych do języka PHP. Brak ujednoliconego interfejsu API był oczywiście inspiracją dla kilku projektów mających na celu stworzenie jakiejś warstwy DBAL (Database Abstraction Layer), która oferowałaby uniwersalny poziom abstrakcji dla wszystkich baz danych. Głównym celem tych wysiłków był zamiar ułatwienia życia programistom, tak aby mogli oni pisać kod komunikujący się z bazą danych, który będzie niezależny od sytemu bazy danych wykorzystywanego przez aplikację. Dzięki temu klienci lub użytkownicy mogliby używać aplikacji w połączeniu z tym systemem zarządzania bazami danych, który im najbardziej odpowiada. Trzy najważniejsze z rozpoczętych w tamtych latach prób stworzenia warstwy abstrakcji dla baz danych to: AdoDB, PEAR::DB i Metabase. W ostatnich latach kolejnym bardzo mocnym kandydatem na uniwersalną warstwę abstrakcji dla baz danych był pakiet PEAR::MDB. Niniejszy rozdział poświęcony będzie kolejnej inkarnacji MDB, a mianowicie MDB2.
PEAR. Programowanie w PHP
Krótka historia MDB2 Wszystko zaczęło się, kiedy Lukas Smith, programista PEAR, opublikował kilka łat do istniejącej już warstwy abstrakcji bazy danych, Metabase. W którymś momencie między Lukasem a autorem Metabase wywiązała się dyskusja na temat, jakby opublikować Metabase w repozytorium PEAR jako nowy pakiet. Celem nowego pakietu byłoby połączenia funkcjonalności oferowanych przez Metabase z interfejsem API istniejącego już i bardzo popularnego pakietu PEAR::DB. Dzięki temu programiści otrzymaliby bogatą w funkcje i znakomicie spisującą się bibliotekę abstrakcji bazy danych, co byłoby z wielką korzyścią dla infrastruktury PEAR. W taki właśnie sposób narodził się przodek pakietu MDB2, czyli pakiet PEAR::MDB. Po kilku latach pracy nad pakietem PEAR::MDB, dla autorów stało się oczywiste, że decyzja utrzymywania interfejsu API pakietu w takiej formie, aby był jak najbardziej zbliżony do interfejsów API Metabase i PEAR::DB, nieuchronnie stwarza pewne problemy, które utrudniają przekształcenie MDB w pełni profesjonalną warstwę abstrakcji bazy danych (DBAL). Ponieważ pakiet PEAR::MDB osiągnął już w repozytorium PEAR stabilną formę w pełni dojrzałego pakietu oprogramowania, niemożliwe było usunięcie pewnych wad bez rezygnacji z kompatybilności z tym starszym modułem, czego właśnie autorzy starali się za wszelką cenę uniknąć. Rozwiązaniem było wykorzystanie doświadczeń zdobytych podczas prac nad Metabase i MDB oraz zastosowanie ich w nowym pakiecie, który zawierać będzie profesjonalnie zaprojektowany i w pełni nowoczesny interfejs API. Nowy pakiet otrzymał nazwę MDB2.
Warstwy abstrakcji Zanim przejdziemy do szczegółowego omawiania, w jaki sposób pakiet MDB2 radzi sobie z tworzeniem abstrakcji dla bazy danych, powinniśmy najpierw zapoznać się przynajmniej pobieżnie z teorią tworzenia warstw abstrakcji obudowujących systemy baz danych, by zrozumieć dokładnie, w jaki sposób się to robi. Na tworzenie warstw abstrakcji dla baz danych można spojrzeć z kilku perspektyw. Omówimy je teraz dokładniej po kolei i opowiemy, jakie mają wymagania.
Warstwa abstrakcji dla interfejsu bazy danych Najważniejszym etapem w tworzeniu abstrakcji systemu bazy danych jest przygotowanie odpowiedniej abstrakcji dla interfejsu bazy danych. Dzięki temu programista będzie mógł sięgać do baz danych zarządzanych przez różne systemy, używając tych samych metod. Oznacza to, że zarówno tworzenie połączenia z bazą danych, wysyłanie zapytania, jak i pobieranie danych zawsze przebiegać będzie identycznie, niezależnie od tego, z jaką bazą danych będziemy współpracować.
16
Rozdział 1. • MDB2
Warstwa abstrakcji dla kodu SQL Większość obecnie używanych systemów baz danych korzysta ze standardowego zestawu podstawowych instrukcji SQL, dlatego też większość kodu SQL pisanego przez programistów powinna działać zawsze, niezależnie od tego, z jakiej bazy danych będą korzystać. Niemniej wiele z systemów baz danych wprowadza własne, specyficzne tylko dla danego systemu instrukcje SQL i pomocnicze funkcje, dlatego też może się zdarzyć, że kod SQL napisany specjalnie dla jednej bazy danych nie będzie działał w innej. W miarę jak system zarządzania bazą danych (ang. Relational Database Management System — RDBMS) rozwija się, czasami implementuje funkcje, które nie są kompatybilne ze starszymi wersjami tej samej bazy danych. Dlatego też dla programisty pragnącego napisać kod, który byłby zgodny ze wszystkimi wersjami bazy danych (lub który mógłby współdziałać z kilkoma różnymi systemami baz danych), jedynym rozwiązaniem jest ograniczenie się tylko do tych instrukcji kodu SQL, o których wiadomo, że na pewno będą działać na wszystkich platformach baz danych. Lepszą opcją jest jednak skorzystanie ze specjalnej warstwy abstrakcji baz danych, która w razie potrzeby emuluje odpowiednie funkcje, jeśli nie będą dostępne na danej platformie. Mimo iż nie jest wykonalne obudowanie każdej możliwej funkcji SQL, pakiet MDB2 obsługuje bardzo wiele powszechnie wykorzystywanych funkcji SQL. Funkcje te to między innymi obsługa zapytań LIMIT, subselektów (podzapytań select) i zapytań preparowanych. Korzystanie z mechanizmu abstrakcji kodu SQL, oferowanego przez MDB2, daje nam gwarancję, że będziemy mogli korzystać z tych zaawansowanych funkcji — nawet wtedy, gdy baza danych, z której korzystamy, samoistnie ich nie obsługuje. W dalszej części tego rozdziału opowiemy o różnych funkcjach oferujących abstrakcję dla zaawansowanych narzędzi SQL, które zapewnia pakiet MDB2.
Warstwa abstrakcji dla typów danych Na koniec wreszcie konieczne jest przygotowanie abstrakcji dla typów danych stosowanych przez bazy. Wynika to z faktu, że różne systemy baz danych często obsługują typy danych w zupełnie inny sposób.
Uwarunkowania związane z prędkością Zapewne czytelnikom cieknie już ślinka, by skosztować tych wspaniałych funkcji wkomponowanych w pakiet MDB2, niemniej najpierw należy powiedzieć kilka słów o zagadnieniach związanych z prędkością i wydajnością. Warto wiedzieć, że gdy korzystamy z warstwy abstrakcji bazy danych, często za bogactwo funkcji oferowanych przez pakiet dokonujący abstrakcji musimy zapłacić mniejszą wydajnością i szybkością działania bazy danych. Nie jest to
17
PEAR. Programowanie w PHP
tylko ułomność pakietu MDB2 ani też warstw abstrakcji bazy danych, ale w ogóle wszelkiego rodzaju warstw abstrakcji i systemów wirtualizacji. Na szczęście, inaczej niż w przypadku VMWare lub Miscrosoft Virtual PC, które dokonują abstrakcji każdego wykonywanego wywołania systemowego, pakiet MDB2 oferuje abstrakcję tylko wtedy, gdy dana funkcja nie jest dostępna w określonym systemie baz danych. Oznacza to, że wydajność zależeć będzie od platformy, na której skorzystamy z MDB2. Jeśli szczególnie zależy nam na szybkości i wydajności, to należy skorzystać z pamięci podręcznej dla kodów operacji (ang. opcode cache) lub włączyć mechanizm przechowywania zapytań w pamięci podręcznej w systemie baz danych, którego używamy. Dzięki wykorzystaniu wspomnianych możliwości języka PHP lub systemu baz danych będziemy mogli w znacznym stopniu ograniczyć negatywne efekty spowolnienia działania bazy danych, nieodłącznie związane z użyciem warstwy abstrakcji.
Konstrukcja pakietu MDB2 Interfejs API pakietu MDB2 został zaprojektowany w taki sposób, aby gwarantować maksimum wszechstronności i elastyczności. Poszczególnym obsługiwanym systemom baz danych i określonym zaawansowanym funkcjom przypisano określone moduły. Każdy z licznych sterowników (ang. drivers) dla baz danych jest osobnym i niezależnie rozwijanym modułem PEAR. Oznacza to, że każdy pakiet sterownika funkcjonuje niezależnie, a kolejne wersje i wersje stabilne publikowane są we własnych, niezależnych od innych sterowników cyklach. Dzięki temu programiści odpowiedzialni za przygotowywanie poszczególnych sterowników mogą je wypuszczać, ilekroć zachodzi taka potrzeba, bez konieczności czekania na publikację kolejnej wersji głównego pakietu MDB2. Pakiet MDB2 może zatem zachowywać stabilność, niezależnie od stanu prac nad pakietami obsługującymi poszczególne sterowniki. W efekcie zdarza się czasem, że stabilna wersja pakietu oferuje sterowniki dla niektórych systemów baz danych jedynie w wersji beta. Ponadto w chwili wypuszczenia nowego sterownika dla bazy danych oznaczany jest on jako wersja alfa i pakiet podlega procedurze sprawdzania zgodnie ze standardami repozytorium PEAR. Drugi rodzaj modułów wbudowanych w pakiet MDB2 to moduły dodające specjalne, rozszerzone funkcje oferowane przez pakiet MDB2. Zamiast dołączać te funkcje do głównego pakietu MDB2 lub dodawać do niego nową klasę implementującą te funkcje, programista ma możliwość utworzenia nowej klasy w osobnym module i następnie załadowanie jej do pakietu MDB2 za pomocą metody loadModule(). Gdy już nowy moduł zostanie załadowany do pakietu MDB2, do jego metod będzie można sięgać w taki sam sposób, jakby były metodami wbudowanymi w pakiet MDB2. Pakiet MDB2 stosuje to rozwiązanie, aby jego wewnętrzne pakiety działały tak szybko, jak to tylko możliwe, a jednocześnie by pozostawić użytkownikom swobodę dołączania do pakietu MDB2 swoich własnych klas. Szczegółowe informacje o tym, jak we własnym zakresie rozwijać pakiet MDB2, można znaleźć w dalszej części tego rozdziału.
18
Rozdział 1. • MDB2
Zaczynamy pracę z MDB2 Poniżej omówimy podstawowe kroki, które trzeba wykonać, by zainstalować pakiet MDB2, utworzyć obiekt MDB2 oraz skonfigurować kilka opcji definiujących tryb pobierania danych. Na koniec powiemy, jak rozłączać się z bazą danych.
Instalowanie MDB2 Podczas instalowania pakietu MDB2 należy pamiętać, że nie zawiera on żadnych sterowników baz danych, dlatego trzeba je będzie zainstalować później osobno. Pakiet MDB2 jest rozprowadzany w wersji stabilnej, niemniej, jak już wspomnieliśmy, niektóre z wchodzących w jego skład modułów sterowników i rozszerzeń mogą być rozwijane w niezależnych cyklach, dlatego niektóre z wykorzystywanych przez nas modułów mogą być dopiero w wersji beta, alfa lub nawet jeszcze w fazie programowania. Należy o tym pamiętać podczas instalowania pakietów zawierających sterowniki poszczególnych baz danych. Najprościej jest zainstalować MDB2 korzystając z programu instalacyjnego repozytorium PEAR: > pear install MDB2
To polecenie zainstaluje klasy tworzące rdzeń MDB2, natomiast nie zainstaluje żadnego z dostępnych sterowników baz danych. Aby zainstalować sterownik właściwy dla systemu baz danych, którego używamy, należy skorzystać z polecenia: > pear install MDB2_Driver_mysql
To akurat polecenie zainstaluje sterownik dla bazy MySQL. Aby zainstalować sterownik dla bazy SQLite, należy wpisać: > pear install MDB2_Driver_sqlite
Oto pełna lista dostępnych aktualnie sterowników: fbsql — Front Base ibase — InterBase mssql — MS SQL Server mysql — MySQL mysqli — system MySQL korzystający z rozszerzenia mysqli PHP; więcej informacji pod adresem: http://php.net/mysqli oci8 — Oracle pgsql — PostgreSQL querysim — Querysim sqlite — SQLite
19
PEAR. Programowanie w PHP
Łączenie się z bazą danych Aby połączyć się z wybraną bazą danych już po udanym zainstalowaniu pakietu MDB2 i modułu sterownika, konieczne będzie najpierw określenie nazwy źródła danych, DSN (Data Source Name). Nazwa DSN może mieć postać łańcucha lub tablicy i definiuje parametry połączenia z bazą danych takie jak: nazwa bazy danych, typ systemu RDBMS (systemu zarządzającego relacyjną bazą danych), nazwa użytkownika i hasło wykorzystywane do łączenia się z bazą danych itp.
Nazwa DSN jako tablica Jeśli nazwa źródła danych, DSN, jest definiowana w formie tablicy, będzie wyglądać mniej więcej tak: $dsn = array ( 'phptype' => 'mysql', 'hostspec' => 'localhost:3306', 'username' => 'user', 'password' => 'pass', 'database' => 'mdb2test' );
Oto lista różnych kluczy parametrów używanych w tablicy nazwy DSN: phptype — nazwa wykorzystywanego sterownika; innymi słowy: nazwa definiująca system RDBMS hostspec — (specyfikacja hosta) określa nazwę hosta, na którym działa baza danych; może przyjmować postać host:port lub też podawać tylko samą nazwę hosta, a port będzie wtedy definiowany osobno w kluczu port database — nazwa bazy danych, z którą się łączymy dbsyntax — jeśli używana składnia jest inna niż właściwa dla systemu phptype protocol — wykorzystywany protokół komunikacyjny, na przykład TCP socket — gniazdo, które należy określić, jeśli łączymy się za pośrednictwem gniazd mode — służy do definiowania trybu otwierania pliku bazy danych
Nazwa DSN jako łańcuch Szybszym i bardziej przyjaznym dla człowieka sposobem (gdy już się do niego przyzwyczaimy) jest definiowanie nazw DSN za pomocą łańcucha tekstowego wyglądającego podobnie do adresu URL. Zasadniczo, jego składnia wygląda tak: phptype://nazwa-użytkownika:hasło@specyfikacja-hosta/baza-danych
gdzie phptype to oczywiście typ systemu baz danych. Dla systemu MySQL łańcuch nazwy DSN może wyglądać na przykład tak: $dsn = 'mysql://user:pass@localhost:3306/mdb2test';
20
Rozdział 1. • MDB2
Więcej informacji na temat nazw DSN oraz inne przykłady prawidłowych łańcuchów DSN można znaleźć w podręczniku repozytorium PEAR, dostępnym pod adresem: http://pear.php.net/ ¦manual/en/package.database.mdb2.intro-dsn.php.
Tworzenie instancji obiektu MDB2 Istnieją trzy metody umożliwiające tworzenie (instancjację) obiektu MDB2: $mdb2 =& MDB2::connect($dsn); $mdb2 =& MDB2::factory($dsn); $mdb2 =& MDB2::singleton($dsn);
Metoda connect() tworzy obiekt i łączy się z bazą danych. Metoda factory() tworzy obiekt, natomiast połączenie utworzy dopiero, gdy będzie ono potrzebne. Wreszcie metoda singleton() działa podobnie jak metoda factory(), ale upewnia się, że istnieje tylko jeden obiekt MDB2 o danej nazwie źródła danych, DSN. Jeśli więc taki obiekt już istnieje, metoda zwraca ten obiekt, a jeśli nie, tworzy nowy. Istnieje też sposób „zakłócania” działania metody singleton() za pomocą metody setDatabase(), która pozwala określić, że bieżąca baza danych ma być inna niż ta określona w nazwie DSN. $dsn = 'mysql://root@localhost/mdb2test'; $mdb2_first =& MDB2::singleton($dsn); $mdb2_first->setDatabase('inna_db'); $mdb2_second =& MDB2::singleton($dsn);
W tym przypadku będziemy mieli dwie różne instancje MDB2. Wszystkie trzy wspomniane metody tworzą obiekt klasy sterownika bazy danych. Jeśli na przykład korzystamy ze sterownika bazy MySQL, zmienna $mdb zdefiniowana powyżej będzie instancją klasy MDB2_Driver_mysql.
Opcje Pakiet MDB2 udostępnia kilka opcji, które można definiować przywołując metody connect(), factory() lub singleton() lub też później korzystając z metody setOption() (by zdefiniować jedną opcję) lub setOptions() (by zdefiniować kilka opcji na raz). Na przykład: $options = array ( 'persistent' => true, 'ssl' => true, ); $mdb2 =& $MDB2::factory($dsn, $options);
lub $mdb2->setOption('portability', MDB2_PORTABILITY_NONE);
21
PEAR. Programowanie w PHP
Pełną listę dostępnych opcji można znaleźć w dokumentacji interfejsu API pakietu MDB2, dostępnej pod adresem: http://pear.php.net/package/MDB2/docs/. Przyjrzyjmy się teraz dwóm najważniejszym opcjom.
Opcja „persistent” Jest to opcja logiczna, która określa, czy utworzone połączenie powinno być połączeniem trwałym, czy też nie. W witrynie mysql.com można znaleźć bardzo dobry artykuł na temat zalet i wad korzystania z trwałych połączeń z bazą danych w systemie MySQL. Należy zajrzeć pod adres: http://www.mysql.com/news-and- events/ newsletter/2002-11/a0000000086.html.
Domyślnie przypisywana jest jej wartość false (fałsz), określająca, że połączenie nie powinno być trwałe. Podczas tworzenia obiektu można zmienić to domyślne ustawienie: $options = array ( 'persistent' => true ); $mdb2 =& MDB2::factory($dsn, $options);
Natomiast metoda setOption() pozwala definiować opcje już po utworzeniu obiektu: $mdb2->setOption('persistent', true);
Opcja „portability” Pakiet MDB2 próbuje poradzić sobie jakoś z różnicami w sposobie implementowania pewnych funkcji baz danych przez różne systemy RDBMS. Opcja portability pozwala określić, w jakim zakresie warstwa bazy danych ma dbać o przenośność naszych skryptów. Różne wartości opcji portability definiowane są jako stałe zaczynające się od MDB2_PORTABILITY_*, a domyślna wartość opcji to MDB2_PORTABILITY_ALL i oznacza „zrób wszystko, co tylko możliwe, by zagwarantować przenośność skryptów”. Pełną listę stałych dla opcji portability oraz ich opis można znaleźć pod adresem: http://pear.php.net/manual/en/package.database. ¦mdb2.intro-portability.php. Można definiować kilka wartości opcji portability, jak również definiować wyjątki za pomocą operatorów bitowych — dokładnie w taki sposób, w jaki definiuje się zasady raportowania błędów w języku PHP. To na przykład ustawienie poleca dbać o przenośność w pełnym zakresie, z wyjątkiem stosowania małych liter: MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_LOWERCASE
Jeśli natomiast nie interesują nas wszystkie funkcje przenośności oferowane przez MDB2, a chcielibyśmy tylko usunąć z wyniku spacje i zmienić puste wartości na łańcuchy null, to należy użyć opcji: 22
Rozdział 1. • MDB2
MDB2_PORTABILITY_RTRIM | MDB2_PORTABILITY_EMPTY_TO_NULL
Prawdopodobnie najlepszym rozwiązaniem będzie jednak pozostawienie domyślnego ustawienia MDB2_PORTABILITY_ALL. W ten sposób, w przypadku jakichś problemów z aplikacją, będziemy wiedzieli, że kod związany z sięganiem do bazy danych został dodatkowo sprawdzony pod kątem współpracy z różnymi systemami baz danych.
Definiowanie trybu pobierania danych Kolejnym ustawieniem, które warto zdefiniować na początku, jest tryb pobierania danych (ang. fetch mode) lub też innymi słowy — sposób, w jaki dane te będą nam zwracane. Możemy otrzymywać dane w postaci uporządkowanej listy (ustawienie domyślne), w formie tablic asocjacyjnych lub w formie obiektów. Oto przykłady definiowania trybu pobierania danych: $mdb2->setFetchMode(MDB2_FETCHMODE_ORDERED); $mdb2->setFetchMode(MDB2_FETCHMODE_ASSOC); $mdb2->setFetchMode(MDB2_FETCHMODE_OBJECT);
Oczywiście najbardziej przyjaznym dla człowieka i najczęściej stosowanym trybem pobierania danych będzie pobieranie ich w tablicach asocjacyjnych, ponieważ wyniki umieszczane są wtedy w tablicy, której klucze odpowiadają nazwom kolumn tabeli w bazie danych. Aby pokazać, na czym polega różnica, przyjrzyjmy się różnym sposobom pobierania danych zawartych w naszym zbiorze wyników: echo $result[0]; // uporządkowana lub indeksowana tablica, domyślnie w MDB2 echo $result['name']; // tablica asocjacyjna echo $result->name; // obiekt
Jest jeszcze jeden tryb pobierania danych, o nazwie MDB2_FETCHMODE_FLIPPED (tryb odwrócony). Jest on cokolwiek nietypowy i jego działanie zostało w dokumentacji API pakietu MDB2 opisane w następujący sposób: „W przypadku wyników wielowymiarowych, zazwyczaj pierwszy poziom tablic jest numerem wiersza, podczas gdy drugi poziom jest indeksowany według nazwy bądź numeru kolumny. Tryb MDB2_FETCHMODE_FLIPPED odwraca ten porządek, w efekcie czego pierwszy poziom tablic będzie nazwą kolumny, a drugi poziom — numerem wiersza”.
Rozłączanie się z bazą danych Aby rozłączyć się z bazą danych, należy użyć następującego kodu: $mdb2->disconnect();
Niemniej nawet jeśli sami nie zaznaczymy w kodzie, że chcemy rozłączyć się z bazą danych, pakiet MDB2 zrobi to za nas automatycznie w swoim destruktorze. 23
PEAR. Programowanie w PHP
Korzystanie z MDB2 Gdy już połączymy się z bazą danych i określimy odpowiednie opcje połączenia oraz tryb pobierania danych, będzie można przystąpić do wykonywania zapytań. Na potrzeby przykładów prezentowanych w tym rozdziale założymy, że mamy tabelę o nazwie people (ludzie), z kolumnami id (identyfikator), name (imię), family (nazwisko) i birth_date (data_urodzenia): id
name
family
birth_date
1
Eddie
Vedder
1964-12-23
2
Mike
McCready
1996-04-05
3
Stone
Gossard
1966-07-20
Przykład Oto prosty przykład pokazujący, jak korzystać z MDB2. W dalszej części opowiemy o wszystkim szczegółowo, teraz jednak rzućmy okiem na kod, starając się zrozumieć w ogólnym zarysie, jak on działa.
Wykonywanie zapytań Aby wykonać zapytanie, można użyć metod query() lub exec(). Metoda query() zwraca obiekt wyniku MDB2_Result, natomiast metoda exec() zwraca liczbę wierszy w tabelach zmienionych przez zapytanie. Dlatego też metoda exec() będzie bardziej odpowiednia w przypadku zapytań, które modyfikują dane. Mimo iż metoda query() pozwala wykonać praktycznie każdą operację na bazie danych, MDB2 oferuje również inne metody, które lepiej nadają się do określonych, często wykonywanych operacji.
Pobieranie danych W przedstawionym wyżej przykładzie można znaleźć następujące wiersze: $sql = 'SELECT * FROM people'; $result = $mdb2->query($sql);
Zmienna $result jest obiektem wyniku typu MDB2_Result lub też, ściślej mówiąc, zależną od konkretnego sterownika bazy danych klasą, która jest rozszerzeniem typu MDB2_Result, na przykład MDB2_Result_mysql. Do przeglądania zbioru wyników można natomiast użyć w pętli metody fetchRow(), która pobiera pojedyncze wiersze. while ($row = $result->fetchRow()) { echo $row['name'], '
'; }
Za każdym razem gdy przywołujemy metodę fetchRow(), sprawdzi ona następny rekord i zwróci odwołanie (ang. reference) do zawartych w nim danych. Oprócz metody fetchRow() jest jeszcze parę innych metod z grupy fetch*(): fetchAll() zwraca od razu tablicę zawierającą wszystkie rekordy. fetchOne() jeśli zostanie przywołana bez żadnych parametrów, zwraca wartość pierwszego pola z bieżącego wiersza. Natomiast jeśli prześlemy jej odpowiednie parametry, będziemy mogli za jej pomocą pobrać dowolne pole z dowolnego wiersza. Na przykład wywołanie fetchOne(1,1) zwróci w naszym przykładzie imię Mike, czyli drugą kolumnę drugiego wiersza.
25
PEAR. Programowanie w PHP
fetchCol($column) zwraca pola w kolumnie o numerze $column dla wszystkich wierszy lub też pierwszą kolumnę, jeśli parametr $column nie zostanie określony. Warto zwrócić uwagę, że metody fetchRow() i fetchOne() przenoszą wewnętrzny wskaźnik do bieżącego rekordu, podczas gdy metody fetchAll() i fetchCol() przeniosą go na koniec zbioru wyników. Można również skorzystać z wywołania $result->nextResult(), by z jego pomocą przenieść wskaźnik do następnego rekordu w zbiorze wyników lub z wywołania $result-> seek($rownum), by przenieść wskaźnik do wiersza określonego w parametrze $rownum. W razie wątpliwości można też skorzystać z wywołania $result->rowCount(), by sprawdzić, w którym miejscu zbioru wyników aktualnie znajduje się nasz wskaźnik. Można również ustalić liczbę wierszy i liczbę kolumn w zbiorze wyników: $sql = 'SELECT * FROM people'; $result = $mdb2->query($sql); echo $result->numCols(); // wyświetla 4 echo $result->numRows(); // wyświetla 3
Skróty ułatwiające pobieranie danych Bardzo często znacznie wygodniej jest pobierać dane w formie tablicy asocjacyjnej (lub zdefiniować je jako preferowany tryb pobierania danych) i nie kłopotać się szczegółami technicznymi związanymi z przeglądaniem zbioru wyników. Pakiet MDB2 oferuje dwa zestawy metod umożliwiających pobieranie danych „na skróty”: metody z grupy query*() i metody z grupy get*(). Za ich pomocą następujące czynności wykonuje się przy użyciu pojedynczego wywołania metody: 1. Wykonywanie zapytania 2. Pobieranie zwracanych danych 3. Zwalnianie zasobów wykorzystywanych przez pobrany wynik
Skróty metod query*() W tej grupie mamy do dyspozycji metody queryAll(), queryRow(), queryOne() i queryCol(), które odpowiadają analogicznym metodom z grupy fetch*(), omówionym wyżej. Oto przykład ilustrujący, czym różni się korzystanie z metod z grupy query*() od metod fetch*(): // instrukcja SQL
$sql = 'SELECT * FROM people'; // jeden ze sposobów pobierania wszystkich danych
$result = $mdb2->query($sql); $data = $result->fetchAll(); $result->free(); // nie wymagane // krótszy sposób
$data = $mdb2->queryAll($sql);
26
Rozdział 1. • MDB2
W obu przypadkach, jeśli wyświetlimy za pomocą metody print_r() zawartość zmiennej $data i korzystamy z trybu pobierania używającego tablic asocjacyjnych, otrzymamy: Array ( [0] => Array ( [id] => 1 [name] => Eddie [family] => Vedder [birth_date] => 1964-12-23 ) [1] => Array ( [id] => 2 [name] => Mike [family] => McCready [birth_date] => 1966-04-05 ) ... )
Skróty metod get*() Oprócz metod z grupy query*() można jeszcze korzystać ze skrótów oferowanych przez metody get*(). Metody z grupy get*() generalnie zachowują się w taki sam sposób jak metody z grupy query*(), niemniej pozwalają również na stosowanie w zapytaniach parametrów. Rozważmy następujący przykład: $sql = 'SELECT * FROM people WHERE id=?'; $mdb2->loadModule('Extended'); $data = $mdb2->getRow($sql, null, array(1));
W tym przykładzie znak zapytania pojawiający się w instrukcji jest zmienną, która zostanie zastąpiona wartością przesłaną w trzecim parametrze metody getRow(). Można również korzystać z parametrów posiadających własne nazwy: $sql = 'SELECT * FROM people WHERE id=:the_id'; $mdb2->loadModule('Extended'); $data = $mdb2->getRow( $sql, null, array('the_id' => 1) );
Warto zwrócić uwagę na to, że metody get*() są częścią modułu Extended pakietu MDB2, co oznacza, że aby były dostępne, należy je najpierw załadować używając polecenia $mdb2-> loadModule('Extended'). Dzięki ładowaniu modułów mamy możliwość przeciążania obiektów, czego nie można było robić przed pojawieniem się PHP5. Dlatego w wersji PHP4 języka, aby sięgnąć do metod modułu Extended, trzeba je było przywoływać w następujący sposób: $mdb2->extended->getAll($sql);
27
PEAR. Programowanie w PHP
Natomiast obecnie wystarczy wpisać: $mdb2->getAll($sql);
getAsoc() Kolejną użyteczną metodą z grupy get*(), która nie ma bezpośredniego odpowiednika w grupie fetch*() ani w grupie query*(), jest metoda getAssoc(). Zwraca ona wyniki w podobny sposób jak metoda getAll(), niemniej kluczami w tablicy wyników będą wartości z pierwszej kolumny tabeli. Dodatkowo zbiór wyników zawiera (w naszym przykładzie) tylko dwie kolumny, ponieważ jedną wykorzystaliśmy już jako indeks tablicy. Druga kolumna zostanie zwrócona w formie łańcucha (a nie w formie tablicy z jednym elementem). Oto kilka przykładów ilustrujących różnice pomiędzy metodami getAll() i getAssoc(): $sql = 'SELECT id, name FROM people'; $mdb2->loadModule('Extended'); $data = $mdb2->getAll($sql);
Metoda getAll() zwróci uporządkowaną tablicę, w której każdy z elementów będzie tablicą asocjacyjną zawierającą wszystkie pola. Array ( [0] => Array ( [id] => 1 [name] => Eddie ) [1] => Array ( [id] => 2 [name] => Mike ) ... )
Jeśli wykonalibyśmy to samo zapytanie za pomocą metody getAssoc(), na przykład wpisując w kodzie $data=$mdb2->getAssoc($sql), to otrzymalibyśmy następujący wynik: Array ( [1] => Eddie [2] => Mike [3] => Stone )
Jeśli zapytanie zwraca więcej niż dwie kolumny, to każdy z wierszy będzie tablicą, a nie skalarem. Oto kod wykonujący takie zapytanie: $sql = 'SELECT id, name, family FROM people'; $mdb2->loadModule('Extended'); $data = $mdb2->getAssoc($sql);
A oto wynik: Array ( [1] => Array ( [name] => Eddie [family] => Vedder ) ... )
28
Rozdział 1. • MDB2
Typy danych Aby poradzić sobie z problemem wynikającym z tego, że różne systemy bazy danych obsługują różne typy danych dozwolone dla pól tabel, pakiet MDB2 dostarcza własnego, uniwersalnego zestawu typów danych. Programista może korzystać z typów danych oferowanych przez MDB2 i pozwolić, by sam pakiet MDB2 zadbał o przenośność typów danych między różnymi systemami RDBMS, po prostu mapując swoje typy na typy odpowiedniego systemu baz danych. Oto lista typów danych oferowanych przez MDB2 i ich domyślne wartości: $valid_types = array ( 'text' => '', 'boolean' => true, 'integer' => 0, 'decimal' => 0.0, 'float' => 0.0, 'timestamp' => '1970-01-01 00:00:00', 'time' => '00:00:00', 'date' => '1970-01-01', 'clob' => '', 'blob' => '', )
Więcej informacji na temat typów danych MDB2 można znaleźć w pliku datatypes.html, znajdującym się w podkatalogu docs w katalogu, w którym zainstalowaliśmy PEAR. Dokument ten jest również dostępny w internecie, w witrynie repozytorium PEAR CVS: http://cvs.php.net/viewcvs.cgi/pear/MDB2/docs/datatypes.html?view=co
Określanie typów danych We wszystkich metodach służących do pobierania danych, którym się do tej pory przyglądaliśmy (z grup query*(), fetch*() i get*()), można było określać typ zbioru wyników, który chcemy otrzymać, i pakiet MDB2 automatycznie konwertował wartości na odpowiedni typ danych. Na przykład metodzie query() można było przesłać jako drugi parametr tablicę zawierającą oczekiwane typy danych dla pól: $sql = 'SELECT * FROM people'; $types = array(); $result = $mdb2->query($sql, $types); $row = $result->fetchRow(); var_dump($row);
W tym przypadku tablica typów $types była pusta, więc metoda zachowała się w domyślny sposób (nie wykonując żadnej konwersji typów danych) i wszystkie wyniki zostały zwrócone w formie łańcuchów. Przykład ten zwraca następujące dane:
29
PEAR. Programowanie w PHP
array(2) { ["id"] => string(1) "1" ["name"]=> string(5) "Eddie" ... }
Możemy jednak zażyczyć sobie, aby pierwsze pole w każdym zwracanym rekordzie było typu integer (całkowitoliczbowego), a drugie typu text (tekstowego), definiując tablicę $type w następujący sposób: $types = array('integer', 'text');
W tym przypadku otrzymamy następujący wynik: array(2) { ["id"]=> int(1) ["name"]=> string(5) "Eddie" ... }
Podczas określania typów można również użyć tablicy asocjacyjnej, w której kluczami będą poszczególne pola tabeli. W takim przypadku można nawet pominąć niektóre pola, jeśli nie chcemy dla nich definiować typów. Oto przykłady poprawnych definicji takiej tablicy: $types = array( 'id' => 'integer', 'name' => 'text' ); $types = array('name'=>'text'); $types = array('integer');
Określanie typów danych podczas pobierania wyników Jeśli nie chcemy określać typów danych już podczas przywoływania metody query(), możemy to zrobić później. Zanim rozpoczniemy pobieranie danych, możemy określić typy danych używając metody setResultTypes(). // wykonujemy zapytanie
$sql = 'SELECT * FROM people'; $result = $mdb2->query($sql); // pobieramy pierwszy wiersz bez konwertowania typów
$row = $result->fetchRow(); var_dump($row['id']); // wynik będzie następujący: string(1) "1" // określamy typy
$types = array('integer'); $result->setResultTypes($types);
30
Rozdział 1. • MDB2
// wszystkie następne pobrania będą konwertować // pierwszą kolumnę na liczbę całkowitą
$row = $result->fetchRow(); var_dump($row['id']); // wynik będzie następujący: int(2)
Określanie typów danych dla metod get*() i query*() Wszystkie metody z grup get*() i query*(), które omawialiśmy wyżej w tym rozdziale, pozwalają na określanie w drugim przesyłanym im parametrze, jakie typy danych mają zwracać. Dokładnie tak samo jak w metodzie query(). Możemy definiować parametr przesyłający typy danych nie tylko jako tablicę $types = array('integer'), ale również jako łańcuch $types = 'integer'. Jest to wygodne, kiedy pracujemy z metodami, które mają zwracać tylko jedną kolumnę danych, takimi jak getOne(), queryOne(), getCol() czy queryCol(), niemniej należy bardzo ostrożnie korzystać z tej techniki w przypadku metod typu *All() i *Row(), ponieważ wówczas łańcuch w parametrze podającym typ określi typ dla wszystkich pól w zbiorze danych.
Ujmowanie wartości i identyfikatorów w cudzysłowy Różne systemy RDBMS zarządzające relacyjnymi bazami danych stosują różne konwencje ujmowania danych w cudzysłowy (na przykład tam gdzie jedne używają pojedynczych cudzysłowów ', inne stosują podwójne cudzysłowy "). Nie ma też żadnej powszechnie przyjętej konwencji ujmowania w cudzysłowy typów danych. Na przykład w systemie MySQL możemy (jeśli chcemy) ujmować wartości typu integer (całkowitoliczbowe) w cudzysłowy, natomiast w innych systemach może to być zabronione. Z tego powodu właśnie lepiej pozostawić obsługę cudzysłowów warstwie abstrakcji bazy danych, ponieważ pakiet MDB2 „wie”, jakie konwencje stosują tutaj różne systemy baz danych. Pakiet MDB2 oferuje specjalną metodę quote(), która umożliwia ujmowanie danych w cudzysłowy, i metodę quoteIdentifier() pozwalającą na ujmowanie w cudzysłowy nazw baz danych, tabel i pól. Wszelkie cudzysłowy wstawione przez pakiet MDB2 będą odpowiednie dla wykorzystywanego systemu RDBMS. Oto przykład: $sql = 'UPDATE %s SET %s=%s WHERE id=%d'; $sql = sprintf( $sql, $mdb2->quoteIdentifier('people'), $mdb2->quoteIdentifier('name'), $mdb2->quote('Eddie'), // domniemany typ danych $mdb2->quote(1, 'integer') // wyraźnie określony typ danych );
Jeśli teraz w bazie MySQL wykonamy polecenie echo $sql, otrzymamy: UPDATE `people` SET `name`='Eddie' WHERE id=1
31
PEAR. Programowanie w PHP
Natomiast w bazie SQLite ten sam kod zwróci: UPDATE "people" SET "name"='Eddie' WHERE id=1
Jak można było zauważyć, przyglądając się poprzednim przykładom, metoda quote() pozwala na przesłanie jej drugiego, opcjonalnego parametru, określającego typ danych (oczywiście typ MDB2), który ma zostać ujęty w cudzysłowy. Jeśli pominiemy drugi parametr, MD2 postara się zgadnąć typ danych.
Iteratory Pakiet MDB2 korzysta ze standardowej biblioteki PHP (Standard PHP Library http://php.net/spl) i implementuje interfejs Iterator, który pozwala na znacznie prostsze i wygodniejsze przeglądanie wyników zapytania: foreach ($result as $row) { var_dump($row); }
W każdej kolejnej iteracji (powtórzeniu) pętli zmienna $row zawierać będzie kolejny rekord (wiersz), przechowywany w formie tablicy. Prezentowany tu kod jest równoważny przywołaniu w pętli metody fetchRow(): while ($row = $result->fetchRow()) { var_dump($row); }
Aby skorzystać z zalet interfejsu iteratorów Iterator, konieczne będzie załączenie pliku Iterator.php z katalogu pakietu MDB2, przy użyciu metody loadFile(): MDB2::loadFile('Iterator');
Po załadowaniu tego pliku będzie można przesyłać metodzie query() jako czwarty parametr nazwę klasy Iterator: $query = 'SELECT * FROM people'; $result = $mdb2->query($query, null, true, 'MDB2_BufferedIterator');
Pakiet MDB2 oferuje dwie klasy iteratorów: MDB2_Iterator — Klasa ta implementuje klasę Iterator biblioteki SPL i najlepiej sprawdza się w pracy z niebuforowanymi zbiorami wyników. MDB2_BufferedIterator — Ta klasa jest rozszerzeniem klasy MDB_Iterator i implementuje interfejs SeekableIterator. Podczas pracy z buforowanymi zbiorami wyników (domyślnymi w MDB2) lepiej jest korzystać z klasy MDB2_BufferedIterator, ponieważ oferuje ona parę dodatkowych przydatnych w tej sytuacji metod, takich jak count() czy rewind().
32
Rozdział 1. • MDB2
Wyszukiwanie błędów Pakiet MDB2 umożliwia programiście przechowywanie listy wszystkich zapytań wykonywanych w danej instancji obiektu MDB2, znacznie ułatwiając wyszukiwanie błędów w naszych aplikacjach. Aby włączyć tę opcję wyszukiwania błędów (debugowania), trzeba przypisać opcji debug dodatnią liczbę całkowitą. $mdb2->setOption('debug', 1);
Po włączeniu jej w ten sposób będziemy mogli w dowolnym momencie sięgnąć do zbieranych przez pakiet MDB2 danych debugowania: $mdb2->getDebugOutput();
Można również włączyć opcję log_line_break, która pozwala określić, w jaki sposób będą oddzielane od siebie dane zapisywane w dzienniku debugowania. Domyślnie odseparowane są znakiem nowego wiersza \n. Oto prosty przykład, w którym włączona została opcja debug i określony odpowiedni separator, następnie wykonano kilka zapytań, a na koniec pobrano nieuporządkowaną listę tych zapytań z zanotowanych w dzienniku danych debugowania. $mdb2->setOption('debug', 1); $mdb2->setOption('log_line_break', "\n\t"); $sql = 'SELECT * FROM people'; $result = $mdb2->query($sql); $sql = 'SELECT * FROM people WHERE id = 1'; $result = $mdb2->query($sql); $sql = 'SELECT name FROM people'; $result = $mdb2->query($sql); $debug_array = explode("\n\t", trim($mdb2->getDebugOutput())); echo '
), jak i komórkami nagłówka tabeli (ujmowanymi w znaczniki | ). Elementy te tworzą podstawowy szkielet tabeli, tak jak przedstawiamy w poniższym przykładzie:
Oglądając ten kod, już na pierwszy rzut oka łatwo stwierdzić, że ręczne tworzenie tabel HTML byłoby bardzo żmudnym zadaniem. Nawet w języku PHP, gdzie tabele wraz z danymi można tworzyć w pętlach, kod staje się szybko bardzo bałaganiarski, ponieważ musimy bez- 64 Rozdział 2. • Wyświetlanie danych pośrednio zajmować się wszystkimi znacznikami, wyliczać, gdzie powinny znaleźć się znaczniki zamykające, itp. W tej sytuacji z pomocą przychodzi pakiet HTML_Table, będący obiektowym obudowaniem kodu PHP zajmującego się tworzeniem i modelowaniem tabel HTML. Za pomocą pakietu HTML_Table tabele tworzy się bardzo prosto: include_once 'HTML/Table.php'; $table = new HTML_Table(); $table->addRow(array("raz", "dwa", "trzy"), null, "th"); $table->addRow(array("raz", "dwa", "trzy")); echo $table->toHtml(); Zaczynamy od utworzenia nowej instancji klasy HTML_Table. Aby użyć atrybutów określających formatowanie całej tabeli, przesyłamy je konstruktorowi klasy — zajmiemy się tym jeszcze później. Gdy już uzyskamy nasz obiekt tabeli, będziemy mogli zacząć dodawać do niego wiersze. Pierwszym parametrem zajmującej się tym funkcji addRow() jest tabela zawierająca dane, które chcemy przechowywać w wierszu, drugi parametr umożliwia określanie dowolnych atrybutów formatujących tworzony wiersz, a trzeci atrybut określa, czy komórki tego wiersza powinny, czy też nie, używać znaczników komórek nagłówka | . Zależy nam na tym, aby pierwszy wiersz będący nagłówkiem tabeli używał znaczników | , natomiast kolejne nie — powinny zawierać zwykłe komórki tabeli. Tworzenie prostego kalendarza za pomocą HTML_Table Teraz, gdy już zobaczyliśmy, jak tworzyć tabelę za pomocą pakietu HTML_Table, możemy przyjrzeć się jakiemuś bardziej praktycznemu przykładowi tabeli. Zaczniemy od przygotowania prostego kalendarza dla miesiąca. Nasz kalendarz będzie prezentował listę dni w miesiącu oraz wyświetlał tygodnie i dni w formacie tabeli. Później dodamy do kalendarza jeszcze kilka opcji; na razie jednak skorzystamy z pakietów PEAR::Calendar i HTML_Table, by za ich pomocą przygotować kalendarz na bieżący miesiąc. include_once 'HTML/Table.php'; include_once 'Calendar/Month/Weekdays.php'; $table = new HTML_Table(); $Month = new Calendar_Month_Weekdays(date('Y'), date('n')); $Month->build(); while ($Day = $Month->fetch()) { if ($Day->isFirst()) { 65 PEAR. Programowanie w PHP if (is_array($week)) { $table->addRow($week); } $week = array(); } $week[] = $Day->isEmpty() ? "" : $Day->thisDay(); } $table->addRow($week); $table->setColAttributes(0, 'bgcolor="#CCCCCC"'); $table->setColAttributes(6, 'bgcolor="#CCCCff"'); $table->updateAllAttributes('align="center"'); echo $table->toHTML(); W kodzie tym po załączeniu niezbędnych pakietów tworzymy nową instancję klasy HTML_Table. Aby zdefiniować obramowania tabeli lub jakikolwiek inny atrybut, należałoby przesłać odpowiedni atrybut do konstruktora klasy HTML_Table. Opiszemy to dokładniej w kolejnym przykładzie. Szczegółowy opis korzystania z klasy Calendar dostępnej w repozytorium PEAR wychodzi poza zakres tego rozdziału. Mówiąc skrótowo jednak, używa się jej w ten sposób, że tworzymy nowy obiekt, który zawiera informacje na temat bieżącego miesiąca, po czym przeglądamy poszczególne dni w pętli, zajmując się każdym dniem z osobna. W naszym przykładzie dodamy każdy dzień do pomocniczej tablicy, a gdy osiągniemy pierwszy dzień kolejnego tygodnia, dodawać będziemy do tabeli, a pomocniczą tablicę opróżnimy, by zrobić miejsce dla kolejnego tygodnia. Niektóre dni pierwszego i ostatniego tygodnia nie będą należeć do bieżącego miesiąca — potraktujemy je jako puste i nie będziemy dodawać ich do kalendarza. Gdy już zakończymy przeglądanie tygodni w pętli, dodamy do naszej tabeli ostatni konstruowany tydzień. Teraz, gdy już wszystkie dane zostały dodane do tabeli, możemy aktualizować i dodać do niej atrybuty dla wierszy lub kolumn, by wzbogacić tabelę o kilka elementów formatowania. Pakiet HTML_Table oferuje funkcje umożliwiające określanie atrybutów wierszy, kolumn lub indywidualnych komórek. Funkcje te to setRowAttributes() dla atrybutów wierszy, setColAttributes() dla atrybutów kolumn i setCell Attributes() dla atrybutów komórek. Kiedy określamy atrybuty części składowych naszej tabeli, należy pamiętać, że wszelkie formatowanie komórki zostanie zmienione, jeśli na wierszu, którego ta komórka jest częścią, zastosujemy funkcję setRowAttribute(). Aby uniknąć tego problemu, trzeba będzie przywoływać funkcje „aktualizujące”, które później aktualizować będą atrybuty komórki. W tym przykładzie, po dodaniu kolorów zaktualizujemy wszystkie komórki tabeli, by je wycentrować. Nie zmieni to wcześniejszego formatowania zastosowanego na nich. 66 Rozdział 2. • Wyświetlanie danych Definiowanie pojedynczych komórek Trzeba naszego szczęścia, by zaraz po przygotowaniu kalendarza dowiedzieć się, że ktoś z kierownictwa zalecił wprowadzenie w kalendarzu poprawek, tak aby kalendarz wyróżniał nie tylko weekendy, ale również wszystkie pozostałe święta w miesiącu. W celu wprowadzenia tej modyfikacji potrzebujemy możliwości bardziej precyzyjnego sięgania do tabeli, dlatego zamiast dodawać do niej miesiące, będziemy teraz dodawać każdy dzień z osobna. Pociągnie to oczywiście za sobą konieczność przeprojektowania sposobu wprowadzania danych do tabeli. Aby ustalić, które dni w miesiącu są dniami świątecznymi, użyjemy pakietu Date_Holidays dostępnego w repozytorium PEAR. Podczas przeglądania w pętli kolejnych dni miesiąca będziemy sprawdzać, czy bieżący dzień jest świętem, i jeśli tak, odpowiednio formatować jego komórkę tabeli. W prawdziwej aplikacji kalendarza dodawalibyśmy jeszcze do numeru dnia nazwę święta. Pakiet Date_Holidays również i tutaj przychodzi nam z pomocą, w tym prostym przykładzie ograniczymy się jednak tylko do podświetlenia komórki dnia świątecznego. require_once 'HTML/Table.php'; require_once 'Calendar/Month/Weekdays.php'; require_once 'Date/Holidays.php'; $tableAttrs = array('border' => "2"); $table = new HTML_Table($tableAttrs); $Germany =& Date_Holidays::factory('Germany', 2005); $Month = new Calendar_Month_Weekdays(2005, 12); $Month->build(); $table->addRow(array('M', 'T', 'W', 'T', 'F', 'S', 'S'), null, "th"); while ($Day = $Month->fetch()) { if ($Day->isFirst()) { $row++; $col = 0; } if (!$Day->isEmpty()) { $table->setCellContents($row, $col, $Day->thisDay()); $t = sprintf('%4d-%02d-%02d', $Day->thisYear(), $Day->thisMonth(), $Day->thisDay()); if ($Germany->isHoliday($t)) { $table->setCellAttributes($row,$col, 'bgcolor="red"'); } 67 PEAR. Programowanie w PHP } $col++; } $table->setRowAttributes(0, 'bgcolor="#CC99FF"'); $table->updateAllAttributes('align="center"'); $table->setCaption("Holidays"); echo $table->toHTML(); Pierwsza zmiana, którą łatwo zauważyć, to dodanie podczas tworzenia tabeli atrybutów obramowania. Atrybut obramowania border został dodany do głównego znacznika tabeli. W tym przykładzie pojawia się kilka nowych funkcji. Najważniejsza z nich to funkcja setCellContens(). Jak można domyślić się po jej nazwie, funkcja ta wymaga przesłania jej numeru wiersza i kolumny, określających położenie komórki, i następnie wypełnia komórkę odpowiednimi danymi. Dodaliśmy do tabeli również nagłówek prezentujący podświetlone pierwsze litery dni tygodnia oraz nagłówek samej tabeli. Obecnie nasz gotowy kalendarz wyświetla kalendarz dla bieżącego miesiąca, w którym wszystkie święta są podświetlone na czerwono. Rysunek 2.1. Tabela prezentująca dni świąteczne w wybranym miesiącu 68 Rozdział 2. • Wyświetlanie danych Pakiet HTML_Table_Matrix rozszerzający możliwości pakietu HTML_Table Pakiet HTML_Table_Matrix (HTM) jest pakietem podrzędnym względem HTML_Table, rozszerzającym jego możliwości, tak by ułatwić formatowanie danych prezentowanych w tabelach. Podstawowa zaleta korzystania z pakietu HTM polega na tym, że zamiast wypełniać każdy wiersz z osobna za pomocą funkcji addRow(), możemy po prostu określić, ile kolumn powinna mieć nasza tabela, przesłać nasze dane i pozwolić pakietowi HTML_Table_Matrix, aby sam je posortował. Pakiet HTML_Table_Matrix został zaprojektowany za pomocą sterowników wypełniania tabeli Filler, które zajmują się porządkowaniem danych umieszczonych w tabeli. Sterowniki te aktualnie umożliwiają wypełnianie tabeli w naturalny sposób: od lewej do prawej i z góry do dołu, a ponadto z dołu do góry, od prawej do lewej, a także np. ruchem spiralnym w kierunku odwrotnym do ruchu wskazówek zegara, poczynając od środka tabeli, i jeszcze na kilka innych sposobów. Każdy sterownik wypełniania tabeli Filler po prostu oferuje metodę next(), z której klasa renderująca tabelę będzie korzystać, by ustalić, gdzie należy umieścić kolejny element danych. Mimo iż raczej rzadko rozpoczyna się wypełnianie tabeli począwszy od środkowej komórki, został jednak przygotowany odpowiednio elastyczny mechanizm wypełniania, który powinien sprostać wszystkim przyszłym potrzebom. Miejsce składowania danych, które mają trafić do tabeli, jest badane tylko raz, aby pobrać dane. W naszym przykładzie użyjemy pakietu Services_Yahoo, by za jego pomocą pobrać z usługi Yahoo Image Search szesnaście obrazków, które zostaną wyświetlone w tabeli. include_once 'HTML/Table/Matrix.php'; include_once 'Services/Yahoo/Search.php'; $table = new HTML_Table_Matrix(array('border' => "2")); $rows = 4; $cols = 4; $term = 'Pears'; $search = Services_Yahoo_Search::factory("image"); $search->setQuery($term); $search->setResultNumber($rows * $cols); $results = $search->submit(); foreach($results as $image) { $data[] = ""; } 69 PEAR. Programowanie w PHP $table->setTableSize($rows, $cols); $table->setFillStart(1, 0); $table->setData($data); $table->addRow(array("Obrazki wyszukane dla '$term'"), "colspan='$cols'", "th"); $f = HTML_Table_Matrix_Filler::factory("LRTB", $table); $table->accept($f); echo $table->toHtml(); Po dołączeniu obu pakietów, z których będziemy korzystać w tym przykładzie, definiujemy kilka zmiennych, które przechowywać będą informacje na temat wyników naszego wyszukiwania. Potrzebna nam będzie tabela zawierająca cztery wiersze i cztery kolumny, w której będziemy przechowywać obrazki znalezione dla terminu wyszukiwania „Pears” („Gruszki”). Gdy już otrzymamy z powrotem od Yahoo dane z wynikami wyszukiwania, określamy rozmiar naszej tabeli, bazując na predefiniowanych zmiennych. Będziemy musieli dodać do tabeli nagłówek, tak więc zaczynamy jej wypełnianie od pojedynczego wiersza znajdującego się u góry tabeli. Robimy to korzystając z funkcji setFillstart(). Ponieważ pakiet HTML_Table_Matrix jest pakietem podrzędnym względem pakietu HTML_Table, więc mimo iż metoda setData dodaje do tabeli od razu wszystkie dane, nadal możemy definiować i zmieniać dane znajdujące się w pojedynczych wierszach i komórkach. Tak właśnie postąpimy w przypadku wiersza nagłówka. Kiedy tworzymy instancję pakietu Filler, musimy przesłać metodzie konstruktora obiekt tabeli, jak również wykorzystywany sterownik wypełniający tabelę. Aby wypełnić tabelę od lewej do prawej i z góry na dół, użyjemy parametru LRTB. Następnie wyświetlimy gotową tabelę. Rysunek 2.2. Wynik wyszukiwania za pomocą usługi Yahoo Image Search 70 Rozdział 2. • Wyświetlanie danych Arkusze kalkulacyjne Excela Generowanie arkuszy kalkulacyjnych to zadanie, które regularnie zlecane jest wielu programistom. Niezależnie od tego, czy lubimy tę pracę, czy nie, trzeba jednak przyznać, że arkusze kalkulacyjne Excela są obecnie standardem prezentowania danych przedstawionych w tabelach i wymieniania się nimi. Dzięki łatwemu w użyciu formatowi oraz szerokiej dostępności różnych programów kompatybilnych z Excelem większość firm decyduje się właśnie na ten format, gdy zachodzi konieczność przygotowywania raportów dla kierownictwa lub wymiany danych między różnymi biurami. Mimo iż istnieje również kilka innych technik generowania plików kompatybilnych z formatem Excela, które zresztą omówimy na końcu tego podrozdziału, jedyną opartą wyłącznie na PHP metodą tworzenia arkuszy w oryginalnym formacie Excela jest klasa Excel_ SpreadSheet_ Writer z repozytorium PEAR. Klasa Excel_SpreadSheet_Writer została przeniesiona do PHP z języka z modułu Perla Spreadsheet::WriteExcel i obsługuje nie tylko wprowadzanie danych, ale również dodawanie formatowania, formuły, wiele arkuszy kalkulacyjnych, używanie obrazków i mnóstwo innych opcji. Klasa Excel_SpreadSheet_Writer nie korzysta z żadnych zewnętrznych komponentów, takich jak COM, więc pakiet ten jest naprawdę uniwersalny i działać będzie na wszystkich platformach, na których funkcjonuje język PHP. Format Excela Format wykorzystywany przez pakiet Excel Spreadsheet Writer nosi nazwę BIFF5 (Binary Interchange File Format, format binarnej wymiany plików). Jest to standard binarny wprowadzony przez program Excel 5 i wszystkie obecne wersje programu Microsoft Excel; również pakiety OpenOffice potrafią interpretować format BIFF5. Format ten jest dość powszechnie akceptowany i rozpoznawany przez wiele programów, lecz brak w nim pewnych funkcji oferowanych przez późniejsze wersje Excela. Microsoft nie dostarczył żadnej oficjalnej dokumentacji formatu BIFF5, niemniej wielu programistów pracujących nad różnymi projektami wymagającymi obsługi tego formatu włożyło sporo pracy w zbadanie go i przygotowanie odpowiedniej dokumentacji. Jednym z najlepszych źródeł dokumentacji jest witryna pakietu biurowego OpenOffice. Odpowiednia dokumentacja została udostępniona pod adresem: http://sc.openoffice.org/excelfileformat.pdf. Jedną z wad pakietu Excel Spreadsheet Writer, na którą skarżą się programiści, jest sposób, w jaki obsługuje ona łańcuchy znaków Unicode. Prawdę powiedziawszy jednak, jest to wada nie tyle pakietu Excel Spreadsheet Writer, ile formatu BIFF5. Podejmowano liczne wysiłki, by dodać do klasy Excel_Spreadsheet_Writer obsługę formatu Unicode, niemniej na obecnym etapie nie planuje się jeszcze dodania żadnego z tych indywidualnie przygotowanych rozwiązań do pakietu Excel Spreadsheet Writer. 71 PEAR. Programowanie w PHP Starsze formaty Microsoftu korzystają z systemu o nazwie OLE, by przy jego użyciu tworzyć dokumenty osadzające formaty różnych programów. Z tego powodu klasa Excel_Spreadsheet_ Writer uzależniona jest od pakietu OLE z repozytorium PEAR, dzięki którego pomocy może obudowywać tworzone przez siebie dokumenty w formacie BIFF5, tak aby były poprawnymi dokumentami Excela. Nasz pierwszy arkusz kalkulacyjny Podstaw korzystania z klasy Spreeadsheet_Excel_Writer można nauczyć się bez trudu. W naszym pierwszym, prostym przykładzie utworzymy arkusz kalkulacyjny i dodamy dane do dwóch jego komórek. Teraz, gdy już wiemy, co zamierzamy zrobić, możemy przyjrzeć się kodowi. require_once 'Spreadsheet/Excel/Writer.php'; $workbook = new Spreadsheet_Excel_Writer(); $worksheet =& $workbook->addWorksheet('Example 1'); $worksheet->write(0, 0, 'Witam wszystkich!'); $worksheet->write(0, 1, 'To jest nasz pierwszy arkusz kalkulacyjny Excela'); $workbook->send('example1.xls'); $workbook->close(); Podczas pracy z klasą Spreadsheet_Excel_Writer mamy do wyboru dwa sposoby zachowywania naszych gotowych arkuszy kalkulacyjnych. Pierwsza opcja, z której korzystamy w tym przykładzie, polega na użyciu metody send() wysyłającej nagłówki Excela (application/vnd.ms-excel), a zaraz po nich dane arkusza kalkulacyjnego do naszej przeglądarki internetowej. W ten sposób otworzymy arkusz kalkulacyjny w oknie przeglądarki, co pozwoli nam obejrzeć jego zawartość lub zachować na komputerze — w zależności od naszych potrzeb i możliwości przeglądarki oraz ustawień komputera. Druga opcja polega na zachowaniu wygenerowanego pliku w naszym lokalnym systemie plików. W tym celu należy konstruktorowi tworzącemu obiekt klasy Spreadsheet_Excel_Writer przesłać po prostu odpowiednią ścieżkę do pliku. W ten sposób, gdy zamkniemy arkusz kalkulacyjny za pomocą funkcji close(), dane zostaną zachowane w zdefiniowanym przez nas pliku. Zastanawiając się, którą z metod wybrać, warto pamiętać, że gdy wysyłamy arkusz kalkulacyjny bezpośrednio do przeglądarki WWW, nie będziemy mogli przesłać wraz z nim żadnego dodatkowego tekstu w formacie HTML. Tak więc rozwiązanie to sprawdza się, gdy zależy nam tylko na przygotowaniu skryptu, którego jedynym zadaniem byłoby dynamiczne serwowanie użytkownikom arkuszy kalkulacyjnych. Niemniej w większości przypadków chcemy zrobić coś więcej: wyświetlić arkusz kalkulacyjny na stronie HTML lub poinformować użytkownika, że zakończyliśmy generowanie arkusza kalkulacyjnego. W takiej sytuacji wygodniej jest zachować arkusz kalkulacyjny w naszym systemie plików i przystąpić do generowania strony HTML, która będzie z niego korzystać. W kolejnych przykładach będziemy dla wygody korzystać właśnie z tej techniki. 72 Rozdział 2. • Wyświetlanie danych Gdy już będziemy mieli gotowy obiekt naszego arkusza kalkulacyjnego, możemy przejść do następnego etapu i wstawić do jego komórek odpowiednie dane. Na koniec zamkniemy nasz „skoroszyt” (zmienna $workbook), co spowoduje skompilowanie danych i odpowiednio do wybranej opcji zapisanie ich w pliku lub wysłanie do przeglądarki. Słowo o komórkach Programista zapisujący arkusz kalkulacyjny może użyć jednej z dwóch metod odnajdywania komórek w arkuszu kalkulacyjnym Excela. Kiedy dodajemy dane do arkusza kalkulacyjnego, określamy pozycję komórki za pomocą współrzędnych X i Y. Ponieważ współrzędne liczone są począwszy od zera, pierwsza komórka określona będzie współrzędnymi 0,0. Aby korzystać z formuł Excela, trzeba z kolei zastosować inną notację, w której kolumny oznaczane są literami, a wiersze numerami. Pierwsza komórka będzie w tej notacji oznaczona jako A1. Różnica pomiędzy oboma wspomnianymi sposobami odwoływania się do komórek stanie się szczególnie wyraźna, gdy zaczynamy pracować z formułami. Na szczęście klasa Speadsheet_ Excel_Writer dostarcza wygodnej funkcji, która dokonuje konwersji z notacji opartej na numerach kolumn i wierszy na drugą notację, opartą na nazwach komórek. $first = 1; $last = 10; for ($i = $first; $i write($i, 1, $i); } $cell1 = Spreadsheet_Excel_Writer::rowcolToCell($first, 1); $cell2 = Spreadsheet_Excel_Writer::rowcolToCell($last, 1); $worksheet1->write($last + 1, 0, "Total ="); $worksheet1->writeFormula($last + 1, 1, "=SUM($cell1:$cell2)"); Jak widać, najpierw używamy numerów kolumn i wierszy, by zapisać dane w odpowiednich komórkach arkusza kalkulacyjnego, a następnie stosujemy statyczną metodę rowcolToCell(), by przekonwertować pozycję definiowana za pomocą numerów i kolumn na adres wykorzystujący do identyfikacji kolumn litery, którego to formatu wymagają formuły Excela. W tym przykładzie wartość łańcucha zapisanego w zmiennej $cell1 to B2, a wartość zapisana w zmiennej $cell2 to B11. Dlatego też formuła wykonywana przez Excel będzie miała postać =SUM(B2:B11). Więcej na temat formuł Excela w dalszej części rozdziału. 73 PEAR. Programowanie w PHP Przygotowywanie strony do wyświetlenia Dostępnych jest wiele różnych opcji umożliwiających modyfikowanie sposobu wyświetlania arkusza kalkulacyjnego. Okazują się one szczególnie użyteczne, jeśli prezentujemy arkusz kalkulacyjny klientowi i chcielibyśmy mieć wpływ na to, w jaki sposób będzie wyświetlony. Wszystkie opcje formatowania mają zastosowanie do całego arkusza kalkulacyjnego. Funkcja Przeznaczenie funkcji $worksheet->setPaper(1); Określa rozmiar strony używając odpowiedniej stałej. $worksheet->setPortrait(); Określają orientację strony, odpowiednio pionową (portret) lub poziomą (pejzaż). $worksheet->setLandscape(); $worksheet->setHeader(); $worksheet->setFooter(); Dodają do każdej strony arkusza kalkulacyjnego odpowiednio nagłówek i stopkę. $worksheet->setMargins(.5); Określa margines naokoło każdej wartości, liczony w calach. Każdy z marginesów można określać z osobna. $worksheet->printArea($firstcol, $firstrow, $lastcol, $lastrow); Definiuje, jaki obszar strony (pomiędzy jakimi kolumnami i wierszami) ma zostać wyświetlony lub wydrukowany. $worksheet->hideGridlines(); Ukrywa siatkę arkusza podczas wyświetlania. $worksheet->fitToPages(2, 2); Określa maksymalną liczbę stron arkusza kalkulacyjnego używanych podczas wyświetlania (drukowania). W tym przypadku dwie strony wszerz i dwie strony w dół. $worksheet->setPrintScale($scale); Określa procentowy współczynnik skalowania arkusza kalkulacyjnego. Domyślnie to 100%. Niweluje działanie opcji „fit to page” (dopasuj do rozmiaru strony). Dodawanie formatowania Teraz, gdy wiemy już, jak tworzyć za pomocą języka PHP pliki Excela, warto zająć się formatowaniem komórek. Inaczej niż w przypadku tworzącego tabele HTML pakietu HTML_Table, gdzie mogliśmy indywidualnie edytować atrybuty każdej komórki, zmieniając jej formatowanie, pakiet Spreeadsheet_Excel_Writer podczas tworzenia i stosowania na komórkach stylów stosuje podejście obiektowe. Aby utworzyć nowy styl, skorzystamy z funkcji addFormat() z klasy workbook (skoroszyt). W ten sposób utworzymy obiekt formatowania, który będziemy mogli zastosować na tylu komórkach, na ilu zechcemy. Przypomina to trochę tworzenie klas CSS (stylów) w języku HTML. W projekcie 74 Rozdział 2. • Wyświetlanie danych zazwyczaj tworzy się przynajmniej kilka standardowych obiektów formatowania, które następnie wykorzystywane są w różnych miejscach projektu. require_once 'Spreadsheet/Excel/Writer.php'; $workbook = new Spreadsheet_Excel_Writer('example2.xls'); $worksheet =& $workbook->addWorksheet("Example 2"); $header =& $workbook->addFormat(array("bold" => true, "Color" => "white", "FgColor" => "12", "Size" => "15")); $worksheet->write(0, 0, 'Witam wszystkich', $header); W tym przykładzie najpierw tworzymy nowy arkusz kalkulacyjny, a następnie przesyłamy nasze parametry formatowania funkcji addFormat(), by pobrać nasze opcje formatowania, które zastosujemy następnie na danych wysyłanych, gdy będziemy dodawać tekst. Każdy z kluczy tablicy przesyłanej jako parametr funkcji addFormat() również będzie posiadać osobną funkcję, której możemy użyć, by niezależnie określić daną wartość formatującą. $header =& $workbook->addFormat(); $header->setBold(); $header->setColor("white"); $header->setFgColor("12"); Dzięki temu, że każdą z wartości formatujących można stosować niezależnie od innych, nasz kod jest łatwiejszy do zarządzania i modyfikacji w przyszłości. Kolory Excel obchodzi się z kolorami w bardzo ciekawy sposób. Uważny czytelnik zauważył zapewne, że atrybutowi FgColor przypisaliśmy wartość 12, a atrybutowi Color tekstu wartość white (biały). Excel korzysta zarówno z nazw kolorów, jak i z własnego wewnętrznego systemu indeksowania kolorów. Przedstawiony tutaj skrypt generuje schemat kompatybilnych z Excelem kolorów, których możemy używać w naszych arkuszach kalkulacyjnych. require_once 'Spreadsheet/Excel/Writer.php'; $workbook = new Spreadsheet_Excel_Writer('example2a.xls'); $worksheet =& $workbook->addWorksheet("Colors"); $row = 0; $col = 0; for ($i = 1; $i addFormat(array("bold" => true, "Color" => "white", "FgColor" => $i)); 75 PEAR. Programowanie w PHP $worksheet->write($row, $col, '#'.$i, $format); $col++; if ($col == 7) { $col = 0; $row++; } } $workbook->close(); Kod ten wygeneruje następujący schemat: Rysunek 2.3. Arkusz Excela z barwnymi komórkami Paleta kolorów zmieniła się odrobinę między Excelem 5 a Excelem 97 i należy o tym pamiętać, jeśli sądzimy, że nasi użytkownicy będą korzystać z bardzo starych wersji Excela. Widoczne na rysunku liczby nie są kodami szesnastkowymi, jak w przypadku języka HTML, a po prostu wewnętrznymi identyfikatorami kolorów Excela. Jak łatwo zauważyć, kolor tła komórki określiliśmy za pomocą atrybutu FgColor. Funkcji tej używamy, ponieważ Excel może użyć jako tła komórki również barwnego desenia. Jeśli nie zostanie określony żaden deseń, domyślnie tło będzie jednolitym kolorem, a atrybut FgColor określi kolor będący w deseniu „na pierwszym planie” (ang. foreground). Owszem, przyznajemy, jest to odrobinę skomplikowane. Desenie opiszemy dokładnie poniżej. Jeśli natomiast zechcemy zastosować kolor, który nie pojawia się na naszym schemacie barw, można spróbować pokryć któryś ze standardowych kolorów naszym własnym kolorem. Nowy kolor tworzy się określając, który z oryginalnych kolorów chcemy zastąpić — w naszym przypadku zastąpimy kolor o identyfikatorze 12 — a następnie wybierając odpowiednie wartości barw składowych RGB (red, green, blue — czerwonej, zielonej i niebieskiej). $workbook->setCustomColor(12, 10, 200, 10); Podmiana koloru stosować się będzie do całego arkusza kalkulacyjnego. 76 Rozdział 2. • Wyświetlanie danych Wypełnianie barwnym deseniem Oprócz własnego zestawu kolorów, Excel oferuje również barwne desenia, których także można używać w charakterze tła. Domyślnym deseniem wypełnienia jest jednakże wypełnienie jednolitym kolorem, gdy wyświetlany jest tylko kolor tła. Kolejny rysunek pokazuje dostępne desenie oraz ich numery identyfikacyjne. W tym przykładzie kolorem pierwszego planu jest ciemnoszary, a kolorem tła — jasnoszary. require_once 'Spreadsheet/Excel/Writer.php'; $workbook = new Spreadsheet_Excel_Writer('example2b.xls'); $worksheet =& $workbook->addWorksheet("Colors"); $row = 0; $col = 0; for ($i = 1; $i addFormat(array("bold" => true, "Color" => "white", "BgColor" => 22, "FgColor" => 23, "Pattern" => $i)); $worksheet->write($row, $col, '#'.$i, $format); $col++; if ($col == 4) { $col = 0; $row++; } } $workbook->close(); Rysunek 2.4. Komórki Excela wypełnione różnymi deseniami Formatowanie liczb Excel dostarcza również bogatego zestawu opcji umożliwiających formatowanie i wyróżnianie kolorami wartości liczbowych. 77 PEAR. Programowanie w PHP Liczby wewnątrz formatów mogą być reprezentowane za pomocą specjalnych symboli zastępczych # lub 0. Różnica między tymi dwoma symbolami zastępczymi polega na tym, że gdy użyjemy 0, to wyniki zostaną wyrównane do formatu określonego za pomocą symbolu przez dodanie w odpowiednich miejscach wypełniających zer, natomiast użycie symbolu # spowoduje wyświetlenie wyłącznie samych liczb. Tak więc format #####.## zastosowany na liczbie 4201.5 wyświetli po prostu tę liczbę, natomiast format 0000.00 wyświetli ją w postaci 04201.50. Najlepiej jest korzystać z kombinacji obu symboli, na przykład #.00, by wyświetlać liczby w formacie możliwie najwygodniejszym dla użytkownika: 4201.50. Innym symbolem zastępczym, którego można używać w formatowaniu, jest znak zapytania, ?. Pozostawia on w miejsce nie mających znaczenia dla wartości liczby zer pustą spację i przydaje się, gdy chcemy po prostu wyrównać liczby według kropki dziesiętnej. Kiedy określamy format liczby, Excel pozwala na definiowanie osobno formatów zarówno dla liczb dodatnich, jak i ujemnych. Formaty oddziela się średnikiem, a odpowiedni format będzie używany w zależności od wartości tekstu lub liczby w danym polu. Jeśli na przykład chcemy, aby liczby dodatnie były wyświetlane na niebiesko, a ujemne na czerwono i dodatkowo ujęte w nawiasy, to należy użyć następującego łańcucha deklarującego sposób formatowania: $format =& $workbook->addFormat(); $format->setNumFormat('[Blue]$0.00;[Red]($0.00)'); $worksheet->write(2, 1, "-4201", $format); $worksheet->write(2, 2, "4201", $format); Możemy również określić osobny format dla wartości 0 lub wartości tekstowych. $format =& $workbook->addFormat(); $format->setNumFormat('[Blue]0;[Red]0;[Green]0;@*-'); $worksheet->write(0, 1, 10, $format); $worksheet->write(0, 1, -10, $format); $worksheet->write(0, 1, 0, $format); $worksheet->write(0, 1, "dziesięć", $format); Ten format będzie wyświetlał liczby dodatnie na niebiesko, liczby ujemne na czerwono, wartości 0 na zielono, a tekst będzie wyrównywany za pomocą tylu kresek, ile zmieści się w komórce. Mając takie możliwości manipulowania formatami, możemy na przykład utworzyć format, który nie będzie wyświetlał wartości 0 lub będzie wyświetlał komunikat o błędzie, jeśli do pola przeznaczonego na wartości liczbowe zostanie dodany tekst. Jeśli chcemy na przykład, aby wynik obliczeń zwracał opisowy tekst 6 zł. 95 gr. zamiast liczby 6.95, to należy użyć następującego łańcucha formatującego: $format =& $workbook->addFormat(); $format->setNumFormat('0 "zł." .00 "gr."'); $worksheet->write(4, 1, 6.95, $format); Można pójść nawet jeszcze dalej i zażyczyć sobie, aby wartość dziesiętna (grosze) przedstawiana była w formie ułamka wartości liczonej w złotych (PLN). 78 Rozdział 2. • Wyświetlanie danych $format =& $workbook->addFormat(); $format->setNumFormat('0 ??/?? "PLN"'); $worksheet->write(0, 1, 42.50, $format); W ten sposób wyświetlimy tekst 42 ½ PLN. Oto tabela zestawiająca niektóre z najczęściej stosowanych formatów: Format Opis 0000 Pokazuje nie mniej niż pięć cyfr. Liczby wyrównywane są przez dodanie na początku zer. ;;;@ Nie pozwala na wyświetlanie liczb i wyświetla tylko tekst (@). #.??? Wyrównuje liczby według kropki dziesiętnej. #, Wyświetla liczby w tysiącach. 0,000,, "Milion" Wyświetla liczby w milionach, dodając na końcu tekst „Milion”. 0;[Red]"Błąd!"; ¦0;[red]"Błąd!" Wyświetla na czerwono tekst Błąd! w przypadku pojawienia się liczby ujemnej lub wartości tekstowej. 0.00_-;0.00- Wyświetla po prawej stronie liczby znak minusa i dodaje spację, aby wyrównać liczby do kropki dziesiętnej. '0","000' Wstawia do liczby przecinek dla oddzielenia tysięcy, dzięki czemu 100000 będzie wyświetlane jako 10,000. ??/?? Wyświetla liczbę dziesiętną jako ułamek zwykły. # ??/?? Wyświetla część dziesiętną liczby w postaci ułamka zwykłego. 0.00E+# Wyświetla liczbę w notacji naukowej. Formuły Tworzenie formuł i przypisywanie ich do komórek jest jedną z podstawowych funkcji Excela. Teraz, gdy wiemy już, jak umieszczać w komórkach arkusza kalkulacyjnego dane i w jaki sposób je formatować, zobaczmy, jak dodawać do komórek formuły, by Excel wykonywał żmudną pracę za nas. require_once 'Spreadsheet/Excel/Writer.php'; $workbook = new Spreadsheet_Excel_Writer('example3.xls'); $worksheet =& $workbook->addWorksheet("Example 3"); $tax =& $workbook->addFormat(); $tax->setNumFormat('.00%'); $price =& $workbook->addFormat(); $price->setNumFormat('$####.00'); 79 PEAR. Programowanie w PHP $worksheet->write(0, 0, 'Arkusz rozliczania podatku'); $worksheet->write(1, $worksheet->write(1, $worksheet->write(2, $worksheet->write(2, 0, 1, 1, 2, 'VAT:'); ".16", $tax); 'Cena'); "Z podatkiem"); $worksheet->freezePanes(array(3)); for ($i = 3; $i < 103; $i++) { $worksheet->write($i, 0, "Item ".($i-2)); $worksheet->write($i, 1, rand(1, 100), $price); $cell = Spreadsheet_Excel_Writer::rowcolToCell($i, 1); $worksheet->writeFormula($i, 2, "=($cell*B2)+$cell", $price); } $worksheet->writeFormula(103, 1, "=SUM(B4:B103,C4:C103)", $price); $workbook->close(); Ten przykład generuje 100 liczb losowych, dodaje je do arkusza kalkulacyjnego i następnie tworzy formułę obliczającą dla nich podatek. Użytkownik arkusza kalkulacyjnego będzie mógł zmieniać tę formułę. Użyliśmy tutaj pomocniczej funkcji rowcolToCell(), szybko przełączającej się z notacji wykorzystującej numer kolumny i wiersza na notację używającą nazw komórek, której Excel wymaga podczas definiowania formuł. Ostatnia formuła, na koniec arkusza kalkulacyjnego, oblicza sumę (SUM) kolumn B i C. Excel jest bardzo czuły, jeśli chodzi o separator argumentów, i przykład ten dodaliśmy po to, by pokazać, że podczas przesyłania argumentów do funkcji Excela metoda writeFormula() będzie wymagać używania jako separatora argumentów przecinka. Warto jednak wiedzieć, że w niektórych, dostosowywanych do lokalnych potrzeb wersjach Excela formuła SUM(B4:B103,C4:C103) będzie musiała być zapisywana w następujący sposób: SUM(B4:B103;C4:C103), z wykorzystaniem jako separatora średnika (;). Z pozoru drobna różnica, lecz może być źródłem trudnych do wykrycia błędów. Ponieważ przykład ten przewija w dół widoczny obszar ekranu, zamroziliśmy trzy górne wiersze używając metody freezePanes(). Wiele arkuszy kalkulacyjnych, obramowania, obrazki Teraz gdy już najcięższa praca za nami, możemy zająć się upiększaniem wyglądu naszego arkusza kalkulacyjnego. 80 Rozdział 2. • Wyświetlanie danych Aby zilustrować korzystanie z formuł, utworzymy prosty generator faktur (ang. invoice). Dla uproszczenia przykładu usunęliśmy większość formatów, tak aby pozostawić zadanie upiększenia arkusza czytelnikowi — jako ćwiczenie domowe. require_once 'Spreadsheet/Excel/Writer.php'; $workbook = new Spreadsheet_Excel_Writer("example4.xls"); $worksheet =& $workbook->addWorksheet(); $worksheet->writeNote(1, 0, "Faktura dla nowego klienta"); $worksheet->setRow(0, 50); $worksheet->insertBitmap(0, 0, "logo.bmp", 0, 0); $left =& $workbook->addFormat(array("Left" => 2)); $right =& $workbook->addFormat(array("Right" => 2)); $number =& $workbook->addFormat(array("NumFormat" => '$####.00')); $worksheet->write(1, 1, "Klient:"); $worksheet->write(2, 1, "Podatek:"); $worksheet->writeNumber(2, 2, .16); $cart = array("Monitor" => 12, "Drukarka" => 14.4); $top = 4; foreach ($cart as $item => $price) { $worksheet->write($top, 1, $item, $number); $worksheet->write($top, 2, $price, $number); $cell = "C" . ($top + 1); $worksheet->writeFormula($top, 3, "=($cell*C3)+$cell", $number); } $top++; $lastrow = $top + 1; for ($i=1; $i writeBlank($i, 0, $left); $worksheet->writeBlank($i, 7, $right); } $worksheet->write($lastrow, 2, "Suma:"); $worksheet->writeFormula($lastrow, 3, "=SUM(D5:D$lastrow)", $number); $workbook->close(); 81 PEAR. Programowanie w PHP Najważniejszymi kwestiami, na które należy tutaj zwrócić uwagę, jest dodanie obrazka i obramowań. Mapy bitowe można dodawać do arkusza kalkulacyjnego za pomocą metody insertBitmap(). Jest to dość prosta technika; ponieważ jednak działa w sposób niezbyt dla niektórych ludzi intuicyjnie zrozumiały, użytkownikom czasem zdarza się uskarżać na błędne zachowanie programu, gdy próbują zmienić wysokość wiersza, w którym znajduje się mapa bitowa. Dzieje się tak dlatego, że wysokość wiersza musi zostać określona, zanim obrazek zostanie załączony do arkusza kalkulacyjnego. Jeśli dodamy obrazek, a następnie spróbujemy zmienić wysokość wiersza, obrazek zostanie rozciągnięty lub ściśnięty, a nie o to nam przecież chodzi. W tym przykładzie najpierw przywołujemy metodę setRow() i dopiero gdy za jej pomocą określimy wysokość wiersza, przywołujemy metodę insertBitmap(), by osadzić w komórce obrazek. Obramowania działają dokładnie w ten sam sposób, co format tekstowy. Po prostu dodajemy styl obramowania do naszego formatu i stosujemy go na komórce. W tym przypadku nie musimy wstawiać do komórek, dla których formatujemy obramowania, żadnych danych, dlatego też używamy metody writeBlank(), która określa formatowanie komórki bez wstawiania żadnych danych. Warto też zwrócić uwagę na to, że w przykładzie tym korzystamy z metod writeNumber() i writeNote(). Są to po prostu jeszcze inne sposoby zapisywania danych w arkuszu kalkulacyjnym przygotowanym za pomocą klasy Spreadsheet_Excel_Writer. W tym przykładzie wygenerowaliśmy tylko jedną fakturę. Niemniej gdybyśmy pobierali dane z jakiegoś zewnętrznego źródła i musielibyśmy przygotować wiele faktur, moglibyśmy bez trudu dodać tyle nowych arkuszy kalkulacyjnych, ile potrzeba. Każdy kolejny arkusz należałoby dodawać za pomocą metody addWorksheet() klasy skoroszytu workbook. Rysunek 2.5. Przykład wygenerowanej faktury 82 Rozdział 2. • Wyświetlanie danych Inne techniki tworzenia arkuszy kalkulacyjnych Klasa Spreadsheet_Excel_Writer jest najlepszym sposobem tworzenia doskonałych arkuszy Excela. Niemniej w sytuacjach, gdy nie potrzebujemy wszystkich funkcji oferowanych przez klasę Excel_Spreadsheet_Writer i chcielibyśmy wyświetlić dane w odrobinę prostszy sposób, możemy skorzystać z innych, łatwiejszych metod. CSV Skromny format CSV (comma separated values, oddzielający wartości przecinkami) jest najprostszym z formatów wykorzystywanych do wymieniania się danymi i dlatego wiele programów umożliwia importowanie i eksportowanie danych w formacie CSV. Repozytorium PEAR oferuje moduł File, za którego pośrednictwem można bez trudu odczytywać i zapisywać dane w plikach w formacie CSV. Trik z nagłówkiem Content-Type Jedna z często wykorzystywanych technik, która sprawdza się całkiem dobrze w nowych wersjach Excela, polega po prostu na utworzeniu tabeli HTML, która zawierać będzie odpowiednie dane, a następnie na wysłaniu nagłówka HTTP z wartością application/vnd.msexcel, a po nim przygotowaniu tabeli HTML. Przeglądarka WWW rozpozna nagłówek i potraktuje tabelę HTML tak, jakby była arkuszem kalkulacyjnym Excela. Program Excel z kolei zaakceptuje tabelę HTML i również ją wyświetli, oferując jednak znacznie mniej funkcji, niż mielibyśmy do wyboru, gdybyśmy pracowali z prawdziwym dokumentem Excela. Trik ten działa dzięki temu, że najnowsze formaty plików Excela bazują na standardzie języka XML i dlatego Excel jest dość spolegliwy, gdy pora na formatowanie. Generowania plików Excela 2003 Inaczej niż kłopotliwy w użyciu format BIFF, współczesne formaty dokumentów Excela oparte są na języku XML, standardy są ze sobą zgodne i dobrze udokumentowane. Jedna z często stosowanych technik polega na tworzeniu naszych dokumentów przy użyciu najnowszych wersji Excela, następnie edytowaniu wygenerowanego za jego pomocą dokumentu XML i dodawaniu znaczników PHP właśnie do tego dokumentu XML. Konieczna będzie w tym celu zmiana konfiguracji serwera WWW, by analizował dokumenty Excela tak jak pliki PHP. Po wprowadzeniu tej drobnej zmiany w konfiguracji będziemy jednak mogli używać języka PHP wprost w dokumentach Excela. 83 PEAR. Programowanie w PHP Tworzenie arkuszy kalkulacyjnych za pomocą formatu OpenDocument repozytorium PEAR Dzięki projektowi Summer of Code firmy Google zostały rozpoczęte prace nad przygotowaniem formatu odczytywania i zapisywania danych OpenDocument dla repozytorium PEAR. Kiedy projekt zostanie zakończony, możliwe będzie tworzenie oferujących pełnię potrzebnych funkcji arkuszy kalkulacyjnych w formacie OpenDocument. W najnowszych wersjach pakietu Microsoft Office podejmowane są zabiegi, by zagwarantować pełną współpracę z formatem OpenDocument. Co prawda obecnie nie można jeszcze pracować z arkuszami kalkulacyjnymi OpenDocument w pakiecie Office, niemniej zostało to uwzględnione w projektach przyszłych wersji pakietu Office. Komponent siatki danych DataGrid Programistom Windows dobrze znana jest technika polegająca na wykorzystaniu do wyświetlania danych w formie wygodnej w użyciu i łatwej do sortowania siatki za pomocą komponentu DataGrid. W prostych scenariuszach wszystko, co będzie musiał zrobić programista, to pobrać dane z odpowiedniego źródła danych (bazy danych, pliku tekstowego, tablicy), a następnie wyświetlić je w postaci łatwej do skonfigurowania strony WWW w kodzie HTML. W bardziej złożonych scenariuszach programista może przygotować siatkę w ten sposób, aby można było sortować jej zawartość, włączyć filtrowanie danych i renderować je w różnych formatach. Od strony sieci WWW programiści ASP.NET mają do dyspozycji komponent DataGrid. Język PHP nie oferuje żadnej standardowej implementacji komponentu DataGrid i większość programistów języka PHP musi pisać własne komponenty lub korzystać z komponentów przygotowanych przez kogoś innego, lub nawet z komercyjnych implementacji komponentu DataGrid. Jak już wspomnieliśmy, korzystanie z komponentu DataGrid wymaga wykonania kilku czynności. Trzeba skądś pobrać dane. Będzie to nasze źródło danych — DataSource. Trzeba utworzyć siatkę DataGrid i wybrać format zwracania danych, co potocznie określane jest jako renderer (Renderer). Trzeba powiązać źródło danych DataSource z siatką DataGrid i wyświetlić siatkę. Wymaganie to zostało odpowiednio ustandaryzowane w różnych implementacjach DataGrid. Dobrze sprawdza się tutaj pakiet Structures_DataGrid z repozytorium PEAR. Oprócz tego, że oferuje nam standardowe opcje umożliwiające pobieranie danych i wiązanie ich z tabelą HTML, to zapewnia również technikę pobierania i renderowania danych opartą na sterownikach. Dzięki temu pakiet Structures_DataGrid może na przykład skorzystać ze swojego sterownika XML DataSource, by pobrać dane z pliku XML. Następnie trzeba ustalić, które pola należy wyświetlić, i renderować dane w dowolnym formacie, dla którego istnieje odpowiedni 84 Rozdział 2. • Wyświetlanie danych sterownik renderowania. Jeśli wymagania projektu się zmienią i konieczne będzie renderowanie siatki danych DataGrid w innych formatach, to wystarczy tylko przygotować nowy renderer Structures_DataGrid. Jak pewnie czytelnicy już się domyślili, daje nam to nie tylko swobodę tworzenia bardzo wszechstronnych siatek danych DataGrid, ale również korzystania z bardzo efektywnego silnika konwersji danych, jako że obecnie jesteśmy w stanie konwertować dane z dowolnego formatu, dla którego istnieje sterownik DataSource, na dowolny inny format, dla którego istnieje odpowiedni renderer. W kolejnych tabelach podajemy listę dostępnych źródeł danych DataSource. Trzecia kolumna w obu tabelach podaje stałą, która reprezentuje źródło danych DataSource lub renderer i jest przesyłana do konstruktora Structures_Datagrid. Źródła danych DataSource Nazwa Funkcja Stała CSV Analizuje dane w formacie CSV — oddzielane przecinkami. DATAGRID_SOURCE_CSV DataObject Wykorzystuje do pobierania danych interfejsu PEAR Object Interface do tabel bazy danych DB_DataObject. DATAGRID_SOURCE_DATAOBJECT RSS Pobiera i rozpatruje dane z zewnętrznego źródła RSS. DATAGRID_SOURCE_RSS DB Pobiera dane używając modułu PEAR::DB. DATAGRID_SOURCE_DB XML Rozpatruje pliki XML. DATAGRID_SOURCE_XML Renderery Nazwa Funkcja Stała Excel Generuje natywne pliki programu MS Excel korzystając z modułu PEAR::Spreadsheet_Excel_Writer. DATAGRID_RENDERER_XLS HTML_Table Domyślny renderer. Prezentuje dane w postaci konfigurowalnej, sortowalnej i dającej się stronicować tabeli HTML. DATAGRID_RENDERER_TABLE XML Formatuje dane do postaci pliku XML. DATAGRID_RENDERER_XML XUL Renderuje siatkę DataGrid do formatu XUL właściwego dla przeglądarki Mozilla Firefox i innych przeglądarek opartych na silniku Gecko. DATAGRID_RENDERER_XUL CSV Renderuje siatkę DataGrid w formacie CSV. DATAGRID_RENDERER_CSV Console Renderuje siatkę DataGrid w postaci tabeli, którą można wyświetlać w konsoli. DATAGRID_RENDERER_CONSOLE 85 PEAR. Programowanie w PHP Przede wszystkim zaczniemy od zaprezentowania kilku prostych przykładów zastosowania pakietu Structures_DataGrid, po czym przejdziemy do zagadnień związanych z renderowaniem, formatowaniem i rozbudowywaniem pakietu Structures_DataGrid. Prosta siatka danych DataGrid Teraz, gdy wiemy już, czym zajmuje się pakiet Structures_DataGrid, pora przyjrzeć się przykładom kodu, by przekonać się, w jaki dokładnie sposób on działa. require_once 'Structures/DataGrid.php'; $data = array(array('Imie' 'Nazwisko' 'Email' array('Imie' 'Nazwisko' 'Email' array('Imie' 'Nazwisko' 'Email' array('Imie' 'Nazwisko' 'Email' ); => => => => => => => => => => => => 'Aaron', 'Wormus', '[email protected]'), 'Clark', 'Kent', '[email protected]'), 'Peter', 'Parker', '[email protected]'), 'Bruce', 'Wayne', '[email protected]') $dg =& new Structures_DataGrid; $dg->bind($data); $dg->render(); Ten przykład pokazuje trzy kroki związane z tworzeniem siatki danych DataGrid. Najpierw tworzymy instancję klasy Structures_DataGrid. Następnie za pomocą metody bind() wiążemy tablicę danych z siatką DataGrid. Domyślny sterownik źródła danych DataSource wykorzystywany przez pakiet Structures_DataGrid to sterownik ARRAY, możemy więc przesłać tablicę z danymi naszej siatce DataGrid bez konieczności definiowania żadnych innych opcji. Gdy już źródło danych DataSource zostanie powiązane z siatką danych DataGrid, renderujemy siatkę używając metody render(), która przygotuje nam do wyświetlenia w pełni funkcjonalną siatkę danych DataGrid. W tym fragmencie kodu warto zwrócić uwagę na to, że instancja klasy DataGrid musi zostać utworzona jako odwołanie za pomocą składni =&. Ta zmiana w DataGrid ma związek ze sposobem, w jaki pakiet Structures_DataGrid współpracuje ze swoimi sterownikami. Ponieważ jednak zmiana ta niszczy wsteczną kompatybilność, należy o niej pamiętać podczas tworzenia lub unowocześniania instancji DataGrid. 86 Rozdział 2. • Wyświetlanie danych Imie Nazwisko Aaron Wormus [email protected] Bruce Wayne [email protected] Clark Kent [email protected] Peter Parker [email protected] Jak widać, jednym z domyślnych zastosowań siatki DataGrid jest sortowanie prezentowanych rekordów. Wystarczy po prostu kliknąć odpowiednie łącza nagłówków, by siatka danych DataGrid posortowała określoną kolumnę. Więcej przykładów zaprezentujemy w dalszej części rozdziału. Stronicowanie wyników Pakiet Structures_DataGrid korzysta z klasy PAGER, by za jej pomocą dodawać do stron WWW mechanizm stronicowania (dzielenia na strony) prezentowanych wyników, podobny do tego stosowanego przez wyszukiwarkę Google. Aby ograniczyć liczbę wyników wyświetlanych na każdej stronie, wystarczy po prostu przesłać konstruktorowi liczbę rekordów, które mają być wyświetlane na pojedynczej stronie. $dg =& new Structures_DataGrid('2'); Po wyświetleniu naszej siatki DataGrid konieczne będzie wyświetlenie łączy (kontrolek) umożliwiających użytkownikom przenoszenie się do danych, które nie zostaną wyświetlone na pierwszej stronie. echo $dg->renderer->getPaging(); W ten sposób przywołujemy renderer HTML i wyświetlamy odpowiednie kontrolki stronicowania. Stronicowanie zależeć będzie od wykorzystywanego renderera. Obecnie, niestety, tylko renderer HTML obsługuje stronicowanie. Korzystanie ze źródła danych DataSource W prawdziwym programie nie dodawalibyśmy jednak danych za pomocą tablicy z danymi, lecz raczej pobieralibyśmy je z jakiegoś zewnętrznego źródła. W tym celu korzystać będziemy z oferowanych DataGrid sterowników DataSource. Aby utworzyć nowe źródło danych DataSource, użyjemy metody create() z klasy Structures_DataGrid_DataSource. Metoda ta wymaga przesłania jej trzech parametrów. Pierwszy parametr wskazuje do lokalizacji danych, drugi przesyła tablicę zawierającą opcje specyficzne dla sterownika, a wreszcie trzeci podaje stałą, która definiuje sterownik źródła danych DataSource. 87 PEAR. Programowanie w PHP W tym przykładzie skorzystamy ze sterownika CSV źródła danych DataSource, by za jego pomocą odczytać dane z bazy danych klientów, która została eksportowana z naszej książki adresowej. require_once 'Structures/DataGrid.php'; require_once 'Structures/DataGrid/DataSource.php'; $opt = array('delimiter' => ',', 'fields' => array(0, 1, 2), 'labels' => array("First Name", "Last Name", "Email"), 'generate_columns' => true); $data = Structures_DataGrid_DataSource::create('data.csv', $opt, DATAGRID_SOURCE_CSV); $dg =& new Structures_DataGrid(); $dg->bindDataSource($data); $dg->render(); Opcje określają, czego będziemy używać w charakterze delimitatora (symbolu oddzielającego) pola, jakie pola chcemy dołączyć do naszej siatki DataGrid, jakie są etykiety pól i na koniec, czy chcemy tworzyć kolumny z nagłówkami. O ręcznym generowaniu nagłówków opowiemy dokładniej w dalszej części książki, na razie ustawienia widoczne w kodzie powinny nam wystarczyć. Powiążemy nasze dane z siatką DataGrid za pomocą metody bindDataSource(), a następnie renderujemy otrzymaną siatkę z danymi. Korzystanie z renderera Teraz, gdy już mamy naszą siatkę danych DataGrid, która pobiera dane z pliku CSV i wyświetla je w postaci siatki danych HTML, możemy skorzystać z rendererów pakietu Structures_DataGrid, by za ich pomocą wyeksportować dane do dokumentu Excela. Zrobimy to zmieniając następujące wiersze z poprzedniego przykładu. // Instruujemy pakiet Structures_Datagrid, by korzystał z renderera XLS $dg =& new Structures_DataGrid(null, null, DATAGRID_RENDER_XLS); // Określamy nazwę pliku, z którego będziemy korzystać $dg->renderer->setFilename('datagrid.xls'); // Wiążemy dane i renderujemy wynik $dg->bindDataSource($data); $dg->render(); Mamy już zatem w pełni funkcjonalny mechanizm konwertujący dane z formatu CSV na XLS. Niestety renderer formatu XLS nie może korzystać ze wszystkich funkcji oferowanych przez pakiet Spreadsheet_Excel_Writer, by za ich pomocą formatować wiersze i nagłówki arkusza, ale dobre i to. Aby skorzystać z innych rendererów, wystarczy po prostu zmienić stałą przesyłaną w konstruktorze klasy Structures_DataGrid. 88 Rozdział 2. • Wyświetlanie danych Konstruktor klasy Structures_DataGrid wymaga przesłania mu trzech parametrów. Pierwszy parametr ogranicza liczbę wyników wyświetlanych na bieżącej stronie, drugi parametr określa, która strona jest wyświetlana, a trzeci, którego z rendererów używamy. Estetyczne formatowanie siatki danych Teraz, gdy mamy już obiekt Structures_DataGrid, wykonujący dokładnie to, czego od niego chcemy, pora zadbać, aby wyświetlane wyniki wyglądały na tyle estetycznie, by zrobić odpowiednie wrażenie na kierownictwie. Każdy z rendererów oferuje wiele różnych opcji formatowania. My skorzystamy z domyślnego renderera HTML_Table i wstawimy do ostatniego skryptu kilka opcji. $dg =& new Structures_DataGrid(2, null, DATAGRID_RENDER_TABLE); $dg->renderer->setTableHeaderAttributes(array('bgcolor' => '#3399FF')); $dg->renderer->setTableOddRowAttributes(array('bgcolor' => '#CCCCCC')); $dg->renderer->setTableEvenRowAttributes(array('bgcolor' => '#EEEEEE')); // Definiujemy atrybuty tabeli DataGrid $dg->renderer->setTableAttribute('width', '100%'); $dg->renderer->setTableAttribute('cellspacing', '1'); // Określamy ikony sortowania $dg->renderer->sortIconASC = '↑'; $dg->renderer->sortIconDESC = '↓'; $dg->bind($data); $dg->render(); Jak łatwo zauważyć, podczas instancjacji klasy (tworzenia obiektu) przesyłamy klasie Structures_DataGrid kilka dodatkowych parametrów. Drugi atrybut określa, którą stronę chcemy wyświetlić, a trzeci definiuje sterownik wykorzystywany przez nas do renderowania siatki DataGrid. Oczywiście nie ma potrzeby wyraźnego definiowania renderera DATAGRID_RENDERER_TABLE, ponieważ jest to domyślny renderer, niemniej przesłaliśmy konstruktorowi odpowiedni parametr dla większej czytelności przykładu. Po utworzeniu instancji obiektu DataGrid możemy zacząć bawić się z rendererem. Jak widać z naszego przykładu, możemy definiować indywidualne atrybuty tabeli, nagłówków lub nawet wierszy parzystych i nieparzystych. Renderer HTML_Table jest jednym z najbardziej dopracowanych rendererów i oferuje o kilka opcji formatowania więcej niż inne. My wykorzystaliśmy tutaj tylko kilka z nich. 89 PEAR. Programowanie w PHP Zanim zakończymy nasz przykład, dodamy jeszcze ikony umożliwiające sortowanie danych, które pojawiać się będą w momencie sortowania nagłówka określonej kolumny. Skorzystamy tutaj z dostępnych w języku HTML elementów „Strzałki w górę” (Up Arrow) i „Strzałki w dół” (Down Arrow). Warto zauważyć, że możemy tutaj wprowadzić dowolny kod HTML, tak więc można również używać odpowiednich obrazków. Poszerzanie możliwości DataGrid Prezentowany wyżej kod określający atrybuty tabeli był dość długi i raczej kłopotliwe byłoby wpisywanie go za każdym razem, gdy trzeba wyświetlić jakąś siatkę DataGrid. Aby rozwiązać ten problem, możemy utworzyć klasę, która poszerzy możliwości pakietu Structures_DataGrid, tak aby za każdym razem gdy przywoływać będziemy naszą nową klasę, automatycznie używane były wszystkie określone przez nas atrybuty renderowania. require 'Structures/DataGrid.php'; class myDataGrid extends Structures_DataGrid { function myDataGrid($limit = null, $page = 0) { parent::Structures_DataGrid($limit, $page); $this->renderer->setTableAttribute('width', '100%'); // ... Tutaj wpisujemy resztę kodu formatującego ... $this->renderer->sortIconDESC = '↓'; } } $dg =& myDataGrid(); Teraz za każdym razem gdy będziemy tworzyć nową instancję obiektu myDataGrid, wszystkie atrybuty tabeli zostaną odpowiednio ustawione, a my będziemy mieli jedno, centralne miejsce, w którym będziemy mogli zmieniać wygląd siatek DataGrid używanych w naszym projekcie. Jeśli natomiast w naszej witrynie korzystamy z kilku różnych siatek danych DataGrid, wygodniejszym rozwiązaniem będzie utworzenie kilku różnych klas, które rozszerzać będą zachowanie klasy Structures_DataGrid na kilka różnych sposobów, zależnie od potrzeb każdej konkretnej siatki danych. Po czym będą dokonywać instancjacji klasy używając prostego mechanizmu fabryki. function getDGInstance($type) { if (class_exists($type)) { $datagrid =& new $type; return $datagrid; } else { 90 Rozdział 2. • Wyświetlanie danych return false; } } $dg = getDGInstance('myDataGrid'); // Możemy utworzyć kolejną instancję siatki DataGrid używając // osobnej, rozszerzonej klasy, takiej jak ta $dg = getDGInstance('myDataGrid2'); Zaprezentowany tu przykład jest bardzo prosty, niemniej dobrze ilustruje, jak łatwe jest tworzenie wielu różnych instancji klas rozszerzających możliwości klasy Structures_DataGrid. Dodawanie kolumn Kolumny naszej siatki danych DataGrid są tak naprawdę instancjami klasy Structures_DataGrid_Column. W poprzednich przykładach siatka DataGrid obsługiwała dla nas kolumny automatycznie za kulisami, tak więc nie musieliśmy sami tworzyć kolumn. Niemniej jeśli chcemy dodać do siatki DataGrid nowe kolumny, trzeba to zrobić ręcznie. W tym przykładzie skorzystamy ze sterownika RSS źródła Danych DataSource, by pobrać dane z zewnętrznego pliku RSS, a następnie wyświetlić je w połączeniu z kilkoma kolumnami dodanymi przez nas. require_once 'Structures/DataGrid/DataSource.php'; // Określamy kolumny RSS, z których chcemy skorzystać $options = array('fields' => array('title', 'link')); $rss = "http://rss.slashdot.org/Slashdot/slashdot"; $ds = Structures_DataGrid_DataSource::create($rss, $options, DATAGRID_SOURCE_RSS); // Tworzymy instancję naszej rozszerzonej klasy DataGrid $dg =& new myDataGrid; // Tworzymy dwie kolumny $titleCol = new Structures_DataGrid_Column('Title', 'title'); $funcCol = new Structures_DataGrid_Column('Function', null); // Dodajemy funkcje formatujące $titleCol->setFormatter('printLink()'); $funcCol->setFormatter('sendLink()'); // Dodajemy kolumny do siatki danych DataGrid $dg->addColumn($titleCol); $dg->addColumn($funcCol); // Wiążemy zbiór danych DataSet z siatką DataGrid i renderujemy wynik $dg->bindDataSource($ds); $dg->render(); 91 PEAR. Programowanie w PHP Większość z prezentowanego tu kodu mieliśmy okazję widzieć już we wcześniejszych przykładach. Najpierw tworzymy źródło danych DataSource używając sterownika RSS, a następnie definiujemy opcje umożliwiające wyświetlanie pól tytułu i łącza. Ciekawy staje się dopiero fragment kodu, w którym tworząc instancje klasy Structures_DataGrid_Columns tworzymy nasze kolumny. Podczas tej czynności przesyłamy parametry określające nazwę kolumny oraz pole, z którym jest ona powiązana. Konstruktor klasy Structures_DataGrid_Column pozwala na przesyłanie mu oprócz tego jeszcze kilku innych wartości, takich jak: opcje formatowania, atrybuty tabeli, wartości automatycznego wypełnienia, czy wartości określające sposób sortowania, niemniej tutaj zrezygnowaliśmy z nich, by nasz przykład był możliwie jak najprostszy. Chcielibyśmy dodać kilka specjalnych opcji do kolumn zawierających adres URL i naszą funkcję, dlatego też skorzystamy z metody setFormatter(), by za jej pomocą wskazać funkcje, które będą formatować konkretne kolumny. Nasz kod będzie teraz wyglądał tak: function printLink($params) { $data = $params['record']; return "$data[title]"; } function sendLink($params) { $data = $params['record']; $link = urlencode($data["link"]); return "Prześlij łącze znajomemu"; } Zmienna $params jest tablicą zawierającą wszystkie dane z bieżącego rekordu ze zbioru danych. Umieszczamy dane rekordu w zmiennej $data, a następnie zwracamy dane sformatowane tak, jak chcemy, w kolumnie naszej siatki danych DataGrid. W tym przypadku mamy tylko dwie kolumny. Pierwsza jest łączem do odpowiedniego artykułu, a druga formatuje adres URL i łączy go z odpowiednim skryptem, tak aby użytkownik mógł przesłać to łącze znajomemu. Generowanie plików PDF Pisząc o formatach plików, nie sposób nie wspomnieć o formacie PDF (Portable File Format, format przenośnych plików), mającym wśród formatów plików taką pozycję, jak trzystukilogramowy goryl wśród małp. Pierwotnie był to wewnętrzny format plików wykorzystywany w programach graficznych firmy Adobe, z czasem jednak zdobył sporą popularność wśród użytkowników, dzięki temu, że rozwiązywał pewien bardzo ważny problem: tworząc dokument w formacie PDF użytkownik ma pewność, że będzie on wyglądał dokładnie w ten sam sposób, niezależnie w jakim systemie operacyjnym będzie oglądany. 92 Rozdział 2. • Wyświetlanie danych Niestety przenośność dokumentów PDF pociąga za sobą pewne koszty. Jest to mianowicie bardzo złożony format, a zatem niezmiernie trudny do rozszyfrowania, nawet dla tych specjalistów, którym udało się przebrnąć przez jego pełną, liczącą ponad 1000 stron specyfikację. Na szczęście dla użytkowników PEAR, nie muszą oni czytać całej tej długiej technicznej specyfikacji modułu i mogą po prostu skorzystać z biblioteki File_PDF, która zadba o wszystko, co jest związane z tworzeniem dokumentów PDF. Za pomocą tego prostego interfejsu API programista może zrealizować większość zadań związanych z dokumentami PDF, takich jak: wyświetlanie tekstu, rysowanie linii i innych obiektów, wyświetlanie obrazów, zapisywanie danych w tabelach itp. Tutaj zaprezentujemy kod generujący prosty list biznesowy za pomocą biblioteki File_PDF. Dla uproszczenia tego w pełni jednak funkcjonalnego przykładu skorzystaliśmy z funkcji setXY(). Funkcja ta definiuje punkt startowy w miejscu określonym przez współrzędne X i Y. Podczas tworzenia większego dokumentu lub dokumentu prezentującego dynamiczną zawartość trzeba będzie jednak zapewne skorzystać z którejś z bardziej zaawansowanych metod wstawiania zawartości, opisanych dalej przy okazji omawiania komórek. require_once "File/PDF.php"; $company_name = "Wormus Consulting"; $my_address = "123 Aaron Way, Gotham City, 12421 RQ, USA"; // Określamy początkowe marginesy $lm $rm $tm $bm = = = = 22; 22; 22; 22; $padding = 10; $pdf = File_PDF::factory(); $pdf->open(); // Można to też zrobić za pomocą metody setMargins $pdf->setLeftMargin($lm + $padding); $pdf->setRightMargin($rm + $padding); $pdf->addPage(); // Określamy czcionkę tytułu $pdf->setFont('Arial', 'B', '12'); $pos = $tm + $padding; $pdf->setXY(10, $pos); // Wyświetlamy nazwę firmy $pdf->cell(0, $padding, $company_name, null, 0, 'R'); $pdf->setFont('Arial', 'B', '8'); $pos += 10; $pdf->setXY(10, $pos); 93 PEAR. Programowanie w PHP $pdf->cell(0, 0, $my_address, null, 1, 'R'); $pos += 3; $pdf->setXY($lm, $pos); $pdf->line($lm + $padding, $pos, 210 - $rm - $lm, $pos); $pos += 10; $pdf->setXY($lm, $pdf->newLine(); $pdf->write('4', $pdf->newLine(); $pdf->write('4', $pdf->newLine(); $pdf->write('4', $pdf->newLine(); $pdf->write('4', $pdf->newLine(); $pos += 20; $pdf->setXY($lm, $pdf->newLine(); $pos); "John Smith"); "122 Peters Lane"); "32235 City, State"); "Country"); $pos); $pdf->write('4', "To whom it may Concern:"); $pos += 6; $pdf->setXY($lm, $pos); $pdf->newLine(); // Tekst skróciliśmy dla wygody czytelnika $text = "Lorem ipsum dolor ... porta eleifend. "; $pdf->MultiCell(210 -$lm -$rm - $padding *2, 3, $text, null, "J"); $pdf->newLine(10); $pdf->write("10", "Best Regards,"); $pdf->output(); Tan prosty przykład prezentuje niektóre z możliwości biblioteki File_PDF i tworzy zupełnie profesjonalny list biznesowy (rysunek 2.6). Po załączeniu głównego pakietu sam proces tworzenia nowej strony będzie już bardzo prosty. Metoda fabryki (factory) utworzy nową instancję klasy File_PDF. Można jej ponadto przesłać kilka parametrów: $pdf = File_PDF::factory(array('orientation' => 'P', 'unit' => 'mm', 'format' => 'A4')); Tutaj określiliśmy, że dokument ma mieć orientację portretową (wertykalną), jednostkami miary mają być milimetry, a format dokumentu to A4. Są to akurat ustawienia domyślne parametrów, więc w tym przypadku nie zachodziła potrzeba ręcznego określania tych ustawień dokumentu. 94 Rozdział 2. • Wyświetlanie danych Rysunek 2.6. Przykładowy wygenerowany plik PDF Gdy już będziemy mieli nową instancję klasy, będziemy mogli przywołać metodę open(), by uruchomić dokument, a następnie dodać do niego stronę. Podczas tworzenia dokumentu najpierw przywoływane są metody nagłówka i stopki header() i footer(). Więcej na ich temat — w dalszej części rozdziału. Gdy będziemy już mieli gotową stronę, można zacząć dodawać do niej dane. Kolory W naszym prostym przykładzie nie zmienialiśmy kolorów, niemniej warto wiedzieć, że dodawanie kolorów do dokumentów PDF nie jest niczym trudnym. Biblioteka File_PDF oferuje dwie funkcje, które umożliwiają dodawanie kolorów do dokumentu. Kiedy określamy kolor dokumentu, to informujemy, że chcemy używać danego koloru od miejsca, w którym został zainicjowany, aż do końca strony. Kiedy biblioteka File_PDF tworzy nową stronę, dokonuje ponownej instancjacji ustawionych opcji koloru, dlatego też, o ile nie zmienimy koloru albo nie przywrócimy poprzedniej wartości koloru, to raz ustawiony kolor stosowany będzie aż do końca dokumentu. 95 PEAR. Programowanie w PHP Dwie funkcje, które w tym celu wykorzystujemy, to setDrawColor() i setFillColor(). Każda z tych funkcji stosuje pierwszy parametr, by ustalić, z jakiej przestrzeni barw korzystamy (rgb, cymk czy szarej, gray), a kolejne parametry służą do definiowania wartości każdego z używanych kolorów. Funkcja setDrawColor() określa kolor wykorzystywany w rysowanych liniach, a funkcja setFillColor() określa kolor tekstu, obszarów oraz komórek, które nie mają przezroczystego tła. $pdf->setDrawColor("rgb", 0, 0, 255); $pdf->setFillColor("rgb", 255, 0, 0); Dodanie tych dwóch wierszy na początku pliku sprawi, że w naszym dokumencie tekst będzie czerwony, a linie w kolorze niebieskim. Czcionki Podobnie jak w przypadku określania kolorów, ustawienia czcionek będą również stosować się do całego dokumentu, począwszy od punktu, w którym parametry czcionki zostały określone. W tym przykładzie czcionka została zdefiniowana jako 8-punktowa czcionka Arial. $pdf->setFont("Arial", "B", 8); Standardowy zestaw czcionek dostępnych w większości systemów operacyjnych został zdefiniowany w bibliotece File_PDF. Programiści, którzy chcieliby skorzystać z innych czcionek, powinni upewnić się, czy są one dostępne w danym systemie. Jeśli nie, to należy przekonwertować je na czcionki Type1 lub TrueType i następnie dodać do systemowego zestawu czcionek. Opis procedury konwertowania i dodawania czcionek wykracza poza zakres tego rozdziału, warto jednak wiedzieć, że wymaga to utworzenia pliku definicji czcionki korzystając z załączonego narzędzia makefont.php, a następnie załadowania danych czcionki za pomocą funkcji setFont(). Komórki Prostym sposobem zapisania danych posiadających pewną strukturę w formacie PDF jest zapisanie ich w komórkach. Komórka to po prostu prostokątny obszar, do którego można wstawić tekst i dla którego opcjonalnie określić można obramowania i kolor tła. $pdf->cell(0, $padding, $company_name, null, 0, 'R'); Pierwszy z widocznych tu parametrów to szerokość komórki. Jeśli przypiszemy jej wartość zero, to komórka rozciągnie się aż do prawego marginesu. Drugi parametr określa wysokość komórki, a trzeci parametr tekst, który ma zostać wyświetlony w komórce. Czwarty parametr określa, czy należy narysować obramowanie komórki. Przypisanie mu wartości null sprawi, że 96 Rozdział 2. • Wyświetlanie danych komórka nie będzie miała widocznego obramowania. Można również określić, które z boków komórki powinny zostać wyrysowane, używając do tego celu piątego parametru. W kolejnym przykładzie rysujemy obramowanie tylko dla lewego i prawego boku komórki. $pdf->cell(0, $padding, $company_name, null, 0, "LR", 'R', 0, "http://example.com"); Kolejny parametr określa, że komórka powinna być justowana do prawej, a siódmy (opcjonalny) parametr pozwala ustalić, czy tło komórki ma być przezroczyste, czy też pomalowane określonym kolorem tła. Na koniec wreszcie możemy określić łącze, do którego komórka ta powinna kierować po jej kliknięciu, oraz utworzyć identyfikator łącza używając funkcji addLink() i umieścić w ten sposób w komórce zamiast adresu URL przypisany mu identyfikator. Tworzenie nagłówków i stopek Biblioteka File_PDF została tak zaprojektowana, aby pozwalać programistom na rozszerzanie możliwości pakietu, na przykład by dodawać nagłówki i stopki podczas tworzenia każdej ze stron. Aby użyć odpowiednich w tym celu metod, trzeba będzie utworzyć nową klasę, z której skorzystamy podczas generowania naszych dokumentów PDF. class My_File_PDF extends File_PDF { function header() { // Wybieramy czcionkę Arial bold (pogrubioną), rozmiar 15 $this->setFont('Arial', 'B', 15); // Przenosimy się w prawo $this->cell(80); // Tytuł w ramkach $this->cell(30, 10, 'Title', 1, 0, 'C'); // Łamanie wiersza $this->newLine(20); } } To tylko jeden z przykładów rozszerzania możliwości biblioteki File_PDF, by pokryć domyślnie oferowane przez nią funkcje. Podczas korzystania z biblioteki File_PDF w naszych projektach często zachodzić będzie konieczność rozszerzania możliwości bazowej klasy, by dodać jakieś nowe funkcje. Więcej na ten temat można dowiedzieć się czytając dokumentację i zapoznając się z przykładami kodu rozprowadzanymi wraz z pakietem biblioteki File_PDF. 97 PEAR. Programowanie w PHP Podsumowanie Rozdział ten pokazywał, jak za pomocą różnych pakietów dostępnych w repozytorium PEAR można wyświetlać dane na stronach WWW. Niemniej prezentowane przez nas przykłady obejmowały tylko niewielką część możliwości oferowanych przez te pakiety. Warto też wiedzieć, że w repozytorium PEAR można znaleźć również i inne pakiety, pozwalające na odczytywanie i zapisywanie danych w innych, często nietypowych formatach, takich jak vcards czy bibTeX. Znaleźć tam również można użyteczne parsery, pozwalające na odczytywanie i zapisywanie danych na przykład w formacie Wiki (Wikipedii) oraz wiele, wiele innych narzędzi, o których nie wspomnieliśmy w tym rozdziale. 98 3 Praca z formatem XML Język XML w ciągu ostatnich lat coraz bardziej zyskuje na popularności. Prawdę powiedziawszy, w nowej wersji języka PHP, mianowicie PHP5, obsługa formatu XML została całkowicie zmodernizowana i obecnie oparta jest na bibliotekach libxml2 oraz libxsl, które praktycznie w całości stosują się do standardów i zaleceń W3C. Popularność XML nie jest tylko i wyłącznie wynikiem szumu medialnego. W niektórych sytuacjach język XML jest zdecydowanie najlepszym rozwiązaniem. Jeśli chcemy na przykład przechowywać hierarchiczne struktury danych, takie jak struktura i zawartość strony w systemie zarządzania zawartością, to format XML idealnie nadaje się do tego zadania. Niemniej systemy zarządzania zawartością to nie jedyne miejsce, gdzie format XML okazuje się pomocny. Nawet gdy projektujemy małą, prostą aplikację WWW, możemy wykorzystać format XML w naszych plikach konfiguracyjnych. W ten sposób staną się one bardziej wszechstronne i łatwiej je będzie rozwijać, jeśli dodamy nowe funkcje. Dokument XML zawiera nie tylko pary klucza i wartość, tak jak w standardowej konfiguracji plików INI, dodatkowo wartości są zawsze powiązane z pozostałą zawartością poprzez ich pozycję w strukturze drzewa dokumentu XML. Kolejnym typowym zastosowaniem formatu XML jest wymiana danych między różnymi firmami, aplikacjami lub serwerami. Jeden ze scenariuszy wymiany danych za pomocą formatu XML został opisany w rozdziale 4., ponieważ praktycznie wszystkie współczesne usługi WWW wykorzystują język XML jako swój podstawowy format danych. Wielość zastosowań nie jest jedyną zaletą tego prostego, lecz bardzo użytecznego formatu. Dzięki jego podobieństwu do formatu HTML ludzie nie mają problemów z odczytywaniem i interpretowaniem jego plików. W odróżnieniu jednak od formatu HTML, XML opiera się na prostszych zasadach, dzięki czemu jest łatwiejszy do przetwarzania dla komputerów i aplikacji. Co więcej, format XML zapewnia projektantowi znacznie więcej swobody niż format HTML. Podczas gdy język HTML definiuje, jakich znaczników należy używać w dokumencie, język XML definiuje tylko pewne podstawowe zasady, do których dokument musi się stosować. Pozostawia programiście swobodę wyboru, których znaczników użyć w dokumencie PEAR. Programowanie w PHP oraz w jaki sposób aplikacja przetwarzająca dokument ma je interpretować. Tworzenie nowej aplikacji opartej na XML polega po prostu na zdefiniowaniu zestawu znaczników, które będą wykorzystywane w dokumencie. Obecnie istnieje już kilka takich opartych na formacie XML aplikacji: XHTML (kompatybilna z XML wersja języka HTML), SGV (Scaleable Vector Graphics, skalowalna grafika wektorowa), XML Schema (język XML definiujący zasady dla innych aplikacji XML), czy też XUL — język wykorzystywany przez przeglądarkę Mozilla do tworzenia swojego interfejsu użytkownika. Ponadto warto wiedzieć, że aby stworzyć własną aplikację XML, nie trzeba należeć do żadnej organizacji ani oficjalnego komitetu. Może to zrobić każdy, kto uważa, że taka aplikacja jest mu potrzebna. Pakiety PEAR wspomagające pracę z XML Ponieważ język XML cieszy się coraz większym zainteresowaniem programistów, nawet tych pracujących na co dzień w języku PHP, nic więc dziwnego, że kategoria XML jest jedną z najszybciej rozwijających się kategorii dostępnych w repozytorium PEAR. W czasie gdy powstawała ta książka, repozytorium PEAR oferowało 28 różnych pakietów (nie wliczając w to usług WWW) wspomagających tworzenie i przetwarzanie dokumentów XML. W tym rozdziale opowiemy o najważniejszych pakietach dostępnych w tej kategorii. Rozdział będzie podzielony na dwie części. Podczas gdy w pierwszej części zostanie pokazane, jak można wykorzystać repozytorium PEAR do tworzenia od podstaw dokumentów, XML, druga część omawiać będzie rozpatrywanie i analizowanie już istniejących dokumentów. Na kolejnych stronach dowiemy się, jak korzystać z pakietów XML_Util lub XML_FastCreate, by za ich pomocą przekształcać drzewo obiektów w poprawny dokument XML, przeglądając po kolei dane. Następnie nauczymy się korzystać ze znakomitego pakietu XML_Serializer umożliwiającego tworzenie dokumentów XML na podstawie dowolnych przesłanych mu danych. Jak się przekonamy, dzięki pakietom tym tworzenie dokumentów XML za pomocą repozytorium PEAR staje się dziecinnie proste, niezależnie od tego, czy mamy dane zorganizowane w tablice, obiekty, czy jakiekolwiek inny format danych źródłowych. W drugiej części tego rozdziału wykorzystamy pakiet XML_Parser do tworzenia programu odczytującego dane konfiguracyjne, który będzie mógł pobierać informacje z dokumentu XML i przygotuje obiektowy interfejs API do konfiguracji. W następnej kolejności skorzystamy z klasy XML_Unserializer, wchodzącej w skład pakietu XML_Serializer, by za jej pomocą konwertować różne dokumenty XML na zagnieżdżone tablice i struktury obiektów. Wykorzystamy również tę klasę do odczytywania tych samych danych konfiguracyjnych w formacie XML, które przetwarzaliśmy przy użyciu pakietu XML_Parser, niemniej tym razem nie przejmując się analizą formatu XML. Na koniec wreszcie przyjrzymy się pakietowi XML_RSS, by nauczyć się, jak pobierać do naszej aplikacji PHP najświeższe wiadomości pochodzące z dowolnej witryny WWW oferującej informacje w którymś z formatów RSS. Zanim rozpoczniemy pracę z repozytorium PEAR, przyjrzyjmy się najpierw regułom obowiązującym w dokumentach XML. 100 Rozdział 3. • Praca z formatem XML Tworzenie dokumentów XML Na pierwszy rzut oka tworzenie dokumentów XML wydaje się dziecinnie proste. W końcu dokument składa się głównie ze znaczników w prostej formie tekstowej, więc w zasadzie nie różni się wiele od dokumentu HTML i powinniśmy móc bez problemu korzystać tutaj z narzędzi sklejania lub funkcji języka PHP przeznaczonych do pracy na łańcuchach. Niemniej istnieje kilka spraw, o których często zapomina się podczas tworzenia dokumentów XML, a które potem mogą stać się przekleństwem gotowej, produkcyjnej aplikacji. Są one ściśle związane z regułami, do których musi się stosować dokument XML: Format XML (w odróżnieniu od formatu HTML) jest wrażliwy na wielkość liter używanych w znacznikach, tak więc znacznik nie będzie równoważny znacznikowi . Każdy znacznik XML musi być domknięty; jeśli określony znacznik ze swej natury nie zawiera żadnych danych, to można pominąć drugi znacznik, a sam znacznik można zapisać w formie znacznika pustego elementu. Oznacza to, że należy używać znacznika zamiast . Znaczniki muszą być prawidłowo zagnieżdżone i dlatego ostatni otwarty znacznik musi być domykany jako pierwszy. Dla przykładu kod Clark Kentto Superman jest oczywiście nieprawidłowy, natomiast kod Clark Kent to Superman będzie całkowicie poprawny. Każdy dokument XML musi posiadać dokładnie jeden element nadrzędny, który otwierany jest u góry dokumentu i zamykany jako ostatni ze znaczników dokumentu. Specjalne znaki &, , " oraz ' muszą być zastępowane odpowiadającymi im jednostkami (kodami) XML, odpowiednio: &, , " i ', jeśli używane są jako część danych lub wartości atrybutów. Są to jedyne jednostki, których można używać w dokumentach XML bez konieczności ich wcześniejszego deklarowania. Wszystkie atrybuty muszą być ujmowane w cudzysłowy: albo podwójne, albo pojedyncze. Dokument musi być zgodny ze swoją definicją zestawu znaków. Zestaw znaków definiowany jest w deklaracji XML, która poprzedza właściwą część dokumentu XML. Jeśli dokument przesyłany jest za pośrednictwem protokołu HTTP, to zestaw znaków można również zdefiniować w nagłówku dokumentu. Jeśli nie zostanie określone żadne kodowanie, stosuje się domyślne kodowanie UTF-8. Dokument XML, który spełnia wszystkie te wymagania, nazywany jest prawidłowo sformułowanym (ang. well formed) dokumentem XML. Oto przykład takiego prawidłowo sformułowanego dokumentu XML. 101 PEAR. Programowanie w PHP Elvis Presley That's All Right (Mama) & Blue Moon Of Kentucky Good Rockin' Tonight Carl Perkins Gone, Gone, Gone Jak widać, przygotowanie dokumentu XML przy użyciu tylko podstawowych funkcji i narzędzi języka XML służących do pracy na łańcuchach nie jest wcale taką prostą sprawą. Na kolejnych stronach dowiemy się, jak generować dokumenty XML za pomocą kilku użytecznych pakietów z repozytorium PEAR. Tworzenie obiektów przechowujących informacje o nagraniach Zanim jednak sięgniemy po pakiety PEAR, by za ich pomocą utworzyć dokument XML, przygotujmy najpierw strukturę danych PHP, którą będziemy mogli wykorzystać jako pojemnik przechowujący rzeczywiste dane potrzebne do wygenerowania dokumentu XML. Jeśli przyjrzymy się uważniej zaprezentowanemu wcześniej dokumentowi, przekonamy się, że zawiera on informacje o trzech różnych elementach: etykiecie studia (Sun Records), artystach, którzy nagrali dane utwory (Elvis Presley i Carl Perkins), i wreszcie o albumach, które ci wy- 102 Rozdział 3. • Praca z formatem XML konawcy nagrali. Dlatego też najpierw implementujemy klasy, które będzie można wykorzystać do przechowywania właściwości tych trzech elementów. Ponieważ główny, bazowy element to etykieta studia, zaczniemy więc od klasy etykiety Label: /** * Przechowuje informacje o etykiecie studia (albumu) * i artyście, który go nagrał */ class Label { public $name = null; public $artists = array(); public function __construct($name) { $this->name = $name; } public function signArtist(Artist $artist) { // pobieramy kolejny, wyższy identyfikator $artist->setId(count($this->artists)+1); $this->artists[] = $artist; } } Oprócz właściwości nazwy $name klasa ta posiada również właściwość $artists, w której później będziemy przechowywać nazwiska artystów. Nazwa etykiety przesyłana jest jako parametr konstruktorowi, a dodawanie nowego artysty do listy umożliwia metoda signArtist(). Metoda ta akceptuje jako argument instancję klasy Artist, którą napiszemy w następnej kolejności: /** * Przechowuje informacje o artyście * i nagranych przez niego albumach */ class Artist { public $id = null; public $name = null; public $records = array(); } public function __construct($name) { $this->name = $name; } public function setId($id) { $this->id = $id; } public function recordAlbum(Record $album) { $this->records[] = $album; } Tak jak poprzednio, konstruktor klasy wykorzystywany jest do definiowania nazwy, tym razem nazwiska artysty, a metoda recordAlbum() umożliwia dodanie do listy nagranych albumów nowej instancji klasy Album. Klasa ta zawiera również metodę setId(), która przywoływana 103 PEAR. Programowanie w PHP jest przez obiekt Label, kiedy do listy artystów dodawany jest kolejny artysta. Na koniec wreszcie będziemy musieli zaimplementować klasę Record, która przechowuje wszystkie informacje o nagranym albumie: /** * Przechowuje informacje o albumie. */ class Record { public $id = null; public $name = null; public $released = null; } public function __construct($id, $name, $released) { $this->id = $id; $this->name = $name; $this->released = $released; } Teraz gdy wszystkie klasy pojemników zostały już zaimplementowane, przygotowanie samej struktury danych jest już bardzo proste: // tworzymy nową etykietę $sun = new Label('Sun Records'); // tworzymy nowego artystę $elvis = new Artist('Elvis Presley'); // dodajemy artystę do listy artystów $sun->signArtist($elvis); // dodajemy informacje o dwóch albumach $elvis->recordAlbum( new Record('SUN 209', 'That\'s All Right (Mama) & Blue Moon Of Kentucky', 'July 19, 1954' ) ); $elvis->recordAlbum( new Record('SUN 210', 'Good Rockin\' Tonight', 'September, 1954' ) ); // Tworzymy drugiego artystę i informacje o kolejnym albumie $carl = new Artist('Carl Perkins'); $carl->recordAlbum( new Record('SUN 224', 'Gone, Gone, Gone', 'October 22, 1955' ) ); 104 Rozdział 3. • Praca z formatem XML // Dodajemy artystę do etykiety studia $sun->signArtist($carl); // tworzymy listę etykiet (jeśli mamy więcej // niż jedną etykietę) $labels = array($sun); Po utworzeniu nowego obiektu etykiety Label możemy bez większych problemów dodać tyle obiektów artystów Artist, ile zechcemy, a dla każdego artysty dowolną liczbę obiektów albumów Record. Jeśli więc dane etykiety studia będą przechowywane w bazie danych, będziemy mogli bez problemu przygotować skrypt, który pobierze dane i zbuduje odpowiednią strukturę korzystając z tych trzech klas. Jeśli teraz gotowa struktura zostanie wyświetlona na ekranie za pomocą funkcji print_r(), to otrzymamy następujący wynik: Array { [0] => Label Object { [name] => Sun Records [artists] => Array { [0] => Artist Object { [id] => 1 [name] => Elvis Presley [records] => Array { [0] => Record Object { [id] => SUN 209 [name] = That’s All Right... [relased] => July 19, 1954 } } } [1] => Artist Object { [id] => 2 [name] => Carl Perkins [records] => Array { [0] => Record Object { [id] => SUN 224 [name] => Gone, gone, Gone [relased] => Julu 19 1954 } } } } } } Dane wyświetlane przez funkcję print_r() zostały oczywiście dla oszczędności miejsca odrobinę skrócone. 105 PEAR. Programowanie w PHP Dlaczego nie należy generować dokumentów XML bezpośrednio z danych pobranych z bazy? Czytelnicy mogliby się zastanawiać, dlaczego nasze trzy pomocnicze klasy zostały zaimplementowane jako obiekty wartości, podczas gdy dokument XML mógłby być przecież wygenerowany bezpośrednio z danych pobranych z bazy. Nowe klasy służą jako swego rodzaju narzędzia abstrakcji umożliwiające przechowywanie danych i ich przydatność ujawnia się wtedy, gdy pobieramy dane z innego źródła (warstwy) danych niż baza danych. Skończyliśmy już tworzyć naszą strukturę danych, pora teraz więc przyjrzeć się paru pakietom PEAR, które można wykorzystać do generowania dokumentów XML na podstawie przygotowanych przez nas danych. Tworzenie dokumentów XML za pomocą klasy XML_Util XML_Util to narzędziowa klasa przeznaczona do pracy z dokumentami XML. Dostarcza ona kilku metod wykonujących typowe zadania związane z dokumentami XML. Wszystkie te metody można przywoływać statycznie, dlatego też w naszych skryptach nigdy nie będzie konieczności tworzenia nowej instancji klasy XML_Util, aby korzystać z oferowanych przez nią funkcji. Wszystko, co trzeba zrobić, to zadeklarować użycie tej klasy w naszym kodzie: require_once 'XML/Util.php'; Załączona w ten sposób klasa XML_Util oferuje metody umożliwiające: Tworzenie kodu XML i deklaracji typu dokumentu. Tworzenie znaczników otwierających i zamykających. Tworzenie kompletnych znaczników (wraz z zawartością znacznika) oraz innych elementów XML, takich jak komentarze. Zastępowanie jednostek XML pojawiających się w dowolnym łańcuchu. Tworzenie atrybutów XML bazując na tablicach asocjacyjnych. Wspomaganie programisty w innych pracach związanych z kodem XML. Ponieważ zadanie, które teraz przed nami stoi, to utworzenie dokumentu XML z obiektów PHP, pakiet ten wydaje się idealnym rozwiązaniem. Interfejs API wszystkich metod oferowanych przez pakiet XML_Util jest bardzo prosty, tak więc aby wygenerować znacznik otwierający, wystarczy przywołać metodę createStartElement() i przesłać jej nazwę znacznika XML: $label = XML_Util::createStartElement('label'); Jako że metoda ta wygeneruje tylko łańcuch , czytelnik mógłby zastanawiać się, jaki jest właściwie pożytek z korzystania z klasy XML_Util. Jej zalety ujawniają się, gdy chcemy utworzyć znacznik, który zawierać będzie również atrybuty. Można je przesyłać metodzie createStartElemnt() w postaci tablicy asocjacyjnej: 106 Rozdział 3. • Praca z formatem XML $attributes = array( 'name' => 'Sun Records', 'location' => 'Nashville' ); $label = XML_Util::createStartElement('label', $attributes); Ten fragment kodu utworzy znacznik otwierający wraz z atrybutami określonymi w tablicy asocjacyjnej. Klasa XML_Util automatycznie posortuje atrybuty alfabetycznie. Metoda createStartElement() obsługuje również przestrzenie nazw XML — wystarczy po prostu przesłać jej adres URI przestrzeni nazw jako trzeci parametr. Co więcej, mamy również wpływ na to, w jaki sposób znacznik będzie renderowany. Jeśli bowiem znacznik posiada wiele atrybutów, staje się bardzo długi i traci na tym czytelność kodu. Ponieważ spacje są w formacie XML ignorowane, klasa XML_Util może podzielić znacznik między kilka wierszy, umieszczając każdy z atrybutów w swoim własnym wierszu. Oto przykład kodu wykorzystującego w formatowaniu spacji oraz rozdzielający atrybuty pomiędzy wiele wierszy: $attributes = array( 'name' => 'Sun Records', 'location' => 'Nashville' ); $label = XML_Util::createStartElement('records:label', $attributes, 'http://www.example.com', true); A tak będzie wyglądał utworzony przez niego znacznik: Klasa XML_Util dostarcza również metody ceateEndElment() umożliwiającej tworzenie znaczników zamykających: $label = XML_Util::createEndElement('label'); Oczywiście metoda ta nie obsługuje żadnych dodatkowych parametrów znacznika, ponieważ znacznik zamykający nie zawiera niczego poza nazwą znacznika. Jeśli natomiast chcemy utworzyć za jednym zamachem znacznik otwierający i zamykający i dodatkowo jeszcze przesłać zawartość tworzonego znacznika, to należy skorzystać z metody createTag(). Podobnie jak metoda createStartElement(), metoda createTag() pobiera jako dwa pierwsze argumenty nazwę znacznika i tablicę z atrybutami znacznika. Niemniej począwszy od trzeciego argumentu metoda ta zaczyna zachowywać się inaczej. Korzystając z metody createTag() możemy w jej trzecim argumencie przesłać zawartość znacznika: $attributes = array( 'name' => 'Sun Records', 'location' => 'Nashville' ); $tag = XML_Util::createTag('label', $attributes, 'Tag content'); 107 PEAR. Programowanie w PHP Metodzie można również przesyłać parę dodatkowych argumentów, które wpływają na to, w jaki sposób znacznik będzie tworzony. Kolejne argumenty można przekazywać w następującym porządku (jeśli nie chcemy przesyłać żadnej konkretnej wartości, należy użyć wartości null): Adres URI przestrzeni nazw, jeśli potrzeba. Informację, czy zastępować specjalne jednostki(symbole) XML pojawiające się w zawartości znacznika (true), czy też nie (false). Jest to bardzo użyteczna opcja, jeśli znacznik zawierać będzie wewnątrz kolejne znaczniki i nie chcemy, aby specjalne jednostki XML były opatrywane znakami ucieczki. Informację, czy rozdzielać atrybuty między różne wiersze (true), czy też nie (false). Jeśli ostatniemu parametrowi zostanie przypisana wartość true, to metodzie można będzie przesłać jeszcze dwa kolejne argumenty określające głębokość wcięcia i sposób dzielenia wierszy podczas rozdzielania atrybutów między różne wiersze. W 99% przypadków najbardziej odpowiednie będą jednak domyślne wartości tych parametrów. Skoro dowiedzieliśmy się już, jak tworzyć znaczniki XML za pomocą klasy XML_Util, to musimy teraz tylko dowiedzieć się, jak tworzyć deklarację XML, i będziemy gotowi, by przystąpić do tworzenia kompletnego dokumentu XML na podstawie naszego drzewa obiektów. Klasa XML_Util oferuje specjalną metodę tworzącą deklarację XML: $decl = XML_Util::getXMLDeclaration('1.0', 'ISO-8859-1'); Metodzie tej można przesyłać trzy parametry: wersję formatu XML, informacje o sposobie kodowania i znacznik logiczny informujący, czy generowany dokument ma być osobnym dokumentem, czy też nie. Cztery opisane tutaj metody w zupełności wystarczają do utworzenia dokumentu XML. Pozostało nam jedynie przejrzenie dokumentów w kolejnych pętlach foreach i przesłanie odpowiednich właściwości obiektów metodom klasy XML_Util. Jeśli chcielibyśmy przesłać dokument do przeglądarki internetowej, należy użyć instrukcji echo. Oto kompletny skrypt tworzący dokument XML z drzewa obiektów: require_once 'XML/Util.php'; echo XML_Util::getXMLDeclaration('1.0', 'ISO-8859-1'); echo XML_Util::createStartElement('labels') . "\n"; foreach ($labels as $label) { echo XML_Util::createStartElement('label', array('name' => $label->name)) . "\n"; echo XML_Util::createStartElement('artists') . "\n"; foreach ($label->artists as $artist) { echo XML_Util::createStartElement('artist', array('id' => $artist->id)) . "\n"; echo XML_Util::createTag('name', array(), $artist->name) . "\n"; echo XML_Util::createStartElement('records') . "\n"; foreach ($artist->records as $record) { 108 Rozdział 3. • Praca z formatem XML echo XML_Util::createStartElement('record', array( 'id' => $record->id, 'released' => $record->released ) ) . "\n"; echo XML_Util::createTag('name', array(), $record->name) . "\n"; echo XML_Util::createEndElement('record') . "\n"; } echo XML_Util::createEndElement('records') . "\n"; echo XML_Util::createEndElement('artist') . "\n"; } echo XML_Util::createEndElement('artists') . "\n"; echo XML_Util::createEndElement('label') . "\n"; } echo XML_Util::createEndElement('labels') . "\n"; Po dołączeniu pliku, zawierającego klasę XML_UTIL, tworzymy deklarację XML, która poprzedzać będzie dokument i określać stosowane przez nas kodowanie. Następnie za pomocą metody createStartElement() tworzony jest znacznik otwierający głównego elementu. Później przeglądamy w pętli wszystkie obiekty Label przechowywane w tablicy $labels. Prawdę powiedziawszy, jest tam tylko jeden element, etykieta Sun Records, niemniej po dodaniu dodatkowych obiektów nie będzie potrzeby modyfikowania kodu. Dla każdej etykiety studia utworzymy element etykiety i dodamy właściwość $name obiektu Label do listy atrybutów: echo XML_Util::createStartElement('label', array('name' => $label->name)) . "\n"; Wewnątrz tej pętli będziemy przeglądać wszystkie obiekty artystów Artist, które zostały zapisane we właściwości $artists obiektu Label po tym, jak utworzony został otwierający znacznik . Dla każdego z obiektów Artist tworzymy odpowiadający mu znacznik , a następnie dodajemy do atrybutów właściwość $id. Na koniec, wewnątrz drugiej pętli będziemy musieli tylko przejrzeć wszystkie obiekty Record, które zostały dodane do właściwości $records obiektu Artist, i utworzyć zamykający znacznik . Oczywiście znaczniki te otoczone są przez znacznik . Na końcu każdej pętli tworzone są znaczniki zamykające odpowiadające znacznikom otwierającym, które utworzone zostały przed rozpoczęciem pętli, dzięki czemu mamy gwarancję, że dokument jest prawidłowo sformułowany. Jeśli uruchomimy ten skrypt, zwróci on dokładnie taki sam dokument XML, jaki zaprezentowaliśmy na początku tego rozdziału, z tą tylko różnicą, że znaczniki nie będą justowane za pomocą wcięć. Klasa XML_Util oferuje metody umożliwiające tworzenie pojedynczych znaczników lub dowolnych elementów XML, niemniej nie utworzy za nas kompletnego dokumentu. W dalszej części rozdziału dowiemy się o innych pakietach PEAR, które to potrafią. Zapoznamy się z pakietami, którymi można przesłać praktycznie dowolną strukturę danych, zamiast po prostu łańcuchów lub tablic asocjacyjnych, i które przekształcą otrzymane dane w dokument XML. 109 PEAR. Programowanie w PHP Dodatkowe funkcje klasy XML_Util Klasa XML_Util oferuje również kilka dodatkowych metod, które okazują się użyteczne podczas pracy z formatem XML. Jeśli generujemy kod XML dynamicznie i nie wiemy, w jaki sposób znaczniki te będą nazywane, klasa XML_Util umożliwia sprawdzenie, czy danego łańcucha można użyć w charakterze nazwy znacznika. $result = XML_Util::isValidName('My tag name'); if (PEAR::isError($result)) { echo 'Niepoprawna nazwa znacznika: ' . $result->getMessage(); } else { echo 'Poprawna nazwa znacznika'; } Jeśli łańcuch, który przesłaliśmy metodzie, może zostać wykorzystany jako nazwa znacznika w dokumencie XML, to metoda zwróci wartość true (prawda). Jeśli natomiast łańcucha nie można użyć w charakterze nazwy znacznika, ponieważ narusza reguły formatu XML, metoda isValidName() zwróci obiekt błędu PEAR_Error, który zawierać będzie informacje, jaka dokładnie reguła została naruszona. Dlatego jeśli uruchomimy ten skrypt, zwróci on następujący komunikat: No valid tag name: XML names may only contain alphanumeric chars, period, hypen, colon and underscores. informujący, że łańcuch nie jest prawidłowym znacznikiem XML, bowiem nazwy XML mogą zawierać tylko znaki alfanumeryczne, kropki, myślniki, dwukropki i znaki podkreślenia. Kolejna użyteczna metoda, replaceEntities() umożliwia zastępowanie niedozwolonych znaków pojawiających się w przesłanym jej łańcuchu odpowiadającymi im jednostkami (kodami): echo XML_Util::replaceEntities('Ten tekst zawiera " & \'.'); Po zastosowaniu tej metody na łańcuchu będziemy mogli bez obaw użyć go w dowolnym dokumencie XML. Aby odwrócić działanie tej metody, należy skorzystać z kolejnej metody klasy XML_Util, reverseEntities(). Więcej na temat klasy XML_Util lub zasad działania jej interfejsu można dowiedzieć się z dokumentacji użytkownika, dostępnej w witrynie repozytorium PEAR pod adresem: http://pear.php. ¦net/manual/en/package.xml.xml-util.php. Tworzenie dokumentów XML za pomocą pakietu XML_FastCreate Pakiet XML_FastCreate (jak zresztą łatwo domyślić się po jego nazwie) służy do bardzo szybkiego i efektywnego tworzenia dokumentów XML. Stosuje jednak zupełnie inne podejście niż pakiet XML_Util. Pakiet XML_FastCreate nie tworzy osobnych fragmentów dokumentu 110 Rozdział 3. • Praca z formatem XML XML, ale zawsze kompletny, prawidłowo sformułowany dokument. Pakiet XML_FastCreate zawsze upewnia się, czy otrzymamy poprawny dokument XML, podczas gdy pakiet XML_Util sprawdza tylko poszczególne znaczniki, w związku z czym cały czas możemy pominąć domykające znaczniki lub popełnić jakiś błąd tam, gdzie znaczniki zostają zagnieżdżone jeden w drugim. Pakiet XML_FastCreate można wykorzystać do: Utworzenia łańcucha, który zawierać będzie dokument XML. Utworzenia w pamięci struktury drzewa również zawierającej dokument XML. W obu przypadkach możemy używać do utworzenia dokumentu XML tego samego interfejsu API, bowiem pakiet XML_FastCreate dostarcza dla tych dwóch zadań odrębnych sterowników. Dlatego zamiast tworzyć nową instancję klasy XML_FastCreate za pomocą operatora new, trzeba za każdym razem używać do tego metody fabryki (factory) klasy XML_FastCreate. require_once 'XML/FastCreate.php'; $xml = XML_FastCreate::factory('Text'); W tym przypadku metoda fabryki zwróci sterownik, który bezpośrednio utworzy łańcuch znaków zawierający dokument XML. W przeważającej części dalszych przykładów będziemy korzystać właśnie z tego sterownika, ponieważ jest łatwiejszy w użyciu i bardziej stabilny niż alternatywny sterownik oparty na pakiecie XML_Tree. Czytelnicy, którzy będą mimo to chcieli korzystać ze sterownika bazującego na pakiecie XML_Tree, muszą się liczyć z tym, że kolejne przykłady mogą nie działać tak, jak powinny, ponieważ sterownik ten nie obsługuje niektórych funkcjonalności opisywanych w przykładach. Co więcej, trzeba będzie korzystać z wersji 2.0.0 pakietu XML_Tree, której kod jest nadal w fazie beta (może być poprawiany). Różnica pomiędzy sterownikiem tekstowym a sterownikiem opartym na pakiecie XML_Tree polega na tym, że ten drugi pozwala na modyfikowanie dokumentu tak, jakby był on obiektem, zanim zapiszemy go w postaci łańcucha znaków. Sterownik tekstowy z kolei od razu wygeneruje łańcuch zawierający dokument XML, którego to łańcucha już nie będzie można tak łatwo modyfikować (przynajmniej bez sięgania do wyrażeń regularnych). Teraz gdy mamy już nową instancję klasy XML_FastCreate, kolejnym krokiem jest utworzenie znaczników dokumentu. Okazuje się, że jest to niezmiernie łatwe! Wszystko, co trzeba zrobić, to przywołać metodę o nazwie odpowiadającej nazwie znacznika, który chcemy utworzyć, i przesłać jej tekst, który powinien znaleźć się między znacznikiem otwierającym a znacznikiem zamykającym: $xml->artist('Elvis Presley'); W ten sposób dodaliśmy do dokumentu XML znacznik . Otrzymany dokument można przesłać do standardowego strumienia wyjścia STDOUT używając metody toXML(): $xml->toXML(); Po uruchomieniu tego kodu wyświetli on następujące informacje: Elvis Presley 111 PEAR. Programowanie w PHP Czytelnicy zastanawiają się pewnie, skąd pakiet XML_FastCreate wiedział, że musi utworzyć znacznik i użyć metody artist(). Ponieważ dokument XML może zawierać praktycznie dowolny znacznik, pakiet XML_FastCreate musiałby oferować nieskończoną liczbę różnych metod, by móc tworzyć wszystkie możliwe znaczniki. Jak pewnie czytelnicy już zgadli, pakiet XML_FastCreate nie implementuje oczywiście tych wszystkich metod. Zamiast tego korzysta z techniki zwanej przeciążaniem (ang. overloading). Przeciążanie pojawia się dopiero w PHP5, niemniej pakiet XML_FastCreate obsługuje je również dla języka PHP4, jeśli włączymy rozszerzenie umożliwiające przeciążanie (które jest włączane domyślnie we wszystkich wersjach, począwszy od wersji PHP4.3.x). Programiści, którzy chcieliby używać pakietu XML_FastCreate z językiem PHP4, mogą dowiedzieć się więcej na temat rozszerzenia udostępniającego możliwość przeciążania czytając dokumentację języka PHP na ten temat, dostępną pod adresem: http://www.php.net/overload. W kolejnych przykładach skoncentrujemy się na obsłudze przeciążania oferowanej przez PHP5. Interludium: przeciążanie w PHP5 Aby zrozumieć, jak działa pakiet XML_FastCreate, trzeba pojąć podstawowe zasady działania mechanizmu przeciążania obiektów. Przeciążanie umożliwia przechwytywanie odwołań do niezdefiniowanych metod obiektu. Rozważmy następujący fragment kodu: class Bird { public function fly() { print "Ja latam.\n"; } } $bird = new Bird(); $bird->fly(); $bird->swim(); Jeśli uruchomimy ten skrypt, wyświetli ona następujące informacje: Ja latam. Fatal error: Call to undefined method Bird::swim() in c:\wamp\www\books\ packt\pear\xml\overloading.php on line 10 Jak widać, skrypt ten zakończył pracę sygnalizując fatalny błąd w momencie, gdy próbowaliśmy przywołać metodę swim() (pływać) na obiekcie Bird (Ptak), a metoda ta nie została zaimplementowana. W tym miejscu właśnie z pomocą przychodzi mechanizm przeciążania obiektów: przeciążanie umożliwia nam przechwytywanie odwołań do metod (i sięgania do właściwości w przypadku gdy mamy do czynienia z niezdefiniowaną metodą lub właściwością). Aby przechwycić odwołanie do niezdefiniowanej metody swim(), konieczne będzie zaimplementowanie tzw. magicznej (ang. magic) metody _call(), której trzeba przesłać dwa argumenty: 1. Nazwę oryginalnej przywoływanej metody. 2. Tablicę zawierającą wszystkie argumenty, które pierwotnie przesyłane były wywoływanej metodzie. 112 Rozdział 3. • Praca z formatem XML Po dodaniu tej metody do klasy Bird klasa będzie wyglądać mniej więcej tak: class Bird { public function fly() { print "Ja latam.\n"; } public function __call($method, $args) { print "Ja nie umiem $method.\n"; } } Jeśli teraz uruchomimy skrypt ponownie, to zwróci on następujące informacje: Ja latam. Ja nie umiem swim. Jak widać, zawsze kiedy przywoływać będziemy metodę, która nie została zaimplementowana w klasie — swim (pływać), zamiast niej przywoływana będzie metoda _call(): $bird->playPoker(); $bird->raiseTaxes(); Wynik działania tego kodu będzie taki, jak być powinien — skrypt wyświetli informacje, że nie może użyć metod playPoker (grać w pokera) i raiseTaxes (zbierać podatki): Ja nie umiem playPoker. Ja nie umiem raiseTaxes. Wracając do XML Tak właśnie działa pakiet XML_FastCreate. Zawsze kiedy przywoływać będziemy jakąś metodę, której nazwa odpowiada nazwie tworzonego przez nas znacznika, język PHP będzie zamiast niej przywoływał metodę _call() i przesyłał jej jako zawartość znacznika nazwę znacznika, który chcemy utworzyć. Pakiet XML_FastCreate umożliwia również zagnieżdżanie znaczników poprzez zagnieżdżanie odwołań do metod: require_once 'XML/FastCreate.php'; $xml = XML_FastCreate::factory('Text'); $xml->artist( $xml->name('Elvis Presley'), $xml->hometown('Memphis') ); $xml->toXML(); Kod ten zwróci dokument XML o następującej strukturze (dodaliśmy wcięcia dla poprawienia czytelności): 113 PEAR. Programowanie w PHP Elvis Presley Memphis Dotychczas wszystkie tworzone przez nas znaczniki zawierały tylko tekst, natomiast nie posiadały żadnych atrybutów. Niemniej dodawanie atrybutów do znaczników XML jest niezmiernie proste. Podobnie jak to było w przypadku klasy XML_Util, listę atrybutów znacznika XML należy przesłać w formie tablicy asocjacyjnej. Tablica ta powinna być przesyłana jako pierwszy parametr każdej metodzie, która tworzy znacznik XML. Aby dodać dwa atrybuty do głównego, nadrzędnego znacznika utworzonego przez nas dokumentu XML, należy w naszym kodzie wprowadzić niewielkie zmiany: require_once 'XML/FastCreate.php'; $xml = XML_FastCreate::factory('Text'); $xml->artist( array( 'id' => 56, 'label' => 'Sun Records' ), $xml->name('Elvis Presley'), $xml->hometown('Memphis') ); $xml->toXML(); Utworzony dokument będzie miał teraz w swoim głównym znaczniku dwa atrybuty: id i label. Elvis Presley Memphis Kolejną sprawą, która może uderzyć czytelnika, jest fakt, że pakiet XML_FastCreate automatycznie tworzy deklarację dla dokumentu, używając kodowania UTF-8, podczas gdy w poprzednich przykładach wykorzystywaliśmy kodowanie ISO-8859-1. Bez obaw! Pakiet XML_ FastCreate umożliwia również definiowanie innych kodowań. Podczas tworzenia instancji sterownika XML_FastCreate za pomocą metody fabryki factory() możemy przesłać jej w drugim argumencie listę opcji. Jedna z tych opcji pozwala określić kodowanie dokumentu: $options = array( 'encoding' => 'ISO-8859-1' ); $xml = XML_FastCreate::factory('Text', $options); Określanie kodowania to tylko jedna z możliwych opcji, które można definiować za pomocą metody fabryki. W kolejnej tabeli prezentujemy listę najważniejszych opcji. Opcje te są obsługiwane przez oba sterowniki oferowane przez bieżącą wersję pakietu XML_FastCreate. 114 Rozdział 3. • Praca z formatem XML Więcej informacji na temat innych opcji, które nie są obsługiwane przez oba sterowniki, można znaleźć w kodzie źródłowym i dołączonej do niego dokumentacji sterowników oraz bazowej klasy. Nazwa opcji Opis Wartość domyślna version Numer używanej wersji XML. 1.0 encoding Używane kodowanie XML. UTF-8 standalone Czy dokument jest samodzielnym dokumentem, czy nie. No (nie) indent Czy w dokumencie XML stosować wcięcia (wymaga pakietu False (fałsz) XML_Beautifier). quote Czy automatycznie zastępować znaki specjalne odpowiadającymi im jednostkami (kodami). True (prawda) doctype Którą deklarację typu dokumentu należy dodać. Brak wartości domyślnej exec Zewnętrzny program, którego należy użyć, by porównać zgodność dokumentu XML z definicją DTD. Brak wartości domyślnej file Plik, w którym należy zapisać wynik sprawdzenia dokumentu XML. Jeśli nie zostanie określony żaden plik, wynik zostanie wyświetlony na ekranie. Brak wartości domyślnej Pakiet XML_Beautifier Pakiet XML_Beautifier pomaga formatować kod XML, tak aby był bardziej czytelny dla ludzi. Dokumenty XML nie wymagają estetycznego dzielenia wierszy czy stosowania wcięć. Aplikacja przetwarzająca dokument XML wszystkie potrzebne informacje pobiera ze znaczników otwierających i zamykających. Niemniej właściwe dzielenie wierszy i odpowiednie wcięcia ułatwiają ludziom przeglądanie dokumentów XML. Dlatego tam, gdzie zachodzi potrzeba zaprezentowania dokumentu XML w formie odpowiednio uporządkowanej, z dodaniem formatujących spacji dla komfortu użytkownika, z pomocą przychodzi pakiet XML_Beautifier. Potrafi on odczytać dowolny dokument XML i zastosować na nim odpowiednie reguły formatowania (na podobnej zasadzie jak nasz edytor potrafi estetycznie sformatować kod PHP). Doda w odpowiednich miejscach podział wierszy, wcięcia, automatycznie zawinie zbyt długie wiersze itp. Po wprowadzeniu w PHP5 nowego rozszerzenia DOM, pakiet XML_Beautifier traci trochę na znaczeniu, rozszerzenie to jest bowiem w stanie do pewnego stopnia sformatować dokument XML (choć nie ma możliwości naśladowania wszystkich funkcji pakietu XML_Beautifier). Teraz wiemy już wszystko, co trzeba, aby utworzyć z przygotowanych wcześniej obiektów dokument XML zawierający etykiety studia. Został nam jeszcze do omówienia tylko jeden element. Kiedy tworzyliśmy znacznik XML przywołując odpowiednią metodę, zawsze ignorowaliśmy wartość zwracaną przez tę metodę. Niemniej warto wiedzieć, że metody tworzące znaczniki zwracają odpowiedni fragment kodu w zależności od sterownika, którego używamy. Kiedy korzystamy ze sterownika Text, metoda zwraca łańcuch, natomiast w przypadku sterownika opartego na XML_Tree metoda zwraca instancję klasy XML_Tree_Node. 115 PEAR. Programowanie w PHP Tworzenie dokumentu XML Jeśli korzystamy ze sterownika Text, różne znaczniki utworzone przez pakiet XML_FastCreate można łączyć w jeden dokument XML używając standardowych funkcji służących do pracy z łańcuchami, które oferuje język PHP. Programista musi jedynie przejrzeć strukturę obiektów w trzech zagnieżdżonych pętlach: jednej dla etykiet, jednej dla artystów przypisanych do każdej etykiety i jednej dla albumów każdego artysty. Rozwiązanie to jest podobne do techniki, którą stosowaliśmy korzystając z klasy XML_Util, niemniej jest tu jedna istotna różnica: pakiet XML_FastCreate zawsze tworzy kompletny element XML (czyli znacznik otwierający i znacznik zamykający). Dlatego też zawartość znacznika trzeba kompilować (składać) jeszcze przed utworzeniem samego znacznika. Znacznik jest niejako tworzony „od środka”. Pierwszymi utworzonymi znacznikami będą znaczniki albumów , potem znaczniki artysty , a następnie znaczniki etykiety . Na koniec wreszcie, w ostatnich wierszach skryptu utworzymy znacznik etykiet , który otacza wszystkie wcześniej utworzone znaczniki. Natomiast gdy korzystaliśmy z klasy XML_Util, tworzyliśmy znaczniki w takiej samej kolejności, w jakiej miały pojawiać się w dokumencie. Jak widać, kiedy pracujemy z pakietem XML_FastCreate, trzeba najpierw zastanowić się nad odpowiednią kolejnością zagnieżdżania znaczników, co sprawia, że tworzenie dokumentów XML za jego pomocą jest odrobinę trudniejsze, szczególnie jeśli nie jesteśmy przyzwyczajeni do takiego odwróconego porządku. Kompletny skrypt, który tworzy odpowiedni dokument AML z przygotowanego drzewa obiektów za pomocą pakietu XML_FastCreate, wygląda tak: require_once 'XML/FastCreate.php'; // określamy podstawowe opcje dokumentu XML $options = array( 'encoding' => 'ISO-8859-1', 'standalone' => 'yes' ); // Pobieramy nową instancję sterownika 'Text' $xml = XML_FastCreate::factory('Text', $options); // Ta zmienna przechowuje wszystkie etykiety w postaci kodu XML $labelsXML = ''; // Przeglądamy etykiety albumów w tablicy foreach ($labels as $label) { // Ta zmienna przechowuje wszystkich artystów danego studia (etykiety) jako XML $artistsXML = ''; // Przeglądamy wszystkich artystów foreach ($label->artists as $artist) { // Ta zmienna przechowuje wszystkie albumy danego artysty jako kod XML $records = ''; 116 Rozdział 3. • Praca z formatem XML // Przeglądamy wszystkie albumy (records) foreach ($artist->records as $record) { $recordAtts = array( 'id' => $record->id, 'released' => $record->released ); // tworzymy i dodajemy jeden znacznik $records .= $xml->record($recordAtts, $xml->name( $record->name)); } $artistAtts = array('id' => $artist->id); // tworzymy i dodajemy jeden znacznik $artistsXML .= $xml->artist($artistAtts, $xml->records($records)); } $labelAtts = array('name' => $label->name); // tworzymy i dodajemy jeden znacznik $labelsXML .= $xml->label($labelAtts, $xml->artists($artistsXML)); } $xml->labels($labelsXML); // Wysyłamy gotowy kod XML do standardowego strumienia wyjścia STDOUT $xml->toXML(); Dla każdej pętli tworzymy nową zmienną i inicjujemy ją, przesyłając jej pusty łańcuch ($labelsXML, $artistsXML i $recordsXML). Wewnętrzne pętle będą następnie zachowywać wyniki swojego działania w tych zmiennych, a po zakończeniu wewnętrznych pętli wartości zapisane w zmiennych będą wykorzystywane jako zawartość odpowiednich, bardziej zewnętrznych znaczników , lub . Jeśli uruchomimy ten skrypt, otrzymamy taki sam dokument XML, jak w przykładzie, w którym korzystaliśmy z klasy XML_Util. Jeśli mamy w komputerze zainstalowany pakiet XML_Beatifier, to możemy również skorzystać z opcji indent pakietu XML_FastCreate, za której pomocą otrzymamy ładnie sformatowany dokument XML. Problemy z pakietem XML_FastCreate Mimo iż pakiet XML_FastCreate wydaje się bardziej wszechstronny niż pakiet XML_Util (i tak jest w istocie), należy jednak pamiętać o kilku istotnych problemach pojawiających się podczas korzystania z niego: Ponieważ przeciążanie przechwytuje tylko odwołania do nieistniejących metod, istnieje w nim pewna grupa zarezerwowanych słów (nazw metod dostarczanych przez pakiet XML_FastCreate), których nie można używać w charakterze nazw znaczników. Jedną z nich jest metoda xml() wykorzystywana przez przechwytującą metodę _call() do tworzenia znaczników. Dlatego też, jeśli użyjemy w kodzie wywołania w rodzaju na przykład $fastcreate->xml('foo'), to podana metoda 117 PEAR. Programowanie w PHP nie zostanie przechwycona, bowiem metoda o tej nazwie już istnieje w pakiecie. Aby zatem otrzymać satysfakcjonujący nas efekt, należy zastosować wywołanie $fastcrete->('xml', 'foo');, ponieważ trzeba użyć odpowiedniej sygnatury metody dla metody xml(). Pakiet XML_FastCreate zależy w znacznym stopniu od pakietów będących jeszcze w fazie alfa lub beta, czyli w przygotowaniu (takich jak XML_DTD czy XMLTree), co może spowodować problemy z wsteczną kompatybilnością kodu, jeśli któryś z tych pakietów zostanie unowocześniony. W skrajnym przypadku może się nawet okazać, że w pewnym momencie pakiet XML_FastCreate nagle przestanie działać. Tworzenie za pomocą sterownika XML_Tree pakietu XML_FastCreate dokumentów XML o dynamicznej strukturze jest bardzo skomplikowane. Sterownik ten zwraca podczas tworzenia znaczników obiekty zamiast łańcuchów, nie możemy więc korzystać z wbudowanych w język PHP funkcji przeznaczonych do pracy z łańcuchami, by za ich pomocą tworzyć większy dokument, który korzystać będzie z dynamicznych nazw znaczników i dynamicznych danych. Sterownik ten nie powinien być używany w dokumentach, które zawierają złożone struktury przygotowywane w trakcie działania programu. Tworzenie dokumentów XML za pomocą pakietu XML_Serializer Wprawdzie pakiet XML_Serializer służy również do tworzenia dokumentów XML, robi to jednak w zupełnie inny sposób niż omówione wyżej pakiety XML_Util i XML_FastCreate. W przypadku tamtych pakietów musieliśmy tworzyć dokument znacznik po znaczniku, dla każdego przywołując osobną metodę. Natomiast kiedy korzystamy z pakietu XML_Serializer, przywołujemy tylko jedną metodę, która tworzy od razu cały dokument. Pobierze ona odpowiednie informacje z tablicy lub obiektu i przekonwertuje je na dokument XML. Rozwiązanie to nie daje aż tyle swobody, co poprzednie dwa podejścia, niemniej pakiet XML_Serializer ciągle jest jednym z najlepszych pakietów służących do tworzenia dokumentów XML. Potrafi serializować dowolne dane, które prześlemy mu jako dokument XML. Dzięki temu jest w stanie przygotować łańcuch reprezentujący praktycznie dowolne dane w formacie XML. Można go traktować jako ekwiwalent XML wbudowanej w język PHP funkcji serialize(), która pozwala na zapisywanie w postaci łańcucha dowolnych danych, niezależnie od tego, czy są to wielokrotnie zagnieżdżone tablice, czy złożone obiekty drzew. Tak przygotowany łańcuch można zapisać w pliku, w sesji użytkownika lub nawet w bazie danych. Język PHP dostarcza również funkcji unserialize(), mogącej odtworzyć oryginalną strukturę danych z łańcucha, w którym zostały serializowane. W drugiej części tego rozdziału poznamy również klasę XML_Unserializer, która robi dokładnie to samo z dokumentami XML przygotowanymi przez klasę XML_Serializer. 118 Rozdział 3. • Praca z formatem XML Oto typowy przebieg pracy z pakietem XML_Serializer: Załączamy do kodu pakiet XML_Serializer i tworzymy nową instancję klasy. Konfigurujemy instancję używając odpowiednich opcji. Tworzymy dokument XML. Pobieramy dokument i robimy z nim to, do czego był nam potrzebny. Korzystanie z pakietu XML_Serializer w prawdziwych aplikacjach nie będzie ani odrobinę trudniejsze. Ponieważ tworząc dokument XML przywołujemy tylko jedną metodę, będziemy musieli przesłać jej wszystkie informacje, które powinny znaleźć się w dokumencie XML. Aby maksymalnie ułatwić programiście zadanie, pakiet XML_Serializer pozwala na przesyłanie tej metodzie praktycznie dowolnych danych przeznaczonych dla generowanego dokumentu XML. Starczy jednak tej teorii; najlepszym sposobem pokazania, jak działa pakiet XML_Serializer, będzie zaprezentowanie odpowiedniego przykładu: // załączamy klasę require_once('XML/Serializer.php'); // tworzymy nowy obiekt $serializer = new XML_Serializer(); // tworzymy dokument XML $serializer->serialize('To jest łańcuch'); // pobieramy dokument echo $serializer->getSerializedData(); W tym przykładzie wykonaliśmy po kolei kroki opisane wyżej. Po uruchomieniu kodu otrzymamy następujący wynik: To jest łańcuch Nie jest to tak złożony dokument, jak wcześniejsze, i prawdopodobnie łatwiej byłoby go utworzyć używając pakietu XML_Util, XML_FastCreate lub nawet narzędzi do sklejania łańcuchów oferowanych przez język PHP. Niemniej kolejny przykład powinien zdecydowanie zmienić nastawienie tych czytelników, którzy nie są do niego przekonani: $data = array( 'artist' => 'Elvis Presley', 'label' => 'Sun Records', 'record' => 'Viva Las Vegas' ); // załączamy klasę require_once('XML/Serializer.php'); // tworzymy nowy obiekt $serializer = new XML_Serializer(); // tworzymy dokument XML $serializer->serialize($data); 119 PEAR. Programowanie w PHP // pobieramy dokument echo $serializer->getSerializedData(); W tym przykładzie zmieniły się tylko dwie rzeczy: Została utworzona zmienna $data zawierająca tablicę z danymi. Zamiast łańcucha z danymi metodzie serialize() przesyłana jest zmienna $data. Reszta skryptu pozostała niezmieniona i nadal działa w taki sam sposób jak poprzedni. Przyjrzyjmy się informacją zwróconym przez ten skrypt: Elvis Presley Sun Records Viva Las Vegas Tworzenie tego dokumentu XML byłoby znacznie trudniejsze, gdybyśmy próbowali zrobić to innym sposobem. Gdybyśmy chcieli dodać więcej danych i kolejne, zagnieżdżone znaczniki XML, utworzenie takiego dokumentu za pomocą pakietów XML_Util lub XML_FastCreate byłoby znacznie trudniejsze. W przypadku korzystania z pakietu XML_Serialize zagnieżdżony kod będzie zawsze taki sam, a ponadto będziemy mogli przesłać funkcji serialize() następujące dane, nie zmieniając w kodzie niczego więcej: $data = array( 'artist' => array( 'name' => 'Elvis Presley', 'email' => '[email protected]' ), 'label' => 'Sun Records', 'record' => 'Viva Las Vegas' ); Jak można się było spodziewać, skrypt ten wygeneruje następujący dokument XML: Elvis Presley [email protected] Sun Records Viva Las Vegas Teraz wiemy już, jak działa pakiet XML_Serializer: programista przesyła metodzie serialize() dowolną strukturę danych PHP, a metoda tworzy na podstawie przesłanych jej danych odpowiedni dokument XML. Pakiet XML_Serializer stara się zgadnąć, w jaki sposób dokument powinien zostać utworzony, tj. jako nazwę głównego znacznika wykorzystuje typ danych, klucze tablicy, jako nazwy wewnętrznych znaczników, i zagnieżdża znaczniki w taki sam sposób, 120 Rozdział 3. • Praca z formatem XML w jaki zagnieżdżone są tablice. Wcześniej wspomniane umożliwiają programiście wpływanie na to, w jaki sposób zgadywanie to będzie działać. Dlatego teraz omówimy najważniejsze opcje pakietu XML_Serializer. Opcje pakietu XML_Serializer Począwszy od wersji 0.17.0, pakiet XML_Serializer oferuje aż 27 różnych opcji. Dla każdej z tych opcji pakiet XML_Serializer dostarcza stałej zaczynającej się od przedrostka XML_SERIALIZER_ OPTION, po którym następuje nazwa opcji. Wartości opcji można określać na jeden z trzech sposobów: Przesłać konstruktorowi klasy XML_Serializer tablicę asocjacyjną zawierającą wybrane opcje i ich wartości. Skorzystać z metod setOption() i setOptions() klasy XML_Serializer. Przesłać metodzie serialize() jako drugi argument tablicę asocjacyjną zawierającą wybrane opcje i przypisane im wartości. Podczas gdy dwa pierwsze sposoby są w zasadzie równoważne i można ich używać do określania opcji dla wszystkich kolejnych dokumentów XML, ostatni sposób pokryje tylko opcje dokumentu, który będzie tworzony w bieżącym odwołaniu do metody serialize(). W większości przypadków zaleca się definiować opcje za pomocą wielokrotnego przywoływania metody setOption(), ponieważ gwarantuje ona większą czytelność skryptów. Teraz, gdy wiemy już, jak ustawiać opcje pakietu XML_Serializer, pora wrócić do utworzonego przez nas dokumentu i spróbować za pomocą odpowiednich opcji zmodyfikować ostateczny wygląd dokumentu XML. Pierwsza rzecz, która rzuca się w oczy, to brak w utworzonym dokumencie XML deklaracji dokumentu XML. Oczywiście można by ją było dodać ręcznie już po utworzeniu dokumentu przez pakiet XML_Serializer, ale łatwiej jest zlecić to zadanie pakietowi XML_Serializer. Trzeba w tym celu dodać jedynie dwa wiersze kodu: // załączamy klasę require_once('XML/Serializer.php'); // tworzymy nowy obiekt $serializer = new XML_Serializer(); // definiujemy opcje $serializer->setOption(XML_SERIALIZER_OPTION_XML_DECL_ENABLED, true); $serializer->setOption(XML_SERIALIZER_OPTION_XML_ENCODING, 'ISO-8859-1'); // tworzymy dokument XML $serializer->serialize($data); // pobieramy dokument echo $serializer->getSerializedData(); 121 PEAR. Programowanie w PHP Teraz nasz dokument będzie posiadał odpowiednią deklarację XML definiującą sposób kodowania wykorzystywany w dokumencie. Następnym naszym krokiem będzie upiększenie odrobinę dokumentu poprzez dodanie odpowiedniego wcięcia justującego znaczniki i zmieniającego nazwę głównego znacznika, ponieważ nazwa array (tablica) niewiele nam mówi. Ponownie wystarczy dodać jedynie dwa wiersze: $serializer->setOption(XML_SERIALIZER_OPTION_INDENT, ' '); $serializer->setOption(XML_SERIALIZER_OPTION_ROOT_NAME, 'artist-info'); Jeśli teraz przyjrzymy się wynikowi działania skryptu, przekonamy się, że nasz dokument XML wygląda obecnie o wiele lepiej: Elvis Presley [email protected] Sun Records Viva Las Vegas Dodawanie atrybutów Dokumenty XML bardzo rzadko zawierają same tylko znaczniki bez atrybutów. Dlatego też warto dowiedzieć się, w jaki sposób za pomocą pakietu XML_Serializer tworzyć znaczniki zawierające atrybuty oraz znaczniki zagnieżdżone i dane znakowe. Jak się przekonamy, jest to równie proste, jak wszystko co do tej pory robiliśmy używając pakietu XML_Serializer. Pakiet XML_Serializer potrafi automatycznie konwertować zmienne skalarne (łańcuchy, wartości logiczne, liczby całkowite itp.) na atrybuty odpowiedniego nadrzędnego znacznika. Wszystko, co trzeba w tym celu zrobić, to włączyć jedną opcję: $serializer->setOption(XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES, true); Jeśli dodamy ten kod do naszego skryptu, po czym uruchomimy go ponownie, to otrzymany dokument XML będzie wyglądał zupełnie inaczej: Jeśli natomiast chcemy tylko przekonwertować wartości łańcuchowe zapisane w tablicy artist znacznika i jednocześnie zachować nienaruszone znaczniki i , to da się to również bez trudu zrobić: 122 Rozdział 3. • Praca z formatem XML $serializer->setOption(XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES, array( 'artist' => true ) ); Możemy nawet indywidualnie dla każdego znacznika decydować, którą z wartości chcemy dodać jako atrybut. Jeśli potrzebujemy adresu e-mail przechowywanego w atrybucie, ale jednocześnie chcemy dodać zagnieżdżony znacznik przeznaczony dla nazwiska artysty, to wystarczy zmienić tylko jeden wiersz w naszym skrypcie: $serializer->setOption(XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES, array( 'artist' => array('email') ) ); Jeśli teraz wykonamy skrypt, zwróci on następujący dokument XML: Elvis Presley Sun Records Viva Las Vegas Kolejna opcja umożliwiająca dodawanie atrybutów do dokumentu XML to ROOT_ATTRIBS. Wraz z opcją tą można przesłać tablicę asocjacyjną określającą atrybuty głównego elementu (znacznika) XML. Wykorzystywanie tablic indeksowanych Większość artystów w trakcie swojej kariery wydaje więcej niż jedną płytę oraz podpisuje kontrakty z więcej niż jednym studiem nagrań (określonym przez etykietę). Jeśli spróbujemy uwzględnić ten fakt w naszym przykładzie, otrzymamy prawdopodobnie strukturę danych zbliżoną do takiej oto tablicy: $data = array( 'artist' => array( 'name' => 'Elvis Presley', 'email' => '[email protected]' ), 'labels' => array( 'Sun Records', 'Sony Music' ), 'records' => array( 123 PEAR. Programowanie w PHP 'Viva Las Vegas', 'Hound Dog', 'In the Ghetto' ) ); Ponieważ pakiet XML_Serializer potrafi przekształcić na dokument XML praktycznie dowolne dane, możemy przesłać mu również i tę tablicę, mając nadzieję, że przygotuje na jej podstawie jakiś użyteczny kod XML. Jeśli spróbujemy uruchomić dla tej tablicy nasz skrypt, to zwróci on następujący dokument XML: Elvis Presley Sun Records Sony Music Viva Las Vegas Hound Dog In the Ghetto To, co prawdopodobnie rzuci się w oczy czytelnikowi zaraz po wyświetleniu tego dokumentu na ekranie, jest często pojawiający się znacznik . Osoby znające się trochę na XML-u bez trudu domyślą się, skąd on się wziął. Podczas serializowania tablicy pakiet XML_Serializer używa jako nazw znaczników kluczy tablicy, a jako ich zawartości — przypisanych kluczowi wartości. W tym przykładzie dane zawierają dwie indeksowane tablice, których klucze są kolejnymi liczbami porządkowymi: „0”, „1” i „2”. Jednakże znaczniki , i nie byłyby prawidłowymi znacznikami XML. Ponieważ pakiet XML_Serializer mimo takich niedogodności nadal potrafi przygotować poprawnie sformułowany dokument XML, zamiast używać nieprawidłowych nazw znaczników, skorzysta z domyślnej nazwy znacznika. Oczywiście tę domyślną nazwę znacznika można zmieniać: $serializer->setOption(XML_SERIALIZER_OPTION_DEFAULT_TAG, 'item'); Gdy dodamy ten wiersz do skryptu, to po jego uruchomieniu otrzymamy odrobinę inny dokument XML niż poprzednio, gdyż znaczniki zostaną zastąpione przez znacznik . Pakiet XML_Serializer daje nam jednak jeszcze więcej swobody w wyborze domyślnych znaczników. Najlepszym rozwiązaniem byłoby, gdyby znacznik nagrań zawierał znaczniki dla każdego pojedynczego nagrania, a znacznik etykiet zawierał znacznik dla każdego studia nagrań, z którym artysta podpisał kontrakt. Można to łatwo sprokurować, ponieważ pakiet XML_Serializer umożliwia definiowanie różnych domyślnych nazw znacznika w zależności od kontekstu. Zamiast łańcucha za- 124 Rozdział 3. • Praca z formatem XML wierającego domyślny znacznik trzeba po prostu opcji DEFAULT_TAG przesłać tablicę asocjacyjną. Klucze tej tablicy asocjacyjnej powinny definiować znaczniki nadrzędne, a wartości określać domyślną nazwę znacznika dla znaczników utworzonych w obrębie danego znacznika nadrzędnego: $serializer->setOption(XML_SERIALIZER_OPTION_DEFAULT_TAG, array( 'labels' => 'label', 'records' => 'record' ) ); Po tej modyfikacji otrzymamy następujący dokument XML: Elvis Presley Sun Records Sony Music Viva Las Vegas Hound Dog In the Ghetto Wiemy już, jak korzystać z najważniejszych opcji oferowanych przez pakiet XML_Serializer. Zanim jednak przystąpimy do implementowania skryptu, który utworzy dokument XML z przygotowanego wcześniej drzewa obiektów, warto zapoznać się z innymi opcjami oferowanymi przez pakiet XML_Serialize, które zestawiliśmy w tabeli: Nazwa opcji Opis Wartość domyślna INDENT Łańcuch używany do justowania znaczników za pomocą wcięć. Pusty LINEBREAKS Łańcuch używany do dzielenia wierszy. \n XML_DECL_ENABLED Określa, czy dodawać do tworzonego dokumentu deklarację XML. false XML_ENCODING Kodowanie dokumentu, jeśli opcji XML_DECL_ENABLED zostanie przypisana wartość true. UTF-8 DOCTYPE_ENABLED Informuje, czy dodawać do dokumentu deklarację typu dokumentu. false DOCTYPE Nazwa pliku z deklaracją dokumentu. Wykorzystywana tylko wtedy, gdy opcji DOCTYPE_ENABLED zostanie przypisana wartość true. Bez wartości domyślnej 125 PEAR. Programowanie w PHP Nazwa opcji Opis Wartość domyślna ROOT_NAME Nazwa głównego znacznika dokumentu. Zależy od serializowanych danych ROOT_ATTRIBS Atrybuty znacznika dokumentu. Pusta tablica NAMESPACE Przestrzeń nazw wykorzystywana w dokumencie. Bez wartości domyślnej ENTITIES Określa, czy kodować specjalne jednostki XML w danych znakowych i atrybutach. true (prawda) RETURN_RESULT Określa, czy po udanej serializacji danych funkcja serialize() powinna zwracać pełny wynik serializacji, czy tylko wartość true. false CLASSNAME_AS ¦_TAGNAME Określa, czy podczas serializowania obiektów należy używać nazwy klasy jako nazwy znacznika. false DEFAULT_TAG Nazwa domyślnego znacznika. Opcja używana zazwyczaj podczas serializowania tablic indeksowanych. Nazwę można podać za pomocą łańcucha lub tablicy asocjacyjnej, by określić domyślne znaczniki w zależności od ich znacznika nadrzędnego. XML_Serializer_Tag TYPEHINTS Określa, czy dodawać do znacznika informacje o typie. false ATTRIBUTE_TYPE Nazwa atrybutu, który przechowuje informacje o typie, jeśli włączona jest opcja podpowiedzi TYPEHINTS. _type ATTRIBUTE_CLASS Nazwa atrybutu, który przechowuje nazwę klasy, jeśli włączona jest opcja podpowiedzi TYPEHINTS. _class ATTRIBUTE_KEY Nazwa atrybutu, który przechowuje nazwę klucza tablicy, jeśli włączona jest opcja TYPEHINTS. _originalKey SCALAR_AS ¦_ATTRIBUTES Określa, czy do atrybutów powinny być dodawane wartości false skalarne (łańcuchy, liczby całkowite itp.). PREPEND ¦_ATTRIBUTES Łańcuch znaków poprzedzający nazwy atrybutów. Bez wartości domyślnej INDENT_ATTRIBUTES Łańcuch wykorzystywany do tworzenia wcięć justujących Bez wartości domyślnej atrybuty, kiedy każdy atrybut wyświetlany jest w pojedynczym wierszu. Opcji tej można przypisać wartość _auto. IGNORE_NULL Opcja informująca, czy podczas serializowania obiektów i tablic ignorować wartości null. false TAGMAP Tablica asocjacyjna umożliwiająca mapowanie kluczy i nazw właściwości na różne nazwy znaczników. Bez wartości domyślnej MODE Określa, jakiego trybu należy używać podczas serializowania tablic indeksowanych: XML_SERIALIZER_MODE_DEFAULT czy XML_SERIALIZER_MODE_SIMPLEXML. DEFAULT 126 Rozdział 3. • Praca z formatem XML Nazwa opcji Opis Wartość domyślna ATTRIBUTES_KEY Wszystkie wartości przechowywane pod kluczem określonym przez tę opcję będą serializowane jako atrybuty. Bez wartości domyślnej CONTENT_KEY Wszystkie wartości przechowywane pod kluczem określonym przez tę opcję będą traktowane jako dane znakowe, a nie dane służące do tworzenia kolejnego znacznika. Opcji tej należy używać w połączeniu z opcją ATTRIBUTES_KEY. Bez wartości domyślnej COMMENT_KEY Wszystkie wartości przechowywane pod kluczem określonym przez tę opcję będą konwertowane na komentarze XML. Bez wartości domyślnej ENCODE_FUNC Nazwa funkcji PHP lub metody stosowanej przed serializacją na każdej z serializowanych wartości. Bez wartości domyślnej Tworzenie dokumentu XML z drzewa obiektów Skoro zapoznaliśmy się z działaniem pakietu XML_Serializer, pora powrócić do zadania, które sobie wyznaczyliśmy, i utworzyć dokument XML z wcześniej utworzonych obiektów zawierających informacje o studiach nagraniowych (będących etykietami albumu), artystach oraz nagranych przez nich albumach. Ponieważ pakiet XML_Serializer akceptuje jako dane wykorzystywane do tworzenia dokumentu XML dowolną zmienną PHP, nasze zadanie najprościej można będzie zrealizować przesyłając zmienną $labels, która zawierać będzie jeden lub więcej obiektów Label. Dodatkowo określimy kilka opcji, które będą nam potrzebne: // załączamy klasę require_once('XML/Serializer.php'); // tworzymy nowy obiekt $serializer = new XML_Serializer(); // konfigurujemy deklarację XML $serializer->setOption(XML_SERIALIZER_OPTION_XML_DECL_ENABLED, true); $serializer->setOption(XML_SERIALIZER_OPTION_XML_ENCODING, 'ISO-8859-1'); // konfigurujemy układ dokumentu $serializer->setOption(XML_SERIALIZER_OPTION_INDENT, ' '); $serializer->setOption(XML_SERIALIZER_OPTION_LINEBREAKS, "\n"); // tworzymy dokument XML $serializer->serialize($labels); // pobieramy dokument echo $serializer->getSerializedData(); Kod ten utworzy następujący dokument XML, który nareszcie wygląda prawie tak, jak byśmy chcieli: 127 PEAR. Programowanie w PHP Sun Records 1 Elvis Presley SUN 209 That's All Right (Mama) & Blue Moon Of Kentucky July 19, 1954 SUN 210 Good Rockin' Tonight September, 1954 2 Carl Perkins SUN 224 Gone, Gone, Gone July 19, 1954 Niemniej pozostało jeszcze kilka problemów do rozwiązania: Główny element w dokumencie powinien nazywać się . Wystąpienia znacznika powinny zostać zastąpione znacznikami , i . Niektóre ze znaczników (takie jak , i ) powinny zostać zastąpione odpowiadającymi im elementami. Wiemy już jednak z poprzednich przykładów, jak rozwiązać te problemy, używając odpowiednich opcji: 128 Rozdział 3. • Praca z formatem XML Główny element można zmienić za pomocą opcji ROOT_NAME. Znaczniki można zastąpić przy użyciu opcji DEFAULT_TAG, przesyłając jej odpowiednią tablicę definiującą nowe nazwy znaczników. Za pomocą opcji SCALAR_AS_ATTRIBUTES można określić, które informacje powinny być serializowane jako atrybuty, a nie jako znaczniki. Oto kompletny skrypt, w którym wszystkie opcje zostały już odpowiednio zdefiniowane. Zmiany w stosunku do poprzedniej wersji skryptu zostały wytłuszczone: // załączamy klasę require_once('XML/Serializer.php'); // tworzymy nowy obiekt $serializer = new XML_Serializer(); // konfigurujemy deklarację XML $serializer->setOption(XML_SERIALIZER_OPTION_XML_DECL_ENABLED, true); $serializer->setOption(XML_SERIALIZER_OPTION_XML_ENCODING, 'ISO-8859-1'); // konfigurujemy układ strony $serializer->setOption(XML_SERIALIZER_OPTION_INDENT, ' '); $serializer->setOption(XML_SERIALIZER_OPTION_LINEBREAKS, "\n"); // konfigurujemy nazwy znaczników $serializer->setOption(XML_SERIALIZER_OPTION_ROOT_NAME, 'labels'); $tagNames = array( 'labels' => 'label', 'artists' => 'artist', 'records' => 'record' ); $serializer->setOption(XML_SERIALIZER_OPTION_DEFAULT_TAG, $tagNames); $attributes = array( 'label' => array('name'), 'artist' => array('id'), 'record' => array('id', 'released') ); $serializer->setOption(XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES, $attributes); $result = $serializer->serialize($labels); echo $serializer->getSerializedData(); Przesyłanie obiektów metodzie _sleep() Ostatni przykład udowodnił, że pakiet XML_Serializer jest w stanie sprawnie pracować nie tylko z tablicami, ale również z obiektami. Pobierze wszystkie publiczne właściwości obiektu i serializuje je dokładnie w taki sam sposób, jakby były wartościami przechowywanymi w tablicy. 129 PEAR. Programowanie w PHP Niemniej w niektórych przypadkach może to nie być najwłaściwsze rozwiązanie. Rozważmy następujący przykład kodu: class UrlFetcher { public $url = null; public $html = null; public function __construct($url) { $this->url = $url; $this->html = file_get_contents($this->url); } } $pear = new UrlFetcher('http://pear.php.net'); $serializer = new XML_Serializer(); $serializer->setOption(XML_SERIALIZER_OPTION_XML_DECL_ENABLED, true); $serializer->setOption(XML_SERIALIZER_OPTION_XML_ENCODING, 'ISO-8859-1'); $serializer->setOption(XML_SERIALIZER_OPTION_INDENT, ' '); $serializer->serialize($pear); echo $serializer->getSerializedData(); Jeśli utworzymy nowy obiekt klasy UrlFetcher, obiekt ten pobierze zawartość HTML spod adresu URL zdefiniowanego w konstruktorze. Jeśli prześlemy ten obiekt pakietowi XML_ Serializer, wydobędzie on z obiektu wszystkie publiczne właściwości i doda je do przygotowywanego dokumentu XML. Dokument ten będzie wyglądać mniej więcej tak: http://pear.php.net ...dla przejrzystości usunęliśmy większosć kodu HTML... W tym przypadku jednak raczej nie zależy nam na tym, aby pakiet XML_Serializer umieszczał w dokumencie XML cały kod pobrany z dość rozbudowanej witryny pear.php.net. Można tego uniknąć korzystając z techniki, która może być znana czytelnikom z serializacji obiektów za pomocą funkcji serialize() języka PHP. Jeśli obiekt, który będzie serializowany przez pakiet XML_Serialize, implementuje metodę _sleep(), to będzie można ją przywołać i użyć zwróconą przez nią wartość w procesie serializacji. Metoda _sleep() powinna zwrócić tablicę zawierającą właściwości obiektu, które powinny zostać dołączone do przygotowywanego dokumentu. Aby zapobiec serializacji właściwości $html, należy teraz wprowadzić jedynie drobną zmianę w klasie UrlFetcher: 130 Rozdział 3. • Praca z formatem XML class UrlFetcher { public $url = null; public $html = null; public function __construct($url) { $this->url = $url; $this->html = file_get_contents($this->url); } public function __sleep() { return array('url'); } } Po wprowadzeniu tej zmiany skrypt przygotuje następujący dokument XML: http://pear.php.net Dodawanie informacji o typie Ostatnią z użytecznych funkcji pakietu XML_Serializer, o której warto tu wspomnieć, jest możliwość dodawania do znaczników XML informacji o typie. Funkcje tę włącza się za pomocą pojedynczej opcji: $serializer = new XML_Serializer(); // konfigurujemy deklarację XML $serializer->setOption(XML_SERIALIZER_OPTION_XML_DECL_ENABLED, true); $serializer->setOption(XML_SERIALIZER_OPTION_XML_ENCODING, 'ISO-8859-1'); $serializer->setOption(XML_SERIALIZER_OPTION_TYPEHINTS, true); // konfigurujemy układ strony $serializer->setOption(XML_SERIALIZER_OPTION_INDENT, ' '); $serializer->setOption(XML_SERIALIZER_OPTION_LINEBREAKS, "\n"); $serializer->setOption(XML_SERIALIZER_OPTION_DEFAULT_TAG, $tagNames); $result = $serializer->serialize($labels); echo $serializer->getSerializedData(); Przypisując opcji TYPEHINTS wartość true, informujemy pakiet XML_Serializer, aby do ujętych w znacznik danych dołączał jako atrybut informacje o typie oraz oryginalną nazwę klucza tablicy lub właściwości, jeśli nie może być użyta jako nazwa znacznika. 131 PEAR. Programowanie w PHP Utworzony w ten sposób dokument (gdy metodzie serialize() przesyłana jest tablica obiektów Label) będzie wyglądał tak: Sun Records 1 Elvis Presley SUN 209 That's ... Kentucky July 19, 1954 SUN 210 Good Rockin' Tonight September, 1954 2 Carl Perkins SUN 224 Gone, Gone, Gone July 19, 1954 132 Rozdział 3. • Praca z formatem XML Możliwość dodawania informacji o typie okazuje się szczególnie przydatna, gdy zależy nam na możliwości przekonwertowania otrzymanych danych XML z powrotem na taką samą strukturę danych, z jakiej został utworzony. Dzięki niej możemy wykorzystywać pakiet XML_Serializer (i odwracający jego działanie pakiet XML_Unserializer, który omówimy w dalszej części tego rozdziału), jako wygodne zastępstwo dla funkcji serialize() i unserialize(). W tej części rozdziału omówiliśmy trzy różne pakiety umożliwiające tworzenie dokumentów XML. Pojawia się jednak pytanie, który z nich najlepiej pasować będzie do konkretnej sytuacji? W przypadku gdy mamy już wszystkie dane zebrane w jakiejś dużej strukturze danych, najlepszym rozwiązaniem z uwagi na bogactwo oferowanych opcji będzie pakiet XML_Serializer. Jeśli tworzymy strukturę z danych, które wyliczane są w czasie przygotowywania dokumentu, to najlepszym rozwiązaniem będzie zapewne pakiet XML_FastCreate. Można go również wykorzystać do tworzenia dokumentów XML w sposób programistyczny. Taka była zresztą intencja twórców tego pakietu. Z pakietu XML_Util należy natomiast korzystać wtedy, gdy chcemy przygotować bardzo mały dokument XML lub tylko fragment dokumentu. Tworzenie aplikacji Mozilli za pomocą pakietu XML_XUL Dotychczas tworzyliśmy tylko dokumenty XML w zdefiniowanym przez nas samych formacie. Niemniej istnieją oczywiście aplikacje XML, które stworzyły pewne standardy formatów XML zaakceptowane przez konsorcjum W3C. Repozytorium PEAR dostarcza kilku pakietów, które ułatwiają tworzenie dokumentów XML dla tych aplikacji; jednym z nich jest pakiet XML_XUL. Dokumenty XUL XUL to skrót od XML User Interface Language (oparty na XML język do tworzenia interfejsu użytkownika); jest on częścią projektu Mozilla. Dokładną specyfikację języka XUL v1.0 (wersja 1.0) można znaleźć w witrynie internetowej Mozilli, pod adresem: http://www.mozilla.org/projects/ ¦xul/xul.html. Język XUL wykorzystywany jest przez aplikacje Mozilli (takie jak Firefox czy Thunderbird) do definiowania, jaką strukturę powinien mieć interfejs użytkownika. Język XUL można łączyć z językiem JavaScript, arkuszami CSS i szkieletem RDF, by tworzyć interaktywne aplikacje, które będą mogły sięgać do wielu różnych źródeł danych. Prawdę powiedziawszy, obecnie praktycznie każdy dodatek dla przeglądarki Firefox lub programu pocztowego Thunderbird jest przygotowany za pomocą języków XUL i JavaScript. Przy użyciu języka XUL 133 PEAR. Programowanie w PHP znacznie łatwiej niż za pomocą HTML tworzy się rozbudowane interfejsy użytkownika, ponieważ język XUL został zaprojektowany właśnie w tym celu, podczas gdy język HTML miał pierwotnie służyć tylko publikowaniu w sieci WWW informacji posiadających określoną strukturę. Dlatego też język HTML oferuje znaczniki umożliwiające porządkowanie tekstu w akapitach, listach i statycznych tabelach HTML, natomiast język XUL udostępnia znaczniki pozwalające na tworzenie sortowalnych siatek danych, pól wyboru koloru czy drzew podobnych do drzewa katalogów w programie Windows Explorer (Eksplorator Windows). Zakończmy już jednak ten wykład; przyjrzyjmy się dokumentowi XUL: 134 Rozdział 3. • Praca z formatem XML Miejsce na inne informacje. Ponieważ język XUL jest zasadniczo odmianą języka XML, dokument ten zaczyna się od deklaracji XML. Po niej następuje kolejna deklaracja, która służy do załączania arkuszy stylów spod adresu URL chrome://global/skin/. Należy wyjaśnić, że chrome to specjalny protokół wykorzystywany wtedy, gdy zachodzi konieczność sięgania do wewnętrznych danych Mozilli. W tym przypadku służy do załączania arkusza stylów, który użytkownik wybrał dla swojej instalacji oprogramowania Mozilla, dzięki czemu wygląd aplikacji będzie idealnie współgrał z wyglądem przeglądarki internetowej. Po tej deklaracji pojawia się element główny całego dokumentu. W większości przypadków będzie to element okna . Wewnątrz elementu zagnieżdżonych zostało kilka innych elementów, takich jak i . Jeśli otworzymy ten dokument w przeglądarce Firefox lub Mozilla, powinniśmy zobaczyć następujący widok: Rysunek 3.1. Kontrolka listy przygotowana za pomocą XUL 135 PEAR. Programowanie w PHP Oczywiście dokładny układ zależeć będzie od motywu (ang. theme), który wykorzystujemy w naszej instalacji przeglądarki Mozilla lub Firefox. Jeśli spróbujemy kliknąć w obrębie tego okna, przekonamy się, że zakładki u góry list oraz element drzewa są w pełni funkcjonalne i możemy ukrywać poszczególne kolumny elementu drzewa. Spróbujmy wyobrazić sobie implementowanie takiego interfejsu za pomocą języka HTML, arkuszy stylów CSS lub języka JavaScript. Ile godzin pracy by nam to zajęło! Przykład ten pokazuje zalety XUL w stosunku do języka XML — XUL jest znakomitym narzędziem do tworzenia interfejsów użytkownika dla aplikacji WWW. Niemniej język XUL ma również swoją ciemniejszą stronę: Język XUL działać będzie tylko w aplikacjach przygotowanych w ramach projektu Mozilla. Ani użytkownicy przeglądarek Microsoft Internet Explorer ani Opera nie będą w stanie korzystać z naszych aplikacji. Język XUL (podobnie jak większość aplikacji XML) jest dość rozbudowany i zawiera wiele wielokrotnie zagnieżdżonych dokumentów XML. Tworzenie dokumentów XUL za pomocą pakietu XML_XUL Repozytorium PEAR dostarcza pakietu oprogramowania, który pozwala rozwiązać drugie ze wspomnianych problemów: za pomocą pakietu XML_XUL można utworzyć dokument XUL, korzystając z łatwego w użyciu interfejsu API języka PHP. Interfejs API pakietu XML_XUL przypomina standardowy interfejs DOM-API — za pomocą pakietu budujemy w pamięci drzewo obiektów, które następnie możemy przenosić i modyfikować do czasu, aż osiągniemy pożądany rezultat. Gdy już będziemy zadowoleni z przygotowanego drzewa, możemy je serializować tworząc kod XML, który następnie można wysłać do przeglądarki. Różnica w stosunku do modelu DOM (ang. Document Object Model, model obiektów dokumentu) polega na tym, że tym razem mamy do dyspozycji nie jedną klasę, jak w modelu DOM, ale kilka różnych klas dla różnych elementów interfejsu (ang. widgets) oferowanych przez język XUL. Klasy dostarczają pomocniczych metod, które umożliwiają na przykład dodawanie nowej zakładki do kart z zakładkami za pomocą pojedynczego wywołania metody, bez konieczności budowania na własną rękę całego drzewa obiektów. Podstawowe kroki wykonywane podczas tworzenia skryptu za pomocą pakietu XML_XUL zawsze są takie same: 1. Załączenie głównej klasy XML_XUL. 2. Utworzenie nowego dokumentu. 3. Utworzenie nowych elementów i złożenie w pamięci drzewa obiektów. 4. Serializacja dokumentu XUL i wysłanie go do przeglądarki. Skomplikowana procedura? Prawdę powiedziawszy, nie jest tak uciążliwa, jak by się mogło wydawać. Oto nasz pierwszy skrypt korzystający z pakietu XML_XUL: require_once 'XML/XUL.php'; // tworzymy nowy dokument $doc = XML_XUL::createDocument(); // łączymy go z arkuszem stylów wybranym przez użytkownika $doc->addStylesheet('chrome://global/skin/'); 136 Rozdział 3. • Praca z formatem XML // tworzymy nowe okno $win = $doc->createElement('window',array( 'title'=> 'Simple XUL' ) ); // dodajemy je do dokumentu $doc->addRoot($win); // tworzymy kolejny element $desc = $doc->createElement('description', array(), 'To w istocie jest XUL.'); $win->appendChild($desc); header('Content-type: application/vnd.mozilla.xul+xml'); $doc->send(); Wykonaliśmy w nim właśnie czynności opisane wyżej. Dołączyliśmy główną klasę i utworzyliśmy nowy dokument korzystając z metody XML_XUL::createDocument(). Następnie zamiast dostarczać naszego własnego arkusza stylów CSS, utworzyliśmy wewnętrzny arkusz stylów CSS za pomocą metody addStylesheet(). Potem przystąpiliśmy do tworzenia elementów interfejsu i komponowania ich w drzewo obiektów (jest to prawdę powiedziawszy dość malutkie drzewko). Wszystkie elementy dodawane do dokumentu muszą być tworzone za pomocą metody createElement(), której przesyła się następujące parametry: Nazwę elementu, która będzie również nazwą tworzonego znacznika. Tablicę asocjacyjną zawierającą atrybuty elementu. Zawartość elementu. Wartość logiczną określającą, czy należy zastępować jednostki XML pojawiające się w treści dokumentu (domyślnie parametr ten ma wartość true, co włącza zastępowanie). Metoda ta zwraca instancję podklasy XML_XUL_Element. Aby dowiedzieć się, jakie elementy interfejsu obsługuje pakiet XML_XUL, należy zajrzeć do folderu XML/XUL/Element w katalogu naszej instalacji repozytorium PEAR. Drzewo elementów buduje się dodając do odpowiedniego już istniejącego elementu, za pomocą jego metody appendChild(), nowy element potomny. Gdy już zakończymy budowanie drzewa, należy przygotować odpowiedni nagłówek, aby przeglądarka Firefox wiedziała, jak powinna traktować dane, i wysłać go do przeglądarki za pomocą metody send(). Jeśli teraz otworzymy skrypt w przeglądarce, powinien ukazać się nasz pierwszy, dynamicznie utworzony dokument XUL. Jeśli przyjrzymy się kodowi źródłowemu dokumentu, łatwo wyłowimy niezbędny kod XUL: 137 PEAR. Programowanie w PHP To w istocie jest XUL. Bez trudu zauważymy tutaj elementy i , utworzone za pomocą metody createElemnt(). Wspomnieliśmy już, że pakiet XML_XUL umożliwia tworzenie dokumentów XUL w łatwy sposób z poziomu kodu PHP, bez konieczności sięgania po model DOM. Spróbujmy teraz wprowadzić pierwsze usprawnienia: require_once 'XML/XUL.php'; // tworzymy nowy dokument $doc = XML_XUL::createDocument(); // łączymy się z arkuszem stylów określonym przez użytkownika $doc->addStylesheet('chrome://global/skin/'); // tworzymy nowe okno $win = $doc->createElement('window',array( 'title'=> 'Simple XUL' ) ); // dodajemy je do dokumentu $doc->addRoot($win); $win->addDescription('To w istocie jest XUL.'); header( 'Content-type: application/vnd.mozilla.xul+xml' ); $doc->send(); Różnica pomiędzy tym przykładem a poprzednim polega na tym, że nowy element do okna dodajemy za pomocą metody $win->addDescription(), zamiast tworzyć i dodawać element ręcznie. Metoda ta jest obsługiwana przez wszystkie klasy reprezentujące elementy, ponieważ dodawanie zawartości tekstowej (opisu) jest jedną z częściej wykonywanych czynności. Następnym krokiem jest utworzenie drzewa podobnego do tego wyświetlanego we wcześniejszym przykładzie. Główny elementem potrzebnym do tego będzie klasa XML_XUL_Element_Tree, którą tworzy się dokładnie tak samo jak każdy inny element: $tree = $doc->createElement('Tree', array( 'flex' => 1, 'height' => 200 ) ); Aby zakończyć tworzenie drzewa, będziemy musieli utworzyć zagnieżdżone elementy i , które definiować będą kolumny drzewa. Za pomocą pakietu ZML_XUL robi się to o wiele prościej. Klasa XML_XUL_Element_Tree udostępnia metodę, która utworzy je dla nas: 138 Rozdział 3. • Praca z formatem XML $tree->setColumns(3, array( 'id' => 'id', 'label' => 'Id', 'flex' => 1, 'primary' => 'true' ), array( 'id' => 'name', 'label' => 'Name', 'flex' => 1 ), array( 'id' => 'email', 'label' => 'E-Mail', 'flex' => 1 ) ); W pierwszym argumencie określamy liczbę kolumn, które chcemy utworzyć, a w następnych argumentach przesyłamy tablice atrybutów dla każdej z nich. Po utworzeniu tej podstawowej struktury możemy zacząć dodawać do drzewa odpowiednie dane, używając do tego celu metody addItem() elementu Tree: $sun = $tree->addItem(array('SUN', 'Sun Records', '[email protected]')); Przywołując tę metodę, musimy przesłać jej tablicę zawierającą wartości dla każdej kolumny. Możemy przesłać również wartość łańcuchową, która używana będzie jako etykieta, lub tablicę asocjacyjną zawierającą wszystkie atrybuty danej kolumny. Metoda ta zwróci instancję klasy XML_XUL_Element_Treeitem, którą można zachować w zmiennej do późniejszego użytku. Na przykład możemy do tego elementu bezpośrednio dodawać elementy potomne, ponieważ nie budujemy prostej tabeli, a rekurencyjną strukturę drzewa: $sun->addItem(array('elvis', 'Elvis Presley', '[email protected]')); $sun->addItem(array('carl', 'Carl Perkins', '[email protected]')); Oczywiście nadal mamy możliwość dodawania do drzewa kolejnych elementów najwyższego poziomu lub nawet głębszego zagnieżdżania drzewa, przywołując w tym celu metodę addItem() na wartościach zwróconych przez wcześniejsze odwołania do metody addItem(). Gdy już zbudujemy drzewo, dodamy je do okna: $win->appendChild($tree); Jeśli otworzymy gotowy skrypt w oknie przeglądarki kompatybilnej z projektem Mozilla, zobaczymy interaktywny element kontrolny w formie drzewa. Podstawowa różnica między tym drzewem a drzewem z pierwszego przykładu jest taka, że nasze drugie drzewo zostało zbudowane dynamicznie za pomocą języka PHP, tak więc możemy korzystać z dowolnych zasobów oferowanych przez język PHP, gdy chcemy zapełnić drzewo danymi. 139 PEAR. Programowanie w PHP Tworzenia karty z zakładkami W tym podrozdziale dowiemy się, jak dodawać do naszego przykładu kontrolkę z zakładkami. Robi się to podobnie jak w przypadku kontrolki drzewa — korzystamy z elementu XML_XUL_ Element_Tabbox, który tworzy się dokładnie tak samo jak każdy inny element: $tabbox = &$doc->createElement('Tabbox', array('height' => 500)); $win->appendChild($tabbox); Po utworzeniu elementu tabbox dodajemy go do głównego okna. Ten nowo utworzony obiekt posiada metodę addTab(), która umożliwia tworzenie nowych zakładek: $tab1 = $tabbox->addTab('Labels'); Do obiektu zwróconego przez metodę addTab() można dodawać dowolne elementy potomne. Potomek tego elementu będzie wykorzystywany jako zawartość utworzonej zakładki. Metodzie addTab() można przesłać kilka parametrów: Etykietę zakładki. Element XML_XUL_Element, który można wykorzystać jako zawartość zakładki. Tablicę zawierającą atrybuty zakładki. Tablicę zawierającą atrybuty panelu zakładek. Skoro dowiedzieliśmy się już, jak za pomocą XML_XUL tworzyć karty z zakładkami, możemy teraz zaimplementować skrypt, który tworzy kod XUL przedstawiony na początku tego podrozdziału. require_once 'XML/XUL.php'; // tworzymy nowy dokument $doc = XML_XUL::createDocument(); // łączymy go z arkuszem stylów dostarczonym przez użytkownika $doc->addStylesheet('chrome://global/skin/'); // tworzymy nowe okno $win = $doc->createElement('window',array( 'title'=> 'Simple XUL' ) ); // dodajemy je do dokumentu $doc->addRoot($win); // Tworzymy karty z zakładkami i dodajemy je do okna $tabbox = &$doc->createElement('Tabbox', array('height' => 500)); $win->appendChild($tabbox); // Tworzymy nowe drzewo 140 Rozdział 3. • Praca z formatem XML $tree = &$doc->createElement('Tree', array( 'flex' => 1, 'height' => 200 ) ); // Określamy etykiety kolumn $tree->setColumns(3, array( 'id' => 'id', 'label' => 'Id', 'flex' => 1, 'primary' => 'true' ), array( 'id' => 'name', 'label' => 'Name', 'flex' => 1 ), array( 'id' => 'email', 'label' => 'E-Mail', 'flex' => 1 ) ); // dodajemy nowy element do drzewa $sun = $tree->addItem(array('SUN', 'Sun Records', 'info@sun-records. com')); // dodajemy do utworzonego elementu dwa nowe elementy niższego rzędu $sun->addItem(array('elvis', 'Elvis Presley', '[email protected]')); $sun->addItem(array('carl', 'Carl Perkins', '[email protected]')); // dodajemy nowy element do drzewa $tree->addItem(array('SONY', 'Sony Records', '[email protected]')); // dodajemy nową zakładkę do etykiety, zawartością zakładki będzie drzewo $tabbox->addTab('Labels', $tree, array(), array('height' => 200)); // dodajemy kolejną zakładkę, jednak bez zawartości $tab2 = $tabbox->addTab('Misc'); // do drugiej zakładki dodajemy przykładową zawartość tekstową $tab2->addDescription('Tutaj umieść jakąś zawartość.'); header( 'Content-type: application/vnd.mozilla.xul+xml' ); $doc->send(); 141 PEAR. Programowanie w PHP W większości przypadków tworzenie kodu XUL za pomocą języka PHP i pakietu XML_XUL będzie prostsze niż pisanie kodu XUL ręcznie — cały przykładowy kod XUL prezentowany w tej książce został utworzony przy użyciu języka PHP. Pakiet XML_XUL umożliwia odczytywanie już istniejących dokumentów XUL, modyfikowanie ich i zapisywanie z powrotem w pliku lub wyświetlanie w przeglądarce WWW. Co więcej, pakiet XML_XUL udostępnia informacje debugowania ułatwiające analizowanie drzew obiektów przechowywanych w pamięci. Na koniec wreszcie warto wspomnieć, że pakiet XML_XUL oferuje klasy pozwalające na tworzenie ponad 70 różnych elementów nawigacyjnych XUL. Przetwarzanie dokumentów XML W pierwszej części tego rozdziału dowiedzieliśmy się, jak tworzyć dokumenty XML z dowolnego źródła danych, używając różnych pakietów PEAR. Niemniej tworzenie kodu XML nie będzie miało sensu, jeśli użytkownik po drugiej stronie nie będzie mógł przetwarzać dokumentów XML, które przygotowaliśmy. Dlatego też w drugiej części tego rozdziału dowiemy się, które z pakietów repozytorium PEAR można wykorzystać do przetwarzania dokumentów XML. Potrzeba przetwarzania dokumentów XML pojawić się może w wielu różnych sytuacjach, ponieważ język XML staje się coraz popularniejszym narzędziem do tworzenia oprogramowania. Oto kilka typowych scenariuszy, w których może zajść konieczność odczytywania i pobierania informacji z dokumentów XML: Odczytywanie plików konfiguracyjnych w formacie XML. Importowanie do aplikacji danych, które zostały wyeksportowane przez inną aplikację właśnie w formacie XML. Wyświetlanie w naszej witrynie WWW zawartości, która została syndykowana przez inną aplikację lub witrynę WWW. Akceptowanie żądań usług WWW. Analiza odpowiedzi nadesłanej przez usługę WWW. Dwa ostatnie scenariusze zostaną dokładniej omówione w następnym rozdziale, istnieje jednak wiele zastosowań dokumentów XML niezwiązanych z usługami WWW. Repozytorium PEAR przychodzi tutaj programistom z pomocą. Zanim jednak przyjrzymy się pakietom PEAR odpowiedzialnym za analizę kodu XML, warto powiedzieć najpierw kilka słów o obsłudze języka XML w PHP. W PHP4 istniał tylko jeden pewny sposób pracy z kodem XML, oparte na parserze expat rozszerzenie xml. Rozszerzenie to umożliwiało analizowanie dokumentów XML korzystając z interfejsu API SAX. Interfejs SAX, którego nazwa jest skrótem od: Simple API to XML (Prosty interfejs API dla języka XML), oparty jest na zdarzeniach. Kiedy korzystamy z interfejsu SAX API, po prostu definiujemy kilka funkcji lub metod obsługujących różne zdarzenia, które mogą zajść podczas analizowania dokumentu. Mogą to być takie zdarzenia, jak otwieranie znaczni- 142 Rozdział 3. • Praca z formatem XML ków i zamykanie znaczników, otwieranie i zamykanie danych znakowych, przetwarzania instrukcji czy przetwarzanie komentarzy XML. Po zarejestrowaniu wszystkich niezbędnych funkcji zwrotnych należy przesłać dokument do parsera, który zanalizuje go znak po znaku, stopniowo przesuwając swój wewnętrzny kursor od początku do końca dokumentu. O analizowaniu dokumentów XML opartym na interfejsie SAX opowiemy dokładniej w dalszej części rozdziału, gdy omawiać będziemy pakiet XML_Parser. Język PHP5 oferuje natomiast cztery rozszerzenia, które ułatwiają przetwarzanie danych XML: rozszerzenie ext/xml, kompatybilne z wersją używaną w PHP4. rozszerzenie ext/dom, zgodne z modelem DOM, tak jak określony jest w standardzie W3C. rozszerzenie ext/simplexml, stosujące zupełnie nowe podejście, unikatowe dla języka PHP. rozszerzenie ext/xmlreader, parser XML będący czymś pośrednim pomiędzy interfejsem SAX a modelem DOM. Przyglądając się tym interfejsom API, można nabrać mylnego przekonania, że zastosowanie pakietów z repozytorium PEAR do przetwarzania kodu XML nie niesie za sobą żadnych korzyści. Nie jest to jednak prawdą. Wszystkie wspomniane interfejsy API są interfejsami API niskiego poziomu, natomiast repozytorium PEAR oferuje kilka pakietów, które działają na wyższym poziomie programowania i dlatego za ich pomocą praca z dokumentami XML staje się znacznie łatwiejsza. Na kolejnych stronach omówimy trzy różne pakiety: XML_Parser, XML_Unserializer i XML_ RSS. Wszystkie te pakiety oparte są na interfejsie API SAX, dlatego też będą działać zarówno w PHP4, jak i w PHP5. Różnica pomiędzy nimi polega na tym, że pakiet XML_Parser umożliwia odczytywanie dowolnego dokumentu XML, podczas gdy pakiet XML_Unserializer potrafi przetwarzać dowolny dokument XML, natomiast pakiet XML_RSS przeznaczony jest wyłącznie do analizy danych RSS. Analizowanie danych XML za pomocą pakietu XML_Parser Pakiet XML_Parser jest obiektowym obudowaniem funkcji analizujących kod XML, dostępnych w języku PHP. Dokumentację tych funkcji można znaleźć w witrynie WWW języka PHP pod adresem: http://www.php.net/xml. Funkcje te umożliwiają przetwarzanie dowolnego dokumentu XML za pomocą interfejsu API SAX. Kiedy korzystamy z interfejsu API SAX, parser stopniowo przesuwa swój wewnętrzny kursor analizując dane dokumentu i jednocześnie w tym samym czasie dzieli dokument na odpowiednie tokeny (wykonuje jego tokenizację). Oto przykłady możliwych tokenów: Znaczniki otwierające lub zamykające (puste znaczniki traktowane są w ten sam sposób jak znaczniki otwierające i zamykające, pomiędzy którymi nie ma żadnych danych). Dane znakowe. 143 PEAR. Programowanie w PHP Instrukcje przetwarzające, takie jak lub . Zewnętrzne jednostki odwołujące się do innych dokumentów XML. Deklaracje notacji. Nieanalizowane deklaracje jednostek. Inne elementy dokumentów XML, takie jak deklaracja typu dokumentu lub komentarze XML. Podczas przesuwania kursora przez dokument parser będzie dla każdego odnalezionego tokenu uruchamiać odpowiednie zdarzenie. Nasza aplikacja powinna obsługiwać te zdarzenia za pomocą odpowiednich funkcji zwrotnych PHP (ściślej: funkcji, metod lub metod statycznych) i pobierać informacje, które chcemy wydobyć z dokumentu. Przed przystąpieniem do analizy dokumentu konieczne będzie zarejestrowanie funkcji zwrotnych dla wszystkich tokenów, które chcemy obsługiwać. Dlatego też typowy kod PHP korzystający z funkcji rozszerzenia xml będzie wyglądać mniej więcej tak: // pobieramy nowy zasób parsera $xml_parser = xml_parser_create(); // rejestrujemy funkcje zwrotne dla znaczników otwierających i zamykających. // Oryginalne implementacje funkcji startElement() i endElement() // zostaną zignorowane xml_set_element_handler($xml_parser, "startElement", "endElement"); // otwieramy plik, który chcemy analizować if (!($fp = fopen($file, "r"))) { die("could not open XML input"); } // odczytujemy plik i przesyłamy odczytane dane do parsera, który zmieni je w tokeny // Jeśli pojawi się błąd (np. dokument jest nieprawidłowo sformatowany, // to kończymy wykonywanie skryptu) while ($data = fread($fp, 4096)) { if (!xml_parse($xml_parser, $data, feof($fp))) { die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser))); } } // zwalniamy zasób parsera xml_parser_free($xml_parser); Podczas korzystania z tych funkcji będziemy używać tego samego kodu wielokrotnie w różnych miejscach naszej aplikacji, ponieważ zawsze konieczne będzie „pobranie” zasobu parsera, zarejestrowanie funkcji zwrotnych, otworzenie plików lub innych strumieni danych i na koniec wreszcie zwolnienie przygotowanego parsera. 144 Rozdział 3. • Praca z formatem XML Zaczynamy pracę z pakietem XML_Parser Pakiet XML_Parser powstał po to, aby umożliwić programiście wielokrotne wykorzystywanie kodu tworzonego na potrzeby analizy dokumentów XML za pomocą interfejsu API SAX. Ponadto oferuje kilka wygodnych funkcji i pozwala na korzystanie z funkcji interfejsu SAX w sposób właściwy dla programowania obiektowego, które jest najwłaściwszym rozwiązaniem w przypadku dużych aplikacji. Aby nauczyć się, jak korzystać z pakietu XML_Parser, skorzystamy z następującego przykładowego dokumentu XML: /usr/share/php/myapp /tmp/myapp /var/www/skins/myapp mysql://user:pass@localhost/myapp myapp_ mysql://root:@localhost/myapp myapp_testing_ Dokument ten mógł zostać na przykład skopiowany z aplikacji, która korzysta z plików konfiguracyjnych bazujących na formacie XML. Dane konfiguracyjne zostały podzielone na różne sekcje, konfigurujące różne części aplikacji. W tym przykładzie mamy sekcje konfigurujące foldery wykorzystywane podczas załączania plików szablonów, plików tymczasowych oraz sekcję konfigurującą dostęp do bazy danych. Ta ostatnia sekcja konfigurująca dostęp do bazy danych pojawia się w pliku konfiguracyjnym dwukrotnie i atrybut environment został dodany do obu tych sekcji. Takie osobne sekcje można wykorzystywać do przechowywania w tym samym pliku osobnych konfiguracji na potrzeby różnych środowisk: testowania, publikowania i udostępniania zawartości online. Na kolejnych stronach za pomocą pakietu XML_Parser zaimplementujemy skrypt odczytujący konfigurację, który będzie mógł zanalizować zaprezentowany tu plik, uwzględniając jednocześnie aktualnie wykorzystywane środowisko. Z pakietu XML_Parser korzysta się odrobinę inaczej niż z innych pakietów PEAR, których używaliśmy wcześniej. Zamiast tworzyć nową instancję obiektu klasy XML_Parser, przygotujemy nową klasę rozszerzającą bazową klasę XML_Parser i utworzymy instancję obiektu właśnie tej klasy. W nowej klasie trzeba będzie zaimplementować tylko różne funkcje obsługi dla każdego z tokenów, które chcemy analizować. Wszystkie pozostałe czynności niezbędne do przeprowadzenia analizy dokumentu (takie jak pobranie parsera, otwarcie plików, obsługa błędów itd.) wykonywane są automatycznie przez klasę bazową. Aby pakiet XML_Parser 145 PEAR. Programowanie w PHP mógł przywoływać różne funkcje zwrotne, które przygotujemy dla różnych tokenów, należy podczas implementowania funkcji zwrotnej w przygotowywanej przez nas klasie stosować się do jego konwencji nazewniczej. W przedstawionej tutaj tabeli prezentujemy wszystkie możliwe funkcje zwrotne wraz z odpowiednimi dla nich nazwami. Sygnatury metod są dokładnie takie same, jak to zostało opisane w dokumentacji języka PHP. Token Nazwa funkcji zwrotnej znacznik otwierający startElement znacznik zamykający endElemnt dane znakowe cdatHandler zewnętrzne jednostki entityrefHandler instrukcje przetwarzania piHandler nieanalizowane deklaracje jednostek unparsedHandler deklaracje notacji notationHandler wszystkie pozostałe tokeny deafultHandler Implementowanie funkcji zwrotnych Teraz, kiedy już znamy nazwy funkcji zwrotnych, zaimplementowanie naszej pierwszej klasy, która analizować będzie dokument XML, nie powinno być niczym trudnym. Przyjrzyjmy się następującemu kodowi klasy: // załączamy klasę bazową require_once 'XML/Parser.php'; // tworzymy klasę rozszerzającą klasę XML_Parser class ConfigReader extends XML_Parser { /** * zajmujemy się znacznikami otwierającymi * * @param zasób parsera * @param łańcuch z nazwą znacznika * @param tablica atrybutów */ public function startHandler($parser, $name, $attribs) { echo "Znaleziono element początkowy $name\n"; } /** * zajmujemy się danymi znakowymi * * @param zasób parsera * @param łańcuch z danymi znakowymi */ 146 Rozdział 3. • Praca z formatem XML public function cdataHandler($parser, $cData) { $cData = trim($cData); if ($cData === '') { return; } echo "...data '$cData' found\n"; } /** * obsługujemy znaczniki zamykające * * @param zasób parsera * @param łańcuch z nazwą znacznika */ public function endHandler($parser, $name) { echo "Znaleziono element końcowy $name\n"; } } // Tworzymy nową instancję klasy $config = new ConfigReader(); // podajemy nazwę analizowanego pliku $config->setInputFile('config.xml'); // analizujemy plik i przechwytujemy błędy $result = $config->parse(); if (PEAR::isError($result)) { echo 'Analiza zakończona niepowodzeniem: ' . $result->getMessage(); } $config->free(); W naszym przykładzie musimy obsłużyć tylko trzy różne typy tokenów: znaczniki otwierające, znaczniki zamykające i dane znakowe ujęte w te znaczniki. Dlatego też musimy zaimplementować w naszej klasie rozszerzającej bazową klasę XML_Handler tylko trzy metody: startElement() dla elementu otwierającego, endElement() dla elementu zamykającego i cDataHandler() dla danych znakowych. Nasz pierwszy przykład, by zapoznać się z działaniem pakietu XML_Parser, prezentować będzie tylko pewne informacje debugowania. Zaraz po zaimplementowaniu nowej klasy ConfigReader tworzymy jej nową instancję. Ponieważ klasa ta jest rozszerzeniem klasy XML_Parser, oferuje więc już wiele użytecznych metod służących do analizy i przetwarzania kodu XML. Jedną z nich jest metoda setInputFile(), dająca programiście możliwość przesłania pliku (lub jakiegokolwiek innego strumienia), który chcemy zanalizować. Aby rozpocząć właściwe przetwarzanie, będziemy musieli przywołać metodę parse(). Metoda ta zwraca albo wartość true (prawda), jeśli dokument można zanalizować, albo instancję klasy PEAR_Error (błędu PEAR), jeśli podczas przetwarzania wystąpią jakieś problemy. Jeśli prześlemy klasie nasz przykładowy dokument XML, na ekranie powinny pojawić się następujące informacje: 147 PEAR. Programowanie w PHP Znaleziono element początkowy CONFIGURATION Znaleziono element początkowy SECTION Znaleziono element początkowy INCLUDES ...data '/usr/share/php/myapp' Znaleziono element końcowy INCLUDES Znaleziono element początkowy CACHE ...data '/tmp/myapp' Znaleziono element końcowy CACHE Znaleziono element początkowy TEMPLATES ...data '/var/www/skins/myapp' Znaleziono element końcowy TEMPLATES Znaleziono element końcowy SECTION Znaleziono element początkowy SECTION Znaleziono element początkowy DSN ...data 'mysql://user:pass@localhost/myapp' Znaleziono element końcowy DSN Znaleziono element początkowy PREFIX ...data 'myapp_' Znaleziono element końcowy PREFIX Znaleziono element końcowy SECTION Znaleziono element początkowy SECTION Znaleziono element początkowy DSN ...data 'mysql://root:@localhost/myapp' Znaleziono element końcowy DSN Znaleziono element początkowy PREFIX ...data 'myapp_testing_' Znaleziono element końcowy PREFIX Znaleziono element końcowy SECTION Znaleziono element końcowy CONFIGURATION Już na pierwszy rzut oka widać, że zwrócone dane nie wyglądają dokładnie tak, jak oczekiwaliśmy. Co prawda funkcje zwrotne obsługujące znaczniki otwierające i zamykające oraz dane są przywoływane w tym samym porządku, w jakim elementy te pojawiały się w źródłowym dokumencie, niemniej wszystkie nazwy znaczników zostały przekonwertowane na wielkie litery. Jest to domyślne zachowanie pakietu XML_Parser, ale można je łatwo wyłączyć, dodając do implementowanej klasy ConfigReader kolejną właściwość: // create a class that extends XML_Parser class ConfigReader extends XML_Parser { /** * wyłączamy zamianę na wielkie litery */ public $folding = false; /* ... reszta kodu pozostaje taka sama ... */ } Po przypisaniu tej właściwości wartości false, pakiet XML_Parser nie będzie zmieniał wielkości znaków w nazwach znaczników przed przesłaniem ich do funkcji zwrotnych. 148 Rozdział 3. • Praca z formatem XML Dodawanie kodu do funkcji zwrotnych Teraz, gdy wiemy już, jak działa pakiet XML_Parser, możemy wykorzystać go do zaimplementowania potrzebnego nam skryptu odczytującego konfigurację. Ponieważ w trakcie analizy pliku konfiguracyjnego musimy zapisywać informacje o stanie, zaczniemy od dodania do naszej klasy kilku właściwości. /** * Klasa odczytująca pliki konfiguracyjne XML */ class ConfigReader extends XML_Parser { /** * wyłączamy zmienianie liter na duże */ public $folding = false; /** * sekcje, które zostały już zanalizowane */ private $sections = array(); /** * wybrane środowisko */ private $environment; /** * tymczasowe przechowywanie danych w trakcie analizy */ private $currentSection = null; private $currentData = null; } Właściwość $sections zostanie później wykorzystana do przechowywania opcji konfiguracyjnych. Właściwość $environment będzie przechowywać informacje o środowisku, w którym będziemy korzystać z naszego parsera odczytującego konfigurację. Natomiast ostatnie dwie właściwości będą używane do tymczasowego przechowywania bieżącej sekcji, gdy kursor i bieżące dane znakowe znajdują się w obrębie znacznika . W następnej kolejności zaimplementujemy konstruktor, któremu przesyłać będziemy w momencie instancjacji obiektu parsera dane o wybranym środowisku: /** * Tworzymy nowy analizator konfiguracji ConfigReader * * @param string, łańcuch określający wykorzystywane środowisko */ public function __construct($environment = 'online') { parent::__construct(); 149 PEAR. Programowanie w PHP $this->environment = $environment; $this->folding = false; } Konstruktor wymaga przesłania mu jako parametru łańcucha, którego wartość zostanie zapisana we właściwości $environment. Teraz, gdy określiliśmy już wszystkie właściwości i przygotowaliśmy konstruktor klasy, zajmiemy się implementowaniem logiki samych funkcji zwrotnych. Pierwsza będzie funkcja zwrotna dla znaczników otwierających: /** * zajmujemy się znacznikami otwierającymi * * @param resource zasób parsera * @param string łańcuch z nazwą znacznika * @param array tablica atrybutów */ public function startHandler($parser, $name, $attribs) { switch ($name) { case 'configuration': break; case 'section': // sprawdzamy, czy zostało określone właściwe środowisko if (!isset($attribs['environment']) || $attribs['environment'] == $this->environment) { // zapisujemy nazwę sekcji $this->currentSection = $attribs['name']; // tworzymy pustą tablicę dla tej sekcji $this->sections[$this->currentSection] = array(); } break; default: $this->currentData = ''; break; } } Technika wykorzystana w kodzie tej funkcji jest dość powszechnie stosowana podczas implementowania parserów kodu XML bazujących na interfejsie SAX. Instrukcja przełącznika switch wykorzystywana jest do wykonywania różnych akcji w zależności od nazwy znacznika. Jeśli wykryty zostanie otwierający znacznik , parser go zignoruje. Jeśli wykryty zostanie znacznik , parser sprawdza, czy w znaczniku została określona wartość atrybutu definiującego środowisko i czy jest identyczna ze środowiskiem określonym w konstruktorze. Jeśli tak jest w istocie, to nazwa sekcji zapisywana jest we właściwości obiektu, a we właściwości $sections tworzona jest nowa tablica dla tej sekcji. Jeśli natomiast oba środowiska się nie zgadzają, to właściwości $currentSection przypisujemy wartość null i ignorujemy wszystkie znaczniki wewnątrz tej sekcji. 150 Rozdział 3. • Praca z formatem XML Jeśli wykryty zostanie jakikolwiek inny znacznik, to bieżącym danym znakowym (currentData) przypisujemy pusty łańcuch. Po zaimplementowaniu funkcji zwrotnej obsługującej znaczniki otwierające przygotujemy kolejną funkcję zwrotną obsługującą dane: /** * obsługujemy dane znakowe * * @param resource, zasób parsera * @param string, łańcuch danych znakowych */ public function cDataHandler($parser, $cData) { if (trim($cData) === '') { return; } $this->currentData .= $cData; } Ta akurat funkcja obsługi jest dość prosta: jeśli dane składają się tylko ze spacji, to są ignorowane; w przeciwnym wypadku dodajemy je do właściwości $currentData. Ostatnia funkcja zwrotna, którą musimy zaimplementować, to metoda obsługująca znaczniki zamykające: /** * obsługujemy znaczniki zamykające * * @param resource, zasób parsera * @param string, łańcuch z nazwą znacznika */ public function endHandler($parser, $name) { switch ($name) { case 'configuration': break; // koniec sekcji, znacznik , oczyszczamy bieżącą sekcję case 'section': $this->currentSection = null; break; default: if ($this->currentSection == null) { return; } // zapisujemy bieżące dane w konfiguracji $this->sections[$this->currentSection][$name] = trim( $this->currentData); break; } } 151 PEAR. Programowanie w PHP Tak jak poprzednio, zamykający znacznik jest ignorowany, ponieważ pełni on tylko funkcję pojemnika, w którym zawarty jest dokument. W momencie natrafienia na zamykający znacznik po prostu repetujemy właściwość $currentSection, ponieważ jesteśmy już poza sekcją. Wszelkie pozostałe znaczniki będą traktowane jak dyrektywy konfiguracyjne, a tekst zawarty wewnątrz tych znaczników (który przechowywać będziemy we właściwości $currentData) będzie wykorzystywany jako wartość tej dyrektywy. Dlatego też zapiszemy tę wartość w tablicy $sections, używając nazwy bieżącej sekcji i nazwy znacznika zamykającego, z wyjątkiem jedynie sytuacji, gdy bieżącej sekcji przypisana będzie wartość null. Sięganie do opcji konfiguracyjnych Ostatnią rzeczą, którą musimy zrobić, jest dodanie metody umożliwiającej sięganie do danych zebranych podczas analizowania dokumentu XML: /** * Pobieramy opcję konfiguracyjną * * @param łańcuch z nazwą sekcji * @param łańcuch z nazwą opcji * @return opcja mieszanej konfiguracji lub fals, jeśli nie została określona */ public function getConfigurationOption($section, $value) { if (!isset($this->sections[$section])) { return false; } if (!isset($this->sections[$section][$value])) { return false; } return $this->sections[$section][$value]; } Metodzie tej można przesłać jako parametr nazwę sekcji lub też nazwę opcji konfiguracyjnej. Następnie sprawdza, czy dana sekcja lub opcja została zdefiniowana w dokumencie XML, i zwraca jej wartość. W przeciwnym wypadku zwraca wartość null. Teraz wreszcie nasz parser odczytujący konfigurację jest gotowy do pracy: $config = new ConfigReader('online'); $result = $config->setInputFile('config.xml'); $result = $config->parse(); printf("Folder buforowania : %s\n", $config->getConfigurationOption('paths', 'cache')); printf("Połączenie z bazą danych : %s\n",$config->getConfigurationOption('db', 'dsn')); 152 Rozdział 3. • Praca z formatem XML Po uruchomieniu skrypt ten zwróci wartości konfiguracyjne przechowywane w pliku XML dla środowiska online: Folder buforowania : /tmp/myapp Połączenie z bazą danych : mysql://user:pass@localhost/myapp Nasz pierwszy rzeczywiście użyteczny parser kodu XML jest już gotowy. Jak widać, pakiet XML_Parser znacznie ułatwił pisanie kodu. Warto jednak wiedzieć, że to jeszcze nie wszystko, co pakiet ten ma nam do zaoferowania! Unikanie dziedziczenia W poprzednim przykładzie rozszerzaliśmy klasę XML_Parser. W prostym programie nie jest to wielkim problemem, jeśli jednak pracujemy nad większą infrastrukturą witryny lub aplikacją WWW, to wygodniej byłoby, gdyby wszystkie nasze klasy rozszerzały klasę bazową, dzięki czemu mogłyby oferować pewien zestaw wspólnych funkcji. Niestety nie da się zmienić klasy XML_Parser, tak by rozszerzała naszą klasę bazową, co wygląda na poważną wadę pakietu XML_Parser. Na szczęście, jeśli korzystamy z pakietu XML_Parser w wersji 1.2.0 lub nowszej, rozszerzanie klasy XML_Parser nie jest konieczne. Prezentowany dalej kod implementuje klasę parsera ConfigReader bez wywodzenia klasy XML_Parser. Oprócz instrukcji extends usunęliśmy również właściwość $folding i odwołanie do metody parent::_construct() w konstruktorze. /** * Klasa odczytująca pliki konfiguracyjne XML */ class ConfigReader { /** * wybrane środowisko */ private $environment; /** * sekcje, które zostały już rozpatrzone */ private $sections = array(); /** * tymczasowe przechowywanie danych w trakcie analizy */ private $currentSection = null; private $currentData = null; /** * Tworzymy nowy analizator ConfigReader * * @param łańcuch z wykorzystywanym środowiskiem */ public function __construct($environment = 'online') 153 PEAR. Programowanie w PHP { $this->environment = $environment; } // Tu powinny pojawić się funkcje obsługi, // ale pominęliśmy je dla oszczędności papieru } Ponieważ nasza klasa nie jest już rozszerzeniem klasy XML_Parser, więc nie dziedziczy też potrzebnych nam funkcji umożliwiających analizę kodu XML. Nadal jednak jest w stanie współpracować z pakietem XML_Parser. Kolejny przykład kodu ilustruje, jak można wykonać analizę tego samego co poprzednio dokumentu XML za pomocą klasy parsera ConfigReader, bez konieczności rozszerzania klasy XML_Parser: $config = new ConfigReader('online'); $parser = new XML_Parser(); $parser->setHandlerObj($config); $parser->folding = false; $parser->setInputFile('XML_Parser-001.xml'); $parser->parse(); printf("Folder buforowania : %s\n", $config->getConfigurationOption('paths', 'cache')); printf("Połaczenie z bazą danych : %s\n", $config->getConfigurationOption('db', 'dsn')); Zamiast tworzyć jak poprzednio jeden obiekt, tworzymy tym razem dwa obiekty: ConfigReader i instancję klasy XML_Parser. Ponieważ klasa XML_Parser nie oferuje funkcji zwrotnych umożliwiających obsługiwanie danych XML, prześlemy parserowi instancję klasy ConfigReader, której będzie następnie używał do przywoływania funkcji obsługi. Jest to jedyna nowa metoda wykorzystywana w tym przykładzie. Musimy tylko wyłączyć właściwość $folding (przypisując jej wartość false), aby pakiet XML_Parser nie konwertował znaczników na duże litery. Następnie wystarczy przesłać parserowi nazwę pliku i rozpocząć proces analizy. Wynik skryptu będzie dokładnie taki sam jak w poprzednim przykładzie, tym razem jednak napisaliśmy parser bez rozszerzania klasy XML_Parser. Inne funkcje pakietu XML_Parser Chociaż poznaliśmy już najważniejsze funkcje pakietu XML_Parser, nadal jednak ma on wiele do zaoferowania. Oto krótkie podsumowanie funkcji, których nie będziemy szczegółowo omawiać. Pakiet XML_Parser potrafi konwertować dane z jednego kodowania na inne. Oznacza to, że możemy odczytywać dokument zakodowany w formacie UTF-8 i automatycznie podczas analizowania dokumentu przekonwertować dane znakowe na format ISO-8859-1. 154 Rozdział 3. • Praca z formatem XML Pakiet XML_Parser umożliwia programiście obywanie się bez instrukcji switch. Przesyłając konstruktorowi jako drugi argument wartość func, zmieniamy tryb analizy na tak zwany tryb function (funkcji). W tym trybie pakiet XML_Parser nie będzie przywoływał metod startElement() i endElement(). Zamiast nich używać będzie do otwierania znaczników metod xmltag_$nazwaznacznika() i _xmltag_$nazwaznacznika(), gdzie $nazwaznacznika będzie nazwą aktualnie obsługiwanego przez niego znacznika. Pakiet XML_Parser oferuje ponadto również klasę XML_Parser_Simple, która implementuje już dla nas metody startElement() i cDataHandler(). Metody te zapisują po prostu dane i przesyłają zebrane informacje metodzie endElement(). W ten sposób możemy za jednym zamachem obsługiwać wszystkie dane związane z konkretnym znacznikiem. Przetwarzanie kodu XML za pomocą pakietu XML_Unserializer Mimo iż pakiet XML_Parser ułatwia przetwarzanie dokumentów XML, nadal sporo pracy spada na programistę. W większości przypadków jednak zależy nam tylko na pobraniu informacji zawartych w dokumencie XML i przekonwertowaniu go na odpowiednią strukturę danych PHP (taką jak tablica czy kolekcja obiektów). Wówczas właśnie z pomocą przychodzi nam pakiet XML_Unserializer. Pakiet ten jest odwrotnością powiązanego z nim pakietu XML_Serializer. Podczas gdy pakiet XML_Serialzer tworzy kod XML ze struktury danych PHP, pakiet XML_Unserializer tworzy strukturę danych z kodu XML. Jeśli mamy już zainstalowany pakiet XML_Serializer, nie będziemy musieli instalować kolejnego pakietu, ponieważ pakiet XML_Unserializer jest częścią tego samego pakietu PEAR. Z pakietu XML_Unserializer korzysta się w podobny sposób jak z pakietu XML_Serializer, gdyż wykonujemy właściwe te same kroki (z jedną drobną różnicą): Załączanie pakietu XML_Unserializer i utworzenie nowej instancji. Skonfigurowania instancji za pomocą odpowiednich opcji. Odczytanie dokumentu XML. Pobranie danych, by zrobić z nimi to, na czym nam zależy. Przyjrzyjmy się bardzo prostemu przykładowi: // załączamy klasę require_once 'XML/Unserializer.php'; // tworzymy nowy obiekt $unserializer = new XML_Unserializer(); // przygotowujemy trochę kodu XML $xml = Array ( [0] => Elvis Presley [1] => Carl Perkins ) ) Jak łatwo zauważyć, pakiet XML_Unserializer przekonwertował dokument XML na zbiór zagnieżdżonych tablic. Główna tablica zawiera tylko jedną wartość przechowywaną pod kluczem artist. Pakiet użył właśnie tego klucza, ponieważ dokument XML na pierwszym poziomie zagnieżdżania zawiera dwa znaczniki . Wartość artist również jest tablicą; tym razem nie jest to jednak tablica asocjacyjna, a tablica indeksowana. Zawiera ona nazwiska dwóch artystów, przechowywane w dokumencie XML. Dlatego prawie wszystkie dane przechowywane w dokumencie będą dostępne w utworzonej tablicy. Jedyną brakującą informacją jest główny znacznik dokumentu: . Użyliśmy tej informacji jako nazwy zmiennej PHP, która przechowuje tablicę; można to jednak zrobić tylko wtedy, gdy wiadomo, jakie informacje będą przechowywane w dokumencie XML. Niemniej, gdybyśmy tego nie wiedzieli, pakiet XML_Unserializer dostarcza metody umożliwiającej sięganie do tych informacji: echo $unserializer->getRootName(); Jak można było oczekiwać, kod ten wyświetli nazwę głównego znacznika właśnie przetworzonego dokumentu XML: artists Jak widać, zamiast implementować nową klasę, możemy użyć pakietu XML_Unserializer i za jego pomocą pobrać wszystkie informacje z dokumentu XML, jednocześnie zachowując strukturę tych informacji. I to wszystko przy użyciu tylko czterech wierszy kodu! Spróbujmy więc zastosować pakiet XML_Unserializer na pliku konfiguracyjnym XML, który poprzednio analizowaliśmy używając pakietu XML_Parser, by przekonać się, jakie wyniki otrzymamy. Ponieważ dokument XML zachowywany jest w osobnym pliku, warto skorzystać z metody file_get_contents(), by zapisać kod XML w zmiennej. Nie jest to jednak konieczne, 156 Rozdział 3. • Praca z formatem XML ponieważ pakiet XML_Unserializer może przetwarzać dowolne dane obsługiwane przez pakiet XML_Parser. Aby poinformować pakiet XML_Unserialize, że powinien traktować dane przesłane metodzie unserialize(), jak by były nazwą pliku, a nie dokumentem XML, wystarczy tylko przesłać tej metodzie dodatkowy parametr: require_once 'XML/Unserializer.php'; $unserializer = new XML_Unserializer(); $unserializer->unserialize('config.xml', true); $config = $unserializer->getUnserializedData(); print_r($config); Po uruchomieniu skrypt ten zwróci następującą tablicę: Array ( [section] => Array ( [0] => Array ( [includes] => /usr/share/php/myapp [cache] => /tmp/myapp [templates] => /var/www/skins/myapp ) [1] => Array ( [dsn] => mysql://user:pass@localhost/myapp [prefix] => myapp_ ) [2] => Array ( [dsn] => mysql://root:@localhost/myapp [prefix] => myapp_testing_ ) ) ) Jeśli przyjrzymy się dokumentowi XML z przykładów wykorzystujących do analizy pakiet XML_Parser, przekonamy się, że pakiet XML_Unserializer wydobył wszystkie informacje, które były przechowywane między znacznikami XML. Nasz przykładowy plik konfiguracyjny definiował kilka sekcji i jak widać, wszystkie dyrektywy konfiguracyjne, które znajdowały się w dokumencie XML, można znaleźć w tablicy, którą otrzymaliśmy. Niemniej brakuje nazw i środowisk dla każdej z sekcji. Informacja ta była przechowywana w atrybutach znaczników , które zostały zignorowane przez pakiet XML_Unserializer. 157 PEAR. Programowanie w PHP Analizowanie atrybutów Oczywiście można to zmienić. Podobnie jak pakiet XML_Serializer, pakiet XML_Unserializer pozwala programiście wpływać na przebieg przetwarzania za pomocą odpowiednich opcji. Opcje te definiuje się dokładnie w taki sam sposób, jak w pakiecie XML_Serializer: Przesyłając tablicę ze zdefiniowanymi opcjami konstruktorowi klasy lub metodzie setOptions(). Przesyłając tablicę z opcjami jako parametr metodzie unserialize(). Lub definiując pojedynczą opcję za pomocą metody setOption(). Jeśli chcemy analizować również atrybuty, trzeba w kodzie wprowadzić tylko niewielką zmianę: require_once 'XML/Unserializer.php'; $unserializer = new XML_Unserializer(); // analizujemy również atrybuty $unserializer->setOption(XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE, true); $unserializer->unserialize('XML_Parser-001.xml', true); $config = $unserializer->getUnserializedData(); print_r($config); Dodaliśmy do skryptu jeden wiersz, przypisujący opcji ATTRIBUTES_PARSE pakietu XML_Serializer wartość true (prawda). Oto jak zmiana ta wpłynęła na dane zwracane przez skrypt: Array ( [section] => Array ( [0] => Array ( [name] => paths [includes] => /usr/share/php/myapp [cache] => /tmp/myapp [templates] => /var/www/skins/myapp ) [1] => Array ( [name] => db [environment] => online [dsn] => mysql://user:pass@localhost/myapp [prefix] => myapp_ ) [2] => Array ( [name] => db 158 Rozdział 3. • Praca z formatem XML [environment] => stage [dsn] => mysql://root:@localhost/myapp [prefix] => myapp_testing_ ) ) ) Teraz utworzona tablica zawiera także dyrektywy konfiguracyjne, jak również metainformacje dla każdej sekcji, które przechowywane są w atrybutach. Niemniej dyrektywy konfiguracyjne i metainformacje zostały przemieszane, co może spowodować problemy podczas korzystania z dyrektyw lub , ponieważ zostaną ona zapisane na wartościach przechowywanych w atrybutach. Na szczęście, tak jak poprzednio, problem ten można rozwiązać wprowadzając do kodu tylko drobną modyfikację: require_once 'XML/Unserializer.php'; $unserializer = new XML_Unserializer(); // analizujemy również atrybuty $unserializer->setOption(XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE, true); // przechowujemy atrybuty w osobnej tablicy $unserializer->setOption(XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY, '_meta'); $unserializer->unserialize('config.xml', true); $config = $unserializer->getUnserializedData(); print_r($config); Definiując opcję ATTRIBUTES_ARRAYKEY polecamy pakietowi XML_Unserializer, by zapisywał atrybuty w osobnej tablicy i nie mieszał ich ze znacznikami. Oto wynik działania skryptu po wprowadzeniu poprawek: Array ( [section] => Array ( [0] => Array ( [_meta] => Array ( [name] => paths ) [includes] => /usr/share/php/myapp [cache] => /tmp/myapp [templates] => /var/www/skins/myapp ) [1] => Array ( 159 PEAR. Programowanie w PHP [_meta] => Array ( [name] => db [environment] => online ) [dsn] => mysql://user:pass@localhost/myapp [prefix] => myapp_ ) [2] => Array ( [_meta] => Array ( [name] => db [environment] => stage ) [dsn] => mysql://root:@localhost/myapp [prefix] => myapp_testing_ ) ) ) Teraz możemy bez trudu wydobyć z dokumentu XML wszystkie opcje konfiguracyjne bez konieczności pisania nowego parsera dla każdego nowego formatu XML. Programiści mający obsesję na punkcie programowania obiektowego mogą się jednak skarżyć, że z obiektowego interfejsu oferowanego przez pakiet XML_Parser dla opcji konfiguracyjnych korzystało się znacznie wygodniej niż z prostych tablic PHP. Czytelnicy, którzy tak właśnie myślą, powinni przeczytać następny podrozdział. Mapowanie kodu XML na obiekty Domyślnie pakiet XML_Unserializer konwertuje złożone struktury XML (tj. każdy znacznik, który zawiera inne zagnieżdżone w nim znaczniki lub atrybuty) na odpowiednią tablicę asocjacyjną. Domyślne to zachowanie można zmienić definiując następującą opcję: $unserializer->setOption(XML_UNSERIALIZER_OPTION_COMPLEXTYPE, 'object'); Jeśli dodamy ten wiersz kodu do skryptu, wynik jego działania zmieni się odrobinę: stdClass Object ( [section] => Array ( [0] => stdClass Object ( [_meta] => Array ( [name] => paths 160 Rozdział 3. • Praca z formatem XML ) [includes] => /usr/share/php/myapp [cache] => /tmp/myapp [templates] => /var/www/skins/myapp ) // ...pozostałe sekcje zostały usunięte... ) Zamiast tablic asocjacyjnych pakiet XML_Unserializer utworzy instancję standardowej klasy stdClass, która jest zawsze definiowana w języku PHP i nie oferuje żadnych metod. Mimo iż uzyskujemy teraz obiektowy dostęp do dyrektyw konfiguracyjnych, podejście to nie jest jednak wiele lepsze od korzystania z tablic, ponieważ nadal będziemy zmuszeni do pisania kodu w następujący sposób: %echo $config->section[0]->templates; Cóż, przynajmniej przypomina to sposób korzystania z modułu SimpleXML, który wielu ludzi uważa za znakomite narzędzie do pracy z kodem XML. Na nasze potrzeby ten sposób sięgania do danych nie będzie odpowiedni. Na szczęście to jeszcze nie wszystko, co — jak to pokaże następny przykład — pakiet XML_Unserializer ma nam do zaoferowania. Pakiet XML_Unserializer potrafi również używać różnych klas dla różnych znaczników. Dla każdego znacznika będzie sprawdzać, czy została już zdefiniowana klasa o tej samej nazwie i zamiast standardowej klasy stdClass będzie tworzył instancję tejże klasy odpowiadającej nazwą znacznikowi. Podczas określania właściwości klas sprawdzać będzie, czy dla każdej właściwości została zdefiniowana metoda umożliwiająca przypisywanie wartości tejże właściwości. Metody pozwalające na przypisywanie wartości właściwościom (ang. setters) zawsze mają nazwy zaczynające się od przedrostka „set”, po którym następuje nazwa właściwości. Reasumując, możemy zaimplementować klasy oferujące odpowiednie funkcjonalności, pozwalając pakietowi XML_Unserializer automatycznie przygotować je dla nas i zdefiniować wszystkie właściwości tych klas odpowiednio do danych zawartych w dokumencie XML. W naszym przykładzie z plikiem konfiguracyjnym potrzebować będziemy dwóch klas: jednej dla konfiguracji i jednej dla każdej sekcji konfiguracji. Oto przykład kodu implementującego te klasy: /** * Klasa umożliwiająca dostęp do konfiguracji */ class configuration { /** * Będzie przechowywać sekcję */ private $sections = null; /** * wybrane środowisko */ private $environment = 'online'; /** 161 PEAR. Programowanie w PHP * Metoda definiująca znacznik sekcji */ public function setSection($section) { $this->sections = $section; } /** * Określamy środowisko konfiguracji * * Będzie przywoływana nie przez XML_Unserializer, * ale przez użytkownika. */ public function setEnvironment($environment) { $this->environment = $environment; } /** * Pobieramy opcję konfiguracyjną * * @param string łańcuch z nazwą sekcji * @param string łańcuch z nazwą opcji * @return mieszana opcja konfiguracji lub false, jeśli nie została określona */ public function getConfigurationOption($section, $value) { foreach ($this->sections as $currentSection) { if ($currentSection->getName() !== $section) { continue; } if (!$currentSection->isEnvironment($this->environment)) { continue; } return $currentSection->getValue($value); } return null; } } Kod implementujący klasę configuration jest dość prosty: mamy właściwość przechowującą wszystkie sekcje konfiguracji oraz właściwość, która przechowuje aktualnie wybrane środowisko. Ponadto mamy odpowiadające im nazwami metody umożliwiające przypisywanie właściwościom wartości oraz jedną metodę pozwalającą na pobieranie wartości konfiguracji. Jedynym, co może zaskoczyć czytelnika w tej implementacji klasy configuration, jest to, że metoda umożliwiająca określanie sekcji nosi nazwę setSection(), a nie setSections(). Dzieje się tak dlatego, że znacznik miał nazwę (słowo „sekcje” w nazwie znacznika występuje w liczbie pojedynczej, a nie mnogiej). Kolejnym krokiem jest zaimplementowanie klasy section: 162 Rozdział 3. • Praca z formatem XML /** * Klasa przechowująca informacje na temat pojedynczej sekcji */ class section { /** * przechowuje metainformacje */ private $meta = null; /** * definiuje metainformacje */ public function setMeta($meta) { if (!isset($meta['name'])) { throw new Exception('Sections require a name.'); } $this->meta = $meta; } /** * Pobieramy nazwę sekcji */ public function getName() { return $this->meta['name']; } /** * poszukujemy określonego środowiska */ public function isEnvironment($environment) { if (!isset($this->meta['environment'])) { return true; } return ($environment === $this->meta['environment']); } /** * Pobieramy wartość z sekcji */ public function getValue($name) { if (isset($this->$name)) { return $this->$name; } return null; } } 163 PEAR. Programowanie w PHP Podobnie jak poprzednia klasa, również ta jest głównie pojemnikiem dla informacji przechowywanych w danej sekcji oraz oczywiście posiada odpowiednie metody do definiowania i pobierania wartości właściwości (ang. setter i getters). Teraz gdy już obie klasy zostały zaimplementowane, możemy pokazać pakietowi XML_Unserializer, jak ma z nich korzystać: require_once 'XML/Unserializer.php'; $unserializer = new XML_Unserializer(); // analizuje również atrybuty $unserializer->setOption(XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE, true); // przechowuje atrybuty w osobnej tablicy $unserializer->setOption(XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY, 'meta'); // używamy obiektów zamiast tablic $unserializer->setOption(XML_UNSERIALIZER_OPTION_COMPLEXTYPE, 'object'); $unserializer->setOption(XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME, true); $unserializer->unserialize('config.xml', true); $config = $unserializer->getUnserializedData(); printf("Folder buforowania : %s\n", $config->getConfigurationOption( 'paths', 'cache')); printf("Połączenie z bazą danych : %s\n", $config->getConfigurationOption('db', 'dsn')); $config->setEnvironment('stage'); print "\nZmieniono środowisko:\n"; printf("Folder buforowania : %s\n", $config->getConfigurationOption( 'paths', 'cache')); printf("Połączenie z bazą danych : %s\n", $config->getConfigurationOption('db', 'dsn')); Tak jak poprzednio, zmiana pojedynczej opcji pozwala zupełnie zmienić działanie pakietu XML_Unserializer. Po uruchomieniu tego skryptu otrzymamy następujące dane: Folder buforowania : /tmp/myapp Połączenie z bazą danych : mysql://user:pass@localhost/myapp Zmieniono środowisko: Folder buforowania : /tmp/myapp Połączenie z bazą danych : mysql://root:@localhost/myapp Jest tylko jeden problem, który może zakłócić działanie naszego nowego programu analizującego konfigurację. Jeśli plik konfiguracyjny zawierałby tylko jedną sekcję, to metoda configuration::setSection() przywoływana byłaby przesyłając jej pojedynczą instancję obiektu sec- 164 Rozdział 3. • Praca z formatem XML tion, a nie tablicę zawierającą kilka obiektów section. To oczywiście wywołałoby błąd w momencie, gdy program próbowałby przeglądać w pętli nieistniejącą tablicę. Aby temu zapobiec, trzeba albo automatycznie tworzyć w tym przypadku tablicę podczas implementowania metody setSection(), albo zdać się na pakiet XML_Unserializer: $unserializer->setOption(XML_UNSERIALIZER_OPTION_FORCE_ENUM, array('section')); Po tej poprawce pakiet XML_Unserializer będzie tworzył indeksowaną tablicę nawet wtedy, gdy znacznik pojawi się tylko raz. Ponieważ wiemy już, w jaki sposób definiuje się opcje dla pakietu XML_Unserializer, warto rzucić okiem na kolejną tabelę zestawiającą wszystkie opcje dostępne w pakiecie XML_Unserializer. Nazwa opcji Opis Wartość domyślna COMPLEXTYPE Definiuje, w jaki sposób należy dokonywać deserializacji znaczników, które nie zawierają żadnych danych znakowych. Może przyjmować wartości array (tablica) lub object (obiekt). array ATTRIBUTE_KEY Definiuje nazwę atrybutu, który użyty zostanie jako nazwa klucza tablicy. _originalKey ATTRIBUTE_TYPE Definiuje nazwę atrybutu, który będzie podstawą dla serializowanych informacji o typie. _type ATTRIBUTE_CLASS Definiuje nazwę atrybutu, z którego pobrana zostanie _class nazwa klasy, jeśli znacznik serializowany będzie jako obiekt. TAG_AS_CLASSNAME Określa, czy należy jako nazwy klasy użyć nazwy znacznika. flase DEFAULT_CLASS Nazwa domyślnej klasy wykorzystywanej podczas tworzenia obiektów. stdClass ATTRIBUTES_PARSE Określa, czy należy analizować atrybuty (true), czy też je ignorować (false). false ATTRIBUTES_PREPEND Określa łańcuch, który należy dodawać na początku nazw atrybutów. pusty łańcuch ATTRIBUTES_ARRAYKEY Określa nazwę klucza lub właściwości, pod którymi w osobnej tablicy będą przechowywane wszystkie atrybuty. Wartość false wyłącza tę opcję. false CONTENT_KEY Określa nazwę klucza lub właściwości, pod którymi przechowywane będą dane znakowe ze znacznika, jeśli zawiera on nie tylko dane znakowe. _content TAG_MAP Definiuje tablicę asocjacyjną zawierającą informacje, które nazwy znaczników należy zmienić na inne (podane w tablicy) nazwy. pusta tablica 165 PEAR. Programowanie w PHP Nazwa opcji Opis Wartość domyślna FORCE_ENUM Definiuje tablicę z nazwami znaczników, które powinny być automatycznie traktowane tak, jakby w dokumencie znacznik pojawiał się więcej niż jeden raz. Dzięki temu będą dla nich automatycznie przygotowywane tablice indeksowane. pusta tablica ENCODING_SOURCE Określa kodowanie źródłowego dokumentu. Informacja ta zostanie przesłana pakietowi XML_Parser. null ENCODING_TARGET Określa kodowanie docelowego dokumentu. Informacja ta zostanie przesłana pakietowi XML_Parser. null DECODE_FUNC Określa funkcję zwrotną PHP, która zostanie zastosowana na danych znakowych i wartościach atrybutów. null RETURN_RESULT Określa, czy metoda unserialize() powinna zwracać wyniki, czy tylko wartość true (prawda) po udanej deserializacji. false WHITESPACE Określa, w jaki sposób powinny być traktowane spacje w dokumencie. Możliwe wartości opcji to: XML_..._WHITESPACE_KEEP (zachowaj spacje), XML_..._WHITESPACE_TRIM (przytnij spacje) lub XML_..._WHITESPACE_NORMALIZE (znormalizuj spacje). XML_..._WHITESPACE ¦_TRIM IGNORE_KEYS Lista znaczników, których zawartość będzie pusta tablica automatycznie przesyłana do znacznika nadrzędnego, zamiast tworzyć nowy znacznik. GUESS_TYPES Określa, czy włączać automatyczne zgadywanie typów dla danych znakowych i atrybutów. false Deserializacja etykiet W przykładach ilustrujących działanie pakietu XML_Serializer tworzyliśmy dokument XML oparty na strukturze zbudowanej z obiektów. W naszym ostatnim przykładzie korzystania z pakietu XML_Unserializer zatoczymy koło i utworzymy tę samą strukturę danych z dokumentu XML. Oto kod, który nam do tego posłuży: require_once 'XML/Unserializer.php'; $unserializer = new XML_Unserializer(); // Nie ignorujemy atrybutów $unserializer->setOption(XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE, true); 166 Rozdział 3. • Praca z formatem XML // Niektóre złożone znaczniki powinny być obiektami, ale wyliczenia // powinny być przechowywane w tablicach $types = array( '#default' => 'object', 'artists' => 'array', 'labels' => 'array', 'records' => 'array' ); $unserializer->setOption(XML_UNSERIALIZER_OPTION_COMPLEXTYPE, $types); // Zawsze tworzymy indeksowane tablice dla etykiet studiów, artystów i albumów $unserializer->setOption(XML_UNSERIALIZER_OPTION_FORCE_ENUM, array('label', 'artist', 'record')); // nie dodajemy zagnieżdżonych kluczy dla etykiet studiów, artystów i albumów $unserializer->setOption(XML_UNSERIALIZER_OPTION_IGNORE_KEYS, array('label', 'artist', 'record')); // analizujemy plik $unserializer->unserialize('first-xml-document.xml', true); print_r($unserializer->getUnserializedData()); Gdy uruchomimy ten skrypt, na ekranie pojawi się kilka komunikatów ostrzegawczych podobnych do tego: Warning: Missing argument 1 for Record::__construct() in c:\wamp\www\ books\packt\pear\xml\example-classes.php on line 48 Dzieje się tak dlatego, że w klasach Label, Artist i Record zaimplementowaliśmy konstruktory, które wymagać będą, aby w momencie tworzenia nowych instancji przesłać im pewne parametry. Pakiet XML_Unserializer nie będzie przesyłać tych parametrów odpowiedniemu konstruktorowi, dlatego też musimy wprowadzić pewne poprawki w kodach naszych klas: class Label { ... public function __construct($name = null) { $this->name = $name; } ... } class Artist { ... public function __construct($name = null) { $this->name = $name; } ... } class Record { ... public function __construct($id = null, $name = null, $released = null) { 167 PEAR. Programowanie w PHP $this->id = $id; $this->name = $name; $this->released = $released; } } Dzięki uczynieniu argumentów konstruktorów opcjonalnymi pozbywamy się ostrzeżeń. Mimo to pakiet XML_Unserializer będzie definiował wszystkie właściwości obiektów po utworzeniu ich instancji. Dzięki temu, jeśli teraz uruchomimy skrypt, otrzymamy wynik, jakiego oczekiwaliśmy — kompletne drzewo obiektów zostanie odtworzone i jak widać, nie ma potrzeby pisania w tym przypadku specjalnego parsera. Inne dodatkowe funkcje Przekonaliśmy się, że za pomocą pakietu XML_Unserializer można tworzyć naprawdę wspaniałe skrypty, i to pisząc zaledwie kilka wierszy kodu. Niemniej nadal nie poznaliśmy jeszcze wszystkich możliwości pakietu XML_Unserializer. Pakiet ten pozwala nam również: Mapować nazwy znaczników na nazwy klas, określając odpowiednią tablicę asocjacyjną. Skorzystać z opcji zgadywania typów, dzięki której pakiet będzie automatycznie konwertował dane na odpowiednie wartości logiczne, liczby całkowite lub liczby zmiennoprzecinkowe. Wykorzystać pakiety XML_Serializer i XML_Unserializer jako poręczne narzędzia zastępujące standardowe funkcje serialize() i unserialize(). Stosować na wszystkich danych znakowych i wartościach atrybutów odpowiednie funkcje zwrotne języka PHP. Usuwać lub, przeciwnie, zachowywać wszystkie spacje w dokumencie. Pakiet XML_Parser a pakiet XML_Unserializer Jeśli zachodzi konieczność pobrania informacji z dokumentu XML, zanim przystąpimy do pisania własnego parsera, warto sprawdzić, czy pakiet XML_Unserializer poradzi sobie z tym zadaniem. W ponad 90% sytuacji pakiet XML_Unserializer okazuje się najlepszym narzędziem. Nawet jeśli pierwsza próba zakończy się niepowodzeniem, w większości przypadków wystarczy tylko użyć odpowiednich opcji. Z pakietu XML_Parser natomiast należy korzystać w następujących sytuacjach: Jeśli nasz dokument XML jest bardzo złożony i nie stosuje się do żadnych reguł. Pakiet XML_Unserializer może w takiej sytuacji nie być w stanie wybrać odpowiednich informacji, natomiast pakiet XML_Parser poradzi sobie z takim nietypowym dokumentem, choć oczywiście będzie to wymagać więcej pracy. 168 Rozdział 3. • Praca z formatem XML Jeśli musimy tylko pobrać fragment dokumentu XML, to pakiet XML_Parser może z tym zadaniem poradzić sobie szybciej niż pakiet XML_Unserializer, ponieważ będziemy mogli zignorować resztę dokumentu XML. W przypadku przetwarzania dużych dokumentów XML pakiet XML_Parser może być bardziej efektywny, ponieważ wymaga mniejszych zasobów pamięci niż pakiet XML_Unserializer. Dzieje się tak dlatego, że pakiet XML_Unserializer przechowuje wszystkie dane pobrane z dokumentu w pamięci. Natomiast pakiet XML_Parser przechowuje informacje wydobyte z dokumentu XML podczas jego przetwarzania w bazie danych, dzięki czemu podczas analizy nie obciąża aż tak bardzo pamięci. Analizowanie danych RSS za pomocą pakietu XML_RSS Skrót RSS można rozwinąć jako: Rich Site Summary. RDF Site Summary. Really Simple Syndication. Wszystkie te narzędzia odnoszą się do pobierania danych z innych witryn WWW. Nas interesuje trzecie z narzędzi, Really Simple Syndication, które jak sama nazwa sugeruje, służy do łatwej syndykacji, czyli oferowania w naszej witrynie zawartości oferowanej przez inne witryny WWW. Technologia RSS jest zazwyczaj wykorzystywana przez blogi (web logi) lub witryny zbierające (inaczej mówiąc: agregujące) wiadomości z innych witryn. Ponieważ RSS jest aplikacją XML, do obsługi treści RSS możemy skorzystać również z już omówionych pakietów. Niemniej repozytorium PEAR oferuje specjalny pakiet, który służy właśnie do pobierania informacji z dokumentów RSS i za którego pomocą praca z zawartością RSS staje się dziecinnie prosta. Używając pakietu XML_RSS można wyświetlać w swojej witrynie nagłówki wpisów lub artykułów z ulubionych blogów, z wykorzystaniem mniej niż dziesięciu wierszy kodu. Można również przygotować w naszej witrynie listę najnowszych wersji naszych ulubionych pakietów, pakietów napisanych przez ulubionych programistów lub odpowiednich kategorii PEAR. Witryna PEAR oferuje różnego rodzaju materiały (ang. feed; tak powszechnie nazywa się adresy URL oferujące różne dokumenty RSS), które zawierają albo informacje o wszystkich publikowanych pakietach, albo tylko o tych ostatnio publikowanych pakietach, ostatnich publikacjach w danej kategorii lub określonego programisty. Listę wszystkich dostępnych materiałów (feedów) w witrynie PEAR i odpowiadających im adresów URL można znaleźć pod adresem: http://pear.php.net/feeds/. W kolejnych przykładach będziemy pracować z feedami dostarczającymi informacji o najnowszych publikacjach w danej kategorii XML. Ten feed można znaleźć pod adresem: http://pear.php.net/feeds/cat_xml.rss. Jeśli otworzymy ten adres URL w naszej przeglądarce lub też ściągniemy feed, zobaczymy dokument XML o następującej strukturze: 169 PEAR. Programowanie w PHP http://pear.php.net/ [email protected] [email protected] en-us PEAR: Latest releases in category xml The latest releases in the category xml XML_Serializer 0.16.0 http://pear.php.net/package/XML_Serializer/ download/0.16.0/ XML_Serializer: - introduced constants for all options (this helps avoiding typos in the option names) - deprecated option 'tagName' is no longer supported, use XML_SERIALIZER_OPTION_ROOT_NAME (or rootName) instead - implement Request #3762: added new ignoreNull option to ignore properties that are set to null when serializing objects or arrays - fixed bug with encoding function - use new header comment blocks XML_Unserializer: - fix bug #4075 (allow tagMap option to influence any kind of value) 2005-06-05T09:26:53-05:00 XML_SVG 1.0.0 http://pear.php.net/package/XML_SVG/download/1.0.0/ PHP5 compatible copy() method. 2005-04-13T19:33:56-05:00 XML_FastCreate 1.0.0 http://pear.php.net/package/XML_FastCreate/download/1.0.0/ 170 Rozdział 3. • Praca z formatem XML BugFix PHP5 ; scripts/example added ; stable release. 2005-03-31T10:41:23-05:00 Dokument ten zawiera informacje o dwóch sprawach. Po pierwsze, globalne informacje o kanale (ang. channel), który oferuje dany feed. Po drugie — sam feed. Informacje te zawierają tytuł i opis feedu, adres URL witryny, która dostarcza feedu, informacje o języku, w którym został zapisany, oraz o tym, kto opublikował i utworzył ten materiał. Następnie feed zawiera kilka elementów, które opisują pozycje wiadomości zawartych w materiale. W tym przypadku są to wiadomości o nowym, opublikowanym oprogramowaniu. Każda z tych pozycji jest ujęta w znacznik i przechowuje następujące informacje: Tytuł. Opis. Adres URL strony, która zawiera dalsze informacje na temat tej pozycji. Datę opublikowania tych informacji. Sięganie do tych informacji z użyciem pakietu XML_RSSS będzie bardzo proste. Wystarczy wykonać następujące czynności: 1. Załączyć pakiet XML_RSS do naszego kodu i utworzyć nową instancję (nowy obiekt) klasy XML_RSS. 2. Przetworzyć feed (materiał) RSS. 3. Pobrać informacje z obiektu XML_RSS. Oto prosty skrypt, który pobiera informacje z kanału i wyświetla je w formacie HTML. require_once 'XML/RSS.php'; $rss = new XML_RSS('http://pear.php.net/feeds/cat_xml.rss'); $rss->parse(); $channel = $rss->getChannelInfo(); print "Dane kanału \n"; printf("Tytuł: %s \n", $channel['title']); printf("Opis: %s \n", $channel['description']); printf("Łącze: %s \n", $channel['link'], $channel['link']); Po otwarciu tego skryptu w przeglądarce zobaczymy następujące informacje: Dane kanału Tytuł: PEAR: Latest releases in category xml 171 PEAR. Programowanie w PHP Opis: The latest releases in the category xml Łącze: http://pear.php.net/ Aby utworzyć listę z informacjami o najnowszych publikacjach pakietów związanych z językiem XML w repozytorium PEAR, wystarczy zmodyfikować ten skrypt tylko odrobinę: require_once 'XML/RSS.php'; $rss = new XML_RSS('http://pear.php.net/feeds/cat_xml.rss'); $rss->parse(); $channel = $rss->getChannelInfo(); print 'Dane z kanału '; printf('Tytuł: %s ', $channel['title']); printf('Opis: %s ', $channel['description']); printf('Łącze: %s ', $channel['link'], $channel['link']); print '
Kod ten wyświetli nieuporządkowaną listę najnowszych pakietów zaraz pod ogólnymi informacjami o kanale. Naprawdę jednak wspaniałe w tym wszystkim jest to, że za pomocą prawie identycznego skryptu można wyświetlić informacje na temat najnowszych publikacji dowolnego programisty PEAR — wystarczy po prostu zastąpić adres URL obecnego feedu adresem na przykład http://pear.php.net/feeds/user_schst.rss. Tego samego skryptu można również użyć, by wyświetlić materiały (feed) z dowolnej innej witryny lub bloga. Aby na przykład wyświetlić najnowsze wiadomości z bloga blog.php-tools.net, wystarczy użyć adresu URL http://blog.php-tools.net/feeds/index.rss2 i zobaczymy w przeglądarce najnowsze wiadomości z bloga PAT. Niemniej konieczne będzie wprowadzenie w skrypcie jednej drobnej poprawki, ponieważ wersja 2 RSS używa znacznika zamiast . Aby móc odczytywać i wyświetlać materiały przygotowane w obu wersjach RSS, konieczne będzie wprowadzenie do skryptu drobnej poprawki: $items = $rss->getItems(); foreach ($items as $item) { if (isset($item['dc:date'])) { $date = strtotime($item['dc:date']); } elseif ($item['pubDate']) { $date = strtotime($item['pubDate']); } 172 Rozdział 3. • Praca z formatem XML printf(' Mimo iż feedy repozytorium PEAR nie obsługują tej funkcji, mamy jednak możliwość przechowywania informacji o obrazach, które powinny być wyświetlane razem z danym feedem. Pakiet XML_RSS oferuje specjalną metodę, umożliwiającą wydobywanie tych informacji: $images = $rss->getImages(); foreach ($images as $image) { $size = getimagesize($image['url']); printf(' ', $image['url'], $size[0], $size[1], $image['title']); } Po dodaniu do skryptu tego fragmentu kodu w oknie przeglądarki pod listą wiadomości powinien pojawić się odpowiedni obrazek. Jak się przekonaliśmy, integrowanie wiadomości RSS z naszą witryną okazuje się bardzo proste, jeśli tylko korzystamy z pakietu XML_RSS z repozytorium PEAR. Podsumowanie W tym rozdziale dowiedzieliśmy się, jak korzystać z pakietów PEAR ułatwiających pracę z kodem XML. Pakiety XML_Util, XML_FastCreate i XML_Serializer służą do tworzenia dokumentów XML abstrahując od poprawności sformułowania kodu XML i justowania znaczników za pomocą odpowiednich wcięć. Pakiet XML_XUL umożliwia tworzenie za pomocą języka PHP interfejsów aplikacji internetowych działających pod przeglądarkami z rodziny Mozilla, takimi jak Firefox. Dzięki temu można przy zachowaniu logiki biznesowej standardowej aplikacji WWW korzystać z przyjaznego interfejsu użytkownika opartego na języku XUL. W drugiej części rozdziału nauczyliśmy się, jak pisać parser oparty na interfejsie SAX, który umożliwi odczytywanie pliku konfiguracyjnego w formacie XML i automatycznie ignoruje nieistotne dla nas części dokumentu XML. Dowiedzieliśmy się też, jak za pomocą pakietu XML_Unserializer przekształcać praktycznie dowolny dokument XML na tablice lub obiekty. Dzięki temu możemy sięgać do informacji zawartych w dokumencie XML bez konieczności posiadania jakiejkolwiek wiedzy na temat przebiegu samego procesu analizy dokumentu. Wreszcie na koniec wykorzystaliśmy pakiet XML_RSS, by za jego pomocą wyświetlić informacje RSS w dowolnej aplikacji opartej na języku PHP. 173 PEAR. Programowanie w PHP 174 4 Usługi WWW Aplikacje WWW odgrywają coraz większą rolę we współczesnych infrastrukturach informatycznych. Dawniej najważniejszą dziedziną programowania było tworzenie aplikacji działających na poszczególnych komputerach, obecnie natomiast coraz więcej firm przenosi swe aplikacje do sieci WWW, aby można je było kontrolować z dowolnego komputera podłączonego do sieci za pomocą większości współczesnych przeglądarek. W ten sposób pracownicy nie są „przywiązani” do swego biurka w firmie, ale mogą korzystać z firmowych aplikacji praktycznie z dowolnego miejsca na świecie. Nadal jednak aplikacje te muszą posiadać zdolność do komunikowania się z innymi aplikacjami, ponieważ żadna firma nie może sobie pozwolić na zaprojektowanie i zaimplementowanie od podstaw wszystkich komponentów oprogramowania, które mogą być przydatne w firmie. Dlatego też bardzo często nowe, firmowe aplikacje WWW, zwykle pisane w języku PHP, muszą funkcjonować w heterogenicznym (niejednorodnym) środowisku i umieć komunikować się z różnymi aplikacjami napisanymi w innych językach programowania, takich jak C/C++, Perl, Java lub nawet w języku COBOL. W przeszłości programiści, by umożliwić komunikację między aplikacjami, zazwyczaj korzystali ze standardu CORBA lub modelu COM. Obecnie jednak wraz z triumfem internetu pojawiło się również wiele nowoczesnych usług WWW. Korzystają one zazwyczaj z solidnych i powszechnie uznawanych protokołów, takich jak HTTP, otwartych standardów, takich jak język XML, i dostępnych dla wszystkich aplikacji, takich jak serwery WWW. Wszystko zaczęło się od bardzo prostego, bazującego na języku XML, protokołu XML-RPC. Nazwa XML-RPC jest skrótem od XML Remote Procedure Cal (zdalne wywoływanie procedur za pomocą XML) i był to pierwszy popularny protokół dla usług WWW. Nadal zresztą jest wykorzystywany przez wiele firm i aplikacji. Z protokołu XML-RPC wyewoluował protokół SOAP, który choć zawdzięcza wiele XML-RPC, jest jednak bardziej wszechstronny, a zatem bardziej skomplikowany. Protokół SOAP jest obecnie obsługiwany przez prawie wszystkie języki programowania, w tym oczywiście przez język PHP. PEAR. Programowanie w PHP Ponieważ protokoły te są często zbyt skomplikowane lub też zbyt statyczne dla potrzeb niektórych firm, tworzą one często swoje własne, prywatne protokoły, zazwyczaj oparte na standardzie XML. Protokoły te nierzadko są do siebie bardzo zbliżone i dlatego określa się je zbiorczym terminem usług REST (opartych na protokole REST, Representational State Transfer), czyli usług, które co prawda nie korzystają z żadnego z oficjalnych protokołów zaprojektowanych do komunikacji między różnymi aplikacjami, ale nadal oparte są na protokole HTTP i języku XML. W tym rozdziale opowiemy o dostępnych w repozytorium PEAR pakietach ułatwiających pracę z różnymi usługami WWW. Korzystanie z usług WWW Większość ludzi rozpoczyna od korzystania z usług WWW oferowanych przez kogoś innego. Są dwa podstawowe powody przemawiające za sięganiem po cudzą usługę WWW: 1. Musimy opierać się na danych na temat klienta, których nie da się zdobyć wysyłając po prostu zapytanie do bazy danych. Przyczyną mogą być względy bezpieczeństwa lub konieczność korzystania ze źródła danych nieobsługiwanego przez język PHP. Bardzo często powodem może być również chęć skorzystania z logiki biznesowej, którą już ktoś w naszej firmie zaimplementował, na przykład za pomocą języka Java. 2. Chcemy skorzystać z usługi oferowanej przez inną firmę. Na przykład jeśli chcemy zintegrować z naszą witryną usługę przeszukiwania internetu, po co pisać taką usługę samodzielnie, skoro możemy zapłacić za znakomitą usługę przeszukiwania oferowaną przez Google? Zapewne wyjdzie to taniej niż ręczne implementowanie wszystkich funkcji dostarczanych przez wyszukiwarkę Google. To samo dotyczy sytuacji, gdy na przykład chcemy przygotować aukcję internetową, sprzedawać przez internet książki itp. Istnieją już w sieci firmy, które oferują najlepsze i dopracowane rozwiązania dla wielu różnych aplikacji WWW. Używając ich usług możemy skorzystać z zalet zaimplementowanych przez nie mechanizmów, jednocześnie nie tracąc nic z tożsamości naszej firmy. W pierwszej części tego rozdziału skorzystamy z usług WWW, które oparte są na standardowych protokołach, takich jak XML-RPC czy SOAP, posługując się w tym celu oczywiście odpowiednimi pakietami PEAR. Potem przyjrzymy się usługom obsługiwanym przez pakiet Google_Package, który ułatwia znacznie pracę usługom internetowym Google. Pamiętajmy, że Google należy do firm oferujących usługi oparte na protokole SOAP. Gdy już zapoznamy się z technikami pracy ze standardowymi protokołami, przyjrzymy się pakietowi Services_Ebay zapewniającemu łatwy i wygodny w użyciu interfejs API dla usług WWW oferowanych przez dom aukcyjny eBay. Pakiet ten jest unikatową mieszanką typowych usług REST i protokołu SOAP. Na koniec wreszcie zapoznamy się z dwoma pakietami PEAR nie należącymi do kategorii usług WWW w repozytorium PEAR. Ułatwiają one natomiast korzystanie z usług WWW 176 Rozdział 4. • Usługi WWW opartych na protokole REST. Za pomocą tych dwóch pakietów będziemy mogli wykorzystać w swojej witrynie praktycznie dowolne usługi WWW oparte na protokole REST, nawet jeśli implementacja właściwego dla nich pośrednika proxy nie jest dostępna w repozytorium PEAR. Korzystanie z usług WWW opartych na XML-RPC XML-RPC to skrót od XML Remote Procedure Call (zdalne wywoływanie procedur za pomocą XML), będący nazwą standardu zaprojektowanego przez firmę Userland Software. Jest to protokół przywołujący funkcje działające na różnych serwerach, korzystający z protokołu HTTP, by zakodować odpowiednie zdalne wywołanie funkcji, a następnie zwracający wyniki w formacie XML. Aby korzystać z usługi XML-RPC, konieczne jest przygotowanie kodu XML zawierającego nazwę metody oraz wszystkie argumenty funkcji i wysłanie go do serwera usługi za pośrednictwem żądania Post protokołu HTTP. Serwer przeanalizuje nadchodzący kod XML, przywoła odpowiednią metodę i utworzy dokument XML zawierający wynik jej działania. Po czym odeśle go do programu wysyłającego żądanie. Nasz skrypt będzie musiał następnie przetworzyć otrzymany od serwera kod XML i wydobyć z niego odpowiednie informacje zwrócone przez wywoływaną za pomocą XML-RPC funkcję. Firma Userland Software oferuje bardzo prostą usługę testową, z której skorzystamy w kolejnych przykładach. Usługa ta zwraca nazwę stanu w USA w zależności od liczby całkowitej, którą prześlemy usłudze. Metoda oferowana przez tę usługę to examples.getStateName() i aby ją przywołać, trzeba przygotować następujący dokument XML: examples.getStateName 15 Jeśli serwer usługi otrzyma taki dokument XML, to odkoduje go i przywoła metodę examples.getStateName(), przesyłając jej jako argument wartość 15. Po wywołaniu metody usługa XML-RPC utworzy nowy dokument zawierający informacje o wartości zwróconej przez metodę i odeśle go z powrotem do klienta, który wysłał żądanie: Iowa 177 PEAR. Programowanie w PHP Jak widać, liczbie 15 odpowiada stan Iowa. To już w zasadzie cała wiedza niezbędna do pracy z protokołem XML-RPC. Posiadając wiadomości na temat tworzenia i przetwarzania dokumentów XML, prezentowane w rozdziale 3., czytelnik byłby już prawdopodobnie w stanie napisać samodzielnie klienta XML-RPC. Nie ma jednak takiej potrzeby, ponieważ repozytorium PEAR oferuje łatwą i wygodną w użyciu implementację protokołu XML-RPC. Co więcej, jest ona już prawdopodobnie zainstalowana na komputerze, ponieważ PEAR począwszy od wersji 1.4.0 wykorzystuje protokół XML-RPC w komunikacji między instalatorem PEAR na naszym komputerze a repozytorium PEAR w internecie. Dlatego wystarczy zapewne po prostu załączyć ten pakiet do kodu i będziemy mogli korzystać z niego w naszych aplikacjach. Skrypt sięgający do przykładowej usługi oferowanej przez firmę Userland można napisać za pomocą oferowanego przez repozytorium PEAR pakietu XML_RPC, używając mniej niż dziesięciu wierszy kodu (jeśli nie liczyć dokumentujących komentarzy i obsługi błędów): require_once 'XML/RPC.php'; // tworzymy nowego klienta $client = new XML_RPC_Client('/RPC2', 'betty.userland.com'); // kodujemy parametry komunikatu $params = array( new XML_RPC_Value(15, 'int') ); // kodujemy wywołanie metody w XML $message = new XML_RPC_Message('examples.getStateName', $params); // wysyłamy komunikat XML-RPC $response = $client->send($message); // Sprawdzamy, czy nie pojawił się błąd if ($response->faultCode()) { echo "Nie mogę skorzystać z usługi XML-RPC.\n"; echo $response->faultString(); exit(); } // pobieramy wartość zwracaną $value = $response->value(); // dekodujemy obiekt XML_RPC_Value na prostą zmienną PHP $stateName = XML_RPC_decode($value); echo "Stan to $stateName\n"; Tak jak w przypadku każdego skryptu sięgającego do zasobów PEAR, zaczynamy pracę od załączenia pakietu, z którego chcemy skorzystać. Następnie tworzymy nowego klienta usługi, do której zamierzamy sięgać. Konstruktor klasy XML_RPC_Client będzie wymagał przesłania mu przynajmniej dwóch parametrów: 178 Rozdział 4. • Usługi WWW Ścieżki do usługi na serwerze (w tym przypadku zlokalizowanej w katalogu /RPC2). Nazwy hosta usługi (w tym przypadku betty.userland.com). Konstruktorowi można przesłać również kolejne parametry, jeśli usługa nie jest zlokalizowana pod portem 80 lub też jeśli zamierzamy sięgać do usługi poprzez odpowiedniego pośrednika proxy. Parametry, których należy użyć, by korzystać za pomocą pakietu XML_RPC z pośrednika proxy, można znaleźć w jego dokumentacji, dostępnej pod adresem: http://pear.php.net/manual/ en/package.webservices.xml-rpc.php. Potem będziemy musieli przygotować kod XML definiujący wywołanie metody, które wyślemy do serwera. W tym celu trzeba wykonać następujące czynności: 1. Najpierw utworzyć indeksowaną tablice zawierającą argumenty funkcji zdefiniowane w postaci obiektów XML_RPC. W naszym prostym przykładzie potrzebujemy tylko jednego argumentu, mianowicie liczby całkowitej, dla której chcemy pobrać nazwę stanu. Konstruktor klasy XML_RPC_Value pozwala na przesłanie mu dwóch parametrów: wartości, którą ma zakodować, i typu tejże wartości. Jeśli nie podamy typu, to domyślnie użyje typu string (łańcucha znaków). 2. Nowo utworzona tablica zostanie następnie wykorzystana, by zakodować samo odwołanie do metody. W tym celu zostanie utworzona nowa instancja obiektu komunikatu XML_RPC_Message. Konstruktor tej klasy wymaga przesłania mu dwóch parametrów: nazwy metody, którą ma wywołać, i tablicy obiektów XML_RPC_Value zawierającej argumenty używane podczas wywoływania metody. Podsumowując, kompletny dokument XML służący do zdalnego wywoływania metody tworzy się za pomocą jedynie dwóch wierszy kodu: // kodujemy parametry komunikatu $params = array(new XML_RPC_Value(15, 'int')); // kodujemy wywołanie metody w XML $message = new XML_RPC_Message('examples.getStateName', $params); Aby wysłać taki komunikat XML-RPC do usługi, należy skorzystać z metody call() klienta, której jako argument przesyła się tylko obiekt XML_RPC_Message. Metoda ta zwróci instancję obiektu odpowiedzi XML_RPC_Response() reprezentującą dokument zwrócony klientowi przez serwer usługi. Obiekt ten pozwala łatwo sprawdzić, czy podczas zdalnego wywoływania procedury wystąpił jakiś błąd. Jeśli metoda faultCode() tego obiektu zwróci wartość inną niż zero, oznaczać to będzie, że coś poszło nie tak. W tym przypadku będziemy mogli skorzystać z metody faultString(), by pobrać zrozumiały dla człowieka opis błędu, który wystąpił podczas wywoływania procedury. Jeśli jednak żaden błąd się nie pojawi, to będziemy mogli skorzystać z metody value(), aby pobrać wartość zwróconą przez odpowiedź serwera. Warto jednak pamiętać, że jest to instancja obiektu XML_RPC_Value, która zawiera rzeczywistą wartość, jak również informacje o typie tej wartości. Jeśli spróbujemy wyświetlić ten obiekt na ekranie, to zobaczymy nie tylko nazwę stanu, ale również dodatkową informację w rodzaju Object id #4. Konieczne będzie pobranie 179 PEAR. Programowanie w PHP z tego obiektu samej tylko wartości i przekonwertowanie jej na zwykłą wartość PHP. Dopiero wtedy będziemy mogli swobodnie z niej korzystać. Klasa XML_RPC oferuje funkcję XML_ RPC_decode(), która to dla nas zrobi. Teraz wartość zwrócona przez funkcję będzie, tak jak oczekiwaliśmy, łańcuchem zawierającym nazwę stanu. Dlatego gdy uruchomimy nasz skrypt, zwróci on następującą informację: Stan to Iowa Za pomocą narzędzi repozytorium PEAR korzystanie z usług WWW jest znacznie prostsze niż można by sądzić. Zaprezentowany przez nas przykład nie jest zbyt użyteczny i w prawdziwym życiu czytelnicy korzystać będą zapewne z bardziej złożonych usług opartych na protokole XML-RPC. Jako że instalator PEAR wykorzystuje protokół XML-RPC do komunikowania się z witryną PEAR, można by sądzić, że tej samej techniki dało by się użyć podczas komunikowania z witryną innej usługi. Czytelnicy, którzy tak myślą, mają zupełną rację. Można to bez trudu zrobić za pomocą pakietu XML_RPC. Jeśli mamy zainstalowane oprogramowanie repozytorium PEAR w wersji 1.4.0 lub nowszej, to program instalacyjny będzie w stanie poinformować nas, jakie metody XML-RPC oferuje dla każdego kanału usługa PEAR. Wystarczy tylko wpisać polecenie pear channel-info pear.php.net i otrzymamy informacje podobne do tych zaprezentowanych poniżej: Channel pear.php.net Information: ================================= Name and Server pear.php.net Alias pear Summary PHP Extension and Application Repository Validation Package Name PEAR_Validate Validation Package default Version Server Capabilities =================== Type Version/REST type Function Name/REST base xmlrpc 1.0 logintest xmlrpc 1.0 package.listLatestReleases xmlrpc 1.0 package.listAll xmlrpc 1.0 package.info xmlrpc 1.0 package.getDownloadURL xmlrpc 1.1 package.getDownloadURL xmlrpc 1.0 package.getDepDownloadURL xmlrpc 1.1 package.getDepDownloadURL xmlrpc 1.0 package.search xmlrpc 1.0 channel.listAll rest REST1.0 http://pear.php.net/rest/ Wytłuszczone wiersze informują, jakie metody XML-RPC oferuje ta usługa. Musimy tylko wiedzieć, gdzie usługa jest zlokalizowana, abyśmy mogli utworzyć nowego klienta. Dla witryny PEAR odpowiednia usługa zlokalizowana jest pod następującym adresem: http://pear.php.net/ xmlrpc.php. Dlatego, jeśli chcemy przeszukać witrynę PEAR w poszukiwaniu pakietu, który zawiera w nazwie litery „XML”, wystarczy skorzystać z następującego skryptu: 180 Rozdział 4. • Usługi WWW require_once 'XML/RPC.php'; $client = new XML_RPC_Client('/xmlrpc.php', 'pear.php.net'); $params = array(new XML_RPC_Value('XML', 'string')); $message = new XML_RPC_Message('package.search', $params); $response = $client->send($message); if ($response->faultCode()) { echo "Nie mogę skorzystać z usługi XML-RPC.\n"; echo $response->faultString(); exit(); } $value = $response->value(); $packages = XML_RPC_decode($value); foreach ($packages as $packageName => $packageInfo) { echo "$packageName\n"; echo " {$packageInfo['summary']} \n"; } %s |
---|
, 64 | , 64 |
---|