Spis treści POCZĄTKI

Nie odkrywajmy Ameryki na nowo!
Otwarty format OASIS dla dokumentów biurowych i CMS-ów 16
Bård Farstad Wklejanie czystego tekstu do CMS-a i jego ręczne formatowanie jest zadaniem żmudnym i podatnym na błędy. Gdyby tak można było stworzyć odpowiedni dokument w swoim ulubionym pakiecie biurowym, a potem po prostu skopiować plik i wkleić go do CMS-a... Taką właśnie możliwość daje połączenie trzech nowatorskich produktów: uniwersalnego formatu OpenDocument, CMS-a eZ publish i pakietu biurowego OpenOffice.org 2.0.

nam wielu programistów PHP, którzy piszą swoje aplikacje bazując wyłącznie na własnych rozwiązaniach – rozwijają frameworki, tworzą proste narzędzia do debugowania, czy próbują sami walczyć z niedopasowaniem relacyjno-obiektowym (narzędzia ORM) w aplikacjach bazodanowych. Za nic nie określiłbym ich mianem domorosłych programistów webowych – to deweloperzy z kilkuletnim doświadczeniem, którzy PRAWIE profesjonalnie tworzą CRM-y czy dedykowane CMS-y. Kiedy opowiadają mi o swoich problemach (programistycznych), rozwiązaniach i pomysłach, przyznaję – ich twórczość i innowacyjność robi na mnie wrażenie. Z reguły jednak odpowiadam im następująco: słuchaj, przecież pisaliśmy już o tym w PHP Solutions. Ich praca jest często mało wydajna, gdyż sami odkrywają i implementują rzeczy już wcześniej wymyślone i zbudowane. Dzieje się tak dlatego, gdyż są nieufni i nieświadomi wielu pożytecznych i istniejących już rozwiązań. Z tego powodu użyłem sformułowania PRAWIE profesjonalnie. Aby tę tendencję odwrócić, regularnie oddajemy w Wasze ręce magazyn PHP Solutions. Projekty i technologie, które opisujemy w obecnym numerze, na pewno pomogą Wam w codziennej pracy. Szczególnie zachęcamy do budowania aplikacji w oparciu o technologię AJAX, która – dzięki wykorzystaniu JavaScriptu i XML-a – pozwala na tworzenie wydajnych i wyjątkowo interaktywnych aplikacji WWW. Zastosowanie AJAX uwolni użytkownika od irytującego klikania i zbędnego oczekiwania – teraz dane mogą być przekazywane bez przeładowywania strony. Gorąco polecam też artykuł o narzędziu PHPUnit (odpowiednik Junit dla Javy) służącym do testowania modułów aplikacji. Dzięki niemu zbudujecie stabilny i pozbawiony błędów kod w dużo krótszym czasie. W numerze weźmiemy również pod lupę kilka absolutnych nowości, m.in. PHP-GTK2, które oferuje rewolucyjne możliwości budowania aplikacji okienkowych. SDO to kolejne rozwiązanie ze świata Javy przeniesione do PHP przez IBM i Zend Technologies, które zapewniając jednolity dostęp do różnego typu danych (bazy danych, XML). Drupal to rewolucyjny CMS pozwalający na tworzenie wielodomenowych, wielojęzycznych i modularnych portali z wykorzystaniem takich technologii, jak AJAX. Zapraszam do lektury kolejnego numeru PHP Solutions. Satysfakcja gwarantowana! Dariusz Pawłowski Redaktor prowadzący

Z

NARZĘDZIA
Programowanie sterowane testami za pomocą PHPUnit
Timo Haberkern Im większy projekt programistyczny, tym trudniej wyłapywać pojawiające się w nim błędy, a usunięcie jednych usterek powoduje często powstanie następnych, w innej części aplikacji. Ręczne tworzenie testów dla setek klas jest nieskuteczne, a poza tym przyprawia o ból głowy i paraliżuje pracę. Z pomocą przychodzi PHPUnit: narzędzie pozwalające zautomatyzować proces tworzenia i wdrażania testów.

22

Drupal, czyli wielodomenowe, wielojęzyczne i modularne portale 30 w oparciu o AJAX i SEO
Uwe Hermann Czy potrzebujesz systemu zarządzania treścią (CMS) ogólnego zastosowania, będącego w stanie obsługiwać w jednej instancji kilka niezależnych serwisów WWW, z których każdy dostępny ma być w kilku wersjach językowych? Czy pełna internacjonalizacja powinna być możliwa za pomocą zaledwie kilku kliknięć myszą? Może chciałbyś także dodać do swojego serwisu elementy dynamiczne korzystające z AJAX, albo zwiększyć jego popularność dzięki zastosowaniu najlepszych technik SEO? Nie musisz dalej szukać: wypróbuj system Drupal.

Nasz magazyn ukazuje się w czterech językach!
polskim
niemieckim francuskim włoskim

TECHNIKI
Service Data Objects, czyli standard uniwersalnego dostępu do danych
Piotr Szarwas Rozwiązania od dawna stosowane w Javie zalewają świat PHP. Należy do nich SDO, czyli Service Data Objects: zunifikowany, wspierany przez takie potęgi, jak IBM, Zend i BEA standard dostępu do danych, eliminujący potrzebę tworzenia osobnych interfejsów dla każdego ich źródła.

42

Jeśli jesteś zainteresowany zakupem licencji na wydawanie naszych pism prosimy o kontakt: Monika Godlewska monikag@software.com.pl tel.: 48 22 887 12 66, fax: 48 22 887 10 11

4

www.phpsolmag.org

PHP Solutions Nr 1/2006

Spis treści
AJAX – wyjątkowo interaktywne 48 i wydajne aplikacje WWW
Joshua Eichorn, Werner M. Krauß PHP zawdzięcza swój sukces nie tylko potężnym możliwościom, ale również samemu modelowi programowania. Aplikacje tworzone w PHP pozwalają osiągnąć bardzo wiele przy ograniczonym oprogramowaniu klienckim, co oznacza łatwe wdrażanie i aktualizacje, a tym samym szybkie efekty pracy. Architektura ta ma też dotkliwe wady, jak opóźnienia między wyświetlaniem kolejnych stron lub brak możliwości pobierania nowych danych bez wysyłania formularza. Na szczęście istnieje mechanizm AJAX.

Pytania dotyczące prenumeraty

Strona WWW/Forum

tel. (22) 887 14 44 e-mail: pren@software.com.pl Software Wydawnictwo Sp. z o.o. dział prenumeraty ul. Piaskowa 3 01-067 Warszawa

strona www: www.phpsolmag.org Tu znajdą Państwo informacje dotyczące aktualnych i przyszłych numerów magazynu PHP Solutions. Forum: www.phpsolmag.org/newforum Zachęcamy do dyskusji na naszym forum. Czekamy na propozycje tematów, które chcieliby Państwo znaleźć w najbliższym numerze pisma. Zapraszamy także do wymiany poglądów z innymi fanami PHP.

CD

PROJEKTY
advAJAX, czyli praktyczne zastosowanie technologii AJAX
Łukasz Lach Ciągłe przeładowywanie strony WWW przy każdej zmianie jej zawartości i żmudne czekanie na wyświetlenie kolejnej porcji danych jest zmorą każdego użytkownika aplikacji webowych i programisty PHP. Nie jesteśmy jednak skazani na te bolączki: wybawia nas od nich zyskująca na znaczeniu PHP technologia AJAX. Dzięki niej ładujący się w nieskończoność pasek postępu przechodzi do lamusa.

tel. (22) 887 14 44 e-mail: cd@software.com.pl Software Wydawnictwo Sp. z o.o. Defekty CD/DVD ul. Piaskowa 3 01-067 Warszawa

Cena

Zamówienia /Numery archiwalne
58

tel. (22) 887 14 44 e-mail: pren@software.com.pl sklep on-line: www.shop.software.com.pl

Prenumerata: 135 zł Przelew na konto nr: 46 1440 1299 0000 0000 0391 8238 Nordea Bank Polska S.A. II Oddział w Warszawie

Kontakt z redakcją

e-mail: redakcja@phpsolmag.org Software Wydawnictwo Sp. z o.o. Redakcja PHP Solutions ul. Piaskowa 3 01-067 Warszawa
Wszystkie listingi z artykułów zostały zamieszczone na naszej stronie internetowej pod adresem www.phpsolmag.org/pl

Nowe możliwości PHP-GTK2
Pablo Dall'Oglio

64

PHP Solutions jest wydawany przez Software-Wydawnictwo Sp. z o.o. Dyrektor Wydawniczy: Jarosław Szumski Market Manager: Sylwia Tuśnio sylwia.tusnio@software.com.pl Product Manager: Maciej Krawcewicz maciej.krawcewicz@phpsolmag.org Redaktor prowadzący: Dariusz Pawłowski dpawlowski@phpsolmag.org Redaktor: Krzysztof Sobolewski ksobolewski@phpsolmag.org Opracowanie CD: Krzysztof Sobolewski Stali współpracownicy: Paweł Kozłowski pkozlowski@phpsolmag.org, Paweł Grzesiak pgrzesiak@phpsolmag.org Kierownik produkcji: Marta Kurpiewska marta@software.com.pl Projekt okładki: Agnieszka Marchocka Skład i łamanie: Agnieszka Zadrożna aga.z@software.com.pl Dział reklamy: adv@software.com.pl Prenumerata: Marzena Dmowska pren@software.com.pl Nakład: 6 000 egz. Adres korespondencyjny: Software-Wydawnictwo Sp. z o.o., ul. Piaskowa 3, 01-067 Warszawa, Polska tel. +48 22 887 10 10, fax +48 22 887 10 11 www.phpsolmag.org cooperation@software.com.pl Dołączoną do magazynu płytę CD przetestowano programem AntiVirenKit firmy G DATA Software Sp. z o.o. Redakcja dokłada wszelkich starań, by publikowane w piśmie i na towarzyszących mu nośnikach informacje i programy były poprawne, jednakże nie bierze odpowiedzialności za efekty wykorzystania ich; nie gwarantuje także poprawnego działania programów shareware, freeware i public domain. Uszkodzone podczas wysyłki płyty wymienia redakcja. Wszystkie znaki firmowe zawarte w piśmie są własnością odpowiednich firm i zostały użyte wyłącznie w celach informacyjnych. Redakcja używa systemu automatycznego składu Do tworzenia wykresów i diagramów wykorzystano program firmy

Rozszerzenie PHP-GTK1 zapoczątkowało nowy sposób myślenia o PHP. Język przeznaczony dla aplikacji sieciowych zaczął być powszechnie stosowany przy pisaniu samodzielnych aplikacji klienckich z graficznym interfejsem użytkownika (ang. Graphical User Interface, GUI). Niemniej jednak, to właśnie pojawienie się PHP-GTK2, umożliwiającego połączenie możliwości PHP5 i biblioteki Gtk-2.6, może dać początek prawdziwej rewolucji.

VARIA
Porównanie ofert polskich firm hostingowych
Paweł Grzesiak Rynek usług hostingowych w Polsce rozwija się dynamicznie. Jeżeli planujemy zakup własnego skrawka miejsca w sieci, warto zapoznać się z przygotowanym przez nas porównaniem usług najpopularniejszych polskich providerów internetowych.

74

Osoby zainteresowane współpracą prosimy o kontakt: cooperation@software.com.pl Druk: ArtDruk Wysokość nakładu obejmuje również dodruki. Redakcja nie udziela pomocy technicznej w instalowaniu i użytkowaniu programów zamieszczonych na płytach CD-ROM dostarczonych razem z pismem. Sprzedaż aktualnych lub archiwalnych numerów pisma po innej cenie niż wydrukowana na okładce – bez zgody wydawcy – jest działaniem na jego szkodę i skutkuje odpowiedzialnością sądową. Pismo ukazuje się w następujących wersjach językowych: polskiej , francuskiej , niemieckiej oraz włoskiej

Aktualności Opis CD

6 8

.

PHP Solutions Nr 1/2006

www.phpsolmag.org

5

Aktualności
PHPAudit
PHPAudit to rozbudowany i bardzo funkcjonalny dedykowany system dystrybucji i licencjonowania oprogramowania, które tworzymy w PHP. Zapewnia wiele gotowych rozwiązań w zakresie licencjonowania skryptów, pozwalając ograniczać ich wykonywanie do jednej instalacji, określonej domeny lub podanych adresów IP. PHPAudit może współpracować z koderem IonCube. http://phpaudit.com/

Pro-PHP Podcast

C

PEAR 1.4.0

Wraz z wersją 1.4.0, instalator PEAR osiągnął dojrzałość. Najważniejszą nowością jest wprowadzenie obsługi i możliwości tworzenia własnych kanałów (PEAR Channels), czyli niezależnych od serwera pear.php.net repozytoriów pakietów, które możemy instalować używając polecenia pear. Pozwala to na wygodną dystrybucję własnego oprogramowania. PEAR 1.4.0 potrafi też rozpakowywać archiwa, w których umieszczono kilka aplikacji (jest to tzw. bundling). Wydzielono również osobne polecenie do obsługi archiwów PECL-a, a także poprawiono wiele błędów z poprzednich wersji. http://pear.php.net/

Zend Core dla Oracle

Wynikiem współpracy firm Oracle i Zend Technologies jest wypuszczenie na rynek wersji beta silnika Zend Core dla Oracle. Jest on certyfikowanym, łatwym w instalacji środowiskiem PHP ze zintegrowaną obsługą bazy Oracle. Oprogramowanie zawiera uaktualniony sterownik OCI8, dzięki czemu udało się uzyskać większą stabilność i wydajność współpracy z produktami bazodanowymi Oracle. Zamiarem obu firm było stworzenie gotowego do użycia środowiska PHP do zastosowań korporacyjnych, ułatwiającego rozwijanie oprogramowania opartego o PHP i bazę Oracle. http://zend.com

hcesz posłuchać, co mają do powiedzenia czołowi programiści PHP na świecie? Zacznij słuchać podcastów o tematyce związanej z PHP. Słowo podcast powstało z połączenia słów broadcasting (transmisja) oraz iPod (znany wszystkim przenośny odtwarzacz plików mp3). W naszym przypadku podcastami będą audycje zapisane w plikach MP3. PHP podcast to najbardziej udane przedsięwzięcie tego typu dla świata PHP. W rozmowach, wywiadach i debatach biorą bowiem udział pionierzy PHP, czyli ludzie najbardziej aktywni w środowisku. Za pośrednictwem audycji usłyszymy ich komentarze, a także myśli i pomysły odnoszące się do PHP. Tematyka koncentruje się zdecydowanie wokół zaawansowanych aspektów programowania. Jeżeli więc chcemy posłuchać, co ma do powiedzenia Greg Beaver na temat nowego PEAR-a, czy Paul M. Jones, twórca Savanta i SOLAR-a, to nic nie stoi na przeszkodzie. Wystarczy wejść na stronę, pobrać odpowiednią audycję i uruchomić ją na komputerze albo na przenośnym odtwarzaczu, np. w drodze do pracy czy podczas przerwy w zajęciach. Wszyst-

kie audycje są bezpłatne i dostępne dla każdego, bez konieczności rejestracji lub wykonywania jakichkolwiek dodatkowych czynności. Podcast to doskonała metoda na odświeżenie słownictwa używanego w środowisku programistów PHP, która może okazać się szczególnie przydatna podczas prezentacji konferencyjnej w języku angielskim. Nowe audycje ukazują się z częstotliwością jednej na dwa tygodnie. http://www.pro-php.com/

KPHPDevelop

KPHPDevelop to środowisko programistyczne (IDE, ang. Integrated Development Environment) pracujące pod KDE. Jest udostępniane bezpłatnie i oferuje standardową funkcjonalność, jak na aplikację tego typu. Koloruje składnię, podpowiada nazwy funkcji oraz parametry wejściowe oraz pozwala na sprawne zarządzanie wszystkimi plikami, które składają się na projekt. Godna uwagi jest obsługa baz danych, z analizatorem zapytań SQL na czele. http://kphpdev.sourceforge.net

MySQL 5.0RC

U

Net_Curl

Ukazała się wersja 1.2.2 biblioteki Net_Curl, która wprowadza obiektowy interfejs do rozszerzenia cURL. Przyda się głównie programistom tworzącym aplikacje zorientowane obiektowo. Wszystkie nazwy funkcji i ich parametry wejściowe pozostają bez zmian w stosunku do cURL. Jedyna różnica polega na tym, ze odwołujemy się do nich jak do metod. http://pear.php.net/package/Net_Curl

HTML_Safe 0.9.0alpha1

Wciąż rosnące zagrożenie ze strony ataków XSS powoduje powstawanie coraz większej ilości bibliotek do walidacji wprowadzanych przez użytkownika danych. XHTML_Safe to narzędzie należące do repozytorium PEAR, pozwalające dokładnie sprecyzować, jakie dane mają prawo być kontrolowane, a jakie nie (możliwość stworzenia dokładnej listy tagów). Parser biblioteki usunie potencjalnie niebezpieczne części kodu HTML, w tym niezamknięte znaczniki, skrypty JavaScript oraz wszystkie inne tagi, które mogą się okazać zagrożeniem. http://pear.php.net/package/HTML_Safe/

kazała się prawdopodobnie najważniejsza odsłona systemu bazodanowego MySQL w historii: 5.0 RC. Poprzedziły ją liczne wersje beta. Liczba pobrań przekroczyła ponad dwa miliony. Piąta odsłona MySQL-a szykuje wiele możliwości, które będą interesujące przede wszystkim dla zaawansowanych programistów. Istotę nowości wprowadzonych w tej wersji dobrze oddają trzy słowa: widoki (ang. views), wyzwalacze (ang. triggers) i transakcje (ang. transactions). Widoki tworzymy w celu ograniczenia dostępu do danych zgromadzonych w tabelach. Każdy widok może zawierać dane umieszczone w różnych tabelach, a użytkownik ma wrażenie, że pracuje na normalnej, rzeczywistej tabeli. Stanowiące drugą nowość wyzwalacze są procedurami uruchamianymi pod warunkiem wystąpienia jakiegoś zdarzenia. Przykładowo, gdy użytkownik spróbuje usunąć dane z którejś z tabel, wyzwalacz wykona zaprogramowaną wcześniej procedurę. Można w ten sposób zapobiec rozhermetyzowaniu danych. Trzeci element wprowadzony w MySQL 5.0, transakcja,

to nierozerwalny blok operacji (zapytań), cechujący się tym, że gdy choć jedno z nich nie będzie wykonane poprawnie, w systemie nie zostanie zapisany rezultat żadnego z nich. W MySQL 5.0 wprowadzono ponadto wiele usprawnień wydajności (w szczególności InnoDB), obsługę kursorów, zaawansowane zarządzanie procedurami i funkcjami użytkownika. Rozszerzono również rozmiar danych typu VARCHAR do 65532 bajtów. Należy wspomnieć także o implementacji dwóch nowych silników do obsługi danych: ARCHIVE (rozległe bloby) i FEDERATED (dla rozwiązań zdalnych). Podsumowując: zmiany dokonane w wersji 5.0RC MySQL-a są poważnym argumentem na rzecz migracji na tę platformę bazodanową. http://mysql.org

6

www.phpsolmag.org

PHP Solutions Nr 1/2006

Aktualności
Zend Platform 2
lchash 0.9.1
cje, błędy zaistniałe podczas połączeń, czy też błędy w samym kodzie. Teraz będzie można prześledzić problemy, docierając do ich źródła. Nowa wersja platformy Zend to również szybsze wykonywanie aplikacji PHP. PHP Performance Managment odpowiada za akcelerację kodu, dynamiczne buforowanie danych wyjściowych oraz kompresję danych, która pozwala ograniczyć do 10% nominalny transfer danych. PHP/Java Integration Bridge to kolejna ciekawa cecha Zend Platform 2. Jak sama nazwa wskazuje, jest to most pomiędzy światami PHP i Javy, umożliwiający m.in. korzystanie z bibliotek i klas Javy z poziomu PHP (w tym bezpośrednie wywoływanie metod tych klas). Podsumowując: opisane rozwiązania dają ogromne możliwości i powinny zainteresować korporacje, które tworzą i korzystają ze złożonych aplikacji napisanych w języku PHP.

F

irma Zend Technologies ogłosiła nową wersję swojej platformy do zarządzania aplikacjami PHP. Zend Platform 2 ma łączyć w sobie takie cechy jak niezawodność, skalowalność i zgodność wymaganą w ważnych aplikacjach biznesowych. Dla bardzo obciążonych serwerów przewidziano Klastrowanie Sesji (ang. Session Clustering), czyli efektywną wymianę danych sesyjnych pomiędzy serwerami pracującymi w strukturze klastra. W ten sposób serwery pracujące w klastrze mogą skuteczniej wyrównywać obciążenie pomiędzy sobą. Wg oficjalnych danych Zend, Klastrowanie Sesji powoduje nawet dziesięciokrotny wzrost wydajności aplikacji. Kolejną ważną funkcjonalnością Zend Platform 2 jest PHP Intelligence: system oparty na zdarzeniach, służący do przeprowadzania analizy aplikacji PHP w czasie rzeczywistym. Jego wykorzystanie pozwoli nam na skuteczniejsze odnajdywanie błędów spowodowanych ciągłymi zmianami dokonywanymi w kodzie programów. Zaprezentuje nieużywane funk-

LCHASH to mała biblioteka, która zapewnia dostęp do natywnych tablic rozproszonych (ang. Hash Tables), dostępnych w bibliotece libC. Korzystając z tego mechanizmu można bardzo szybko i efektywnie tworzyć oraz przechowywać duże porcje danych, używając przy tym jedynie pamięci podręcznej. Rozwiązanie to jest co prawda trochę wolniejsze od tablic PHP, lecz wykorzystuje znacznie mniej pamięci. http://pecl.php.net/package/lchash

File 1.2.2

Ukazała się wersja 1.2.2 narzędzia File umożliwiającego obiektowy dostęp do plików. Zawiera ona zestaw metod, które w znaczący sposób przyspieszają wykonywanie operacji na plikach. Przydatny może okazać się także interfejs do obsługi plików zapisanych w formacie CSV. Licencja: PHP http://pear.php.net/package/File/

CodeGen_MySQL_UDF

http://zend.com/

Istnieją dwa sposoby dodawania nowych funkcji do MySQL-a. Pierwszą jest wbudowanie ich do natywnego kodu MySQL-a i skompilowanie serwera bazodanowego. Drugą jest dodanie funkcji poprzez interfejs UDF (ang. user-definied function). Z tej możliwości korzysta biblioteka UDF_Gen, której zadaniem jest ułatwianie dodawania funkcji do MySQL-a. UDF_Gen czyta konfigurację, prototypy funkcji i fragmenty kodu z pliku XML i generuje gotowe do użycia rozszerzenie UDF. Przyda się każdemu, kto chcąc przyspieszyć swoje aplikacje, powierza niektóre zadania bazie danych. http://pear.php.net/package/CodeGen_ MySQL_UDF/

Roundcube Webmail Project

DB_QueryTool 1.0.1

C

hoć bezpłatnych programów do obsługi poczty przez WWW jest wiele, w Roundcube jest coś, co zwraca uwagę. Narzędzie to ma szczególnie estetyczny i funkcjonalny wygląd, charakterystyczny dla aplikacji pracujących na platformie Macintosh. Roundcube jest łatwy w instalacji i konfiguracji. Działa w oparciu o PHP oraz systemy bazodanowe, takie jak: MySQL, PostgreSQL oraz SQLite (którego popularność rośnie wraz z rozwojem PHP5). Motywy graficzne aplikacji stworzone zostały w oparciu o standardy projektowania stron WWW, takie jak XHTML i CSS 2.0. Roundcube jest aplikacją dostępną w różnych wersjach językowych, obsługuje typy MIME i wiadomości w formacie HTML. Pozwala również wysyłać załączniki (ang. attachments) do wiadomości. W aplikację wbudowano prostą książkę adresową, możliwość tworzenia katalogów i system buforowania, przyspieszający pracę całej aplikacji. Ponieważ Roundcube wciąż jest w stadium rozwoju alfa, wiele opcji nie zostało jeszcze zaimplementowanych.

DB_QueryTool to narzędzie pozwalające tworzyć zapytania bazodanowe w oparciu o paradygmat obiektowy. Umożliwia budowanie kwerend przy pomocy metod takich, jak setWhere(), setGroup(), setJoin(), itp. W ten sposób rozległe zapytania wyglądają na bardzo uporządkowane i takimi w istocie są. Dokonywanie późniejszych zmian w kodzie staje się znacznie łatwiejsze. Warto jeszcze dodać, że trendem w tworzeniu modułów działających pod PHP5 jest całkowite przejście wszystkich bibliotek na model obiektowy. http://pear.php.net/package/DB_QueryTool/

PHPRunner

Wśród funkcji, które producent planuje, znajduje się przekazywanie wiadomości wraz z załącznikami, wyszukiwarka wiadomości, obsługa filtrów i reguł, wsparcie dla standardu VCard (wizytówki), możliwość importu oraz eksportu maili, edytor wiadomości HTML (typu WYSIWYG), narzędzia do sprawdzania pisowni czy wsparcie dla GPG/PGP. Produkt udostępniany jest na licencji GPL. Jego działanie przetestowano na przeglądarkach Firefox 1.0, Safari 2.0, Opera 8.0 oraz Internet Explorer 6.0. Roundcube działa w nich prawidłowo.

PHPRunner to narzędzie do zautomatyzowanego tworzenia interfejsów bazodanowych w PHP. Twórcy zapewniają, że za jego pomocą można całkowicie uniknąć własnoręcznego programowania. Generuje strony WWW na podstawie danych wprowadzonych w panelu i pozwala zabezpieczyć je hasłem oraz dodać formularz logowania. Stworzony interfejs może być wielojęzyczny. Wbudowany do PHPRunnera klient FTP pozwala automatycznie zamieszczać stronę WWW na serwerze. http://www.xlinesoft.com/phprunner/

phpHtmlLib 2.5.4

http://roundcube.net/

Pojawiła się wersja 2.5.4 phpHtmlLib – zbioru klas i bibliotek służących do budowy, debugowania i przetwarzania dokumentów zapisanych w formatach XML, HTML, XHTML, WAP/WML oraz SVG (Scalable Vector Graphics). Do phpHtmlLib dochodzi zaawansowany analizator formularzy, który pozwala budować złożone formularze HTML/XHTML. W wersji 2.5.4 poprawiono wiele błędów i dodano parę nowości, takich jak możliwość szeregowania w klasie FEComboListBox czy całkowicie nowe klasy (DataObjectDataListSource i FEColorPicker). Licencja: LGPL http://phphtmllib.newsblob.com/

PHP Solutions Nr 1/2006

www.phpsolmag.org

7

Opis CD
Gregarius
Agregator RSS to aplikacja, która automatycznie pobiera i wyświetla aktualności z wielu kanałów RSS (również RDF i ATOM) jednocześnie. Gregarius to kompletny skrypt pełniący rolę agregatora. Pobiera z wielu wskazanych wcześniej źródeł internetowych świeże informacje, aby je następnie przetworzyć i wyświetlić na stronie głównej. Program oferuje sprawny mechanizm przeszukiwania wiadomości i generuje strony wynikowe w oparciu o XHTML i CSS. Do poważnych zalet narzędzia Gregarius zalicza się również łatwa instalacja i prosty w obsłudze panel administracyjny. Licencja: GPL http://gregarius.net

HTML_Progress

P

Libchart

Tworzenie wykresów w PHP nie należy do najprzyjemniejszych czynności. Biblioteka Libchart próbuje to zmienić, oferując bardzo prosty interfejs do tworzenia wykresów słupkowych i kołowych. Produkt współpracuje z PHP4 i PHP5, a do pracy wymaga zainstalowanej biblioteki GD obsługującej FreeType. Pozwala umieszczać etykiety danych (m.in. na osiach) oraz tworzyć legendę. Projekt ma w miarę dobrą dokumentację (w tym tutorial). Najnowsza wersja Libcharta nosi numer 1.0. Licencja: GNU LGPL http://naku.dohcrew.com/libchart

User Files

Własna usługa w rodzaju e-dysku (dysku internetowego, pozwalającego gromadzić swoje pliki na koncie dostępnym przez przeglądarkę WWW)? Z User Files to bardzo proste. Narzędzie to pozwala zarejestrowanym użytkownikom przechowywać pliki na dysku naszego serwera. By skorzystać z usługi, każdy użytkownik musi założyć swoje konto, podając swój prawdziwy adres e-mail. Możliwe jest jednoczesne przesyłanie wielu plików na serwer, określenie maksymalnych limitów objętości oraz ustalenie dozwolonych przez nas formatów plików. User Files potrafi zmieniać rozmiary obrazów i dodawać do nich opisy. Licencja: Freeware http://www.playth.com/scripts/userfiles/

odczas przesyłania plików na serwer przydatny jest pasek postępu. HTML_Progress to biblioteka należąca do repozytorium PEAR, która implementuje takowy. Jej możliwości są naprawdę zaawansowane. Mamy do wyboru ponad 30 różnych typów paska postępu, w tym pionowe, poziome, w postaci okręgu, eliptyczne, kwadratowe i prostokątne. HTML_Progress może ponadto pokazywać liczbowo procent przesłanych danych. Wszystkie elementy układu paska są w pełni konfigurowalne na poziomie HTML, a biblioteka spełnia warunki zgodności ze standardami XHTML i CSS. Najmniejszych problemów nie powinna również sprawić integracja biblioteki HTML_Progress z systemami szablonów, takimi jak np. Smarty. Za generowanie kolejnych etapów postępu odpowiadają skrypty napisane w języku JavaScript. Możemy umieszczać wiele pasków postępu na jednej stronie i nie wymaga to korzystania z wewnętrznych ramek (iframe). Użytkownik w każdej chwili może zatrzymać proces przesyłania

danych na serwer. Jedyne wymagania stawiane przez aplikację to dostęp do PHP oraz obsługa DHTML-a w przeglądarce WWW. Biblioteka została stworzona w oparciu o wzorzec projektowy Obserwator (ang. Observer), co pozwala na dodawanie Listenerów. Podsumowując: HTML_Progress istotnie wzbogaci funkcjonalność naszych stron WWW. Licencja: PHP License 3.0 http://pear.php.net/package/HTML_ Progress/

PhpPeanuts

P

HTML_Table_Matrix 1.0.8

Łatwe i automatyczne tworzenie i uzupełnianie tabel staje się możliwe dzięki wykorzystaniu biblioteki HTML_Table_Matrix. Jest ona bardzo zaawansowanym rozwiązaniem i pozwala dowolnie sortować dane, które chcemy wprowadzić do tabeli (rosnąco, malejąco, losowo, itd.), dbając przy tym o czystość kodu wynikowego tabeli. Podawanie wymiarów tworzonej tabeli nie jest wymagane, gdyż biblioteka sama dokona obliczeń na podstawie umieszczanych w niej danych. Do działania HTML_Table_Matrix wymaga pakietu PEAR-owego HTML_Table. Licencja: PHP v3.0 http://pear.php.net/package/HTML_Table_ Matrix/

MP3_Playlist 0.5.0alpha1

Były już skrypty do odczytu zawartych w plikach MP3 tagów ID3, a teraz przyszła pora narzędzie do obsługi list odtwarzania (ang. playlists). MP3_playlist pozwala na tworzenie i przetwarzanie tych list. Po uruchomieniu rozpocznie przeszukiwanie folderów w celu znalezienia plików MP3 i utworzenia z nich listy odtwarzania. Plik wynikowy możemy zapisać w wielu formatach: M3U, SMIL, a nawet XML i XHTML. Istnieje ponadto możliwość tworzenia kopii zapasowych list odtwarzania w oparciu o bazę SQLite. Licencja: PHP http://pear.php.net/package/MP3_Playlist/

eanuts to zorientowany obiektowo framework do tworzenia aplikacji w PHP. Programowanie z jego użyciem opiera się na architekturze kierowania modelami (ang. Model Driven Architecture), która jest tworem stosunkowo świeżym, bazującym na założeniach programowania ekstremalnego (XP, ang. eXtreme Programming). PhpPeanuts pokazuje swoje zalety już w bardzo wczesnych fazach rozwoju oprogramowania. Wystarczy stworzyć kilka komponentów aplikacji i tabel w bazie danych, a oprogramowanie automatycznie utworzy działający prototyp aplikacji. Użytkownicy będą w stanie przetestować prototyp i wyrazić swoją opinię na jego temat, co w rezultacie pozwoli efektywnie dopracować i uzupełnić program. Twórcy frameworka przygotowali łatwe w użyciu komponenty do generowania tabel, zakładek, formantów, okien dialogowych oraz wyszukiwania stron w bazach danych. Kiedy tylko dokonamy zmian, phpPeanuts błyskawicznie zaktualizuje interfejs użytkownika. Pozwala to maksymalnie

skrócić cykl rozwoju oprogramowania. Wszystkie części frameworka są skalowalne, każda może zostać pominięta, poszerzona lub uzupełniona. PhpPeanuts został napisany w PHP z wykorzystaniem HTMl-a i JavaScriptu. Może pracować na dowolnych konfiguracjach i jest łatwy w instalacji. Intefejs użytkownika phpPeanuts całkowicie opiera się na wzorcu Model-Widok-Kontroler, czyli MVC (ang. Model-View-Controller). phpPeanuts pozwala na jednoczesną obsługę wielu aplikacji na jednym serwerze. Zdecydowanie polecamy ten framework zaawansowanym programistom, którzy posiadają duże doświadczenie w zakresie programowania zorientowanego obiektowo.

Licencja: Academic Free License v. 2.0 http://www.phppeanuts.org/

8

www.phpsolmag.org

PHP Solutions Nr 1/2006

Opis CD
PHP121 Instant Messenger
Time Management

P

HP121 Instant Messenger to komunikator internetowy na stronie WWW, nie różniący się wyglądem od komunikatorów działających po stronie klienta. Osoba, która chce korzystać z PHP121 Instant Messengera na naszej witrynie, musi się najpierw zarejestrować, podając swój identyfikator (login), hasło oraz adres e-mail. Po zalogowaniu się może rozmawiać z innymi użytkownikami, którzy w tym czasie przebywają w sieci. Ciekawym zastosowaniem programu może być ułatwienie obsługi klientów, którzy aktualnie znajdują się na naszym sklepie internetowym i chcieliby nas o coś zapytać. Jego głównym przeznaczeniem jest jednak umożliwienie rozmów między zarejestrowanymi użytkownikami serwisu internetowego. Istnieje osobne wydanie PHP121 IM, stworzone z myślą o użytkownikach pakietu PHPNuke. Integruje się ono z pozostałą częścią portalu. Wymagania, jakie stawia PHP121 Instant Messenger to parser PHP i baza MySQL. Po stronie przeglądarki musi być włączona obsługa JavaScriptu. W przy-

Time Management to prosty, ale funkcjonalny skrypt, który pełni rolę terminarza zadań. Użytkownik widzi typowy miesięczny kalendarz podzielony na oznaczone datami kratki, w których wpisane są czynności do wykonania o określonej godzinie. Nowe zadania przypisujemy do konkretnych dni i godzin, korzystając z bardzo wygodnego formularza. Możemy także edytować i kasować zadania znajdujące się na liście. Tę pierwszą czynność wykonujemy również za pomocą formularza. Zarządzanie kalendarzem jest możliwe dla zalogowanego użytkownika. Time Management wymaga dostępu do bazy MySQL 4+, a jego instalacja jest bardzo prosta. Licencja: GPL http://phptime.us/

oci8 1.1.1
padku, gdy wybierzemy wydanie PHPNuke, potrzebny będzie również system PHPNuke w wersji nowszej niż 6.0. Produkt dostępny jest w zasadzie bezpłatnie, lecz aby otrzymać jego najnowszą wersję, należy wesprzeć oprogramowanie odpowiednim datkiem (co stanowi pewne kuriozum, gdy weźmiemy pod uwagę jego licencję). Tym niemniej, poprzednia wersja programu jest zawsze darmowa. Licencja: GNU GPL http://www.php121.com/

Pojawiła się udoskonalona wersja interfejsu do obsługi baz Oracle. Narzędzie to korzysta z Oracle Call Interface (OCI). W tym, jak również poprzedzającym je o kilkanaście dni poprzednim wydaniu poprawiono wiele błędów, w tym błąd naruszenia ochrony pamięci (segmentation fault) czy problemy pojawiające się po użyciu funkcji oci_error() bez argumentów. Wprowadzono też obsługę keszowania oraz zewnętrznych list uwierzytelniających. Dodano również nowe opcje konfiguracji stałych połączeń oraz poprawiono błędne działanie funkcji oci_close(), odpowiedzialnej za zamykanie połączenia z bazą. Licencja: PHP http://pecl.php.net/package/oci8

Net_IPv6 1.0.5

DreamWeaver 8

D

reamWeaver to nazwa znana każdemu chyba webmasterowi i twórcy stron WWW. W ósmej odsłonie tego produktu odnajdziemy sporo zmian. Oprogramowanie zaoferuje nam większą stabilność, wydajność, nowe narzędzia oraz łatwiejszą obsługę. W DreamWeaver 8 spotkamy się z nowym pomysłem na menu, które z rozwijalnego zamieniło się na zakładkowe. Dla programistów przewidzianio nowy pasek narzędziowy usprawniający formatowanie. Tam, gdzie chcemy lepiej zorganizować sobie duże partie kodu, przyda się możliwość rozwijania i zwijania jego fragmentów. Zaznaczając wybrane linie, grupujemy je, co pozwala na ich zwinięcie (schowanie), a następnie rozwinięcie. Pomyślano również o twórcach designu, dodając opcję powiększania. Funkcja ta, w połączeniu z linijką, pozwala na bardzo precyzyjne ustalanie takich parametrów, jak np. marginesy przygotowywanej strony. Usprawniono również nawigator pozwalający poruszać się po stylach CSS, który jest teraz łatwiej dostępny i posiada swój własny panel. Zaś

Biblioteka Net_IPv6 pozwala na stwierdzenie czy wprowadzony adres IP jest adresem z rodziny IPv6 –protokołu, który jest następcą IPv4. Umożliwia też sprawdzenie, czy adres IPv4 posiada końcówkę kompatybilną z IPv6. Wśród jego funkcji jest również kompresja i dekompresja adresów IPv6. Wdrażanie protokołu IPv6 rozpoczęto w roku 2000, a jego wprowadzenie ma zaradzić problemowi kończącej się puli adresów w IPv4. Licencja: PHP http://pear.php.net/package/Net_IPv6/

pecl_http 0.13.0

ulepszony rendering powoduje, że wygląd strony stworzonej w edytorze jest bardzo zbliżony do tego, co zobaczymy w oknie przeglądarki internetowej. Poprawiono również obsługę XML-a, CSS, PHP, WebDAV, ColdFusion 6.0 MX i Flasha 8.0. Wciąż brakuje natomiast wsparcia dla AJAX-a. Udoskonalono za to synchronizację danych pomiędzy edytowanym kodem a serwerem FTP. Wszystkie operacje odbywają się w tle, dzięki czemu nie trzeba czekać, aż proces się zakończy. Licencja: komercyjna http://www.macromedia.com/software/ dreamweaver/

Biblioteka pecl_http implementuje rozszerzoną obsługę protokołu HTTP. Umożliwia tworzenie bezwzględnych URI oraz spełniających warunki RFC przekierowań. Zapewnia zgodną z RFC obsługę daty. Pozwala parsować zarówno nagłówki, jak i treść wiadomości. Umożliwia też buforowanie danych przez użycie nagłówka Last-Modified, a także wysyłanie danych, plików i potoków danych w różnych zakresach obsługi. Funkcjonalność biblioteki jest bardzo szeroka i przyda się twórcom zaawansowanych aplikacji internetowych. Licencja: PHP http://pecl.php.net/package/pecl_http

Math_Fraction 0.4.0

Biblioteka Math_Fraction pozwala na wyświetlanie i manipulowanie ułamkami zwykłymi. Przy jej użyciu możemy dokonywać podstawowych operacji arytmetycznych na ułamkach: porównywać je, odnajdywać największy wspólny dzielnik lub największą wspólną wielokrotność dwóch liczb całkowitych. Biblioteka pomoże przy upraszczaniu (redukcji) ułamków, zwróci także jego odwrotność. Pozwala również przekształcić liczbę całkowitą na ułamek. Math_Fraction znajduje się obecnie w stadium rozwoju beta. Licencja: PHP http://pear.php.net/package/Math_Fraction/

PHP Solutions Nr 1/2006

www.phpsolmag.org

9

Opis CD
File_Find 1.0.1
Jeżeli potrzebujemy biblioteki do przeszukiwania dużej ilości plików, powinniśmy zwrócić uwagę na bibliotekę File_Find. Obsługuje poszukiwanie rekurencyjne (ang. recursive), co oznacza, że zagląda do wszystkich katalogów podrzędnych. Biblioteka pozwala na odnajdywanie plików, których nazwy spełniają postawione założenia. Możemy również szukać ciągu znaków wewnątrz plików podając określony szablon. File_Find sporządzi ponadto mapę plików w katalogu oraz rekursywną mapę wszystkich plików i katalogów podrzędnych. Licencja: PHP http://pear.php.net/package/File_Find/

PHP/Java Bridge

P

HTTP_Session 0.5.1

Prezentowana biblioteka jest interfejsem obiektowym dla funkcji z rodziny session_*. Oprócz typowej zamiany nazw funkcji na nazwy metod, mamy do czynienia z paroma dodatkowymi możliwościami, takimi jak składowanie danych o sesjach w bazach danych przy użyciu pakietów (Pear) DB, MDB, MDB2. Pojawiają się również nowe metody, w tym: isNew(), useCookies(), setExpire(), setIdle(), isExpired(), isIdled() i inne. Licencja: PHP http://pear.php.net/package/HTTP_Session/

crack 0.4

Ukazała się w pełni stabilna wersja narzędzia crack, nosząca nxumer 0.4. Zadaniem cracklib jest sprawdzanie jakości haseł. Dokonuje ono setek testów, mających na celu ustalenie, czy wybrane przez nas hasło jest trudne do odgadnięcia, czy też nie. Wykorzystuje do tego słownik, sprawdzając czy jako hasła nie używamy powszechnienie znanego słowa. Biblioteka próbuje też znaleźć podobieństwo hasła do nazwy użytkownika. Ostatecznie uzyskujemy informację, czy wybrane przez nas hasło spełnia podstawowe zasady bezpieczeństwa. Crack bazuje na bibliotece libcrack, która jest standardowo dostępna w większości systemów unixowych. W wersji 0.4 narzędzia crack, zapewniono zgodność z PHP 4.1 oraz współpracę z PEAR 1.4.0. Dodano także wersję binarną dla Windows. Licencja: PHP http://pecl.php.net/package/crack

HP/Java Bridge, czyli most pomiędzy PHP a Javą to moduł PHP, który pozwala na połączenie systemu obiektowego PHP z systemem Javy lub ECMA. Jest dostępny jako java.so (pod Linuksa) lub php_java.dll (pod Windows). Tam, gdzie to możliwe, implementuje JSR 223 (język skryptowy Javy) i może być używany do połączenia z językami CLR (wspólnego środowiska uruchomieniowego, stanowiącego podstawę frameworka Microsoftu .NET), takimi jak VB.NET, czy C#. Podstawowym środowiskiem, z którym moduł ten umożliwia połączenie, jest Java, włączając w to również takie rozwiązania, jak KAWA czy Jruby. Aby zapewnić efektywną wymianę informacji, most pomiędzy PHP, a Javą komunikuje się z wirtualną maszyną Javy (ang. Java Virtual Machine) przy użyciu lokalnych portów. Dzięki PHP/Java Bridge możemy odwoływać się bezpośrednio do Javy, korzystając z typowej składni PHP. Ten fakt w znaczący sposób ułatwia pracę z zewnętrznymi aplikacjami tworzonymi w języku Java. PHP/Java Bridge przyda się firmom, które doceniając możliwości PHP

chcą powoli rezygnować z rozwiązań opartych o Javę oraz integrować swoje oprogramowanie z PHP. Już dziś obserwujemy wzrastającą tendencję migracji z Javy na PHP. Widać to choćby po coraz liczniejszych projektach, próbujących przenieść funkcjonalność Javy do PHP, co jest w dużej mierze spowodowane nowym, ulepszonym modelem (i silnikiem) obiektowym PHP5. Problemem PHP/Java Bridge może się jednak okazać jego jego niewystarczająca wydajność. Zaleca się więc stosowanie tego rozwiązania wszędzie tam, gdzie nie istnieje zagrożenie przeciążenia serwera, czyli głównie w rozwiązaniach intranetowych oraz ekstranetowych. Podsumowując: kolejny dobrze pomyślany projekt, który przysparza popularności PHP i pozwala temu językowi zdobywać następne zastosowania i przełamywać kolejne bariery.

Licencja: PHP License http://php-java-bridge.sourceforge.net/

PHPBeans

P

html2ps 0.7.1

Biblioteka html2ps oferuje funkcjonalność zbliżoną do jej odpowiednika napisanego w Perlu. Jej zadanie polega na konwertowaniu dowolnego dokumentu HTML (również zawierającego DHTML) lub XHTML do formatu PostScript. Pozwala ustawiać m.in. orientację strony (pozioma lub pionowa) i rozmiar papieru w dokumencie wynikowym. Biblioteka radzi sobie z obrazkami, skomplikowanymi tabelami (również tymi używającymi tagów rowspan i colspan), warstwami, znacznikami <div> oraz stylami CSS. W nowej wersji dokonano wielu udoskonaleń w metodzie odpowiedzialnej za zwracanie dokumentu wynikowego (biblioteka PDFLIB), m.in. poprawiono obsługę bloków. Licencja: GPL http://www.tufat.com/script19.htm

PHPlist 2.10.1

PHPList to jeden z najpopularniejszych i najlepszych programów do masowego rozsyłania poczty elektronicznej (mailingu) i tworzenia newsletterów. Aplikację przeznaczono do obsługi wielu list mailingowych, wzbogacając ją o mechanizm pozwalający gościom naszej strony zapisywać się na wybrane listy. W nowej wersji (2.10.1) dodano wiele nowych funkcji. Najważniejszą z nich jest edytor WYSIWYG, którego rolę pełni znany produkt o nazwie FCK Editor 2, pracujący zarówno pod przeglądarką Firefox, jak i Mozilla. http://www.phplist.com

HPBeans jest serwerem obiektów i zgodnie z aktualną tendencją, stanowi kolejne rozwiązanie przeniesione z Javy. Jest oparty o specyfikację RMI, czyli Remote Method Invocation (zdalne wykonywanie metod). Jego działanie opiera się na relacji klient-serwer. W roli tego drugiego występuje PHPBeans Object Server, stanowiący repozytorium, w którym umieszczamy wszystkie nasze klasy przeznaczone do udostępnienia aplikacjom klienckim. Interfejsem klienta jest z kolei PhpBeans Client API. Może on połączyć się zdalnie z serwerem w celu wykonania zgromadzonych tam metod. Przyda się to szczególnie tam, gdzie istnieje wiele rozproszonych instancji jednej aplikacji. Istnieją dwie wersje oprogramowania klienckiego: jedna z nich jest przeznaczona dla PHP, a druga dla Ruby'ego (niezależny skryptowy język obiektowy). Oprogramowanie serwera i klienta pobieramy oddzielnie. Do wymiany informacji między serwerem a klientem służy phpBeans Protocol. Nie został on oparty na po-

pularnych protokołach dla Web Services, takich jak SOAP czy XML-RPC. Twórcy phpBeans Protocol wykorzystali własne, dedykowane rozwiązania, by komunikacja była łatwa, szybka i efektywna. PhpBeans to jednak nie tylko oprogramowanie klienta i serwera. To przede wszystkim metoda dostępu do klas ulokowanych poza naszym środowiskiem pracy. PHPBeans to rozwiązanie kierowane do twórców zaawansowanych, komercyjnych rozwiązań na dużą skalę.

Licencja: GNU GPL lub komercyjna (serwer) oraz GNU LGPL (klienci dla PHP i Ruby'ego) http://www.phpbeans.com/

10

www.phpsolmag.org

PHP Solutions Nr 1/2006

Opis CD
GraPHPite

W

ykresy w PHP możemy budować od podstaw, ale znacznie prostsze i wydajniejsze jest skorzystanie z gotowych, rozbudowanych narzędzi, takich jak obiektowo zorientowana biblioteka GraPHPite. Pod względem swojej funkcjonalności, biblioteka znacząco wyróźnia się ponad przeciętność. GraPHPite pozwala na szybkie generowanie wykresów liniowych, słupkowych, kołowych, opartych o mapę geograficzną czy mających kształt pajęczyny. Do tego dochodzą wariacje poszczególnych typów wykresów. Możliwe jest również łączenie paru typów wykresów z legendą na jednej grafice, a nawet rysowanie wykresu na wykresie. GraPHPite obsługuje antialiasing, półprzezroczystość i potrafi tworzyć gradientowe tła. Pozwala na stosowanie skali logarytmicznej. Bibliotekę można w pełni dostosować do własnych potrzeb. Dlatego też możliwa jest zmiana wszystkich elementów wyświetlanych jako grafika, od koloru linii, aż po czcionkę używaną do opisów.

GraPHPite korzysta z czcionek TrueType. Otrzymane wykresy możemy zapisać w formacie PNG lub JPEG. Docenieniem funkcjonalności bibilioteki GraPHPite jest umieszczenie jej w repozytorium PEAR. Będzie tam dostępna pod nazwą Image_Graph. Twórcy GraPHPite planują dodanie obsługi większej ilości formatów wyjściowych, tj. SVG, SWF i PDF oraz zapewnienie możliwości

tworzenia trójwymiarowych wykresów kołowych. GraPHPite korzysta z biblioteki GD lub GD2 i może współpracować z PHP4 i PHP5. Do biblioteki dołączana jest rozległa dokumentacja. Podsumowując: projekt bardzo przydatny każdemu, kto chce tworzyć zaawansowane wykresy w PHP. Licencja: GNU LGPL http://graphpite.sourceforge.net/

e

eyeOS

yeOS to niecodzienne rozwiązanie: CMS przypominający swoim wyglądem i zachowaniem okienkowy system operacyjny, taki jak Windows czy Linux z zainstalowanym środowiskiem X-Window. Działa w przeglądarce internetowej i umożliwia wykonywanie typowych czynności biurowych, takich jak liczenie na kalkulatorze, tworzenie podręcznych notatek czy edytowanie dokumentów tekstowych. Obecnie w dystrybucji systemu znajduje się 10 tego rodzaju aplikacji. Przykładowo, aby dokonać obliczeń, uruchamiamy pod eyeOS-em swój kalkulator, a gdy na chwilę nie będzie nam potrzebny, minimalizujemy go i np. uruchamiamy edytor tekstu, który przypomina Worda lub OpenOffice.org Writera. Każda aplikacja działa w osobnym oknie, które możemy dowolnie przesuwać w oknie przeglądarki, a także minimalizować, powiększać lub zamykać. Aplikacja dysponuje ponadto własnym komunikatorem oraz przeglądarką stron WWW. Nie zabrakło również narzędzia do zarządzania plikami. Cały system sprawia wrażenie funkcjonalnego i intuicyjnego, bazując przy tym na nowoczesnym designie. eyeOS jest również wielojęzyczny

(ma m.in. wersję polską, francuską, włoską i niemiecką). Produkt znajduje się jeszcze w fazie rozwoju, a aktualną wersją pozostaje 0.8.3. EyeOS ma pewne problemy z obsługą przeglądarek, choć nie szkodzi to zbytnio jego funkcjonalności. Z założenia jest również rozbudowywalny, a jego twórcy zachę-

cają do przenoszenia do niego kolejnych aplikacji ze świata PHP. Podsumowując: bardzo ciekawe rozwiązanie, które może zrewolucjonizować nasze podejście do kwestii związanych ze środowiskiem pracy oraz interfejsami użytkownika w PHP. Licencja: GNU GPL http://www.eyeos.org

12

www.phpsolmag.org

PHP Solutions Nr 1/2006

Opis CD
SugarCRM 3.5.0

J

eżeli szukamy systemu do zarządzania kontaktami z klientami (CRM), bardzo możliwe, że to właśnie SugarCRM spełni wszystkie nasze wymagania. Oprogramowanie oferowane jest w dwóch wersjach. Pierwsza z nich jest bezpłatna i dostępna na opensourcowej licencji SPL (bazującej na MPL). Druga wersja SugarCRM-a jest komercyjna i ma nieco większą funkcjonalność oraz zaplecze w postaci pomocy technicznej. SugarCRM to aplikacja webowa pracująca po stronie serwera. Jest przeznaczona na platformę LAMP (Linux, Apache, MySQL, PHP), ale uruchomimy ją wszędzie tam, gdzie jest PHP i MySQL, niezależnie od typu serwera WWW i systemu operacyjnego. SugarCRM działa w oknie przeglądarki WWW i nie potrzebuje żadnych dodatkowych modułów. Współpracuje ze wszystkimi przeglądarkami internetowymi. Niestety, praca pod Internet Explorerem nie jest pozbawiona mankamentów, gdyż dane przychodzące z serwera są szyfrowane, a IE ma mniej wydajny algorytm deszyfrujący niż Firefox. SugarCRM przeznaczony jest dla działów sprzedaży, marketingu i wsparcia, w firmach, w których potrzebna jest bardziej efektywna komunikacja z klientami. W odróżnieniu od innych systemów tej klasy, SugarCRM zrywa z konwencją systemu o wysokich kosztach, słabej skalowalności i małych możliwościach rozszerzania funkcjonalności. Zamiarem jego autorów było stworzenie aplikacji o przystępnej, modułowej architekturze,

która obalałaby dotychczasowe przekonanie, że CRM-y to rozwiązania przeznaczone wyłącznie dla najbogatszych firm. Jak już powiedzieliśmy, SugarCRM to narzędzie opensourcowe. System skupia wokół siebie społeczność programistów, dla których stworzono repozytorium dodatków i udoskonaleń o nazwie SugarForge (analogia do SourceForge.net). W jego ramach swoje projekty rozpowszechnia ponad 2000 programistów. Znajdziemy tam zarówno pakiety lokalizujące, jak i oprogramowanie pozwalające zintegrować naszego CRM-a z systemami CMS takimi, jak Mambo. SugarCRM pozwoli obsłużyć całą armię ludzi, ograniczając jednocześnie ich uprawnienia. Wszystkim spośród swoich pracowników możemy utworzyć osobne konta, nadając im prawa do przeglądania wyłącznie własnych materiałów. Będąc szefem firmy, mamy możliwość przeglądania i nadzorowania efektów pracy wszystkich naszych podwładnych. Panel aplikacji zaprojektowano bardzo funkcjonalnie. Poziomo rozmieszczono zakładki, które stanowią menu całej aplikacji. Główna strona panelu pozwala szybko zorientować się, jakie w danym dniu mamy spotkania i obowiązki, czym aktualnie się zajmujemy, jakie sprawy i zadania aktualnie prowadzimy. Tym, co rzuca się w oczy, jest wykres możliwych

do uzyskania przychodów. Pokazuje, jakie mamy możliwości zarobków w różnych dziedzinach naszej pracy. Przydatny będzie też z pewnością kalendarz, pełniący jednocześnie funkcję terminarza przypominającego o ważnych sprawach (spotkaniach, zadaniach, telefonach). Podstawowym zadaniem każdego systemu CRM jest składowanie pełnej korespondencji z danym klientem. Tak stworzone archiwum spotkań, rozmów, notatek i korespondencji emailowej pozwala w szybki sposób prześledzić zachowania klienta lub znaleźć potrzebne informacje na jego temat. Bez wątpienia przyda się możliwość szybkiego dodawania i zarządzania kontaktami. SugarCRM pozwala importować kontakty zapisane w formacie vCard, CSV, jak również całe bazy, z programów typu Microsoft Outlook czy Act!2005. Pozyskiwanie nowych klientów ułatwi moduł Przesłanki handlowe, a zakładka Okazje sprzedaży przyczyni się do wzrostu obrotów w firmie. Obsługę spraw pozasprzedażowych ułatwi moduł Sprawy, pozwalajacy obsłużyć np. reklamacje gwarancyjne. SugarCRM oferuje znacznie więcej funkcji i nie sposób ich tu wszystkich wymienić. Narzędzie znajduje się na bardzo zaawansowanym etapie rozwoju i jest praktycznie pozbawione większych błędów projektowych. Gdy dodamy do tego możliwość rozszerzania systemu, uzyskamy znakomity, bezpłatny produkt. Jeśli i to nam nie wystarczy, zawsze możemy sięgnąć po komercyjną wersję programu SugarCRM. Licencja: GPL Cena wersji komercyjnej (Sugar Professional): 239,00$ http://www.sugarcrm.com

14

www.phpsolmag.org

PHP Solutions Nr 1/2006

Na CD
Narzędzia UltraEdit-32 11.20 Programy z serii Ultra są 45-dniowymi trialami. Triale mogą być przedłużone na prośbę czytelników, którzy wyślą w tej sprawie mail na adres: idm@idmcomp.com UltraSentry v2.0 UltraCompare Professional v3.00 Roadsend Compiler for PHP 1.6 – 21-dniowy trial e-biznes Simpletest 1.0.0 – GPL SugarCRM 3.5 – System do zarządzania php-time – GPL kontaktami z klientami na GPL! php-java-bridge-2.8.0 – GPL php-mono-bridge-2.8.0 – GPL EyeOS 0.8.4 – GPL 14 książek elektronicznych w tym 4 nowe IDE DreamWeaver 8 – 30-dniowy trial Maguma Workbench 2.6 – 30-dniowy trial UltraStudio 05 – 45-dniowy trial z możliwością przedłużenia na zasadach takich jak UltraEdit, Sentry oraz Compare PHPDesigner – freeware PhpwScite – GPL CMS
Drupal 4.6 – GPL eZ Publish 3.7.0 – GPL Bitrix Site Manager 4.0.5 – 30-dniowy trial. Czytelnicy PHP Solutions mają 5% zniżki na pełną wersję programu. Szczegóły na www.bitrixsoft.com/phpsolutions Content Manager 2.2.07 – Specjalna wersja – niewygasające demo Samba-3 by example The Rise of Open Source Licensing Linux in the Workplace Linux Device Drivers 3rd Edition

PEAR
HTML_AJAX-0.2.3 SDO-0.6.0 PHPUnit2-2.3.2 pecl_http-0.16.0 PDO

W

z ra

ie

p

wz mó ble ro

apisać pod a proszę n dres : cd płytą @s of

tw are .co

m .p
l

Wszystkie listingi z artykułów zostały zamieszczone na naszej stronie internetowej pod adresem www.phpsolmag.org/pl

Wszystkie listingi z artykułów zostały zamieszczone na naszej stronie internetowej pod adresem www.phpsolmag.org/pl

Początki

Otwarty format OASIS dla dokumentów biurowych i CMS-ów
Bård Farstad

Wklejanie czystego tekstu do CMS-a i jego ręczne formatowanie jest zadaniem żmudnym i podatnym na błędy. Gdyby tak można było stworzyć odpowiedni dokument w swoim ulubionym pakiecie biurowym, a potem po prostu skopiować plik i wkleić go do CMSa... Taką właśnie możliwość daje połączenie trzech nowatorskich produktów: uniwersalnego formatu OpenDocument, CMS-a eZ publish i pakietu biurowego OpenOffice.org 2.0.

F
W SIECI
1. http://www.oasisopen.org/committees/ tc_home.php?wg_ abbrev=office – standard OASIS OpenDocument 2. http://en.wikipedia.org/wiki/ OpenDocument – wpis w Wikipedii na temat formatu OpenDocument 3. http://www.ez.no – strona projektu eZ publish 4. http://www.ez.no/community/ contribs/import_export/ oasis_open_document_ extension – rozszerzenie OASIS dla eZ publish 5. http://www.ez.no/ documentation – dokumentacja eZ publish

ormat OpenDocument, czyli otwarty format dla aplikacji biurowych, został opracowany przez konsorcjum OASIS (Organization for the Advancement of Structured Information Standards, czyli Organizację ds. Rozwoju Strukturalizowanych Standardów Informacyjnych) i jest propozycją standardowego formatu zapisu i wymiany dokumentów tworzonych w pakietach biurowych, a więc arkuszy kalkulacyjnych, prezentacji, wykresów czy też tekstów. W tym artykule zajmiemy się przede wszystkim dokumentami tekstowymi. Pierwsza wersja specyfikacji OpenDocument powstała w wyniku współpracy organizacji zrzeszonych w ramach OASIS. Podstawą dla specyfikacji był XML-owy format plików OpenOffice.org, jednak specyfikacja wprowadza też wiele istotnych zmian. Format OpenDocument został zatwierdzony jako standard OASIS pierwszego maja 2005 roku. Celem standardu jest dostarczenie wspólnej metody

zapisu dokumentów, niezależnej od wewnętrznych formatów poszczególnych aplikacji. Wprowadzenie wspólnego formatu oznacza dla użytkownika swobodę wyboru aplikacji, a dla producentów oprogramowania zwiększoną konkurencję, a zarazem konieczność współpracy. Od strony technicznej, specyfikacja OASIS jest dokumentem określającym metody opisu tekstu i treści multimedialnych w typowych dokumentach pakietów biurowych. Standard definiuje schemat XML pozwalający opisywać

Powinieneś wiedzieć... Obiecujemy...

Powinieneś znać podstawy pracy z PHP i eZ publish. Po przeczytaniu artykułu będziesz wiedział jak przekazywać treść między programem OpenOffice.org Writer a systemem eZ publish oraz jak dołączać obsługę formatu OpenDocument do aplikacji PHP.

16

www.phpsolmag.org

PHP Solutions Nr 1/2006

Otwarty format dokumentów OASIS

Początki

• • • •

OpenOffice.org 1.1.5 i 2.0, Scribus, TextMaker, Visioo Writer.

Migracja z formatów poszczególnych aplikacji

Rysunek 1. Dokument edytowany w MS Word

strukturę tekstu, nagłówków, list, tabel, obiektów osadzonych i innych elementów dokumentu. Wynikowy plik jest zwykłym archiwum ZIP, zawierającym pewną liczbę plików XML i ewentualnie osadzonych w dokumencie obrazów lub obiektów multimedialnych. Format OASIS OpenDocument jest standardem otwartym i prawdopodobnie stanie się w przyszłości standardem ISO. Ponieważ jest to format otwarty, można go używać do zapisu i wymiany informacji w dowolnych aplikacjach.

Według Wikipedii, do aplikacji obsługujących format OpenDocument należą obecnie: • • • • • Abiword, eZ publish, IBM Workplace, Knomos, KOffice,

Istniejące dokumenty zapisane we własnych formatach aplikacji (na przykład dokumenty MS Word) można konwertować do formatu OpenDocument za pomocą oprogramowania Open Source, na przykład pakietu OpenOffice.org – darmowej aplikacji obsługującej wiele różnych formatów, w tym OpenDocument. Konwersję dokumentów w OpenOffice.org dodatkowo ułatwia możliwość wywoływania makr z linii poleceń. Oznacza to, że wykorzystanie OpenOffice.org pozwala nie tylko ręcznie konwertować dokumenty do formatu OpenDocument, ale również automatyzować ten proces i wbudowywać go w inne aplikacje. Niektóre produkty (na przykład eZ publish) już teraz obsługują tę metodę konwersji, choć korzystanie z ich możliwości wymaga zainstalowania wersji 2 pakietu OpenOffice.org.

Praktyczny przykład

Wykorzystanie standardu OpenDocument i oprogramowania Open Source do

Aplikacje obsługujące format OpenDocument

Od czasu oficjalnego ogłoszenia standardu OpenDocument w maju 2005 r. pojawiło się już kilka aplikacji ten format obsługujących. Coraz szerszej obsłudze standardu w aplikacjach towarzyszy jego rosnąca popularność wśród użytkowników.

Rysunek 2. Kopiowanie dokumentu do systemu eZ publish za pośrednictwem interfejsu WebDAV

Rysunek 3. Dokument importowany do eZ publish

PHP Solutions Nr 1/2006

www.phpsolmag.org

17

Początki

Otwarty format dokumentów OASIS
tworzenia, konwersji i udostępniania danych prześledzimy na przykładzie. Zaczniemy od stworzenia zwykłego dokumentu MS Word w formacie DOC. Dokument ten importujemy następnie do eZ publish za pośrednictwem WebDAV, po czym z pomocą pakietu OpenOffice.org 2.0 konwertujemy go do własnego formatu danych eZ publish. Opisywany tu eZ publish (http://www.ez.no) jest systemem zarządzania treścią (CMS-em) klasy enterprise, napisanym w PHP i dostępnym na zasadach Open Source. Pierwszą czynnością będzie utworzenie w Wordzie zwykłego dokumentu, zawierającego sformatowany tekst i obrazek. Ostateczny dokument powinien wyglądać mniej więcej tak, jak na Rysunku 1 i powinniśmy go zapisać gdzieś na lokalnym dysku. Następnym krokiem będzie przeniesienie zawartości tego dokumentu do systemu eZ publish uruchomionego na innym komputerze. Do samego przekazania dokumentu posłużymy się obsługiwanym przez eZ publish interfejsem WebDAV, pozwalającym w prosty sposób kopiować i publikować dokumenty. W tym przykładzie użyjemy menedżera plików Konqueror uruchomionego w linuksowym środowisku graficznym KDE – dzięki WebDAV wystarczy jedynie przeciągnąć dokument z dysku windowsowego udostępnianego poprzez SMB i „upuścić go” do eZ publish. Rysunek 2 przedstawia widok interfejsu WebDAV dla eZ publish z poziomu Konquerora – widoczny jest również plik dokumentu przeciągany z dysku Windows. eZ publish automatycznie rozpoznaje format plików Worda publikowanych za pośrednictwem interfejsu WebDAV i za pomocą pakietu OpenOffice.org konwertuje je na format OpenDocument. Zawartość dokumentu po konwersji jest importowana już bezpośrednio do systemu zarządzania treścią dzięki rozszerzeniu eZ publish obsługującemu format OASIS. Po zakończeniu importu, dokument jest od razu dostępny jako artykuł opublikowany w eZ publish – Rysunek 3 przedstawia widok takiego artykułu. Podstawowa wersja operacji importu danych z formatu OpenDocument pobiera całą sformatowaną treść, ale bez definicji układu – importowane są na przykład wszystkie nagłówki, ale już nie zdefiniowany w Wordzie rozmiar czy kolor czcionki. Załóżmy, że chcemy taki artykuł edytować za pośrednictwem standardowego interfejsu administracyjnego eZ publish

Rysunek 4. Edycja importowanego dokumentu w eZ publish

Rysunek 5. Eksport dokumentu z eZ publish

18

www.phpsolmag.org

PHP Solutions Nr 1/2006

Otwarty format dokumentów OASIS

Początki

Rysunek 6. Podgląd wydruku naszego dokumentu w programie OpenOffice.org Writer
– Rysunek 4 przedstawia ekran edycji dokumentu. Jak widać, zachowywane jest formatowanie zdefiniowane w oryginalnym dokumencie, więc nagłówek, lista wypunktowana i obraz pozostają na swoich miejscach. Teraz chcemy się zająć dalszą edycją dokumentu, ale tym razem w programie OpenOffice.org Writer. W tym celu wystarczy wyeksportować artykuł z eZ publish do pliku w formacie OASIS OpenDocument. Rysunek 5 pokazuje, jak eksportować artykuł z eZ publish. Po eksporcie dokument, możemy dowolnie modyfikować jego styl, na przykład nakładając zwykły szablon OpenOffice.org. Pozwala to, w bardzo prosty sposób, utrzymywać jednolitą stylistykę dokumentów, a podstawowy szablon można rozbudować na przykład o nagłówki i stopki stron. Rysunek 6 przedstawia podgląd wydruku naszego dokumentu po

Rysunek 8. Wygląd bardziej złożonego dokumentu w programie OpenOffice.org Writer
zakończeniu edycji w programie OpenOffice.org Writer. W porównaniu z pierwotnym dokumentem, dodaliśmy nagłówek i stopkę strony oraz zdefiniowaliśmy styl czcionki – elementy opisane w warstwie prezentacji formatu OpenDocument. Po zapisaniu zmodyfikowanego dokumentu w OpenOffice.org możemy go ponownie importować do systemu eZ publish z pomocą funkcji zastępowania (dokadnie funkcja Repleace OpenOffice.org w menu kontekstowym). Pozostaje już tylko podziwiać efekt końcowy, czyli artykuł eZ publish po edycji w OpenOffice.org i ponownym imporcie (Rysunek 7). W tym przykładzie skorzystaliśmy z bardzo prostego dokumentu. Rysunek 8 przedstawia dokument o znacznie bardziej złożonym formatowaniu, importowany bezpośrednio z pliku OpenDocument stworzonego w OpenOffice.org. Import treści do eZ publish zachowuje formatowanie dokumentu, ale szczegóły wyglądu artykułu są zależne od aktualnych ustawień (Rysunek 9). Poznaliśmy więc przykład podstawowych możliwości wykorzystania otwartych standardów, pozwalających bezproblemowo wymieniać treści między różnymi aplikacjami i systemami.

Struktura pliku OpenDocument

Rysunek 7. Aktualizacja artykułu importowanego do eZ publish

Bez większego wysiłku można rozbudować dowolną aplikację PHP o obsługę standardu OpenDocument – format pliku jest szczegółowo udokumentowany, a do generowania i zapisu dokumentów wystarczają standardowe narzędzia PHP. Sam plik OpenDocument (o rozszerzeniu .odt) jest zwykłym archiwum ZIP, więc wystarczy go po prostu rozpakować, by przyjrzeć się jego zawartości (Rysunek 10). Jak sama nazwa wskazuje, plik meta.xml zawiera metadane dokumentu, a więc datę utworzenia, czas edycji, statystyki (np. liczbę słów i akapitów) itd. Plik mimetype zawiera po prostu typ MIME dokumentu – w naszym przypadku będzie to application/vnd.oasis.opendocu ment.text. W pliku styles.xml znajdują się definicje czcionek, wyrównania kolorów i innych atrybutów stylu. Wreszcie plik settings.xml zawiera ustawienia interfejsu użytkownika dla aplikacji, w której ostatnio edytowano dokument, a manifest.xml wyszczególnia wszystkie pliki składające się na dokument. W tym przypadku będzie nas interesować plik content.xml oraz wszelkie pliki znajdujące się w podkatalogu Pictures

PHP Solutions Nr 1/2006

www.phpsolmag.org

19

Początki

Otwarty format dokumentów OASIS
– plik content.xml zawiera faktyczną treść dokumentu, więc tam będziemy szukać danych, natomiast w katalogu Pictures spodziewamy się znaleźć plik osadzonego w dokumencie obrazu w formacie JPEG. Schemat XML używany w ramach formatu OpenDocument wykorzystuje przestrzenie nazw, więc stosowany do przetwarzania dokumentów parser musi je obsługiwać. Listing 1 przedstawia uproszczoną wersję pliku content.xml (usunąłem z niego znaczniki niezwiązane z podstawową strukturą dokumentu). Opisywany dokument składa się z nagłówka i akapitu tekstu. Treść tą ujęto w znacznik <office:text>, który zawiera się kolejno w znacznikach <office:body> i <office: document-content>. Tu uwaga: oglądanie plików XML generowanych przez OpenOffice.org jest znacznie wygodniejsze po wyłączeniu ich optymalizacji (menu Narzędzia -> Opcje -> Ładuj/Zapisz -> Ogólne) – pliki bez optymalizacji są dużo bardziej czytelne.

Rysunek 9. Bardziej złożony dokument po imporcie do eZ publish
Listing 1. Dokument XML opisujący nagłówek i akapit tekstu
<office:document-content> <office:body> <office:text> <text:h text:style-name="Heading_20_1" text:outline-level="1">To jest nagłówek</text:h> <text:p text:style-name="Text_20_body"> To jest zwyczajny akapit.</text:p> </office:text> </office:body> </office:document-content>

Wykorzystanie formatu OpenDocument w aplikacjach PHP

Listing 2. Parsowanie zawartości pliku .odt w PHP
<h1>Test importu XML z pliku OpenDocument</h1> <hr /> <?php include_once('lib/ezxml/classes/ezxml.php'); // $xml jest instancją eZXML, a $dom - modelem obiektowym dokumentu XML $xml = new eZXML(); $dom =& $xml->domTree(file_get_contents("documents/simpledoc/content.xml")); $bodyNodeArray = $dom->elementsByNameNS( 'body', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0' ); // sprawdzenie, czy w pliku występuje pojedynczy znacznik ciała dokumentu if ( count( $bodyNodeArray ) == 1 ){ $bodyNode =& $bodyNodeArray[0]; $xhtmlTextBody = ""; foreach ( $bodyNode->children() as $childNode ){ $xhtmlTextBody .= handleNode( $childNode ); // dopisywanie wynikowego kodu XHTML } print( $xhtmlTextBody ); }

Wiemy już, na czym polega wymiana danych OpenDocument między klientem i serwerem, pora więc zająć się implementacją obsługi tego formatu w naszych aplikacjach PHP. Napiszemy skrypt pobierający treści tekstowe z pliku ODT i przekształcający je na stronę XHTML. Najpierw musimy się zaopatrzyć w parser DOM XML – w tym przykładzie wykorzystamy wchodzącą w skład środowiska eZ publish bibliotekę eZ xml, ale równie dobrze sprawdzi się dowolny inny parser XML. Spójrzmy na przykładowy kod z Listingu 2. Zaczynamy od wypisania tekstu HTML informującego o teście importu XML-a z pliku OpenDocument, po czym wstawiamy poziomą linię. Kod PHP zaczynamy od dołączenia biblioteki XML, której nakazujemy następnie wczytanie danych XML i utworzenie dla nich drzewa DOM. Potrzebne nam dane znajdują się wewnątrz znacznika <body>, więc próbujemy pobrać zawartość węzła body, podając jego nazwę i URI przestrzeni nazw. Warto zwrócić uwagę, że identyfikatorem przestrzeni nazw nie jest tu sam przedrostek office, ale pełna nazwa zasobu (URN), czyli urn:oasis:names:tc: opendocument:xmlns:office:1.0. Jest to konieczne, gdyż przedrostek przestrzeni

20

www.phpsolmag.org

PHP Solutions Nr 1/2006

Otwarty format dokumentów OASIS
nazw jest jedynie aliasem dla pełnej nazwy zasobu i może ulec zmianie. Sprawdzamy, czy znaleźliśmy dokładnie jeden pasujący węzeł drzewa DOM, co pozwala dodatkowo zweryfikować poprawność dokumentu (powinien być tylko jeden znacznik <body>). Gdy już mamy węzeł body, możemy przejść po wszystkich jego węzłach potomnych w pętli foreach. Do przetwarzania węzłów potomnych stworzyłem funkcję handleNode(), zwracającą wyświetlany na końcu kod HTML. Kod funkcji handleNode() przedstawia Listing 3. Zaczynamy od sprawdzenia nazwy bieżącego węzła (czyli znacznika) w bloku switch. Dla potrzeb tego przykładu obsłużymy jedynie <h> oraz <p> – napotkanie dowolnego innego znacznika spowoduje wyświetlenie komunikatu Nieobsługiwany element. W przypadku znacznika <h> (czyli nagłówka), zaczynamy od pobrania atrybutu level, na podstawie którego generujemy Listing 3. Kod funkcji handleNode()
function handleNode( $node ){ $xhtmlTextContent = ""; switch ( $node->name() ){ case 'h' : { $level = $node->attributeValueNS( 'level', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0' ); if ( $level >= 1 && $level <= 6 ){ $xhtmlTextContent.="<h$level>".$node->textContent()."</h$level>"; } else print( "Nieobsługiwany poziom nagłówka" ); }break; case 'p' : { $paragraphContent = ""; foreach ( $node->children() as $childNode ){ switch ( $childNode->name() ){ case "#text" : { $paragraphContent .= $childNode->content(); }break; case "image" : { $href = ltrim( $childNode->attributeValueNS( 'href', 'http://www.w3.org/1999/xlink' ), '#' ); $paragraphContent .= "<img src='$href' alt=''/>"; }break; default: { print( "Nieobsługiwany element: " . $childNode->name() . "<br>" ); }break; } } $xhtmlTextContent .= '<p>' . $paragraphContent . '</p>'; }break; default: { print( "Nieobsługiwany element " . $node->name() . "<br/" ); } } return $xhtmlTextContent; } ?>

Początki

kod nagłówka HTML odpowiedniego poziomu. Potem wystarczy już tylko dopisać tekst nagłówka wraz ze stosownymi znacznikami do zmiennej $htmlTextContent, w której przechowujemy zwracany przez funkcję kod HTML. Przetwarzanie znacznika <p> jest nieco bardziej skomplikowane. Węzeł akapitu może mieć węzły potomne, po których przechodzimy w pętli foreach. W bloku switch sprawdzamy nazwę bieżącego węzła – w naszym prostym przykładzie obsługiwane są jedynie węzły o nazwach #text i image, a napotkanie dowolnej innej nazwy znów kończy się komunikatem Nieobsługiwany element. Zawartość węzła #text jest doklejana do zmiennej przechowującej treść akapitu, natomiast w przypadku węzła image (czyli obrazu), będzie nas interesować tylko ścieżka do pliku obrazu, więc pobieramy wartość atrybutu href węzła obrazu i używamy jej do zbudowania HTML-owego znacznika

��������������� �������� ������������ �������� ���������� ������������� ����������� �������� �������� ������������ ����������

Rysunek 10. Zawartość pliku .odt – struktura plików i katalogów
<img>.

Dodatkowo usuwamy pierwszy znak ciągu pobranego z href, dzięki czemu otrzymujemy względną ścieżkę do pliku, nadającą się do bezpośredniego użytku. Po przetworzeniu wszystkich węzłów potomnych dopisujemy ich zawartość oraz zamykający znacznik </p> do zmiennej przechowującej wynikowy kod HTML akapitu.

Podsumowanie

W tym artykule starałem się pokazać możliwości wykorzystania standardu OASIS OpenDocument w codziennej pracy i wymianie informacji. Aplikacje Open Source dają tu ogromne możliwości, w tym łatwe przechodzenie z własnych formatów aplikacji (np. Worda) na formaty otwarte oraz możliwość bezpośredniej edycji konwertowanego dokumentu. Prześledziliśmy też przykład edycji treści w różnych aplikacjach i kontekstach, przekonując się, że dane mogą równie dobrze występować w postaci tradycyjnych dokumentów, jak i artykułów publikowanych w Internecie. Wszystko to pokazuje, jak wiele mogą zdziałać producenci oprogramowania, gdy ustalą wspólny standard i zaimplementują go w swoich aplikacjach. n

O autorze
Bård Farstad jest jednym z założycieli i głównym deweloperem eZ systems. CMS-ami profesjonalnie zajmuje się od 1999 roku. Jest autorem wielu projektów oraz bibliotek dla PHP (np. ez XML – parser XML-a). Kontakt z autorem: bf@ez.no

PHP Solutions Nr 1/2006

www.phpsolmag.org

21

Narzędzia

Programowanie sterowane testami za pomocą PHPUnit
Timo Haberkern

Im większy projekt programistyczny, tym trudniej wyłapywać pojawiające się w nim błędy, a usunięcie jednych usterek powoduje często powstanie następnych, w innej części aplikacji. Ręczne tworzenie testów dla setek klas jest nieskuteczne, a poza tym przyprawia o ból głowy i paraliżuje pracę. Z pomocą przychodzi PHPUnit: narzędzie pozwalające zautomatyzować proces tworzenia i wdrażania testów.

W

W SIECI
1. http://www.phpunit.de – strony projektu PHPUnit 2. http://www.testdriven.com – witryna o TDD 3. http://www.junit.org – projekt jUnit

yobraźmy sobie fikcyjną, obiektowo zorientowaną aplikację PHP, która składa się z bardzo wielu klas. Między tymi klasami istnieją oczywiście zależności, co oznacza również, że błędy występujące w klasie bazowej powodują kolejne w klasach potomnych. W aplikacji zawierającej kilkaset klas może to doprowadzić do poważnych problemów, gdyż nie możemy po każdej zmianie w klasach bazowych testować od nowa całej aplikacji. Przynajmniej dotychczas tak było! W świecie Javy od dawna możemy przeprowadzać testy zwane jednostkowymi (ang. unit tests). Są one wykonywane automatycznie przez framework testowy, a ich rezultatem jest m.in. liczba testów zakończonych powodzeniem i niepowodzeniem. Stworzone przez Sebastiana Bergmanna narzędzie PHPUnit jest frameworkiem zapewniającym tę funkcjonalność w PHP. Zapytacie pewnie, jak to możliwe, że jeden program testuje automatycznie dzia-

łanie innego? Odbywa się to za pomocą predefiniowanych testów. W każdym z nich sprawdzana jest konkretna funkcjonalność wybranej klasy, a zwrócony rezultat funkcji jest porównywany z wartością oczekiwaną. Jeżeli oba wyniki są równe, to znaczy, że badana funkcja działa poprawnie. W przeciwnym wypadku mamy do czynienia z błędem. PHPUnit pozwala na łatwe pisanie testów i gromadzenie ich większej liczby w tzw. zbiorach testów (PHPUnit: TestSuites). PHPUnit sprawdza wszystkie testy w zbiorze i zwraca nam liczbę

Co należy wiedzieć... Co obiecujemy...

Powinieneś znać podstawy programowania obiektowego w PHP. Po przeczytaniu artykułu będziesz wiedział, jak automatycznie i bez zbytniego wysiłku sprawdzać działanie aplikacji PHP za pomocą testów jednostkowych i frameworka PHPUnit. Artykuł bazuje na wersji 2.2.1 PHPUnit.

22

www.phpsolmag.org

PHP Solutions Nr 1/2006

PHPUnit

Narzędzia

Instalacja przy użyciu PEAR-a

PHPUnit jest częścią repozytorium PEAR, więc aby go zainstalować, wpisujemy w linii poleceń: pear install --alldeps phpunit2. Dzięki użyciu parametru --alldeps zainstalowane zostaną także wszystkie wymagane przez PHPUnit biblioteki.

Listing 1. Interfejs klasy słownikowej Dictionary dla pierwszych przykładów
<?php class Dictionary{ public function addTranslation($strEnglish, $strTranslated){ } public function getTranslation($strEnglish){ } public function removeTranslation($strEnglish){ } public function isEmpty(){ } public function loadFromDb(){ } public function saveToDb(){ } private $m_hashTranslations; } ?>

Instalacja bez PEAR-a

Instalacja PHPUnit jest możliwa również bez PEAR-a. W tym celu pobieramy go spod adresu http://pear.php.net/package/PHPUnit. Potrzebujemy również pakietów Log (http://pear.php.net/package/Log) i Benchmark (http://pear.php.net/package/Benchmark). Wszystkie pakiety rozpakowujemy w wybranym katalogu naszego serwera, a następnie dopisujemy ścieżkę tego folderu do parametru include_path w pliku php.ini. Jeżeli nie mamy dostępu do pliku php.ini, możemy podawać ścieżkę include_path w każdym skrypcie za pomocą wyrażenia:
ini_set("include_path", "path/to/phpunit/library".PATH_SEPARATOR.ini_get("include_path"));

Ręczna instalacja nie jest, niestety, identyczna z PEAR-ową. Brakuje na przykład konsolowej komendy phpunit, której używamy w tym artykule do wykonywania testów. Bez niej musimy się ograniczyć do skryptów PHP, które działają na serwerze WWW. Dlatego, o ile to możliwe, powinniśmy zawsze wybierać instalację PEAR-ową.

Instalacja Xdebug

W celu uzyskiwania informacji o działaniach zachodzących w kodzie, PHPUnit używa rozszerzenia PHP o nazwie Xdebug (http://www.xdebug.org). Aby je zainstalować pod Windows, wystarczy je pobrać (w postaci pliku DLL) spod adresu http://www.xdebug.org/ link.php?url=xdebug200b1-50-win, a następnie zapisać w katalogu rozszerzeń PHP (php/ ext). Później dodajemy nazwę tego pliku do listy rozszerzeń PHP, dopisując w php.ini następującą linijkę: zend_extension_ts="c:\php\ext\xdebug-5.0-2.0.0beta1.dll". Po restarcie serwera Xdebug jest do naszej dyspozycji. Ważne jest, aby zainstalować Xdebug jako rozszerzenie Zend, gdyż w przeciwnym razie nie będziemy mogli go stosować wraz z PHPUnitem. Jeśli więc w naszym pliku php.ini jest już wpis: extension=php_ xdebug.dll, to musimy go usunąć lub zablokować znakiem komentarza. Instalacja Xdebuga pod Linuksem jest nieco bardziej skomplikowana, a jej dokładny opis znajdziemy pod adresem http://www.xdebug.org/install.php.

przeprowadzonych bezbłędnie oraz listę tych, które nie zostały zakończone pozytywnie (Rysunek 1). Rysunek 2 pokazuje jeszcze raz zależności między opisanymi tutaj komponentami (testami i metodami). Ogromną zaletą testów jednostkowych w porównaniu do testów przeprowadzanych ręcznie przez programistę jest ich szybkość i możliwość dowolnej liczby powtórzeń. Wykonanie takiego zbioru testów wymaga bardzo niewiele czasu i (w zależności od liczby testów) i pokrywa dużą część naszego kodu. Umożliwia nam to sprawdzenie funkcjonowania naszej aplikacji w najdogodniejszym czasie, np. pod koniec dnia pracy czy po wprowadzeniu zmian.

Pierwszy test

dziedziczy po klasie PHPUnit_Framework_ TestCase. Na Listingu 2 pokazujemy nasz przykładowy test. Przy pomocy dwóch testów jednostkowych chcemy się upewnić, czy funkcja ta pracuje tak, jak oczekujemy. Aby wykonać test z Listingu 2, musimy przejść do katalogu z testem i wykonać na konsoli następującą komendę:
phpunit FillTest

Tyle teorii – teraz czas na przyjrzenie się tworzeniu i wykonywaniu testów. Dla tego i następnych przykładów będziemy testować klasę, która implementuje słownik polsko-angielski. Powinna ona zawierać metody umożliwiające dodawanie, kasowanie i odczytywanie tłumaczeń słów. W dalszej części artykułu rozszerzymy tę klasę o pewne właściwości, jak np. połączenie bazodanowe. Na Listingu 1 przedstawiamy interfejs klasy Dictionary, której używamy w przykładach. W naszym pierwszym teście chcemy sprawdzić, czy metoda isEmpty() naszej klasy Dictionary działa poprawnie. Każdy test utworzony za pomocą PHPUnit

Rysunek 1. Strona z wynikami zbioru testów

Wynikiem testu jest krótkie zestawienie pokazujące, ile testów zostało przeprowadzonych oraz czy zakończyły się one sukcesem. Oprócz łącznej liczby testów otrzymujemy ilość bezbłędnych i błędnych sprawdzeń. PHPUnit rozróżnia tutaj Failures oraz Errors. Errors to błędy, które nastąpiły w czasie przetwarzania samego PHP (np. wyjątki). Failures to z kolei testy, przy których klasa zwróciła wartości inne od oczekiwanych. Rysunek 3 pokazuje każdorazowo przykład bezbłędnego i błędnego przebiegu testu. Widzimy, że w naszym przypadku zostały przeprowadzone dwa testy. Po starcie testu PHPUnit wykonuje każdą metodę, której nazwa zaczyna się od test. Właściwe testowanie odbywa się za pomocą obecnych w PHPUnit metod assert. W metodzie testIsEmptyWithoutFill() używamy assertTrue(). Jeśli wywołanie funkcji isEmpty() zwraca wartość true, to znaczy,

PHP Solutions Nr 1/2006

www.phpsolmag.org

23

Narzędzia

PHPUnit
Tests run: 1, Failures: 1, Errors: 0,
������������
������
������
������

�������
��������
��������

Incomplete Tests: 0.

�������
��������
��������

Ponieważ komunikaty zwracane przez niektóre metody assert, np. assertEqual() są wyczerpujące, więc dodawanie własnych informacji nie zawsze jest konieczne.

���
������

Gromadzenie testów w zbiory

���������

�������

������

������ �������

Rysunek 2. Zasada działania zautomatyzowanych testów

że nasza klasa pracuje prawidłowo, jako że nie mamy żadnych słówek w naszym słowniku. Jeżeli jednak isEmpty() zwróci false, to mamy do czynienia z błędem. Założenie assertTrue() będzie niespełnione i PHPUnit stwierdzi błąd. Zadaniem metod assert jest porównywanie oczekiwanego wyniku ze zwróconym przez funkcję. Do dyspozycji mamy kilka rodzajów tych metod, niektóre z nich przedstawiamy w Ramce Metody assert. Będziemy z nich korzystać w następnych przykładach. Metoda testująca może zawierać więcej metod typu assert. Można to wykorzystać, by sprawdzać wartości zależne od siebie.

(jako parametr) własne komunikaty, które zostaną wyświetlone w przypadku niepowodzenia testu. Jeżeli zastąpimy linię z metodą assert na Listingu 3 zapisem:
$this->assertTrue("samolot" == $strResult, "Tłumaczeniem 'plane' jest '$strResult'. Oczekiwano: 'samolot'")

Z reguły nie chcemy wywoływać pojedynczego testu. Postępowanie, które opisaliśmy nie sprawdziłoby się w przypadku dużej liczby testów. Na szczęście również tutaj PHPUnit oferuje nam wygodne mechanizmy. Większą liczbę testów możemy umieścić w zbiorze testowym (TestSuite), a następnie wywołać pojedynczym poleceniem. Listing 4 pokazuje, jak można dodawać pojedyncze testy do TestSuite za pomocą metody addTest(). TestSuite możemy następnie wykonać poleceniem:
phpunit DictionaryTestSuite

Wynik, który zobaczymy na ekranie, odpowiada temu, co znamy z dotychczasowych pojedynczych wywołań.

to w przypadku niepowodzenia testu otrzymamy następujący komunikat:
F Time: 0.004560 There was 1 failure: 1) testTranslation(TranslateTest) Tłumaczeniem 'plane' jest 'error'. Oczekiwano: 'samolot'. FAILURES!!!

setUp() i TearDown()

Ulepszone komunikaty o błędach

Jeśli przyjrzymy się uważnie Listingowi 1, zwrócimy uwagę na funkcje loadFromDb() i saveToDb(). Jak sugerują nazwy, odpowiadają one za ładowanie (load) i zapisywanie (save) słownika do bazy danych. Załóżmy, że w bazie znajduje się kilka tysięcy słów, więc ich ładowanie trochę potrwa. Ponieważ wiele testów korzysta z wypełnionego słownika, więc ładowanie danych dla każdego pojedynczego testu

Komunikaty zwracane przez PHPUnit w razie zakończenia testu niepowodzeniem mówią czasami niewiele. Załóżmy, że metoda getTranslation() zwraca nieprawidłowy rezultat, co spowoduje, że nasz test z Listingu 3 zakończy się następującym komunikatem:
F Time: 0.012339 There was 1 failure: 1) testTranslation(TranslateTest) FAILURES!!! Tests run: 1, Failures: 1, Errors: 0, Incomplete Tests: 0.

Informacje tego rodzaju nie są zbyt pomocne, zwłaszcza gdy mamy do czynienia z dużą liczbą testów. Dlatego wszystkim metodom assert możemy przekazywać

Rysunek 3. Bezbłędne i błędne przebiegi testów w PHPUnit

24

www.phpsolmag.org

PHP Solutions Nr 1/2006

PHPUnit
miałoby niewielki sens. Również z tego powodu klasa PHPUnit2_Framework_TestCase oferuje metodę setUp(). Metoda ta jest wywoływana przed wykonaniem testu, stanowi więc idealne miejsce na załadowanie słownika z bazy. Musimy ją jedynie nadpisać w naszej własnej klasie PHPUnit_ Framework_TestCase i dokonać inicjalizacji. Użycie tej funkcji ma sens wszędzie tam, gdzie chcemy mieć dostęp do tych samych danych z różnych testów lub gdzie inicjalizacja używanych klas jest uciążliwa, a chcemy je stosować w wielu testach. Rzadziej niż setUp() używamy metody tearDown(). Jest ona przeciwieństwem setUp(), gdyż jest wywoływana po zakończeniu testów. Możemy w niej np. zwalniać użyte zasoby, takie jak połączenia bazodanowe, wskaźniki do danych, itp. Użycie obu metod przedstawiamy na Listingu 5.

Narzędzia

Metody assert
• • •

PHPUnit oferuje szeroką paletę metod assert:
assertTrue() – oczekuje ona, że wyrażenie zwróci wartość true, w przeciwnym wypadku mamy do czynienia z niepowodzeniem testu. Przykład: $this-> assertTrue($expected == $result). assertFalse() – metoda przeciwna wobec assertTrue(). Oczekuje, że wyrażenie zwróci wartość false. assertEquals() – za jej pomocą możemy sprawdzić równość dwóch wyników. Wywołanie $this->assertEquals($expected, $result) odpowiada wywołaniu $this-> assertTrue($expected == $result) Ma jednak tę przewagę nad assertTrue() że . , assertSame() – ta metoda sprawdza identyczność dwóch obiektów. Odpowiada wywołaniu $this->assertTrue($expected === $result). Również w tym wypadku mamy metodę przeciwną – assertNotSame(). assertNull() – sprawdza, czy wynik jest równy null. Jeśli nie, test kończy się niepowodzeniem. Metodą przeciwną jest assertNotNull(). assertRegExp() – ten assert używa wyrażenia regularnego do sprawdzenia wyniku. Niepasujący wzór jest traktowany jako niepowodzenie. Za pomocą $this-> assertRegExp('/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,5})+$/', $result)

• • •

w przypadku niepowodzenia otrzymujemy bardziej szczegółowy komunikat.

Analiza pokrycia kodu

Skąd mamy wiedzieć, czy napisaliśmy wystarczającą liczbę testów dla naszej aplikacji? PHPUnit oferuje nam możliwość sprawdzenia, czy nasze testy obejmują cały kod. Uruchomiony z parametrem linii komend --coverage-html PHPUnit tworzy plik HTML, który pokazuje kompletne źródło przetestowanej klasy. Linie, które zostały przetestowane, są podświetlone. Na Rysunku 4 przedstawiamy tego rodzaju plik dla pierwszego przeprowadzonego w tym artykule testu. Korzystając z konsoli, uruchamiamy phpunit z następującymi parametrami:

możemy na przykład sprawdzić, czy został zwrócony adres e-mail i czy jego format jest prawidłowy. assertType() – sprawdza, czy wynik jest wskazanego typu. Jeżeli zwrócony typ nie odpowiada oczekiwanemu, porównanie kończy się niepowodzeniem. Przykładowo, $this->assertType('string', $result) pozwala sprawdzić, czy wynik jest typu string. Metodą przeciwną jest assertNotType(). assertContains() – sprawdza, czy dany łańcuch (string) jest zawarty w innym stringu. Jeżeli nie, zostaje zwrócone niepowodzenie. Przykładowe wywołanie: $this->assertContains('f','foo'). Metoda przeciwna to assertNotContains().

phpunit --coverage-html FillTest.html FillTest

Na podstawie utworzonego pliku HTML łatwo widzimy, czy potrzebujemy dalszych testów, czy też te przeprowadzone dotychczas wystarczają, aby w pełni przetestować nasz kod źródłowy. Niestety, braki w dokumentacji utrudniają używanie tej opcji.

Musimy przygotować dwie rzeczy. PHPUnit tworzy wprawdzie plik HTML ze wszystkimi koniecznymi informacjami o pokryciu kodu, jednak używa zewnętrznego pliku CSS (codecoverage.css). Plik ten nie jest tworzony, ani też nie znajduje się w katalogach instalacyjnych PHPUnit. Na Listingu 6 przedstawiamy więc plik CSS, którego można używać jako codecoverage.css. Kolejną przeszkodą

R

E

K

L

A

M

A

PHP Solutions Nr 1/2006

www.phpsolmag.org

25

Narzędzia

PHPUnit

w używaniu tej opcji jest zależność od rozszerzenia xdebug, które jest zainstalowane na niewielu serwerach. W ramce Instalacja opisujemy więc, jak można owe rozszerzenie zainstalować, aby PHPUnit mógł z niego korzystać. PHPUnit potrafi też zapisywać informacje o pokryciu kodu także w plikach tekstowych (--coverage-text) oraz w formacie, który może być łatwo używany przez inne programy (--coverage-data).

Metoda test-first

Funkcje pomocnicze

Jak widzimy, tworzenie testów wymaga pisania sporej ilości kodu. Aby ułatwić nam to zadanie, PHPUnit pozwala generować szkielety testów, które następnie uzupełniamy o właściwą funkcjonalność. Aby to zrealizować, wywołujemy polecenie phpunit z parametrem będącym nazwą pliku, dla którego powinien być utworzony szkielet: phpunit --skeleton Dictionary. Szkielet wynikowy dla naszej klasy Dictionary przedstawiamy na Listingu 7. Zwrócenie wyników testu na konsoli może się niekiedy okazać niewystarczające. W przypadku większych zespołów programistycznych testy są przeprowadzane o określonej porze, z reguły w nocy. Następnie wykonywany jest często szereg procesów towarzyszących. Tak więc oprócz testów jednostkowych tworzona jest dokumentacja API (np. za pomocą phpDocumentora), a także bekap danych i synchronizacja systemu działającego live. Dlatego ważne jest, aby wyniki testów były pokazywane nie tylko na ekranie, ale także zapisywane do pliku, ażeby można było w późniejszym czasie obejrzeć rezultaty. Aby coś takiego zrealizować, wywołujemy phpunit z parameterem --log-xml oraz nazwą pliku docelowego.
phpunit --log-xml result.xml DictionaryTestSuite

W tym artykule pisaliśmy testy, które sprawdzają funkcjonalność klas już istniejących: najpierw utworzyliśmy kod źródłowy w PHP, a potem odpowiedni test. Można jednak postępować inaczej. Ze świata Extreme Programming (XP) pochodzi metoda test-first, czasem zwana również Test-Driven Development (TDD). Inaczej niż w dotychczasowych przykładach, w których pisaliśmy testy do już istniejących klas, tutaj najpierw definiujemy interfejs klasy jako pustą funkcję z listą parametrów (podobnie jak na Listingu 1). Następnie tworzymy testy dla wszystkich możliwych zastosowań. Co się stanie, gdy wykonamy taki zbiór testów? Wszystkie testy zakończą się niepowodzeniem, ponieważ testowana klasa nie zawiera żadnego kodu. Potem implementujemy wszystkie metody. Jeżeli żaden test nie zakończy się niepowodzeniem, to klasa jest gotowa. Zaletą tej metody jest to, że przed implementacją funkcji możemy przemyśleć jej przebieg i napisać dla niego test. Dzięki temu, gdy klasa zostanie zaimplementowana, będziemy już dysponowali solidnym i obszernym zbiorem testów pozwalających stwierdzić, czy pracuje ona poprawnie. To, którą metodę postępowania wybierzemy, zależy od indywidualnych upodobań.

Listing 2. Pierwszy test (FillTest.php)
<? require_once ("Dictionary_inc.php"); require_once ("PHPUnit2/Framework/TestCase.php"); class FillTest extends PHPUnit2_Framework_TestCase { function testIsEmptyWithoutFill() { $objDictionary = new Dictionary(); $this->assertTrue($objDictionary->isEmpty()); } function testIsEmptyWithFill() { $objDictionary = new Dictionary(); $objDictionary->addTranslation("car", "samochód"); $this->assertFalse($objDictionary->isEmpty()); } } ?>

Listing 3. Test zwracający niewiele mówiące komunikaty o niepowodzeniu.
<?php require_once ("dictionary_inc.php"); require_once ("PHPUnit2/Framework/TestCase.php"); class TranslateTest extends PHPUnit2_Framework_TestCase { function testTranslation() { $objDictionary = new Dictionary(); $objDictionary->addTranslation("car", "samochód"); $objDictionary->addTranslation("plane", "samolot"); $strResult = $objDictionary->getTranslation("plane"); $this->assertTrue("samolot" == $strResult); } } ?>

Listing 4. Gromadzenie pojedynczych testów w TestSuite
<?php require_once "PHPUnit2/Framework/TestSuite.php"; require_once "FillTest.php"; require_once "TranslateTest.php"; class DictionaryTestSuite { public static function suite() { $suite = new PHPUnit2_Framework_TestSuite; $suite->addTest(new FillTest("testIsEmptyWithoutFill")); $suite->addTest(new FillTest("testIsEmptyWithFill")); $suite->addTest(new TranslateTest("testTranslation")); return $suite; } } ?>

Przykład pliku XML utworzonego przy takim wywołaniu przedstawiamy na Rysunku 5. Plik ten może być użyty także do automatycznej generacji dokumentacji testowej w formacie HTML lub PDF za pomocą XSLT.

Organizacja testów jednostkowych

Teraz, gdy dysponujemy całą wiedzą potrzebną nam do pisania testów jednostkowych, możemy się zająć ostatnią otwartą sprawą: jak organizować własne testy?

26

www.phpsolmag.org

PHP Solutions Nr 1/2006

PHPUnit
Jak to często bywa, nie ma tutaj jednoznacznej odpowiedzi. W praktyce najczęściej stosujemy dwa wzorce. Nazywają się one Inline-Testing i Outline-Testing. Różnica leży w miejscu zapisywania testów. W przypadku Inline-Testing kod testu jest zapisywany w samej testowanej klasie. Dzięki temu zawsze wiemy, gdzie znajduje się kod testowy danej klasy i możemy go łatwiej dopasować, gdy zmienimy samą klasę. Kolejna zaleta takiego podejścia ujawnia się, gdy używamy systemu kontroli wersji, jak np. CVS czy Subversion, bowiem test jest zawsze dopasowany do odpowiedniej wersji klasy. Wspólne zapisywanie kodu testującego i klasy funkcjonuje tylko dlatego, że pliki klas zazwyczaj nie są bezpośrednio wywoływane, lecz dołączane w innych plikach za pomocą include. Możemy z tego skorzystać, aby umieścić kod testujący w pliku PHP. Trzeba jeszcze tylko dopisać zapytanie, które rozstrzyga, czy plik będzie dołączany przez include, czy też wywołany przez PHPUnit. Listing 8 pokazuje ów warunek. W przeciwieństwie do Inline-Testing, w przypadku Outline-Testing, kod testujący jest zapisywany oddzielnie od testowanej klasy. Outline-Testing omija niektóre problemy, które tam się pojawiają. Jeżeli kod testujący jest zapisywany razem z kodem klasy, to znajduje się również w późniejszej wersji produkcyjnej, jeśli klasy zostaną umieszczone na serwerze w Internecie. Ponieważ PHP jest

Narzędzia

Rysunek 4. Analiza pokrycia kodu za pomocą PHPUnit. Linie, które zostały przetestowane, są podświetlone

Rysunek 5. Wynik testów w formacie XML
językiem interpretowanym, kod testujący jest przy każdym wywołaniu parsowany na nowo, co zajmuje czas. Dlatego może okazać się, że oddzielenie kodu testującego od klas testowanych ma sens – postępujemy tak we wszystkich przykładach w tym artykule. Pliki z testami możemy oddzielać na kilka sposobów. Rysunek 6 pokazuje trzy możliwości zapisywania testów. Wielu programistów zapisuje pliki testowe w tym samym katalogu, co klasy. Jest to najprostsze, ale niestety mało przejrzyste rozwiązanie. Innym, bardzo często stosowanym wyjściem jest utworzenie osobnego, przeznaczonego dla testów folderu tests w danym podkatalogu z klasami. Pozwala to na wyraźne
Listing 6. Arkusz stylów CSS (codecoverage.css) do formatowania pliku z informacjami o pokryciu kodu
td.ccLineNumber, td.ccCoveredLine, td.ccUncoveredLine, td.ccNotExecutableLine{ font-family: Verdana, Arial, monospace; font-size: 10pt; white-space: pre;} td.ccLineNumber { background-color: #aaaaaa;} td.ccCoveredLine { background-color: #F0F7B9;} td.ccNotExecutableLine { color: #aaaaaa;}

Listing 5. Zastosowanie metody setUp() dla wspólnie używanych danych
<?php require_once ("dictionary_inc.php"); require_once ("PHPUnit2/Framework/TestCase.php"); class TranslateTest extends PHPUnit2_Framework_TestCase { function setUp() { $this->m_objDictionary = new Dictionary(); $this->m_objDictionary->loadFromDb();} function testTranslation() { $strResult = $this->m_objDictionary->getTranslation("plane"); $this->assertTrue("samolot" == $strResult, "Tłumaczeniem 'plane' jest '$strResult'. Oczekiwano 'samolot'");} function testIncorrectWord() { $strResult=$this->m_objDictionary->getTranslation("not in dictionary"); $this->assertNull($strResult);} function testResultType() { $strResult = $this->m_objDictionary->getTranslation("plane"); $this->assertType("string", $strResult);} function testSize() { $this->assertTrue($this->m_objDictionary->getSize() != 0);} private $m_objDictionary; } ?>

PHP Solutions Nr 1/2006

www.phpsolmag.org

27

Narzędzia

PHPUnit
oddzielenie testów od właściwego kodu. Najbardziej radykalnym posunięciem jest całkowite oddzielenie kodu od aplikacji. Możemy wówczas przy jej ładowaniu na serwer internetowy wysłać wszystko poza wybranym katalogiem z testami, a więc nasza aplikacja nie posiada plików, które nie są jej konieczne do funkcjonowania. Organizacja testów pozostaje tak czy inaczej w gestii programisty. W zależności od wielkości projektów każda z metod ma swoje wady i zalety.

Rysunek 6. Możliwości oddzielenia testów od klas testowanych

Listing 7. Część automatycznie utworzonego szkieletu testowego dla klasy słownikowej
<?php if (!defined("PHPUnit2_MAIN_METHOD")) { define("PHPUnit2_MAIN_METHOD", "DictionaryTest::main"); } require_once "PHPUnit2/Framework/IncompleteTestError.php"; require_once "PHPUnit2/Framework/TestCase.php"; require_once "PHPUnit2/Framework/TestSuite.php"; require_once "Dictionary.php"; // Test class for Dictionary. // Generated by PHPUnit2_Util_Skeleton on 2005-09-11 at 18:09:24. class DictionaryTest extends PHPUnit2_Framework_TestCase { public static function main() { require_once "PHPUnit2/TextUI/TestRunner.php"; $suite = new PHPUnit2_Framework_TestSuite("DictionaryTest"); $result = PHPUnit2_TextUI_TestRunner::run($suite); } // @todo Implement testAddTranslation(). public function testAddTranslation() { throw new PHPUnit2_Framework_IncompleteTestError; } // @todo Implement testGetTranslation(). public function testGetTranslation() { throw new PHPUnit2_Framework_IncompleteTestError; } // @todo Implement testRemoveTranslation(). public function testRemoveTranslation() { throw new PHPUnit2_Framework_IncompleteTestError; } // @todo Implement testIsEmpty(). public function testIsEmpty() { throw new PHPUnit2_Framework_IncompleteTestError; } } if (PHPUnit2_MAIN_METHOD == "DictionaryTest::main") { DictionaryTest::main(); } ?>

Podsumowanie

Listing 8. Umiejscowienie testu wewnątrz pliku klasy
<?php class example { // some class code here } if ( strstr($_SERVER['PHP_SELF'], "TextUI/TestRunner.php") != false ) { require_once ("PHPUnit2/Framework/TestCase.php"); class exampleTest extends PHPUnit2_Framework_TestCase { // your test cases here } } ?>

PHPUnit jest interesującym projektem, który bardzo ułatwia kontrolowanie bezbłędności naszej aplikacji w trakcie jej pisania. Drastyczne zmniejszenie nakładów pracy odczujemy przede wszystkim dzięki automatycznemu dodawaniu testów dla każdej metody, które uwalnia nas od żmudnego, ręcznego wykonywania tych operacji. W polączeniu z łatwością obsługi PHPUnita, możliwości te zadają kłam pokutującemu przekonaniu, że z powodu konieczności dotrzymania terminów nie ma czasu na testowanie programu w czasie jego powstawania. Przyszłość PHPUnita rysuje się jeszcze ciekawiej. Niedługo ukaże się wersja dla PHP 5.1, korzystająca z jego nowych możliwości, a także (co nie ma związku z PHP 5.1) oferująca funkcjonalność taką, jak atrapy obiektów (ang. mock objects) – to drugie zbliża ją do pierwowzoru ze świata Javy, którym jest jUnit. Autorzy zapowiadają również możliwość tworzenia testów przy użyciu interfejsu graficznego (GUI) działającego pod GTK. Ma on zostać napisany, gdy pojawi się GTK dla PHP5. W odległej przyszłości planowana jest integracja PHPUnita ze środowiskami programistycznymi, takimi jak Eclipse, Zend Studio czy Maguma Workbench. Jak będzie, pokaże czas, a na razie warto zacząć korzystać z dostępnych możliwości PHPUnita, wdrażając go w swoich projektach. n

O autorze
Timo Haberkern jest architektem oprogramowania biznesowego w firmie EMEDIA OFFICE. Poza tym bierze aktywny udział w rozwijaniu wielu projektów open source w językach PHP, Java i Javascript.

28

www.phpsolmag.org

PHP Solutions Nr 1/2006

Narzędzia

Drupal, czyli wielodomenowe, wielojęzyczne i modularne portale w oparciu o AJAX i SEO
Uwe Hermann

Czy potrzebujesz systemu zarządzania treścią (CMS) ogólnego zastosowania, będącego w stanie obsługiwać w jednej instancji kilka niezależnych serwisów WWW, z których każdy dostępny ma być w kilku wersjach językowych? Czy pełna internacjonalizacja powinna być możliwa za pomocą zaledwie kilku kliknięć myszą? Może chciałbyś także dodać do swojego serwisu elementy dynamiczne korzystające z AJAX, albo zwiększyć jego popularność dzięki zastosowaniu najlepszych technik SEO? Nie musisz dalej szukać: wypróbuj system Drupal.

D

W SIECI
1. http://drupal.org – oficjalna strona domowa Drupal 2. http://cvs.drupal.org – repozytoria CVS Drupal 3. http://drupaldocs.org – dokumentacja API programistycznych Drupal 4. http://opensourcecms.com – strona o CMS

rupal to opensourcowy, napisany w PHP CMS. Opiera się na relacyjnej bazie danych (MySQL lub PostgreSQL). Jest modularny, elastyczny i skalowalny. Rdzeń systemu Drupal, podobnie jak większość modułów i motywów graficznych przygotowanych przez jego użytkowników, udostępniany jest na licencji GNU General Public License. Drupal ma potężne możliwości w porównaniu z rozwiązaniami konkurencyjnymi, a jednocześnie jest wystarczająco elastyczny, by mógł być wykorzystywany na serwisach WWW dowolnego rodzaju: portalach, stronach osobistych, forach dyskusyjnych, blogach, fotoblogach czy podcastach – możliwości jest wiele. Pokażemy, jak wykorzystać Drupala do zbudowania złożonego serwisu portalowego, koncentrując się na najbardziej zaawansowanych komponentach systemu, takich jak wielowitrynowość, czyli instalacja dla wielu serwisów (ang. multi-site installation), wielojęzyczność, kategoryzacja (ang.

categorizing) i znakowanie (ang. tagging) treści, optymalizacja pod kątem wyszukiwarek (ang. Search Engine Optimization, SEO) oraz użycie technologii AJAX w interfejsie użytkownika. W artykule zajmujemy się przyszłą, jeszcze niewydaną wersją Drupala o numerze 4.7, która będzie oferowała wiele ciekawych rozszerzeń w stosunku do bieżącej wersji stabilnej.

Powinieneś wiedzieć...

Powinieneś znać PHP, Apache'a i MySQL-a. Przydatna będzie również wiedza ogólna o systemach CMS oraz o SEO.

Obiecujemy...

Po przeczytaniu artykułu będziesz wiedział, jak za pomocą Drupal stworzyć wielodomenowy, wielojęzyczny serwis WWW. Skupimy się przy tym na niektórych spośród jego najbardziej zaawansowanych elementów, takich jak instalacja dla wielu serwisów, internacjonalizacja, interfejsy użytkownika AJAX oraz Search Engine Optimization.

30

www.phpsolmag.org

PHP Solutions Nr 1/2006

Drupal

Narzędzia

Etymologia Drupal

Instalacja
Ÿ

Projekt Drupal został powołany do życia przez Driesa Buytaerta w roku 2000. Jego nazwa pochodzi od holenderskiego słowa druppel, które znaczy kropla (ang. drop). Dries Buytaert wybrał taką nazwę, ponieważ przypadkowo wpisał drop.org zamiast dorp.org (dorp to po holendersku wioska) szukając domeny dla serwisu społeczności, na którym planował korzystać z Drupala. Nazwa drop przyjęła się i tak narodził się Drupal.

Poniższe instrukcje dotyczą bieżącej wersji HEAD Drupal w CVS, więc po pojawieniu się wydania 4.7 konieczna będzie zmiana numeru wersji w kilku miejscach. Pobierz i rozpakuj archiwum Drupala do katalogu /var/www/testsite:
mkdir /var/www/testsite wget http://drupal.org/files/projects/drupal-cvs.tar.gz tar xfvz drupal-cvs.tar.gz mv drupal-cvs/* drupal-cvs/.htaccess /var/www/testsite

Ÿ

Stwórz bazę danych dla Drupala i załaduj odpowiednie skrypty bazodanowe:
mysqladmin -u database_user_name -p create dbtestsite mysql -u database_user_name -p dbtestsite < database/database.mysql

Koncepcja naszego portalu

Ÿ

Zmodyfikuj odpowiednio zmienne $db _ url i $base _ url w sites/default/settings.php:
$db_url = "mysql://nazwa_użytkownika:hasło@serwer_bazodanowy/dbtestsite"; $base_url = "http://www.example.com/testsite";

Pokażemy możliwości Drupala budując prosty portal dla społeczności osób interesujących się darmową, legalnie dostępną w Internecie muzyką. Witryna będzie ukierunkowana na fanów i artystów niezależnych. Ma zapewniać fora dyskusyjne, blogi, cotygodniowe głosowanie i wiele więcej. Planujemy stworzenie odrębnego serwisu dla każdego gatunku muzyki, gdzie wykorzystamy wielowitrynowość Drupala. Każdy z tych serwisów będzie miał poddomenę w ramach naszej wspólnej domeny example.com. Główny serwis będzie dostępny pod adresem http: //www.example.com; osobne poddomeny dla gatunków będą nosiły nazwy takie, jak http://rock.example.com, http://blues.example.com czy http://electronica.example.com. Witryna dla każdego gatunku będzie miała własne ustawienia, motywy graficzne, bloki, treść, fora itd. Z drugiej jednak strony chcemy, by nasi użytkownicy mogli założyć pojedyncze konto i wykorzystywać je do logowania się we wszystkich serwisach (ang. single sign-on). Z tego względu poszczególne serwisy będą korzystały ze wspólnej bazy użytkowników. Listing 1. Definicja naszych poddomen dla Apache'a
<VirtualHost *:80> DocumentRoot /var/www ServerName www.example.com </VirtualHost> <VirtualHost *:80> DocumentRoot /var/www ServerName rock.example.com </VirtualHost> <VirtualHost *:80> DocumentRoot /var/www ServerName blues.example.com </VirtualHost>

Ÿ

I to wszystko. Nasz serwis jest dostępny pod adresem http://www.example.com/ testsite.

Kroki opcjonalne, ale zalecane:
Ÿ Stwórz katalog files/ z prawem zapisu przez serwer WWW. W katalogu tym system Drupal będzie przechowywał przesyłane przez klientów obrazki oraz inne pliki:
mkdir /var/www/testsite/files chmod 777 /var/www/testsite/files

Ÿ

Stwórz zadanie crona pytające regularnie o plik cron.php. Przykładowo, aby wywoływać tę czynność co godzinę, należy dodać do /etc/crontab następującą linijkę:
0 * * * * wget -O - -q http://www.example.com/testsite/cron.php

Wymagania

Minimalne wymagania instalacyjne Drupala to: PHP (PHP4 od wersji 4.3.3 lub PHP5), system relacyjnych baz danych (MySQL lub PostgreSQL) oraz serwer HTTP (Apache lub IIS). Opcjonalne, ale zalecane są: rozszerzenie XML dla PHP, obsługa mod_rewrite i plików .htaccess.

Instalacja wielowitrynowa (wieloserwisowa)

Instalacja wielowitrynowa pozwala nam tworzyć wiele serwisów, dostępnych pod osobnymi domenami lub poddomenami czy podkatalogami pojedynczej domeny. Każdy z tych serwisów może mieć własne ustawienia, moduły i motywy graficzne pomimo, że korzysta z tej samej co inne instalacji systemu Drupal. Ponadto w zależności od potrzeb, poszczególne serwisy mogą korzystać z całkowicie niezależnych, częściowo wspólnych, bądź całkowicie wspólnych tabel w bazie danych.

Jak działa instalacja wielowitrynowa

Podstawowym mechanizmem stojącym za instalacją wieloserwisową jest sposób

wyszukiwania przez system Drupal plików konfiguracyjnych, modułów oraz innych elementów. W przypadku instalacji jednoserwisowej, pełna ścieżka do pliku settings.php to sites/default/settings.php. W przypadku scenariusza wielowitrynowego musimy natomiast stworzyć odpowiedni podkatalog dla każdego serwisu w sites, a następnie umieścić w nim dostosowany do tej witryny plik setttings.php. Nazwy tych podkatalogów zawierać muszą nazwę poddomeny (vhost) domeny, numer portu (tylko w przypadku, gdy jest on inny niż 80) oraz nazwy folderów na danym serwisie. Poszczególne części składające się na taką nazwę oddzielone są kropkami. Dotyczy to również numeru portu, który zazwyczaj oddzielamy dwukropkiem.

PHP Solutions Nr 1/2006

www.phpsolmag.org

31

Narzędzia

Drupal
zamiast poddomen wykorzystamy podkatalogi.

Terminologia Drupal
Ÿ Ÿ Ÿ Ÿ Ÿ Ÿ Węzeł (ang. node) – zawartość naszego serwisu przechowywana jest w węzłach; istnieje szereg typów węzłów, takich jak: strona, wpis w blogu, ankieta czy też zdarzenie. Blok (ang. block) – niewielki fragment zawartości, umieszczany zwykle po lewej lub prawej stronie strony serwisu. Jako przykłady można wymienić Kto jest zalogowany lub Najświeższe komentarze. Moduł (ang. module) – rozszerzenie Drupala (napisane w PHP), dodające do naszego serwisu określoną funkcjonalność. Taksonomia (ang. taxonomy) – pozwala porządkować zawartość naszego serwisu według kategorii bądź znaczników. Motyw (ang. theme) – przesłania możliwe do modyfikacji funkcje Drupal, zapewniając unikalny wygląd naszego serwisu. Silnik motywów (ang. theme engine) – każdy motyw wymaga odpowiedniego silnika motywów. Domyślnym jest PHPTemplate, ale możemy skorzystać z innych, jak np. XTemplate czy Smarty.

Tworzenie tabel w bazie danych

Kolejnym krokiem jest stworzenie tabel dla każdego serwisu w bazie danych. Mamy tutaj kilka możliwości. Jeżeli chcemy, by żadna z tabel nie była dzielona między serwisami, tj. żeby były one od siebie całkowicie niezależne, po prostu tworzymy osobną bazę dla każdego serwisu:
mysql -u dbuser -p database1 mysql -u dbuser -p database2

< database/database_1.mysql < database/database_2.mysql

§ §

W przypadku naszego przykładowego portalu, struktura katalogów i plików dla serwisów www.example.com, blues.example.com i rock.example.com powinna wyglądać następująco:
Ÿ Ÿ Ÿ

sites/example.com/settings.php, sites/rock.example.com/settings.php, sites/blues.example.com.subdir/ settings.php.

Jeżeli serwis ma być dostępny zarówno jako example.com jak i www.example.com, zaleca się utworzenie katalogu o nazwie example.com.

Konfiguracja poddomen

Przejdźmy teraz do stworzenia opisanych poddomen. W tym celu zmodyfikujemy konfigurację naszego serwera WWW tak,

by działały w nim hosty wirtualne. Ponieważ używamy Apache'a, którego drzewo dokumentów znajduje się w /var/www, musimy dodać do naszego pliku /etc/apache/ httpd.conf zawartość Listingu 1. Spowoduje to skierowanie poddomen: www, rock oraz blues do naszego drzewa dokumentów, w którym zainstalujemy pojedynczą kopię kodu Drupal, mającą obsługiwać wszystkie nasze serwisy. Opisany scenariusz wieloserwisowy powinien działać także w przypadku większości dzielonych kont hostingowych. Konieczne może okazać się jednak pozostawienie konfiguracji poddomen administratorowi, albo skorzystanie z udostępnionego przez niego w tym celu interfejsu webowego, gdyż przeważnie nie będziemy mieli dostępu do pliku httpd.conf. Jeżeli żadne z tych rozwiązań nie jest możliwe,

W przeciwnym przypadku dzielone będą wszystkie tabele, a wszystkie serwisy będą miały identyczne ustawienia, motywy graficzne, treść itd. Wystarczy nam wtedy pojedyncza baza dla wszystkich serwisów:
mysql -u database_user_name db_for_all -p

< database/database.mysql

§

W przypadku naszego portalu muzycznego skorzystamy z oferowanych przez Drupal możliwości pracy z prefiksami tabel. Tabele dla wszystkich podserwisów naszej witryny będą przechowywane w jednej bazie, ale ich nazwy będą się zaczynały odpowiednimi przedrostkami. Pozwoli to wyraźnie określić, do którego serwisu należy dana tabela. Chcąc uniknąć zbędnych komplikacji, nadamy tabelom głównego serwisu przedrostek shared_ (jako, że część z nich będzie dzielona z innymi serwisami). Tak więc, serwis rock.example.com otrzyma prefiks rock_, a blues.example.com – blues_. Aby uzyskać taki rezultat, użyjemy najpierw skryptu prefix.sh z katalogu scripts/, który nada odpowiednie przedrostki nazwom tabel z pliku database/ database.mysql, a następnie załadujemy tabele do bazy danych:
./prefix.sh "shared_" database.mysql > database.mysql.shared ./prefix.sh "rock_" database.mysql > database.mysql.rock

§

§

oraz:
./prefix.sh "blues_" database.mysql > database.mysql.blues

§

Rysunek 1. Strona ustawień Drupal pozwala nam modyfikować rozmaite aspekty naszego serwisu

Patrząc na polecenia SQL, plik database.mysql.shared zawiera teraz linijki takie,

32

www.phpsolmag.org

PHP Solutions Nr 1/2006

Drupal
jak CREATE TABLE shared_users zamiast wcześniejszej CREATE TABLE users, podczas gdy w database.mysql.rock znajduje się polecenie CREATE TABLE rock_users, itd. Teraz, kiedy nazwy tabel w każdym pliku mają odpowiednie przedrostki, możemy załadować je do naszej bazy MySQL:
mysql -u dbuser -p dbname < database.mysql.shared mysql -u dbuser -p dbname < database.mysql.rock

Narzędzia

§ §

oraz:
mysql -u dbuser -p dbname < database.mysql.blues

§

Konfiguracja serwisów

Ostatnim krokiem instalacji wieloserwisowej jest edycja każdego pliku settings.php tak, by jego treść odpowiadała wymaganiom danego serwisu. W wariancie minimalnym oznacza to konieczność

Zawartość plików settings.php

Oto istotne ustawienia znajdujące się w pliku sites/www.example.com/settings. php:
$base_url="http://www.example.com"; $db_url="mysql://database_user: database_password@database_host/ database_name"; $db_prefix = "shared_";

Dla odmiany, w sites/rock.example.com/ settings.php będziemy mieli:
$base_url="http://rock.example.com"; $db_url= ...; $db_prefix = array( 'default' =>'rock_', 'users' =>'shared_', 'users_roles' =>'shared_', 'sessions' =>'shared_', 'role' =>'shared_', 'authmap' =>'shared_', 'sequences' =>'shared_', 'profile_fields' =>'shared_', 'profile_values' =>'shared_', );

podania w tych plikach odpowiednich wartości zmiennych $base_url, $db_url i $db_prefix. Zmienna $base_url zawiera główny URL danego serwisu (np. http:// rock.example.com), podczas gdy $db_url wskazuje na miejsce przechowywania parametrów serwisu w bazie. W przypadku naszego portalu, wszystkie serwisy korzystają z tej samej bazy, więc wartość $db_url jest identyczna we wszystkich plikach settings.php. Zmienna $db_prefix zawiera tablicę asocjacyjną kojarzącą nazwy tabel z ich przedrostkami (patrz ramka Zawartość plików settings.php). Jeżeli nie chcemy dzielić żadnych tabel, $db_url zamiast tablicy będzie zawierać łańcuch, np. prefix_, albo "" (pusty tekst), jeżeli nie chcemy w ogóle stosować przedrostka. Należy pamiętać, że chcemy zaimplementować wspólne logowanie do wszystkich serwisów naszego portalu, a więc część tabel będzie dzielona. Decyzja o tym, które tabele powinny być dzielone nie jest łatwa, ponieważ wymaga ona bliższej znajomości wewnętrznych mechanizmów działania systemu Drupal i w dużym stopniu zależy od jego docelowej konfiguracji. Dla naszych potrzeb będziemy dzielić następujące tabele: users, users_roles, sessions, role, authmap, sequences, profile_fields oraz profile_values. Oprócz zdefiniowania zmiennych $base_url, $db_url i $db_prefix, w odpowiednich plikach settings.php możemy

na sztywno ustawić pewne parametry poszczególnych serwisów. W szczególności, Drupal pozwala nam na przesłonienie dowolnych spośród swoich zmiennych konfiguracyjnych (przechowywanych zwykle w tabeli variable w bazie danych) przy wykorzystaniu następującej konstrukcji:
$conf = array( 'site_name'=> 'Rock music discussion site', 'theme_default'=>'pushbutton', 'comment_preview'=>'1' );

W powyższym przykładzie definiujemy własną nazwę serwisu oraz jego domyślny motyw graficzny, a także ustawiamy funkcjonalność podglądu publikowanych komentarzy na wymaganą. Oprócz własnego pliku settings.php, każdy serwis może mieć swoje katalogi modules/ i themes/. Pozwala to powiązać określone moduły i motywy graficzne z konkretnymi serwisami. Wymienione foldery zakładamy w odpowiednim dla danego serwisu katalogu konfiguracyjnym, na przykład:
Ÿ Ÿ Ÿ

sites/rock.example.com/settings.php sites/rock.example.com/modules sites/rock.example.com/themes

Teraz, kiedy już zakończyliśmy instalację wieloserwisową, przejdziemy do konfigurowania naszego portalu.

Podczas gdy w sites/blues.example.com/ settings.php musimy ustawić:
$base_url= "http://blues.example.com"; $db_url = ...; $db_prefix = array( 'default' => 'blues_' ... );

Rysunek 2. Strona parametrów motywów pozwala nam włączyć jeden lub więcej motywów, a także wybrać jeden z nich jako domyślny dla danego serwisu

PHP Solutions Nr 1/2006

www.phpsolmag.org

33

Narzędzia

Drupal

Podstawowa konfiguracja

Czas utworzyć naszego pierwszego użytkownika. Po wejściu na stronę http:// www.example.com, klikamy na Utwórz nowe konto w bloku Logowanie po lewej stronie ekranu. Pierwszy stworzony przez nas użytkownik, niezależnie od nadanej mu nazwy, będzie administratorem posiadającym uprawnienia do wykonywania wszelkich operacji na serwisie. Po zalogowaniu się na to konto, pod zakładką zarządzaj –> ustawienia zmienimy kilka podstawowych ustawień, takich jak nazwa serwisu (Portal muzyczny). Ustawimy też opcję Raportowanie błędów na Zapisuj błędy do dziennika, nie chcemy bowiem pokazywać użytkownikom naszych wewnętrznych błędów. Uaktywnimy także opcję Czyste URLe, aby uzyskać ładniej wyglądające adresy URL i włączymy wbudowany mechanizm pamięci podręcznej, aby zwiększyć wydajność. Warto pamiętać, że ponieważ dzielimy między bazami tylko tabelę users oraz tabele z nią powiązane, parametry te nie będą wspólne dla wszystkich serwisów. Nasze ustawienia przedstawiamy na Rysunku 1.

Rysunek 3. Korzystając z podstawowych odnośników definiujemy menu w prawym górnym rogu serwisu

pojawiał się komunikat napisany przez użytkownika <nazwa> dnia <data>, nie chcemy widzieć go na stronach statycznych, takich jak O .... W tym celu wyłączymy opcję Włącz wyświetlanie informacji nt. wypowiedzi dla węzłów typu strona.

Ÿ

Ÿ

Motywy graficzne

Do dystrybucji systemu Drupal dołączono kilka motywów graficznych. Więcej możemy znaleźć pod adresem http://drupal.org/ project/Themes. Procedura ich instalacji jest całkiem prosta: należy pobrać odpowiedni plik spod adresu drupal.org i rozpakować go do katalogu themes/. W naszym głównym portalu wykorzystamy domyślny motyw graficzny, za to dla podstron dla poszczególnych gatunków muzyki pobierzemy i zainstalujemy inne; przykładowo, na rock.example.com wykorzystamy motyw FriendsElectric, rozpakowując go do katalogu sites/rock.example.com/themes/ friendselectric. Następnie wybierzemy odpowiedni motyw za pomocą menu zarządzaj –> motywy graficzne (patrz Rysunek 2). Korzystając z zarządzaj –> motywy graficzne –> konfiguruj możemy też załadować własne logo oraz obraz favicon dla naszego serwisu. Natomiast aby wyświetlać linki O ..., FAQ i Kontakt (patrz Rysunek 3) wykorzystamy listę podstawowych odnośników, podając ich nazwy i względne URL-e. Oczywiście, musimy stworzyć odpowiednie strony! Wreszcie, o ile chcemy, by we wpisach w blogach i komentarzach

Moduły

Rozszerzymy teraz funkcjonalność naszego portalu, włączając na stronie zarządzaj -> moduły (patrz Rysunek 4) kilka dodatkowych, dołączonych do pakietu Drupal modułów:

profile – pozwala nam określić jakie pola powinien zawierać profil użytkownika, np. imię, dzień urodzenia, e-mail, ulubione zespoły, itd. blog – daje każdemu z zarejestrowanych użytkowników możliwość posiadania bloga. Przykładowo, URL-em bloga użytkownika naszego serwisu o nazwie 23 to http://www.example.com/ blog/23. Dodatkowo, automatycznie i bez żadnych dodatkowych działań otrzymujemy blog grupowy (gdzie agregowane są posty wszystkich użyt-

Rysunek 4. Strona przeglądu modułów Drupal pokazuje wszystkie zainstalowane moduły

34

www.phpsolmag.org

PHP Solutions Nr 1/2006

Drupal

Narzędzia

Istnieje wiele innych, stworzonych przez użytkowników modułów; znajdziejmy je pod adresem http://drupal.org/project/ Module. W naszym portalu wykorzystamy następujące spośród nich:
Ÿ

Ÿ

Rysunek 5. Drupal pozwala na implementację w naszym serwisie precyzyjnej kontroli dostępu

Ÿ

Ÿ

kowników naszego serwisu): http:// www.example.com/blog. Rzecz jasna, każdy blog posiada własny strumień RSS, np. http://www.example.com/blog/ 23/feed. Uaktywniając blogi dajemy naszym artystom platformę, na której mogą oni mówić o swojej muzyce, natomiast fanom zapewniamy miejsce wyrażania opinii o dziełach twórców. ping – automatycznie powiadamia serwisy takie, jak technorati.com czy pingomatic.com o zmianach treści naszej witryny, co jest bardzo istotne i wygodne dla naszych blogowiczów.

Ÿ

Ÿ

Ÿ

Ÿ

upload – pozwala użytkownikom przesyłać na naszą witrynę pliki, np. obrazki lub MP3. forum – pozwala nam stworzyć jedno lub więcej forów dyskusyjnych w ramach naszego serwisu. poll – pozwala nam tworzyć cotygodniowe ankiety (możliwe są pytania z wieloma odpowiedziami), w których nasi użytkownicy mogą głosować. search – uaktywnia wbudowaną w systemie Drupal wyszukiwarkę, pozwalającą przeszukiwać nasz serwis pod kątem konkretnych treści lub użytkowników.

Ÿ

image – pozwala nam przesyłać obrazy na serwis, porządkować je w galerie oraz wstawiać we wpisach w blogach. Automatycznie tworzy miniaturę każdego obrazka. audio – pozwala naszym użytkownikom przesyłanie plików MP3 na serwis i zarządzanie nimi. Artyści mogą go wykorzystywać do publikowania swoich utworów albo do podcastingu. Wraz z modułem dostarczany jest napisany we Flashu odtwarzacz, pozwalający nam odtwarzać MP3 w przeglądarce WWW. trackback – pozwala blogowiczom na naszym serwisie wysyłać i otrzymywać trackbacki. spam – potężny zestaw narzędzi automatycznie radzący sobie ze spamem w komentarzach i trackbackach na naszym serwisie.

Użytkownicy, role i uprawnienia

Kolejnym krokiem będzie zdefiniowanie na stronie zarządzaj –> reguły dostępu pewnych ról i nadanie im pożądanych przez nas uprawnień (patrz Rysunek 5). W tym momencie stworzymy role: artysta (otrzymuje dodatkowe uprawnienie do przesyłania plików na serwis) oraz opiekun serwisu, dla zaufanych użytkowników pomagających nam opiekować się serwisem (rola ta otrzymuje dodatkowe uprawnienia do administracji forami, itd.). Skorzystamy ponadto z automatycznie zdefiniowanych ról zarejestrowany użytkownik (zalogowani użytkownicy) i anonimowy użytkownik (użytkownicy niezalogowani), które przypiszemy wszystkim pozostałym użytkownikom. Wreszcie, musimy przypisać właściwe role naszym użytkownikom, klikając na stronie zarządzaj –> użytkownicy na odnośniku edytuj dla interesującego nas użytkownika. Końcowy zestaw uprawnień użytkownika to skumulowane uprawnienia wszystkich ról, do których użytkownik został przypisany.

Tworzenie zawartości
Rysunek 6. Tworzymy prostą stronę z treścią

Nadeszła pora, by rozpocząć tworzenie zawartości naszego serwisu. Będzie to statyczna strona O ... informująca, jaką
www.phpsolmag.org

PHP Solutions Nr 1/2006

35

Narzędzia

Drupal

Rysunek 9. Oparty na JavaScript pasek postępu przesyłania plików na serwis
Rysunek 8. Automatyczne uzupełnianie nazwy użytkownika przy pomocy AJAX-a
niewielką, wbudowaną bibliotekę podstawową AJAX, która już teraz wykorzystywana jest w niektórych (aczkolwiek nie wszystkich) obszarach do polepszenia obsługi serwisów na niej opartych. Biblioteka ta oferuje API dla modułów zewnętrznych, które dzięki temu mogą wykorzystywać funkcjonalność AJAX i JavaScript do własnych potrzeb. Jednym z ciekawszych elementów AJAX/JavaScript jest zwijalne pole formularza, znacznie podnoszące wygodę korzystania ze stron zawierających formularze. Przykładem jego użycia są strony utwórz zawartość –> wpis do dziennika. Rzadko używane części formularza i całe formularze na takiej stronie są domyślnie zwinięte (zamiast całego formularza widoczny jest tylko jego tytuł), dzięki czemu cała strona staje się krótsza i bardziej zwięzła. Kliknięcie lewym przyciskiem na zwiniętym formularzu spowoduje jego rozwinięcie z wykorzystaniem JavaScript (patrz Rysunek 7) bez potrzeby pobierania przez przeglądarkę kolejnej strony z serwera. W naszym portalu zwijalne pola formularza przydałyby się np. w formularzu pozwalającym wysłać list do artysty, zamieszczonym na jego (lub jej) stronie, lub umożliwiającym wyświetlenie bardziej szczegółowych informacji biograficznych. Kolejną przydatną możliwością AJAX jest autouzupełnianie zawartości, póki co dostępne tylko w polach z nazwą użytkownika i znacznikami (kategoriami). Przykładowo, jeżeli chcemy zmienić właściciela/twórcę węzła w naszym serwisie, musimy zmienić zawartość pola Stworzony przez dla odpowiedniego węzła. Jeżeli teraz w tym polu wpiszemy li, Drupal automatycznie pokaże nam wszystkie nazwy użytkowników zawierające ciąg znaków li, np. Antonio Salieri, Franz Liszt czy Georg Philipp Telemann. Dzięki temu, jednym kliknięciem możemy wybrać odpowiedniego użytkownika. Ten sam mechanizm wykorzystywany jest w przypadku znaczników (patrz Rysunek 8). Kolejnym mechanizmem korzystającym z AJAX-a jest pasek postępu, który jest wykorzystywany podczas przesyłania plików na naszą witrynę. Informuje on użytkownika o tym, jaka część pliku została przesłana (patrz Rysunek 9).

Edycja WYSIWYG

Rysunek 7. Zwijalne pole formularza
funkcję pełni nasz portal muzyczny. Drupal udostępnia nam do wykorzystania szereg rodzajów węzłów (np. strona, wpis w blogu czy wątek na forum). My wykorzystamy typ strona. Po kliknięciu na utwórz zawartość –> strona naszym oczom ukaże się prosty formularz, w którym określimy nazwę węzła oraz jego zawartość (patrz Rysunek 6). Jako, że uruchomiliśmy moduł path, możemy także określić ścieżkę, pod którą nowa strona będzie dostępna. Tym razem użyjemy tutaj about, w wyniku czego adresem tej strony będzie http://www.example.com/ about. Dla nowej strony (lub innego węzła) możemy ustawić szereg opcji, np. zablokować na niej komentowanie, nie publikować jej na stronie głównej, itd. Możemy także, zaznaczając odpowiednie pole, stworzyć nową wersję węzła. Spowoduje to zapisanie zarówno jego starej, jak i nowej wersji, co umożliwi nam ewentualny powrót do starszej wersji bądź porównanie jej z nową.

Aby ułatwić użytkownikom formatowanie tekstu w naszym serwisie, zainstalujemy prosty edytor typu WYSIWYG (What You See Is What You Get). W tym celu możemy skorzystać z jednego spośród stworzonych przez użytkowników modułów, takich jak HTMLarea, TinyMCE czy FCKeditor. Wybierzemy FCKeditor, który został oparty na opensourcowym edytorze o tej samej nazwie, dostępnym pod adresem http://www.fckeditor.net. Oferuje on naszym użytkownikom szeroki wachlarz możliwości formatowania tekstu (Rysunek 10).

Kategorie i znakowanie

Rozszerzenia AJAX i JavaScript

AJAX, czyli Asynchroniczny JavaScript z XML (ang. Asynchronous JavaScript and XML) to zyskująca coraz większą popularność metoda tworzenia interaktywnych serwisów WWW. Piszemy o niej szerzej w artykule AJAX – porządkowanie aplikacji w tym numerze PHP Solutions. Drupal 4.7 będzie zawierał

Wbudowany w systemie Drupal system taksonomii jest bardzo potężnym narzędziem pozwalającym na porządkowanie dowolnych treści w kategorie. Pod zarządzaj –> kategorie możemy zdefiniować dowolną liczbę słowników (np. gatunek muzyki), z których każdy zawierać będzie tzw. terminy taksonomiczne, czyli kategorie (np. rock, pop albo trance). Każda kategoria ma własną stronę (np. http:// www.example.com/taxonomy/term/1), na której wyświetlana jest lista wszystkich należących do niej węzłów, a użytkownikom udostępniany jest strumień RSS (np. http:// www.example.com/taxonomy/term/1/0/ feed). Nową możliwością w systemie Drupal 4.7 są słowniki wolnego znakowania (ang. free tagging), pozwalające użytkownikom wprowadzać w odpowiednim formularzu listy oddzielonych przecinkami znaczników.

36

www.phpsolmag.org

PHP Solutions Nr 1/2006

Narzędzia

Drupal

W chwili zapisu edytowanego przez użytkownika węzła, wszystkie znaczniki automatycznie staną się normalnymi kategoriami systemu Drupal. Pozwala to blogowiczom korzystającym z naszej witryny znakować swoje wpisy, podobnie jak ma to miejsce w innych serwisach “znakowania społecznego” (ang. social tagging), takich jak del.icio.us, flickr czy technorati. Na stronie zarządzaj –> kategorie –> dodaj słownik tworzymy słownik Znaczniki Blogów i oznaczamy go jako słownik wolnego znakowania (patrz Rysunek 11). Możliwości w tej dziedzinie znacząco zwiększa zewnętrzny moduł tagadelic, oferujący nam tzw. chmury znaczników (ang. tag clouds)– zbiory nazw znaczników łączących się z odpowiednimi kategoriami (np. kliknięcie na znacznik muzyka wyświetli stronę kategorii/znacznika o tej samej nazwie). Rozmiar czcionki każdego znacznika zależy od jego popularności. Moduł tagadelic oferuje nam jedną chmurę znaczników na słownik (np. http: //www.example.com/tagadelic/chunk/1) oraz jedną dodatkową chmurę łączącą znaczniki ze wszystkich słowników. Aby wyświetlić chmurę o nazwie Znaczniki Blogów w bloku, należy włączyć możliwość używania znaczników w Znacznikach Blogów na stronie zarządzaj –> bloki (patrz Rysunek 12). Chmury znaczników oferują gościom naszego serwisu wygodną możliwość przeglądania jego zawartości pod kątem kategorii, które ich najbardziej interesują.

Rysunek 11. System taksonomii pozwala nam korzystać z szerokiego zakresu różnych słowników i konfiguracji

Serwisy międzynarodowe

Chcemy, aby nasz portal był wielojęzyczny. System Drupal pomaga nam tutaj na kilka sposobów: do kodowania znaków wykorzystuje Unicode oraz ułatwia nam tworzenie osobnych wersji językowych interfejsu i zawartości.

Języki

Wbudowany moduł locale pozwala na tłumaczenie interfejsu użytkownika systemu Drupal na dowolny język. Możemy

Rysunek 10. Moduł FCKeditor zastępuje standardowe formularze wprowadzania tekstu przyjazną dla użytkownika wersją graficzną

dodawać bądź usuwać języki na stronie zarządzaj –> języki –> dodaj język oraz oznaczyć jeden z nich jako język domyślny (patrz Rysunek 13). W naszym przypadku językiem domyślnym będzie angielski, udostępnimy jednak również polski, niemiecki, francuski i włoski. Tłumaczenie interfejsu możemy przeprowadzić na dwa sposoby. Pierwszym jest skorzystanie z wygodnego interfejsu webowego, dostępnego pod zarządzaj –> języki –> zarządzaj napisami. Możemy przeszukiwać nasz serwis pod kątem pewnych łańcuchów znaków (przetłumaczonych bądź nie) i tłumaczyć je na dowolny z dodanych przez nas języków lub modyfikować już dostępne tłumaczenie. Drugim sposobem obsługi tłumaczeń są możliwości importu oraz eksportu dostępne w module locale. Tłumaczenia Drupala mogą być importowane z plików Portable Object (.po) systemu gettext, bądź eksportowane do tego formatu. Jest on bardzo popularny i istnieje mnóstwo narzędzi ułatwiających manipulowanie plikami z niego korzystającymi, takich jak popularne aplikacje tłumaczące kbabel czy poEdit. Warto jednak wiedzieć, że w przypadku większości języków nie ma konieczności tłumaczenia wszystkich tekstów. Pod adresem http://drupal.org/ project/Translations znaleźć można wiele gotowych, przygotowanych przez użytkowników tłumaczeń. Do tej pory

38

www.phpsolmag.org

PHP Solutions Nr 1/2006

Drupal

Narzędzia

Rysunek 12. Chmurka znaczników „znaczniki blogów” (ang. „Blog Tags”) dla naszego serwisu, pokazana w bloku

Drupal zyskał ponad 40 wersji językowych. Na potrzeby naszego portalu pobierzemy z drupal.org tłumaczenia na polski, niemiecki, włoski i francuski, a następnie zaimportujemy pliki pl.po, de.po, it.po i fr.po korzystając z opcji zarządzaj –> języki –> importuj. Możemy tutaj wybrać, czy chcemy nadpisać już przetłumaczony tekst, czy też dodać tylko te łańcuchy znaków, które nie mają jeszcze swoich tłumaczeń. Warto zauważyć, że moduł locale pozwala nam zmodyfikować praktycznie każdy tekst w naszym serwisie. Przykładowo,

możemy w menu zarządzaj –> języki –> zarządzaj napisami znaleźć tytuł bloku Kto jest aktywny i zmienić go np. na Aktywni użytkownicy.

Internacjonalizacja (tłumaczenie zawartości)

Ostatnim elementem, którego potrzebujemy do stworzenia prawdziwie międzynarodowego portalu muzycznego jest zewnętrzny moduł Internationalization (i18n). Daje on nam możliwość tłumaczenia zawartości naszego serwisu na inne języki. Moduł i18n oparty jest na

Rysunek 13. Dialog parametrów języków

następującej architekturze: tłumaczenia modelowane są przez zwyczajne węzły, zawierające wskaźnik do węzłów, do których należą (których są tłumaczeniem). Informacja o języku przechowywana jest w URL-u danego węzła (zamiast w cookie sesji), np. http:// www.example.com/fr/about w przypadku węzła francuskiego. Aby uczynić nasz serwis wielojęzycznym, skonfigurujemy najpierw wspomniany wyżej moduł pod zarządzaj –> ustawienia –> i18n (patrz Rysunek 14). Uaktywnimy opcję detekcja języka przeglądarki, pozwalającą wybrać język, w którym wyświetlana będzie zawartość serwisu w oparciu o ustawienia przeglądarki gościa portalu. Jest to zwykle najwygodniejszy sposób. Jeżeli nie włączymy tej opcji, wybór języka będzie następował kolejno na podstawie URLa, ustawień użytkownika, konfiguracji przeglądarki bądź domyślnego języka systemu Drupal. Pod zarządzaj –> ustawienia –> i18n mamy także możliwość ograniczenia tłumaczenia treści do konkretnych typów węzłów. Chcemy jednak, aby na naszym portalu tłumaczone były wszystkie rodzaje zawartości. Przykładowo, przetłumaczymy teraz stronę O ... na język niemiecki, klikając na odnośnik tłumacz węzła http://www.example.com/ about. Zobaczymy listę języków zdefiniowaną przez nas wcześniej dla modułu locale. Możemy przetłumaczyć ten węzeł na dowolny z nich. Kliknięcie odnośnika utwórz tłumaczenie dla niemieckiego przywołuje standardowy dialog tworzenia węzła, wzbogacony o nowe pole o nazwie Język (w tym przypadku domyślnie ustawione na niemiecki). Możemy teraz wprowadzić przetłumaczony tekst w treści węzła, wybrać de/about jako ścieżkę dla przetłumaczonego węzła – i gotowe. Węzeł O ... będzie odtąd widoczny w języku określonym albo na podstawie URL-a, albo konfiguracji przeglądarki (tak, jak opisaliśmy powyżej). Ponadto, u dołu strony widnieć będzie jedna lub więcej małych flag. Kliknięcie na jedną z nich pozwoli nam przejść do odpowiedniej wersji językowej. Moduł i18n oferuje nam także możliwy do uaktywnienia blok wyboru języka, zawierający listę wszystkich zdefiniowanych języków. Kliknięcie na jednym z nich powoduje zmianę bieżącego

PHP Solutions Nr 1/2006

www.phpsolmag.org

39

Narzędzia

Drupal

języka (działa to również w przypadku użytkowników anonimowych). Moduł ten posiada jednak pewne ograniczenia: nie ma możliwości tłumaczenia zawartości bloków czy też menu.

Optymalizacja pod kątem wyszukiwarek

Ponieważ chcemy, by nasz serwis zajmował jak najwyższe pozycje w wyszukiwarkach internetowych, a jego popularność ciągle wzrastała, istotnym zagadnieniem jest dla nas optymalizacja pod kątem wyszukiwarek (SEO).

Czyste URL-e i moduł path

Biorąc pod uwagę, że największe wyszukiwarki wyżej oceniają strony, których URL-e zawierają poszukiwane frazy, zoptymalizujemy odpowiednio adresy na naszej witrynie. Z systemem Drupal jest to bardzo proste. Dysponuje on m.in. opcją Czyste URL-e, która usuwa ? z adresów na naszym serwisie. Efektem użycia tej funkcji będą adresy typu http: //www.example.com/node/123 zamiast http://www.example.com/?q=node/123. Opcję Czyste URL-e możemy uaktywnić pod zarządzaj -> ustawienia, będziemy jednak potrzebowali działającego modułu Apache'a o nazwie mod_rewrite. Dalszej optymalizacji naszych URL-i możemy dokonać zastępując zawarte w nich liczby znaczącymi słowami kluczowymi, na co z kolei pozwala wbudowany moduł path. Po jego włączeniu, tworząc bądź edytując węzeł, każdorazowo będziemy proszeni o podanie dla niego aliasu ścieżki. W ten sposób możemy tworzyć adresy URL wyglądające jak http://www.example.com/about-this-site, co znacznie zwiększy ilość trafień na nasze strony w wynikach wyszukiwania.

Rysunek 14. Dialog parametrów internacjonalizacji

wiamy pole aliasu ścieżki puste, a moduł pathauto automatycznie wygeneruje odpowiedni alias. W naszym portalu skonfigurujemy wzorzec [user]/[yyyy]-[mm]-[dd]/[title] dla wpisów w dziennikach. Jeżeli użytkownik John Doe utworzy w Wigilię wpis o tytule Dlaczego lubię dobrą muzykę, moduł pathauto automatycznie wygeneruje alias ścieżki john-doe/2005-12-24/dlaczego-lubie-dobra-muzyke.

Google Sitemaps

Kolejną rzeczą, którą możemy uczynić, by zwiększyć nasze notowania w wyszukiwarkach, jest skorzystanie z ze-

wnętrznego, dedykowanego dla Google modułu Google Sitemaps (gsitemap). Moduł ten tworzy XML-ową mapę serwisu w formacie określonym przez specyfikację Google Sitemaps (http://www.google.com/ webmasters/sitemaps) i umieszcza ją pod adresem http://www.example.com/ gsitemap. Mapa serwisu to, w skrócie, lista stron naszego serwisu uporządkowana wg ich priorytetu, którą Google wykorzystuje, aby inteligentniej i dogłębniej przeglądać nasze strony, dając w efekcie lepsze i bardziej aktualne wyniki wyszukiwania. Moduł gsitemap pozwala nam określić priorytet, jaki wybrane strony

Pathauto

Kolejne usprawnienia w tej dziedzinie zapewnia nam zewnętrzny moduł pathauto, wymagający standardowego modułu path. Pozwala on na elastyczną automatyzację generacji aliasów ścieżek dla węzłów, wpisów do blogów, stron użytkowników, terminów taksonomii (kategorii bądź znaczników) i wielu innych. Główną ideą tego modułu jest, by zamiast ręcznie wprowadzać alias ścieżki podczas tworzenia węzła, jednorazowo podawać wzorzec ścieżki pod zarządzaj –> ustawienia –> pathauto. Od tej pory, tworząc nowy węzeł po prostu pozosta-

Rysunek 15. Dialog parametrów pathauto

40

www.phpsolmag.org

PHP Solutions Nr 1/2006

Drupal
powinny posiadać w wygenerowanej mapie serwisu – na przykład według rodzaju węzłów, ilości komentarzy dla węzła, jego czasu stworzenia czy aktualizacji lub też kwestii, czy węzeł związany jest ze stroną główną. Możemy także polecić, aby moduł gsitemap powiadamiał Google o zmianie mapy serwisu oraz by odnotowywał fakt odwiedzenia naszej mapy przez pająka Google.
<meta name="keywords" content= "music, free, independent, genre, artists, portal" />

Narzędzia

Słowa kluczowe węzłów

Podsumowując, możemy użyć modułu nodewords, aby stworzyć zestaw metainformacji (w szczególności słów kluczowych) dla każdej strony naszego serwisu i mieć nadzieję, że będą one kolejnym elementem polepszającym naszą pozycję w wyszukiwarkach.

Efektywność słów kluczowych meta (metatagów) pod względem SEO jest wysoce niepewna: mówi się, że wiele wyszukiwarek zwyczajnie je ignoruje. Niemniej zakładając, że przynajmniej niektóre z nich korzystają ze słów kluczowych, możemy wykorzystać zewnętrzny moduł nodewords. Pozwala on nam określić streszczenie, opis oraz notę o prawach autorskich dla każdego węzła, jak również listę słów kluczowych do umieszczenia w znaczniku <meta> w nagłówkach naszych stron. Istnieje możliwość skonfigurowania globalnej noty o prawach autorskich oraz globalnej listy słów kluczowych do wykorzystania na wszystkich stronach, z wyjątkiem węzłów, dla których zostaną one przesłonięte. W naszym przypadku chcemy, by każda strona zawierała następujące znaczniki meta:
<meta name="copyright" content= "Copyright 2005 Music Community" />

Wydajność

W oczekiwaniu na (miejmy nadzieję) rosnącą popularność naszego portalu muzycznego, pod zarządzaj –> ustawienia uruchomimy mechanizm pamięci podręcznej systemu Drupal oraz dołączony do tego samego pakietu moduł throttle. Moduł zablokuje niektóre bloki i część funkcjonalności serwisu, jeżeli jego obciążenie przekroczy pewien (możliwy do ustawienia) pułap. Możemy także uaktywnić tłumienie (ang. throttling) konkretnych modułów i bloków w przypadku wystąpienia tzw. efektu Slashdot. Dokonamy tego za pomocą zarządzaj –> moduły oraz zarządzaj –> bloki, zaznaczając odpowiednie pola throttle.

związanego z tworzeniem i zarządzaniem osobnymi instalacjami dla każdego serwisu i wersji językowej. Intensywnie korzysta z AJAX i włącza jego funkcjonalność do interfejsu, a także pozwala użytkownikom stosować go na własną rękę. Wreszcie, możliwości systemu Drupal w zakresie SEO oferują nam wszystko, czego potrzebujemy, aby nasz serwis stał się widoczny dla wyszukiwarek. Biorąc pod uwagę dynamiczną, tętniącą życiem społeczność skupioną wokół systemu Drupal oraz jego znakomitą dokumentację, możemy ze spokojem wypróbować ten system. Jego modularność i co za tym idzie elastyczność, podobnie jak w systemach XOOPS czy Mambo, zapewnia dużą swobodę i łatwość tworzenia rozbudowanych, wielodomenowych oraz wielojęzycznych portali. n

O autorze
Uwe Hermann jest studentem informatyki na Technische Universität München w Niemczech oraz niezależnym programistą. Pracuje na rzecz wielu projektów Open Source, takich jak Debian GNU/ Linux i Drupal. Zainicjował i pielęgnuje projekt Unmaintained Free Software (http://unmaintained-free-software.org), którego celem jest znajdowanie nowych opiekunów dla opuszczonych projektów Open Source. Kontakt: uwe@hermann-uwe.de

Podsumowanie

Jak zademonstrowaliśmy w naszym przykładzie, Drupal jest czymś, na co użytkownicy systemów CMS czekali od dawna. Uwalnia administratora od zamieszania E K L A M A

R

PHP Solutions Nr 1/2006

www.phpsolmag.org

41

Techniki

Service Data Objects, czyli standard uniwersalnego dostępu do danych
Piotr Szarwas

Rozwiązania od dawna stosowane w Javie zalewają świat PHP. Należy do nich SDO, czyli Service Data Objects: zunifikowany, wspierany przez takie potęgi, jak IBM, Zend i BEA standard dostępu do danych, eliminujący potrzebę tworzenia osobnych interfejsów dla każdego ich źródła.

K
W SIECI
1. http://pecl.php.net/package/ sdo – pakiet SDO w repozytorium PECL 2. http://us2.php.net/manual/ en/ref.sdo.php – rozszerzenie SDO – oficjalna dokumentacja PHP 3. http://www.zend.com/pecl/ tutorials/sdo.php – tutorial o SDO 4. http://www.ibm.com/ developerworks/webservices – specyfikacje technologii SDO

ażdy, kto interesuje się PHP i jego rozwojem zdążył zauważyć, że wielkie firmy informatyczne, takie jak Oracle czy IBM zaczęły traktować ten język i otaczające go środowisko programistów bardzo poważnie. Świadczą o tym chociażby specjalnie przygotowane wersje PHP dla baz danych Oracle i DB2. Dzięki temu, nie tylko PHP wkracza w świat aplikacji klasy Enterprise, ale także technologie wywodzące się z rozwiązań tej klasy wkraczają do PHP. Tak też stało się z SDO. SDO to skrót od Service Data Objects. Jest nazwą architektury stworzonej przez IBM i BEA, mającej na celu ułatwić, czy wręcz ujednolicić dostęp do danych pochodzących z wielu różnych źródeł. Dzięki niej, programiści będą mogli, poprzez spójny interfejs, zarządzać danymi m.in. z relacyjnych baz danych, plików XML czy Web Services. Spójrzmy na Rysunek 1, na którym przedstawiamy schemat architektury SDO. Za dostęp do źródeł odpowiedzialne są obiekty DAS (Data Access Service). Dzię-

ki nim programista nie musi martwić się o sposób fizycznego rozmieszczenia danych w różnych źródłach. Dane, wchodzące do obiektów DAS i z nich wychodzące, mają postać drzewa obiektów SDO. Bez względu na fakt, czy operujemy na danych pochodzących z plików XML, czy też relacyjnej bazy danych, otrzymywane przez nas obiekty są identyczne. Pokażemy, w jaki sposób wykorzystać SDO do stworzenia prostej aplikacji służącej do administracji i prezentacji systemu newsów na stronie WWW. Newsy będą podzielone na kategorie, a każdy z nich będzie mógł należeć do jednej kategorii.

Co należy wiedzieć... Co obiecujemy...

Powinieneś znać podstawy MySQL-a i programowania obiektowego. Po przeczytaniu artykułu będziesz wiedział jak wykorzystać SDO do stworzenia prostego systemu newsów.

42

www.phpsolmag.org

PHP Solutions Nr 1/2006

SDO

Techniki

PHP i SDO

SDO powstało z myślą o platformie J2EE. Jednak dzięki dużemu zainteresowaniu IBM-a technologią PHP i porozumieniu z ZEND Technologies, zostało ono włączone również do PHP. Obecna implementacja nie jest tak dopracowana, jak wydanie dla Javy i nie osiągnęła nawet statusu wersji stabilnej. Dostępna jest jedynie jako rozszerzenie PECL (PHP Extensions Community Library). Ponadto, do swojego poprawnego działania wymaga parsera PHP w wersji 5.1, który w czasie pisania tego artykułu również nie był dostępny w wersji stabilnej. Mimo wczesnego stadium rozwoju, SDO dla PHP pozwala już zarządzać danymi pochodzącymi z bazy danych i plików XML. Niestety, aktualna implementacja ma szereg ograniczeń, o których szczegółowo napiszemy w dalszej części artykułu.

������

������

���������������

���

�������������

�����

Rysunek 1. Architektura SDO warstwa obiektów DAS. Ukrywa ona przed programistą szczegóły implementacji związane z komunikacją ze źródłami danych. W przybliżeniu, jest to rozszerzenie koncepcji warstwy dostępu do danych, często spotykanej w korzystających z baz danych aplikacjach PHP. Twórcy specyfikacji DAS poszli krok dalej i nie tylko ujednolicili dostęp do baz danych, ale zadbali także, aby w podobny sposób można było się komunikować z innymi źródłami, np. plikami XML. To ostatnie jest szczególnie przydatne podczas eksportu danych z bazy oraz ich importu. Wymiana danych z bazami odbywa się z wykorzystaniem rozszerzenia PDO. Do komunikacji z plikami XML zaprzęgnięto natomiast rozszerzenie SimpleXML. My skupimy się na pokazaniu, w jaki sposób SDO komunikuje się z bazą danych. Aktualna implementacja obiektów DAS ma, niestety, szereg ograniczeń. Najważniejsze z nich to m.in. obsługa jedynie dwóch silników bazodanowych: DB2 i MySQL, czy możliwość zdefiniowania tylko jednego (jednopolowego) klucza głównego na tabelę oraz brak transparentnej obsługi relacji typu wieledo-wielu. To ostatnie oznacza, że model SDO nie przewiduje przechowywania informacji o relacji pomiędzy danymi z dwóch tabel w tzw. tabeli relacji.

Tajemnice drzewa obiektów SDO

Tajemnicę swojego działania SDO ukrywa w koncepcji tzw. grafów danych (ang. data graphs). Mówiąc najprościej, dane są zorganizowane w postaci drzewiastych struktur obiektów. Przykładowo, jeżeli dane, z którymi aktualnie pracujemy, pochodzą z bazy, to każdy z obiektów SDO odpowiada jednemu rekordowi w określonej tabeli znajdującej się w tej bazie. Natomiast relacje pomiędzy obiektami znajdującymi się w drzewie są tożsame z relacjami w bazie danych (np. jeden-do-wielu). Pełną koncepcję drzewa SDO obrazuje Rysunek 2. Poza drzewem danych, w osobnej gałęzi w ramach struktury obiektów SDO przechowywane są informacje o wszystkich zmianach wykonanych na danych. Dzięki temu, obiekty DAS mogą w prosty sposób synchronizować źródła danych z drzewami obiektów. SDO zapewnia pełne API do zarządzania zmianami w drzewie, co umożliwia nam tworzenie własnych obiektów DAS. Drzewo SDO posiada jeszcze jedną ciekawą cechę: umożliwia przeszukiwanie wszystkich dostępnych w drzewie obiektów przy pomocy zapytań XPath.

Instalacja rozszerzenia SDO

Procedura instalacji zależy od platformy systemowej. W przypadku Windows, pobieramy ze strony PHP ostatnią skompilowaną wersję PHP 5.1 (w czasie pisania artykułu była to wersja RC1) i zestaw

������

������ ��������

������ �����

Data Access Services

Jak już wspomnieliśmy, jednym z głównych elementów architektury SDO jest

Rysunek 2. Drzewo obiektów SDO

PHP Solutions Nr 1/2006

www.phpsolmag.org

43

Techniki

SDO

skompilowanych rozszerzeń PECL (gdy pisałem artykuł, SDO było dostępne w wersji 0.5.1). Jeżeli instalujemy SDO pod Linuksem, proces instalacji jest dużo bardziej skomplikowany, głównie dlatego, że na php.net nie ma oficjalnej dystrybucji binarnej PHP dla tego systemu. Twórcy niektórych dystrybucji Linuksa dostarczają pakiety binarne PHP, czasem jednak trzeba go skompilować samemu. Źródła PHP pobieramy ze strony http://php.net. Podobnie uczynimy z SDO, którego źródła można pobrać z repozytorium PECL (http:// pecl.php.net/package/sdo). Pamiętajmy, że do poprawnego działania SDO potrzebuje rozszerzeń PDO i SimpleXML. Na szczęście są one dostępne standardowo w kodzie źródłowym PHP 5.1.

����������
�� ������ �������� ��������������

�������������
�� �� ������ ������ ����������� ������� ����������

Rysunek 3. Diagram encji dla bazy danych newsów

Pierwszy przykład

Jeżeli już zainstalowaliśmy i uruchomiliśmy PHP 5.1 z rozszerzeniem SDO, możemy zająć się utworzeniem wspomnianego wcześniej systemu aktualności. Spróbujmy więc przekonać się na własne oczy, jak szybko i elegancko możemy przy pomocy tej technologii na-

Listing 1. Skrypt tworzący bazę newsów dla MySQL-a
/**************************************** * tabela z grupami newsów * *****************************************/ create table `news_groups` ( ng_id int(11) not null auto_increment, ng_name varchar(100) not null, ng_description text, primary key (ng_id) ); /**************************************** * tabela z newsami. Zawiera relację do news_groups * *****************************************/ create table news_contents ( nc_id int(11) not null auto_increment, ng_id int(11) not null, nc_subject varchar(100) not null, nc_lead text, nc_content text not null, primary key (nc_id), );

Listing 2. Konfiguracja klasy DAS dla dwóch tabel (news_groups i news_contents)
$newsGroupsMap = array ( 'name' => 'news_groups', 'columns' => array( 'ng_id', 'ng_name', 'ng_description' ), 'PK' => 'ng_id' ); $newsContentsMap = array ( 'name' => 'news_contents', 'columns' => array('nc_id','ng_id','nc_subject','nc_lead','nc_content'), 'PK' => 'nc_id', 'FK' => array ( 'from' => 'ng_id', 'to' => 'news_groups', ) ); $newsContentsRelationsMap = array( 'parent' => 'news_groups', 'child' => 'news_contents' );

pisać system newsów. Pracę rozpoczniemy od stworzenia prostej bazy danych. Na Listingu 1. przedstawiamy schemat bazy dla MySQL-a. Aktualności przechowywane są w dwóch tabelach. Pierwsza z nich, o nazwie news_groups zawiera podstawowe informacje o grupach newsów, takie jak id grupy (ng_id), nazwa grupy (ng_name) i opis (ng_description). Same wiadomości gromadzone są w drugiej tabeli, news_contents. Każdy rekord z tej tabeli zawiera następujące informacje: id aktualności (nc_id), jej tytuł (nc_subject), streszczenie (nc_lead), treść (nc_content) oraz referencje do jednej z grup (ng_id). Na Rysunku 3. przedstawiony jest diagram encji dla obu tabel. Wynika z niego, że tabela news_contents znajduje się w relacji wiele-do-jednego w stosunku do tabeli news_groups. Przejdźmy teraz do pisania pierwszego skryptu wykorzystującego rozszerzenie SDO. Pierwszą rzeczą, jaką musimy uczynić, jest konfiguracja klasy DAS. Jak wiemy, klasa ta odpowiada za dostęp do źródła danych (w naszym przykładzie – MySQL). Sama klasa jest na tyle ogólna, że nie wie, jaką strukturę ma nasza baza. Dlatego też musimy powiedzieć jej, do których tabel i kolumn ma zapisać odpowiednie elementy drzewa obiektów SDO. Aby skonfigurować DAS, należy dla każdej tabeli utworzyć mapping – tablicę PHP zawierającą następujące informacje: Ÿ Ÿ Ÿ nazwę tabeli, tablicę z nazwami pól, nazwę pola z kluczem głównym (klucz główny w tabeli może składać się z jednego pola), tablicę opisująca relacje do innej tabeli.

Ÿ

Dodatkowo, jeżeli nasz obiekt DAS ma obsługiwać wiele tabel, pomiędzy którymi

44

www.phpsolmag.org

PHP Solutions Nr 1/2006

SDO
są relacje, potrzebna jest jeszcze jedna tablica zawierająca informacje o kierunku tych relacji, tzn. która z tabel jest nadrzędna, a która podrzędna. Przypomnijmy, że obecna implementacja obsługuje jedynie relacje jeden-do-wielu oraz jeden-do-jednego. Na Listingu 2. pokazujemy przykładową konfigurację dla tabel news_groups i news_contents. Patrząc na tablice konfiguracyjne, zauważamy jedne z największych bolączek obecnej implementacji SDO: klucz główny jest jednopolowy, a tablica może

Techniki

Listing 3. Skrypt dodający grupę newsów do bazy danych
<?php require_once 'Relational.php'; $dbConnection = new PDO('mysql:host=localhost;dbname=nazwa_bazy_danych', 'użytkownik','hasło'); $das = new SDO_DAS_Relational( array($newsGroupsMap,$newsContentsMap), 'news_groups',array( $newsContentsRelationsMap )); $root = $das->createRootDataObject(); $newsGroup1 = $root->createDataObject('news_groups'); $newsGroup1->ng_name = "Grupa Pierwsza"; $newsGroup1->ng_description = "Opis dla pierwszej grupy"; $das->applyChanges($dbConnection, $root); ?>

Listing 4. Skrypt pobierający jeden rekord z bazy danych i modyfikujący go
<? require_once 'Relational.php'; $dbConnection = new PDO ('mysql:host=localhost;dbname=nazwa_bazy_danych','użytkownik','hasło'); $das = new SDO_DAS_Relational( array($newsGroupsMap,$newsContentsMap), 'news_groups', array( $newsContentsRelationsMap ) ); $root = $das->executeQuery($dbConnection, 'select * from news_groups where ng_id=1', array( 'news_groups.ng_id', 'news_groups.ng_name', 'news_groups.ng_description' ) ); $root['news_groups'][0]->ng_name = 'The future of GUIs for PHP'; $das->applyChanges($dbConnection, $root); ?>

Listing 5. Skrypt pobierający jeden rekord z bazy danych i kasujący go. Drugą metodą usunięcia obiektu z drzewa jest przypisanie mu wartości null
require_once 'Relational.php'; $dbConnection = new PDO ('mysql:host=localhost;dbname=nazwa_bazy_danych','użytkownik','hasło'); $das = new SDO_DAS_Relational( array($newsGroupsMap,$newsContentsMap), 'news_groups', array( $newsContentsRelationsMap ) ); $root=$das->executeQuery($dbConnection, 'select * from news_groups where ng_id=1', array( 'news_groups.ng_id', 'news_groups.ng_name', 'news_groups.ng_description' ) ); unset( $root['news_groups'][0] ); $das->applyChanges($dbConnection, $root);

Listing 6. Skrypt umożliwiający jednoczesne dodanie do bazy danych grupy i związanego z nią newsa
$newsGroup = $root->createDataObject('news_groups'); $newsGroup->ng_name = "PHP-related news"; $newsGroup->ng_description = "The best news around the world of PHP"; $newsContent1 = $newsGroup->createDataObject('news_contents'); $newsContent1->nc_subject = 'SDO is becoming popular'; $newsContent1->nc_lead = 'SDO or Service Data Objects is gaining more and more'; $newsContent1->nc_content = 'the content of article on SDO'; $newsContent2 = $newsGroup->createDataObject('news_contents'); $newsContent2->nc_subject = 'AJAX and PHP'; $newsContent2->nc_lead = 'AJAX is a solution for the interactive web pages'; $newsContent2->nc_content = 'the content of the article'; $das->applyChanges($dbConnection, $root);

mieć tylko jedną relację (jeden klucz obcy). Na szczęście, w wielu prostych bazach danych, ograniczenia te nie mają znaczenia. Ponadto, należy zakładać, że w miarę rozwoju projektu, autorzy rozwiną jego funkcjonalność również w tych dziedzinach. Skoro mamy już skonfigurowany obiekt DAS, najwyższa pora utworzyć kod, który zapisze grupę newsów do bazy danych. Odpowiedni skrypt z pominięciem mappingu przedstawiamy na Listingu 3. Pierwszą operacją, jaką musimy wykonać jest otwarcie połączenia do bazy danych. Pamiętajmy, że SDO korzysta w tym celu z PDO. Po nawiązaniu połączenia tworzymy obiekt klasy SDO_DAS_Relational, który będzie odpowiedzialny za zapisanie grupy newsów do naszej bazy danych. Jako parametry, klasa ta musi otrzymać tablice konfigurujące wszystkie tabele, z którymi będziemy się komunikować poprzez SDO, a także nazwę tabeli nadrzędnej i tablicę z typami relacji pomiędzy tabelami. Nic nie stoi na przeszkodzie, aby dla każdej z tabel naszej bazy stworzyć osobne obiekty DAS. Pozbawiłoby nas to jednak możliwości jednoczesnego zapisywania drzew obiektów, między którymi istnieje relacja. Wracając do przykładu: dwie najważniejsze klasy są już gotowe. Musimy jeszcze utworzyć korzeń drzewa obiektów SDO. Służy do tego metoda createRootDataObject(), którą wywołujemy na obiekcie DAS. Mając korzeń, możemy teraz utworzyć obiekt news_ groups, który zapiszemy później do bazy danych. Do tworzenia obiektów w drzewie SDO, przeznaczona jest metoda createDataObject(). Ma ona jeden parametr: nazwę tabeli, dla której należy utworzyć obiekt. W naszym przypadku jest to news_groups. Obiekt news_groups posiada atrybuty publiczne, których nazwy są tożsame z nazwami odpowiednich kolumn w tabeli news_groups, znajdującej się w bazie danych. Informacji o nazwach tych kolumn dostarcza SDO mapping. Przypisując tym atrybutom odpowiednie wartości możemy stworzyć pożądany obiekt. Aby zapisać go do bazy danych, należy na obiekcie DAS wykonać metodę applyChanges(). Ma ona dwa parametry: obiekt PDO i drzewo obiektów SDO. Tak oto, w siedmiu linijkach kodu, bez napisania choćby jednego polecenia

PHP Solutions Nr 1/2006

www.phpsolmag.org

45

Techniki

SDO

SQL, zapisaliśmy rekord w bazie danych. Warto wspomnieć, że za jednym razem możemy zapisać wiele obiektów. Nic nie stoi bowiem na przeszkodzie, aby metodę createDataObject() wywołać wiele razy, tworząc tym samym wiele obiektów news_groups. Możemy nawet wielokrotnie zapisywać te obiekty w bazie danych: SDO samo zadba o to, co powinno zostać w niej zapisane. Możliwe jest to dzięki opisanemu już mechanizmowi, który zapamiętuje każdą zmianę stanu drzewa obiektów. Jeżeli więc cokolwiek dodamy, skasujemy lub zmienimy, drzewo będzie miało informacje na ten temat. Każda operacja zapisu zmian zeruje rejestr zmian, dlatego nawet po dwukrotnym wykonaniu operacji applyChanges dane zostaną zapisane tylko raz. Skoro potrafimy już zapisać dane do bazy, spróbujmy je teraz odczytać, zmodyfikować i ponownie zapisać. Tak jak poprzednio, posłużymy się przykładem. Na Listingu 4. przedstawiamy kod, który potrafi wykonać te zadania. Tak jak w poprzednim przykładzie, skrypt rozpoczyna się od inicjalizacji klasy SDO_DAS_Relational i nawiązania połączenia. W następnym kroku, przy użyciu metody executeQuery() pobieramy obiekty SDO z bazy danych. Warto w tym miejscu zwrócić uwagę na fakt, że mimo iż składnia polecenia zawiera zapytanie SQL, wynikiem jego działania jest drzewo obiektów

object(SDO_DataObjectImpl)[12] public 'news_groups' => object(SDO_DataObjectList)[11] object(SDO_DataObjectImpl)[14] public 'ng_id' => '10' public 'ng_name' => 'Grupa Pierwsza' public 'ng_description' => 'Opis dla pierwszej grupy' public 'ng_contents' => object(SDO_DataObjectList)[13] object(SDO_DataObjectImpl)[15] public 'nc_id' => '4' public 'nc_subject' => '10' public 'nc_lead' => 'Jakiś tytuł' public 'nc_content' => 'Jakaś zajawka' object(SDO_DataObjectImpl)[16] public 'ng_id' => '11' public 'ng_name' => 'Grupa Pierwsza' public 'ng_description' => 'Opis dla pierwszej grupy' public 'ng_contents' => object(SDO_DataObjectList)[18] object(SDO_DataObjectImpl)[17] public 'nc_id' => '5' public 'nc_subject' => '11' public 'nc_lead' => 'Jakiś tytuł' public 'nc_content' => 'Jakaś zajawka'

Rysunek 4. Wynik działania zapytania, które za jednym razem wyciąga z bazy grupy newsów i same wiadomości SDO. Na otrzymanym drzewie możemy wykonywać dowolne operacje, np. dodać do niego nowy element (metoda createDataObject), a także zmienić czy skasować istniejący element. Procedura kasowania obiektu pokazana jest na Listingu 5. Tak jak w poprzednim przypadku, aby zapisać dokonane zmiany, należy wykonać na obiekcie DAS metodę applyChanges().

Drugi przykład

Listing 7. Kwerenda pobierająca z bazy danych drzewo z grupami i newsami
$root = $das->executeQuery($dbConnection, 'select * from news_groups, news_contents where news_groups.ng_id=news_contents.ng_id', array( 'news_groups.ng_id', 'news_groups.ng_name', 'news_groups.ng_description','news_contents.nc_id', 'news_contents.nc_subject', 'news_contents.nc_lead', 'news_contents.nc_content' ));

Listing 8. Kasowanie grupy wraz z newsami
$root = $das->executeQuery($dbConnection, 'select news_groups.ng_id, news_contents.nc_id from news_groups, news_contents where news_groups.ng_id=news_contents.ng_id', array( 'news_groups.ng_id', 'news_contents.nc_id' )); unset( $root['news_groups'][0] ); $das->applyChanges($dbConnection, $root);

Spróbujmy teraz napisać skrypt, który będzie potrafił dodać jednocześnie grupę newsów i kilka związanych z nią wiadomości. Kod prezentujący ten przykład przedstawiamy na Listingu 6. Dla uproszczenia pokazujemy tam tylko kod charakterystyczny dla tej operacji, pomijając początkową inicjalizację obiektów. Zwróćmy uwagę, że po utworzeniu obiektu SDO dla grupy newsów, wywołujemy na tym obiekcie metodę createDataObject(), która tworzy obiekt SDO dla konkretnej wiadomości. Jeżeli chcemy utworzyć więcej niż jedną wiadomość, to wystarczy po raz kolejny wywołać metodę createDataObject(). Warto zauważyć, że nie musimy martwić się o przekazanie obiektowi newsa id grupy, gdyż SDO zrobi to za nas. Ponadto nic nie stoi na przeszkodzie, aby za jednym razem stworzyć wiele grup i przypisać im wiele newsów. Kolejnym zadaniem jest pobranie z bazy drzewa z grupami i związanymi z nimi newsami przy pomocy jednego

46

www.phpsolmag.org

PHP Solutions Nr 1/2006

SDO
zapytania. Jego rozwiązanie pokazujemy na Listingu 7. Pomijając oczywiście operacje związane z konfiguracją połączenia i klasy DAS widzimy, że wszystko, co musimy napisać, zawiera się w jednej linii kodu! Metoda executeQuery() potrzebuje dwóch parametrów: połączenia do bazy danych i zapytania SQL. Trzeci parametr jest opcjonalny, wymagany jedynie w sytuacjach, gdy nazwa którejkolwiek kolumny występuje dwa razy w mappingu. Taka sytuacja ma miejsce w naszym przykładzie: kolumna ng_id pojawia się zarówno w tabeli news_groups, jak i news_contents. Fakt, że są to inne tabele, nie ma znaczenia. Jeżeli więc nie chcemy podawać trzeciego parametru, musimy pamiętać, aby w naszym modelu nie powtarzała się nazwa żadnej kolumny. Wystarczy więc przyjąć odpowiednią strategię nazewnictwa. Rezultatem wykonania tego kodu jest drzewo obiektów SDO, które przedstawiamy na Rysunku 4. Na zakończenie pokażemy jeszcze, jak skasować grupę i związane z nią newsy. Jest to bardzo proste, ponieważ wystarczy pobrać grupę wraz ze związanymi z nią newsami (wszystkimi) i wykonać na niej funkcję unset() lub przypisać do niej wartość null. Jeżeli pobierzemy samą grupę, to SDO będzie próbowało skasować jedynie ją. Nie jest to oczywiście problemem, gdy pracujemy na bazie wyposażonej w mechanizm kluczy obcych. Jeżeli jednak nasza baza danych to np. MySQL, a tabele na których operujemy, są w formacie MyISAM, to usunięcie samej grupy powiedzie się, ale dane w bazie będą niespójne. Skrypt ilustrujący kasowanie grupy wraz z newsami pokazany jest na Listingu 8.

Techniki

ju, jego funkcjonalność jest imponująca. Miejmy nadzieję, że już wkrótce jego autorzy wydadzą wersję stabilną, która będzie pozbawiona obecnych mankamentów. Warto pamiętać, że pomijając wymienione ograniczenia, już obecnie kod biblioteki SDO jest bardzo solidny. Zachęcamy więc do własnych eksperymentów i zapraszamy do następnego artykułu o technologiach SDO i XML, który ukaże się w kolejnym numerze PHP Solutions. n

O autorze
Piotr Szarwas jest pracownikiem SUPERMEDIA Interactive i doktorantem na wydziale Fizyki Politechniki Warszawskiej. Od 2003 roku projektuje aplikacje WWW w oparciu o PHP4/5. Obecnie zajmuje się tworzeniem frameworka dla PHP opartego na rozwiązaniach Hibernate i Spring.

Podsumowanie

Budując prostą bazę danych dla systemu newsów, pokazaliśmy możliwości drzemiące w SDO. Mimo, że rozszerzenie to znajduje się ciągle w fazie rozwo-

R

E

C

E

N

Z

J

A

PHP5 Zaawansowane programowanie

«««««

Autorzy: Ed Lecky-Thompson, Heow Eide-Goodman, Steven D. Nowicki, Alec Cove Wydanie: Helion 2005 Cena: 79,00 zł
Jestem nieco znudzony ofertą polskich wydań książek o PHP, pewnie dlatego również po tę pozycję sięgałem niechętnie. Przypuszczałem, że będzie to materiał podobny do bardzo dobrej książki PHP Zaawansowane programowanie. Vademecum profesjonalisty autorstwa Georga Schlossnagle’a, tyle że z większym naciskiem na PHP5. Na szczęście PHP5 Zaawansowane programowanie okazało się inne. Dużo inne. Książka nie stanowi vademecum profesjonalisty – nie przeczytamy w niej (w sposób encyklopedyczny) o wszystkich zaawansowanych cechach języka PHP. Przeczytamy w niej o profesjonalnym podejściu do tworzenia złożonych aplikacji w PHP5. Autorzy skupili się na dogłębnym opisaniu modelowania w UML, korzystania ze wzorców projektowych, testowania aplikacji (PHPUnit) czy tworzenia własnego warsztatu programisty – narzędzi które będzie można wielokrotnie wykorzystać w każdym projekcie. Bardzo ciekawa jest część (ponad 200 stron) opisująca Studium przypadku – automatyzacja działu sprzedaży. Dowiemy się, jak podejść do dużego projektu informatycznego i jak nim zarządzać. Omówione zostaną pojęcia planowania i architektury systemu. Razem z autorami stworzymy aplikację automatyzującą pracę zespołu sprzedaży i solidną platformę raportującą. Szczerze poleciłbym tę książkę programistom PHP, którzy mają już za sobą kilka projektów w PHP, ale chcą zacząć tworzyć aplikacje w sposób bardziej profesjonalny – przemyślany, solidny i wydajny. Jeśli planujesz wykonanie większego projektu lub nie wierzysz (nie znasz) w PHP5, a szukasz dobrej platformy programistycznej do tworzenia aplikacji WWW – ta lektura jest dla Ciebie.

Dariusz Pawłowski

PHP Solutions Nr 1/2006

www.phpsolmag.org

47

Techniki

AJAX – wyjątkowo interaktywne i wydajne aplikacje WWW
Joshua Eichorn, Werner M. Krauß

Aplikacje tworzone w PHP pozwalają osiągnąć bardzo wiele przy ograniczonym oprogramowaniu klienckim, co oznacza łatwe wdrażanie i aktualizacje, a tym samym szybkie efekty pracy. Architektura ta ma też dotkliwe wady, jak opóźnienia między wyświetlaniem kolejnych stron lub brak możliwości pobierania nowych danych bez wysyłania formularza. Na szczęście istnieje mechanizm AJAX.

A
W SIECI
1. http://sourceforge.net/ projects/jpspan/ – strona główna projektu JPSpan 2. http://pear.php.net/package/ HTML_AJAX/ – strona główna pakietu HTML_AJAX 3. http://www.google.com/ webhp?complete=1&hl=en – Google Suggest 4. http://www.ajaxpatterns.org/ – serwis poświęcony wzorcom aplikacji AJAX 5. http://www.ajaxpatterns.org/ Suggestion – opis wzorca w stylu Google Suggest 6. http://blog.joshuaeichorn. com/archives/category/php/ ajax – blog Joshuy Eichorna poświęcony AJAX-owi

JAX pozwala stworzyć dodatkowy kanał komunikacji między klientem a serwerem PHP, a tym samym wysyłać i odbierać dane bez przeładowywania strony. Otwiera to zupełnie nowe możliwości, a w połączeniu z operacjami na modelu DOM z poziomu JavaScriptu, oznacza nadejście ery bogato wyposażonych, interaktywnych aplikacji PHP, wolnych od irytującego klikania i czekania. W tym artykule przedstawimy praktyczne wprowadzenie do techniki AJAX na przykładzie dwóch bibliotek PHP i niewielkiej aplikacji o działaniu podobnym do Google Suggest.

asynchronicznej wymiany danych. Techniki te są łączone w jedną całość za pomocą JavaScriptu, odpowiedzialnego za logikę aplikacji i dynamiczną aktualizację interfejsu użytkownika stosownie do potrzeb. Pomimo XML w nazwie, AJAX niekoniecznie wymaga używania formatu XML do wymiany danych. Poza XML-em obsługiwane są między innymi zwykły tekst, sformatowany HTML (dodawany do bieżącej strony poprzez właściwość innerHTML) oraz format JSON (JavaScript

Powinieneś wiedzieć...

Czym jest AJAX?

AJAX (skrót od Asynchronous JavaScript And XML) jest nazwą nowej metody programowania, łączącej kilka różnych technik: (X)HTML i CSS do tworzenia interfejsu użytkownika, DOM (Document Object Model) do obsługi elementów dynamicznych i interakcji oraz XMLHttpRequest do

Powinieneś się dobrze orientować w zasadach programowania obiektowego w PHP4 lub PHP5. Przyda się też pewna znajomość JavaScriptu.

Obiecujemy...

Po przeczytaniu artykułu będziesz znał zasadę działania i stosowania techniki AJAX oraz śledzenia kodu. Pokażemy też możliwości bibliotek implementujących tę technikę.

48

www.phpsolmag.org

PHP Solutions Nr 1/2006

AJAX

Techniki

Object Notation), który można przepuścić przez eval() w celu uzyskania typów JavaScript. Można też korzystać z dowolnego innego formatu danych dającego się obsłużyć w JavaScripcie i PHP. AJAX-a można najprościej zdefiniować jako metodę wykorzystania JavaScriptu do komunikacji z serwerem niezależnie od tradycyjnych żądań POST i GET. Strona techniczna jest tu jednak mniej istotna – najważniejsze są zupełnie nowe możliwości tworzenia aplikacji internetowych. Podstawą pracy AJAX-a jest obiekt XMLHttpRequest, stanowiący standardowy element wielu przeglądarek. Jeśli postanowisz dodać obsługę AJAX-a do swojej aplikacji za pomocą biblioteki, to nie musisz wiele wiedzieć o samym XMLHttpRequest, gdyż wszystkim zajmie się biblioteka. Fizyczna implementacja obiektu XMLHttpRequest zależy od konkretnej przeglądarki – w Internet Explorerze jest to wbudowany obiekt ActiveX, natomiast w Firefoksie, Safari i większości innych przeglądarek jest on wewnętrznym obiektem JavaScriptu. XMLHttpRequest udostępnia proste API, pozwalające wysyłać do serwera żądania HTTP metodami GET i POST. Dla serwera są to zwyczajne żądania przeglądarki, zawierające nawet wszystkie pliki cookie dla bieżącej domeny oraz autoryzację HTTP (jeśli oczywiście jest włączona). Od strony JavaScriptu, XMLHttpRequest daje dostęp do treści i nagłówków podczas wysyłania i odbioru żądań. Możliwość ta jest często używana do poinformowania serwera, że żądanie nie zostało zgłoszo-

������������������������

������

Rysunek 2. Przepływ danych w aplikacji AJAX ne bezpośrednio przez użytkownika, lecz poprzez XMLHttpRequest. Odebraną treść można traktować jako zwykły tekst, ale w przypadku treści typu text/xml można też stworzyć obiekt XML DOM. Obsługa modelu DOM przyczyniła się do popularności XML-a jako formatu wymiany danych między klientem a serwerem, natomiast utrzymanie obsługi zwykłego tekstu pozwala korzystać z dowolnego formatu dającego się przetworzyć na poziomie JavaScriptu. możliwość uzyskania znacznie wyższego poziomu interaktywności w aplikacjach internetowych. Reakcje programu na działania użytkownika są dużo szybsze, bez nużącego klikania i czekania, przez co obsługa całego programu znacznie bardziej przypomina pracę z tradycyjną aplikacją stacjonarną. Rysunek 1 przedstawia przepływ danych w typowej aplikacji internetowej. Użytkownik wypełnia formularz i wysyła go na serwer WWW, który przetwarza formularz i odsyła dane do czekającego użytkownika. Zwracanym wynikiem jest pełna strona HTML, którą przeglądarka klienta musi załadować w całości (treść i strukturę). Marnuje się w ten sposób czas i pasmo, gdyż kod strony wynikowej najczęściej niewiele się różni od kodu poprzedniej strony. Aplikacja AJAX wysyła do serwera wyłącznie żądania pobierające nowe, potrzebne dane, a odpowiedź serwera jest przetwarzana przez JavaScript po stronie klienta. Dzięki wprowadzeniu tej dodatkowej warstwy JavaScriptu, przetwarzanie danych nie spowalnia działania interfejsu użytkownika. Cała aplikacja działa znacznie szybciej, gdyż między serwerem, a klientem przesyłanych jest nieporównanie mniej danych, a spora część przetwarzania odbywa się po stronie klienta (Rysunek 2). Praktycznie rzecz ujmując, stworzenie aplikacji AJAX wymaga zatem dwóch

Dlaczego AJAX?

Najważniejszym argumentem przemawiającym za korzystaniem z AJAX-a jest

����

������������������

���
����������������

�������������� ������������������������ ������������

����
������������������ ������

������������������� ���������������

�������������� ������������������������ �������������

����
������������������ ������

������������������� ���������������

����������������

������
��� �����������������

Rysunek 1. Przepływ danych w tradycyjnej aplikacji internetowej

PHP Solutions Nr 1/2006

����

������������������

���������������������

������������������ ���������������������

���� �������������������� �������������������

�����������������������

www.phpsolmag.org

49

Techniki
elementów: odpowiednich skryptów po stronie klienta i specjalnego kanału komunikacji z serwerem. Explorerze z wyłączoną obsługą ActiveX, co często dotyczy na przykład kafejek internetowych. Może się także zdarzyć, że aplikacja będzie działać nieco inaczej w różnych przeglądarkach i na różnych platformach, choć to samo dotyczy tworzenia tradycyjnych aplikacji internetowych. AJAX oferuje spore możliwości interakcji, ale do wielu zadań po prostu się nie nadaje, na przykład do dynamicznego rysowania elementów czy obsługi animacji – w takich sytuacjach lepiej korzystać Listing 1. Serwer JPSpan
<?php session_start(); // Klasa powitania class HelloWorld { function HelloWorld() { if (!isset($_SESSION['strings'])) { $_SESSION['strings'] = array('Hello','World','Hello World'); } } function addString($string) { $_SESSION['strings'][] = $string; return $this->stringCount(); } function randomString() { $index = rand(0,count($_SESSION['strings'])-1); return $_SESSION['strings'][$index]; } function stringCount() { return count($_SESSION['strings']); } } // Ustawienie stałej JPSPAN require_once 'jpspan-0.4.3/JPSpan.php'; // Załadowanie serwera PostOffice require_once JPSPAN . 'Server/PostOffice.php'; // Utworzenie serwera PostOffice $S = & new JPSpan_Server_PostOffice(); // Rejestracja klasy w serwerze $S->addHandler(new HelloWorld()); // Obsługa wyświetlania JavaScriptu po dodaniu // ciągu ?client do URL-a serwera if (isset($_SERVER['QUERY_STRING']) && strcasecmp($_SERVER['QUERY_STRING'], 'client')==0) { // Wyłączenie kompresji wynikowego JavaScriptu // (m.in. usuwania białych znaków) z powodu // problemów wydajnościowych define('JPSPAN_INCLUDE_COMPRESS',false); // Wyświetlenie klienckiego JavaScriptu $S->displayClient(); }else { // Początek faktycznej obsługi żądań // Dołączenie obsługi błędów // błędy, ostrzeżenia i komunikaty serializowane do JS // Obsługiwanie żądań $S->serve(); } ?>

Zalety techniki AJAX

AJAX ma wiele zalet, z których najbardziej zauważalną jest znaczące rozszerzenie zakresu możliwości interfejsu użytkownika. Jednak samo w sobie to nie wystarczy – w końcu istnieje też wiele innych technologii o zbliżonych możliwościach. O wyjątkowości AJAX-a stanowi przede wszystkim to, że bazuje on na uznanych standardach, więc w przeciwieństwie do innych narzędzi do tworzenia interaktywnych aplikacji internetowych (na przykład Flasha) można go z łatwością wpasować w istniejące procesy deweloperskie. Można więc dalej korzystać ze swojego ulubionego edytora czy środowiska programistycznego, bez konieczności poznawania nowych narzędzi. Istnieje też wiele darmowych zestawów narzędzi Open Source ułatwiających tworzenie i rozwijanie aplikacji AJAX, a przy okazji redukujących objętość kodu JavaScriptu, jaki trzeba wpisywać ręcznie. W dalszej części artykułu zobaczymy, jak dołączać obsługę AJAX-a do własnych aplikacji z pomocą popularnych bibliotek.

z Flasha. Warto w tym miejscu zaznaczyć, że pomimo teoretycznej możliwości połączenia zalet AJAX-a i Flasha w ramach jednej aplikacji, złożoność takiego rozwiązania jest na tyle duża, że lepiej używać tych technik osobno.

Wykorzystanie bibliotek

Istnieje wiele bibliotek narzędziowych mających na celu ułatwienie integracji JavaScriptu i PHP. Wszystkie uwzględniają jakąś metodę przesyłania danych, ale większość oferuje dodatkowe możliwości,

Wady AJAX-a

Opisywana metoda interakcji z klientem ma też swoje wady. Nie można przewidzieć, z jakiej przeglądarki korzysta użytkownik, więc aplikacja może nie działać na niekompatybilnych przeglądarkach lub przy wyłączonej obsłudze JavaScriptu. Oznacza to, że dobrą praktyką jest uwzględnienie awaryjnej metody obsługi, na przykład poprzez stworzenie bazowej aplikacji z wykorzystaniem tradycyjnych technik, a następnie rozbudowanie jej o opcjonalne usprawnienia używające AJAX-a. Trzeba też pamiętać, że aplikacje z AJAX-em nie będą działać w Internet

JPSpan i PEAR

Wersja biblioteki JPSpan dla repozytorium PEAR jest dostępna w serwisie http://www.pearified.com. Do instalacji będzie potrzebny PEAR w nowej wersji 1.4, obsługujący inne kanały niż tylko http://pear.php.net. Poleceniem pear channel-discover pearified.com należy dodać kanał do repozytorium, po czym można już zainstalować JPSpan poleceniem pear install pearified/ JavaScript_JPSpan.

50

www.phpsolmag.org

PHP Solutions Nr 1/2006

AJAX
od bezpośredniego odwzorowania metod klas PHP na pośrednika JavaScriptu, po środowisko tworzenia elementów interfejsu użytkownika. Przyjrzyjmy się bliżej dwóm popularnym pakietom: bibliotekom JPSpan i HTML_AJAX. zwalającą odwzorowywać tablice PHP na obiekty JavaScriptu. Strona z serwera jest dołączana do wynikowych stron HTML ze znacznikiem client, generując pośrednie klasy JavaScript o takim samym API, jak klasy PHP. Ze względu na ograniczenia PHP4, wszystkie nazwy klas i metod są zapisywane małymi literami. Domyślny serwer JPSpan nosi nazwę JPSpan_ Server_PostOffice i może służyć do odwzorowywania na JavaScript zarówno całych klas, jak i ich części. Korzystając z serwera w dużym serwisie można rozważyć dodanie znacznika class, co pozwoli ograniczyć liczbę klas dołączanych i rejestrowanych na serwerze, a tym samym zmniejszy koszt przetwarzania. Osobiście nie doświadczyłem jednak żadnych problemów wydajnościowych nawet przy pięciu stale zarejestrowanych klasach integracyjnych.

Techniki

HTML_AJAX a nazwy klas

JPSpan

Najpierw zajmiemy się pakietem JPSpan – jedną z bardziej dojrzałych bibliotek AJAX dla PHP, dostępną od listopada 2004 r. Podstawową funkcją biblioteki jest niezależna od przeglądarki obsługa mechanizmu AJAX bazująca na XMLHttpRequest, z możliwością wyboru pracy synchronicznej lub asynchronicznej. Dostępne jest wspólne, obiektowe API dla JavaScriptu i PHP. JPSpan obsługuje też wiele innych funkcji, na przykład przezroczyste odwzorowania obiektów z bardzo dobrą serializacją danych, poListing 2. Klient JPSpan

Nazwy klas zwracane przez PHP4 są zawsze zapisane małymi literami. Jeśli koniecznie chcesz zachować rozróżnienie wielkości liter lub potrzebujesz zgodności między PHP4 i PHP5, musisz skorzystać z dodatkowych parametrów metody registerClass(), określających nazwę klasy rejestrowanej w JavaScripcie oraz tablicę eksportowanych metod:
$server = new HTML_AJAX_Server(); $hello = new HelloWorld(); $methods = array('foo','bar'); $server->registerClass($hello, 'Example', $methods);

§

<html> <head> <title>Hello World w JPSpan</title> <script type='text/javascript' src='jpspan_server.php?client'></script> <script> // Klasa JavaScript zawierająca metody zwrotne var hwCallback = { randomstring: function(result) { document.getElementById('canvas').innerHTML += '<p>'+result+'</p>'; }, stringcount: function(result) { document.getElementById('count').innerHTML = result; }, addstring: function(result) { document.getElementById('count').innerHTML = result; } } // Utworzenie obiektu zdalnego. Jego nazwa jest odwzorowana małymi literami, // gdyż w nazwach klas i funkcji PHP4 wielkość liter nie jest rozpoznawana. // Rejestrując każdą klasę na serwerze można przywrócić rozróżnianie // wielkości liter. var remoteHW = new helloworld(hwCallback); function do_addString() { remoteHW.addstring(document.getElementById('string').value); document.getElementById('string').value = ''; } </script> </head> <body onLoad="remoteHW.stringcount()"> <input type="button" name="check" value="Pokaż losowy napis" onclick= "remoteHW.randomstring(); return false;"> <div>Liczba losowych napisów: <span id="count"></span></div> <div id="canvas" style="border: solid 1px black; margin: 1em; padding: 1em;"></div> <div> Podaj nowy napis: <input type="text" name="string" id="string" size="20"> <input type="button" name="check" value="Dodaj nowy napis" onclick= "do_addString(); return false;"> </div> </body> </html>

Wywołania polegają na utworzeniu instancji odpowiedniej klasy JavaScriptu, a następnie wywoływaniu jej metod. W chwili utworzenia instancji w trybie asynchronicznym, określana jest klasa zwrotna, po czym wyniki są przesyłane jej metodom o takich samych nazwach, jak metody pierwotnie wywoływane. JPSpan dodatkowo obsługuje złożone typy danych, w tym wielowymiarowe tablice i obiekty, jak również serializację i przekazywanie błędów PHP do JavaScriptu z możliwością konfiguracji obsługi błędów po stronie klienta.

Strona serwera

Przyjrzyjmy się działaniu JPSpan nieco bliżej na prostym przykładzie typu Hello World, wyświetlającym losowy napis w reakcji na kliknięcie i pozwalającym dodawać nowe napisy. Zaczynamy od utworzenia klasy helloworld, zawierającej proste metody PHP do obsługi dodawania napisów do tablicy sesyjnej, zwracania długości tablicy i wyświetlania losowego napisu z tablicy. Są to odpowiednio metody addString(), stringCount() i randomString(), a ich kod przedstawia Listing 1. Praca z klasami JPSpan nie różni się niczym od obsługi zwykłych klas. Trzeba tylko pamiętać, że klasa ta jest odtwarzana przy każdym wywołaniu ze strony JavaScriptu, więc utrzymywanie danych składowych między wywołaniami wymaga pamiętania instancji klasy w ramach sesji. Musimy jeszcze dołączyć JPSpan i odpowiedni plik serwera PostOffice, po czym możemy stworzyć nową instancję serwera i zarejestrować w niej naszą klasę wywołując $S->addHandler(new

PHP Solutions Nr 1/2006

www.phpsolmag.org

51

Techniki
HelloWorld()).

Pozostaje jeszcze tylko określić, czy chcemy wysłać kliencki kod JavaScript, czy też obsługiwać żądania. Jak widać na Listingu 1, obiektowe API JPSpan bardzo ułatwia przygotowania po stronie serwera.

Listing 3. HTML_AJAX może posłużyć do pobierania treści z innej strony na tym samym serwerze
<html> <head> <script type='text/javascript' src="server.php?client=main"></script> <script type='text/javascript' src="server.php?client=dispatcher"></script> <script type='text/javascript' src="server.php?client=HttpClient"></script> <script type='text/javascript' src="server.php?client=Request"></script> <script type='text/javascript' src="server.php?client=json"></script> </head> <body> <script type="text/javascript"> function clearTarget() { document.getElementById('target').innerHTML = 'clear'; } // Operacja 'grab' jest najprostszym zastosowaniem HTML_AJAX, polegającym na // wysłaniu żądania do strony i pobraniu wyników. Można jej używać w trybie // synchronicznym lub asynchronicznym (z wywołaniem zwrotnym). var url = 'README'; function grabSync() { document.getElementById('target').innerHTML = HTML_AJAX.grab(url); } function grabAsync() { HTML_AJAX.grab(url,grabCallback); } function grabCallback(result) { document.getElementById('target').innerHTML = result; } // Operacja 'replace' może działać albo na adresie (tak jak grab), albo na // zdalnej metodzie. W tym drugim przypadku trzeba ustawić defaultServerUrl na adres // eksportujący wywoływaną metodę. Obecnie replace używa wyłącznie synchronicznych // wywołań AJAX - wywołania asynchroniczne mogą się pojawić w przyszłości. function replaceUrl() { HTML_AJAX.replace('target',url); } </script> <ul> <li><a href="javascript:clearTarget()">Czyszczenie pola docelowego</a></li> <li><a href="javascript:grabSync()">Przykład pobrania synchronicznego</a></li> <li><a href="javascript:grabAsync()">Przykład pobrania asynchronicznego </a></li> <li><a href="javascript:replaceUrl()">Zastąpienie zawartości treścią pobraną spod wskazanego adresu</a></li> </ul> <div style="white-space: pre; padding: 1em; margin: 1em; width: 600px; height: 300px; border: solid 2px black; overflow: auto;" id="target">Pole docelowe</div> </body> </html>

Strona klienta

Teraz zajmiemy się stroną kliencką naszej prostej aplikacji – Listing 2 przedstawia kod klienta. Od razu zwraca uwagę wyraźne rozdzielenie kodu HTML i PHP podczas pracy z JPSpan. Wystarczy tylko umieścić w ramach strony HTML poniższy, automatycznie generowany kod, odpowiedzialny za faktyczne połączenie HTML i PHP:
<script type='text/javascript' src='jpspan_server.php?client'> </script>

Przy odrobinie pracy nagłówkami pozwala to wygodnie obsłużyć składowanie znanych informacji po stronie klienta, co bardzo przydaje się na przykład przy dodawaniu do istniejącego serwisu pola autouzupełniania. Następnie tworzymy klasę JavaScriptu o nazwie hwCallback, zawierającą metody zwrotne zastępujące treść odpowiednich elementów <div> wartościami podanymi przez serwer z wykorzystaniem właściwości innerHTML. Pozostaje już tylko utworzyć zdalny obiekt:
var remoteHW=new helloworld(hwCallback);

Klasa helloworld jest wyeksportowaną klasą PHP, którą wcześniej utworzyliśmy po stronie serwera. Nazwa klasy zawiera wyłącznie małe litery, gdyż PHP4 nie rozróżnia wielkości liter w nazwach klas i funkcji. Reszta kodu z Listingu 2. to już tylko dodanie formularza HTML z odpowiednimi metodami obsługi – i już możemy się pobawić naszą pierwszą aplikacją stworzoną w technologii AJAX.

Listing 4. Klasa implementująca serwer podpowiedzi (plik suggest.class.php)
class suggest { function suggest() { require_once 'pear_array.php'; $this->strings = $aPear; } function getString($input='') { if ($input == '') return ''; $input = strtolower($input); $suggestStrings=array(); foreach ($this->strings as $string) { if (strpos(strtolower($string),$input) === 0) { $suggestStrings[] = $string; } } return $suggestStrings; } }

HTML_AJAX

Biblioteka HTML_AJAX daje znacznie większe możliwości niż JPSpan, ale dla uproszczenia przykładu skorzystamy z podobnej konfiguracji, co w przypadku poprzedniego przykładu: zewnętrzna strona serwera generuje kod pośredni JavaScriptu, który jest dołączany i faktycznie wykonywany na stronie HTML. HTML_ AJAX potrafi też generować cały kod pośrednika i serwera w jednym skrypcie, ale

52

www.phpsolmag.org

PHP Solutions Nr 1/2006

AJAX

Techniki

Ÿ

Ÿ

i JSON), a w przyszłości zostanie dodana obsługa dodatkowych, opcjonalnych części. Żądanie generowanej namiastki, zawierające ?stub=nazwaklasy w ciągu zapytania (można też podać wartość all). Żądanie AJAX, zawierające w ciągu zapytania ?c=nazwaklasy&m=nazwame tody.

in, Dispatcher, HttpClient, Request

Rysunek 3. Serwer podpowiedzi w akcji

nie polecałbym tej metody, gdyż tracimy wtedy możliwość lokalnego składowania wcześniej wygenerowanego kodu JavaScriptu. Instalacja pakietu HTML_AJAX jest bardzo prosta – wystarczy wykonać polecenie pear install HTML_AJAX-alpha. Jeśli na serwerze nie masz narzędzia PEAR, możesz po prostu pobrać pakiet http://pear.php.net/package/HTML_AJAX, rozpakować go i ręcznie umieścić w wybranym katalogu, wymienionym oczywiście w ramach parametru include_path w php.ini.
R

Serwer

Strona HTML_AJAX działająca na serwerze jest bardzo prosta – jej działanie polega na utworzeniu instancji serwera HTML_AJAX_ Server, zarejestrowaniu wszystkich eksportowanych klas (zwanych tu stubs, czyli namiastkami) i obsługiwaniu nadchodzących żądań. Istnieją trzy możliwe rodzaje żądań: Ÿ Żądanie klienckie, zawierające w ciągu zapytania. Zamiast all można też podać jedną z części składowych klienta. Obecnie obsługiwanych jest pięć części ( Ma?client=all

Pierwsze dwa rodzaje żądań można łączyć w jednym żądaniu, lecz trzeba pamiętać, że wiąże się to z pewnym kompromisem: mniej żądań to mniej połączeń z serwerem, ale z drugiej strony generowane namiastki zmieniają się częściej od danych klienckich, co może negatywnie wpłynąć na efektywność lokalnego składowania kodu. Ostrożnie należy też korzystać z żądań stub=all, gdyż namiastka dla, na przykład, dziesięciu klas może już być spora. W kolejnej wersji biblioteki HTML_AJAX pojawi się możliwość podawania wielu klas w ramach jednego żądania namiastki w postaci listy rozdzielanej przecinkami, a więc stub=test,test2. A

E

K

L

A

M

PHP Solutions Nr 1/2006

www.phpsolmag.org

53

Techniki
Tryb synchroniczny i asynchroniczny
z tablicy zawierającej możliwe hasła wyszukiwania, choć w rzeczywistej aplikacji byłyby one prawdopodobnie pobierane z bazy danych. Lista wyszukiwania wymaga tylko jednej metody getString(), która porównuje przekazany ciąg znaków z kolejnymi pozycjami tablicy. Pasujące elementy są następnie kopiowane do tablicy wyników, która jest ostatecznie zwracana. Teraz uruchamiamy serwer usługi (Listing 5). W tym przykładzie skorzystamy z klasy AutoServer, która rozszerza podstawową klasę serwera i dodaje metodę inicjalizacyjną dla każdej klasy. Pozwala to zarządzać eksportem kilku klas PHP za pomocą jednego serwera – wystarczy ustawić wartość zmiennej $initMethods na true i nadać metodom inicjalizacyjnym nazwy w postaci initNazwaKlasy, co dla naszej klasy oznacza utworzenie metody initSuggest(). Wykorzystanie klasy AutoServer w tak prostym przykładzie to oczywiście strzelanie z armaty do muchy, ale pokazuje ciekawe możliwości biblioteki HTML_AJAX, które mogą się bardzo przydać w większych projektach. I to by było na tyle po stronie PHP. Jeśli nasza metoda działa poprawnie i nie generuje żadnych błędów, to nie musimy jej więcej zmieniać i możemy się zająć implementacją po stronie klienta. Rzut oka na Listing 6 pokazuje, że zaczynamy od dołączenia JavaScriptu dla serwera:
<script type='text/javascript' src='auto_server.php?client= all&stub=suggest'></script>

Biblioteki JPSpan i HTML_AJAX obsługują pracę zarówno w trybie asynchronicznym (z wywołaniami zwrotnymi), jak i synchronicznym (z bezpośrednim zwracaniem wartości). Ogólnie lepiej jest stosować operacje asynchroniczne, gdyż wywołania synchroniczne mogą wstrzymywać pracę interfejsu użytkownika w oczekiwaniu na odpowiedź drugiej strony. Oczywiście niekiedy jest to zachowanie pożądane, ale przesyłając większe ilości danych trzeba wtedy zawsze pamiętać o wyświetleniu komunikatu typu proszę czekać. Wywołania synchroniczne są znacznie łatwiejsze do oprogramowania, ale mimo to lepiej oprzeć się pokusie bezkrytycznego ich stosowania. Żądania AJAX w sieci lokalnej najczęściej trwają nie dłużej niż 50 ms, ale w przypadku przesyłania danych przez Internet czas ten najczęściej wzrasta do ponad 250 ms. Oznacza to, że użytkownik nie będzie w stanie skorzystać z żadnego elementu strony czy nawet przełączyć się na inną zakładkę przeglądarki przez ćwierć, a często i pół sekundy.

Łatwa aktualizacja treści bez AJAX-a

HTML_AJAX pozwala też korzystać z podstawowych możliwości AJAX-a wyłącznie po stronie klienckiego JavaScriptu, dzięki czemu można bardzo szybko dodać do strony proste elementy używające AJAX-a lub włączyć HTML_AJAX do istniejącego środowiska. Typowym zastosowaniem jest aktualizacja zawartości elementu HTML za pomocą treści wygenerowanej przez inną stronę PHP, co daje elastyczność ramek <iframe> bez ich wad (patrz Listing 3). Po dołączeniu do strony niezbędnego kodu JavaScriptu można pobierać treść ze wskazanego adresu na serwerze w trybie synchronicznym za pomocą HTML_ AJAX.grab(url) lub w trybie asynchronicznym za pomocą HTML_AJAX.grab(url,grab Callback), gdzie argument grabCallback wskazuje funkcję zwrotną automatycznie wywoływaną przez HTML_AJAX po pobraniu treści. Można też wywołać HTML_A JAX.replace('target',url), by zastąpić

zawartość elementu o identyfikatorze podanym jako target treścią pobraną z adresu url. Ze względów bezpieczeństwa można w ten sposób pobierać treści wyłącznie z adresów na tym samym serwerze. Nie jest to jednak ograniczenie biblioteki HTML_AJAX, lecz ograniczenie przeglądarki, mające na celu zapobieganie atakom cross site scripting (XSS).

Przykład w stylu Google Suggest z HTML_AJAX

Pora na nieco bardziej zaawansowany przykład: pole podpowiedzi podobne do mechanizmu Google Suggest (http:// www.google.com/webhp?complete=1&hl= en), ale służące do wyszukiwania pakietów PEAR (patrz też Ramka Wzorzec AJAX Suggest). HTML_AJAX pozwala zarządzać interakcją klienta z serwerem w kilku prostych wierszach kodu. Jak widać na Listingu 4., klasa obsługująca stronę PHP jest dość prosta. Dla potrzeb przykładu korzystamy

Wzorzec AJAX Suggest

Podpowiadanie użytkownikom możliwych sposobów uzupełnienia tekstu wprowadzanego w polu tekstowym uatrakcyjnia stronę i ułatwia korzystanie z niej – wystarczy zaproponować kilka słów lub wyrażeń, które mogą pasować do danych wprowadzanych przez użytkownika. Do implementacji mechanizmu uzupełniania służy najczęściej połączenie pola tekstowego z listą rozwijaną wraz z synchronizacją tych elementów. Użytkownik może podać dowolny tekst, a bieżąca pozycja na liście będzie odpowiadać dotychczas wprowadzonemu ciągowi. Możliwe jest również wybranie elementu z listy, co spowoduje zastąpienie zawartości pola tekstowego wybranym hasłem. Implementacja tego wzorca wiąże się z reguły z wykorzystaniem zwykłego pola tekstowego i stworzeniem niewidocznej z początku warstwy (elementu <div>), w której będą umieszczane kolejne podpowiedzi. Do pola tekstowego trzeba dołączyć funkcję obsługi zdarzenia, kontrolującą zawartość pola w celu zapewnienia poprawnego wyświetlania pasujących podpowiedzi na liście. Nie będziemy oczywiście wysyłać żądania po każdym naciśnięciu klawisza – lepiej skorzystać z techniki dławienia zgłoszeń. W tym przypadku przeglądarka sprawdza co (na przykład) 350 milisekund, czy zawartość pola uległa zmianie – jeśli tak, to do serwera wysyłane jest odpowiednie żądanie. Pozwala to ograniczyć liczbę żądań (i tym samym zaoszczędzić nieco pasma), a przy okazji nie będzie przeszkadzać szybko piszącemu użytkownikowi. Serwer odpowiada na żądanie wysyłając uporządkowaną listę pasujących podpowiedzi, którą po stronie klienta odbiera funkcja zwrotna. Funkcja ta wprowadza w modelu dokumentu odpowiednie zmiany, by użytkownik mógł przejrzeć nowe podpowiedzi i ewentualnie wybrać jedną z nich. Z każdą pozycją listy skojarzona jest funkcja obsługi kliknięcia, odpowiadająca za aktualizację pola tekstowego treścią wybranej przez użytkownika pozycji.

Następnie tworzymy kod obsługi żądania w postaci metody do_suggest() oraz funkcję zwrotną do wyświetlania wyników, a na koniec tworzymy nową instancję zdalnej wyszukiwarki AJAX. Reszta kodu to po prostu formularz z jednym polem tekstowym i elementem <div> do wyświetlania wyników. Dodanie do pola tekstowego obsługi zdarzenia onkeyup="do_suggest(); return false;" powoduje wywołanie funkcji do_suggest() po każdym zwolnieniu klawisza (zdarzenie onkeypress byłoby obsługiwane zbyt wcześnie).

Jak to działa?

Każda zmiana wartości pola tekstowego powoduje wywołanie funkcji do_suggest(), która z kolei wywołuje metodę remoteSu ggest.getstring() javascriptowej klasy

54

www.phpsolmag.org

PHP Solutions Nr 1/2006

AJAX

Techniki

Listing 5. Serwer podpowiedzi w HTML_AJAX (plik auto_server.php)
session_start(); require_once 'HTML/AJAX/Server.php'; class AutoServer extends HTML_AJAX_Server { // Ustawienie tego znacznika jest konieczne, by korzystać z metod inicjalizacyjnych var $initMethods = true; // Metody inicjalizacyjne dla klasy podpowiedzi function initSuggest() { require_once 'suggest.class.php'; $suggest = new suggest(); $this->registerClass($suggest); }

} $server = new AutoServer(); $server->handleRequest();

Listing 6. Prosty klient podpowiedzi
<html> <head> <title>HTML_AJAX Suggest</title> <script type='text/javascript' src='auto_server.php?client=all&stub=suggest'> </script> <script> function do_suggest() { remoteSuggest.getstring(document.getElementById('string').value); } // Stworzenie tablicy asocjacyjnej do składowania metod zwrotnych var suggestCallback = { getstring: function(result) { document.getElementById('suggestions').innerHTML = result; } } // Utworzenie obiektu zdalnego. Jego nazwa jest odwzorowana małymi literami, // gdyż w nazwach klas i funkcji PHP4 wielkość liter nie jest rozpoznawana. // Rejestrując każdą klasę na serwerze można przywrócić rozróżnianie // wielkości liter. var remoteSuggest = new suggest(suggestCallback); </script> </head> <body> <div> Podaj nazwę pakietu PEAR: <input type="text" name="string" id="string" size="20" onkeyup=" do_suggest(); return false;"> <input type="button" name="check" value="Podpowiedz..." onclick="do_suggest(); return false;"> </div> <div id="suggestions">&nbsp;</div> </body> </html>

HTML_AJAX. Ta komunikuje się z serwerem, który odsyła tablicę pasujących podpowiedzi, przekazywaną następnie funkcji zwrotnej, która kończy cały proces dokonując niezbędnych zmian w strukturze dokumentu i wyświetlając podpowiedzi w ramach elementu <div>. W ten sposób mamy już działający, choć nie najpiękniejszy przykład. Po pierwsze, funkcja autouzupełniania przeglądarki w tym przypadku tylko przeszkadza. Możemy ją jednak łatwo wyłączyć dodając do pola tekstowego atrybut autocomplete="off". Po drugie, sposób wyświetlania podpowiedzi pozostawia bardzo wiele do życzenia. Spróbujmy więc ulepszyć funkcję zwrotną – Listing 7. przedstawia poprawiony kod. Po usunięciu poprzedniej zawartości elementu resultDiv, wyświetlającego wyniki, opakowujemy każdy wynik w osobny znacznik <span> dla uzyskania lepszej kontroli nad formatowaniem, po czym w pętli for dodajemy kolejne wyniki do warstwy resultDiv. Etap opakowania wykonujemy wywołując metody JavaScriptu document. createElement("span") i appendChild(). Dla zwiększenia czytelności można popracować nad stylem (Listing 8). Najważniejszy jest tu wpis powodujący wyświetlanie podpowiedzi jedna pod drugą zamiast w jednym wierszu:
#suggestions span { display: block; }

Listing 8. Style CSS dla klienta podpowiedzi
* { padding: 0; margin: 0; font-family : Arial, sans-serif; } #suggestions { max-height: 200px; width : 306px; border: 1px solid #000; overflow : auto; margin-top : -1px; float : left; } #string { width : 300px; font-size : 13px; padding-left : 4px; } #suggestions span { display: block; }

Listing 7. Ulepszona metoda zwrotna
var suggestCallback = { getstring: function(resultSet) { var resultDiv = document.getElementById('suggestions'); resultDiv.innerHTML = ''; for(var f=0; f<resultSet.length; ++f){ var result=document.createElement("span"); result.innerHTML = resultSet[f]; resultDiv.appendChild(result); } } }

PHP Solutions Nr 1/2006

www.phpsolmag.org

55

Techniki
Warstwa wyników powinna początkowo być ukryta, więc w pliku CSS podajemy dla niej atrybut display: none, który po otrzymaniu wyników przełączamy na wartość block w ramach metody zwrotnej:
resultDiv.style.display='block'; if (!resultSet) resultDiv.style.display='none';

Listing 9. Ostateczna wersja klienta podpowiedzi
<html> <head> <title>Podpowiedzi HTML_AJAX</title> <link rel="StyleSheet" type="text/css" href="suggest3.css" /> <script type='text/javascript' src='auto_server.php?client=all&stub=suggest'> </script><script> var string = ''; var oldstring = ''; var timeout= 1000; /* czas w ms między sprawdzeniami - dobrą wartością jest 250*/ function do_suggest() { string = document.getElementById('string').value; if (string != oldstring) { /* Przy pustym polu nie wysyłaj żądania... */ if (string) { remoteSuggest.getstring(string); } /* ... tylko ukryj warstwę */ else { document.getElementById('suggestions').style.display = 'none'; } oldstring = string; } window.setTimeout('do_suggest()', timeout); } // Stworzenie tablicy asocjacyjnej do składowania metod zwrotnych var suggestCallback = { getstring: function(resultSet) { var resultDiv = document.getElementById('suggestions'); resultDiv.innerHTML = ''; resultDiv.style.display = 'block'; if (!resultSet) resultDiv.style.display = 'none'; else{ for(var f=0; f<resultSet.length; ++f){ var result=document.createElement("span"); result.innerHTML = resultSet[f]; result.onmouseover = highlight; result.onmouseout = unHighlight; result.onmousedown = selectEntry; resultDiv.appendChild(result); } } } } // Utworzenie obiektu zdalnego var remoteSuggest = new suggest(suggestCallback); // Funkcje obsługi interakcji function highlight () { this.className = 'highlight'; } function unHighlight () { this.className = ''; } function selectEntry () { document.getElementById('string').value =this.innerHTML; } </script> </head> <body onload="do_suggest()"> <h1>HTML_AJAX Example: Suggest</h1> <p>Uwaga: czas między sprawdzeniami jest ustawiony na 1000ms dla celów demonstracyjnych. W praktyce lepiej korzystać z wartości rzędu 350ms.</p> <div id="error"></div> Podaj nazwę pakietu PEAR: <form method="get" id="suggest"> <input type="text" name="string" id="string" size="20" autocomplete="off"> <input type="button" name="check" value="Podpowiedz..." onkeyup="do_suggest(); return false;"> <div id="suggestions">&nbsp;</div> </form> </body> </html>

Dodatkowe sprawdzenie zapobiega wyświetleniu warstwy, gdy serwer nie zwróci żadnych podpowiedzi. Pora dodać do wyników nieco interakcji – na razie tylko widzimy podpowiedzi, ale nie możemy wybierać pozycji z listy. Efekt wybierania osiągniemy dodając obsługę zdarzeń do elementu <span> każdego wyniku:
result.onmouseover = highlight; result.onmouseout = unHighlight; result.onmousedown = selectEntry;

Spowoduje to dodanie funkcji JavaScript do każdego zdefiniowanego zdarzenia. Działanie funkcji highlight() i unHighlight() polega po prostu na zmianie klasy CSS elementu <span>:
function highlight (){ this.className='highlight'; }

Klasa CSS highlight wygląda tak:
.highlight { background-color: 0000ff; color: fff; }

Minimalna wersja naszej wyszukiwarki powinna obsługiwać zastąpienie zawartości pola tekstowego podpowiedzią klikniętą przez użytkownika. Pole ma identyfikator string, więc podmiana jego treści jest prosta:
function selectEntry () { document.getElementById('string') .value = this.innerHTML; }

Wartość pola tekstowego jest zastępowana zawartością danego elementu <span>, czyli jednym z wyników zwróconych przez serwer AJAX. Całość wygląda już znacznie lepiej (Rysunek 3). Przykład działa poprawnie,

56

www.phpsolmag.org

PHP Solutions Nr 1/2006

AJAX
ale do serwera wysyłanych jest zbyt wiele żądań – jeśli użytkownik wpisze coś bardzo szybko, a korzysta z powolnego łącza, to może dochodzić do wysyłania kolejnego żądania podpowiedzi przed otrzymaniem odpowiedzi na żądanie poprzednie. Spróbujmy jakoś temu zapobiec. Skorzystajmy z techniki zwanej dławieniem zgłoszeń (ang. submission throttling). W tym przypadku kliencki JavaScript będzie co jakiś czas (na przykład co 350 milisekund) sprawdzał, czy wartość pola tekstowego uległa zmianie – jeśli tak, to zostanie wysłane żądanie do serwera (patrz Listing 9). Dodatkowo sprawdzimy też, czy pole nie jest przypadkiem puste – w takiej sytuacji nie wysyłamy żądania i ukrywamy warstwę wyników. Jak widać, dodanie imponującej interakcji do formularzy i aplikacji nie jest wcale trudne. Nasz prosty przykład można oczywiście rozbudować, dodając na przykład możliwość przechodzenia po liście wyników klawiszami kursora czy też obsługę lokalnego składowania danych, pozwalającego oszczędzić sporo pasma.

Techniki

Komunikat ładowania

Uruchamiając przykłady z HTML_AJAX zauważysz, że przy każdym wywołaniu AJAX pojawia się czerwone okienko z komunikatem loading. Powiadomienie to jest automatycznie wyświetlane przez HTML_AJAX i jest po prostu warstwą o określonym identyfikatorze, tworzoną jeśli wcześniej nie istniała. Jeśli więc chcesz zmienić ten komunikat, wystarczy gdzieś w kodzie HTML umieścić na przykład taki fragment:
<div id="HTML_AJAX_LOADING" style= "background-color : blue; color : white; display : none; position : absolute; right : 50px; top : 50px;"> Ładowanie nowego napisu...</div>

Wyświetlania komunikatu nie da się w obecnej wersji wyłączyć po stronie serwera, ale w przyszłych wersjach HTML_AJAX taka możliwość już będzie. Aby zapobiec wyświetlaniu komunikatu trzeba nadpisać generującą go funkcję JavaScriptu:
HTML_AJAX.onOpen = function(){ // nic }

JavaScriptu, co wynika z tego, że JPSpan przechwytuje również te błędy i zgłasza je jako ostrzeżenia. Podczas pracy z HTML_AJAX można dodać własną funkcję obsługi błędu, która będzie podmieniać zawartość elementu <div> o identyfikatorze error:
HTML_AJAX.onError = function(e) { msg = "\nn"; for(var i in e) { msg += i + ':' + e[i] +"\n"; } document.getElementById('error'). innerHTML += msg; }

Śledzenie kodu AJAX

Podczas eksperymentów z AJAX-em zauważysz zapewne, że technika ta wymaga nowego podejścia do śledzenia kodu. Nie wystarczy już śledzić kodu PHP – trzeba jeszcze pilnować JavaScriptu i obsługiwanej przez AJAX-a komunikacji między klientem i serwerem. Na szczęście nie jest to trudne. Przede wszystkim, każdy moduł kodu należy testować osobno. Pracując w JavaScripcie dobrze jest stworzyć funkcję pomocniczą, na przykład prościutki odpowiednik print_r() z PHP:
function print_r(input) { var ret; ..for(var i in input) { ....ret += "["+i+"] = "+input[i]+"\n"; ..} ..alert(ret); }

Pozwoli to przechwytywać wszystkie błędy AJAX – najczęściej będą to zwyczajne błędy PHP, ale mogą się też pojawiać błędy 404, błędy wygaśnięcia i inne. Na koniec zalecałbym tworzenie aplikacji dla przeglądarki Firefox, a dopiero potem testowanie ich w Internet Explorerze. Firefox ma nieporównanie lepsze narzędzia programistyczne od IE, a w dodatku oferuje bardzo wiele przydatnych rozszerzeń.

treści, niż dodawać interakcję korzystającą z AJAX-a. Koniecznie trzeba też uwzględniać docelowych odbiorców – jeśli większość użytkowników z różnych względów ma wyłączoną obsługę JavaScriptu, to wprowadzenie AJAX-a raczej nie będzie dobrym pomysłem. Podobną rolę odgrywa skala – aplikacja wyposażona w AJAX-a znacznie lepiej sprawdzi się w przypadku niewielkich serwisów intranetowych (gdzie łatwo rozwiązać problemy konfiguracyjne i ujednolicić przeglądarki) niż w przypadku rozbudowanych, publicznie dostępnych witryn. Krótko mówiąc, wprowadzenie AJAX-a może dać dobre wyniki pod warunkiem, że nadrzędnym celem pozostanie uzyskanie jak najlepszych walorów użytkowych. n

O autorze
Joshua Eichorn tworzy serwisy PHP od siedmiu lat. Jest autorem phpDocumentor – wielokrotnie nagradzanego i szeroko używanego narzędzia dokumentującego kod PHP. Jest też szefem projektu HTML_AJAX, dostarczającego implementację techniki AJAX dla repozytorium PEAR. Obecnie pracuje jako starszy architekt oprogramowania w firmie Uversa Inc., gdzie tworzy dla klientów unikatowe rozwiązania. Technikę AJAX stosował jeszcze przed jej oficjalnym opracowaniem i popularyzacją. Mieszka w Phoenix w stanie Arizona (USA). Kontakt: josh@bluga.net Werner M. Krauß programuje w PHP od 1999 r. Gdy nie gra na gitarze, zajmuje się tworzeniem dokumentacji dla frameworka Seagull. Kontakt: werner.krauss@hallstatt.net

Podsumowanie

Możliwości obserwacji w bibliotece JPSpan pozwalają też rejestrować między innymi błędy i udane wywołania funkcji AJAX. W domyślnej konfiguracji serwera błędy PHP są przekazywane jako ostrzeżenia JavaScript. Niekiedy można też natrafić na ostrzeżenia wynikające z błędów

Wiedząc już czym jest AJAX i jak z niego korzystać, możesz rozważyć zastosowanie tej techniki w swoich stronach. Prawidłowe używanie AJAX-a pozwala często osiągnąć imponujące wyniki, ale nie znaczy to, że należy bezkrytycznie stosować tę technikę we wszystkich witrynach. Zawsze trzeba mieć na uwadze podstawowe przeznaczenie danego serwisu – być może w konkretnym przypadku lepiej byłoby dopracować nawigację lub prezentację

PHP Solutions Nr 1/2006

www.phpsolmag.org

57

Projekty

advAJAX,czyli praktyczne zastosowanie technologii AJAX
Łukasz Lach

Ciągłe przeładowywanie strony WWW przy każdej zmianie jej zawartości i żmudne czekanie na wyświetlenie kolejnej porcji danych jest zmorą każdego użytkownika aplikacji webowych i programisty PHP. Nie jesteśmy jednak skazani na te bolączki: wybawia nas od nich wkraczająca do świata PHP technologia AJAX. Dzięki niej ładujący się w nieskończoność pasek postępu przechodzi do lamusa.

O

W SIECI
1. http://advajax.anakin.us/ – projekt advAJAX 2. http://www.ajaxpatterns.org/ – strona o wzorcach AJAX-a 3. http://www.w3schools.com/ jsref/default.asp – dokumentacja języka JavaScript

technologii AJAX pisaliśmy już w tym numerze PHP Solutions, w artykule AJAX – wyjątkowo interaktywne i wydajne aplikacje WWW. Teraz zajmiemy się jej praktycznym wykorzystaniem i stworzymy kilka przykładów obrazujących jej możliwości. Jak już wiemy, główną zaletą AJAX-a jest to, że pozwala on na dokonywanie zmian w interfejsie użytkownika po stronie przeglądarki, bez potrzeby przeładowywania całej strony i ponownego wczytywania arkuszy CSS czy plików graficznych. Zyskuje na tym atrakcyjność serwisu, gdyż możemy uczynić go dynamicznym, jak i jego wydajność, ponieważ AJAX umożliwia nam przesyłanie jedynie niewielkiej porcji danych (zamiast całej strony WWW) pomiędzy serwerem, a klientem. To z kolei oznacza skrócenie czasu ich dostarczania i zmniejsza obciążenie serwera. Przesyłane za pomocą AJAX-a dane mogą mieć dowolny format, np. CSV albo XML.

Do stworzenia naszych przykładów wykorzystamy napisany przez autora tego artykułu obiekt języka JavaScript o nazwie advAJAX (ang. Advanced AJAX, http:// advajax.anakin.us). Obiekt advAJAX ułatwia korzystanie z opisywanej technologii i znacznie rozszerza jej możliwości, dodając m.in. pełną obsługę formularzy HTML, przedawnień czasu połączeń, kontrolę nad pamięcią tymczasową przeglądarki czy kontrolę nad kilkoma wywołaniami AJAX jednocześnie, za pomocą systemu

Powinieneś wiedzieć...

Powinieneś znać języki JavaScript i PHP. Pomocna w zrozumieniu technologii advAJAX będzie również lektura artykułu

AJAX – wyjątkowo interaktywne i wydajne aplikacje WWW.

Obiecujemy...

Po przeczytaniu artykułu będziesz wiedział, jak korzystać z obiektu advAJAX i z jego pomocą tworzyć aplikacje internetowe wykorzystujące AJAX-a.

58

www.phpsolmag.org

PHP Solutions Nr 1/2006

advAJAX

Projekty

grupowań. Dzięki niemu, pobranie dokumentu z serwera sprowadzi się do wywołania jednej funkcji, a my będziemy mieli pełną kontrolę nad odbieraniem danych, obsługą błędów czy dołączeniem funkcjonalności AJAX-a do formularzy. Przejdźmy więc do pierwszego przykładu.

System bezpiecznego logowania

Niemal każdy serwis tworzony w PHP zawiera panel administracyjny, za pomocą którego zarządzamy jego zawartością. Nieodłączną częścią panelu administracyjnego jest formularz logowania, który umożliwia dostęp wyłącznie uprawnionym użytkownikom. Potrzebujemy więc przesyłać nazwę użytkownika i hasło, co pociąga za sobą ryzyko przechwycenia tych danych przez osoby niepowołane. Stosowanie połączeń szyfrowanych SSH zmniejsza to zagrożenie. My postąpimy inaczej: wykorzystując AJAX-a stworzymy system bezpiecznego logowania, który nie używa SSH. Jak już wiemy, zastosowanie technologii AJAX uwalnia nas od konieczności przeładowywania całej strony. W przypadku systemu logowania, potrzebujemy jedynie wysłać na serwer kilkadziesiąt bajtów, które składają się na nazwę użytkownika i hasło. Odpowiedź serwera również będzie krótka: zamiast całego dokumentu HTML, otrzymamy jeden bajt. Jest to idealny przykład, pokazujący możliwości optymalizacyjne AJAX-a. Podstawą naszej pracy jest formularz logowania, podobny do przedstawionego na Listingu 1. W nagłówku widocznego tam dokumentu HTML dołączamy trzy skrypty w języku JavaScript. Pierwszy, o nazwie advajax.js zawiera obiekt advAJAX. W drugim, md5.js, umieszczone zostały funkcje tworzące hash MD5, które pozwolą nam na zakodowanie hasła przed wysłaniem. Ostatni skrypt, 1.js, zawiera funkcję updateObjects(), uruchamianą po załadowaniu dokumentu. Umieszczony na stronie formularz, któremu nadaliśmy unikalną nazwę loginForm, przesyła dane do pliku 1.php. Z pozoru jest to typowa strona logowania, jednak po wywołaniu wspomnianej funkcji updateObjects() wszystko się zmienia. Funkcja ta wywołuje wewnętrzną metodę obiektu advAJAX (patrz Listing 2) o nazwie assign(), która powoduje włączenie funkcjonalności AJAX-a do for-

Rysunek 1. System bezpiecznego logowania

mularza. Jej pierwszym parametrem jest obiekt formularza, który ma zostać zmodyfikowany – w naszym przypadku jest

to loginForm. Drugim jest nieposiadający nazwy obiekt definiujący wywołanie – zestaw parametrów obiektu advAJAX.

Rysunek 2. Stronicowanie danych

PHP Solutions Nr 1/2006

www.phpsolmag.org

59

Projekty

advAJAX
Przyjrzyjmy się elementom wspomnianego już obiektu, będącego drugim parametrem metody assign(). Pierwszym z nich jest funkcja onInitialization() – pozwala ona określić akcję, która ma zostać wykonana przed rozpoczęciem głównych procedur pobierania dokumentu. Dostęp do obiektu możliwy jest poprzez jedyny parametr tej funkcji o nazwie obj. W naszym skrypcie, funkcja onInitialization() tworzy skrót (hasz) hasła algorytmem MD5 oraz informuje użytkownika, że pobierany jest wynik logowania, umieszczając opis proszę czekać na przycisku służącym do wysyłania formularza. Również w tej funkcji odczytujemy hasło. Jest ono przechowywane przez pole password obiektu parameters (obj.parameters), z którego korzystamy podobnie, jak z tablic asocjacyjnych w PHP. Następnym elementem jest onComplete(). Powinien on wskazywać funkcję, która zostanie wywołana po zakończeniu pobierania dokumentu. W tym miejscu bardzo istotne jest to, że wewnątrz tej funkcji nie wiemy, czy wywołanie zakończyło się powodzeniem. Funkcja, uruchamiana po udanym wywołaniu, znajduje się pod elementem onSuccess(). Sprawdzamy w niej, czy logowanie zakończyło się sukcesem: jeśli tak, to wartością zmiennej obj.responseText powinien być znak 1 (obiecany jeden bajt). W takim wypadku, przekierowujemy użytkownika na stronę przeznaczoną dla administratora, a w innym wyświetlamy odpowiedni komunikat i proces się powtarza. Warto wspomnieć, że ciasteczka są obsługiwane przez advAJAX, tak więc można bez problemu stworzyć sesję po stronie serwera i będzie ona dostępna tak samo, jak w sytuacji niekorzystania z AJAX-a. Ostatni element o nazwie onError także wskazuje na funkcję, która ma zostać wywołana w przypadku problemu z pobraniem dokumentu (nie z połączeniem), np. kiedy plik nie istnieje i serwer zwróci kod 404 Not Found. Jak widzimy, cały proces dołączenia możliwości AJAX-a do naszego formularza sprowadził się do wykorzystania jednej metody. W efekcie otrzymaliśmy przyjaźniejszy dla użytkownika i bardziej optymalny i – co najważniejsze – bezpieczny system logowania. Przedstawiamy go na Rysunku 1.

Należy w tym miejscu wspomnieć, że nie musimy nigdzie podawać adresu URL dokumentu, który ma zostać wywołany, jak również metody jego wysłania (POST lub GET), ponieważ są one automatycznie pobierane z atrybutów action i method znacznika form. Omawiany obiekt zadba także o to, żeby zablokować wszystkie pola formularza aż do zakończenia zapytania, aby użytkownik nie mógł dokonywać żadnych zmian w trakcie wykonywania i pobierania wyniku. Listing 1. Formularz logowania

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/ DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pl" lang="pl"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script type="text/javascript" src="advajax.js"></script> <script type="text/javascript" src="md5.js"></script> <script type="text/javascript" src="1.js"></script> </head> <body onload="updateObjects()"> <form method="post" action="1.php" id="loginForm"> <label for="username">Nazwa użytkownika:</label> <input type="text" name="username" id="username" /> <br /> <label for="password">Hasło:</label> <input type="password" name="password" id="password" /> <br /> <input type="submit" value="OK" id="submitBtn" /> </form> </body> </html>

Listing 2. Dołączenie funkcjonalności AJAX-a do formularza HTML
function updateObjects() { advAJAX.assign(document.getElementById("loginForm"), { onInitialization : function(obj) { obj.parameters["password"] = hex_md5(obj.parameters["password"]); document.getElementById("submitBtn").value = "Proszę czekać..."; }, onComplete : function() { document.getElementById("submitBtn").value = "OK"; }, onSuccess : function(obj) { if (obj.responseText == "1") document.location = "/admin"; else { alert("Nieprawidłowa nazwa użytkownika lub hasło."); document.getElementById("password").value = ""; window.setTimeout("document.getElementById('password').focus();", 100); } }, onError : function(obj) { alert("Nie można nawiązać połączenia z serwerem, spróbuj później."); }

Stronicowanie danych

});

Jeśli w naszym serwisie umieszczamy większe ilości danych, to koniecznością staje się ich wyświetlanie z podziałem na strony zawierające po kilka lub kilkanaście rekordów. W tradycyjnej aplikacji webowej, każdorazowa zmiana strony tej listy pociąga za sobą przeładowanie całego dokumentu HTML, mimo, że modyfikacji ulega tylko jego główna

60

www.phpsolmag.org

PHP Solutions Nr 1/2006

advAJAX
część, a nagłówek czy menu nawigacyjne pozostają niezmienione. Posługując się AJAX-em unikniemy tej uciążliwej konieczności – pozwala on wszak modyfikować zawartość otwartej w przeglądarce strony WWW. Jako przykład jego wykorzystania, stworzymy prostą książkę telefoniczną z podziałem na strony zawierające po 10 rekordów. Będzie ona wyświetlała odczytane z serwera i przesłane w postaci dokumentu XML imiona, nazwiska i numery telefonów. Dla uproszczenia, w naszym przykładzie nie implementujemy komunikacji z serwerem. Zacznijmy od tabeli, w której będziemy umieszczać rekordy pobrane z serwera. Jej kod źródłowy przedstawiamy na Listingu 3. W nagłówku znajdują się nazwy kolumn, otoczone hiperłączami wskazującymi na funkcję changeSort(), która zmienia sposób sortowania danych. Na początku, tabela jest pusta: zostanie wypełniona zawartością pobraną poprzez wywołanie AJAX. W stopce umieściliśmy pole, które będzie zawierało dane o aktualnie przeglądanych danych oraz przyciski nawigacyjne. Znacznik tbody otrzymał ponadto unikalny identyfikator dataTable, do którego będziemy się odwoływać podczas wpisywania danych. Spójrzmy teraz na kod źródłowy przedstawiony na Listingu 4. Metodą wywoływaną podczas zakończenia ładowania strony oraz przy zmianie strony jest getRecords. Jak w poprzednim przykładzie, tak i teraz wywołuje on tylko jedną, wewnętrzną metodę obiektu advAJAX. Tym razem jest to metoda advAJAX.get(), która wykonuje wywołanie typu GET na ustalony w parametrach adres URL. advAJAX pozwala korzystać zarówno z POST, jak i GET, którym odpowiadają nazwy metod obiektu advAJAX. Opisywana metoda przyjmuje jeden parametr, którym jest lista parametrów obiektu. Element url jest wymagany, a jego wartością może być zarówno pełny adres URL, jak i nazwa pliku. W naszym skrypcie wywołujemy skrypt 2.php z parametrami p (któremu przypisana jest wartość zmiennej globalnej currentPage, czyli numer pobieranej strony), oraz s, wskazująca na kolumnę i tryb sortowania pobieranych rekordów. Następnym elementem jest znany nam już onInitialization(), którego wywołanie

Projekty

Rysunek 3. Prosty edytor tekstu na stronie WWW

Listing 3. Tabela książki telefonicznej
<table> <thead> <tr> <td>ID</td> <td><a id="nameSort" href="javascript:changeSort('name')" title="Sortowanie po imieniu">Imię</a></td> <td><a id="surnameSort" href="javascript:changeSort('surname')" title="Sortowanie po nazwisku">Nazwisko</a></td> <td><a id="telephoneSort" href="javascript:changeSort('telephone')" title="Sortowanie po numerze telefonu">Numer telefonu</a></td> </tr> </thead> <tbody id="dataTable"></tbody> <tfoot> <tr> <td colspan="4"> <span id="dataStats" style="float: left; margin-top: 2px"></span> <span style="float: right"> <a id="btnFirst" href="javascript:changePage(-currentPage)" title="Pierwsza strona">&laquo;&laquo;</a> <a id="btnPrev" href="javascript:changePage(-1)" title="Poprzednia strona">&laquo;</a> <a id="btnNext" href="javascript:changePage(1)" title="Następna strona ">&raquo;</a> <a id="btnLast" href="javascript:changePage(maxPage-currentPage-1)" title="Ostatnia strona">&raquo;&raquo;</a> </span> </td> </tr> </tfoot> </table>

PHP Solutions Nr 1/2006

www.phpsolmag.org

61

Projekty

advAJAX
będącym instancją obiektu XMLDocument, której kod również znajduje się na Listingu 4. Aby dostać się do danych zawartych w pobranym dokumencie XML, korzystamy z wbudowanych metod DOM (ang. Document Object Model). Całość jest już w pełni funkcjonalna, jednak nic nie szkodzi na przeszkodzie, aby przedstawiony przykład rozbudować. Można dodać jeszcze jedną kolumnę, w której umieścimy hiperłącza pozwalające na usunięcie czy edycję wybranego rekordu. Możliwości jest wiele, gdyż potencjał AJAX-a jest ogromny. Ostateczny wynik zależy zaś wyłącznie od naszych potrzeb.

powoduje ukrycie przycisków nawigacyjnych i wyświetlenie informacji, że trwa pobieranie żądanych danych. Kiedy nastąpi wywołanie funkcji zapisanej pod onSuccess() powoduje uruchomienie metody parseRecords(), z parametrem

Listing 4. Główny skrypt dynamicznej tabeli
var maxPage; function $(id) { return document.getElementById(id); } // Funkcja konwertująca dokument XML na kolejne wiersze tabeli function parseRecords(xml) { // Zapisanie danych bieżącej strony with (xml.getElementsByTagName("records").item(0)) { page = getAttribute("page")*1; maxPerPage = getAttribute("max_per_page"); startId = maxPerPage*page+1; total = getAttribute("total")*1; maxPage = Math.ceil(total/maxPerPage); // ...wpisanie danych do stopki tabeli } // Wpisanie danych rekordów do tabeli d = $("dataTable"); for (i = d.rows.length-1; i >= 0; i--) d.deleteRow(i); record = xml.getElementsByTagName("record"); result = ""; for (i = 0; i < record.length; i++) { tr = document.createElement("tr"); td = document.createElement("td"); td.innerHTML = startId + i; tr.appendChild(td); for (j = 0; j < 3; j++) { td = document.createElement("td"); td.innerHTML = record[i].childNodes[j].childNodes[0].nodeValue; tr.appendChild(td); } d.appendChild(tr); } return result;

Szybka edycja danych

}

// Główna funkcja wywołująca skrypt PHP i pobierająca dokument XML function getRecords() { advAJAX.get({ url : "2.php?p="+currentPage+"&s="+currentSort+"%20"+currentSortOrder, onInitialization : function() { $("dataStats").innerHTML = 'Pobieranie danych...'; $("btnPrev").style.visibility = $("btnNext").style.visibility = $("btnFirst").style.visibility = $("btnLast").style.visibility = "hidden"; }, onSuccess : function(obj) { parseRecords(obj.responseXML); } }, onError : function(obj) { alert("Nie można nawiązać połączenia z serwerem, spróbuj później."); });

}

Ostatnim, równie przydatnym i efektywnym przykładem będzie stworzenie edytora pozwalającego na zmianę treści dokumentu bez przeładowywania strony, na której jest on wyświetlany. Jego interfejs oprzemy na warstwach. Edytor będzie uruchamiany po dwukrotnym kliknięciu na wybraną warstwę. Wówczas utworzona zostanie warstwa zawierająca obiekt pola tekstowego (textarea), do którego wpisana zostanie treść edytowanego tekstu oraz dwa przyciski, służące do zapisywania i anulowania wprowadzanych zmian. Oczywiście edytor tego typu powinien być dostępny jedynie, gdy zalogujemy się jako administrator, dlatego też postaramy się maksymalnie ułatwić dołączenie jego kodu do istniejącej już strony. W tym celu stworzymy funkcję o nazwie assignEditor(), której parametrem jest unikalna nazwa identyfikująca warstwę, wpisana w atrybucie id dowolnego znacznika HTML. Pełny kod omawianej funkcji, jak i całego skryptu edytora znajduje się na Listingu 5. Istotną funkcją, którą stworzymy jest wywoływana w celu zapisania zmodyfikowanych danych na serwerze saveData(). Tym razem korzystamy z metody advAJAX.post(), ponieważ przesyłane dane mogą mieć większy rozmiar, niż pozwala na to metoda GET. Również ta metoda przyjmuje jeden argument – parametry wywołania obiektu advAJAX. Pierwszym elementem jest url, wskazujący na skrypt 3.php, który zapisze wyedytowane dane po stronie serwera. Następnie mamy element parameters, omawiany już przy skrypcie bezpiecznego logowania. Tym razem przekazujemy unikalny identyfikator edytowanego tekstu oraz jego nową treść. Dostęp do tych zmiennych uzyskamy poprzez tablicę $_POST ($_POST['id'] oraz $_POST['value']) w skrypcie PHP. Funkcja zapisana

62

www.phpsolmag.org

PHP Solutions Nr 1/2006

advAJAX

Projekty

Listing 5. Kod źródłowy edytora
function assignEditor(objectId) { // Stworzenie edytora w przypadku podwójnego kliknięcia document.getElementById(objectId).ondblclick = function() { runEditor(this) } } function saveData(objectId) { advAJAX.post({ url : "3.php", parameters : { id : objectId, value : $(objectId + "_editor_ta").value }, onInitialization : function() { $(objectId+"_editor_ta").disabled=$(objectId+"_editor_save").disabled= $(objectId + "_editor_cancel").disabled = "disabled"; }, onError : function() { alert("Nie można nawiązać połączenia z serwerem, spróbuj później."); $(objectId + "_editor_ta").removeAttribute("disabled"); $(objectId + "_editor_save").removeAttribute("disabled"); $(objectId + "_editor_cancel").removeAttribute("disabled"); }, onSuccess : function(obj) { if (obj.responseText == "1") $(objectId).innerHTML = $(objectId + "_editor_ta").value; removeElement(objectId + "_editor"); } }); } function runEditor(obj) { // Pobranie współrzędnych położenia edytowanej warstwy l = t = 0; obj2 = obj; while (obj2.parentNode) { l += obj2.offsetLeft; t += obj2.offsetTop; obj2 = obj2.parentNode; } // Stworzenie warstwy edytora i jego komponentów editor = document.createElement("div"); h = obj.offsetHeight >= 70 ? obj.offsetHeight : 70; with (editor.style) { position = "absolute"; zIndex = "100"; top = t + "px"; left = l + "px"; width = obj.offsetWidth + "px"; height = h + "px"; backgroundColor = "#ffffff"; } editor.id = obj.id + "_editor"; editor.innerHTML = "<textarea id='" + obj.id + "_editor_ta'></textarea> <br /><button id='" + obj.id + "_editor_save' onclick=###BOT_TEXT###quot;saveData('" + obj.id + "')###BOT_TEXT###quot;>Zapisz</button>&nbsp;<button id='" + obj.id + "_editor_cancel' onclick=###BOT_TEXT###quot;removeElement('" + obj.id + "_editor')"> Anuluj</button>"; document.body.appendChild(editor); ta = $(obj.id + "_editor_ta"); with (ta.style) { width = "100%"; height = (h - 25) + "px"; } ta.value = obj.innerHTML;

pod onInitialization() spowoduje wyłączenie kontrolek edytora, aby użytkownik nie mógł modyfikować danych podczas ich przesyłania na serwer. W przypadku błędu, obiekt advAJAX korzysta z funkcji umieszczonej pod nazwą onError(), która w tym przypadku wyświetla komunikat o błędzie i odblokowuje kontrolki, zezwalając jednocześnie użytkownikowi na podjęcie kolejnej próby zapisania danych. W przypadku powodzenia, wywołana zostaje funkcja zapisana pod onSuccess(), a skrypt PHP powinien zwrócić znak 1 (jak widzimy, znowu jeden bajt zamiast całego dokumentu HTML po przeładowaniu!). Następnie edytor zostaje usunięty i od tej pory każdy użytkownik odwiedzający tę stronę zobaczy już nową, zapisaną przed chwilą, treść warstwy. Nic nie stoi na przeszkodzie, aby wykorzystać pole typu input, jeśli mamy do czynienia z małą, zawierającą się w jednej linijce porcją danych. Możemy również stworzyć edytor graficzny, pozwalający dodatkowo na zmianę stylu czcionki czy dodawanie hyperlinków i plików graficznych – wszystko znowu zależy od potrzeb, a przedstawiony skrypt jest idealną podstawą do dalszej rozbudowy.

Podsumowanie

AJAX nie jest nową technologią, ale dopiero teraz został w pełni zaimplementowany w najpopularniejszych przeglądarkach internetowych, dzięki czemu krąg jego użytkowników rośnie. Zastosowanie obiektu advAJAX pozwala natomiast na proste i efektywne wykorzystanie jego możliwości, co stanowi klucz do tworzenia prawdziwie interaktywnych aplikacji internetowych. n

O autorze
Łukasz Lach jest studentem informatyki. Od kilku lat zajmuje się programowaniem serwisów internetowych z wykorzystaniem technologii takich, jak XHTML, PHP czy JavaScript. Jest autorem wielu publikacji dotyczących szeroko pojętego programowania stron internetowych. Obecnie pracuje nad projektem advAJAX. Kontakt: anakin@php5.pl

}

PHP Solutions Nr 1/2006

www.phpsolmag.org

63

Projekty

Nowe możliwości PHP-GTK2
Pablo Dall'Oglio

Rozszerzenie PHP-GTK1 zapoczątkowało nowy sposób myślenia o PHP, otwierając przed językiem przeznaczonym do tworzenia aplikacji sieciowych świat wyposażonych w graficzny interfejs użytkownika (GUI) programów klienckich. Jednakże dopiero pojawienie się PHP-GTK2 może dać początek prawdziwej rewolucji...

P

HP-GTK2 umożliwia połączenie funkcjonalności PHP5 i biblioteki Gtk-2.6. W artykule przyjrzymy się bliżej niezwykłym możliwościom tej technologii na przykładzie aplikacji umożliwiającej proste zarządzanie przedmiotami (produktami) w sklepie. Będziemy korzystać zarówno z nowości, które pojawiły się w PHP-GTK2, jak i tych, które oferuje PHP5 (np. nowy model obiektowy czy obsługa wyjątków). Dane będziemy składować w bazie SQL.

Tak więc, w pierwszym menu będziemy mogli dodawać nowe produkty (Dodaj), edytować już istniejące (Edytuj), wyczyścić całą bazę (Wyczyść), bądź zakończyć działanie programu (Wyjdź). Korzystając z drugiego otworzymy prosty edytor tekstu, potencjalnie przydatny np. do robienia notatek na temat naszych zapasów. Trzecie natomiast będzie zawierało jedną opcję: Pomoc, wyświetlającą okno dialogowe O programie. Będziemy również potrzebowali okien dialogowych m.in. dla opcji Wyczyść

W SIECI
1. http://gtk.php.net – strona domowa projektu PHP-GTK 2. http://www.agata.org.br – Agata Project – CRM/ERP korzystający z PHP-GTK 3. http:///tulip.solis.coop.br –Tulip – edytor dla programisty PHP oparty na PHP-GTK

Idea

Zastanówmy się, czego potrzebujemy, aby stworzyć nasz przykładowy program. Podstawowym elementem aplikacji GUI jest okno, w którym znajdują się kontrolki (widgety). W nim umieścimy trzy rozwijane menu: Plik, Edycja oraz Pomoc. Poszczególne pozycje w menu będą opisane za pomocą tekstu oraz towarzyszących mu odpowiednich ikon.

Powinieneś wiedzieć...

Powinieneś znać PHP5, a w szczególności nowy model obiektowy oraz wyjątki. Przydatna będzie również ogólna wiedza o graficznych interfejsach użytkownika.

Obiecujemy...

Po przeczytaniu artykułu będziesz wiedział, jak napisać przykładową aplikację wykorzystującą nowe możliwości PHPGTK2.

64

www.phpsolmag.org

PHP Solutions Nr 1/2006

PHP-GTK2

Projekty

(pytanie oraz informacja), Dodaj i Edytuj (formularz umożliwiający dodawanie nowych i edycję istniejących produktów), a także dialogu plików i niezależnego okna dla naszego edytora tekstu.

Zabieramy się do pracy

Nasze zadanie zaczniemy od napisania głównej klasy Application, którą przedstawiamy na Listingach 1 i 2. Najpierw stworzy ona obiekt klasy GtkWindow, który będzie głównym oknem aplikacji i ustawi jego podstawowe parametry (rozmiar, położenie i tytuł). Następnie stworzymy kontener porządkujący pozycjonowanie naszych widgetów: wykorzystamy w tym celu klasę GtkVBox, tworząc jej instancję o nazwie $vbox. Przejdźmy do menu rozwijanych. Zaczniemy od utworzenia menu głównego, zakładając obiekt $MenuBar klasy GtkMenuBar. Następnie za pomocą klasy GtkMenuItem stworzymy poszczególne sekcje menu o nazwach Plik, Edycja oraz Pomoc. Będą to osobne obiekty, których póki co nie dodamy jeszcze do menu.

Rysunek 1. Menu naszej aplikacji Druga, Dodaj, pokaże użytkownikowi dialog zawierający formularz pozwalający dodać nowy produkt, podczas gdy trzecia, Edytuj, wyświetli listę produktów, pozwalając na edycję wybranej pozycji. Wreszcie, kliknięcie na pozycji Wyjdź spowoduje zakończenie działania aplikacji. W menu Edycja umieścimy tylko jedną opcję, Edytor – będzie ona uruchamiała wspomniany już edytor tekstu. W menu Pomoc znajdzie się również jedna opcja, Pomoc, której zadaniem będzie wyświetlenie okna dialogowego O programie. Wszystkie menu przedstawiamy na Rysunku 1. Kolejnym punktem programu jest połączenie sygnałów wysyłanych przez elementy menu w chwili wystąpienia zdarzenia clicked z metodami callbackowymi wykonującymi odpowiednie operacje. Metody te nosić będą nazwy: onClear(), onAdd(), onList(), onEdit() i onHelp(). Powiązania tworzymy natomiast wywołując dla każdego elementu metodę connect(). Dodamy teraz elementy do odpowiednich menu. Ostatnią operacją, jaką musimy przeprowadzić na menu rozwijanych jest dodanie ich do paska głównego menu. Czynność ta przebiegnie dwuetapowo: najpierw dla każdej pozycji w pasku menu zastosujemy metodę set_submenu(), która połączy tę pozycję z odpowiadającym jej podmenu, a następnie dodamy te trzy pozycje do głównego paska ($MenuBar). Pozostałymi operacjami w konstruktorze są: wstawienie kontenera $vbox w okno, dodanie do niego widgetów oraz wyświetlenie całej zawartości okna za pomocą metody $window->show_all().

Menu główne

Kolejnym krokiem będzie stworzenie odpowiednich menu, otwieranych kliknięciem na wymienione sekcje. W tym celu użyjemy klasy GtkMenu, tworząc jej instancję o nazwie $SubMenuFile... ale zaraz, chcieliśmy, by opcje w tych menu posiadały ikony! Na szczęście nie musimy tworzyć ich sami, gdyż PHP-GTK2 oferuje nam Obrazy Standardowe (Stock Images), czyli zbiór podstawowych, często używanych ikon dla przycisków, menu, list itd. Przedstawiają one operacje takie, jak zapis, otwieranie, zamykanie, kasowanie, dodawanie, czyszczenie, wychodzenie z programu, opcje TAK i NIE, itd. Nazwy ikon przestrzegają przejrzystej konwencji, co znacząco ułatwia korzystanie z nich. Każda pozycja należąca do GtkMenu będzie obiektem klasy GtkImageMenuItem. Ikonę będziemy przekazywać przez argument konstruktora tej klasy. Jeżeli nie podamy nazwy ikony, to pozycja nie będzie posiadała obrazka. W menu Plik dodajemy pozycje o nazwach: Wyczyść, Dodaj, Edytuj oraz Wyjdź. Kliknięcie na pierwszej spowoduje uruchomienie bazy, a następnie usunięcie tabeli produktów i ponowne jej stworzenie.

Metody callbackowe i dialogi wiadomości

Najwyższy czas rozpocząć tworzenie metod callbackowych dla poszczególnych elementów menu. Zaczniemy od onClear(). W pierwszej kolejności metoda ta sprawdzi, czy baza, którą chcemy wyczyścić, istnieje. Potem poprosi nas o potwierdzenie skasowania wszystkich zawartych w bazie rekordów. W tym celu użyjemy okna dialogowego GtkMessageDialog. Znajduje ono zastosowanie

Rysunek 2. Dialog pozwalający nam zdecydować, czy chcemy zrestartować bazę danych

Rysunek 3. Wiadomość, że baza została wyczyszczona

PHP Solutions Nr 1/2006

www.phpsolmag.org

65

Projekty

PHP-GTK2
w bardzo wielu sytuacjach, m.in. przy wyświetlaniu ostrzeżeń, komunikatów o błędach, dialogów potwierdzenia, okien do wprowadzania danych, itd. Użyjemy tego okna w trybie MODAL, co oznacza, że dopóki nie zostanie ono zamknięte, będzie cały czas widoczne na ekranie, a wykonywanie innych operacji w ramach aplikacji nie będzie możliwe. Chcemy, aby w obrębie dialogu widniał znak zapytania – żaden problem, użyjemy tych samych standardowych ikon, co wcześniej. Zastosujemy również standardowe przyciski YES i NO. Całość przedstawiamy na Rysunku 2. Po wyświetleniu okna, aplikacja będzie czekać na reakcję użytkownika. Jeżeli zezwoli on na skasowanie danych, nawiążemy połączenie z naszą bazą SQLite, a następnie usuniemy tabelę produktów i ponownie ją utworzymy. O poprawnym wyczyszczeniu bazy informuje użytkownika kolejne okno dialogowe, które pokazujemy na Rysunku 3.

Listing 1. Główny interfejs: główne okno naszej aplikacji; kod zawarty w pliku product.php
<?php // Klasa Application – zawiera w sobie główny interfejs class Application{ private $window; function __construct(){ // tworzy nowe okno i ustawia jego parametry $this->window = new GtkWindow; ... $vbox = new GtkVBox; // tworzy listwę menu $MenuBar = new GtkMenuBar; // opcje menu $MenuFile = new GtkMenuItem('_Plik'); ... // podmenu Plik, z elementami standardowymi $SubMenuFile = new GtkMenu; $ItemFile1= new GtkImageMenuItem(GTK::STOCK_CLEAR); ... $ItemFile4= new GtkMenuItem; $ItemFile5= new GtkImageMenuItem(GTK::STOCK_QUIT); // połącz opcje menu z metodami $ItemFile1->connect('activate', array($this, 'onClear')); ... $ItemFile5->connect('activate', array($this, 'onQuit')); // dodaj obiekty do podmenu $SubMenuFile->append($ItemFile1); ... // podmenu Edycja $SubMenuEdit= new GtkMenu; $ItemEdit1= new GtkImageMenuItem(GTK::STOCK_EDIT); $ItemEdit1->connect('activate', array($this, 'onEdit')); $SubMenuEdit->append($ItemEdit1); // podmenu Pomoc ... $MenuFile->set_submenu($SubMenuFile); ... $MenuEdit->set_submenu($SubMenuEdit); ... $this->window->add($vbox); $vbox->pack_start($MenuBar, false, false); $this->window->show_all(); } // Metoda onClear – tworzy struktury w bazie danych function onClear(){ if (file_exists('data.db')){ $dialog = new GtkMessageDialog(null, Gtk::DIALOG_MODAL, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO, 'Czy chcesz dokonać ponownej inicjalizacji bazy ?'); $response = $dialog->run(); $dialog->destroy(); if ($response == Gtk::RESPONSE_YES){ // usuń tabelę products $conn = sqlite_open('data.db'); ... } else if ($response == Gtk::RESPONSE_NO){ return; } } // stwórz tabelę products $conn = sqlite_open('data.db'); $sql = 'CREATE TABLE products (code,description,' . 'unit,amount,cost,price)'; sqlite_query($conn, $sql);

Dodawanie nowych produktów: formularz produktów

Kolejną metodą odwoławczą jest onAdd(), która pozwala na dodanie nowego produktu. Wyświetla ona na ekranie dialog widoczny na Rysunku 4. Właściwie jedynym zadaniem tej metody jest wykorzystanie klasy ProductNew, która znajduje się w pliku ProductNew.class.php (patrz Listing 3). Pełne opisanie tej klasy zajęłoby zbyt wiele miejsca i mogłoby być nużące dla czytelnika. Skoncentrujemy się więc jedynie na najważniejszych zagadnieniach z nią związanych. Po pierwsze, w konstruktorze ProductNew tworzymy taki sam obiekt klasy GtkWindow, z jakim mieliśmy do czynienia wcześniej. Po drugie, do wyświetlania opisów wykorzystamy etykiety (GtkLabel). Najbardziej interesującym aspektem ich użycia jest możliwość formatowania ich zawartości. W PHP-GTK2 jest to dużo łatwiejsze i bardziej elastyczne niż w PHPGTK1, gdzie trzeba było wiele wysiłku, by nadać tekstowi pożądany przez nas wygląd (nawet w przypadku tak prostych efektów, jak pogrubienie, kursywa, podkreślenie bądź kolory). PHP-GTK2 wykorzystuje Pango, opensourcowy framework GTK zajmujący się wszystkimi szczegółami związanymi z układem i renderowaniem. Pango umożliwia formatowanie tekstu do wyświetlenia za pomocą wywodzącego się z SGML języka znaczników.

66

www.phpsolmag.org

PHP Solutions Nr 1/2006

PHP-GTK2

Projekty

Rysunek 4. Formularz do dodawania nowych produktów

Listing 2. Główny interfejs: ciąg dalszy kodu głównego okna naszej aplikacji
// poinformuj użytkownika o sukcesie $dialog = new GtkMessageDialog(null, Gtk::DIALOG_MODAL, Gtk::MESSAGE_INFO, Gtk::BUTTONS_OK, 'Baza wyczyszczona'); $response = $dialog->run(); $dialog->destroy(); } // Metoda onAdd – tworzy formularz produktów function onAdd(){ include_once 'ProductNew.class.php'; new ProductNew; } // Metoda onList– wyświetla listę produktów, zezwalając użytkownikowi // na jej edycję function onList(){ include_once 'ProductList.class.php'; $obj = new ProductList; $obj->Show(); $obj->showData(); } // Metoda onEdit – Otwiera mały edytor tekstu function onEdit(){ include_once 'TextEditor.class.php'; new TextEditor; } // Metoda onHelp – Wyświetla dialog O programie function onHelp(){ include_once 'AboutDialog.class.php'; new AboutDialog('Produkty', 'Jest to program open-source'); } // Metoda onQuit – wyjście z aplikacji function onQuit(){ Gtk::main_quit(); } } // tworzy nową instancję Application new Application; Gtk::Main(); ?>

Pozwala też na łatwe definiowanie stylów i kolorów (patrz Rysunek 5). Chociaż nie widać tego w przykładzie, równie łatwo możemy wyświetlać tekst w pionie lub pod kątem (wyrażonym w stopniach). Dialog dodawania nowego produktu będzie zawierał następujące etykiety: Kod, Opis, Ilość, Jednostka, Koszt i Cena. Pod nimi z kolei umieścimy etykietę informującą o konieczności wypełnienia pól Kod, Opis i Cena. Chcielibyśmy jednak, by nie była ona widoczna przez cały czas, gdyż nie będzie potrzebna bardziej doświadczonym użytkownikom. To ostatnie nie stanowi żadnego problemu dla PHP-GTK2: możemy skorzystać z komponentu GtkExpander, będącego kontenerem pozwalającym na rozwijanie i zwijanie kontrolek, które się w nim znajdują. Umieścimy w nim naszą etykietę pomocy. W tym celu zadeklarujemy obiekt $expander klasy GtkExpander, a następnie stworzymy etykietę pomocy o nazwie $help. Na koniec, dodamy tę etykietę do $expander. Domyślnie będzie ona rozwinięta, co zapewnia nam metoda set_ expanded(true). Kolejnym wykorzystywanym przez nas widgetem będzie pole wprowadzania tekstu (GtkEntry). Umieścimy je przy każdej etykiecie oprócz $help. W celu ułatwienia wprowadzania tekstu, PHP-GTK2 oferuje bardzo przydatny mechanizm autouzupełniania. Pozwala on na połączenie widgetu GtkEntry z GtkListStore – modelem danych zawierającym listę wartości używanych przy autouzupełnianiu. Połączenia instancji obu klas dokonujemy przy użyciu obiektu trzeciej klasy, GtkEntryCompletion. Pojawiające się podpowiedzi będą się zmieniać w miarę wprowadzania przez użytkownika kolejnych znaków. Autouzupełnianie jest rozwiązaniem powszechnie używanym w aplikacjach Gtk, szczególnie w dialogach wyboru pliku, gdzie program podpowiada nazwy pasujące do wprowadzonego wzorca (patrz Rysunek 6). W naszym dialogu zastosujemy GtkEntryCompletion w polu Jednostka, gdzie podpowiedzią będzie lista często stosowanych jednostek.

Rysunek 5. Efekty wyświetlania tekstu w PHP-GTK2

PHP Solutions Nr 1/2006

www.phpsolmag.org

67

Projekty

PHP-GTK2

W tym celu, obok obiektu $this->
entries[3]

klasy GtkEntry stworzymy instancję klasy GtkListStore o nazwie $store. Następnie używając metody append() obiektu $store dodamy do niego powszechnie stosowane jednostki. Potem utworzymy obiekt $completion klasy GtkEntryCompletion, po czym wskażemy mu obiekt $store jako źródło danych dla autouzupełniania, a na koniec użyjemy metody set_completion() pola Jednostka, aby połączyć z nim cały mechanizm. Ostatnią potrzebną nam rzeczą jest przycisk Zapisz, po naciśnięciu którego umieścimy nowo wprowadzony produkt w bazie danych. Utworzymy więc instancję klasy GtkButton, którą nazwiemy $button1. Na przycisku tym umieścimy standardową ikonę dyskietki, a następnie połączymy jego zdarzenie clicked z metodą callbackową o nazwie onSaveClick(). Metoda ta pobierze wprowadzone wartości z formularza, zapisze je do bazy danych i, korzystając ze zdefiniowanej przez nas metody Clear(), wyczyści formularz.

Listing 3. Kod formularza produktów, przechowywany w pliku ProductNew.class.php
<?php // Klasa ProductNew – formularz produktu do ich wstawiania class ProductNew extends GtkWindow{ ... // Konstruktor klasy – tworzy okno i całą jego zawartość public function __construct(){ ... // tworzy wszystkie etykiety i pola tekstowe, linia po linii $this->labels[0] = new GtkLabel('<span foreground="red"><b>Kod </b></span>'); $this->entries[0] = new GtkEntry; $this->entries[0]->set_size_request(80,-1); ... // Tworzy model danych $store = new GtkListStore(Gtk::TYPE_STRING); // dodaj wartości do modelu danych $store->append(array('UN')); ... // Tworzy EntryCompletion $completion = new GtkEntryCompletion(); $completion->set_model($store); $completion->set_text_column(0); $this->entries[3]->set_completion($completion); ... // upakuj wszystkie etykiety i pola tekstowe w pionowym boksie ... // stwórz przycisk Zapisz i boks dla niego $save_box= new GtkHBox(); $button1 = GtkButton::new_from_stock(Gtk::STOCK_SAVE); $button1->connect('clicked', array($this, 'onSaveClick')); $save_box->pack_start(new GtkHBox, true); ... $expander = new GtkExpander('<b><i>Help</i></b>'); $expander->set_use_markup(true); // Krótki tekst pomocy do umieszczenia w ekspanderze $help = new GtkLabel; $help->set_alignment(0.2, 0.5); $help->set_markup('<b><u>Wypełnianie formularza</u></b> Aby być w stanie wypełnić formularz, nie możesz pozostawić pól Kod, Opis i Cena pustymi...'); $expander->add($help); $expander->set_expanded(true); $vbox->pack_start($expander, false, false); parent::add($vbox); parent::show_all();

Pokaż mi produkty, czyli dlaczego lubimy drzewa

Kolejną metodą callbackową w klasie Application jest onList(), wywoływana po kliknięciu pozycji Edytuj w menu Plik. Korzysta ona z klasy ProductList przechowywanej w pliku ProductList.class.php (patrz Listingi 4 i 5). Nie musimy jej dokładnie opisywać, więc podobnie jak w przypadku poprzedniej metody, skoncentrujemy się na jej najważniejszych komponentach. Głównym zadaniem tej klasy jest wyświetlenie edytowalnej listy wszystkich posiadanych przez nas produktów. W przypadku edycji, dane mają być aktualizowane bezpośrednio w bazie. Za wyświetlanie listy będzie odpowiadał wprowadzony w PHP-GTK2 widget GtkTreeView.

... ...

} // Metoda onSaveClick – Zapisuje dane z ekranu do bazy public function onSaveClick(){ // odczyt danych z pól tekstowych i zapisywanie ich do bazy $conn = sqlite_open('data.db'); $product->code = $this->entries[0]->get_text(); $sql = "INSERT INTO products (code, description, amount,

} // Metoda Clear – czyści wszystkie pola formularza. private function Clear(){ for ($n=0; $n<=5; $n++){ $this->entries[$n]->set_text(''); } // ustawia kursor w pierwszym polu parent::set_focus($this->entries[0]); }

Rysunek 6. Autouzupełnianie

68

www.phpsolmag.org

PHP Solutions Nr 1/2006

PHP-GTK2

Projekty

Rysunek 7. GtkTreeView w trybie drzewa

Rysunek 8. GtkTreeView w trybie listy

Listing 4. Kod ProductList, przechowywanej w pliku ProductList.class.php
<?php // Klasa ProductList – lista produktów class ProductList extends GtkWindow { private $window; private $model; private $list; public function __construct(){ ... // tworzy widok drzewa $this->list = new GtkTreeView; $scroll->add($this->list); // tworzy model, z sześcioma elementami $this->model = new GtkListStore(Gtk::TYPE_STRING, Gtk::TYPE_STRING, Gtk::TYPE_STRING,Gtk::TYPE_STRING, Gtk::TYPE_STRING, Gtk::TYPE_STRING); // tworzy kolumny $column1 = new GtkTreeViewColumn(); ... $column1->set_title('Code'); ... // definiuje renderery $cell_renderer1 = new GtkCellRendererText(); ... // łączy renderery z edycją danych przez użytkownika $cell_renderer2->connect("edited",array($this,'onEdit'),1,'description'); ...

Stanowi on olbrzymi krok naprzód względem dostępnych już wcześniej, prostych komponentów w rodzaju GtkCTree czy GtkCList. O ile te poprzednie widgety pozwalały wyświetlać tylko drzewa (pierwszy z nich; patrz Rysunek 7) lub tylko listy (drugi; patrz Rysunek 8), GtkTreeView umożliwia wyświetlanie obu struktur, a w dodatku znacząco rozszerza ich funkcjonalność. Przykładowo, pozycje listy lub drzewa mogą teraz zawierać checkboksy oraz obrazki. Najciekawsze dla nas jest to, że zarówno drzewa, jak i listy mogą mieć kolumny, a zawarte w nich dane możemy edytować podobnie, jak w arkuszu kalkulacyjnym. W naszym przykładzie wykorzystamy klasę GtkTreeView w trybie edytowalnej listy z kolumnami. Najpierw stworzymy jej instancję, $this->list, a później dostarczymy jej dane. Powinniśmy wiedzieć, że GtkTreeView zapewnia całkowitą separację warstw: Modelu, Widoku i Kontrolera, według wzorca MVC (Model-View-Controller). Oznacza to, że model danych jest całkowicie odseparowany od jego reprezentacji na ekranie. Dane przechowywane są w Modelu, którym może być obiekt klasy GtkListStore (w przypadku list) lub GtkTreeStore (w przypadku drzew). My użyjemy GtkListStore, deklarując jej instancję o nazwie $this->model. Następnie stworzymy kolumny dla Widoku, w których będziemy wyświetlali dane. Wykorzystamy do tego osobną klasę o nazwie GtkTreeViewColumn. Dla każdej z kolumn określimy tytuł oraz stworzymy i dodamy (za pomocą metody connect()) renderer odpowiedzialny za wizualizację danych przechowywanych w modelu. W naszym przykładzie wykorzystamy klasę GtkCellRendererText, pozwalającą na wyświetlanie tekstu. Warto wiedzieć, że istnieją renderery pozwalające wyświetlać inne typy danych, takie jak obrazy czy checkboksy, a każda kolumna może zawierać co najmniej jeden z nich. Po dodaniu rendererów należy je odpowiednio skonfigurować, ustawiając m.in. szerokość kolumny i możliwość edycji znajdującego się w niej tekstu oraz łącząc funkcję callbackową onEdit() ze zdarzeniem edited, które występuje przy edycji dowolnego z elementów listy. Kolejnym krokiem będzie dodanie kolumn do listy reprezentowanej przez obiekt $this->list. W tym celu

PHP Solutions Nr 1/2006

www.phpsolmag.org

69

Projekty

PHP-GTK2
skorzystamy z metody append_column() tego obiektu. Teraz musimy dodać Model do listy: osiągniemy to za pomocą metody set_ model() obiektu $this->list. Póki co nie zawiera on żadnych danych, które zostaną dodane później. Przy okazji, istotną zaletą separacji warstw GtkTreeView jest możliwość wykorzystania modelu danych przez więcej niż jeden obiekt GtkTreeView, dzięki czemu te same dane mogą być wyświetlane w różnych miejscach aplikacji. Nasza klasa ProductList jest już prawie gotowa. Musimy jeszcze stworzyć metodę wypełniającą model danymi, którą nazwiemy showData(). Będzie ona wywoływana z naszej klasy Application, a jej działanie będzie następujące: najpierw nawiąże ona połączenie z bazą i pobierze dane, a następnie włączy je do modelu za pomocą metody set() obiektu $this->model. Kolejną niezbędną metodą callbackową jest onEdit(). Jej wywołanie nastąpi, gdy użytkownik zmieni jakąkolwiek komórkę listy. Metoda ta automatycznie zaktualizuje dane w bazie, wyszukując właściwy wiersz w odpowiedniej tabeli na podstawie kodu produktu. I to wszystko jeśli chodzi o ProductList.

Rysunek 9. Prosty edytor tekstu

Listing 5. Kod ProductList, ciąg dalszy
// upakuj renderery $column1->pack_start($cell_renderer1, true); // określ szerokość $cell_renderer1->set_property('width', 50); // zezwól użytkownikowi na edycję danych $cell_renderer2->set_property('editable', True); // zdefiniuj pozycję modelu do połączenia z rendererami $column1->set_attributes($cell_renderer1, 'text', 0); $this->list->append_column($column1); $this->list->set_model($this->model); $this->list->show_all(); } // Metoda showData – wyświetla wszystkie produkty obecne w bazie danych public function showData(){ // otwiera połączenie z bazą i czyta wszystkie dane za pomocą pętli // while() $conn = sqlite_open('data.db'); $query = sqlite_query($conn, 'select code, description, amount,'. ' unit, cost, price from products'); while ($data = sqlite_fetch_array($query)){ $iter = $this->model->append(); $this->model->set($iter, 0, $data['code'], 1, $data['description'], 2, $data['amount'], 3, $data['unit'], 4, $data['cost'], 5, $data['price']); } sqlite_close($conn); } // Metoda onEdit – Wywoływana, gdy użytkownik zmienia dane public function onEdit($cell_renderer, $path, $new_text, $column_number, $column_name){ // pobierz zaznaczenie $treeselection = $this->list->get_selection(); list($model, $iter) = $treeselection->get_selected(); // ustaw nową wartość iteratora $model->set($iter, $column_number, $new_text); // pobierz pierwszą kolumnę $code = $this->model->get_value($iter, 0); // otwiera połączenie z bazą danych i dokonuje aktualizacji $conn = sqlite_open('data.db'); $query = sqlite_query($conn, "update products set " . "$column_name='$new_text' where code='$code'"); sqlite_close($conn); }

...

...

...

... ...

...

Edytor

} ?>

Kolejnym elementem, który chcemy dodać do naszego projektu, jest prosty edytor tekstu wywoływany przy użyciu metody callbackowej onEdit() klasy Application. Oprócz edycji tekstu, powinien on umożliwiać także jego wczytywanie i zapisywanie. Jego wygląd przedstawiamy na Rysunku 9. Stworzymy klasę TextEditor i zapiszemy ją w pliku TextEditor.class.php. Jej kod pokazujemy na Listingu 6. Głównym komponentem tej klasy jest okno tekstowe, do implementacji którego użyjemy klasy GtkTextView i GtkTextBuffer. W PHP-GTK1 skorzystalibyśmy z przestarzałego już komponentu GtkText. Istotną sprawą jest, że o ile w PHP-GTK1 ten sam obiekt przechowywał treść i odpowiadał za jej wizualizację, w PHP-GTK2 dane przechowujemy poza widgetem, we wspomnianej już klasie GtkTextBuffer. Co za tym idzie, ten sam tekst może być wyświetlany przez różne widgety GtkTextView, analogicznie jak w przypadku GtkTreeView. GtkTextBuffer

70

www.phpsolmag.org

PHP Solutions Nr 1/2006

PHP-GTK2

Projekty

Listing 6. Kod edytora tekstu, przechowywany w pliku TextEditor.class.php
<?php // Klasa Editor – mały edytor tekstu final class TextEditor extends GtkWindow{ private $textview; private $textbuffer; function __construct(){ // tworzy okno ... // Tworzy pasek narzędzi $toolbar = new GtkToolbar; // Tworzy przyciski “zapisz” i “otwórz” $save = new GtkToolButton; $save->set_label('zapisz'); $save->set_stock_id('gtk-save'); $save->connect('clicked', array(&$this, 'saveFile')); ... // Wstawia przyciski na pasek $toolbar->insert($open, 0); $toolbar->insert($save, 0); $vbox->pack_start($toolbar, false, false); ... // Tworzy parę TextView/TextBuffer; $this->textview = new GtkTextView; $this->textbuffer = new GtkTextBuffer; $this->textview->set_buffer($this->textbuffer); ... } // Metoda openFile – Wyświetla FileDialog i wczytuje zawartość pliku // do TextBuffer public function openFile(){ // Tworzy FileChooserDialog $dialog = new GtkFileChooserDialog('Otwarcie pliku', NULL, Gtk::FILE_CHOOSER_ACTION_OPEN,array(Gtk::STOCK_OK,Gtk::RESPONSE_OK, Gtk::STOCK_CANCEL, Gtk::RESPONSE_CANCEL)); // Wyświetla FileChooserDialog $response = $dialog->run(); if ($response == Gtk::RESPONSE_OK){ // jeżeli użytkownik kliknął OK, //wyczyść TextBuffer i wstaw do niego zawartość wybranego pliku $first = $this->textbuffer->get_start_iter(); $end = $this->textbuffer->get_end_iter(); $this->textbuffer->delete($first, $end); $this->textbuffer->insert_at_cursor(file_get_contents( $dialog->get_filename())); } $dialog->destroy(); } // Metoda saveFile – Wyświetla FileDialog i zapisuje zawartość TextBuffer // do wybranego pliku public function saveFile(){ // Tworzy FileChooserDialog $dialog = new GtkFileChooserDialog('Zapis do pliku', NULL, Gtk::FILE_CHOOSER_ACTION_SAVE,array(Gtk::STOCK_OK,Gtk::RESPONSE_OK, Gtk::STOCK_CANCEL, Gtk::RESPONSE_CANCEL)); // Wyświetla FileChooserDialog $response = $dialog->run(); if ($response == Gtk::RESPONSE_OK){ // jeżeli użytkownik kliknął OK, // pobierz zawartość TextBuffer i zapisz ją do pliku $first = $this->textbuffer->get_start_iter(); $end = $this->textbuffer->get_end_iter(); $text = $this->textbuffer->get_text($first, $end); file_put_contents($dialog->get_filename(), $text); } $dialog->destroy(); } } ?>

udostępnia nam ponadto iteratory, czyli możliwość zaznaczania określonych miejsc w tekście. W konstruktorze naszej klasy tworzymy instancję GtkTextView o nazwie $this->textview. To samo uczynimy z GtkTextBufffer, tworząc obiekt $this>textbuffer. Wreszcie, połączymy model danych z widgetem za pomocą metody set_buffer() obiektu $this>textview. Jak już wspomnieliśmy, chcemy mieć możliwość zapisywania i ładowania (otwierania) plików tekstowych. Jak już pokazaliśmy na Rysunku 9, edytor będzie wyposażony w pasek narzędzi zawierający dwa opatrzone odpowiednimi ikonami elementy: Zapisz i Otwórz. W celu stworzenia tego paska wykorzystamy klasę GtkToolbar, nazywając jej instancję $toolbar. Następnie utworzymy przyciski do zapisu i ładowania danych, konstruując obiekty klasy GtkToolButton. Każdemu z nich nadamy odpowiedni opis i przypiszemy właściwą ikonę. Kolejnym krokiem będzie połączenie wydarzenia clicked każdego z tych przycisków z odpowiadającą mu metodą: openFile() w przypadku ładowania plików, saveFile() przy ich zapisie. Główna część naszego edytora jest już gotowa, zabierzmy się zatem za metody obsługujące zdarzenia. W pierwszej z nich, openFile(), chcemy mieć możliwość wyboru pliku z listy. PHP-GTK2 oferuje w tym celu GtkFileChooserDialog (patrz Rysunek 10), zastępujący – jako bardziej użyteczny i łatwiejszy w rozbudowie – GtkFileSelection z PHP-GTK1. Komponent GtkFileChooserDialog jest związany z klasą GtkFileChooser z biblioteki Gtk2, wykorzystywaną praktycznie we wszystkich aplikacjach opartych na Gnome, takich jak Evolution, Gnumeric, Gaim czy Gimp. Instancję GtkFileChooserDialog nazwiemy $dialog, a następnie nadamy jej tytuł Otwarcie pliku i skojarzymy ją z reakcjami na możliwe odpowiedzi, czyli na OK i ANULUJ. Również odpowiedzi będą posiadały odpowiednie ikony standardowe. Jeżeli potwierdzimy naszą chęć otwarcia pliku, bufor tekstu zostanie opróżniony (za pomocą $this-> textbuffer->delete()), a zawartość pliku – wstawiona na bieżącej pozycji kursora ($this->textbuffer->

PHP Solutions Nr 1/2006

www.phpsolmag.org

71

Projekty

PHP-GTK2
insertatcursor()).

Po wykonaniu tych czynności dialog zostanie zniszczony. Kolejna metoda nosi nazwę saveFile(). Wykorzystamy w niej tę samą klasę GtkFileChooserDialog, aby pobrać nazwę i położenie pliku, do którego chcemy zapisać nasz tekst. Podobnie jak w przypadku otwierania pliku, dialog będzie posiadał dwa przyciski: OK i ANULUJ. Jeżeli użytkownik kliknie OK, saveFile() odczyta tekst z bufora korzystając z metody $this-> textbuffer->get_text(), a następnie zapisze go w pliku o wybranej (lub wpisanej) nazwie, za pomocą metody file_put_contents(). Na koniec (analogicznie jak w przypadku openFile()) dialog zostanie zniszczony.
Rysunek 10. Wybór pliku

Listing 7. Kod klasy AboutDialog, zawarty w pliku AboutDialog.class.php
<?php // Klasa AboutDialog – Wyświetla informację o działającej aplikacji final class AboutDialog extends GtkWindow{ public function __construct($software, $text){ ... // spróbuj załadować obrazek try{ $pixbuf = GdkPixbuf::new_from_file('images/gnome.png'); $imagem = new GtkImage; $imagem->set_from_pixbuf($pixbuf); $vbox->pack_start($imagem); } catch{ (PhpGtkGErrorException $error) // jeżeli wystąpiły jakieś błędy, wyświetl je na ekranie $dialog = new GtkMessageDialog(null, Gtk::DIALOG_MODAL, Gtk::MESSAGE_ERROR,Gtk::BUTTONS_OK, $error->message); $response = $dialog->run(); $dialog->destroy(); return;

Kto mnie stworzył: wykorzystanie dialogu O programie

}

// wyświetl komunikat na temat aplikacji $this->label = new GtkLabel($text); $vbox->pack_start($this->label); // tworzy przycisk “Zamknij” $this->button = new GtkButton('Zamknij'); $this->button->connect('clicked', array($this, 'onClose')); $vbox->pack_start($this->button, false, false); parent::show_all();

}

... } ?>

// Metoda onClose – Zamyka okno public function onClose(){ parent::destroy(); }

Ostatnią rzeczą, która pozostała nam do zrobienia, jest dialog O programie, pojawiający się po wybraniu opcji Pomoc z menu o tej samej nazwie. W tym celu stworzymy klasę AboutDialog i umieścimy ją w pliku AboutDialog.class.php. Jej kod przedstawiamy na Listingu 7. Będziemy w niej korzystać z wyjątków oraz możliwości manipulacji obrazem. Na początku swojego działania, konstruktor klasy AboutDialog ustawi (jak zwykle) podstawowe parametry okienka dialogowego. Następnie załadujemy obrazek o nazwie gnome.png i wyświetlimy go (patrz Rysunek 11). W tym celu wykorzystamy klasę GdkPixbuf. Obsługuje ona najpopularniejsze formaty obrazu, takie jak PNG, JPEG, itd., co zwalnia nas z obowiązku dodatkowej konwersji plików graficznych. W PHP-GTK1 klasa ta była dostępna, jednak korzystano z niej jedynie opcjonalnie. W GTK2 stała się ona częścią zbioru klas natywnych i nie jest już opcjonalna. Możemy ją wykorzystywać we wszystkich zadaniach związanych z manipulacją obrazkami, również w przypadku używanych na paskach narzędzi i w menu ikon. Nie ma potrzeby opierania się na formacie XPM (XpixMap), który był stosowany powszechnie w PHP-GTK1 w sytuacjach, gdy nie korzystaliśmy z GdkPixbuf. Podsumowując: stworzymy instancję klasy GdkPixbuf o nazwie $pixbuf,

72

www.phpsolmag.org

PHP Solutions Nr 1/2006

PHP-GTK2
ny, a w przypadku jego nieznalezienia aplikacja nie zakończy działania zgłaszając błąd. Z tego względu całość operacji związanych z obrazkiem zostanie zamknięta w bloku try{} konstrukcji try{}..catch{}. PHP-GTK2 współdziała z wprowadzonym w PHP5 systemem wyjątków, generując je w sytuacjach takich, jak błędy występujące podczas konstruowania obiektu, a także pojawiające się w metodach wykorzystujących mechanizm Gerror (takich jak np. statyczne konstruktory w rodzaju GdkPixbuff:: new_from_file()) oraz przy konwersji stron kodowych. W naszym przypadku, nieznalezienie logo spowoduje wygenerowanie wyjątku obsługiwanego w konstruktorze klasy AboutDialog. I to wszystko. Nasza aplikacja jest już gotowa i możemy ją przetestować.

Projekty

nowości w PHP-GTK2. Zachęcamy do jego rozbudowy i poznawania szerszego spektrum fascynujących możliwości tej wersji PHP-GTK na własną rękę. Na pewno nie będziecie zawiedzeni: PHPGTK2 stanowi bowiem wielki krok naprzód w stosunku do poprzedniej wersji, a w połączeniu z nowymi możliwościami PHP5 może uczynić PHP poważnym wyborem dla programistów piszących klienckie aplikacje GUI. n

Rysunek 11. Dialog O programie
przekazując jej konstruktorowi ścieżkę do pliku gnome.png. Następnie za pomocą klasy GtkImage stworzymy widget obrazka, który nazwiemy $imagem. Korzystając z jego metody set_from_ pixbuf(), przekopiujemy do niego zawartość $pixbuf. Chcemy jednak zapewnić także, że plik gnome.png zostanie załadowa-

O autorze
Pablo Dall’Oglio jest autorem pierwszej na świecie książki o PHP-GTK. Jest również autorem Agata Report (www.agata.org.br) oraz Tulip Editor (http://tulip.solis. coop.br), a także koordynatorem projektu GNUTeca (www.gnuteca.org.br) – opensourcowego oprogramowania do zarządzania biblioteką. Kontakt z autorem: pablo@dalloglio.net

Podsumowanie

Pokazany przykład jest dość nieskomplikowany, ilustruje jednak najważniejsze

R

E

K

L

A

M

A

PHP Solutions Nr 1/2006

www.phpsolmag.org

73

Ranking

Porównanie ofert polskich firm hostingowych
Paweł Grzesiak

R

ynek usług hostingowych w Polsce rozwija się dynamicznie. Dowodem na to są coraz lepsze parametry usług oferowanych przez firmy, podczas gdy średnia cena hostingu wciąż maleje. Jeżeli planujemy zakup własnego skrawka miejsca w sieci, warto zapoznać się z przygotowanym przez nas porównaniem usług najpopularniejszych polskich providerów internetowych. Rok 2005 z pewnością możemy nazwać rokiem, w którym polski Internet rozwijał się bardzo szybko. Motorem dla całej branży jest coraz powszechniejszy dostęp Polaków do łącz szerokopasmowych. Firmy telekomunikacyjne walczą o klientów, co odbija się zarówno na cenie usług, jak i coraz wyższej jakości łączy. Dziś rynek zaspokaja potrzeby zarówno tych, którzy potrzebują szybkiego dostępu (liczonego w MBPS), jak i tych, dla których podstawowym kryterium jest cena (już nie przekraczająca 100 zł). Coraz większa liczba szybkich łącz dla klientów indywidualnych, pociąga za sobą rozwój infrastruktury telekomunikacyjnej. Skoro użytkownicy pobierają więcej plików z sieci, serwery muszą sprostać wzrastającemu obciążeniu. W ten sposób, w ciągu ostatniego roku obserwujemy na rynku znaczący wzrost pojemności serwerów WWW, większe limity transferu miesięcznego, a także niższe ceny za ruch dodatkowy. Powodów sytuacji, gdzie parametry usług są coraz lepsze, a ceny usług maleją, jest co najmniej kilka. Główny impuls do zmian dały zagraniczne serwisy, oferujące konta e-mailowe o wielkościach (na tamte czasy) astronomicznych. Mniej

więcej w czasie, gdy Google ogłosiło, że planuje uruchomienie własnego serwera pocztowego Gmail, oferującego zawrotną pojemność 1 GB, stało się jasne, że szykuje się w Polsce wojna o klienta. Usługi hostingowe musiały bowiem nadążać za rodzimymi portalami, które zaoferowały bezpłatne skrzynki pocztowe, docelowo o pojemności 1 GB. Co zabawne, zwiększenie pojemności kont wcale nie równało się poniesieniu ogromnych nakładów finansowych. Większość użytkowników nie wykorzystuje bowiem nawet 1% swojej powierzchni na korespondencję. Zadziałał tu więc raczej marketing. Kolejnym powodem, dla którego pośrednio obniżyły się ceny usług, było wejście usług hostingowych, z serwerami zlokalizowanymi poza Polską. Miejsce na takim serwerze było swego czasu sporo tańsze, dlatego że serwery lokowano za oceanem. Na korzyść serwerów w USA działała różnica czasu. Gdy Amerykanie spali, my korzystaliśmy z ich serwerów. Jednocześnie w Stanach ceny usług hostingowych i sprzętu komputerowego są niższe, więc możliwe było zaoferowanie ciekawych warunków cenowych. Wadą były długie czasy odpowiedzi serwera, czyli tzw. pingi, co wynikało z długości odległości połączenia i bariery kontynentalnej. Dla przeciętnego użytkownika nie były jednak zauważalne opóźnienia przy ładowaniu stron. Dziś już odchodzi się od lokowania serwera w USA, na rzecz krajów Europy Zachodniej, a konkretniej Holandii i Niemiec, które są nam geograficznie bliższe (stąd strony ładują się szybciej),

a jednocześnie są wciąż tańsze od naszych krajowych dostawców. Jednak w perspektywie czasu, serwer ulokowany poza granicami Polski może przestać się opłacać. Jeszcze półtora roku temu providerzy bronili się rękami i nogami przed tworzeniem zbyt wielkiej liczby kont e-mail, czy podpinaniem wielu domen. Dziś standardem stało się wyrażenie „bez ograniczeń”, a na serwerze za kilkaset złotych można z powodzeniem uruchomić kilkanaście niezależnych serwisów internetowych.

Jak powstało porównanie?

Tworząc swoisty ranking usług hostingowych, za podstawowe kryterium wzięliśmy statystyki ilości obsługiwanych domen przez każdego z providerów, bazując na wynikach z witryny top100.pl. Usługi hostingowe bardzo ciężko jest jednoznacznie ocenić, wyłaniając przy tym zwycięzcę. Zarówno oferty, jak i ich ceny są bardzo do siebie zbliżone. Mogliśmy bazować na popularności i opinii klientów. Postawiliśmy na to, sądząc, że największe zaufanie budzi ten provider, który zarejestrował dla swoich klientów najwięcej domen. Oczywiście nie powinno to stanowić kryterium decydującego o zakupie danej usługi. Zaprezentowane 12 firm prezentuje bowiem bardzo wyrównany, wysoki poziom. Należy jeszcze tylko dodać, że do rankingu zostały zakwalifikowane tylko te firmy, których podstawowym przedmiotem działalności są usługi hostingowe. n

74

www.phpsolmag.org

PHP Solutions Nr 1/2006

Firma Poczta Polska Polska Bazy Danych Kont e-mail

Usługa

Powierzchnia

Limit transferu (miesięczny) Restrykcje ilościowe Obsługa baz danych

Lokalizacja serwera

FTP

PHP Solutions Nr 1/2006
Holandia Polska Polska Polska Polska Polska Polska Polska Polska Polska Domen / Baz Serwisów Kont FTP MySQL Subdomen danych b.o. 1 1 1 Tak b.o. b.o. 1 5 Tak b.o. b.o. b.o. 15 Tak b.o. b.o. b.o. b.o. Tak b.o. b.o. b.o. b.o. Tak 5 1 1 2 Tak 7 2 5 4 Tak 20 5 10 8 Tak 100 7 10 12 Tak b.o. 9 10 16 Tak b.o. 11 20 20 Tak b.o. 16 b.o. 40 Tak 1 1 1 1 Tak 5 3 5 1 Tak 15 5 15 2 Tak 30 10 b.o. 3 Tak b.o. b.o. 1 1 Tak b.o. b.o. 1 b.o. Tak b.o. b.o. 1 1 Tak b.o. b.o. 1 5 Tak b.o. b.o. 1 5 Tak b.o. b.o. b.o. b.o. Tak b.o. b.o. b.o. b.d. Tak b.o. b.o. b.o. b.d. Tak b.o. b.o. b.o. b.d. Tak b.o. 1 1 b.o. 1 1 b.o. 1 1 b.d. Tak b.o. 1 1 b.d. Tak b.o. 1 1 b.d. Tak b.o. b.d. b.d. n.d.* b.o. b.d. b.d. n.d.* b.o. b.o. b.o. 1 Tak b.o. b.o. b.o. 2 Tak b.o. b.o. b.o. 3 Tak b.o. b.o. b.o. 5 Tak b.o. b.o. b.o. 8 Tak b.o. 1 b.o. 1 Tak b.o. 2 b.o. 2 Tak b.o. 4 b.o. 4 Tak b.o. 20 b.o. 20 Tak b.o. 50 b.o. 50 Tak b.o. 150 b.o. b.o. Tak b.o. b.d. b.d. 2 Tak b.o. b.d. b.d. 3 Tak b.o. b.d. b.d. 3 Tak b.o. b.d. b.d. 4 Tak b.o. b.d. b.d. 5 Tak b.o. b.d. b.d. 7 Tak * firma oferuje dostęp do bazy danych SQLite (moduł PHP5) Polska PostgreSQL Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Tak Ceny (netto) Abonament Roczny 300 600 900 300 600 32,78 45,08 72,13 108,19 144,26 198,36 270,49 199 399 699 999 300 600 300 600 220 420 50 100 500 407 660 880 1199 1507 298 598 300 400 600 1000 1600 99 190 290 399 599 1500 100 150 200 300 400 600

Home (home.pl)

www.phpsolmag.org

Business Starter Business Server Business PRO Progreso (progreso.pl) Biznes Biznes plus Livenet (livenet.pl) Small Medium Large Big X-big Portal Prioritaire Netlink (nq.pl) nQ.START nQ.BIZNES nQ.PROFESJA nQ.VIP Nazwa (nazwa.pl) Active Pro KEI Provider (kei.pl) Biuro Extra Lider Domeny.org (domeny.org) Start Classic Futuro (futuro.pl) Flexo Start Flexo Flexo Biz Ogicom (ogicom.pl) Biznes 50 Biznes 150 Biznes 250 Biznes 350 Biznes 500 Agnat (agnat.pl) Profit Expert Sisco (sisco.pl) Economic 300 Business 500 Pro 1000 VIP 2000 Max 3000 AlphaNet (alpha.pl) Promo Prima Expo Multi Opti Solid+ AMM-Komputer (amm.net.pl) Start Start+ Mini Standard Business Professional

1 GB 10 GB b.o. 5 GB 20 GB b.o. 10 GB 30 GB b.o. 5 GB 12 GB b.o. 10 GB 24 GB b.o. 100 MB 1 GB 3 200 MB 2,5 GB 10 500 MB 5 GB 30 1 GB 8 GB 50 2 GB 10 GB b.o. 5 GB 15 GB b.o. 10 GB 30 GB b.o. 1 GB 5 MB 4 GB 5 2 GB 25 MB 10 GB 20 5 GB 200 MB 16 GB 50 10 GB 300 MB 24 GB b.o. 5 GB 10 GB b.o. 10 GB 20 GB b.o. 5 GB 10 GB b.o. 10 GB 20 GB b.o. 4 GB 4 GB 2 GB 8,3 GB b.o. 10 GB 10 GB 5 GB 16,7 GB b.o. 2 GB 5 MB 1 GB b.o. 5 GB 5 MB 10 GB b.o. 10 GB 7 MB 20 GB b.o. 100 MB 1 GB 4 300 MB 3 GB 12 500 MB 5 GB 30 700 MB 7 GB 50 1 GB 10 GB b.o. 5 GB 5 GB 100 10 GB 10 GB b.o. 300 MB 5 GB 5 500 MB 8 GB 10 1 GB 16 GB b.o. 2 GB 25 GB b.o. 3 GB 40 GB b.o. 500 MB 500 MB b.o. 5 GB b.o. 1 GB 1 GB b.o. 10 GB b.o. 5 GB 20 GB b.o. 3 GB 20 GB b.o. 6 GB 30 GB b.o. 15 GB 100 GB b.o. 1 GB 25 MB 2 GB b.o. 1,5 GB 50 MB 3 GB b.o. 3 GB 75 MB 4 GB b.o. 5 GB 10 GB b.o. 6 GB 15 GB b.o. 7,5 GB 20 GB b.o. b.o. – bez ograniczeń; n.d. – nie dotyczy; b.d. – brak danych;

Ranking

75

Portal aktywnych internautów

C

odziennie w Polsce nowe firmy rozpoczynają działalność, łączą się, zmieniają nazwy. Każdy przedsiębiorca i webmaster poszukuje sposobów, by pokazać w sieci siebie i swą ofertę z jak najlepszej strony, jak najniższym kosztem. W ostatnich latach było wiele firm oferujących usługi hostingowe, które po kilku miesiącach kończyły swą działalność, a nasze pieniądze i trud w budowaniu stron legł w gruzach. Dla webmastera podstawową sprawą jest znalezienie takiego usługodawcy, który zapewni stabilność strony, szybki dostęp, a także wiele usług dodanych. Jednak fundamentalnym kryterium przy wyborze miejsca, w którym będziemy przetrzymywali naszą stronę jest cena. Tworząc strony w PHP staramy się, by czynność ich aktualizacji przełożyć na barki właścicieli stron. Korzystamy zatem z systemów umożliwiających użytkownikowi samodzielne zarządzanie treścią (CMS). Niestety na naszym rynku istnieje niewielka liczba firm, których oferta odpowiadałaby naszym potrzebom. Najczęściej dostęp do serwera oferującego kilkaset megabajtów miejsca na stronę WWW, możliwość dostępu do PHP w wersji nowszej niż 4.0, gwarantującego bezpieczeństwo naszych danych, kosztuje od kilkudziesięciu do kilkuset złotych miesięcznie.

w żadnych zbiorczych zestawieniach firmy statystycznej Gemius SA. A wystarczy tylko sprawdzić, iż wyszukiwarka Google zaindeksowała 2,5 mln dokumentów portalu osemka.pl – dla porówania w Google znajduje się 2,3 mln dokumentów Wirtualnej Polski (webpark.pl), czy 1,6 mln firmy Prv.pl. Świadczy to o ogromnej popularności usług proponowanych przez wspomniany serwis oferujący 200 MB konta na strony www oraz dostęp przez ftp. Wyróżniającą cechą tego portalu wśród konkurencji jest wysoka pozycja stron w domenie friko.pl i za.pl w wyszukiwarkach (strona główna ma Page Rank 6), a także możliwość skorzystania ze skryptów PHP 4.4.0. Niezwykle istotne jest, iż osemka.pl nie tworzy żadnych limitów miesięcznych, czy rocznych na transfery plików. Ta opcja jest praktycznie niedostępna u konkurencji, a ponadto nawet serwisy płatne stosują limity.

SZUKAMY WIĘCEJ MIEJSCA
Naszą uwagę w poszukiwaniach darmowego hostingu z obsługą php przykuła inna oferta polskiej firmy Spox.pl. Wspomniany serwis oferuje całkowicie bezpłatnie profesjonalny hosting o parametrach zarezerwowanych do tej pory dla komercyjnych i często bardzo drogich serwerów www. Spox.pl proponuje aż 500 MB pamięci dyskowej, transfer bez limitu, łatwy dostęp przez FTP, cPanel. Dodatkowo istnieje także opcja posiadania własnej domeny. Tutaj o rejestracji nie decyduje automat, a każde zgłoszenie jest osobno rozpatrywane. Na odpowiedź i potwierdzenie zgłoszenia rejestracyjnego czekaliśmy niecałe 24h. Zaraz po zalogowaniu się i zmianie hasła mogliśmy już swobodnie umieścić naszą stronę w serwisie spox.pl. Transfer był szybki i nie zauważyliśmy jakichkolwiek problemów z funkcjonowaniem naszego site’a. Strona nadal działa bezbłędnie.

TESTUJEMY
Postanowiliśmy przetestować ofertę osemka.pl. Rejestracja wyglądała prosto i sprawnie – należało podać jedynie swoje dane oraz adres email, następnie wybrać domenę dla naszej strony z łatwo zapadających w pamięć – za.pl i friko.pl. Momentalnie otrzymaliśmy wygenerowany automatycznie email z potwierdzeniem rejestracji i hasłem. Po zalogowaniu się nie napotkaliśmy jakichkolwiek problemów z edycją hasła, swoich danych itp. W panelu użytkownika znaleźliśmy nieaktywną opcję zakładania baz MySQL. Fakt jej umieszczenia może wskazywać, że programiści przewidzieli taką możliwość w przyszłości. Chwilę później po szybkim transferze plikow na serwer, mogliśmy się już pochwalić naszą stroną internetową, zbudowaną w systemie CMS, naszym znajomym i klientom, i to zupełnie za darmo! Największą wadą tego serwisu, którą napotkaliśmy był tryb działania safe mode, który nieco ogranicza funkcje, lecz jednocześnie zwiększa szansę, że nasz serwer bedzie działał dłużej. Zauważoną niedogodnością była funkcja include, którą można stosować tylko do bieżącego katalogu. Utrudnia to nieco pracę przy większych ilościach podstron, lecz zważywszy na fakt, iż jest to całkowicie darmowy serwis – to i tak nieznaczne uchybienie.

POSZUKUJEMY DARMOWEGO HOSTINGU
Szukając rozwiązania naszych problemów związanych z koniecznością uiszczania opłat za hostowanie naszych stron, można natknąć się na oferty kilku firm oferujących darmowe usługi. Jedną z nich jest oferta Ósemka.pl Internet Media, właściciela za.pl i friko.pl. Ósemka to jedna z najlepszych firm oferujących nieprzerwanie od 1999 roku usługi hostingowe. Obecnie w nowopowstałym Portalu Aktywnych Internautów, który połączył tradycję darmowych kont za.pl i friko.pl, zarejestrowanych jest 202 tysiące użytkownikow, a 66 tysięcy kont hostingowych jest aktywnych. Ze statystyk wynika, iż łącznie strony hostowane na serwerach portalu hosting.osemka.pl w miesiącu wrześniu 2005r. obejrzało blisko 3mln użytkownikow, generując ponad 25 mln odsłon. Budzi zdziwienie fakt, iż nie jest to odnotowane

ZA GRANICĄ ZA DARMO
Przykładem sprawnego działania, bez konieczności obsługi skryptów PHP, jest zagraniczny serwis happyhost.org. Przede wszystkim należy zaznaczyć, że jest to całkowicie darmowy serwis, bez limitów na przesył danych. Co prawda pingi wysyłane z Polski wracają po nieco dłuższym czasie, niż w przypadku innych rodzimych serwisów, jednak sprawdzając połączenia z serwerów znajdujących się w USA, wyniki wyglądają rewelacyjnie.

76

zamów prenumeratę PHP Solutions nowa niższa cena: a otrzymasz prezent!
150zł 135zł
wybierz prezent: » » » » » » » program PHPRunner o wartości 199$ 20% zniżki na oprogramowanie firmy Zend dwa dowolne numery archiwalne PHP Solutions pakiet internetowy nQ.Biznes firmy Netlink o wartości 486,70 zł roczny abonament na Usługę Business Starter pakiet Xtreeme SiteXpert firmy Xtreeme Maguma Workbench Bundle Starter

* cena prenumeraty rocznej w promocji zimowej oferta ważna do wyczerpania zapasów; szczegółowe informacje: www.phpsolmag.org/prenumerata lub pren@software.com.pl

PHPRunner: Szybkie prototypowanie PHP na twój sposób

F

irma Universal Data Solutions wydała PHPRunner 2.0 – biznesowe rozwiązanie do budowania wizualnie atrakcyjnych interfejsów sieciowych dla najpopularniejszych systemów baz danych, mianowicie: MySQL, PostgreSQL, Oracle, MS Access oraz MS SQL Server. Jest zaprojektowane tak, by spełniać oczekiwania wszystkich użytkowników – od początkujących po doświadczonych deweloperów. PHPRunner posiada różne zastosowania: przeszukiwanie baz danych przez sieć, wprowadzanie danych bądź tworzenie sieciowych raportów. Branże: samochodowa, nieruchomości, medyczna i turystyczna to tylko kilka spośród wielu, w których można wykorzystać to rozwiązanie. Istotnym elementem jest wykorzystywanie przez PHPRunner interfejsu opartego na kreatorach oraz zbioru szablonów. Oznacza to, że administrator bazy danych nie musi napisać ani jednej linii kodu, czy nawet posiadać doświadczenia programistycznego. Oprócz szczodrej kolekcji szablonów, dostępny jest także ich edytor, pozwalający administratorom baz danych dopasowywać wygląd stron stworzonych w PHP do własnych potrzeb, przez wstawianie logo i etykiet oraz modyfikację czcionek i kolorów. Zaawansowani użytkownicy mają także możliwość poprawiania generowanego przez program kodu PHP. Obecnie oprócz MySQL używać można także PostgreSQL, Oracle’a, MS Access i MS SQL Server, co stanowi istotny krok w dziedzinie ulepszania tego rozwiązania. Ponadto PHPRunner wzbogacony został o szereg przydatnych funkcji, takich jak wielopoziomowe uprawnienia dostępu do różnych tabel, wsparcie dla wielu języków (wybierać można z ponad 20), a także wiele innych możliwości.

PHPRunner jest świetnym rozwiązaniem do tworzenia chronionych hasłem serwisów tylko dla użytkowników; ponadto pozwala budować strony pozwalające przeprowadzać automatyczną rejestrację użytkowników. Można z jego pomocą tworzyć funkcje przypominania i zmieniania haseł. Operacjom wywoływanym za pomocą tych funkcji może towarzyszyć na przykład wysyłanie pocztą powiadomienia dla administratora w sytuacji, gdy zarejestrował się nowy użytkownik. Możliwe jest także ustawienie dla każdego użytkownika wielopoziomowych uprawnień do różnych tabel. Przykładowo: Użytkownik A może jedynie edytować i dodawać dane do tabel Orders i Customers, podczas gdy Użytkownik B może kasować dane w tabeli Orders oraz edytować tabelę Customers. Oprócz tego, PHPRunner może tworzyć użytkowników Administrator oraz Gość. Administrator miałby dostęp do wszystkich tabel i rekordów, Gość zaś – tylko prawa odczytu. PHPRunner czyni edycję danych dziecinnie łatwą. Zapewnia on szereg zaawansowanych kontrolek edycyjnych takich jak selektory dat, łączone rozwijane listy, możliwość przesyłania plików i obrazków na serwer, edytor tekstu z formatowaniem HTML, pola list, przyciski radio i wiele innych. PHPRunner dostarcza także szereg funkcji walidacji danych, chroniących użytkowników

przed wprowadzeniem niewłaściwych danych do dostępnych dla nich pól. Po wygenerowaniu plików PHP, użytkownicy mogą szybko wysłać pliki na zdalny serwer WWW za pomocą wbudowanego klienta FTP – z możliwością wyboru, czy wysłane mają być wszystkie pliki, czy tylko zmodyfikowane. PHPRunner jest tak prosty w obsłudze, że w pełni funkcjonalną aplikację można za jego pomocą wygenerować w zaledwie 15 minut. PHPRunner rozpowszechniany jest elektronicznie przez Internet. Darmowa wersja próbna dostępna jest na stronie producenta. Cena pojedynczej kopii to 199 USD. Dystrybutorom oprogramowania oraz kupującym większe ilości kopii sugerujemy negocjację specjalnych zniżek. Wersja próbna (ważna 21 dni) może być pobrana ze strony http://www.xlinesoft.com/phprunner/ download.htm

CO NASI KLIENCI MAJĄ DO POWIEDZENIA O PHPRUNNER:
l

l

l

Kontakt: Telefon: +1 (888) 290-6617 http://www.xlinesoft.com
l

Informacje o programie: http://www.xlinesoft.com/phprunner

Cóż, stało się wreszcie! Znalazłem rozwiązanie umożliwiające szybkie prototypowanie w świecie MySQL/PHP – i wprawdzie istnieją także i inne, jednak to jest pierwsze widziane przeze mnie rozwiązanie, które jest zarówno praktyczne, jak i funkcjonalne. Tym produktem jest PHPRunner – Peter B. MacIntyre. Korzystając z PHPRunner właśnie zbudowałem serwis dla bazy MySQL w ciągu około trzydziestu minut. FANTASTYCZNE. Po prostu nie mogę tego opisać. Kilku moich kolegów nie może wyjść z podziwu, jak gładko to wszystko poszło – Randy Samms, SBC/ Southwest-ESAC. Muszę Ci to powiedzieć stary, ten program to najwspanialszy wynalazek od czasu krojonego chleba. Jako obecny w sieci fotograf, piszący swoje własne galerie itp. korzystam z wielu programów, ale PHPRunner to chyba najbardziej pomocny program w mojej biblioteczce. Po prostu tak bardzo upraszcza mi życie – Stephen Jones, Australia. PHPRunner to prawdopodobnie najlepszy generator PHP na rynku. Zanim trafiłem na to narzędzie wypróbowałem coś koło trzech innych i mogę powiedzieć, że nie znajdziesz lepszego, ani nawet porównywalnego – Pedro Ruiz, Database Manager.

78

ded sites > > > Recommen
Jeśli prowadzisz ciekawą stronę internetową i chcesz abyśmy przedstawili ją w ramach akcji Recommended sites, skontaktuj się z nami pod adresem redakcja@phpsolmag.org

Całkowicie przebudowany serwis poświęcony skryptom. Obecnie skupia się na zbiorze skryptów z całego świata. www.antraxja-skrypty.iweb.pl

Strona zawierająca artykuły związane z PHP napisane przez autora. Zawiera też kolekcję zdjęć oraz opisy ciekawych książek. www.antylameriada.net

Serwis dla webmasterów, organizujący konkurs we współpracy z naszym magazynem, promujący rozwiązania bez użycia systemów CMS itp. www.webmaster.bbsoft.pl

Serwis stworzony z myślą o programistach PHP, który stara się prezentować także ogólne wiadomości związane Internetem. www.compzone.org

Bogate archiwum skryptów PHP, JavaScript, CGI, Perl i ASP. Zawiera także artykuły, porady, tutoriale, książki. www.megaskrypty.com

Skryptoteka to jeden z największych i najbardziej znanych zbiorów skryptów. Zawiera także artykuły, czcionki, szablony i podręczniki. www.skryptoteka.pl

Młody, rozwijający się serwis związany z PHP. Zdaje się, że autorowi zależy na niestandardowej oprawie i zawartości. www.sm00f.boo

Serwis zawierający kompletne kursy 9 technologii związanych z webmasteringiem oraz skrypty, elementy grafiki, czcionki i narzędzia. www.tymex.org

Portal, który oprócz emitowania newsów czy wywiadów gromadzi bogate materiały związane z projektowaniem i pisaniem stron WWW. www.webinside.pl

Serwis powstał w celu związania społeczności webmasterskiej. Użytkownicy mogą wymieniać doświadczenia oraz uzyskiwać porady. www.webmade.org

Wortal webmastera bogaty w wiele materiałów. Związany jest z Webserv – pakietem Apache, PHP, MySQL, MySQL CC oraz NoIP. www.webpl.org

Xklonos to serwis istniejący już od 2001 roku. Każdy użytkownik może wpływać na jego formę. www.xklonos.cal.pl

ites Recommended s

www.shop.software.com.pl
Zaprenumeruj swoje ulubione magazyny i zamów archiwalne numery!

Już teraz w kilka minut możesz zaprenumerować swoje ulubione pismo. Gwarantujemy: - preferencyjne ceny - bezpieczną płatność on-line - szybką realizację Twojego zamówienia Bezpieczna prenumerata on-line wszystkich tytułów Wydawnictwa Software!

zamówienie prenumeraty
Prosimy wypełnić czytelnie i przesłać faksem na numer: (22) 887 10 11 lub listownie na adres: Software-Wydawnictwo Sp. z o.o., Piaskowa 3, 01-067 Warszawa, e-mail: pren@software.com.pl. Przyjmujemy też zamówienia telefoniczne: (22) 887 14 44
Imię i nazwisko............................................................................................ Nazwa firmy................................................................................................. ID kontrahenta.......................................................................................... Numer NIP firmy.......................................................................................

Dokładny adres.................................................................................................................................................................................................................... Telefon (wraz z numerem kierunkowym)................................................... Faks (wraz z numerem kierunkowym) .................................................... E-mail (niezbędny do wysłania faktury)............................................................................................................................................................................
automatyczne przedłużenie prenumeraty

Tytuł
Software Developer’s Journal (1 płyta CD) – dawniej Software 2.0 Miesięcznik profesjonalnych programistów SDJ Extra (od 1 do 4 płyt CD lub DVD) – dawniej Software 2.0 Extra! Numery tematyczne dla programistów
Linux+ (2 płyty CD)

Ilość numerów

Ilość zamawianych prenumerat

Od numeru pisma lub miesiąca

Opłata w zł z VAT

12 6 12 12 8 6 6 6 4

250/1801 150/1352 250/1801 270/1981 232/1982 135 135 140 1193

Miesięcznik o systemie Linux
Linux+DVD (2 płyty DVD)

Miesięcznik o systemie Linux
Linux+Extra! (od 1 do 7 płyt CD lub DVD)

Numery specjalne z najpopularniejszymi dystrybucjami Linuksa
PHP Solutions (1 płyta CD)

Dwumiesięcznik o zastosowaniach języka PHP
Hakin9, jak się obronić (1 płyta CD)

Dwumiesięcznik o bezpieczeństwie i hakingu
.psd (1 płyta CD + film instruktażowy)

Dwumiesięcznik użytkowników programu Adobe Photoshop
Aurox Linux (4 płyty CD + 1 płyta DVD)

Magazyn z najpopularniejszym polskim Linuksem

Suma

1 2 3

Cena prenumeraty rocznej dla osób prywatnych Cena prenumeraty rocznej dla osób prenumerujących już Software Developer’s Journal lub Linux+ Cena prenumeraty dwuletniej Aurox Linux

Jeżeli chcesz zapłacić kartą kredytową, wejdź na stronę naszego sklepu internetowego:

www.shop.software.com.pl

W następnym numerze PHP Solutions

W sprzedaży od 20 lutego!

Ilia Alshanetsky, twórca FUDforum, autor książki Guide to PHP Security, a także współautor wielu projektów Open Source i rozszerzeń takich jak: PDO, SQLite czy GD prezentuje: Ataki XSS oraz CSRF na aplikacje internetowe Aaron Wormus: PEAR::Structures_DataGrid – poznajemy narzędzia tworzące datagrid w formatach HTML, XML, XLS, XUL i innych. Pakiet umożliwia także łatwe implementowanie mechanizmów stronicowania i sortowania w celu limitowania wyświetlanych i przetwarzanych danych. Idea wzorowana jest na .NET Framework DataGrid control.

Ponadto planujemy: ■ ■ ■
SDO (Service Data Objects) i XML Phalanger, czyli z .NET do PHP Wzorce projektowe