You are on page 1of 84

Spis treści

AKTUALNOŚCI
Ze świata PHP 6
Deweloperzy poszukiwani? Przedstawiamy garść najciekawszych wiadomo-
ści dla deweloperów PHP.
... bardzo dobra znajomość PHP4/5 oraz MySQL, do-
bra znajomość HTML oraz JavaScript, doświadczenie
w tworzeniu aplikacji WWW z wykorzystaniem Zend Fra- Opis CD
mework... Zawartość CD 10
Tak już niedługo może brzmieć ogłoszenie poten-
Prezentujemy zawartość płyty i sposób działania
cjalnego pracodawcy poszukującego Programisty PHP.
najnowszej wersji naszej dystrybucji PHP Solu-
Zend, firma „od PHP”, wypuszcza „prawdziwy” framework dla PHP tions Live.
– Zend Framework. Narzędzie do szybkiego i wyjątkowo łatwego pisa-
nia w PHP. Wszyscy oczekiwali po tym narzędziu bardzo wiele i ... nie
zawiedli się. Mimo że projekt znajduję się jeszcze w głębokiej fazie be- WYWIAD
ta, zyskał już setki zwolenników, którzy ze zniecierpliwieniem czekają na Rozmowa z Dmitrim Stogovem 12
kolejne wersje rozwojowe, jak również na te stabilne. A wsparcie gwa-
rantują mu najwięksi deweloperzy PHP, sama firma Zend oraz ogrom- Dariusz Pawłowski
na społeczność PHP. Można byłoby więc zaryzykować twierdzenie, że Dmitry Stogov jest jednym z inżynierów rozwojo-
Zend Framework już wkrótce stanie się pewnego rodzaju standardem. wych w firmie Zend i szefem rosyjskiego zespo-
Taki stan rzeczy z pewnością ułatwiłby i ustandaryzował tworzenie oraz łu tej firmy. Należy do grona twórców Zend Engi-
rozwijanie aplikacji WWW. Wielu programistów zaczęłoby poniekąd roz- ne 2. Stworzył też Turck MMCache i szereg roz-
mawiać wspólnym językiem, a znajomość Frameworka byłaby dodatko- szerzeń SOAP i Perl dla PHP5.
wym atutem lub nawet wymogiem na rynku pracy w kategorii Programi-
sta PHP. Zakłada się bowiem, że jego znajomość pozwoli na zbudowanie
praktycznie każdej aplikacji w PHP. Czy tak się stanie? Z odpowiedzią POCZĄTKI
na to pytanie musimy jeszcze trochę poczekać. Już teraz jednak, w arty- Obiektowość w PHP
kule Piotra Szarwasa, przedstawimy Wam możliwości nowego narzędzia na przykładzie IRCBota 14
i praktyczne przykłady jego użycia.
Marcin Staniszczak
W obecnym numerze przedstawimy też konkurencyjne dla Zend Fra-
W dalszym ciągu wiele osób nie próbowało pro-
mework rozwiązanie: eZ components, będące platformą do tworzenia
gramowania obiektowego. Oto artykuł, który po-
zaawansowanych aplikacji klasy Enterprise. Główny deweloper projek-
wstał, by wprowadzić w świat obiektowości i
tu, Tobias Schlitt, przedstawi Wam praktyczne wykorzystanie najważniej-
PHP5 . Krok po kroku piszemy aplikację wpro-
szych komponentów frameworka. wadzającą do tego modelu programowania.
Na koniec prawdziwa niespodzianka – Kryptografia w PHP. Zdziwie-
ni? Zaskoczeni? Nic więcej nie zdradzę. APIlity: zarządzanie reklamami
Google AdWords z poziomu PHP 26
Serdecznie zapraszam do lektury!
Thomas Steiner
Dariusz Pawłowski
Załóżmy, że prowadzimy rozbudowane kampa-
nie marketingowe w oparciu o Google AdWords.
Zarządzanie nimi przy użyciu zwykłej strony in-
ternetowej jest mocno ograniczone i uciążliwe.
Chcielibyśmy stworzyć własnego klienta Ad-
Words, który pozwoliłby nam na elastyczne za-
rządzanie naszym kontem i automatyzację wielu
Nasz magazyn ukazuje się w czterech językach! procesów. Naprzeciw naszym oczekiwaniom wy-
chodzi Google AdWords API.
polskim niemieckim francuskim włoskim

BEZPIECZEŃSTWO
Kryptografia w PHP 34
Łukasz Lach, Michał Stanowski
Prawdopodobnie każdy z nas wie, na czym polega
kryptografia. Bierzemy porcję informacji (np. tekstu)
i szyfrujemy ją tak, aby nikt nieuprawniony nie był w
stanie jej odczytać. Z kryptografią mamy do czynie-
nia na co dzień – czy to korzystając z banku inter-
netowego, czy też wysyłając szyfrowanego maila. I
Jeśli jesteś zainteresowany zakupem licencji na wydawanie naszych pism prosimy o kontakt:
Monika Godlewska monikag@software.com.pl tel.: 48 22 887 12 66, fax: 48 22 887 10 11 to wszystko z wykorzystaniem PHP.

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


Spis treści

NARZĘDZIA
Zend Framework 42 Pytania dotyczące
Piotr Szarwas prenumeraty
Pojawienie się Zend Frameworka wzbudziło tel. (22) 887 14 44
w świecie PHP wiele emocji. Zend, jedyna praw- e-mail: pren@software.com.pl
Software Wydawnictwo Sp. z o.o.
dziwa firma od PHP, wypuściła – tak jak należa-
dział prenumeraty
ło – prawdziwy Framework dla PHP. Wszyscy
ul. Piaskowa 3
oczekiwali po tym rozwiązaniu bardzo wiele i ... 01-067 Warszawa
nie zawiedli się.
CD Strona WWW/Forum
tel. (22) 887 14 44 strona www: www.phpsolmag.org
eZ Components w akcji, e-mail: cd@software.com.pl Tu znajdą Państwo informacje
czyli galeria zdjęć krok Software Wydawnictwo Sp. z o.o. dotyczące aktualnych i przyszłych
po kroku 50 Defekty CD/DVD numerów magazynu PHP Solutions.
Tobias Schlitt ul. Piaskowa 3
01-067 Warszawa Forum: www.phpsolmag.org/newforum
Komponenty eZ stanowią w ogólnym ujęciu
Zachęcamy do dyskusji na naszym
opartą na PHP platformę do budowy rozwiązań Zamówienia forum. Czekamy na propozycje
biznesowych. Stosowanie tej wysokiej klasy ko- /Numery archiwalne tematów, które chcieliby Państwo
lekcji niezależnych bloków budulcowych znacz- tel. (22) 887 14 44 znaleźć w najbliższym numerze pisma.
nie przyśpiesza proces tworzenia oprogramo- e-mail: pren@software.com.pl Zapraszamy także do wymiany
wania we wspomnianym języku, zmniejszając sklep on-line: www.buyitpress.com poglądów z innymi fanami PHP.
jednocześnie ryzyko załamania się projektu. Kontakt z redakcją Cena
e-mail: redakcja@phpsolmag.org Prenumerata: 135 zł
Software Wydawnictwo Sp. z o.o. Przelew na konto nr:
TECHNIKI Redakcja PHP Solutions
ul. Piaskowa 3
46 1440 1299 0000 0000 0391 8238
Nordea Bank Polska S.A.
Dekorator: wzorzec projektowy 01-067 Warszawa II Oddział w Warszawie
na każdą bolączkę 62
Paweł Kozłowski Wyróżnieni betatesterzy: Ł. Witczak, K. Trynkiewicz, K. Kaczmarczyk, Ł. Jasiński, T. Skaraczyński,
P. Sobstel, M. Nowakowska, T. Tomczyk, T. Skaraczyński, K. Bąbol, B. Czech, K. Wdowicz,
Nazwa wzorca projektowego dekorator jest nie-
T. Malinowski, P. Plewa, W. Andruszkiewicz, A. Polit
co myląca, ponieważ sugeruje, że będziemy
coś wzbogacać, dekorować czy upiększać. Nic
bardziej błędnego! Omawiany wzorzec znajduje PHP Solutions jest wydawany przez Software-Wydawnictwo Sp. z o.o.
szerokie zastosowanie, niezależnie od tego, czy
Dyrektor Wydawniczy: Jarosław Szumski
projektujemy warstwę dostępu do bazy danych, Market Manager: Sylwia Tuśnio sylwia.tusnio@software.com.pl
logikę biznesową lub kontroler MVC. Product Manager: Maciej Krawcewicz maciej.krawcewicz@phpsolmag.org
Redaktor prowadzący: Dariusz Pawłowski dpawlowski@phpsolmag.org
Redaktor : Krzysztof Sobolewski krzysztof.sobolewski@phpsolmag.org
Metoda aktywnego rekordu 72 Stali współpracownicy: Paweł Kozłowski pkozlowski@phpsolmag.org,
Kierownik produkcji: Marta Kurpiewska marta@software.com.pl
Jakub Sacha Projekt okładki: Agnieszka Marchocka
Skład i łamanie: Sławomir Zadrożny slawekz@software.com.pl
Tworząc aplikacje internetowe na codzień, czę- Dział reklamy: adv@software.com.pl
sto zdarza się nam operować na listach danych Prenumerata: Marzena Dmowska pren@software.com.pl
Nakład: 6 000 egz.
np artykułów, autorów, wpisów w księdze gości,
nowości na stronie. Każda z list organizowana Adres korespondencyjny: Software-Wydawnictwo Sp. z o.o.,
jest w mniej lub bardziej złożone tabele w bazie ul. Piaskowa 3, 01-067 Warszawa, Polska
tel. +48 22 887 10 10, fax +48 22 887 10 11
danych. Dużą część czasu, poświęcanego na www.phpsolmag.org cooperation@software.com.pl
przygotowanie aplikacji zajmuje pisanie naprze-
Dołączoną do magazynu płytę CD przetestowano programem AntiVirenKit firmy G DATA Software Sp. z o.o.
miennie zapytań wstawiających lub edytujących
rekordy dla różnego rodzaju danych, różniących 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
się od siebie nazwami oraz ilością kolumn.
także poprawnego działania programów shareware, freeware i public domain.
Uszkodzone podczas wysyłki płyty wymienia redakcja.
Wszystkie znaki firmowe zawarte w piśmie są własnością odpowiednich firm
i zostały użyte wyłącznie w celach informacyjnych.
ZAPOWIEDZI Redakcja używa systemu automatycznego składu
W następnym numerze 82 Do tworzenia wykresów i diagramów wykorzystano program firmy

Osoby zainteresowane współpracą prosimy o kontakt: cooperation@software.com.pl


Zapowiedzi, artykułów, które planujemy do na-
stępnego wydania naszego pisma. 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 .

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


Aktualności

Najpopularniejsze
frameworki dla PHP
Wybierz najlepszy CMS
Na stronie http://www.phpit.net/article/ten-
different-php-frameworks/ znajdziemy ze-
stawienie dziesięciu najpopularniejszych
W ybór odpowiedniego CMS-a do
stworzenia portalu/witryny może
spędzić sen z powiek nie jednemu z nas,
frameworków dla PHP. Branych pod uwa-
gę jest kilkanaście cech w tym m.in: wyko- szczególnie, że jest w czym przebierać,
rzystanie PHP5, MVC, ORM, wsparcie dla
różnych baz danych czy AJAX-a. Z kolei na a różnice i zastosowania poszczególnych
stronie http://www.phpwact.org/php/mvc_ aplikacji są znaczne. Jak sprawdzić, któ-
frameworks znajduje się zestawienie ok. 40
frameworków implementujących wzorzec ry system odpowiada naszym potrze-
MVC. Wybierajmy jednak z głową (i maga- bom i będzie najlepszy do wykorzystania
zynem PHP Solutions).
właśnie w naszym przypadku? Tego do- Poza generowaniem porównań możemy
Seagull 0.6.0RC2 wiecie się w kilku ciekawych miejscach też oceniać wybrane rozwiązania i dysku-
Pojawiła się nowa wersja frameworka w Sieci. tować na forum.
Seagull, który oferuje możliwości z pograni-
cza zwykłego frameworka i CMS-a. Seagull Na stronie www.cmsmatrix.org znaj- Szukając CMS-a warto zajrzeć rów-
po zainstalowaniu zawiera kilka praktycz- dziecie nie tylko wykaz najpopularniej- nież na www.opensourcecms.com. Au-
nych modułów, co oszczędza czas na pisa-
nie tych komponentów samodzielnie.
szych systemów CMS wraz z ich szcze- torzy nie oferują wprawdzie żadnego po-
Framework jest kompatybilny z PHP4 gółowym opisem. Najciekawsze jest równania systemów, ale za to znajdzie-
i PHP5, korzysta z pakietów PEAR-a i jest narzędzie do generowania zestawień my tam bardzo efektywny podział pro-
łatwo integrowany z innymi aplikacjami. Je-
go dużym plusem jest silne wsparcie przez – porównań wybranych CMS-ów. Li- duktów na kategorie (CMS-y typu porta-
międzynarodową społeczności i dostępno- sta wyboru jest oszałamiająca i zawiera lowgo, blogi itd.) i świetne forum, na które
ści w wielu językach (nawet w chińskim).
W najnowszej wersji m.in. naprawiono wie- 570 pozycji!!! Wystarczy zaznaczyć od 2 trzeba zajrzeć przed wdrożeniem nowego
le błędów, zapewniono pełne wsparcie dla do 10 produktów i wygenerować tabelkę, (nieznanego) CMS-a.
PostgreSQL-a.
http://seagullproject.org/
w której znajdzie się porównanie. Do ze- Dla ciekawości dodamy, że już niedłu-
stawienia pod uwagę wzięto m.in.: wyma- go w naszym magazynie pojawi się test
Praca dla programistów PHP gania systemowe, bezpieczeństwo, ła- systemów CMS, który powstanie przy
PHP-freelancers.com to portal dla ludzi po-
szukujących programistów do szybkiego wy- twość użycia, wydajność, zarządzanie, współpracy z cmsmatrix.org i php.pl. Już
konania małych i dużych projektów w PHP elastyczność czy obecność aplikacji/mo- teraz gorąco polecamy!
oraz dla samych deweloperów PHP zainte-
resowanych pracami dorywczymi – zlece-
dułów wbudowanych, takich jak kalen-
niami na wykonanie projektu lub jego czę- darz czy system do wysyłki newslettera. http://www.cmsmatrix.org
ści. Jest w czym wybierać, projekty ma-
ja bardzo zróżnicowane ceny: od 10 do kil-
ku tysięcy dolarów. I tak za napisanie klasy
do porównywania dwóch fragmentów tekstu WebGUI

W
i wyświetlania różnic pomiędzy nimi moż-
na otrzymać od 10 do 300 dolarów. Do pro- ebGUI to CMS/framework do bu-
jektu zgłasza się z reguły kilku dewelope- dowania aplikacji klasy Enterpri-
rów oferując swoje ceny oraz czas realiza-
cji zadania.
se. Autorzy systemu chcieli stworzyć
http://www.php-freelancers.com/ CMS-a z możliwie najprostszą obsługą
i administracją, tak aby zbędny okazał
Integracja Propela
się helpdesk IT. Program zawiera in-
z Zend Framework
Na stronie Zend Developer Zone pojawiły sie terfejs administracyjny z funkcją dra-
nowe ciekawe tutoriale i artykuły. Na uwagę g&drop, wbudowany edytor WYSWIG
zasługuje tekst opisujący integrację Prope-
la (bardzo popularne narzędzie ORM) z Zend
działający pod IE i Mozillą, przechowuje
Frameworkiem. O Propelu pisaliśmy w nu- historie zmian treści serwisu, co umoż-
merze 5/2005, a o Zend Framework możecie
liwia ich cofanie, jak również oferuje
przeczytać w obecnym wydaniu naszego ma-
gazynu. Wśród innych materiałów na stronie HELP w JavaScript, który osobom nie- gacji w zbudowanym serwisie oraz przy-
znajdziemy też tutoriale o połączeniu znającym HTML-a pomoże odpowiednio jazne URL-e.
Frameworka ze Smarty (Zend Framework nie
ma póki co wbudowanego systemu szablo- sformatować dokument. WebGUI zawiera szereg gotowych
nów) i eZ components. WebGUI mile zaskakuje pod wzglę- do użycia modułów: newsy, artykuły, fo-
http://devzone.zend.com/node/view/id/184
dem bezpieczeństwa: posiada certyfi- ra, blogi, sondy, FAQ, galerie zdjęć, ka-
phpDocumentor 1.3.0RC6 kat bezpieczeństwa (OSI Certified Open lendarze czy nawet komponent agre-
Ukazała się nowa wersja najbardziej popular- Source Software), bezpieczne logowanie gujący i prezentujący oferty pracy. Sys-
nego oprogramowania do generowania doku-
mentacji aplikacji na podstawie kodu PHP. (SSL), system kopii zapasowych/wersji, tem posiada również bardzo elastycz-
Na temat phpDocumentora opublikowali- co umożliwia łatwe przywrócenie po- ny sklep internetowy z wbudowanymi
śmy obszerny artykuł w numerze 5/2004
(www.buyitpress.com). Wersja 1.3.x ma być przednio zapisanych treści oraz komer- modułami płatności. Dostęp do różnych
ostatnią z pierwszej linii, bo już zapowie- cyjne wsparcie. usług/produktów, które będziemy ofero-
dziano wcielenie 2.x. Najnowsza wersja w
Aplikacja nie rozczaruje również sa- wać, może być sprzedawany np. w po-
pełni wspiera PHP5, wykorzystuje PEAR::
XML_Beautifier do upiększania (formatowa- mych deweloperów: system jest modu- staci kodów dostępu na określony okres
nie komentarzy, dodawania znaków końca li- larny i bardzo łatwo rozszerzalny, pozwa- (np. tydzień).
nii itp.). kodu, koloruje kod HTML/XML czy
pozwala na wygodniejszą pracę z grafikami. la na łatwe tworzenie wersji językowych
Licencja: GPL czy nowych modułów. Wprowadzono też Licencja: GPL
http://pear.php.net/package/PhpDocumentor/
możliwość tworzenia indywidualnej nawi- http://www.webgui.org

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


Aktualności

MobiLog
eZ publish Conference 2006 MobiLog to opensourcowe narzędzie do mo-

M
bloggingu (mobile weblogging), czyli zarzą-
agazyn PHP Solutions został
dzania zawartością witryny z poziomu urzą-
głównym sponsorem konferencji dzenia przenośnego takiego jak telefon ko-
organizowanej przez firmę eZ systems, mórkowy czy PDA. Wystarczy pod wskazany
adres mailowy wysłać wiadomość, aby zaktu-
producenta takich znanych rozwiązań alizować dane na stronie WWW. Do działania
jak eZ publish i eZ components. Pierw- oprogramowanie potrzebuje: MySQL 4.1.1,
Perl 5.8, PHP 4.2 oraz kilka dodatkowych mo-
sza, tegoroczna edycja odbędzie się dułów Perla.
w Norwegii w dniach 21-23 czerwca. Licencja: BSD
http://www.accliptic.com/products/opensource/ml/
W ciągu trzech dni imprezy bę-
dzie można posłuchać wykładów na te- phc
mat m.in.: eZ components (Derick Re- Poza uczestnictwem w ciekawych phc to kompilator, który konwertuje kod PHP
na asemblera pod Linuksa. Może być uży-
thans, Tobias Schlitt), eZ publish (Bard wykładach, będzie również okazja do ty jako np. framework C++ do rozwijania róż-
Farstad), Open Source (Zak Greant), spotkania się z najlepszymi dewelope- nych narzędzi (np. obfuskatory, narzędzia re-
factoringu).
MySQL (Kristofer Petterson), OOP rami i firmami związanymi z PHP z ca- phc oferuje także interfejs do modyfikacji kodu
w PHP (Marcus Börger), testowania apli- łego świata. powstałego z PHP oraz jego konwersji z powro-
kacji z wykorzystaniem PHPUnit3 (Seba- tem do PHP.
Licencja: BSD
stian Bergman) czy zwiększania wydaj- http://ez.no/company/events/ez_publish_ http://www.phpcompiler.org
ności aplikacji PHP (Ilia Alshanetsky). conference_2006
Zend Framework wyżej niż
Microsoft .NET Framework
TYPO3 w wersji 4 Na razie tylko w wyszukiwarce, ale co cieka-
we w tej Microsoftu – msn.com :). Po wpisa-

T YPO3 to popularny CMS, który po- niu w wyszukiwarce słowa „framework” pro-
dukt Zenda ukazywał się jako pierwszy,
dobnie jak eZ publish zaliczany a framework Microsoftu jako drugi. Fakt ten
jest do oprogramowania klasy Enter- uchwycił Endi Gutmans i opublikował zrzut
z ekranu na swoim blogu.
prise. W nowej wersji systemu pojawi- http://andigutmans.blogspot.com/
ło się sporo zmian. CMS pracuje teraz
na PHP5 i posiada cały szereg nowych NuSphere PhpED 4.5
To jeden z najlepszych IDE dla PHP. W nowej
funkcjonalności. wersji udoskonalono podświetlanie składni
Panel administracyjny został całko- dla PHP4, PHP5, XML, XHTML, HTML, CSS,
Perl, Python, Javascript, ASP, SQL, C/C++
wicie przebudowany, dzięki czemu za- i Smarty.
rządzanie serwisem będzie dużo ła- Mamy też możliwość dynamicznego wyboru
sposobu/szablonu podświetlania składni, któ-
twiejsze. Standardowo do dyspozycji zwala na kontrolę i np. uniknięcie usu- re możemy wcześniej zdefiniować. Dodano
mamy edytory WYSWIG (HTMLArea), nięcia podstrony, do której linkujemy również wsparcie dla SQLite i Firebird (MySQL,
MSSQL, Oracle i PostgreSQL wspierane są już
opcję drag and drop do przenosze- z innego miejsca.
od dawna).
nia obiektów pomiędzy okienkami czy Polepszono silnik do wyszukiwa- Licencja: komercyjna, 299$
możliwość tworzenia tzw. workspaces, nia danych, dodano wsparcie dla m.in. http://www.nusphere.com

do pracy z grupą obiektów. Tworzenie Oracle'a i PostgreSQL-a oraz wprowa- PRADO 3.0.0
workspaces pozwala na edycję i zmia- dzono nowy oparty o XML system sza- Po dziesięciu miesiącach oczekiwania po-
jawiła się nowa wersja frameworka PRADO
ny nie tylko wersji live, ale i wersji ro- blonów – TemplaVoila. Dokonano rów-
(o frameworku tym pisaliśmy w numerze
boczych, które mogą być potem do- nież refaktoryzacji kodu. 5/2005). Przypomnijmy: jest to oparty na
wolnie wykorzystane. W ramach nowości warto jeszcze komponentach (component-based)
i na zdarzeniach (event-driven) framework
Grupy użytkowników mogą admi- wymienić możliwość generowania gra- dla PHP5, który zwyciężył w konkursie or-
nistrować innymi grupami, a zarzą- fiki za pomocą rozszerzenia GIFBU- ganizowanym przez firmę Zend ponad rok
temu. W nowej wersji system został całko-
dzanie dostępem do poszczególnych ILDER TypoScript, przechowywanie wicie przepisany, a deweloperzy położy-
części serwisu jest teraz dużo bardziej historii zmian czy pewne ulepszenia li szczególny nacisk na wydajność, nieza-
wodność oraz elastyczność. PRADO zo-
elastyczne. w zakresie bezpieczeństwa.
stał wielokrotnie nazwany RAD-em dla pro-
Dodano tablicę z odniesieniami, Dużym plusem TYPO3 była za- gramistów PHP5 i pod wieloma względami
w której zapisane są nazwy linków wsze 100% kompatybilność z po- przypomina ASP.NET.
Licencja: BSD
oraz obiekty na które wskazują. To po- przednimi wersjami. Tym razem prze- http://www.pradosoft.com/
chodząc do nowej wersji niestety nie
obejdziemy się bez małych modyfi-
E-mail Injections
Warto przyjrzeć się budowie formularzy
kacji w zakresie CSS (content rende- kontaktowych w naszych aplikacjach i za-
ring). Dobra wiadomość jest taka, że bezpieczyć przed wysłaniem spamu
z naszego serwera WWW. Atak polega na
można włączyć tryb kompatybilności dołączeniu dowolnych nagłówków do wysy-
z wersją 3.8 i nie przejmować się ty- łanego maila, co pozwala np. na spamowa-
nie. Aby się zabezpieczyć, wystarczy od-
mi zmianami. powiednio filtrować dane wprowadzane do
formularza.
http://securephp.damonkohler.com/index.php/
Licencja: GPL Email_Injection
http://typo3.com/

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


Aktualności

Stypendium na wakacje
– Google sponsoruje
Joomla
projekty Open Source
Google Summer of Code to program oferu-
jący stypendia studentom, którzy zdecydują
J oomla to system powstały na bazie
Mambo – bardzo popularnego, ale
też uważanego przez wielu za NIEprofe-
się na wykonywanie w czasie wakacji pro-
jektu pod opieką organizacji np. php.net. sjonalnego i mało bezpiecznego CMS-a.
Drupal czy Debian. Można otrzymać Mambo posiada ogromną społeczność
5000$, z czego 4500$ dostaje student,
a 500$ opiekun projektu. Lista opiekunów i jest używany przez tysiące serwisów
na edycję 2006 jest już zamknięta i liczy WWW. Jest bardzo łatwy we wdrożeniu
kilkadziesiąt pozycji. W dalszym ciągu jed-
nak mogą zgłaszać się studenci. php.net oraz utrzymaniu. Bez problemu można
już zgłosiło kilka propozycji np. konwerter pobrać gotowy, nowy szablon graficzny
z PHP4 na PHP5 (PHP6) (http://php.net/
czy wersję językową systemu. Wraz z wy-
ideas.php). W ramach projektu można roz-
wijać własne (rozpoczęte) projekty. Na daniem 4.5.3, w sierpniu 2005 roku, część weloperów Joomla – Forge.Joomla.org.
stronie Google znajdują się wyczerpujące deweloperów odeszła z Mambo, by roz- Dokonuje się częściowej refaktoryzacji ko-
informacje mówiące o tym jak przystąpić do
programu i jakie są jego zasady. począć nowy projekt – tak powstał Joom- du, polepsza dokumentację, tworzy sekcje
http://code.google.com/soc/ la. Nowe Mambo wygląda na projekt sil- online dla testerów – wszystko po to, aby
php.MVC nie rozwojowy – powstają już wersje be- zapewnić lepszy rozwój projektu. Road-
php.MVC to w pełni obiektowy framework ta, które oferują tzw. Multisite CMS (jedna map na stronie Joomla mówi o wielu po-
MVC. Korzysta z Pear::DB Database Abs- instalacja pod wiele domen) czy wsparcie ważnych zmianach, które mają nastąpić
traction Layer, jest wyjątkowo bezpieczny
(aplikacje posiadają jeden tzw. entry dla 11 serwerów baz danych. Szczególny w niedalekiej przyszłości: dalsza praca
point). Oferuje możliwość łatwego zarzą- nacisk położono na kwestie bezpieczeń- nad funkcją Multisite, system kontroli wer-
dzania sekwencją akcji i statycznymi tre-
ściami. Każda aplikacja posiada XML-owy stwa naprawiając wiele poważnych błę- sji, wirtualny system plików, menedżer ak-
plik konfiguracyjny. Framework implemen- dów w aplikacji (zlikwidowano m.in. podat- tualizacji wersji czy wsparcie dla MySQL5.
tuje klasę LookupDispatchAction(),
która mapuje butony z formularzy HTML na
ności na SQL Injections w module Sond
odpowiednie metody logiki biznesowej (co i podczas aktywacji użytkownika oraz Licencja: GPL
umożliwia ich wykonywanie). Projekt Email Injections w funkcji „Email from http://www.joomla.org
powstał na bazie znanego ze świata Javy
frameworka Struts, co stanowi jego dosko- Friend”). Powstało nawet centrum dla de- http://www.mamboserver.org
nałą rekomendację.
Licencja: LGPL
http://www.phpmvc.net/
Zarządzanie projektami PHP w PHP
Zend Guard 4
Zend Guard to oprogramowanie, któ-
re zapewnia kompleksową ochronę ko-
du źródłowego aplikacji PHP, które chcemy
P anowanie nad dużą liczbą projektów
i przypisanych im zadań to nie la-
da wyzwanie dla kierownika zespołu lub
rozpowszechniać/sprzedawać. W skład pa-
kietu wchodzi enkoder (kodowanie źródła), samego programisty. W PHP zosta-
obfuskator (zaciemnianie kodu) oraz mene- ło stworzonych kilka dobrych systemów,
dżer licencji pozwalający np. na ograniczanie
czasu działania oprogramowania czy bloko-
które usprawniają zarządzanie projektami
wanie adresów IP. Dodatkowo pakiet zwięk- i zadaniami. Mamy do dyspozycji progra-
sza wydajność aplikacji dzięki zastosowaniu my zaawansowane takie jak dotProject
nowych technik optymalizacji kodu.
Licencja: komercyjna, $995 (http://www.dotproject.net/) czy phpCol-
http://www.zend.com/products/zend_guard lab (http://www.php-collab.org/), któ-
Maguma za darmo re oferują bardzo wiele opcji: dzielenie go dodanego projektu dodajemy zada-
Maguma znana jest z dwóch komercyjnych, projektów na etapy (wykres Gantta), do- nie wraz z terminem zakończenia i opi-
profesjonalnych i silnie rozwojowych produk-
dawanie użytkowników z rożnymi upraw- sem. Do dyspozycji mam także m.in. li-
tów: Maguma Workbench oraz
Maguma Studio. Na sourceforge.net nieniami, fora dyskusyjne, kalendarze sty, checklisty czy możliwość przeglą-
(http://sourceforge.net/projects/openstudio) czy menedżery plików. Wymienione pro- dania raportów i podsumowań. Pro-
można pobrać darmową wersję Maguma
Open Studio, która różni się od Magumy Stu- gramy można łatwo rozwijać budując jekt GTD-PHP jest opensourcową wer-
dio brakiem menedżera CVS oraz pojawiają- własne moduły, zmieniając szablony czy sją komercyjnego oprogramowania GTD
cym się okienkiem z informacjami o licencji.
Niestety, tak jak jej komercyjny odpowiednik,
dodając kolejną wersję językową. Sam (http://www.davidco.com/).
IDE dostępne jest jedynie pod Windows. dotProject oferuje wsparcie dla kilkudzie- Przed ostatecznym wyborem syste-
Licencja: MPL 1.1 (Mozilla Public License)
sięciu języków. mu zachęcamy do wypróbowania wersji
http://www.maguma.com
http://sourceforge.net/projects/openstudio Jeśli jednak potrzebujemy czegoś DEMO obecnych na stronach poszcze-
znacznie prostszego – możliwości doda- gólnych projektów.
Zend/PHP wania projektu (wraz z kategoriami) oraz
Conference&Expo 2006
W dniach 29 października – 2 listopada 2006 przypisywania im zadań – to dobrym
w San Jose, USA odbędzie się kolejna edycja rozwiązaniem może być lekki program
konferencji PHP organizowanej przez firmę
o nazwie Getting Things Done (http:// Licencja: http://gtd-php.sourceforge.net/
Zend. Częścią imprezy będą również warsz-
taty oraz szkolenia przygotowujące do certyfi- gtd-php.sourceforge.net/). Do działania gtd/about.php
katu Zend Certified Engeenier. potrzebuje PHP4/5 oraz MySQL w wer- http://www.dotproject.net/
http://zendcon06.kbconferences.com/
sji 4.x. Korzystanie z programu jest http://www.php-collab.org/
bardzo proste i intuicyjne. Do każde- http://gtd-php.sourceforge.net

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


Opis CD

PHP Solutions Live

T ę wersję PHP Solutions Live zbudo-


waliśmy, opierając się o dystrybucję
Aurox i skrypty automatycznej generacji
graficzne KDE oraz dodanie wielu przy-
datnych programistom aplikacji. Teraz,
oprócz kompletnego środowiska LAMP
(www.aurox.org/pl/live). Narzędzia dystry- (Linux, Apache, MySQL, PHP) w syste-
bucji, które nie znajdują się na dołączonej mie gotowe do użycia są środowiska pro-
do pisma płycie, instalowane są z repozy- gramistyczne, takie jak: Eclipse, Nvu, Qu-
torium Auroxa za pomocą programu yum. anta, BlueFish oraz wiele innych charak-
W porównaniu z poprzednią wersją terystycznych dla KDE i Linuksa narzędzi.
PHP Solutions Live zasadniczą zmianą PHP Solutions Live – to w pełni funk-
jest przejście z Fluxboksa na środowisko cjonalny system operacyjny, który znajdu-

Rysunek 2. Ekran powitalny PHP


Solutions Live z opcjami uruchomienio-
wymi
je się na płycie dołączonej do pisma. Jest
to bootowalna dystrybucja Auroxa, zawie-
rająca bogaty zestaw przydatnych pro-
gramistom i webmasterom narzędzi, do-
kumentację, tutoriale i materiały dodat-
kowe do artykułów. System został przy-
gotowany w taki sposób, by zaraz po uru-
chomieniu systemu można było testować
i tworzyć nowe skrypty.
Aby zacząć pracę z PHP Solutions Li-
ve, wystarczy uruchomić komputer z CD.
Po uruchomieniu systemu zostaniemy
automatycznie zalogowani w systemie,
a po krótkiej chwili powinna uruchomić się
przeglądarka Mozilla Firefox wraz z Menu
przedstawiającym zawartość płyty zwią-
Rysunek 1. Pulpit PHP Solutions Live ze skrótami do aplikacji. zaną z bieżącym numerem.

Materiały dodatkowe

M ateriały dodatkowe zostały umiesz-


czone w następujących katalogach:
licencyjnymi, które są dokładnie sprecy-
zowane w pliku Readme, dostarczanym
wraz z dystrybucją aplikacji. W tym sa-
activestate.com /Products /Download /
Download.plex?id=Komodo
Dla tych czytelników PHP Solutions,
l Tutoriale video – trzecia część multi- mym pliku opisano szczegółowo różni- którzy zainteresowani są pełną wer-
medialnego kursu, zatytułowana user ce pomiędzy wersją 3.5.5 oraz najnow- sją programu, firma ActiveState oferuje
signup, szymi, dostępnymi na stronie producen- zniżkę w wysokości 100$ (standardowa
l Aplikacje – hity numeru, w bieżącym ta – firmy Gecko Tribe. CaRP Koi 3.5.5 cena programu to 295$). Aby skorzy-
numerze: CaRP Koi 3.5.5 – konwerter jest napisany w PHP dzięki czemu łatwo stać ze zniżki należy do 21 lipca;
RSS do HTML oraz Komodo IDE. można go wykorzystać na serwerze. We
l e-books – książki i inne dokumenty wspomnianym pliku Readme, znajduje l odwiedzić stronę
w formacie PDF się także opis funkcjonalności programu, http://www.ActiveState.com/
instrukcja instalacji oraz zbiór odpowie- PHPSolutions
Program CaRP Koi 3.5.5 firmy GeckoTri- dzi na najczęściej zadawane przez użyt- l wybrać opcję „Buy”
be dostępny jest w pełnej wersji komer- kowników pytania. l wpisać kod „PHPSOL”
cyjnej w licencji dla jednego Webmaste- Druga komercyjna aplikacja za-
ra. Jest to w pełni funkcjonalna wersja, mieszczona na płycie to Komodo 3.5 W przypadku przeglądania płyty z pozio-
której można używać w komercyjnych Professional, czyli profesjonalne śro- mu uruchomionego PHP Solutions Live,
i prywatnych zastosowaniach, a jedynym dowisko IDE dla programistów. Na CD a nie systemu operacyjnego zainstalowa-
ograniczeniem jest to, by każdej kopii z zamieściliśmy 30 – dniowy trial, któ- nego na dysku komputera, wymienione
pisma używał tylko jeden Webmaster. ry można uruchomić przy pomocy kodu aplikacje i materiały dostępne są z podka-
Zalecamy zapoznanie się z warunkami dostępnego pod adresem: http://www. talogu /mnt/cdrom.

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


Na CD
Przetestuj dowolne skrypty
bez instalacji!

3 nowe książki elektroniczne


Auditing your web site security
An Introduction to Web Services Enabled with PHP HIT
PHP5 Power Programming Pełna wersja komercyjnego programu CaRP Koi
firmy Gecko Tribe o wartości około $20
Aplikacje
PHP Compiler Nowy kurs multimedialny
Komodo Professional 3.5 PHP IDE user signup

Narzędzia w dystrybucji
Kompletne środowisko LAMP

Opensource’owe IDE dla developerów:


Nvu, Bluefish, Quanta, Eclipse
i wiele innych aplikacji
na naszej stronie internetowej pod adresem www.phpsolmag.org/pl

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


Wszystkie listingi z artykułów zostały zamieszczone

Wszystkie listingi z artykułów zostały zamieszczone


Wywiad

Wywiad z Dmitrim Stogovem – programistą


Zend i twórcą Turck MMcache

Dmitry Stogov jest jednym z inżynierów


rozwojowych w firmie Zend i szefem rosyjskiego
zespołu tej firmy. Należy do grona twórców Zend
Engine 2. Stworzył też Turck MMCache i szereg
rozszerzeń SOAP i Perl dla PHP5.

Dariusz Pawłowski: Jak zaczęła się Two- szy technicznie był projekt www.guestbo- wał pamięci współdzielonej i nie miał żad-
ja przygoda z PHP? oks4all.com. Aplikacja pozwalała na two- nego optymalizatora. Po dłuższym namyśle
Dmitry: To było w roku 2000, kiedy rzenie własnych ksiąg gości dostosowa- postanowiłem więc stworzyć projekt MMCa-
pracowałem w Sankt Petersburgu w firmie nych do potrzeb konkretnej witryny bez che na wewnętrzne potrzeby naszego ser-
Turck Software (wbrew nazwie firmie nie- konieczności programowania. wisu ksiąg gości. Do moich głównych zain-
mieckiej, nie tureckiej). Miałem już wtedy DP: Jesteś autorem Turck MMCache. teresowań programistycznych należały bu-
jakieś 10 lat doświadczenia w programo- Skąd wziął się pomysł projektu i jakie były dowa kompilatorów i optymalizacja kodu,
waniu, ale nigdy nie programowałem dla jego pierwotne założenia? więc MMCache powstawał szybko i w krót-
Internetu – nie znałem języków HTML, Ja- Dmitry: Potrzeba stworzenia MMCa- kim czasie zaczął dawać wyniki porówny-
vaScript czy Perl, a o PHP nawet nie sły- che pojawiła się wraz z rosnącą popular- walne z produktami komercyjnymi. Uzna-
szałem. Gdy pewnego dnia otrzymałem nością serwisu ksiąg gości, kiedy to zaczy- liśmy, że nierozsądnie byłoby trzymać tak
zadanie napisania aplikacji internetowej, naliśmy nawet rozważać możliwość wpro- dobre oprogramowanie wyłącznie do użyt-
zacząłem próbować różnych możliwych wadzenia klastra serwerów. Na szczęście ku wewnętrznego, a zarazem nie chcieli-
technologii. Spróbowałem JSP, skryp- znaleźliśmy czysto programistyczny spo- śmy go sprzedawać, więc postanowiliśmy
tów Perla, jakiegoś komercyjnego CMS-a sób wydajnego obsłużenia rosnącej licz- udostępnić MMCache na licencji GPL.
opartego na TCL, aż w końcu stanęło na by wizyt: składowanie skompilowanych Do końca mojej pracy w Turck Softwa-
PHP (wtedy było to PHP3). kodów stron PHP. Zaczęliśmy szukać ist- re utrzymywałem projekt MMCache, a je-
Moją pierwszą aplikacją w PHP był niejących, darmowych rozwiązań. Postano- go popularność gwałtownie rosła. Pod ko-
system śledzenia czasu pracy. Całość wiliśmy odrzucić rozwiązania o zamkniętym niec 2003 r. właściciel Turck Software po-
mieściła się z 500 wierszach kodu, chodzi- kodzie źródłowym, więc pozostało nam je- stanowił zakończyć działalność w branży
ła na Linuksie i używała bazy IBM DB2 do dyne wówczas oprogramowanie open sour- oprogramowania, a dla mnie nadeszła po-
składowania danych. W ciągu dwóch lat w ce tego typu, czyli Afterburner. Niestety, da- ra rozejrzenia się za nową pracą.
Turck Software opracowaliśmy kilka apli- wał on zaledwie połowę przyrostu wydaj- DP: Obecnie jesteś szefem rosyjskie-
kacji internetowych, z których najciekaw- ności produktów Zend i ionCube, nie uży- go zespołu firmy Zend. Jak trafiłeś do

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


Wywiad

Zenda? Czy to prawda, że Zend „kupił” miasto (może z wyjątkiem mrozu zimą, nologies i inne firmy przez cały czas mają
Cię, gdyż Turck MMCache stanowił kon- brudu na wiosnę, komarów latem i desz- te cechy na uwadze i pracują nad stopnio-
kurencję dla własnych produktów Zenda? czu na jesieni) i nie chciałbym mieszkać wym zbliżaniem PHP do statusu rozwiąza-
Dmitry: I tak, i nie. Do pracy w Zen- nigdzie indziej. nia klasy Enterprise.
dzie zgłosiłem się sam. Oczywiście wie- Najczęściej zajmuję się zadaniami dłu- Jedynym istotnym pojęciem, jakiego
działem, że znają mnie jako twórcę goterminowymi, więc nierzadko nie kon- brakuje w samym języku PHP, jest coś
MMCache, więc był to dla mnie dodat- taktuję się z Zendem nawet przez kilka w rodzaju modułu lub pakietu, coś co po-
kowy atut w negocjacjach, zwłaszcza że tygodni. Gdy kontakt jest konieczny, poro- zwoliłoby projektować i tworzyć rozbudo-
wtedy nie prowadzili jeszcze działalności zumiewamy się za pośrednictwem pocz- wane aplikacje bez konieczności zajmo-
na terenie Rosji. Wiedziałem też, że bę- ty elektronicznej, MSN lub Skype’a. Głów- wania się rozwiązywaniem konfliktów po-
dę musiał zakończyć prace nad projek- ne biura Zenda znajdują się Izraelu i USA, między poszczególnymi podsystemami.
tem MMCache, ale i tak nie widziałem więc nawet zwykli pracownicy firmy komu- DP: Dlaczego Twoim zdaniem tak wie-
już dla niego przyszłości – Zend Engine nikują się przede wszystkim tymi drogami. le firm unika PHP, wybierając na przykład
w PHP5 zmieniał się zbyt szybko, więc Zend jest główną siłą napędową roz- ASP.NET, JSP lub J2EE? Czy znasz jakiś
dalsze prace nad MMCache polegałyby woju PHP, ale w końcu nie tworzy go bank internetowy wykorzystujący PHP ja-
raczej na ciągłej adaptacji do zmian niż na w pojedynkę. Często rozmawiam ze ko główną technologię?
faktycznych pracach rozwojowych. Uzna- świetnymi programistami należącymi do Dmitry: Tu się nie zgodzę. Wiele firm
łem więc, że więcej dobrego zdziałam pra- społeczności twóców PHP. Z niektórymi korzysta z PHP, ale używa go przede
cując nad samym Zend Engine. spotkałem się w Paryżu na ostatnim PHP wszystkim zgodnie z jego pierwotnym
Po kilku miesiącach pracy w Zendzie Developers Meeting, ale często nic nie przeznaczeniem, czyli jako technologii
otrzymałem taką okazję i zająłem się wpro- wiem o osobie na drugim końcu łącza (nie tworzenia interfejsu WWW. Mniejszym fir-
wadzaniem ulepszeń modułu wykonaw- wiem nawet, czy to on, czy ona). mom nierzadko wystarcza samo PHP, ale
czego w PHP-5.1 dających niemal dwu- Zespół programistów rosyjskich to większe przedsiębiorstwa na ogół mają
krotnie szybsze wykonywanie surowego w dużej mierze moi starzy znajomi (niektó- bardziej rozbudowane systemy informa-
kodu PHP niż w przypadku starszych wersji. rzy również z Turck Software). Z począt- tyczne, których poszczególne elementy
Oczywiście nie ja sam wymyśliłem wszyst- ku spotykaliśmy się raz w tygodniu, ale korzystają z bardziej odpowiednich tech-
kie usprawnienia, ale ich implementacja jest po doszlifowaniu procesu prac nad pro- nologii i języków. Nie sądzę, by PHP miał
moim dziełem. Jestem więc bardzo zado- jektami jesteśmy już w stanie rozwiązy- kiedykolwiek nadawać się do absolutnie
wolony z pracy w Zendzie i mam nadzieję, wać większość trudności bez konieczno- wszystkich zadań.
że firma jest równie zadowolona ze mnie. ści spotykania się. Obecnie spotykamy się Co do banków internetowych, to na
Pod koniec 2004 r. powstał projekt więc w czasie wolnym, a nie w pracy. pewno wiem, że takie istnieją. Na przy-
Zend Core, którego część Zend postano- Uruchomienie nowego projektu wyma- kład Dresdner Bank korzysta z aplika-
wił realizować w Rosji. Tak powstał rosyjski ga niekiedy wizyt w biurze Zenda w Izra- cji PHP do dostarczania klientom usług
zespół Zend, którego szefem zostałem ja. elu. W styczniu tego roku wszystkich pra- finansowych i informacji. Z grona użyt-
DP: Nad czym obecnie pracujesz dla cowników zaproszono do Eilat na obcho- kowników PHP mogę też wymienić wiele
Zenda? dy szóstych urodzin firmy. znanych firm: Hewlett-Packard, Boeing,
Dmitry: Wszystkich tajemnic nie mo- DP: Co Twoim zdaniem będzie naj- Lufthansa, Disney Online, Yahoo!, Lycos,
gę ujawnić, ale mniej więcej połowę cza- bardziej potrzebne, by PHP osiągnął Sprint, T-Mobile, Orange, Wall Street On-
su zajmuje mi praca nad PHP6 (obsłu- status platformy klasy Enterprise z praw- line, Siemens...
ga Unikodu) i wprowadzanie ulepszeń dziwego zdarzenia, porównywalnej na Dlaczego tak wiele firm nadal używa
i poprawek w istniejących wersjach PHP przykład z .NET? JSP, ASP czy nawet CGI, Perla, TCL i C++
i w moich rozszerzeniach. Oczywiście Dmitry: PHP nie da się bezpośred- do generowania treści WWW? Nie wiem,
uczestniczę też w komercyjnych projek- nio porównać ze środowiskami w rodza- może nie chcą mieszać różnych techno-
tach Zenda. W przeszłości miałem swój ju .NET czy J2EE. PHP to po prostu język logii lub dopiero zaczęli rozważać PHP :).
udział w tworzeniu rodziny produktów programowania specjalizowany dla aplika- Pewnie PHP jest jeszcze za młode.
Zend Enabler (nie WinEnabler, ale na cji internetowych, więc bardziej na miej- DP: Jakie masz plany na przyszłość?
przykład SunOne Enabler i Shared Ho- scu byłoby porównywanie go z Java Se- Dmitry: Podczas pracy nie mam cza-
sting Enabler), byłem przez jakiś czas rver Pages czy ASP.NET, i moim zdaniem su na rozmyślanie nad nowymi pomysła-
szefem technicznym projektu Zend Core w tym towarzystwie PHP jest najlepsze. mi. Większość moich wysiłków skupia
i wprowadziłem nieco ulepszeń w projek- Platformę klasy Enterprise powinny się obecnie na PHP6 (obsługa Unikodu,
tach Zend Optimizer i Zend Accelerator. jednak cechować takie zalety, jak skalo- ulepszenia języka, dodatkowa optymali-
DP: Jak wygląda Twoja codzienna walność, niezawodność, łatwość utrzyma- zacja). Gdy zacznie się zbliżać premiera
praca? Mieszkasz i pracujesz w Rosji, nia, łatwość i szybkość tworzenia aplikacji PHP6, wtedy wraz z całą społecznością
więc jak kontaktujesz się z Zendem? Jak ze sprawdzonych komponentów, dostęp- twórców PHP zaczniemy zapewne zbie-
często masz spotkania i wyjazdy? ność licznych bibliotek i narzędzi wspoma- rać nowe pomysły, które być może trafią
Dmitry: Pracuję w domu, w Sankt Pe- gających programowanie, solidne wspar- do PHP7.
tersburgu. To piękne i bardzo przyjemne cie techniczne i tym podobne. Zend Tech- DP: Dziękuję za rozmowę. n

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


Początki

Programowanie obiektowe
w PHP na przykładzie IRCBota
Stopień trudności: lll
Marcin Staniszczak

Pewnie słyszałeś już termin programowanie


obiektowe i ktoś przedstawiał Ci jego zalety.
Dobrze, ale jak je zastosować w PHP? Wraz
z wersją PHP5 pojawiło się wiele nowych
funkcjonalności, które możesz wykorzystać
w swoich aplikacjach: pokażemy Ci, jak to
zrobić, tworząc prostą, ale użyteczną aplikację
sieciową...

W
raz z nadejściem PHP5 otrzy- którzy ich nie czytali, przybliżamy w skró-
maliśmy w końcu możliwość cie ideę obiektowości. Jej podstawą jest
pisania programów obiekto- obiekt – odrębny, zamknięty fragment ko-
wych z prawdziwego zdarzenia. Co wię- du, zawierający metody (czyli funkcje) mo-
cej, ta wersja PHP jest oferowana przez gące przeprowadzić określone czynności
coraz większą liczbę providerów, co za- oraz pola (czyli zmienne) zwane też atry-
chęca coraz większą liczbę programistów butami lub cechami, które przechowują
W SIECI do korzystania z niej w swoich projektach. dane (np. łańcuchowe, liczbowe czy in-
Warto jednak pamiętać, że progra- ne obiekty). To, że obiekt jest zamknięty,
mowanie obiektowe wiąże się ze zmia- uniemożliwia przypadkowe interakcje je-
l http://www.faqs.org/rfcs/ ną sposobu myślenia o aplikacji. Teraz go metod i pól z innymi elementami pro-
rfc2812.html – RFC 2812 w mniejszym stopniu myślimy o funkcjach gramu (np. innymi obiektami), pozwalając
opisujące IRC
l http://www.cybernexus.biz/ (zwanych w klasach metodami), czyli ze- jednocześnie wykorzystać obiekt w dowol-
irc/default.htm – informacje o stawie instrukcji do wykonania konkret- nym miejscu aplikacji (dając tym samym
IRC w przystępnej formie
l http://www.phppatterns.com nych zadań, a w coraz większym o obiek-
– strona o programowaniu tach modelujących tworzony system.
obiektowym i wzorcach pro- Co należy wiedzieć...
jektowych w PHP Należy znać podstawy programowania
l http://www.php.net/manual/ Czym jest programowanie w PHP. Przydatna będzie również znajo-
en/language.oop5.php – ofi- obiektowe? mość protokołu i usług IRC.
cjalny manual PHP, rozdział
nt. obiektowości w PHP5 O programowaniu obiektowym już pisali-
l http://en.wikipedia.org/wiki/ śmy na naszych łamach, choćby w arty- Co obiecujemy...
Anti-pattern – antywzorce, Pokażemy, jak stworzyć bota IRC-owe-
czyli jak NIE należy progra- kułach Erika Zoltana „Po co nam PHP5”,
go, kładąc szczególny nacisk na demon-
mować „Medoty magiczne w PHP5”, zamiesz-
strację możliwości obiektowych PHP5.
czonych w numerze 5/2005. Tym z Was,

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


IRCBot – programowanie obiektowe Początki

dużą elastyczność) i nazywa się enkapsu-


lacja, którą omówimy później.
 
Każdy obiekt należy do jakiejś klasy
i jest jej instancją (czyli egzemplarzem),
przykładowo obiekt lampkaPrzyMoimŁóżku  

może należeć do klasy lampkaNocna. Sko-  


jarzenia ze światem materialnym nasuwa-


ją się same: dzięki obiektom łatwo zasy-
mulujemy przedmioty i zjawiska występu-
jące w naszej rzeczywistości. Modelowa-   
   


   
nie rzeczywistości zaczynamy od wdraża-
nia głównych cech, a następnie przecho-
 
  
  

dzimy do kolejnych detali.  
  
  

Kontynuujmy nasz przykład z lampką  



 


nocną. Możemy utworzyć klasę o nazwie
lampkaNocna, która będzie zawierała me-
tody zgaś() i zapal(). Pominiemy mniej
istotne szczegóły, takie jak np. kolor czy Rysunek 1. Klasa lampka i jej klasy pochodne
kształt obudowy lampki. Następnie, chcąc
umieścić lampkę nocną w naszym progra- wystąpią we wszystkich klasach pochod- jedną z najmocniejszych zalet programo-
mie, po prostu utworzymy instancję klasy nych. W przypadku lampy będą to metody wania obiektowego.
lampkaNocna. zapal() oraz zgaś(). Moglibyśmy dodać Teraz bazując na klasie lampka mo-
Co jednak będzie, jeżeli zechcemy za- do niej również pole żarówka, ale co zrobić żemy utworzyć klasy pochodne (dziedzi-
symulować również lampę biurową, lam- z lampami, które zawierają np. świetlów- czące): lampkaNocna oraz lampkaBiurowa.
pę wiszącą czy latarnię uliczną? Czy bę- kę? Rozsądnym wyjściem byłoby utworze- Zwróćmy uwagę, jak bardzo jest to wy-
dziemy za każdym razem tworzyć no- nie drugiej klasy, np. źródłoŚwiatła, z któ- godne. Możemy teraz zasymulować jed-
wą klasę od podstaw (np. lampaWisząca rej będzie korzystać klasa lampka i która nego człowieka (klasa człowiek) potra-
i latarniaUliczna)? Mijałoby się to z sen- również będzie posiadała klasy pochodne. fiącego obsługiwać wszystkie utworzone
sem, gdyż: Dodane właśnie metody zapal() przez nas lampki. Co więcej, podczas pi-
i zgaś() istnieją wewnątrz klasy lampka. sania klasy człowiek nie musimy nawet
• część elementów tych klas (jak meto- Inna, całkowicie odrębna klasa, np. wiedzieć, jakie lampki będzie on obsługi-
dy zgaś() i zapal()) będzie się pokry- znicz, mogłaby mieć metody o takich wał, ani ile ich będzie. Istotne jest tylko,
wała, samych nazwach i nie kolidowałyby one aby dziedziczyły one po klasie lampka.
• jeśli wymodelujesz sobie człowieka w żaden sposób z metodami lampki. To Niestety, nie jest powiedziane, że
(lub robota) obsługującego lampkę, samo dotyczy pól. Dostęp do nich jest nasz człowiek będzie potrafił w pełni wy-
będziesz musiał implementować mu możliwy jedynie przy podaniu nazwy kla- korzystać każdą nową lampkę: klasa po-
obsługę każdej lampki z osobna. sy (lub jej obiektu), w której zostały zde- chodna może bowiem rozszerzać li-
finiowane. To jest właśnie enkapsulacja stę metod i pól o własne, o których nasz
Lepiej więc byłoby zacząć od utworzenia (lub hermetyzacja): zamknięcie metod człowiek nie będzie nic wiedział (pamię-
bardziej ogólnej klasy lampka, będącej ba- i pól wewnątrz klasy. Bardzo ułatwia ona tajmy, że zna on jedynie klasę lampka).
zą dla wszelkiego rodzaju dziedziczących tworzenie oprogramowania, szczegól- Zobacz Rysunek 1. Prezentujemy tam
po niej lamp, lampek, reflektorów, latarń nie dużych projektów (wystarczy sobie klasy wszystkich lampek włącznie z no-
czy latarek kieszonkowych. Ta podstawo- wyobrazić jakikolwiek interfejs graficz- wą, którą jest klasa LampkaPrzyciemniana,
wa lampa powinna zawierać wyłącznie ny, w którym najmniejszy przycisk ma kil- symulująca lampkę z regulacją jasno-
najbardziej ogólne pola i metody: te, które kadziesiąt własnych cech i funkcji) i jest ści (sciemniaczem). Obok standardo-
wych metod zapal() oraz zgaś() posia-
da ona także metody rozjaśnij() oraz
Programowanie obiektowe – historia i teraźniejszość sciemnij(). Nasz człowiek potrafiący ob-
Programowanie obiektowe jest już dojrzałą metodologią projektowania oprogramowa- służyć obiekty klasy lampka, po której
nia komputerowego. Stworzona już w 1967 roku Simula posiadała pewne cechy języka
dziedziczy nasza lampkaPrzyciemniana,
obiektowego, zaś w latach 70. ubiegłego wieku Xerox pracował nad językiem Smalltalk
– do dziś niedoścignionego wzorca w dziedzinie obiektowości. Z bardziej znanych, często będzie potrafił ją zapalić oraz zgasić, ale
stosowanych języków wystarczy wymienić C++ czy Javę. Przed pojawieniem się PHP5, nie będzie umiał jej przyciemnić czy rozja-
w PHP było znacznie gorzej: teoretycznie już w PHP4 można było korzystać z pewnych śnić. Niestety, nie można mieć wszytego.
technik obiektowych, ale było to niewydajne i zawodne. Prawdziwy, rewolucyjny przełom
Istnieją co prawda sposoby obejścia tego
nastąpił wraz z nadejściem PHP5, która oferuje nowy, solidny silnik obiektowy oraz wie-
le użytecznych cech, których poprzednio brakowało, a które czynią z PHP język obiektowy problemu, nie są one jednak tak elastycz-
z prawdziwego zdarzenia. ne i nie są w pełni zgodne z podejściem
obiektowym.

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


Początki IRCBot – programowanie obiektowe

Podsumowując, najistotniejsze cechy klasy (tego, czy będzie to klasa ba-


programowania obiektowego to: zowa, czy określona klasa pochod- Szybki start IRCBota
na – można to sprawdzić) czy od ilo- Jeśli chcesz sprawdzić, jak działa nasz
• Abstrakcja – oznacza, że każda kla- ści lub typu parametrów (przeciąże- bot, pobierz jego źródła z naszej strony
WWW. Możesz go uruchomić z linii pole-
sa w programie opisuje pewien model nie parametrów). Przykładowo, meto-
ceń lub w przeglądarce internetowej.
rzeczywistości, będąc najczęściej swo- da zapal() może działać zupełnie ina- Przed uruchomieniem bota musisz
istą abstrakcją obiektów rzeczywistych czej w przypadku lampki nocnej z ża- go skonfigurować. Ustawienia znajdują
(jak nasza lampka). Dzięki temu progra- rówką, niż w przypadku lampy sufito- się w pliku Configuration.class.php. Po-
nadto musisz zmodyfikować wpis w pli-
mista może operować bezpośrednio na wej ze świetlówką.
ku admins – zmień tam nicka oraz host,
modelowanych obiektach, modyfiko- • Dziedziczenie – możliwość tworzenia spod którego będziesz się łączył z ser-
wać oraz poznawać ich aktualny stan. klas pochodnych opartych o klasę ba- werem IRC (nie dodawaj ręcznie nowych
• Enkapsulacja (hermetyzacja) – za- zową. użytkowników – po zalogowaniu się mo-
żesz nimi zarządzać za pomocą komend
mknięcie metod i pól wewnątrz klasy,
add_admin: oraz remove_admin: – pa-
co wyklucza ich ewentualne kolizje z Obiektowość w PHP5 miętaj o dwukropku).
metodami i polami innych klas. Dzięki Dzięki nowemu silnikowi PHP, Zend II, Aby uruchomić bota z linii poleceń,
niej każda klasa jest odrębnym bytem. modelo obiektowy PHP5 jest niezawod- przejdź do jego katalogu i wydaj pole-
cenie: php ConsoleBOT.class.php. Pa-
W połączeniu z możliwością określe- ny i wydajny i ma wiele nowych cech, ta-
miętaj, że aby to polecenie zadziała-
nia widoczności zmiennych (publicz- kich jak: ło, potrzebujesz odpowiednio ustawio-
ne, prywatne lub chronione – omówi- nej ścieżki PATH: musi ona wskazywać
my to później), enkapsulacja stanowi • możliwość określenia widoczności na katalog z PHP. Alternatywnie, możesz
podać pełną ścieżkę dostępu do progra-
jedną z najmocniejszych zalet progra- (ang. visibility) każdej metody czy po-
mu php (lub php.exe pod Windows).
mowania obiektowego. la, czyli dostępu do niej, podobnie jak Aby uruchomić bota w przeglądar-
• Polimorfizm – zapewnia możliwość ma to miejsce np. w Javie czy C++. ce internetowej, skopiuj jego pliki do ka-
wykonywania różnych działań przez Cecha ta określa, czy metoda (lub po- talogu, do którego ma dostęp twój ser-
wer HTTP (np. Apache lub IIS), a następ-
tę samą metodę, np. w zależności od le) ma być publiczna (dostępna dla
nie wywołaj w przeglądarce skrypt Web-
BOT.class.php. Po uruchomieniu bota
możesz się do niego zalogować – wyślij
Czym jest IRC do niego prywatną wiadomość o treści:
IRC jest jedną z usług internetowych. Protokół IRC został stworzony w roku 1988 roku
login: test. Wszelkie komendy wyda-
przez Jarkko Oikarinena. Umożliwia prowadzenie pogawędek internetowych, będąc proto-
wane botowi mają postać: komenda: pa-
plastą dzisiejszych czatów, które są dostępne na wielu witrynach internetowych. W przeci-
rametry oddzielone spacjami. Dwukro-
wieństwie jednak do czatów, IRC nie jest w żaden sposób powiązany ze stronami WWW,
pek po instrukcji jest wymagany nawet,
lecz ma swoje serwery i oprogramowanie klienckie, którego potrzebujemy do prowadzenia
gdy nie przyjmuje ona parametrów, np.
konwersacji. Najpopularniejsze programy klienckie IRC to:
disconnect:.
• mIRC – komercyjny, dość wygodny w obsłudze program dla systemu Windows (http://
www.mirc.com/)
• ViRC – darmowy, całkiem dobry i rozbudowany klient IRC dla Windows (http://
wszystkich), prywatna (dostępna tyl-
www.visualirc.net/) ko dla klasy, w której została zdekla-
• XiRCON – inny darmowy klient IRC dla Windows (http://www.xircon.com/) rowana), czy też chroniona (dostępna
• ShadowIRC – klient IRC dla Macintosha (http://www.shadowirc.com/) dla klasy, w której została zdeklarowa-
• ircII – klient IRC dla systemu UNIX (http://www.eterna.com.au/ircii/)
• Chatzilla – IRC w Twojej przeglądarce, czyli plugin dla Firefoksa oraz Mozilli (http://
na oraz jej klas pochodnych).
chatzilla.hacksrus.com/) • wprowadzenie do klas destruktorów,
które pozwalają posprzątać po likwi-
Po połączeniu się z serwerem, korzystamy z komend wydawanych w linii poleceń progra- dowanym obiekcie (powiemy o nich
mu klienckiego, z których najważniejsze to:
później),
• /server nazwa _ serwera – łączy się z wybranym serwerem, • ujednolicenie nazw konstrukto-
• /join #nazwa _ kanału – wchodzi na określony kanał (jeżeli jesteśmy połączeni rów (__construct()) i destruktorów
z serwerem),
(__destruct()) dla wszystkich klas,
• /msg nick wiadomość – wysyła prywatną wiadomość do użytkownika o określonym
nicku, • możliwość automatycznego ładowa-
• /say wiadomość – wysyła informację do aktywnego okna, dla wszystkich użytkowni- nia klas, bez konieczności używania
ków (w większości klientów wpisywanie say nie jest potrzebne). instrukcji typu include czy require
(nie jest to co prawda związane bez-
Komend IRC jest o wiele więcej. Ich pełną listę znajdziemy na stronach większości progra-
mów klienckich oraz w ich manualach. pośrednio z programowaniem obiek-
Bardzo dużo jest również serwerów IRC i są one rozsiane po całym świecie. Więk- towym, jednak często bardzo ułatwia
szość z nich jest połączona w większe sieci (będąc podłączonymi do jednego serwe- pracę),
ra z takiej sieci możemy rozmawiać z osobami podłączonymi do innych), takie jak bar-
• wprowadzenie klas abstrakcyjnych
dzo popularny w Polsce IRCNet (w każdej chwili jest tam ok. 12000 polskich użytkowni-
ków), zobacz www.irc.pl. Oto kilka polskich serwerów należących do tej sieci IRC: – war- oraz interfejsów (opiszemy je dokład-
szawa.irc.pl, krakow.irc.pl, poznan.irc.pl, lublin.irc.pl. Z serwerami tymi łączymy się korzy- niej w dalszej części artykułu),
stając z portów od 6661 do 6667. Warto także odwiedzić serwer irc.php.pl, a na nim kanał • możliwość deklarowania pól oraz me-
#php.pl – oficjalny serwer IRC polskiego serwisu o PHP – www.php.pl. Serwer ten dodat-
tod statycznych (static), dostępnych
kowo oferuje kanał #test, na którym możemy swobodnie testować swojego bota.
bezpośrednio w klasie, a nie jej instan-

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


IRCBot – programowanie obiektowe Początki

cji. Teoretycznie było to możliwe już tyczące operatorów i botów wyjaśniamy


w PHP4, ale wersja 4 pozwalała na ta- w Ramce Opy i boty na IRC. Protokół IRC w sieci
kie traktowanie dowolnych metod i pól, Dodajmy, że bot jest skryptem działa- Oficjalny opis protokołu klienta IRC zaj-
muje 63 strony i jest opisany w dokumen-
co nie jest wskazane, jącym jak klient IRC. Przeważnie pracuje cie RFC 2812 pt. Internet Relay Chat:
• możliwość deklarowania klas oraz me- w linii poleceń, np. na tej samej maszynie, Client Protocol. Dokument ten znajdzie-
tod jako finalne (po klasach finalnych na której stoi serwer IRC (choć nie jest to my w wielu miejscach w Internecie, m.in.
nie można dziedziczyć, zaś metod fi- konieczne), a wywołujemy go korzystając pod adresem http://www.faqs.org/rfcs/
rfc2812.html.
nalnych przykryć (przedefiniować), z telnetu czy ssh. Nasz bot będzie mógł Suchy opis protokołu nie jest jed-
czyli dopasować ich zachowania do działać zarówno w taki sposób, jak i za nak zbyt przyjazny. Zachęcamy więc
wymagań klasy pochodnej), pośrednictwem przeglądarki internetowej, do rozpoczęcia lektury od strony http://
• wprowadzenie obsługi błędów w po- w której będzie aktywny aż do zamknię- www.cybernexus.biz/irc/default.htm.
Znajdziesz tam wprowadzenie do pro-
staci wyjątków, których tu nie opisze- cia jej okna. tokołu oraz przykładowe logi komunika-
my (poświęciliśmy im inne artykuły, Czego jeszcze potrzebujemy? Zna- cji klienta IRC z serwerem, wraz z wyja-
w tym „Obsługa błędów za pomocą jomości podstaw specyfikacji protokołu, śnieniami.
wyjątków SPL”, z numeru „PHP Solu- na którym oparte są aplikacje klienckie
tions 2/2005). IRC. Jest on dosyć dobrze opisany (m.in. • odbieranie i wykonywanie poleceń wy-
w RFC 2812), nie powinieneś więc mieć dawanych wyłącznie przez zalogowa-
Nowości dotyczących programowania problemu z zagłębieniem się w jego tajniki nych do niego użytkowników,
obiektowego jest znacznie więcej. Już te (zob. Ramka Protokół IRC w sieci). • śledzenie wydarzeń (ang. events) za-
wymienione pokazują, jak zaawansowa- chodzących na wybranym kanale,
nym językiem obiektowym stał się PHP5. Koncepcja IRCBota • możliwość pracy na wielu serwerach
Pokażemy te możliwości na przykładzie Zanim przystąpimy do tworzenia naszego i kanałach IRC jednocześnie,
praktycznej aplikacji: stworzymy bota dzia- IRCBot-a, zastanówmy się, jakimi cechami • modularna budowa, pozwalająca na
łającego w sieciach IRC. powinien się on wyróżniać. Są wśród nich: dodawanie:

Tworzymy obiektowego
Listing 1. Klasa konfiguracyjna bota
IRCBota
Naszego bota nazwiemy IRCBot. Będzie <?php
to skrypt, który działa na kanale IRC uda- class Configuration {
jąc zwykłego użytkownika i pełniąc rozma- static public $arrConfiguration = array (
'bot' => array ( /* Konfiguracja bota */
ite funkcje potrzebne administratorowi, ta-
'Nick' => 'AlaMaKota', /* Nick bota na serwerach */
kie jak piilnowanie porządku na tym ka- 'Password' => '', /* Hasło do serwera – najczęściej puste */
nale, nadawanie statusu Opa (operato- 'Ident' => 'MSBot', /* identyfikator bota */
ra) wybranym osobom, czy ich wyrzucanie // Prawdziwa nazwa użytkownika (nazwa i wersja bota)
(wykopywanie, ang. kick) z kanału. 'Realname' => 'MSBot R1',
'QuitMessage' => 'Idę sobie...', // informacja pożegnalna
Jeśli nie znasz świata IRC, zajrzyj
'Host' => 'localhost', /* Nazwa hosta z pod jakiego łączy się bot */
do Ramki Czym jest IRC; szczegóły do- ),
// serwery, z którymi bot ma się połączyć po uruchomieniu
Opy i boty na IRC 'Servers' => array (
W IRCNecie (a także w wielu innych sie- array (
ciach IRC, które nie posiadają możliwo- 'Host' => 'irc.php.pl', // host serwera
ści rejestrowania kanałów; dziś wiele no- // Tablica z portami obsługiwanymi przez serwer
wych sieci oferuje taką funkcję), pierw- 'Ports' => array ('6667'),
sza osoba wchodząca na kanał otrzymu- // tablica z kanałami serwerów, na które bot ma wejść
je status tzw. Opa (operatora czy admi- 'Channels' => array ('#test'),
nistratora). // czy komunikaty idą z/do serwera mają być logowane
Jego uprawnienia są ogromne – mo- 'Logging'=>true,
że on ustalać temat kanału (np. Dysku- ),
sja o PHP6), nadawać status Opa innym ),
użytkownikom kanału, wyrzucać z niego /* Klasy eventy */
niechciane osoby czy zabraniać im po- 'Events' => array (
nownego wstępu udzialając tzw. bana 'KickReJoinEvent',
(ang. ban znaczy zakaz) na wybranego ),
nicka lub hosta.
'System' => array (
Niestety, w momencie opuszcze-
/* Plik z informacjami o administartorach */
nia kanału, Op traci swoje uprawnienia.
'AdminsFile' => 'Configuration\admins',
Aby temu zaradzić, administratorzy sto-
'LoggingClass' => 'FileLogging', /* klasa zapisująca logi */
sują boty. Jak już wspomnieliśmy, bot to
'LogsDir'=>'logs/', // Katalog w którym mają być zapisywane logi
skrypt udający zwykłego użytkownika ka-
nału (niektórzy nazywają go „sztuczną in- ),
teligencją”, ale jest to raczej określenie );
na wyrost). Jego zadaniem jest przyjmo- }
wanie i wykonywanie poleceń Opa. ?>

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


Początki IRCBot – programowanie obiektowe

• obsługi nowych zdarzeń (eventów wy-


stępujących na danym kanale (np. wy- Listing 2. Klasa IRCBot
kopania użytkownika). Każde zdarze-
abstract class IRCBot {
nie będzie obsługiwane przez odręb- static public $strVersion = '0.1.0';
ny moduł, protected $arrServers = array();
• nowych komend – bot będzie odbierał private $arrServerLogs = array();
komendy wysyłane do niego jako pry- private $arrEvents = array();
private $objAdmins;
watne wiadomości,
public function __construct() {
• możliwość pracy zarówno w linii pole- $this->objAdmins = Admin::getInstance($this);
ceń, jak i w oknie przeglądarki. $this->registerEvent('CommandEvent');
$this->registerEvent('AdminLogoutEvent');
Nasza aplikacja ma być w pełni obiekto- foreach (Configuration::$arrConfiguration['Events'] as $strEvent) {
$this->registerEvent($strEvent); }
wa, czyli składać się z klas realizujących
}
określoną funkcjonalność. Zastanówmy public function __destruct() { $this->show(' ', ' ', '--- END --- '); }
się teraz, jakich klas potrzebujemy. public function run() {
foreach (Configuration::$arrConfiguration['Servers'] as $arrServer) {
Klasy IRCBota $this->connect($arrServer); }
for (;;) {
Bot będzie się składał z następujących
if (count($this->arrServers)===0) { return; }
klas: usleep(50000); $this->ircProc(); // przetwarzamy komunikaty z serwera
}
• Configuration – zawrzemy w niej spis }
wszystkich ustawień naszego progra- public function connect($arrServer) {
if (isset($arrServer['Logging']) && $arrServer['Logging']===true) {
mu (m.in. nazwę użytkownika, adresy
$this->arrServerLogs[$arrServer['Host']] = new FileLogging($arrServer[
i porty serwerów, listę obsługiwanych 'Host']); }
wydarzeń, itd.), $this->arrServers[$arrServer['Host']] = new Server($arrServer,
• IRCBot – główna klasa IRCbota, odpo- Configuration::$arrConfiguration['bot'], $this);
wiadająca za inicjalizację i komunika- $this->arrServers[$arrServer['Host']]->connect();
}
cję z serwerami, a także obsługę wy-
public function getServers() { return $this->arrServers; }
darzeń i komend, protected function ircProc() {
• Server – będzie ona zapewniała ko- foreach ($this->arrServers as $objServer) {
munikację z wybranym serwerem IRC $mixData = null;
(m.in. nawiązanie i zakończenie połą- if (($objServer !== null) &&
(($arrData = $objServer->serverProc()) !== false) &&
czenia),
$arrData !== true) { $this->executeEvents($objServer, $arrData); }
• Channel – klasa odpowiadająca jedne- elseif ($objServer !== null && $arrData !== true) {
mu kanałowi, na którym przebywa bot, $objServer->disconnect();
• Event – klasa bazowa dla klas obsłu- $this->arrServers = array_diff_key(
gujących zdarzenia, $this->arrServers,array($objServer->getName()=>''));
}
• ILogging – nie jest klasą, lecz interfej-
}
sem, który jest potrzebny do logowa- }
nia komunikatów, public function executeEvents($objServer, $arrData) {
• Admin – odpowiada za zapamiętywa- if (isset($this->arrEvents[$arrData['type']])) {
nie i zarządzanie zalogowanymi użyt- foreach ($this->arrEvents[$arrData['type']] as $objEvent) {
$objEvent->execute($objServer, $arrData); }
kownikami.
}
}
Konfiguracja public function log($strServer, $strDirection, $strContent) { }
– klasa Configuration public function registerEvent($strEventName) {
Zaczniemy od klasy Configuration. Jak if(file_exists(ROOT_DIR.'Events'.
DIRECTORY_SEPARATOR.$strEventName.'.class.php')) {
już wiemy, jej zadaniem będzie prze-
require_once(ROOT_DIR.'Events'.
chowywanie ustawień naszego bo- DIRECTORY_SEPARATOR.$strEventName.'.class.php');
ta: będzie się ono odbywało w zmien- $objEvent = new $strEventName($this);
nej statycznej (ang. static) o nazwie if (!($objEvent instanceof Event)) {
$arrConfiguration. Nazwa celowo roz- $this->show(' ',' ',"Nieznany typ klasy obsługującej zdarzenia
($strEventName)"); }
poczyna się od arr (skrót od array), aby
foreach ($objEvent->getEventNames() as $strEvent) {
ułatwić rozpoznawanie typu zmiennej. $this->arrEvents[$strEvent][] = $objEvent; }
Stosowanie takiej zasady ułatwia póź- } else { $this->show(' ', ' ', "Brak klasy zdarzenia ($strEventName)"); }
niejsze modyfikacje i pielęgnację kodu. }
Zmiennej mogącej przechowywać da- abstract public function show($strServer, $strChannel, $strContent);
}
ne różnego typu nadalibyśmy przedro-
stek mix.

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


Początki

Na Listingu 1 znajduje się przykłado- tej zmiennej ($arrConfiguration) oraz (po-


wa konfiguracja naszego bota. Jak widzi- nieważ jest to tablica asocjacyjna ) jej wy-
my, w zmiennej $arrConfiguration określa- brany klucz. Gdybyśmy chcieli odwołać
my dane bota (klucz bot, m.in. nick, hasło, się do zmiennej statycznej bezpośrednio
identyfikator, prawdziwą nazwę czy ho- z wnętrza klasy, w której ona została zde-
sta, spod jakiego łączy się bot), serwerów finiowana, użylibyśmy słowa kluczowego
(klucz Servers), z którymi się łączy, zestaw self, wskazującego na bieżącą klasę:
obsługiwanych zdarzeń (klucz Events) czy
dane systemowe (klucz System), m.in. self::$arrConfiguration[’bot’][’Nick’];
ścieżka do pliku z informacjami o admi-
nistratorach, katalog zawierający logi czy Zauważmy, że zmienne statyczne tworzy-
nazwa klasy, która je zapisuje. my z wykorzystaniem słowa kluczowego
Dlaczego użyliśmy zmiennej sta- static. Wyraz public oznacza natomiast,
tycznej? Ze względu na łatwość dostępu że jest to zmienna publiczna, czyli dostęp
do niej: zamiast tworzyć instancję klasy do niej jest możliwy również spoza klasy.
Configuration (co w tym przypadku byłoby Słowo to możemy pominąć (zmienna jest
jedynie dodatkowym, zbędnym krokiem domyślnie typu public), ale nie należy te-
do wykonania, gdyż konfiguracja jest jed- go robić, gdyż jego użycie porządkuje kod
na dla całego programu), odwołujemy się i ułatwia pracę programistom korzystają-
bezpośrednio do samej klasy. Przykłado- cym z innych języków, takich jak Java.
wo, aby odczytać nick bota, wpiszemy:
Klasa IRCBot
Configuration::$arrConfiguration[’bot’] Jak już wiemy, IRCBot to główna klasa na-
[’Nick’]; szego bota. Będzie ona odpowiadała za
nawiązywanie połączeń z serwerami, ob-
Czyli podajemy najpierw nazwę klasy za- sługę (w pętli) komunikatów przychodzą-
wierającej zmienną statyczną (Configura- cych z serwerów, rejestrowanie klas zda-
tion), a następnie, po znakach ::, nazwę rzeń (eventów), a także zapisywanie lo-

Listing 3. Szablon klasy Server

class Server {
private $arrConfiguration, $arrServerConfig, $resFP, $objBot;
private $arrChannels = array(), $strPort = '';
public function __construct($arrServerConfig,$arrConfiguration,IRCBot $objBot){
if (!isset($arrServerConfig['Host']) ||
!isset($arrServerConfig['Ports'])) {
throw new Server_Exception('Server configuration error!');
}
$this->arrServerConfig = $arrServerConfig;
$this->arrConfiguration = $arrConfiguration;
$this->objBot = $objBot;
}
public function connect() { }
public function disconnect($strReason=null) { }
public function join($strChannel) {
$this->arrChannels[$strChannel]=new Channel(
$this, $this->objBot, $strChannel);
$this->arrChannels[$strChannel]->join();
return $this->arrChannels[$strChannel];
}
public function getChannel($strChannel) {
if (isset($this->arrChannels[$strChannel])) {
return $this->arrChannels[$strChannel];
} else { return false; }
}
public function getName() { return $this->arrServerConfig['Host']; }
public function getPort() { return $this->strPort; }
public function getChannels() { return array_keys($this->arrChannels); }
public function send($strCommand) { }
public function serverProc() { }
}
class Server_Exception extends Exception {}

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


Początki IRCBot – programowanie obiektowe

gów w plikach oraz wyświetlanie komu- nie w pętli foreach rejestruje inne zda- łamy metodę show(), wskutek czego w kon-
nikatów w konsoli lub na stronie WWW. rzenia, które zostały zapisane w zmien- soli lub w oknie przeglądarki pojawi się na-
Przyjrzyjmy się Listingowi 2. nej $arrConfiguration['Events'] w klasie pis ---- END ----. Zwróćmy teraz uwagę
Pierwszym, co rzuca się w oczy, jest Configuration. Pamiętajmy, że te ostatnie na słowo kluczowe $this, używane również
słowo abstract w linii deklaracji klasy. są opcjonalne, a ich dobór zależy od nas. w konstruktorze klasy IRCBot. Jest ono
Określa ono, że klasa jest abstrakcyjna, Przyjrzyjmy się zdarzeniu Command- specjalną zmienną symbolizującą obiekt
co oznacza, że nie można bezpośrednio Event. Ma ono swoją klasę, CommandEvent, bieżącej klasy i umożliwia odwoływanie się
utworzyć jej instancji. Tym określeniem która jest wykonywana w wyniku zdarze- z jego wnętrza do jego pól i metod:
opatrzona jest również metoda show(). nia PRIVMSG (wysłanie komunikatu prywat-
Po co używamy abstract? Jak pamię- nego do bota).Analogicznie, jak przy two- $this->objAdmins()
tamy, chcemy móc używać naszego bo- rzeniu obiektu, podczas jego niszczenia $this->run()
ta w konsoli lub na stronie WWW. W tym PHP5 również wywołuje metodę: jej nazwą
celu utworzymy klasy pochodne wobec jest zawsze __destruct(). Nie posiada ona Aby odwołać się do atrybutów lub me-
IRCBot, które będą się od niej różniły głów- (i nie może posiadać) żadnych parame- tod danego obiektu z zewnątrz (z po-
nie zawartością metody show(). Zadekla- trów. Wykorzystujemy ją do sprzątania po ziomu kodu, w którym została utworzo-
rowanie tej ostatniej jako abstract wymu- likwidowanym obiekcie klasy (np. zamyka- na instancja klasy, np. z programu głów-
sza jej zaimplementowanie (przypisanie nia lub kasowania plików). W klasie IRCBot nego czy wnętrza innej klasy) zamiast
jej bloku kodu, choćby pustego) w klasie użyjemy jej, aby poinformować użytkowni- $this używamy nazwy tego obiektu, np.
pochodnej. Zastosowanie abstract chroni ka o końcu pracy bota – w tym celu wywo- $instancja->run();.
nas więc przed utworzeniem instancji nie-
kompletnej klasy IRCBot, co wymusza pe- Listing 4. Klasa Channel
wien porządek w kodzie PHP5.
Zauważmy również, że implementa- class Channel {
cja większości metod jest pusta: to rów- private $strChannelName, $objServer, $objBot;
public function __construct(Server $objServer, IRCBot $objBot,
nież jest celowe i spowodowane tym, że
$strChannelName) {
samo wdrożenie komunikacji bota z ser- $this->strChannelName = $strChannelName;
werem nie jest tematem tego artykułu, $this->objServer = $objServer;
a kompletny kod źródłowy można zoba- $this->objBot = $objBot;
czyć na naszej stronie WWW. }
public function join() {
Przyjrzyjmy się teraz metodzie
$this->objBot->show($this->objServer->getName(), '',
__construct(), czyli konstruktorowi kla- "Joint to {$this->strChannelName} channel");
sy IRCBot. Metoda ta jest automatycznie $this->objServer->send("JOIN {$this->strChannelName}");
wywoływana podczas instancji klasy. Jej }
przeznaczenie zależy wyłącznie od nas public function leave() {
$this->objBot->show($this->objServer->getName(), $this->strChannelName,
– przeważnie dokonujemy w niej inicjali-
"Leave {$this->strChannelName} channel");
zacji klasy, np. przypisywania wartości do- $this->objServer->send("PART {$this->strChannelName}");
myślnych określonym polom. Możemy też }
wykonywać w konstruktorze czynności bę- }
dące celem istnienia klasy – wybór nale-
Listing 5. Klasa Event
ży do nas.
Instancję klasy tworzymy natomiast za abstract class Event {
pomocą operatora new: protected $objBot;
public function __construct(IRCBot $objBot) {
$this->objBot = $objBot;
$zmienna = new NazwaKlasy($parametry);
}
abstract public function getEventNames();
Po operatorze new podajemy nazwę abstract function execute(Server $objServer, $arrData);
tworzonej klasy, a w nawiasie parame- }
try, których oczekuje konstruktor (al-
bo pusty nawias, jeżeli ich nie wymaga). Listing 6. Przykładowa klasa obsługująca zdarzenie KICK
W przypadku klasy IRCBot, konstruktor class KickReJoinEvent extends Event {
inicjalizuje prywatne pole $objAdmins (jak public function getEventNames() { return array('KICK'); }
wskazuje jego nazwa, pole to ma przecho- public function execute(Server $objServer, $arrData) {
wywać obiekt pewnej klasy, w tym przy- $strChannel = substr($arrData['message'], 0,
strpos($arrData['message'], ' ')+1);
padku Admin), rejestruje standardowe zda-
$objServer->join($strChannel);
rzenia, które muszą istnieć: CommandEvent $this->objBot->show($objServer->getName(), $strChannel, 'ReJoin');
(obsługa komend przysyłanych do bota ja- }
ko wiadomości prywatne przez Opa) oraz }
AdminLogoutEvent (umożliwia wylogowa-
nie wybranego administratora), a następ-

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


IRCBot – programowanie obiektowe Początki

Warto wiedzieć, że niektóre zaawan- Kolejną ważną metodą jest register- nazwie NazwaKlasy.class.php. Jeśli nie,
sowane edytory programistyczne gro- Event(). Służy ona do rejestrowania no- wyświetla informację o błędzie.
madzą dane o strukturze zadeklarowa- wych typów zdarzeń, czyli dodawania klas W przeciwnym wypadku metoda
nych klas i umożliwiają podpowiadanie je obsługujących. Nazwa klasy zdarzenia registerEvent() dołącza ten plik do
pól i metod po wpisaniu $this-> czy na- jest przekazywana przez parametr. Meto- skryptu (require_once), a następnie two-
zwy obiektu. da registerEvent() sprawdza następnie, rzy obiekt klasy o nazwie przekazanej
Po stworzeniu naszej klasy musimy czy w katalogu Events znajduje się plik o w następującym parametrze:
wywołać metodę run(), która urucha-
mia bota i zawiera jego główną petlę.
Nawiązuje ona połączenie z serwerami Listing 7. Przykładowa klasa implementująca interfejs ILogging
znajdującymi się w tablicy $arrConfigura class FileLogging implements ILogging {
tion['Servers'] w klasie Configuration private $resFP = null;
oraz uruchamia pętlę obsługującą zda- public function __construct($strFile) {
rzenia pochodzące od poszczególnych // otwieramy plik do zapisu logów
$strDir = isset(Configuration::$arrConfiguration['System']
serwerów.
['LogsDir']) ?Configuration::$arrConfiguration['System']
Do nawiązywania połączenia z ser- ['LogsDir'] :'';
werami wykorzystywana jest metoda if (($this->resFP=fopen($strDir.urlencode($strFile).'.log','a'))
connect() klasy IRCBot, do której prze- === false) {
kazujemy (przez parametr) ustawienia throw new IRCBot_Exception('Failed to open log file('.
$strDir.urlencode($strFile).'.log'.')');
danego serwera:
}
$strDate = date('\[Y-m-d H:i:s\]');
$this->connect($arrServer); fwrite($this->resFP, "\n\n$strDate\t \t==> BEGIN <==\n\n");
}
Sama metoda connect() tworzy obiek- public function __destruct() { fclose($this->resFP); }
public function log($strServer, $strDirection, $strContent) {
ty klasy Server i zapamiętuje je w tablicy
// jeżeli istnieje komunikat, zapisujemy go w logu
asocjacyjnej $this->arrServers: if (strlen(trim($strContent))<1) {
return; }
$this->arrServers[$arrServer['Host']]= $strDate = date('\[Y-m-d H:i:s\]');
new Server( fwrite($this->resFP, "$strDate\t$strDirection\t$strContent\n");
}
$arrServer,Configuration::
}
$arrConfiguration['bot'],$this);

Listing 8. Przykładowa klasa implementująca interfejs ILogging


Jak widzimy, indeksami tej tablicy są ad-
resy hostów poszczególnych serwerów class Admin {
private static $objThis = null;
– ułatwi to odwoływanie się do obiektów
private function __construct(IRCBot $objIRCBot) {
konkretnych serwerów. Zauważmy też, $this->objIRCBot = $objIRCBot;
że zanim metoda connect() nawiąże po- $this->reloadAdminsList();
łączenie, sprawdza, czy ma ono być lo- }
gowane. Jeżeli tak, tworzona jest instan- static public function getInstance(IRCBot $objIRCBot) {
if (!isset(self::$objThis)) { self::$objThis = new self($objIRCBot); }
cja odpowiedniej klasy (podanej w kla-
return self::$objThis;
sie konfiguracyjnej, albo domyślnej klasy }
FileLogging), do której wrócimy w dalszej }
części artykułu.
Obsługa komunikatów odbieranych Listing 9. Funkcja __autoload wczytująca automatycznie pliki z naszymi klasami
z serwerów przebiega w nieskończo-
function __autoload($strClassName) {
nej pętli for(;;), którą moglibyśmy też static $arrClassesMap = array (
zapisać jako while(true). Zaczynamy 'Configuration' => 'Configuration/Configuration.class.php',
w niej od sprawdzenia, czy tablica $this- 'IRCBot' => 'IRCBot.abstract.class.php',
>arrServers zawiera jakiekolwiek ser- 'Server' => 'Server.class.php',
'Channel' => 'Channel.class.php',
wery (jej rozmiar, odczytany za pomocą
'ILogging' => 'Logging.interface.php',
count() jest różny od zera): jeżeli nie, pro- 'FileLogging' => 'FileLogging.class.php',
gram kończy pracę. Następnie wywołuje- 'Event' => 'Event.abstract.class.php',
my metodę ircProc(), która dla każdego 'Command' => 'Command.abstract.class.php',
z serwerów (w pętli foreach) znajdujących 'Admin' => 'Admin.class.php',
);
się w tablicy $arrServers wywoła metodę
if (isset($arrClassesMap[$strClassName])) {
serverProc() obiektu klasy Server, a po require($arrClassesMap[$strClassName]);
otrzymaniu komunikatów z serwera wy- }
kona odpowiednie dla tych komunikatów }
zdarzenia.

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


Początki IRCBot – programowanie obiektowe

$objEvent = new $strEventName($this); Klasa Server syłanie do niego komend (send()). Wej-
Nadszedł czas na klasę Server, która ście na kanał zapewnia metoda join().
Zwróćmy uwagę na przekazanie zmiennej również należy do najważniejszych klas W konstruktorze klasy Server zainicja-
$this jako parametru dla konstruktora kla- naszego bota. Na Listingu 3 przedsta- lizujemy jej konfigurację oraz utworzymy
sy zdarzenia: w ten sposób przesyłamy do wiamy, jak poprzednio, schemat klasy $objBot, instancję klasy IRCBot (przekaza-
niej bieżący obiekt klasy IRCBot, z którego (większość metod jest pusta). W przeci- ną przez parametr).
następnie będziemy mogli w pełni korzy- wieństwie jednak do klasy IRCBot, klasa Sama idea działania tej klasy jest
stać w tej klasie. Server nie jest abstrakcyjna. Odpowia- dość prosta: tworzymy jej instancję we-
Po utworzeniu instancji klasy obsłu- da ona za komunikację z konkretnym wnątrz klasy IRCBot (zob. Listing 1),
gującej zdarzenia musimy sprawdzić, czy serwerem IRC, a więc między innymi a następnie z tego samego miejsca wy-
obiekt ten jest odpowiedniego typu. Użyje- za nawiązywanie (connect()) oraz koń- wołujemy metodę connect(), aby na-
my w tym celu operatora instanceof, któ- czenie (disconnect()) połączenia. Znaj- wiązać połączenie z serwerem. Meto-
ry sprawdza, czy obiekt podany po jego le- duje się w niej również metoda obsługu- da ta sprawdza kolejne porty z konfigu-
wej stronie jest instancją klasy określonej jąca żądania nadchodzące z serwera racji danego serwera (pobrane ze wcze-
po prawej (wyrażenie ma wartość true je- (serverProc()) oraz umożliwiająca wy- śniej ze zmiennej $arrConfiguration
żeli tak, false jeżeli nie). W naszym przy-
padku, wszystkie klasy zdarzeń będą mu- Listing 10. Implementacja klasy głównej bota, wyświetlająca komunikaty w oknie
siały pochodzić od Event: konsoli

require_once('autoload.inc.php');
if (!($objEvent instanceof Event)){
class ConsoleBOT extends IRCBot {
$this->show(' ',' ', public function __construct() { parent::__construct(); }
"Nieznany typ klasy obsługującej public function show($strServer, $strChannel, $strContent) {
zdarzenia ($strEventName)"); $strDate = date('\[Y-m-d H:i:s\]');
} echo "$strDate\t$strServer\t$strChannel\t$strContent\n";
}
}
Spójrzmy teraz na metodę show() kla- $objBot = new ConsoleBOT();
sy IRCBot. Nie posiada ona ciała (kodu) $objBot->run();
i została zadeklarowana jako abstrakcyj-
na (abstract). Możemy się do niej odwo- Listing 11. Implementacja klasy głównej bota, wyświetlająca komunikaty w oknie
przeglądarki internetowej
ływać w innych metodach (a także z ze-
wnątrz obiektu), ale jej kod utworzymy require_once('autoload.inc.php');
dopiero w klasach pochodnych wobec class WebBOT extends IRCBot {
IRCBot – jak wiemy, umożliwi nam to pod- public function __construct() {
mianę tej metody w zależności od tego, echo <<<EOT
<html><head>
czy chcemy, aby nasz skrypt działał w linii
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
poleceń, czy w przeglądarce WWW oraz <title>IRCBot</title>
zapobiegnie uruchomieniu niekompletnej </head>
klasy. Pamiętajmy, że w klasie dziedziczą- <body>
cej po IRCBot będziemy musieli utworzyć <table border="1" style="color: white;"><colgroup>
<col span="1" style="background-color: navy"/>
ciało metody show(), chyba, że będzie to
<col span="1" style="background-color: blue"/>
również klasa abstrakcyjna. <col span="1" style="background-color: red"/>
Na koniec wyjaśnijmy, czym są sło- <col span="1" style="background-color: gray"/>
wa public, private oraz protected. </colgroup>
Pozwalają one określić dostęp (zwa- <tr><th>Data</th><th>Serwer</th><th>Kanał</th>
<th>Treść wiadomości</th></tr>\nEOT;
ny widocznością) do metod i pól naszej
parent::__construct();
klasy, przed którymi zostały umieszczo- }
ne. Znaczenie pierwszego z nich już zna- public function __destruct() {
my: swobodny dostęp zarówno z wnę- echo "</table></body></html>";
trza ($this->nazwaPola), jak i z zewnątrz foreach ($this->getServers() as $objServer) { $objServer->disconnect(); }
}
obiektu ($nazwaObiektu->nazwaPola).
public function show($strServer, $strChannel, $strContent) {
Z kolei private pozwala wyłącznie na $strDate = date('Y-m-d H:i:s');
dostęp do pola lub metody w klasie, w któ- echo
rej zostały zadeklarowane (na zewnątrz "<tr><td>$strDate</td><td>$strServer</td><td>$strChannel
obiektu oraz w klasach pochodnych już </td><td>$strContent</td></tr>\n";
flush(); ob_flush();
nie). Ostatnie słowo, protected, umożli-
}
wia dostęp do pola lub metody wyłącznie }
dla naszej klasy oraz jej klas pochodnych $objBot = new WebBOT();
(jego zasięg jest więc szerszy niż w przy- $objBot->run();
padku private).

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


IRCBot – programowanie obiektowe Początki

Oczywiście, komenda zostanie wy-


słana do serwera, na którym znajduje się
kanał – w naszej strukturze klas każdy
kanał (obiekt klasy Channel) jest utworzo-
ny wewnątrz odpowiedniej instancji kla-
sy Server.

Klasa Event
O klasie Event (Listing 5) wspomnieliśmy
już przy okazji klasy IRCBot. Wykorzy-
stywaliśmy ją tam do sprawdzenia typu
klas obsługujących zdarzenia na serwe-
rach IRC. Event jest klasą abstrakcyjną,
gdyż służy wyłącznie jako baza dla innych
klas obsługujących zdarzenia. Jest rów-
nież bardzo prosta w budowie: składa się
z konstruktora, w którym przekazujemy
Rysunek 2. IRCBot w konsoli i inicjujemy instancję klasy IRCBot oraz
z klasy Configuration i przekazane kla- Klasa Channel dwóch metod abstrakcyjnych:
sie Server przez parametr konstruktora) Jest to bardzo prosta klasa odpowia-
i łączy się przez pierwszy dostępny. Na- dająca jednemu kanałowi, na którym • getEventNames() – zwraca tablicę
stępnie informuje użytkownika o połą- przebywa bot (jeśli dobrze pamiętasz, z nazwami zdarzeń, których wystąpie-
czeniu i wysyła kolejno IRC-owe po- klasa Server tworzy po jednej instancji nie spowoduje wywołanie obiektu tej
lecenia PASS, NICK i USER do serwera, klasy Channel dla każdego). Przedsta- klasy (pochodnej od Event),
aby się do niego zalogować. Każdemu wiamy ją na Listingu 4. Przez konstruk- • execute() – metoda ta jest wywoły-
poleceniu towarzyszą odpowiednie da- tor przekazujemy jej dwa obiekty: klasy wana, gdy wystąpi zdarzenie, które
ne (hasło, nick oraz zestaw: identyfika- Server oraz IRCBot, a także nazwę ka- obsługuje dana klasa (pochodna od
tor, host i prawdziwa nazwa). nału. Poza konstruktorem, klasa Channel Event).
Następnie wywoływana jest meto- ma tylko dwie metody: join() (przyłącze-
da join(), która tworzy obiekty klasy nie się do kanału) oraz leave() (opusz- Ponieważ budowa tak prostej klasy abs-
Channel dla każdego kanału, z którym łą- czenie go). Każda z nich wywołuje zdefi- trakcyjnej niewiele nam powie, przyj-
czy się nasz bot. Ponieważ możemy się niowaną w klasie IRCBot metodę send(), rzyjmy się dziedziczącej po niej klasie
łączyć z wieloma serwerami, więc za- która z kolei wysyła odpowiednią komen- KickReJoinEvent (dzięki extend Event), ob-
rządzanie kanałami w klasie odpowia- dę do serwera IRC: $this->objServer- sługującej zdarzenie KICK, czyli wyrzuce-
dającej każdemu serwerowi jest bardzo >send(komenda); nie z kanału (Listing 6). Klasa ta powodu-
dobrym wyjściem (każdy serwer może Dla join() jest to IRC-owa komenda je ponowne wejście bota na kanał, z które
mieć przecież wiele przypisanych do sie- JOIN,
a dla leave() – PART. go został wyrzucony. Obie metody abstrak-
bie kanałów).
W konstruktorze klasy Server pojawi-
ła się nowość – podawanie oczekiwane-
go typu parametrów w linii nazwy (dotyczy
parametru $objBot):

public function __construct(


$arrServerConfig,
$arrConfiguration, IRCBot $objBot)

Począwszy od wersji PHP5, możemy


w taki właśnie sposób określić, jaki typ ma
mieć parametr przekazywany do metody
(niestety, nie możemy podać typu stan-
dardowego, np. string – mechanizm ten
działa wyłącznie dla klas). Daje nam to
pewność, że PHP nie pozwoli przekazać
zmiennej innego typu. Przykładowo, gdy-
byśmy spróbowali utworzyć klasę Server,
a zamiast zmiennej typu IRCBot podali np.
łańcuch (string), PHP wyświetli stosowną
informację o błędzie. Rysunek 3. IRCBot w przeglądarce internetowej

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


Początki IRCBot – programowanie obiektowe

cyjne klasy Event zostały zaimplementowa- ILogging. Zapisuje ona logi w pliku (przy cji wczytujemy odpowiedni pliku z klasą
ne w KickReJoinEvent: getEventNames() pomocy metody log()), którego otwarcie (na podstawie nazwy tej ostatniej). Na Li-
zwraca tablicę zawierającą jeden ele- następuje w jej konstruktorze, a zamknię- stingu 9 znajduje się implementacja funk-
ment: KICK, a execute() ponownie wywo- cie – w destruktorze. cji __autoload() stworzona specjalnie na
łuje metodę join() obiektu $objServer kla- potrzeby naszego projektu.
sy Server oraz informuje o tym użytkownika Klasa Admin
(za pomocą metody show() obiektu $this-> Klasa Admin odpowiada za zapamiętywa- Implementacja klasy
objBot klasy IRCBot). nie użytkowników zalogowanych do bota abstrakcyjnej IRCBot
oraz zarządzanie nimi. Jest ona więc swo- Na zakończenie musimy jeszcze utwo-
Interfejs ILogging istą implementacją sesji. Tym, co ją wyróż- rzyć klasę, która będzie implementowa-
Podczas nawiązywania połączenia z ser- nia od innych, jest fakt, że musi ona mieć ła IRCBot. Musimy w niej zaimplemen-
werem, klasa IRCBot uruchamia logowa- jedną i tylko jedną instancję w całej apli- tować metodę show(), wyświetlającą
nie komunikatów pochodzących z ser- kacji. Aby to zapewnić, stworzymy klasę informacje pochodzące od bota oraz kon-
wera oraz wychodzących do niego (jeśli korzystając z wzorca projektowego (ang. struktor. Na Listingu 10 znajduje się wersja
zostało to włączone w konfiguracji). Logo- pattern design: gotowego przepisu na roz- tej klasy dla konsoli (ConsoleBOT), a na 11
waniem zajmuje się osobna klasa, której wiązanie ogólnie znanego problemu pro- – dla przeglądarki WWW (WebBOT). W obu
nazwa jest również zapisana w konfigura- gramistycznego) o nazwie Singleton. Taka przypadkach, w konstruktorze znajduje się
cji. Zrobiliśmy tak, bo chcemy mieć możli- klasa ma prywatny konstruktor, co unie- następująca linia: parent::__construct();.
wość wyboru sposobu zapisu logów – np. możliwia utworzenie jej instancji za pomo- Jest to wywołanie konstruktora klasy-rodzi-
w bazie danych czy w pliku. Klasa logu- cą operatora new, a także posiada statycz- ca (parent), po której ConsoleBOT i WebBOT
jąca musi mieć konstruktor oraz metodę ną metodę zwracającą utworzoną instan- dziedziczą. Przypomina ono nieco wywoła-
log(), która będzie zapisywała komunikat cję klasy. Spójrzmy na Listing 8. nie metody statycznej. Na Rysunkach 2 i 3
i przyjmowała trzy argumenty: nazwę ser- Jak widzimy, konstruktor klasy jest przedstawiamy zrzuty ekranów z przykła-
wera, kierunek wysyłania komunikatu (OUT prywatny. Do stworzenia instancji klasy dowych sesji z IRCBotem.
dla komend wychodzących z naszego bo- wykorzystywana jest zaś statyczna (sta-
ta oraz IN dla wiadomości przychodzących tic) metoda getInstance(), przyjmująca Podsumowanie
z serwera) oraz treść komunikatu. te same parametry co konstruktor. Spraw- Nasz IRCBot jest gotowy. Można go te-
Aby wymusić podstawową zgodność dza ona, czy w statycznym polu $objThis raz uruchomić i przetestować (patrz ramka
struktury tej klasy z naszymi oczekiwania- znajduje się już utworzony obiekt naszej Szybki Start). Warto również na własną rę-
mi, utworzymy interfejs o nazwie ILogging. klasy. Jeśli nie, to go tworzy, wykorzystu- kę poznawać RFC 2812 oraz inne materia-
Klasy zajmujące się logowaniem będą jąc do tego słowo kluczowe self (wskazu- ły na temat IRC, a także analizować klasy
musiały go implementować. Interfejs jest jące na aktualną klasę). Następnie zwraca odpowiadające za obsługę wydarzeń. Po-
bardzo podobny do klasy, tyle że nie za- obiekt $objThis. Oto, jak należy pobrać in- zwoli to nam pogłębić wiedzę na temat taj-
pewnia implementacji żadnych metod, sta- stancję klasy typu Singleton: ników IRC oraz sztuczek i kruczków przy-
nowiąc jedynie swego rodzaju zapowiedź datnych przy stosowaniu botów. Powin-
ich wystąpienia. Do pewnego stopnia mo- $obj = Admin::getInstance($parametry); niśmy również nadmienić, że względna
żemy patrzeć na interfejsy jak na klasy prostota tworzenia naszej aplikacji wyni-
w pełni abstrakcyjne, czyli posiadające wy- Pozostałe metody klasy są już typowe: ka właśnie z obiektowości: z tego, że mo-
łącznie metody abstrakcyjne. Trzeba pa- login() służy do oznaczenia użytkow- żemy logicznie podzielić funkcje przez nią
miętać, iż wszystkie metody zadeklaro- nika na liście jako zalogowanego do bo- wykonywane na odrębne, łatwe do odróż-
wane w interfejsie muszą być publiczne ta, logout() do wylogowania z niego, nienia obiekty, w których będą bezpieczne
(oczywiście słowo kluczowe public można serverLogout() do wylogowania bota przed przypadkową modyfikacją. Zachęca-
pominąć). A oto kod naszego interfejsu: z serwera, a reloadAdminsList() do od- my do dalszego poznawania programowa-
świeżenia informacji o administratorach. nia obiektowego (włączając w to bardziej
interface ILogging { Zadaniem addAdmin() jest dodanie admina zaawansowane obszary, jak wzorce pro-
public function __construct($strFile); do listy, zaś do removeAdmin() należy usu- jektowe), w czym zawsze chętnie służymy
public function log($strServer, nięcie go z niej. Wreszcie, metoda check() pomocą w postaci naszych artykułów. n
$strDirection, sprawdza uprawnienia dostępu danego
$strContent); administratora na określonym serwerze.
}
Automatyczne O autorze
Każda klasa może implementować dowol- ładowanie klas Marcin Staniszczak jest studentem pierw-
ną liczbę interfejsów (podczas gdy dzie- Jak wspomnieliśmy na początku arty-
szego roku informatyki studiów uzupełnia-
dziczyć może tylko po jednej klasie). Nic kułu, PHP5 umożliwia automatyczne ła- jących magisterskich na WSHE w Łodzi. W
nie stoi również na przeszkodzie, aby da- dowanie klas. Aby z niego skorzystać, PHP programuje od wielu lat. Jest autorem
na klasa C implementowała interfejs B należy utworzyć specjalną funkcję kilkunastu publikacji o tematyce PHP i In-
ternetowej. Jest autorem frameworka MVC
i dziedziczyła po klasie A. o nazwie __autoload(), przyjmującą ja-
dla PHP5 (web.framework) oraz systemu
Na Listingu 7 przedstawiamy klasę ko parametr nazwę klasy, która powinna szablonów dla PHP5 (web.template).
FileLogging, implementującą interfejs zostać załadowana. Następnie w tej funk-

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


Początki

APIlity: zarządzanie reklamami


Google AdWords z poziomu PHP
Stopień trudności: lll
Thomas Steiner

Załóżmy, że prowadzimy rozbudowane


kampanie marketingowe w oparciu o Google
AdWords. Zarządzanie nimi przy użyciu zwykłej
strony internetowej jest mocno ograniczone
i uciążliwe. Chcielibyśmy stworzyć własnego
klienta AdWords, który pozwoliłby nam na
elastyczne zarządzanie naszym kontem
i automatyzację wielu procesów. Naprzeciw
naszym oczekiwaniom wychodzi Google
AdWords API.

K
orzystając z wyszukiwarek inter- nięć)? Obliczanie pozycji reklamy odby-
netowych określamy dokładnie, wa się wg następującej formuły: Ad Rank
czego szukamy. Powiedzmy, że = koszt kliknięcia * jakość wyników.

W SIECI mieszkamy w Warszawie i chcemy ku- Reklamodawcy mogą ustalić maksy-


pić nowy rower. Wpisujemy więc sklep malny koszt kliknięcia (CPC, ang. cost
rowerowy Warszawa. Załóżmy teraz, że per click), czyli kwotę, którą są goto-
l http://google- Alicja prowadzi sklep rowerowy w tym wi za nie zapłacić. Na jakość wyników
apility.sourceforge.net
mieście: dzięki reklamom tekstowym (ang. quality score) wpływ ma nato-
– strona domowa APIlity
l http://google-apility. Google (Google text ads) możemy się miast clickthrough rate (CTR) każdego
sourceforge.net/APIlity_ nawzajem znaleźć. Za każdym razem, słowa kluczowego (procent kliknięć ba-
Reference_Standalone.html
– opis elementów biblioteki gdy podajemy przytoczoną wcześniej nera w stosunku do odwiedzin na stro-
APIlity frazę, podobnie jak w przypadku zwro- nie, na której jest on zamieszczony),
l http://groups-beta.google.
com/group/adwords-api- tów rowery, tanie rowery, wycieczka ro- dopasowanie reklamy do wyszukiwa-
php – grupa dyskusyjna werowa Warszawa, a nawet (celowa po- nej treści, dotychczasowa (historyczna)
użytkowników APIlity
myłka w pisowni) sklep RWoerowy, po-
l http://www.google.com/apis/
adwords/index.html – strona winna się pojawić reklama zamieszczo-
l
domowa AdWords API
http://groups.google.com/
na przez Alicję. Weźmy jednak pod uwa- Co należy wiedzieć...
gę, że w Warszawie istnieje wiele skle- Należy znać podstawy programowania
group/adwords-api – grupa
dyskusyjna użytkowników obiektowego w PHP. Przydatna będzie
pów rowerowych: przykładowo, oprócz
AdWords API również wiedza na temat SOAP i AJAX.
l http://www.google.com/ Alicji prowadzą je jeszce Robert i Ka-
support/adwordsapi/bin/ mila, którzy również ogłaszają się ko- Co obiecujemy...
answer.py?answer=21996 Pokażemy, jak stworzyć prostą aplikację
rzystając z Google text ads. Kto lub co
– taryfa zużycia jednostek do okresowego sterowania kontem Go-
kwoty decyduje o tym, czyj tekst ukaże się na
ogle AdWords.
najwyższej pozycji w rankingu (wg klik-

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


Google Apility Początki

tycznego zawieszenia całej serii reklam


Instalacja APIlity w reakcji na określone wydarzenie czy
Pobieramy APIlity spod adresu http://google-apility.sourceforge.net i rozpakowujemy do automatyzacja ustalania cen przypisa-
katalogu, do którego PHP ma dostęp.
nych do słowa kluczowego na podstawie
Wymagania zapasów magazynowych firmy.
APIlity działa zarówno na PHP4, jak i PHP5. Aby móc uzywać funkcji pobierania ra-
portów (report downloading), musimy włączyć rozszerzenie CURL (zobacz http:// Technologia wykorzystywana
www.php.net/curl). Użytkownicy PHP4 (tylko oni) potrzebują też rozszerzenia DOM XML
(http://www.php.net/domxml). W PHP5 rozszerzenie to zostało zastąpione wbudowaną w AdWords
biblioteką DOM. Podczas dołączania APIlity sprawdzi, czy te warunki są spełnione: jeże- Technologią odpowiedzialną za przesy-
li nie, ujrzymy odpowiednie ostrzeżenia. łanie danych w AdWords API jest SO-
Konfiguracja biblioteki AP – protokół oparty na XML-u i HTTP,
Każde żądanie wysłane do AdWords API wymaga uwierzytelnienia oraz pokazania tzw. szeroko stosowany przez twórców opro-
identyfikatora programisty (ang. developer token), który określa każdego dewelopera gramowania. Pisaliśmy o nim w artyku-
(reklamodawcę korzystającego z API). Zakładamy, że już masz konto wraz ze wszystki- łach: Mariaż Pythona i PHP. Tworzy-
mi wymaganymi informacjami dostępowymi. Aby ułatwić proces uwierzytelniania, APIlity
używa pliku authentication.ini.
my interfejs graficzny z wykorzystaniem
Ogólnie, agencja reklamowa ma pewną liczbę klientów, z których każdy przyznał jej SOAP (poprzedni numer) oraz XUL-owy
dostęp do swojego konta przez API. Informacje o tych klientach są zgromadzone w nale- interfejs dla PHP (3/2005). AdWords API
żącym do agencji My Client Center (MCC). Szef tego centrum jest nazywany menadże- używa SOAP 1.1 o stylu document/literal.
rem klientów (client manager). Jego główny adres emailowy identyfikuje agencję, pod-
czas gdy jego klientów identyfikują ich własne adresy emailowe. Aby więc uzyskać do-
W największym skrócie, jest to proto-
stęp do wybranego klienta, musimy się uwierzytelnić przy użyciu swojego emaila mena- kół klient-serwer, gdzie po stronie serwe-
dżera klientów hasła, identyfikatora programisty oraz adresu email tego klienta: wystar- ra istnieje aplikacja udostępniająca swo-
czy wprowadzić te dane w pliku authentication.ini i gotowe. je funkcje lub klasy (to drugie jest możliwe
w nowszych implementacjach SOAP),
wydajność słowa kluczowego oraz wiele nie lub całkowite kasowanie grup re- a po stronie klienta – program, który
innych czynników. klam (np. przy zerwaniu współpracy z nich korzysta tak samo, jak ze swo-
z określonym reklamodawcą). Raporty ich własnych funkcji lub klas: wywołuje je
Struktura konta Google wydajności umożliwiają natomiast śle- (z ew. parametrami) i uzyskuje zwraca-
AdWords dzenie suckesów konta AdWords. ny rezultat. Zestaw oferowanych funkcji
Konto Google AdWords jest podzie- lub klas jest nazywany usługami sieciowy-
lone na kampanie (reklamowe). Wra- AdWords API: mi lub webserwisem (ang. Web Services).
cając do naszego przykładu, Alicja wprowadzenie Dodatkowo, SOAP pozwala na komunika-
może wprowadzić różne strategie mar- Google dobrze wie, że do zarządzania cję między serwerem a klientem niezależ-
ketingowe dla rowerów górskich i ko- tak rozbudowanym systemem reklamo- nie od tego, w jakich językach są napisane
lażówek. Każdej kampanii przypisa- dawcom nie wystarcza zwykła strona in- aplikacje po obu stronach. Przykładowo,
na jest jedna lub więcej grup reklam. ternetowa: potrzebują API (ang. Applica- aplikacja serwerowa może być stworzona
Przykładowo, w kampanii dotyczącej tion Programming Interface), które po- w PHP, a klient w Javie czy Pythonie.
kolażówek mogą istnieć osobne gru- zwala agencji reklamowej na przesłanie Zestaw udostępnianych funkcji, ich
py dla każdego producenta. Na koniec, serii słów kluczowych za jednym zama- dokładne parametry, konwersja typów
każda grupa zawiera listę słów klu- chem i które można wykorzystać w swo- zmiennych między językami, pliki, w któ-
czowych lub kryteriów opisujących wi- ich własnych aplikacjach. Inne pożądane rych te funkcje są umieszczone, itd. moż-
trynę internetową oraz listę creatives cechy takiego API to np.: opcja automa- na określić w XML-owym pliku WSDL
(reklam tekstowych, które pokazują się
w wyszukiwarce internetowej). Każde-
mu słowu Alicja może przypisać mak- ��������������������

symalne CPC, które jest gotowa zapła- �������������������� ��������������������

cić; może też użyć domyślnego CPC �������������������� ��������������������


dla każdej grupy. Analogicznie, z każ-
dym słowem można skojarzyć odręb- �������������������� ��������������������

nego URL-a, pod który jest kierowany ������ �������������������� �������������������� �������������������� ��������������������
�����������
użytkownik klikający na reklamę, albo
��������������������
po prostu wybrać ogólny adres (np. wi-
trynę internetową sklepu rowerowego). ������������������
��������������������

Na podobniej zasadzie można do- ������������


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

kładnie dopasować kampanie i grupy


��������������������
do potrzeb reklamodawcy. Przykłado-
���������������� ��������������������

wo, Google pozwala na prowadzenie


kampanii regionalnych oraz zawiesza- Rysunek 1. Struktura klas APIlity

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


Początki Google Apility

(Web Services Definition Language), któ- gółów implementacji wykonywanych Obiekty w świecie APIlity
ry znajduje się na serwerze. Tak też czy- przez nią zadań. Jak już powiedzieliśmy, konta AdWords
ni Google AdWords. W naszym przypad- Plik Clients.php służy jako łącznik po- są podzielone na kampanie, którym są
ku, Google udostępnia zestaw funkcji między wysokopoziomowymi obiektami przypisane grupy reklam zawierające cre-
opisanych jaki AdWords API APIlity, a biblioteką NuSoap i przetwa- atives i np. słowa kluczowe lub kryteria
Utworzymy więc klienta, który bę- rzanymi przez nią komunikatami SOAP. opisujące witrynę.
dzie się łączył z AdWords przez SOAP Pozostałe klasy w bibliotece APIlity zaj- Każda kampania, podobnie jak gru-
(w PHP4 musimy w tym celu dołączyć mują się m.in. uwierzytelnianiem i obsłu- pa reklam jest opisana przez unikalne w
odpowiednią bibliotekę; w PHP5 jest gą błędów. skali światowej ID. Natomiast identyfika-
ona wbudowana) i wywoływał funkcje
AdWords API, podzielone na odrębne
Listing 1. Plik authentication.ini biblioteki APIlity
usługi, takie jak TrafficEstimatorService
czy AdGroupService. ...
Email = "yourEmail@someISP.tld"
Password = "y0uR5ecR3t"
APIlity, czyli łatwe i przyjemne
Developer_Token = "yOurD3v3l0p3RT0k3n"
tworzenie klienta AdWords. Client_Email = "yourClientsEmail@someISP.tld"
Aby uprościć tworzenie klienta AdWords
API w PHP, firma Google utworzyła Listing 2. Hello World w APIlity
bibliotekę APIlity, która ukrywa wszyst-
<?php
kie szczegóły techniczne jego usług sie-
require_once('apility.php');
ciowych. Dla Javy istnieje analogicz- // pobierz wszystkie kampanie klienta
na biblioteka google-adwords-api-client, $allCampaigns = getAllCampaigns();
dostępna również jako projekt openso- // wyświetlanie tablicy kampanii
urcowy (http://sourceforge.net/projects/ echo "<pre>";
print_r($allCampaigns);
goog-ad-api-cli/). Biblioteka APIlity umo-
echo "</pre>";
żliwia łatwe tworzenie obiektowych apli- ?>
kacji klienckich korzystających z Google
AdWords API. Pokażemy Wam, jak jej Listing 3. Przykładowy wynik działania "Hello world"
używać.
Array
( [0] => Campaign Object
System kwotowy (
AdWords API wprowadza system kwoto- [name] => Some Name
wy, który służy do zarządzania ruchem [id] => 12345678
[status] => Active
przysyłanych do serwera żądań. Każdy
[startDate] =>
reklamodawca dostaje pewną liczbę jed- 2006-03-31T11:03:21.000Z
nostek kwotowych (quota units) do wyko- [endDate]=>
rzystania w ciągu danego miesiąca. Każ- 2007-01-01T07:59:59.000Z
de żądanie wysłane do serwera AdWords [dailyBudget] => 567
[languages] => Array
(np. zapytanie o listę słów kluczowych)
( [0] => en
powoduje zużycie określonej liczby jedno- [1] => pl
stek (różniącej się w zależności od działa- [2] => fr )
nia). Kwota miesięczna podlega zmianom [geoTargetType] => countries
i zależy od różnych czynników, m.in. od [geoTargets] => Array
( [0] => UK
ilości kont, które posiadamy w systemie
[1] => PL
oraz stopnia, w jakim ich używamy. Sys- [2] => GB )
tem ten wprowadzono w celu ochrony [isEnabledOptimizedAdServing]=>
serwerów Google AdWords przed prze- [networkTargeting] => Array
ciążeniem przez nadmierny ruch. ( [0] => SearchNetwork
[1] => ContentNetwork )
[isEnabledSeparateContentBids]=>1 ) )
Struktura APIlity
Do komunikacji z AdWords API, APIli- Listing 4. Pobieranie informacji o grupach reklam
ty używa zmodyfikowanej wersji bibliote-
ki NuSoap. Strukturę jej klas przedstawia- // odczytaj pierwszą kampanię i pobierz jej grupy reklam
$allAdGroups=
my na Rysunku 1. Centralna klasa całego
$allCampaigns[0]->
projektu jest zawarta w pliku apility.php getAllAdGroups();
(oznaczona na zielono) i dołącza (inklu- echo "<pre>";
duje) wszystkie inne klasy. Każda Usługa print_r($allAdGroups);
sieciowa ma swoją własną klasę (żółta) echo "</pre>";
– ułatwia to szybkie odnalezienie szcze-

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


Google Apility Początki

łanie funkcji GetAllCampaigns() obiektu


$allCampaigns (Listing 4. Pamiętajmy, że
�������
����
��
mamy już tablicę kampanii z poprzednie-
��������������������
����
go przykładu. Podobnie jak poprzednio,
otrzymany wynik będzie miał postać tabli-
������������ ����������
�� ������
�������������������� ������
���������� �������� cy asocjacyjnej (zob. Listing 5).
�������� �������������� ��������������
���� ��������� Moglibyśmy rozszerzać nasz przy-
�� �������������
������
�������
��������
������������
����������������
���� kład wywołując getAllAdGroups() na
���������
���� ��
�������
�����������
��
������������
�������������������� wszystkich obiektach kampanii, a na-
��������������������� ����
����������������
���������
������ ������������� stępnie, rekurencyjnie getAllCreatives()
������ ������������� ����������
�������������
���������
������������� �� ������ i getAllCriteria() na każdym obiek-
������ �������������������� ������
�������������������������������
������������������������������� ���������� ������
��������
cie grupy reklam, aż otrzymalibyśmy
����������������������������� ��������������
���������������������������� ���������
�������������
�������������� kompletne drzewo odzwierciedlające
����
�����
���������������� strukturę konta klienta. Zostało to zade-
���
������
��������
��
��������������������
monstrowane w dołączonym do APIlity
������������
��������
�������������
����������
przykładowym projekcie.
���� ������
������

Obsługa błędów
��������
��������������

i programowanie defensywne
Gdy mamy do czynienia z działający-
Rysunek 2. Hierarchia obiektów APIlity
mi zdanie Usługami sieciowymi, obsłu-
cja creatives, słów kluczowych oraz kry- listę wszystkich naszych kampanii wy- ga i usuwanie błędów staje się szczegól-
teriów opisujących witrynę odbywa się wołując funkcję getAllCampaigns(); oraz nie istotne. APIlity pozwala zarówno na
za pomocą ID, które są unikalne na skalę wyświetlał ją w przeglądarce interneto- straight-forward programming, jak i pro-
danego konta. Kampanie są obiekta- wej. Jeżeli Twoje konto zawiera tylko jed- gramowanie defensywne. Przyjęta w tej
mi niezależnymi, podczas gdy wszyst- ną kampanię, wynik powinien wyglądać bibliotece konwencja mówi, że w wypad-
kie pozostałe obiekty posiadają odwoła- jak na Listingu 3. ku niepowodzenia (błędu) funkcja zwra-
nia do miejsc, do których należą. Zgodnie No dobrze, a jak zobaczyć wszyst- ca fałsz (false), jeśli natomiast wszystko
z tą zasadą, grupy reklam przechowu- kie grupy reklam? To proste: jak wie- jest OK:
ją ID kampanii, w ramach których istnie- my, każda grupa należy do jakiejś
ją, creatives pamiętają ID swojej grupy re- kampanii. Wystarczy więc zapytać kam- • funkcja zwraca żądany rezultat (jeśli
klam, itd. Kompletną hierarchię obiektów panie o przypisane im grupy reklam: słu- powinna go zwracać),
przedstawiamy na Rysunku 2. ży do tego metoda getAllAdGroups() • funkcja zwraca prawdę (true), jeżeli
utworzonego poprzednio przez wywo- nie ma zwracać niczego innego.
„Hello World” w APIlity
Jeżeli pobraliśmy i zainstalowaliśmy API-
Listing 6. Krok 1: tworzymy listę kryteriów słów kluczowych dla naszego konta
lity oraz wpisaliśmy dane uwierzytelnia-
jące w pliku authentication.ini (Listing 1), <?php
możemy pokazać jej działanie na przy- require_once('apility.php');
function getAllCriteriaOfAccount() {
kładzie prostej aplikacji typu Hello World.
// pobieramy wszystkie kampanie
Zaczniemy od dołączenia biblioteki APIlity // w wypadku niepowodzenia tego podstawowego kroku zwracamy false
w kodzie: require _ once('apility.php'); if ( !$allCampaigns = getAllCampaigns() ) return false;
i umieszczenia jej w tym samym folderze, // pobieramy wszystkie grupy reklam przypisane do danej kampanii
co nasza aplikacja. Nasz przykład, który for($i=0; $i<sizeOf($allCampaigns); $i++) {
$campaign = $allCampaigns[$i];
przedstawiamy na Listingu 2, będzie po-
// w wypadku błędu omiń aktualną iterację
bierał mającą postać tablicy asocjacyjnej if ( !$allAdGroupsOfCampaign[$i] = $campaign->getAllAdgroups() ) continue;
}
Listing 5. Przykładowy wynik // inicjalizujemy tablicę do przechowywania kryteriów słów kluczowych
"rozszerzonego hello world" $allCriteriaOfAccount = array();
// pobierz słowa kluczowe zawarte w każdej kampanii i każdej grupie reklam
Array for($i=0; $i<sizeOf($allAdGroupsOfCampaign); $i++) {
( [0] => AdGroup Object for($j=0; $j<sizeOf($allAdGroupsOfCampaign[$i]); $j++) {
( [maxCpc] => 1 $adGroup = $allAdGroupsOfCampaign[$i][$j];
[maxCpm] => // w wypadku błędu po prostu pomijamy aktualną iterację
[maxContentCpc] => if ( !$allCriteria = $adGroup->getAllCriteria() ) continue;
[name] => sdsdsd foreach($allCriteria as $criterion) {
[id] => 279945007 array_push($allCriteriaOfAccount, $criterion);
[belongsToCampaignId]=>10838947 } }
[status] => Active } return $allCriteriaOfAccount; }
) ) ?>

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


Początki Google Apility

Odstępstwem od tej reguły są pro-


Listing 7. Krok 2: odpowiednie wydarzenie powoduje zmianę maksymalnego ste funkcje pobierania obiektów (object
CPCs get-functions), ponieważ wymagają one
<?php
poprawnie utworzonego obiektu. Przy-
// zmienne globalne określające początek i koniec przerwy na lunch (pory lunchu) kładowo, funkcja addCampaign() doda-
$lunchStartTime = "11:00:00"; je i zwraca jako rezultat nową kampa-
$lunchEndTime = "13:00:00"; nię. Jeśli więc wszystko poszło dobrze,
$sleepTime = 15; // czekaj 15 minut pomiędzy cyklami sprawdzenia
funkcja powinna zwrócić obiekt kampa-
function startDaypartingDaemon($lunchStartTime, $lunchEndTime,
$sleepTime,$allCriteria=null,$bidsRaised=false,$bidsLowered=false){
nii jako rezultat. Jeśli dodawanie się nie
/* przy pierwszym uruchomieniu demona daypartingu odczytaj kryteria słów powiodło (np. AdWords API nie chce
/* kluczowych przez wywołanie API. Potem słowa kluczowe będą przekazywane przyjąć naszej kampanii), zwróconym
/* i zwracane przy każdym wywołaniu rekurencyjnym */ wynikiem będzie false.
if ( is_null($allCriteria) ) {
Możemy więc programować na dwa
echo "Dayparting Daemon started.<br />\n";
ob_flush(); flush();
sposoby. Pierwszym jest programowanie
// jeśli poniższe zawiedzie, nie mamy dostępu do konta defensywne:
if (!$allCriteria=getAllCriteriaOfAccount()){
exit("Brak połączenia z kontem"); if($campaignObject=addCampaign(...)){
}else{echo "Dane konta zostały pomyślnie załadowane.<br />\n"; }
// obsługa błędu
}
}
// sprawdź, czy nadeszła pora lunchu
$now = time(); print_r($campaignObject);
// nadeszła pora lunchu
if (($now >= makeUnixTimeStamp($lunchStartTime)) && a drugim straight-forward programming:
($now <= makeUnixTimeStamp($lunchEndTime))) {
// jeśli stawki nie zostały podniesione, to je podnieś
$campaignObject = addCampaign(...);
if (!$bidsRaised) {
if ( !raiseBids($allCriteria)) { print_r($campaignObject);
exit("Błąd podnoszenia maksymalnego CPC dla słowa kluczowego.");
}else{ Wszystkie błędy są wrzucane na stos
echo "Pora lunchu rozpoczęta, <font color='red'>
błędów, który przechowuje informacje
podnoszę</font> stawki.<br />\n";
$bidsRaised = true;
o nich w standardowy sposób, jako
$bidsLowered = false; obiekty błędów. Daje to dużą elastycz-
} ność gromadzenia i przetwarzania da-
} else echo "Stawki zostały już podniesione. nych o błędach, gdyż na podstawie
Pora lunchu nadal trwa.<br />\n";
tych obiektów łatwo możemy np. wyge-
// czas pracy; jeśli stawki nie zostały jeszcze obniżone, to je obniż
}else{
nerować plik tekstowy, HTML czy XML
if (!$bidsLowered) { zawierający potrzebne informacje, co
if (!lowerBids($allCriteria)) { umożliwi sporządzanie raportów.
exit("Błąd obniżania maksymalnego CPC dla słów kluczowych.");
}else{
Dalsze możliwości
echo "Pora lunchu zakończona, <font color='green'>
obniżam</font> stawki.<br />\n";
Na podstawie dotychczasowej wie-
$bidsLowered = true; $bidsRaised = false; dzy, używając APIlity stworzysz wy-
} godną stronę WWW (frontend) do za-
}else echo "Stawki został już obniżone. Czas pracy wciąż trwa.<br />\n"; rządzania swoim kontem, narzędzie do
}
przesyłania całego zestawu słów klu-
// wypisz zawartość bufora i poczekaj
ob_flush();
czowych za jednym zamachem (bulk
flush(); uploader), aplikację pozwalającą spo-
sleep($sleeptime * 60); rządzać raporty jednym kliknięciem
// wywołanie rekurencyjne; uwaga: talblica $allCriteria jest już wypełniona, i wiele innych. Warto też wspomnieć
// a bity statusu przechowują informację o tym, czy stawki zostały zmienione
o możliwości eksportu słów kluczo-
startDaypartingDaemon($lunchStartTime, $lunchEndTime, $sleepTime,
$allCriteria, $bidsRaised, $bidsLowered);
wych do plików Excela lub ich importu
} z Excela do bazy AdWords.
// ta funkcja konwertuje czas w formacie (12:00:00) na uniksowy timestamp Wielu reklamodawcom to wystarcza:
function makeUnixTimeStamp($isoTime) { prawdziwe możliwości zaczynają się jed-
$timeComponents = split(":", $isoTime);
nak dopiero przy wprowadzeniu elemen-
return mktime($timeComponents[0], $timeComponents[1],$timeComponents[2],
date("m"), date("d"), date("Y"));
tów interakcji i automatyzacji.
}
// uruchom demona (będzie działał “w nieskończoność”) Budujemy większy
startDaypartingDaemon($lunchStartTime, $lunchEndTime, $sleepTime); projekt
?>
W języku osób zajmujących się rekla-
mami (nie tylko internetowymi), day-

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


Google Apility Początki

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

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

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

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

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

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


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

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

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

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

Rysunek 3. Zasada działania APIlitAx

parting polega na dzieleniu dnia na Koncepcja szy. Nie musimy jednak tego robić każ-
określone części, różniące się rodza- Naszym celem jest stworzenie najprost- dego dnia, o ile nie zmieniamy naszych
jem wyświetlanych reklam. Reklamy szego demona zajmującego się daypar- słów kluczowych. W zastosowaniach
te są często adresowane do określo- tingiem. Będzie on działał przez całą do- praktycznych, dane byłyby przechowy-
nego odbiorcy, z uwzględnieniem wy- bę, a w godzinach pomiędzy 11 a 13 pod- wane w lokalnej bazie danych (działają-
konywanych przez niego o określonej nosił CPC każdego kryterium słowa klu- cej po stronie naszego klienta AdWords
porze zajęć. Pamiętacie o Alicji, któ- czowego np. o 20 groszy. API) i okresowo aktualizowane na pod-
ra prowadzi sklep rowerowy? Więk- Krok 1: nasze zamierzenie zrealizu- stawie informacji z AdWords. W naszej
szość rowerzystów cięzko pracuje (lub jemy na poziomie kryteriów słów kluczo- prostej aplikacji, jednorazowo pobiera-
się uczy) w ciągu dnia, z małą przerwą wych konta Alicji, co jest bardzo proste: my kryteria słów kluczowych w funk-
na odpoczynek (lunch) i surfowanie po wystarczy rozszerzyć nasz przykład Hel- cji, która omówimy w następnym kroku.
Sieci. Dobrym pomysłem byłoby więc lo World (Listing 6). Ponieważ potrzebna Funkcja ta następnie będzie wywoływa-
podnoszenie maksymalnego CPC w nam jest jedynie lista wszystkich kryte- na rekurencyjnie (ze swojego wnętrza).
tej porze dnia, aby zwiększyć krąg od- riów słów kluczowych, nie musimy prze- Lista słów kluczowych zostanie pobra-
biorców reklamy oraz obniżanie CPC chowywać pochodzenia każdego z nich na z serwera jedynie za jej pierwszym
(a nawet całkowite zawieszanie reklam) ani tworzyć w tym celu rozbudowanej ta- wywołaniem, natomiast przy kolejnych
w momentach o większej stagnacji. blicy (informację o pochodzeniu możemy będziemy ją przekazywać przez para-
zresztą bardzo łatwo odtworzyć na pod- metr tej funkcji.
Listing 8. Krok 3: funkcje stawie pola belongsToAdGroupID każdego Krok 2: gdy już mamy wszystkie
modyfikujące obiekty obiektu kryterium). Tworząc nasz skrypt, kryteria słów kluczowych, musimy zde-
wykorzystamy taktykę programowania finiować wydarzenia powodujące pod-
<?php
defensywnego. niesienie maksymalnego CPC i jego
function raiseBids($allCriteria){
Wykonanie kroku 1. jest niezbęd- ponowne obniżenie. U nas są nimi po-
foreach($allCriteria as
$criterion) { ne, aby odczytać dane po raz pierw- czątek i koniec pory lunchu. Demon
if(!$criterion->
setMaxCpc( Listing 9. Przykładowy plik pośredniczący w PHP
$criterion->
getMaxCpc()+0.2)) <?php header("Content-type:application/xml; Charset=UTF-8"); ?>
return false; <?php
} echo utf8_encode("<?xml version='1.0' encoding='UTF-8'?>\n");
return true; include('./apility/apility.php');
} $authenticationContext->setEmail($_POST['email']);
function lowerBids($allCriteria){ $authenticationContext->setPassword($_POST['password']);
foreach($allCriteria as $authenticationContext->setToken($_POST['developerToken']);
$criterion) { $authenticationContext->setClientEmail($_POST['clientEmail']);
if(!$criterion->setMaxCpc( $campaignObject = createCampaignObject($_POST['id']);
$criterion-> if (!$campaignObject) {
getMaxCpc()-0.2)) $fault = array_pop($faultStack);
return false; $fault->printFault();
} return true; } }else { $xml .= $campaignObject->toXml(); }
?> echo utf8_encode($xml);
?>

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


Początki Google Apility

zajmujący się daypartingiem będzie re- Pamiętajmy, że musimy przechowy- Istnieje jeszcze inna wada obecne-
gularnie sprawdzał, czy ta pora nade- wać listę wszystkich kryteriów, a pro- go podejścia: każda operacja zajmuje
szła, czy nie. Jeśli nadeszła, demon tokół HTTP jest bezstanowy (ang. sta- czas i gdy jest wykonywana przez Ad-
podnosi stawki (tylko raz danego dnia), teless), co znaczy, że przy przełado- Words API, użytkownik musi czekać.
a jeśli się skończyła, obniża je (znów, waniu strony tracimy wszystkie dane. Choć nie możemy przyspieszyć odpo-
tylko raz dziennie). We wszystkich in- Moglibyśmy zapisywać je w zmiennych wiedzi AdWords API, powinniśmy móc
nych przypadkach demon powinien po sesyjnych, lecz nie jest to dobrym roz- wysłać do niego skumulowaną listę żą-
prostu kontynuować swoje działanie. wiązaniem, gdyż powoduje spadek wy- dań (zapytań) i pozwolić użytkowniko-
Aby sprawdzić aktualną godzinę, użyje- dajności wraz ze wzrostem ilości da- wi na wykonywanie innych czynności
my PHP-owej funkcji time(), natomiast nych. Dałoby się również każdorazow w tym czasie. Przykładowo, jedno żą-
do porównywania czasu wykorzysta- ściągać wszystkie dane, ale jest to zły danie może pobierać obszerny raport,
my uniksowy format timestamp (zobacz pomysł, gdyż jest czasochłonny oraz podczas gdy drugie będzie wysyłało
Listing 7). oznacza konieczność wysyłania ko- zestaw nowych kampanii. Wprawdzie
Krok 3: pozostało już tylko utworzenie lejnych zapytań do serwera AdWords, PHP nie jest wielowątkowe, ale uży-
funkcji modyfikujących maksymalne CPC: a każde z nich zmniejsza dostępną kwo- wając kombinacji różnych technik mo-
raiseBids(), która służy do jego podno- tę. Krótko mówiąc, potrzebujemy trwałej żemy emulować wielowątkowe zapyta-
szenia oraz lowerBids(), która będzie je warstwy danych (ang. persistence layer) nia do API.
obniżała (Listing 8). – zespół twórców APIlity ma ją w swoich
planach. Powinna ona pozwalać na ła- APIlity + AJAX =
Wady tego podejścia twą integrację z bazą danych. Pod adre- APIlitAx
Nasz demon zajmujący się daypartingiem sem http://www.adoptimize.de/glossar/ Jak sama nazwa wskazuje, AJAX
jest gotowy. Jako, że jest to prosty przy- mysql-4.1.11.sheme.apility.0.4.html obej- (Asynchronous Javascript And XML)
kład, moglibyśmy w nim wiele poprawić: rzymy wstępną wersję schematu ba- – jedno z najmodniejszych w tym ro-
przykładowo, moglibyśmy użyć bazy da- zy danych (w postaci kwerend tworzą- ku słów ze świata PHP (o którym pisa-
nych do lokalnego przechowywania kry- cych tablice i wprowadzających przykła- liśmy w numerze 1/2006 w artykułach:
teriów słów kluczowych. dowe dane). AJAX – wyjąkowo interaktywne i wy-

Rysunek 4. Przykładowa aplikacja w APIlitAx, korzystajjąca z APIlity i AJAX-a

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


Google Apility Początki

Przykładowy skrypt
Tworzymy konto AdWords pośredniczący PHP
Zaczynamy od założenia konta AdWords. W tym celu idziemy na stronę główną Opiszemy przykładowy skrypt pośred-
AdWords i postępujemy wg wskazówek kreatora: https://adwords.google.com/select/
niczący PHP (zob. Listing 9). Rozpo-
starter/signup/Fork.
Po utworzeniu i potwierdzeniu konta AdWords, musimy je aktywować: w tym ce- czynamy go od wysłania nagłówka,
lu trzeba wybrać opcje płatności i wysłać szczegółowe informacje z nimi związane (funkcją header()), w którym zdeklaru-
(billing information). Odtąd mamy możliwość zarządzania reklamami, ale jeszcze nie jemy zawartość strony wynikowej ja-
przez API: do tego ostatniego musimy jeszcze uzyskać dostęp. W tym celu wchodzi-
ko XML (a nie, jak domyślnie, HTML)
my na stronę https://adwords.google.com/select/ApiWelcome i wpisujemy dane nasze-
go konta i loginu. Po chwili pojawi się kreator, który pomoże nam w utworzeniu konta i ustawimy zestaw znaków na UTF-8.
My Client Center.. Następnie, w zakładce My Account pojawi się nowa strona zatytu- Od tej chwili musimy ściśle przestrze-
łowana AdWords API Center: będzie ona zawierała identyfikator programisty (develo- gać reguł XML-a: każdy tekst wypisa-
per token) oraz informacje o tym, jak używamy API (w tym kwota). W zaczęciu pracy
ny przez PHP (np. komunikat o błędzie
z AdWords API pomogą nam następujące tutoriale:
czy ostrzeżenie) spowoduje błąd jego
● http://services.google.com/awp/en_us/breeze/264836/index.html, przetwarzania. W rezultacie, żądanie
● http://services.google.com/awp/en_us/breeze/267718/index.html, XHML HTTP Request nigdy nie przy-
● http://services.google.com/awp/en_us/breeze/264848/index.html. niesie wyniku i będzie czekało w nie-
skończoność – jest to jeszcze jeden
Odpowiedzi na wiele spośród naszych pytań uzyskamy również na grupie dyskusyjnej de-
powód dla stosowania programowania
veloperów AdWords API: http://groups.google.com/group/adwords-api.
defensywnego.
Następnie dołączamy bibliotekę
APIlity (apility.php) i dokonujemy uwie-
dajne aplikacje WWW oraz advAJAX, Wymagania rzytelnienia na serwerze AdWords.
czyli praktyczne zastosowanie techno- Aby APIlitAx w ogóle działało, APIli- Uwierzytelnienie to odbywa się w spo-
logii AJAX), pozwala na asynchronicz- ty nie może być w trybie verbous (wy- sób dynamiczny, przy użyciu obiek-
ne przesyłanie danych (stąd A w na- pisywanie komunikatów na temat wy- tu globalnego $authenticationContext
zwie). X z kolei oznacza wykorzystanie konywanych czynności, błędów, itd.); oraz danych HTTP POST. Jeśli wystą-
XML-a. APIlity pozwala na łatwą kon- zwracanie przez PHP komunikatów pi błąd, wyświetlony zostanie komuni-
wersję obiektów PHP-owych na XML: o błędach również musi być wyłączone, kat o błędzie AdWords API (komuni-
$someAPIlityObject->toXml();. Umoż- gdyż każda taka wiadomość zniszczy- katy o błędach muszą być w formacie
liwia to przesyłanie obiektów APIlity ja- łaby strukturę XML tworzoną w pośred- XML). Jeżeli natomiast wszystko pój-
ko odpowiedzi na zapytania AJAX-a. niczącym skrypcie PHP. Aby tego unik- dzie dobrze, odebrany obiekt kampa-
Wreszcie, J oznacza JavaScript. nąć, APIlity oferuje tzw. Silence Stealth nii APIlity zostanie przekonwertowa-
Jak połączyć AJAX-a i APIlity? Mode, w którym wszystkie ostrzeżenia ny na XML.
Jest to całkiem proste: po stronie ser- PHP, jak i komunikaty APIlity zostają Niezbędny kod źródłowy JavaScript
wera mamy AdWords API, a po stronie wyłączone. oraz działająca aplikacja przykładowa
klienta –przeglądarkę WWW. Pomię- Błędy AdWords API są natomiast zostały zamieszczone na stronach pro-
dzy nimi jest APIlity. W przeglądarce cały czas przechwytywane, ale nie jektu na licencji BSD.
WWW mamy zupełnie zwyczajną stro- wyświetlane. Jeżeli używamy APIli-
nę internetową zawierającą formularze ty jako biblioteki osadzonej (ang. em- Podsumowanie
i tekst. Za każdym razem, gdy użyt- bedded), dobrym pomysłem jest prze- Możliwości Google AdWords API są
kownik wysyła zmiany, raport, itd., uru- chowywanie informacji o tych błędach ogromne: możemy śmiało powiedzieć,
chamiane jest żądanie XML HTTP Re- w XML-u. że jego wprowadzenie stworzyło nową
quest: przesyła ono parametry funk- Dodatkowo, z natury Silence Ste- jakość w zarządzaniu reklamami in-
cji APIlity jako zwyczajne dane HTTP alth Mode wynika, że błędy niezwiąza- ternetowymi. Wiedza o tym systemie,
POST. ne z API są trudne do znalezienia. Po- którą Wam przekazaliśmy, stanowi so-
Istnieje też pośrednicząca w trans- winniśmy więc przyłożyć szczególną lidną podstawę do tworzenia bardziej
misji strona PHP, która przetwarza uwagę do zapobiegania im: pośredni- rozbudowanych projektów: zachęcamy
dane POST i wywołuje odpowiednią czący skrypt PHP powinien działać do własnych eksperymentów i życzy-
funkcję natywną APIlity. Dostarcza i przynajmniej generować poprawnego my samych sukcesów w biznesie. n
ona również rezultaty funkcji w forma- XML-a – nawet, jeżeli nie wysłano pa-
cie XML lub JSON, co pozwala na ła- rametru POST.
twe przetwarzanie odpowiedzi przez Najłatwiej to sprawdzić uruchamia-
przeglądarkę internetową. Ponieważ jąc ten plik jako niezależny, poza na- O autorze
ten skrypt PHP znajduje się na lokal- szym systemem. W znalezieniu typo-
nym serwerze WWW, na którym zain- wych błędów, takich jak variable undec- Thomas Steiner pracuje dla Google,
Inc. jako inżynier oprogramowania i po-
stalowano również APIlity, wirtualizu- lared (niezadeklarowana zmienna) po- pularyzator AdWords API. Odpowiada
je on w pewnym sensie AdWords API, może nam też wyłączanie i włączanie za opensourcowy projekt APIlity.
pozwalając jednemu użytkownikowi Silence Stealth Mode podczas debu- Kontakt z autorem: tomac@google.com
wysyłać wiele żądań. gowania skryptu.

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


Bezpieczeństwo

Kryptografia w PHP
Stopień trudności: lll
Łukasz Lach, Michał Stanowski

Prawdopodobnie każdy z nas wie, czym jest


kryptografia. Bierzemy porcję informacji
(np. tekstu) i szyfrujemy ją tak, aby nikt
nieuprawniony nie był w stanie jej odczytać.
Z kryptografią mamy do czynienia na co dzień
– czy to korzystając z banku internetowego, czy
też wysyłając szyfrowanego maila. I to wszystko
z wykorzystaniem PHP.

W
ikipedia definiuje kryptogra- tykule pokażemy, w jaki sposób można
fię jako „naukę zajmującą się efektywnie korzystać ze wspomnianych
układaniem szyfrów”. W prak- funkcji na podstawie trzech praktycznych
tyce jest to konwersja jednej informacji przykładów.
w drugą za pomocą złożonych obliczeń ma-
tematycznych (szyfrowanie) oraz ewentual- System bezpiecznego
na konwersja tejże informacji do stanu pier- logowania
wotnego (deszyfrowanie). W kontekście Zacznijmy od stworzenia bezpieczne-
aplikacji internetowych jest to dość często go systemu logowania. Jest to idealny
spotykana dziedzina, zarówno w małych, przykład na wykorzystanie funkcji kryp-
jak i w dużych, komercyjnych serwisach tograficznych dostępnych w PHP. W na-
– podczas logowania się do banku interne-
W SIECI towego wpisywane przez nas dane przesy-
Co należy wiedzieć...
łane są bezpiecznym, szyfrowanym łączem
Przydatna będzie podstawowa znajo-
SSL, a wysyłając poufne wiadomości e-ma- mość zagadnień kryptografii oraz PHP.
l http://php.net/mcrypt il korzystamy z systemu PGP, chroniącego
– MCrypt ich treść przed osobami trzecimi.
l http://php.net/mhash

PHP zawiera szereg funkcji pozwala-


Co obiecujemy...
– MHash Z artykułu dowiesz się, jak zbudować
l http://phpsec.org/
jących na szyfrowanie i generowanie ha- bezpieczny system logowania oraz apli-
– PHP Security Consortium
l http://php.net/security szów dowolnych łańcuchów znaków czy kację do składowania i szyfrowania pli-
– Bezpieczeństwo PHP zawartości plików. Część z nich dostęp- ków na serwerze WWW. Na przykła-
l http://pajhome.org.uk/crypt/ dach tych przedstawimy najpopularniej-
na jest natywnie, jednak duża większość
md5 sze i najlepsze rozwiązania kryptogra-
– funkcje haszujące dla JS przy wykorzystaniu zewnętrznego rozsze- ficzne w PHP.
rzenia, dołączonego do PHP. W tym ar-

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


Kryptografia w PHP Bezpieczeństwo

szym, bezpiecznym systemie hasło wpisa-


ne przez użytkownika przesyłane będzie Algorytmy i funkcje haszujące
w formie funcji skrótu, nawet dwukrotnie: Funkcją haszującą (czy też funkcją skrótu) nazywamy funkcję, która dowolnemu łańcu-
za pierwszym razem przez algorytm MD5, chowi znaków przyporządkowuje inną, unikalną wartość nazywaną powszechnie haszem
a następnie HMAC-MD5. (ang. hash) lub skrótem. Algorytm skrótu jest jednokierunkowy, otrzymanie pierwotnej war-
tości na podstawie wcześniej wygenerowanego skrótu wymaga ogromnej mocy oblicze-
Przesyłana wartość będzie prawidłowa niowej i jest praktycznie niemożliwe. Istotną właściwością funkcji skrótu jest to, że zmiana
tylko i wyłącznie raz, ponieważ klucz pu- choć jednego znaku łańcucha wejściowego, bez względu na jego długość, powoduje wy-
bliczny, potrzebny w procesie szyfrowania, generowanie całkiem innej wartości skrótu.
będzie za każdym razem unikalny – dyna- Nie posiadając dołączonego do PHP rozszerzenia MHash mamy do dyspozycji pięć
standardowo dostępnych funkcji pozwalających generować hasze dla trzech, najbardziej
micznie tworzony po stronie serwera. popularnych algorytmów – CRC32, MD5 oraz SHA1. Najnowsza wersja PHP5 udostęp-
Taki system okaże się bardzo pożą- niać będzie ponadto rozszerzenie Hash (http://php.net/hash), które zawiera praktycznie
dany, kiedy na serwerze nie posiadamy wszystkie algorytmy dostępne w MHash, a przy tym jest domyślnie zintegrowane z PHP
SSL, a chcemy przesyłać zaszyfrowane i nie wymaga żadnych dodatkowych bibliotek czy konfiguracji. Jednak póki co rozszerze-
nie Hash jest dostępne wyłącznie do testów i nie powinno być powszechnie wykorzysty-
dane. W przeciwieństwie do tradycyjnego wane tym bardziej, że zarówno sama zasada jego działania, jak i lista dostępnych algoryt-
systemu logowania, ten skutecznie ochro- mów może się jeszcze zmienić.
ni użytkowników przez atakami typu snif- Mimo to, dostępne natywnie w PHP algorytmy haszowania są w zupełności wystar-
fing, polegających na podsłuchaniu infor- czające, bez różnicy na rozmiar tworzonego projektu. Algorytmy takie jak CRC32 i MD5
są powszechnie i z powodzeniem wykorzystywane do tworzenia sum kontrolnych plików
macji wymienianych pomiędzy klientem lub poufnych danych, które muszą być dostępne jedynie w postaci umożliwiającej walida-
a serwerem, a co w konsekwencji pozwa- cję (np. hasła). Tabela 1 zawiera algorytmy dostępne natywnie w PHP oraz poprzez roz-
la na przejęcie wpisanej nazwy użytkowni- szerzenie MHASH.
ka oraz hasła i wykorzystanie ich do za-
logowania. Dodatkowo, wszystkie hasła
przechowywane będą jedynie w postaci gorytm HMAC wykorzystamy do wygene- wygenerowanie nowego klucza, a co za
skrótu MD5, także ich ewentualne pozna- rowania skrótu hasła, dołączając do nie- tym idzie, stworzony w oparciu o niego
nie przez osoby niepowołane jest całkowi- go losowy klucz wygenerowany po stro- hasz będzie miał całkiem inną wartość.
cie niegroźne (przyjmując oczywiście, że nie serwera. PHP nie posiada implemen- Co nam to daje? Otóż nawet w przypad-
hasła mają przyzwoitą długość, czyli przy- tacji HMAC algorytmu MD5, także musimy ku podsłuchania danych przez osobę
najmniej 10 znaków). Podstawowe infor- ją najpierw napisać opierając się o dostęp- trzecią, zdobyte w ten sposób informa-
macje dotyczące funkcji haszujących za- ną dokumentację (http://www.ietf.org/rfc/ cje będą całkowicie bezużyteczne. Skup-
wiera Ramka Algorytmy i funkcje haszu- rfc2104.txt). Kod źródłowy funkcji hmac_md5 my się najpierw na części HTML kodu
jące. Na Rysunku 1 przedstawiliśmy sche- przedstawiliśmy na Listingu 1. źródłowego, która umieszczona jest na
mat tworzonego systemu bezpiecznego Dodatkowym atutem przedstawio- Listingu 2. W pierwszych linijkach skryp-
logowania. nej implementacji jest to, że pozwala nie tu dołączamy plik języka JavaScript
W naszym przykładzie skorzystamy tylko na wygenerowanie skrótu HMAC- o nazwie md5.js, który zawiera dwie klu-
również z wersji HMAC algorytmu MD5. MD5, ale również HMAC-SHA1, tak więc czowe dla nas funkcje – hex_md5 oraz
HMAC-MD5 jest rozszerzeniem funkcji mamy do dyspozycji dwa dodatkowe al- hex_hmac_md5, będące odpowiednika-
skrótu MD5 i pozwala na dołączenie do- gorytmy do wykorzystania w naszych mi funkcji md5 i hmac_md5, dostępnych od
datkowego, dowolnego klucza. Wygenero- projektach. strony skryptu PHP. Poniżej umieszczo-
wany w ten sposób łańcuch znaków mo- W naszym systemie zakładamy, że ny jest kod źródłowy funkcji parseForm,
że być wówczas przesłany do odbiorcy, przesłane przez użytkownika hasło w po- która wywoływana jest tuż przed przesła-
który, posiadając owy sekretny klucz, mo- staci skrótu HMAC-MD5 będzie prawdzi- niem formularza. Formularz ten zawarty
że sprawdzić poprawność przesłanego we tylko i wyłącznie raz. Ponowne otwar- jest w części body dokumentu i zawiera
ciągu. W naszym systemie logowania al- cie czy przeładowanie strony spowoduje cztery istotne pola:

• key – pole typu hidden zawierają-


����� ���������� ce wygenerowany po stronie serwe-
������ ��� ��������
ra unikalny klucz, wykorzystywany na
���������� etapie logowania przez wersję HMAC
�����
����� ����������� ��������� funkcji haszującej,
���������
��������� • js – pole typu hidden, którego wartość
����������
określa, czy przeglądarka użytkowni-
������ ������������ ka obsługuje JavaScript. Jego domyśl-
ną wartością jest 0 (zero), zamieniane
�����������
���������
przed przesłaniem na 1 (jeden) w koń-
cowych linijkach funkcji doLogin,
• username – pole typu text, zawierają-
Rysunek 1. Schemat działania systemu bezpiecznego logowania ce wpisaną nazwę użytkownika,

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


Bezpieczeństwo Kryptografia w PHP

• password – pole typu password, zawie- pieczeństwa. Przyjrzyjmy się teraz innej Podstawy kryptografii
rające wpisane hasło. funkcji dostępnej natywnie w PHP, pozwa- – crypt
lającej na szyfrowanie łańcuchów znaków Wbrew przypuszczeniom funkcja crypt
Rola pola js jest bardzo ważna – w przy- za pomocą takich algorytmów jak DES czy pozwala na szyfrowanie w jedną stronę,
padku, gdy jego wartość jest równa 0, wie- Blowfish. Mowa tu o funkcji crypt nie ma funkcji decrypt, która pozwoliła-
my, że dane nazwy użytkownika i hasła
przesłane zostały w postaci jawnej, jed- Listing 1. Implementacja algorytmu HMAC-MD5 dla PHP
nak mimo to nadal jesteśmy się w stanie
bezproblemowo zalogować. Wróćmy jed- <?php
nak do kodu źródłowego omawianej funk- define('HMAC_MD5', 1);
define('HMAC_SHA1', 2);
cji. W przypadku nie podania nazwy użyt-
function hmac($string, $key, $algorithm = HMAC_MD5) {
kownika lub hasła, wyświetlamy stosowny $algorithm = $algorithm == HMAC_MD5 ? 'md5' : 'sha1';
komunikat i przenosimy kursor do pustego if (function_exists('hash_hmac'))
pola. W innym wypadku modyfikujemy za- return hash_hmac($algorithm, $string, $key);
wartość pola zawierającego hasło na hasz if (isset($key{64}))
$key = pack('H*', $algorithm($key));
HMAC-MD5 skrótu MD5 wpisanego hasła
$key = str_pad($key, 64, "\0");
oraz unikalnego klucza, dostępnego w po- $iPad = str_repeat("\x36", 64);
lu key. Dodatkowo ustawiamy wartość po- $oPad = str_repeat("\x5c", 64);
la js na 1 i zezwalamy na wysłanie formu- return $algorithm(($key ^ $oPad).pack('H*', $algorithm(($key ^ $iPad) .
larza zwracając wartość true. $string)));
}
Zobaczmy teraz, co dzieje się po stro-
function hmac_md5($string, $key) {
nie serwera – pełny kod PHP opisywane- return hmac($string, $key, HMAC_MD5);
go przykładu znajduje się na Listingu 3. }
Główna część skryptu to funkcja doLogin, ?>
której zadaniem jest sprawdzenie nazwy
Listing 2. Kod HTML systemu bezpiecznego logowania
użytkownika i hasła. W przypadku nie po-
dania którejkolwiek z tych danych zwraca- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
my wartość -1. Następnie sprawdzamy po- "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
prawność przesłanego z poziomu formula- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="PL">
<head>
rza klucza (pole key) z tym zapisanym
<title>PHP Solutions :: hmac_md5</title>
w sesji. Jeśli do tego momentu wszyst- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
ko zakończyło się pomyślnie, nawiązuje- <script type="text/javascript" src="md5.js"></script>
my połączenie z bazą danych i pobiera- <script type="text/javascript">
my nazwę użytkownika i skrót MD5 hasła. function $(id) { return document.getElementById(id) }
function parseForm() {
Jeśli osoba o poszukiwanej nazwie użyt-
var u = $('username'), p = $('password');
kownika nie istnieje, zwracamy wartość -3. if (u.value == '') {
W innym wypadku dokonujemy sprawdze- alert('Podaj nazwę użytkownika.');
nia poprawności przesłanego hasła w za- u.focus(); return false;
leżności od tego, czy przeglądarka użyt- }
if (p.value == '') {
kownika wykonała funkcję JavaScript. Je-
alert('Podaj hasło.');
śli hasło się zgadza, zwracamy nazwę p.focus(); return false;
użytkownika, jeśli nie, zwracamy wartość }
-4. Wspomniane ujemne wartości liczbo- p.value = hex_hmac_md5($('key').value, hex_md5(p.value));
we są wykorzystywane w głównej czę- $('js').value = 1; return true;
}
ści kodu do wyświetlenia odpowiedniego
</script></head><body>
komunikatu o błędzie. W przypadku, gdy <?php
zwrócona przez funkcję doLogin wartość if (!empty($errorString))
jest łańcuchem znaków, traktujemy ją ja- echo '<div id="error">'.$errorString.'</div>';
ko nazwę użytkownika i zapisujemy do ?>
<form method="post" action="login.php" onsubmit="return parseForm()"><div>
zmiennej sesji przekierowując jednocze-
<input type="hidden" name="key" id="key" value="<?php echo $_
śnie użytkownika na właściwą stronę pa- SESSION['key'] ?>" />
nelu administracyjnego. Na końcu modyfi- <input type="hidden" name="js" id="js" value="0" />
kujemy wartość zmiennej klucza zapisując <label for="username">Nazwa użytkownika:</label>
w niej nową, unikalną wartość. <input type="text" name="username" id="username" /><br />
<label for="password">Hasło:</label>
Reasumując: z pomocą jednej funkcji
<input type="password" name="password" id="password" /><br />
haszującej stworzyliśmy mały objętościo- <input type="submit" value="OK" />
wo skrypt o wielkich możliwościach, za- </div></form></body></html>
pewniający użytkownikom logującym się
do naszego serwisu wysoki poziom bez-

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


Kryptografia w PHP Bezpieczeństwo

by na odszyfrowanie informacji. W prakty-


Listing 3. Kod PHP systemu bezpiecznego logowania ce więc jest to kolejna funkcja haszująca.
Zobaczmy, jak na jej podstawie możemy
<?php
zbudować znacznie prostszy skrypt uwie-
function doLogin() { rzytelniania, oparty o protokół HTTP i al-
// Nie podano nazwy użytkownika lub hasła gorytm DES.
if (empty($_POST['username']) || Zasada działania każdego systemu
empty($_POST['password']))
uwierzytelniania jest niemal identyczna.
return -1;
$js = $_POST['js']; Po podaniu nazwy użytkownika i hasła
$key = $_SESSION['key']; skrypt dokonuje porównania tych danych
z informacjami przechowywanymi w bazie
// Klucz przesłany w rządaniu HTTP nie zgadza się z tym danych lub pliku. Najczęściej, ze wzglę-
// zapisanym w sesji
dów bezpieczeństwa, hasło użytkownika
if ($key != $_POST['key'])
return -2; nie jest przechowywane w postaci jawnej
– do bazy zapisujemy hasz hasła. Do two-
// Wyszukanie rekordu administratora rzenia tego rodzaju haszy możemy użyć
// o podanej nazwie użytkownika funkcji crypt. Jest ona standardowo do-
$mysql_handle = mysql_connect('host', 'username',
stępna w każdej instalacji PHP, więc nie
'password')
or die(mysql_error()); ma potrzeby ponownej komplikacji PHP,
mysql_select_db('test', $mysql_handle) czy modyfikacji pliku konfiguracyjnego.
or die(mysql_error()); Spróbujmy na początek stworzyć pro-
$mysql_result = mysql_query('SELECT username, password sty przykład oparty o funkcję crypt. Jego
FROM admin
kod źródłowy znajduje się na Listingu 4.
WHERE username = "'.mysql_real_escape_string(
$_POST['username'], $mysql_handle).'"'); Przy każdym przeładowaniu strony/
skryptu uzyskamy unikalną wartość ha-
// Brak rekordu administratora sza. Dzieje się tak dlatego, że za każdym
// o podanej nazwie użytkownika razem tworzony jest losowy ciąg znaków
if (mysql_num_rows($mysql_result) == 0)
– $salt – niezbędny w procesie tworze-
return -3;
$row = mysql_fetch_assoc($mysql_result); nia skrótu. Od drugiego argumentu wywo-
łania omawianej funkcji zależy jaki algo-
// Walidacja hasła w zależności od tego, rytm zostanie wykorzystany. I tak dla dwu-
// czy wykorzystany został JavaScript znakowej wartości zmiennej $salt będzie
if ($js) {
to standardowy algorytm DES, dla dzie-
require 'hmac_md5.php';
if (hmac_md5($row['password'], $key) != $_POST['password']) więcioznakowej – algorytm DES w wersji
return -4; rozszerzonej. Aby wykorzystać algorytm
} else { MD5, za zmienna $salt musi składać się
if (md5($_POST['password']) != $row['password']) z dwunastu znaków z początkowym łań-
return -4;
cuchem $1$, zaś dla algorytmu Blowfish
}
// Logowanie powiodło się, zwracamy nazwę użytkownika będzie to szesnaście znaków rozpoczy-
return $row['username']; nając od $2$ lub $2a$. Zmienna $salt jest
} później dołączana na początek wygenero-
session_start(); wanego skrótu.
$errorString = '';
Wróćmy do naszego przykładu,
$loginResult = doLogin();
switch ($loginResult) { w którym chcemy stworzyć skrypt uwie-
case -1: break; rzytelniania poprzez protokół HTTP. Na-
case -2: $errorString = zwy użytkowników i ich hasła trzymane
'Nieprawidłowa wartość klucza'; break; będą w pliku tekstowym (Listing 5).
case -3: $errorString =
Warto wspomnieć najpierw o cieka-
'Nieprawidłowa nazwa użytkownika'; break;
case -4: $errorString = wej właściwości funkcji crypt. Jeśli spró-
'Nieprawidłowe hasło'; break; bujemy stworzyć hasz informacji przesła-
default: nej przez użytkownika (przykładowo ha-
$_SESSION['username'] = $loginResult; sła) podając jako $salt hasz trzymany
header('Location:
w bazie danych lub pliku (wzorzec hasła),
http://server.com/admin/panel.php');
die(); funkcja powinna zwrócić nam identyczny
} skrót. Sytuacja ta ma miejsce tylko wtedy,
$_SESSION['key'] = md5(uniqid(rand(), true)); gdy podane hasło jest zgodne z tym wy-
korzystywanym przy tworzeniu skrótu po
?>
raz pierwszy. Wykorzystamy tę własność
w naszym skrypcie uwierzytelniania.

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


Bezpieczeństwo Kryptografia w PHP

Pamiętajmy, że uwierzytelnianie oparte na ne pliki i szczegółowe informacje o nich, Na początek coś prostego
protokole HTTP dostępne jest jedynie wte- takie jak wielkość czy typ MIME) – szyfrujemy fragment tekstu
dy, jeśli nasze PHP działa jako moduł Apa- przechowywać będziemy w bazie da- Zanim przejdziemy do implementacji na-
che. Jeżeli jest inaczej (czyli PHP działają- nych. Zapytanie SQL tworzące odpo- szej bezpiecznej składnicy danych, po-
ce jako CGI), nasz przykład nie zadziała. wiednią tabelę przedstawiliśmy na Li- znajmy schemat działania i możliwości
Przyjrzyjmy się Listingowi 6. Na po- stingu 7. W odpowiednich kolumnach rozszerzenia MCrypt. Na Listingu 8 przed-
czątku musimy sprawdzić, czy użytkownik przechowywać będziemy nazwę pliku, stawiony został przykładowy kod PHP, któ-
przesłał już swój login i hasło. Jeżeli nie, datę umieszczenia w bazie, typ MIME, rego zadaniem jest szyfrowanie podanego
wysyłamy odpowiednie nagłówki HTTP, a także długość oryginalnego pliku, klucz łańcucha znaków przy pomocy klucza,
które poinformują przeglądarkę, by wy- i sam plik w postaci zaszyfrowanej. a następnie jego odszyfrowanie.
świetlić odpowiednie okno logowania. Te
same nagłówki będą wysyłane, jeżeli po- Listing 4. Przykład wykorzystania funkcji crypt
damy błędne dane logowania. Gdy użyt-
kownik przesłał nam już odpowiednie da- <?php
$password = 'qwerty';
ne, musimy pobrać zawartość pliku, któ-
$salt = '$1$abcde$';
ry pełni funkcje naszej bazy i przechowuje $hash = crypt($password, $salt);
hasła użytkowników. Przy pomocy funkcji echo $hash;
explode podzielimy pobrane dane, oddzie- ?>
lając nazwy użytkowników i hasła. Ostat-
ni krok to pętla, która przechodzi przez Listing 5. Format danych pliku z danymi użytkowników
wszystkie rekordy, sprawdzając, czy użyt- admin:$1$Lp5.2//.$UMLhAWJJB9fLR.FU4gSAw1
kownik może być dopuszczony do danej user:$1$hI3.Wd0.$thbtd1at0auCNfn7s5Bdy1
strony. Aby sprawdzić poprawność hasła,
wykorzystujemy właściwość funkcji crypt Listing 6. Skrypt walidacji danych podczas autoryzacji
opisaną wcześniej.
<?php
Jak widać może być ona bez proble- define('USERS_FILE', 'users.txt');
mu wykorzystania w celu stworzenia pro- if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']))
stego systemu uwierzytelniania. Mimo {
wszystko jednak lepszym pomysłem jest $users = explode("\n", file_get_contents(USERS_FILE));
foreach($users as $user)
korzystanie z algorytmów takich jak MD5
{
czy SHA1 (czyli funkcji md5, sha1), ponie- $user = explode(':', trim($user), 2);
waż algorytm DES ma znacznie mniejszą if($user[0] == $_SERVER['PHP_AUTH_USER'])
złożoność, a przy tym podczas tworzenia {
skrótu wykorzystywane jest tylko 8 pierw- if(crypt($_SERVER['PHP_AUTH_PW'], $user[1]) != $user[1])
{
szych znaków.
header('WWW-Authenticate: Basic realm="Private"');
header('HTTP/1.0 401 Unauthorized');
Zaawansowana exit;
kryptografia – MCrypt }
MCrypt jest rozszerzeniem PHP udostęp- break;
}
niającym metody pozwalające na szyfro-
}
wanie danych przy użyciu algorytmów ta- }
kich jak DES, TripleDES, Blowfish, RC2 else
i wielu innych, lista najpopularniejszych al- {
gorytmów dostępnych dla tego rozszerze- header('WWW-Authenticate: Basic realm="Private"');
header('HTTP/1.0 401 Unauthorized');
nia umieszczona została w Tabeli 2. Opis
exit;
instalacji i konfiguracji biblioteki znajdu- }
je się w Ramce Instalacja rozszerzenia ?>
MCrypt.
MCrypt jest potężnym narzędziem, Listing 7. Struktura tabeli przechowującej dane zaszyfrowanych plików
dlatego też na jego podstawie stworzy- CREATE TABLE 'secure_files' (
my dość rozbudowany przykład. Napi- 'id' int(11) unsigned NOT NULL auto_increment,
szemy aplikację, która pozwoli nam na 'name' varchar(100) default NULL,
przechowywanie na serwerze plików 'mime_type' varchar(30) default NULL,
'date' int(10) unsigned default '0',
dowolnego typu w postaci zaszyfrowa-
'length' varchar(255) default '0',
nej, a pobranie ich na lokalny dysk moż- 'key' varchar(32) default NULL,
liwe będzie jedynie po podaniu właści- 'data' text,
wego hasła, identycznego z tym uży- PRIMARY KEY ('id')
tym w procesie szyfrowania dokumen- ) TYPE=MyISAM;

tu. Wszystkie dane (czyli zaszyfrowa-

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


Kryptografia w PHP Bezpieczeństwo

Tabela 1. Algorytmy dostępne natywnie w PHP oraz poprzez rozszerzenie MHASH sca w pamięci. Korzystamy w tym celu
Nazwa algorytmu Natywnie / Nazwa funkcji MHASH / Nazwa stałej z funkcji mcrypt_generic_init, przekazu-
jąc jej utworzone wcześniej uchwyty do
ALDER32 X – MHASH_ADLER32
modułu i wektora inicjującego, jak rów-
CRC32 X – crc32( ) X – MHASH_CRC32
nież wartość klucza, który będzie wyko-
GOST X – MHASH_GOST rzystywany w procesie szyfrowania. Klucz
HAVAL128 X – MHASH_HAVAL128 został przycięty do maksymalnej długo-
HAVAL160 X – MHASH_HAVAL160 ści obsługiwanej przez wybrany algorytm.
HAVAL192 X – MHASH_HAVAL192 Następnie wywołujemy funkcję mcrypt_
generic, której wynikiem jest zaszyfrowa-
HAVAL256 X – MHASH_HAVAL256
ny łańcuch znaków. Po wszystkim zwal-
MD4 X – MHASH_MD4 niamy zarezerwowaną pamięć funkcją
MD5 X – md5( ), md5_file( ) X – MHASH_MD5 mcrypt_generic_deinit i kończymy pra-
RIPEMD160 X – MHASH_RIPEMD160 cę z biblioteką MCrypt poprzez wywoła-
SHA1 X – sha1( ), sha1_file( ) X – MHASH_SHA1 nie mcrypt_module_close. Deszyfrowanie
informacji przebiega niemal analogicznie,
SHA256 X – MHASH_SHA256
z tą różnicą, że zamiast funkcji mcrypt_
TIGER X – MHASH_TIGER
generic stosujemy mdecrypt_generic,
TIGER128 X – MHASH_TIGER128 która przyjmuje identyczne argumenty co
TIGER160 X – MHASH_TIGER160 poprzednia.

Aby zainicjować mechanizmy bi- frowania wraz z opisem umieszczone zo- Bezpieczna składnica danych
blioteki Mcrypt, musimy wywołać funk- stały w Tabeli 3. Wynik działania omawia- Posiadając już niezbędną wiedzę na te-
cję mcrypt_module_open, która jako pierw- nej funkcji zapisujemy w zmiennej pomoc- mat wykorzystania biblioteki MCrypt, na-
szy argument przyjmuje nazwę algoryt- niczej $td. Kolejnym krokiem jest utworze- piszmy odpowiednie funkcje do naszej
mu, z którego chcemy skorzystać. Drugi nie za pomocą funkcji mcrypt_create_iv aplikacji. Kod źródłowy skryptu szyfrują-
i czwarty parametr to ścieżki do odpowied- tzw. wektora inicjującego. Funkcja ta cego przesyłany plik przedstawiony jest
nich modułów, pozwalających na skorzy- wymaga podania rozmiaru wektora oraz na Listingu 9.
stanie z danego algorytmu. Pozostawienie źródła danych losowych. Najlepiej skorzy- Przed dodaniem rekordu do tabe-
ich pustych, jak w przykładzie, powodu- stać ze stałej MCRYPT_RAND, ponieważ spo- li sprawdzamy czy użytkownik podał
je wykorzystanie ścieżek zdefiniowanych sób ten działa zarówno pod systemem wymagany klucz oraz czy sam plik został
w pliku konfiguracyjnym php.ini. Trzeci ar- Linux jak i Windows. W tym miejscu bi- pomyślnie przesłany na serwer. Następ-
gument definiuje tryb, w jakim ma odby- blioteka MCrypt jest gotowa na przyjęcie nie tworzymy skrót klucza wykorzystując
wać się szyfrowanie – my skorzystamy danych do zaszyfrowania. Najpierw musi- algorytm MD5, aby później przy pobie-
z trybu ecb. Wszystkie możliwe tryby szy- my zarezerwować odpowiednią ilość miej- raniu pliku móc szybko zweryfikować je-
go prawidłowość. Unikniemy tym samym
sytuacji, gdy ktoś ściągnie plik, po czym
okaże się, że pomylił klucze, a ściągnięty
plik jest bezużyteczny. Po odczytaniu pliku
szyfrujemy jego zawartość i dodajemy od-
powiedni wpis do bazy danych.
Zobaczmy, jak wygląda funkcja
pobierania wcześniej dodanych plików.

Instalacja rozszerzenia
MCrypt
Jeżeli posiadasz system Windows, mu-
sisz posiadać plik libmcrypt.dll do-
stępny do pobrania ze strony http://
ftp.emini.dk/pub/php/win32/mcrypt/,
a następnie umieścić go w katalogu c:
\windows\system32\. Posiadacze innych
systemów powinni odwiedzić stronę http://
mcrypt.sourceforge.net/ i pobrać źródła
biblioteki, a następnie je skompilować.
Aby korzystać z rozszerzenia MCrypt
w PHP, musimy go skompilować z dyrek-
tywą -with-mcrypt[=DIR] gdzie DIR to
ścieżka do biblioteki libmcrypt.
Rysunek 2. Strona domowa rozszerzenia MCrypt dla PHP (http://php.net/mcrypt)

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


Bezpieczeństwo Kryptografia w PHP

Tabela 2. Najpopularniejsze algorytmy szyfrowania dostępne w rozszerzeniu MCrypt Najpierw dokonujemy porównania klu-
Nazwa stałej Algorytm Rozmiar Rozmiar cza nadesłanego przez użytkownika
klucza bloku z tym przypisanym do danego pliku. Jeśli
MCRYPT_3DES Potrójny DES 168 64 klucze (a w praktyce ich skróty MD5) są
identyczne możemy rozszyfrować zwar-
MCRYPT_THREEWAY 3way 96 96
tość pliku. Cały ten proces nie różni się
MCRYPT_BLOWFISH Blowfish do 448 64
znacznie od zwykłego szyfrowania i został
MCRYPT_CRYPT Szyfrowanie unikowe 104 8 już wcześniej zaprezentowany. Ponieważ
MCRYPT_DES DES 56 64 użytkownik ma mieć możliwość zapisania
MCRYPT_GOST Sowiet Gosudarswiennyj 256 64 pliku na swój dysk, koniecznie staje się
wysłanie odpowiednich nagłówków do je-
MCRYPT_IDEA International Data Encryption 128 64
Algorithm go przeglądarki. Wykorzystamy do tego
funkcję header oraz nagłówki Content-
MCRYPT_SERPENT Serpent 128, 192 128
lub 256 Type i Content-Disposition. Na koniec,
jeszcze przed wysłaniem zawartości pliku,
MCRYPT_TWOFISH Twofish 128, 192 128
lub 256 musimy usunąć ewentualne puste znaki,
które funkcje szyfrujące mogły pozostawić
na końcu pliku, a które mogłoby spowodo-
Listing 8. Przykład wykorzystania rozszerzenia MCrypt wać późniejsze błędy w jego odczycie. Ja-
ko że znamy długość oryginalnego pliku
<?php
// Nasz klucz i łańcuch znaków do zaszyfrowania (została zapisana w bazie danych), może-
$key = 'tajny klucz'; my to bez problemu wykonać przy użyciu
$plain_text = 'bardzo poufne dane, które trzeba zaszyfrować'; funkcji substr.
// Inicjacja mechanizmów szyfrujących Przedstawiony przykład pokazał,
$td = mcrypt_module_open('blowfish', '', 'ecb', '');
jak szybko możemy zaszyfrować nasze
$key = substr($key, 0, mcrypt_enc_get_key_size($td));
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND); dane i jak przechowywać je w bazie.
// Szyfrowanie Mogą to być zarówno hasła jak i zawar-
mcrypt_generic_init($td, $key, $iv); tości całych plików. Pamiętajmy jednak,
$encrypted = mcrypt_generic($td, $plain_text); że proces szyfrowania 40MB pliku mo-
mcrypt_generic_deinit($td);
że dość znacznie obciążyć procesor,
echo $encrypted;
// Deszyfrowanie a także spowodować przepełnienie pa-
mcrypt_generic_init($td, $key, $iv); mięci przydzielonej dla PHP, co w efek-
$decrypted = mdecrypt_generic($td, $encrypted); cie spowoduje wyświetlenie komuni-
mcrypt_generic_deinit($td);
echo $decrypted;
mcrypt_module_close($td);
?>
O autorach
Listing 9. Skrypt szyfrujący przesłany plik
Łukasz Lach studiuje informatykę na
<?php
$file = $_FILES['file'];
wydziale Cybernetyki Wojskowej Aka-
if($file['error'] == UPLOAD_ERR_OK && !empty($_POST['key'])) demii Technicznej w Warszawie. Od
{ kilku lat zajmuje się tworzeniem apli-
if(!empty($_POST['name'])) kacji internetowych z wykorzystaniem
$file['name'] = $_POST['name'];
takich technologii jak PHP, XHTML,
$key = md5($_POST['key']);
$data = file_get_contents($file['tmp_name']);
JavaScript czy XML. Posiada certyfi-
$td = mcrypt_module_open('des', '', 'ecb', ''); kat Zend Certified Engineer. Jest au-
$subkey = substr($key, 0, mcrypt_enc_get_key_size($td)); torem licznych publikacji prasowych
$iv_size = mcrypt_enc_get_iv_size($td); w czasopismach polskich i zagra-
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
nicznych na temat tworzenia aplikacji
mcrypt_generic_init($td, $subkey, $iv);
$encrypted_data = mcrypt_generic($td, $data);
internetowych i bezpiecznym progra-
mcrypt_generic_deinit($td); mowaniu w PHP.
mcrypt_module_close($td); Michał Stanowski opanował
mysql_query("INSERT INTO 'secure_files' ('name', 'mime_type', 'date', w stopniu zaawansowanym umiejęt-
'length', 'key', 'data')
ność obiektowego tworzenia aplikacji
VALUES ('{$file['name']}', '{$file['type']}', UNIX_TIMESTAMP(),
'{$file['size']}', '$key', '".mysql_real_escape_string
w PHP5. Interesuje się bezpieczeń-
($encrypted_data)."')") or die(mysql_error()); stwem aplikacji internetowych oraz ich
} użytecznością. Swój wolny czas spę-
?> dza zwiedzając Polskę na rowerze.

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


Kryptografia w PHP Bezpieczeństwo

Tabela 3. Tryby szyfrowania wspierane przez rozszerzenie


MCrypt
Nazwa stałej Opis

MCRYPT_MODE_ECB Tryb książek elektronicznych

MCRYPT_MODE_CBC Tryb łańcuchowego łączenia blo-


ków
MCRYPT_MODE_CFB Tryb sprzężenia zwrotnego szy-
frowania
MCRYPT_MODE_OFB Tryb sprzężenia zwrotnego wyj-
ścia z 8 bitami sprzężenia
MCRYPT_MODE_NOFB Tryb sprzężenia zwrotnego wyj-
ścia z liczbą bitów sprzężenia
równą rozmiarowi bloku algo-
rytmu
MCRYPT_MODE_STREAM Tryb szyfrowania strumienio-
wego

Rysunek 3. Aplikacja bezpiecznego przechowywania plików na


serwerze w postaci zaszyfrowanej

katu o błędzie i zakończenie działania skryptu. Wydłużenie


czasu szyfrowania może być również spowodowane wybra-
niem algorytmu o dużej złożoności obliczeniowej. Dobrym
rozwiązaniem jest w tym wypadku szyfrowanie pojedynczych
bloków danych. O tym, jak duży może być blok dla danego
algorytmu szyfrującego, informuje nas funkcja mcrypt_get_
block_size lub mcrypt_enc_get_block_size.

Podsumowanie
W artykule przedstawiliśmy tylko część możliwości funkcji
kryptograficznych, jakie możemy zastosować w aplikacjach
PHP. Nasze przykłady pokazały Wam, jak zabezpieczać da-
ne czy tworzyć bezpieczne systemy uwierzytelniania. W ko-
lejnych artykułach opowiemy m.in. o wykorzystaniu bibliote-
ki OpenSSL oraz wysyłaniu bezpiecznych emaili z poziomu
PHP. n

PHP Solutions Nr 4/2006 41


Narzędzia

Zend Framework
Stopień trudności: lll
Piotr Szarwas

Pojawienie się Zend Frameworka wzbudziło


w świecie PHP wiele emocji. Zend, jedyna
prawdziwa firma od PHP, wypuściła – tak jak
należało – prawdziwy Framework dla PHP.
Wszyscy oczekiwali po tym rozwiązaniu bardzo
wiele i... nie zawiedli się.

M
imo że cały projekt jest jeszcze łaby nierówna. Porównanie to z pewno-
mocno rozwojowy, zdobywa co- ścią przedstawimy w jednym z kolejnych
raz większą popularność i uzna- artykułów. Póki co zacznijmy od samego
nie tysięcy deweloperów z całego świata. początku.
Czy zatem Zend Framework to rewolu-
cyjne rozwiązanie? Z wielu względów na Czy potrzebujemy
pewno tak: na razie można jedynie przy- kolejnego frameworka?
W SIECI puszczać, że stanie się on pewnym stan- Pytanie to zadawało wielu programistów,
dardem, przez co tworzenie i rozwijanie kiedy usłyszeli o pomyśle Zenda. Wielu
aplikacji w PHP stanie się dużo wydajniej- z Was wie, że dla PHP istnieje już wiele
• http://framework.zend.com/ sze i prostsze. Wielu deweloperów może frameworków, wystarczy zajrzeć na stronę
– strona główna Zend Fra- zacząć budować aplikacje używając tego
mework
• http://www.zend.com/php_ samego, solidnego narzędzia i rozmawiać Co należy wiedzieć...
collaboration_project – stro- tym samym językiem. Wsparcie Frame- Przydatna będzie podstawowa znajo-
na główna PHP Collabora-
worka przez firmę Zend i jej najlepszych mość budowy frameworków i programo-
tion Project.
• http://devzone.zend.com/ deweloperów może być gwarantem suk- wania obiektowego w PHP.
– strona główna Zend Deve-
loper Zone.
cesu i powszechności użycia. Co obiecujemy...
• http://www.zend.com/phpide/ Mimo to, za wcześnie jeszcze na peł- Z artykułu dowiesz się, jak zbudować
– z tej strony można pobrać prostą aplikację do agregacji newsów
ną ocenę i porównywanie projektu do tak
Eclipse PHP IDE z kanałów RSS z wykorzystaniem Zend
• http://framework.zend.com/ znanych rozwiązań jak eZ components, Frameworka. Pokażemy Wam najważ-
svn/framework/ – dostęp WACT, Solar czy Seagull. W trakcie pisa- niejsze komponenty Frameworka i po-
do repozytorium kodu Zend wiemy, czy warto używać go w środowi-
Framework poprzez WWW nia tego artykułu Zend Framework dostęp-
sku produkcyjnym.
i SVN. ny był w wersji 0.1.2, której daleko jest do
bycia kompletną i stabilną, więc walka by-

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


Zend Framework Narzędzia

http://www.phpwact.org/php/mvc_frame- komponentów. Zaliczają sie do nich ją się w specjalnym katalogu “incubator”.


works i sprawdzić, że ich ilość w przypad- przede wszystkim: Zend_Db, Zend_Feed, Są to moduły, które nie weszły jeszcze
ku frameworków MVC przekracza 40. Zend_InputFilter, Zend_Mail, Zend_Pdf, w skład Frameworka, ale prawdopodobnie
Czy warto więc pisać kolejny i odkry- Zend_Search, Zend_Log i Zend_Service. niedługo się w nim znajdą. Ich kod jest już
wać Amerykę na nowo? Pomysł jest jesz- Pełna lista komponentów wraz z krótkim jednak na tyle stabilny, że można próbo-
cze za „świeży”, aby móc powiedzieć, czy opisem znajduje się w Tabeli 1. wać je wykorzystywać we własnych pro-
jest potrzebny społeczności PHP, czy też Praktycznie każdy z modułów jest na- jektach i co najważniejsze wpływać na ich
nie. Warto dodać, że Zend Framework pisany tak, że jeżeli tylko istnieje taka po- funkcjonalność, chociażby poprzez wysy-
nie jest zwykłym framoworkiem MVC. trzeba możemy nie ingerując w kod frame- łanie maili do autorów kodu.
Jak twierdzą jego twórcy, ma rozwiązy- worka dodawać do niego potrzebne nam
wać 80% wszystkich zagadnień pojawia- funkcjonalność. Możliwe jest to dzięki sta- Piszemy pierwszą
jących sie podczas pisania aplikacji webo- rannemu projektowaniu i wykorzystywaniu aplikację
wych, dlatego między innymi w jego skład takich technik programowania obiektowe- W artykule postanowiłem skupić się na kil-
wchodzą takie komponenty jak Zend_Db, go jak pisanie do interfejsu czy też kompo- ku komponentach, które moim zdaniem
Zend_Mail, Zend_Pdf, Zend_Log czy też zycja. Wraz z podstawowymi komponen- mogą być najczęściej wykorzystywane
Zend_Search. Wszystkie one mają przy- tami dostajemy również te, które znajdu- w codziennej pracy. Zaliczyłem do nich
spieszyć i ułatwić pracę programisty, jak
żaden inny framework do tej pory.
PHP Collaboration Project
Założenia i motywacje Podczas konferencji Zend/PHP Conference and Expo 2005, która obyła się w drugiej poło-
wie października 2005 w San Francisco świat usłyszał o projekcie PHP Collaboration Pro-
Głównym celem projektu Zenda jest stwo-
ject. Celem projektu miałoby być utworzenie społeczności składającej się z firm i niezależ-
rzenie frameworka, w którym będzie się nych programistów, którzy razem stworzą narzędzia pozwalające PHP zaistnieć na poważ-
pisać równie łatwo jak w samym PHP nie w świecie wielkich projektów informatycznych. Głównym motorem tego projektu stały
i promowanie PHP 5, a w szczególności się trzy składniki Zend Framework, PHP Eclipse IDE i Zend Developer Zone. Zend Frame-
work miałby stać się podstawowym narzędziem zawierającym wszystkie potrzebne kom-
jego cech obiektowych. Szczególny nacisk
ponenty do napisania aplikacji webowej, PHP Eclipse IDE miałoby stanowić profesjonal-
położono tu na bezpieczeństwo. Frame- ne środowisko programistyczne (niestety część jego składników ma być płatna), natomiast
work ten w założeniach będzie mógł rywa- Zend Developer Zone to miejsce wymiany idei i pomysłów dla deweloperów. Wszystko to
lizować z takimi frameworkami jak Ruby dostępne za darmo. Tuż po ogłoszeniu tej inicjatywy wielu programistów w swoich blo-
gach i wypowiedziach na różnych forach komentowało ten fakt i, jak to było do przewidze-
on Rails czy Spring, będzie posiadał testy
nia, świat podzielił się na kilka frakcji. Byli tacy, którzy twierdzili, że Zend chce zdominować
jednostkowe (Unit Tests), dobre przykłady, wolny świat PHP, byli umiarkowani sceptycy, którzy mówili, że pomysł jest dobry, ale prze-
dokumentację i tutoriale. Wszystko to nie cież jest już tyle dobrych frameworków, byli też tacy, którzy twierdzili, że to świetny pomysł
jest niczym innowacyjnym. To co jest jed- i kolejny sukces PHP. Tych, którzy chcieliby prześledzić historię tych wypowiedzi odsyłam
do archiwum serwisu phpdeveloper.org.
nak wyjątkowe w przypadku Zend Frame-
owrka, to społeczność, która ma uczestni- Zend Framework – szybki start
czyć w budowaniu kodu. Już teraz należą Kod frameworka można pobrać ze strony http://framework.zend.com/download. Aktual-
na wersja ma numer 0.1.2. Dodatkowo należy jeszcze tak skonfigurować PHP, aby ścież-
do niej największe firmy IT i wielu znanych
ka gdzie umieścimy katalog z frameworkiem, była uwzględniana przez funkcje require
specjalistów PHP. i include przy poszukiwaniu plików. Można to zrobić na trzy sposoby w zależności od
możliwości konfiguracyjnych, do których macie dostęp:
Ogólna architektura
• zmodyfikować zmienną include _ path w pliku php.ini,
i najważniejsze składniki • utworzyć plik .htaccess w katalogu głównym aplikacji i umieścić w nim wpis php_va-
Zend Framework ma budowę kompo- lue include_path = '/ścieżka/do/katalogu'
nentową. Został tak zaprojektowany, aby • umieścić w pliku index.php wywołanie systemowej funkcji:
ini _ set( 'include _ path', ini _ get('include _ path').PATH _ SEPARATOR.'/
praktycznie każdy z jego elementów da-
ścieżka/do/katalogu' );.
ło się wykorzystać osobno. Oznacza to,
że jeżeli korzystamy już z jakiegoś frame- Aby móc korzystać z części MVC frameworka, potrzebny jest Apache z modułem mod_
worka nie musicie się w całości przesia- rewrite. W przypadku problemów z instalacją niedziałających komponentów, czy też innych
problemów odsyłam Was do stron FAQ Frameworka (http://framework.zend.com/faq),
dać na Zend Framework możecie wyko-
strony informującej o bugach (http://framework.zend.com/bugs) i dokumentacji (http://
rzystać tylko te z jego elementów, które są framework.zend.com/manual). Dodatkowo wiele ciekawych informacji na temat frame-
dla was użyteczne. worka możecie znaleźć w Zend Developer Zone znajdującej się pod adresem http://
Wielu osobom framework kojarzy devzone.zend.com/. Zawartość serwisu Zend Develor Zone jest o tyle ciekawa, że znaj-
dziecie tak szereg tutoriali i artykułów wprowadzających w tajniki programowania w PHP
się z kawałkiem kodu, który w pewnej
i Zend Frameworka.
mniej lub bardziej skończonej formie Warto jeszcze dodać, że jeżeli chcielibyście mieć dostęp do najświeższej wersji kodu
implementuje wzorzec architektoniczny Zend Frameworka, od niedawna można pobrać jego kod z repozytorium SVN. Instrukcja
MVC. Tak jak wcześniej wspomnieliśmy, zawierająca informację, jak należy to zrobić dostępna jest na stronie Frameworka. Opcja
ta, jest niezwykle atrakcyjna, gdyż z mojej obserwacji wynika, że praktycznie każdego
Zend Framework to nie tylko imple-
dnia w repozytorium pojawiają się jakieś zmiany poprawiającej funkcjonalność kodu i nie
mentacja wzorca MVC, to również sze- tylko.
reg bardzo przydatnych i praktycznych

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


Narzędzia Zend Framework

Tabela 1. Najważniejsze komponenty Zend Frameworka


Zend_Controller
Zawiera klasy implementujące wzorzec MVC. Głównymi składnikami komponentu są:
Zend_Controller_Front Implementacja wzorca Front Controller wraz z wzorcem Intercepting Filter.
Zend_Controller_Router Odpowiada za dekompozycję URL-a do nazwy kontrolera i akcji, którą należy wykonać.
Zend_Controller_Dispatcher Odpowiada za znalezienie właściwego kontrolera i wywołanie w nim odpowiedniej akcji.
Zend_Controller_Action Podstawowa implementacja kontrolera akcji.
Zend_View
Komponent do budowy obiektów widoku wzorca MVC.
Zend_Db
Zestaw klas do wykonywania wszelkich operacji typu CRUD na bazach danych. Implementacja opiera się na PDO:
Zend_Db_Adabter Warstwa abstrakcji uniezależniająca od różnych dialektów baz danych.
Zend_Db_Select Klasa ułatwiająca budowanie złożonych zapytań SQL przy pomocy interfejsu obiektowego. Jest to bar-
dzo prosta implementacja tzw. Criteria API znanego od dawna w świecie Javy.
Zend_Db_Table, Implementacja wzorca Active Row, niestety w obecnej chwili nie działa poprawnie z PDO, wspierany
Zend_Db_Table_Row jest jedynie driver mysqli.
Zend_Feed
Komponent do przetwarzania wiadomości RSS i Atom.
Zend_Filter
Klasa zawierająca szereg statycznych metod do służących do walidacji. Przed wykorzystaniem tej klasy należy koniecznie zapoznać się z jej
kodem. Powodów jest kilka po pierwsze część metod nie ma jeszcze implementacji (np. isEmail i isUri). Cześć metod do poprawnego dzia-
łania wymaga ustawienia odpowiedniego setlocale. W szczególności tyczy się to tych wszystkich metod, które korzystają z klas znakowych
([:alpha:],[:alnum:]). Implementacja metody isPhone nie jest dostosowana do standardów wielu krajów Europy i nie tylko.
Zend_HttpClient
Prosty klient HTTP
Zend_InputFilter
Klasa do filtrowania danych wejściowych (np. Tablice $_GET i $_POST). Klasa korzysta ze statycznych metod klasy Zend_Filter. Warte zapa-
miętania jest, że w podstawowej konfiguracji tablice $_GET i $_POST są ustawiane na null, a do danych w nich zawartych można odwoływać
się jedynie poprzez obiekty klasy Zend_InputFilter.
Zend_Json
Klasa serializująca zmienne PHP do formatu JSON. Więcej o formacie JSON możecie przeczytać na stronie http://www.json.org.
Zend_Log
Zestaw klas do logowania wszelkiego rodzaju operacji, z możliwością jednoczesnego logowania do wielu źródeł (np. plik i ekran).
Zend_Mail
Zestaw klas do budowania i wysyłania maili wraz z załącznikami. Posiada możliwość wysyłki maili zarówno przy pomocy funkcji mail jak
i bezpośredniego połączenia z serwerem SMTP.
Zend_Mime
Klasa zawierająca szereg użytecznych metod do współpracy z wiadomościami MIME.
Zend_Pdf
Komponent zawiera klasy do dynamicznej budowy plików PDF. Niestety wspierane są jedynie znaki ISO-8859-1, tak więc dla wielu ta klasa
jest póki co bezużyteczna.
Zend_Search
Jest to implementacja znanego z Javy silnika do wyszukiwania Lucene. Komponent napisany jest całkowicie w PHP, nie wymaga do swojego
działania bazy danych. Wszystkie potrzebne mu do działania dane przechowywane są w plikach. Niestety podobnie jak Zend_Pdf rozwiązanie
to wspiera w obecnej chwili jedynie znaki ISO-8859-1. Autorzy piszą, że dodanie wsparcia dla innych stron kodowych jest na ich liście TODO.
Zend_Service
Zestaw klas wpierających komunikację z Web Serwisami. Najważniejsze składniki komponentu to:
Zend_Service_Rest Klasa ogólnego zastosowania dla Web Serwisów typu REST
Zend_Service_Yahoo Klasa ułatwiająca korzystanie z wielu Web Serwisów Yahoo
Zend_Service_Amazon Klasa ułatwiająca korzystanie z wielu Web Serwisów Amazon
Zend_Service_Flickr Klasa ułatwiająca korzystanie z wielu Web Serwisów Flickr
Zend_XmlRpc
Komponent ułatwiający zdalne wywoływanie procedur w standardzie XML-RPC
Zend_Cache
Kod tego komponentu znajduje się w tzw. inkubatorze nie jest więc podstawowym składnikiem Frameworka. Służy do cachowania wszelkie-
go rodzaju danych, w plikach lub bazie SQLite.

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


Narzędzia Zend Framework

��

�� ��

�� ��

��

��

��

��

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

��

��

Rysunek 1. Struktura bazy danych do agregacji newsow z kanałów RSS

Zend_Controller, Zend_View, Zend_Db, w katalogu log. Poza logowaniem do pli- musimy przekazać do niższych warstw
Zend_Log i Zend_Mail. ku można jeszcze wysyłać logi na ekran aplikacji. Najlepiej zrobić to wykorzystu-
Wykorzystując te moduły napisze- i do bazy danych. Jeżeli jest taka potrzeba jąc dwie statyczne metody znajdujące się
my prostą aplikację do agregacji newsów możemy napisać własny mechanizm logo- w klasie Zend: register i registry. Meto-
z kanałów RSS. Pominiemy cześć apli- wania, wystarczy zaimplementować inter- dy te wspólnie implementują powszechnie
kacji do agregacji newsów, a skupimy się fejs Zend_Log_Adapter_Interface i zare- znany wzorzec Registry, pierwsza z nim
jedynie na części prezentacyjnej. Zakłada- jestrować swój adapter tak, jak zrobiłem zapisuje obiekty w rejestrze, a druga pobie-
my, że każdy news należy do jednego ka- to w przykładzie. Kolejny krok to konfigu- ra je z niego. Sam rejestr jest Sigletonem
nału i jednej kategorii, a kategorie są od racja bazy danych. Jest ona bardzo pro- dostępnym w każdym miejscu warstwy apli-
siebie niezależne. Struktura bazy danych sta wymaga jedynie wywołania statycznej kacji. Ostatnim elementem pliku index.php
pokazana jest na Rysunku 1. Funkcjonal- metody factory na klasie Zend_Db. Me- jest wywołanie metody dispatch na obiek-
ność tej części jest następująca: użytkow- toda ta zwróci obiekt/sterownik połącze- cie front kontrolera, która rozpoczyna pro-
nik może wyświetlić wszystkie newsy dla nia do bazy danych, na którym będzie- ces przetwarzania, w wyniku którego od-
jednej kategorii i wysłać do kogoś ema- my wykonywać wszystkie operacje zwią- naleziona zostanie odpowiednia akcja i wi-
il z powiadomieniem o ciekawym newsie. zanie z dostępem do danych. W naszym dok (Rysunek 2). Dla nas najważniejsza
Każda akcja wysłania emaila jest logowa- przypadku zwrócony obiekt opakowuje jest wiedza, że dzięki modułowi mod_rew-
na w celu zbierania statystyk. mechanizm PDO. Zend Framework do- rite wszystkie odwołania do aplikacji prze-
Tworzenie kodu rozpoczniemy od pli- starcza jeszcze dwa dedykowane sterow- chodzą przez index.php. Zend Framework
ku index.php, jego zawartość pokazana niki dla Oracla i MySQL ten drugi działa wyposażony jest w prosty dispatcher tych-
jest na Listingu 1. Skrypt ten jako jedy- w oparciu o rozszerzenie mysqli. Po uru- że odwołań na kontrolery i akcje, jego dzia-
ny widoczny jest przez WWW. Odpowia- chomieniu połączenia do bazy danych po- łanie najlepiej wyjaśnić na podstawie odwo-
da za załadowanie wszystkich potrzeb- zostało nam jedynie skonfigurowanie kla- łań pojawiających się mojej aplikacji:
nych klas Zend Frameworka przy pomo- sy Zend_Controller_Front stanowiącej
cy statycznej metody loadClass znajdu- implementację wzorca Front Controller • / przekierowuje do akcji indexAction
jącej się w klasie Zend. Klasa ta posia- i klasy Zend_View. Jedynymi parametrami w kontrolerze IndexController,
da jeszcze kilka pomocnych metod, któ- konfiguracyjnymi dla tych klas są ścież- • /index/ przekierowuje do akcji inde-
re zaprezentuję w dalszej części artykułu. ki, w których znajdować się będą utwo- xAction w kontrolerze IndexController,
Kolejnym krokiem jest konfiguracja logge- rzone w późniejszej części artykułu kon- • /index/index/ przekierowuje do akcji
ra, bazy danych i front kontrolera. W przy- trolery akcji i widoki. Podczas powyższe- indexAction w kontrolerze IndexCon-
padku loggera zakładamy, że do logowa- go procesu konfiguracji utworzyliśmy trzy troller,
nia będzie wykorzystywany adapter logu- bardzo ważne obiekty: sterownik bazy da- • /mail/ przekierowuje do akcji index-
jący dane do pliku log.txt znajdującego się nych, widok i front kontroler. Dwa z nich Action w kontrolerze IndexController,

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


Zend Framework Narzędzia

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

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

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

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

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

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

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

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

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

Rysunek 2. Diagram sekwencji prezentujący schemat przetwarzania odwołania do strony przez klasę Zend_Controller_Front.
Z uwagi na przejrzystość pewne mało znaczące elementy procesu zostały pominięte

• /mail/send przekierowuje do akcji sen-


dAction w kontrolerze MailController, Listing 1. Plik index.php – fundament naszej aplikacji
• /index/index/id/2 przekierowuje do ak-
// dodajemy ścieżkę do Zend Framework
cji indexAction w kontrolerze index ini_set( 'include_path', ini_get('include_path').PATH_SEPARATOR.'../lib' );
z parametrem id równym 2.
// ładujemy główną klasę Zend (Zend.php),
Podsumowując: jeżeli nie został wy- // klasa ta zawiera jedynie statyczne metody.
include 'Zend.php';
brany żaden kontroler, dispatcher
wywoła akcję indexAction w kontrole- // ładujemy wszystkie niezbędne klasy
rze IndexController, jeżeli została po- Zend::loadClass('Zend_Controller_Front');
dana nazwa kontrolera, a nie została Zend::loadClass('Zend_Controller_Action');
podana nazwa akcji, dispatcher wywoła Zend::loadClass('Zend_View');
Zend::loadClass('Zend_Db');
akcje indexAction w podanym kontrole-
Zend::loadClass('Zend_Filter');
rze. Aby przekierowania zadziałały, pli- Zend::loadClass('Zend_InputFilter');
ki, w których znajdują się klasy kontro- Zend::loadClass('Zend_Log');
lerów, muszą mieć takie same nazwy Zend::loadClass('Zend_Log_Adapter_File');
jak kontrolery.
Zend_Log::registerLogger(new Zend_Log_Adapter_File('../logs/info.txt'));
Nasza aplikacja będzie składała się
z dwóch kontrolerów IndexController $dbParams = array ('host' => '127.0.0.1', 'username' => 'postgres',
i MailController. Pierwszy odpowiada za 'password' => '', 'dbname' => 'myrss');
wyświetlanie wiadomości RSS, drugi za $db = Zend_Db::factory('pdoPgsql', $dbParams);
wysyłanie maili. Kod tych dwóch kontro-
$controller = Zend_Controller_Front::getInstance();
lerów znajduje się na Listingach 2 i 3.
$controller->setControllerDirectory('../app/controllers');
Wszystkie dziedziczą po abstrakcyjnej
klasie Zend_Controller_Action. Kontro- $view = new Zend_View();
ler IndexController zawiera dwie meto- $view->setScriptPath('../app/views');
dy indexAction i noRouteAction. Metoda
// zapisywanie obiektów w rejestrze
noRouteAction ma specjalne zastoso-
Zend::register('view',$view);
wanie wywoływane przez front kontroler Zend::register('db',$db);
(tylko w kontrolerze IndexController)
w sytuacji, gdy nie zostanie znalezio- $controller->dispatch();
ny kontroler, który miał obsłużyć dane
żądanie. Każdy kontroler dziedziczący

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


Narzędzia Zend Framework

po klasie Zend_Controller_Action mu- dzo proste, sprowadza się do utworze- Utworzenie logu jest jeszcze prost-
si implementować metodę indexAction, nia obiektu klasy Zend_Mail, ustaleniu sze, aby to zrobić wystarczy wywołać
w przypadku kontrolera IndexControl- adresata, odbiorcy, treści i tematu wia- statyczną metodę log klasy Zend_Log.
ler metoda ta odpowiada za wyświetle- domości i w końcu wysłania maila. W najprostszym wydaniu metoda ta
nie listy wszystkich kategorii RSS-ów
i listy RSS-ów należących do katego-
Listing 2. Kod kontrolera InextController
rii wybranej przez użytkownika aplika-
cji. Jeżeli żadna kategoria nie zosta- // każda aplikacja musi posiadać kontroler o nazwie IndexController,
ła wybrana zakładamy wybór pierwszej // jest on standardowo wywoływany wraz z akcją index w sytuacji, gdy użytkownik
z listy. Obiekt bazy danych i widoku, // nie wymienił w URL-u ani kontrolera, ani akcji.
class IndexController extends Zend_Controller_Action {
na którym operuje akcja pobierane są
z rejestru. Dane z bazy pobieramy przy // metoda indexAction jest wykonywana w sytuacji, gdy nie została podana
pomocy metody query, która ma dwa // żadna akcja w ramach kontrolera lub użytkownik wywołał akcję index.
parametry, pierwszym jest zapytanie public function indexAction() {
SQL, a drugim opcjonalnym lista pa- // pobieramy z rejestru wcześniej utworzone obiekty widoku
// i połączenia do bazy danych
rametrów przekazywana do zapytania.
$view = Zend::registry('view');
Działanie metody query z oboma para- $db = Zend::registry('db');
metrami prezentuje jej drugie wywołanie
w akcji index. Dzięki takiemu przekazy- // metoda query odpowiada za wykonywanie zapytań SQL do bazy, zwraca tzw.
waniu parametrów są one zawsze qu- // statement. Wywołanie na obiekcie statementu metody fetchAll powoduje,
// że zwrócone zostają rekordy w postaci ponumerowanej od zera tablicy
otowane, co powoduje, że kod odpor-
// tablic asocjacyjnych, w których kluczami są nazwy kolumn zwróconych
ny jest na ataki SQL Injection. Metoda // w zapytaniu.
query zwraca obiekt PDOStatement. Da- $categories = $db->query("SELECT * FROM feeds_categories
ne pobrane z bazy przekazywane są ORDER BY fc_name" )->fetchAll();
do widoku dzięki mechanizmowi ma-
// klasa View implementuje magiczne metody __set i __get,
gicznych metod __get i __set, który
// dzięki którym w prosty sposób możemy w obiekcie widoku tworzyć nowe
jest wyjątkową cechą PHP. Identyczny // atrybuty, dane zapisane w tych atrybutach będą dostępne w szablonie pod
efekt można uzyskać wywołując metodę // zmienną $this->nazwa_atrybutu.
assign na obiekcie widoku. $view->categories = $categories;
Ostatnią operacją wykonywaną
// wszystkie przekazane do kontrolerów dane z zapytania dostępne są
w akcji index jest wyświetlenie pliku wi-
// poprzez metodę _getParam, ma ona dwa parametry, pierwszym jest
doku. Zend Framework nie posiada me- // nazwa parametru, drugim wartość, która zostanie zwrócona w przypadku
chanizmu szablonów, nie jest też sprzę- // gdy data o podanej nazwie nie istnieje. Drugi parametr jest
żony z żadnym znanym rozwiązaniem // opcjonalny. Przed zapisaniem do zmiennej pomocniczej $categoryId
tego typu, dlatego jako szablony wyko- // parametru id filtrujemy jego zawartość, tak aby w $categoryId
// zapamiętać jedynie część liczbową id.
rzystujemy standardowe pliki PHP, gdzie
$categoryId = Zend_Filter::getDigits( $this->_getParam( 'id' ) );
językiem szablonów jest również PHP.
Przykładowy szablon pokazany jest na if ( !$categoryId ){
Listingu 4. Szablon renderowany jest $categoryId = $view->categories[0]['fc_id'];
wewnątrz obiektu widoku stąd wszystkie }
dane przekazane do widoku dostępne
$view->items = $db->query("SELECT feeds_items.* FROM feeds_channels
są pod zmienną $this, dodatkowo dzię- LEFT JOIN feeds_items
ki takiemu podejściu możemy w szablo- ON ( feeds_channels.fch_id = feeds_items.fch_id )
nie korzystać ze wszystkich metod klasy WHERE feeds_channels.fc_id = :fc_id
Zend_View, należy do nich np. metoda ORDER BY feeds_items.fi_pub_date",
array( "fc_id" => $categoryId ) )->fetchAll();
escape. Drugi kontroler naszej aplikacji
posiada również dwie metody: pierw- // metoda render odpowiada za wygenerowanie i wyświetlenie szablonu
sza, prostsza – indexAction – wyświe- echo $view->render('IndexView.php');
tla jedynie formularz do wysyłania ma- }
ili. Druga metoda jest już znacznie cie-
// metoda ta jest wywoływana w sytuacji, gdy użytkownik chciał odwołać się
kawsza, a jej głównym zadaniem jest
// do nieistniejącego kontrolera. Metodę te wywołuje front kontroler.
wysłanie maila do osoby, której adres public function noRouteAction() {
podamy w formularzu wysyłki i zalo-
gowanie tego faktu do pliku logu. Kod // w przypadku gdy zostanie wywołany nieistniejący kontroler
został znacznie uproszczony – docelo- // przekierowujemy użytkownika na stronę główną aplikacji, czyli do
// kontrolera IndexController i metody indexAction.
we rozwiązanie powinno zawierać cho-
$this->_redirect('/');
ciażby sprawdzanie, czy podany w for- }
mularzu email jest faktycznie emailem. }
Jak widać, wysłanie maila jest bar-

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


Zend Framework Narzędzia

wymaga podania jednego parametru, dowym poziomem logowania jest DE- ne wysłanie maila, jeżeli ktoś odświeży
treści logu, jeżeli jest taka potrzeba, BUG. Po wysłaniu maila i zalogowaniu widok strony w przeglądarce.
jako drugi parametr możemy podać tego faktu, akcja sendAction przy po-
poziom logu. Dostępne jest sześć po- mocy metody _redirect przekierowuje Czy powinniśmy
ziomów, wszystkie są zdefiniowane nas automatycznie na stronę główną. przesiąść się na Zend
w klasie Zend_Log jako stałe, standar- Dzięki temu nie będzie możliwe ponow- Framework?
Pozostałe komponenty Frameworka, któ-
rych nie omówiliśmy w artykule, są rów-
Listing 3. Kod kontrolera MailController
nie proste w użyciu – do części z nich
class MailController extends Zend_Controller_Action { znajdziecie przykłady w dokumentacji lub
w kodzie, w katalogu demos.
public function indexAction() {
Pomimo że kod w Zend Framewor-
$view = Zend::registry('view');
echo $view->render('MailView.php'); ku pisze się bardzo szybko i prosto, zde-
} cydowanie za wcześnie jest na produk-
cyjne wykorzystanie tego narzędzia. Co
public function sendAction() { prawda wiele z jego elementów jest już
$view = Zend::registry('view');
w pełni funkcjonalnych, ale dewelope-
// klasa Zend_InputFilter opakowuje statyczne metody klasy rzy Frameworka mają jeszcze sporo do
// Zend_Filter, wywołana w formie takiej jak poniżej czyści tablicę zrobienia. Najważniejsze braki, i nie je-
// $_POST, wszystkie znajdujące się w niej parametry dostępne są dyne, to niedziałający dobrze kompo-
// poprzez filtrujące metody klasy Zend_InputFilter. Czyszczenie nent Zend_Db. Moduły Zend_Search
// tablicy można wyłączyć ustawiając drugi parametr konstruktora na false
i Zend_Pdf wspierają jedynie kodowanie
$post = new Zend_InputFilter($_POST);
ISO-8859-1, co czyni je bezużyteczny-
$emailTo = $post->getAlpha('emailTo'); mi dla wielu programistów. Brak mecha-
$emailFrom = $post->getAlpha('emailFrom'); nizmu szablonów, czy też integracji ze
Smarty lub PHPTAL jest dodatkowym mi-
// przygotowujemy i wysyłamy mail. Robimy to przy pomocy klasy Zend_Mail.
nusem. Sama dokumentacja ma jeszcze
Zend::loadClass('Zend_Mail');
spore braki. Dlatego na dzień dzisiejszy
$mail = new Zend_Mail('iso-8859-2'); proponuję wam pozostać przy rozwią-
$mail->addTo($emailTo); zaniach, które wykorzystujecie obecnie.
$mail->setFrom($emailFrom); Nie zapominajcie jednak o Zend Frame-
$mail->setBodyText('tekst powiadomienia');
worku – wydaje się, iż jest skazany jest
$mail->setSubject('TestSubject');
$mail->send(); na sukces: w jego rozwój zaangażowani
są znani deweloperzy i największe firmy
// rejestrujemcy w logu fakt wysłania maila. To gdzie zostanie wysłany log związane z PHP. Na pewno wrócimy do
// zależy od tego, jaki appender został zarejestrowany (plik index.php) tego tematu w kolejnych wydaniach ma-
Zend_Log::log('Został wysłany mail');
gazynu, gdyż już niedługo Zend Frame-
$this->_redirect('/'); work może stać się bardzo ważnym, albo
} nawet najważniejszym graczem na are-
nie frameworków dla PHP, a jego prak-
} tyczna znajomość może okazać się po-
trzebna przy szukaniu pracy na stanowi-
Listing 4. Przykładowy plik szablonu
sku programisty PHP. n
<!-- Nagłówek -->
<table width="90%" cellspacing="0" cellpadding="0" border="0" align="center">
<tr>
<td>
<table width="100%" cellspacing="0" cellpadding="2" border="0">
O autorze
<?php foreach( $this->items as $item ) { ?>
<tr> Piotr Szarwas jest pracownikiem
<td> SUPER-MEDIA Interactive i dokto-
<?= $this->escape( $item['fi_title'] ) ?> rantem na wydziale Fizyki Politechniki
</td>
Warszawskiej. Od 2003 roku projektuje
</tr>
<? } ?> aplikacje WWW w oparciu o PHP4/5.
</table> Obecnie zajmuje się tworzeniem fra-
</td> meworka dla PHP opartego na rozwią-
</tr> zaniach Hibernate i Spring.
Kontakt z autorem:
</table><!-- Stopka -->
piotr.szarwas@gmail.com

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


Narzędzia

eZ componets w akcji, czyli


galeria zdjęć krok po kroku
Stopień trudności: lll
Tobias Schlitt

eZ components stanowią platformę dla PHP


do budowy aplikacji WWW klasy Enterprise.
Stosowanie tych wysokiej jakości komponentów
znacznie przyśpiesza proces tworzenia
oprogramowania zmniejszając jednocześnie
ryzyko niepowodzenia projektu.

A
plikacja webowa, którą będziemy gu 1 pokazane jest, w jaki sposób moż-
analizować w ramach niniejsze- na stworzyć tabelę do składowania albu-
go artykułu, to prosta galeria ob- mów (prezentowana składnia jest specy-
W SIECI razów. Funkcjonalność, którą będziemy ficzna dla MySQL, jednak nie powinno być
budować to tworzenie albumów oraz problemu z zaadoptowaniem jej do innych
l http://ez.no/doc/components/ wczytywanie zdjęć do tych albumów. Ga- rozwiązań bazodanowych).
overview leria będzie ponadto skalować wczyty- Struktura albumu jest bardzo prosta.
– dokumentacja API kompo-
nentów eZ
wane obrazy i tworzyć ich pomniejszone Mamy tutaj pola tytuł (ang. title) i opis
l http://ez.no/doc/components/ wersje w celach prezentacyjnych. (ang. description) oraz numeryczny iden-
view/latest/(file)/tutorials.html Prezentowana aplikacja ma charak- tyfikator. Warto zwrócić w tym miejscu
– materiały do nauki obsługi
komponentów eZ ter edukacyjny, jednak przy odrobinie do- uwagę na użycie modyfikatora auto_incre-
l http://ez.no/community/ datkowego wysiłku ze strony czytelnika, ment. Jeśli wykorzystywany silnik bazoda-
forum/ez_components
– forum użytkowników kom- może być z powodzeniem zastosowana
ponentów eZ w rozwiązaniach klasy produkcyjnej.
http://ez.no/community/
Co należy wiedzieć...
l

articles/image_manipula-
tion_with_ez_components Zaczynamy! Czytelnik powinien znać język PHP
– artykuł na temat kompo- Struktura albumów w naszej galerii, oraz (przynajmniej na poziomie podstawo-
nentów eZ wym) oraz ogólne zasady projektowania
l http://pecl.php.net/package/ dane na temat zdjęć będą przechowywa- orientowanego obiektowo.
filter ne w bazie danych. W prezentowanym
– rozszerzenie do filtrowa- Co obiecujemy
nia PECL przykładzie użyłem bazy MySQL, jed- Czytając niniejszy artykuł czytelnik na-
l http://www.phppatterns.com/ nak czytelnik może równie dobrze sko- uczy się budować aplikację webową (ga-
docs/start lerię obrazów) z wykorzystaniem kompo-
– wzorce projektowe dla
rzystać z innych technologii wspieranych
PHP przez komponenty eZ (http://ez.no/doc/ nentów eZ.
components/overview/latest). Na Listin-

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


eZ components Narzędzia

nowy nie obsługuje tego mechanizmu, to Tabela1. Struktura katalogów naszej aplikacji
w prezentowanym dalej kodzie PHP na- Directory Description
leżałoby wprowadzić pewne modyfikacje.
actions/ Katalog actions/ będzie zawierał źródła kontrolerów dla akcji obsługi-
Zakładam jednak, że opcja ta jest dostęp- wanych z poziomu naszej aplikacji (używam tutaj wzorca projektowe-
na i aby nie powodować zamętu, nie będę go MVC).
opisywał tego szczególnego przypadku. data/ Katalog data/ będzie zawierał dane obrazów. Warto zauważyć, że ten
Generalnie, sugeruję aby w trakcie prak- katalog musi mieć ustawione prawa zapisu i odczytu z poziomu serwe-
tycznego badania rozpatrywanego przy- ra webowego. Trzeba pamiętać, że tego rodzaju uproszczenie nie po-
kładu – szczególnie przy pierwszym czy- winno występować w aplikacjach o charakterze produkcyjnym.
taniu – pracować z bazą MySQL. Zaosz- interfaces/ W katalogu interfaces/ będziemy przechowywać interfejsy kontrolerów
czędzi to czytelnikowi niepotrzebnych kło- dla poszczególnych akcji.
potów. models/ Katalog models/ będzie zawierał klasy modelu, reprezentujące album i
Na Listingu 2 zaprezentowana jest ko- fotografię.
lejna tabela, tym razem służąca do prze- pos/ Katalog pos/ ma specjalne znaczenie, jako że bedzie zawierał de-
chowywania informacji odnośnie poszcze- finicje obiektów przeznaczonych do składowania (http://ez.no/doc/
gólnych zdjęć. Tabela ta jest bardzo pro- components/view/latest/(file)/classtrees_PersistentObject.html). Obiek-
ty takie stanowią implementację wzorca “aktywny rekord” w katego-
sta: oprócz własnego identyfikatora zawie-
riach komponentów eZ.
ra ona dodatkowo identyfikator powiąza-
nego albumu. Można tu również znaleźć templates/ Katalog templates/ będzie zawierał szablony HTML (w kategoriach
MVC będzie to warstwa prezentacyjna naszego projektu).
pola tytuł i opis.
Stwórzmy teraz podstawową strukturę Jeśli instalacja PEAR jest odpowiednio ez.no/doc/components/view/latest/(file)/
katalogów dla naszej aplikacji – propozy- skonfigurowana (warto zwrócić uwagę na classtrees_UserInput.html) jest zależny od
cja znajduje się w Tabeli 1. parametr include_path), to sprawa jest filtra PECL. Najprostszym sposobem po-
praktycznie zakończona. brania tego dodatkowego pakietu jest po-
Instalacja Oczywiście nie każdy użytkow- nownie użycie instalatora PEAR:
Kolejnym krokiem przy realizacji naszego nik ma ochotę używać PEAR. Inny spo-
zadania jest pobranie komponentów eZ. sób instalacji polega na pobraniu archi- $ pecl install filter-beta
Najprościej byłoby użyć w tymcelu insta- wum ze strony domowej projektu (http:
latora PEAR (www.pear.php.net). Mając to //ez.no/download/ez_components) i roz- Jeśli użytkownik i w tym przypadku nie
narzędzie wystarczy wpisać: pakowaniu go w wybranym katalogu (po- chce korzystać z PEAR, to istnieje moż-
nownie należy zwrócić uwagę, aby pa- liwość pobrania odpowiedniego pakietu
$ pear channel-discover components.ez.no rametr include_path zawierał docelo- źródłowego (http://pecl.php.net/package/
$ pear install ezc/eZComponents wy katalog). Komponent UserInput (http:// filter) ze strony PECL i ręczna kompilacja
rozszerzenia. Po wykonaniu tych kroków
Listing 1. Tabela do składowania albumów trzeba jeszcze odpowiednio skonfiguro-
wać plik php.ini.
CREATE TABLE album (
id int(11) unsigned NOT NULL auto_increment, Kodowanie:
title varchar(200) NOT NULL default '',
description text NOT NULL,
pierwsze kroki
PRIMARY KEY (id) Możemy wreszcie rozpocząć kodowanie.
) ENGINE=MyISAM DEFAULT CHARSET=utf8; Na początku tworzymy plik gallery.php,
który powinien znajdować się w głów-
Listing 2. Tabela do przechowywania informacji na temat zdjęć nym katalogu naszego projektu. Plik ten
CREATE TABLE photo (
będzie punktem wejścia do naszej ga-
id int(11) unsigned NOT NULL auto_increment, lerii: do niego będziemy odwoływać się
album int(11) NOT NULL default '0', z przeglądarki. Wewnątrz pliku umie-
title varchar(200) NOT NULL default '', ścimy podstawowe elementy nasze-
description text NOT NULL,
go kodu: mechanizm automatycznego
PRIMARY KEY (id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
wczytywania (ang. autoload) (http://pl.
php.net/autoload), klasę głównego kon-
Listing 3. Pierwszy kod w naszym projekcie trolera i odwołania do kolejnych elemen-
tów funkcjonalności.
require_once 'ezc/Base/base.php';
Warto w tym miejscu spojrzeć na Li-
function __autoload( $className )
{ return ezcBase::autoload( sting 3. Na samym początku widać od-
$className ); wołanie do pliku ezc/Base/base.php.
} require_once 'config.php'; Jest to jedyny plik powiązany z kom-
ponentami eZ, do którego będziemy

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


Narzędzia eZ components

odnosić się w sposób bezpośredni. Znaj- szablon związany z daną akcją i przeka- nizm obsługi kontrolera. Obiekty typu sin-
dują się tam podstawowe, wspólne me- zuje do niego odpowiednie parametry. gelton zbadamy szczegółowo w dalszej
chanizmy, wykorzystywane przez wszyst- Dodatkowo wspomniana klasa ma części tekstu.
kie komponenty eZ; między innymi wspo- statyczne metody, które udostępniają Na początek rozpatrzmy definicję tabli-
mniany wcześniej mechanizm automa- wszystkie globalne obiekty w postaci sin- cy odwzorowującej akcje na odpowiednie
tycznego wczytywania (aktywowany po- geltonów. W ten właśnie sposób obsługi- pliki z kodem źródłowym pomocniczych
przez definiowanie funkcji __autoload()) wane są mechanizmy konfiguracji i logo- kontrolerów, które są wczytywane przy
oraz kilka podstawowych wyjątków. wania, łączenie z bazą danych (getSes- okazji występowania konkretnych żądań.
Główny komponent sprawujący kon- sion()) i konwersja obrazów. Dzięki sto- Ze względów bezpieczeństwa pliki te są
trolę nad naszym projektem nazywa się sowaniu tego rodzaju podejścia możli- określone w sposób statyczny – umiesz-
ezcGallery (patrz Listing 4). Klasa ta za- we jest bardzo proste odwoływanie się czone bezpośrednio w kodzie. Warto za-
wiera konstruktor i dwie metody: run() do tych mechanizmów w każdym punkcie pamiętać prostą zasadę: nigdy nie defi-
i display(). Pierwsza ze wspomnianych naszej aplikacji – bez potrzeby tworzenia niujmy elementów logiki aplikacji na bazie
metod jest odpowiedzialna za przekazy- wielokrotnych instancji obiektów, na przy- napisu przychodzącego od użytkownika.
wanie żądanej akcji do odpowiedniego kład: ezcGallery::getSession(). W tej Poniżej przedstawiona jest definicja wspo-
kontrolera roboczego, zaś druga pobiera części artykułu przeanalizujemy mecha- mnianej tablicy:

private
Listing 4. Szkielet klasy ezcGallery
$actions = array(
class ezcGallery 'listing' => 'actions/listing.php',
{ public function __construct() 'show' => 'actions/show.php',
{...}
'create' => 'actions/create.php',
public function run()
{...} 'add' => 'actions/add.php',
public static function getConfig() 'archive' => 'actions/archive.php',
{...} ); private $currentAction;
public static function getLog()
{...}
Zmienna $currentAction przechowuje
public function display()
{...} aktualnie wybrany obiekt reprezentujący
public static function getSession() akcję. Spójrzmy na Listing 5, aby zoba-
{...} czyć jak to wygląda.
public static function getConverter() Na początek w konstruktorze, przy-
{...} }
pisujemy do zmiennej $action wartość
Listing 5. Konstruktor klasy ezcGallery listing, która określa akcję domyślną.
W dalszej kolejności sprawdzamy przy
public function __construct() użyciu dedykowanego komponentu Use-
{ $action = 'listing';
rInput, czy pojawiło się żądanie GET. Je-
if ( ezcInputForm::hasGetData() )
{ $definition = array(
śli tak, to definiujemy zbiór reguł dla Use-
'action' => new ezcInputFormDefinitionElement( rInput w celu walidacji żądania. W tym
ezcInputFormDefinitionElement::REQUIRED, 'string' przypadku oczekujemy, że w nagłów-
), ); ku pojawi się pole action, zawierające ja-
$form = new ezcInputForm( INPUT_GET, $definition );
kiś napis. Dalej tworzymy instancję obiek-
$action = ( $form->hasValidData( 'action' ) ) ? $form->action : listing';
} if ( !isset( $this->actions[$action] ) ) {
tu ezcInputForm, którego zadaniem jest
throw new Exception( "Invalid action <".htmlentities($action).">." ); obsłużenie żądania GET. W ostatnim
} kroku (końcowa sekcja bloku if), przypi-
require_once $this->actions[$action]; sujemy pobraną akcję do odpowiedniej
$this->currentAction = new $action;
zmiennej (jeśli dane były poprawne) lub
}
ustawiamy akcję domyślną (jeśli proces
Listing 6. Metoda display w klasie ezcGallery realizuje prosty mechanizm szablo- walidacji przebiegł niepomyślnie).
nowania W dalszej kolejności sprawdzamy, czy
stworzona akcja może być obsłużona.
public function display()
Jeśli tak nie jest, to rzucamy wyjątek (nie
{ $template = $this->currentAction->getTemplateVars();
ob_start();
możemy obsłużyć nieistniejącej akcji). Je-
$res = include 'templates/' . $this->currentAction->getTemplate(); śli akcja jest poprawna, to odwołujemy się
$html = ob_get_contents(); do powiązanego z nią pliku i uruchamiamy
ob_end_clean(); odpowiedni kontroler.
if ( $res === false ) { throw new Exception( 'Template error.' ); }
Proszę zwrócić uwagę, że w pre-
echo $html;
}
zentowanym przykładzie używam wyjąt-
ków, aby powiadomić użytkownika o sy-
tuacji awaryjnej. Praktyka ta nie powin-

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


eZ components Narzędzia

na być stosowana w rozwiązaniach pro- powiedzialny za tę czynność przedstawio- waliśmy w ramach przygotowań bazy da-
dukcyjnych ze względu na kwestie bezpie- ny jest na Listingu 7. nych dla naszej galerii. Kod źródłowy kla-
czeństwa – wyjątki mogą zostawić istotne W tym momencie podstawowa struk- sy Album przedstawiony jest na Listingu 8.
z punktu widzenia ochrony aplikacji infor- tura naszej aplikacji jest gotowa. W klasie tej można zaobserwować
macje! Metoda run jest dla odmiany bar- trzy publiczne składowe, które odpo-
dzo prosta. W jej ciele wywołujemy jedynie Obsługa albumów wiadają polom rekordu w bazie danych.
metodę run, na stworzonym w konstrukto- Teraz omówimy kod używany w celu wy- Jako że obiekty tej klasy są przeznaczo-
rze obiekcie reprezentującym akcję: świetlenia strony z widokiem ogólnym al- ne do składowania, dlatego do interfejsu
bumu. Funkcjonalność ta biedzie stano- wprowadzamy dwie metody: setState()
public function run() wić domyślną akcję w naszej aplikacji. Je- i getState(). Metody te będą wykorzysta-
{ śli ktoś z Czytelników zainstalował oma- ne do serializacji i deserializacji modelu
$this-> wiany przykład i chciałby go przetestować obiektu do bazy danych i z powrotem.
currentAction->run(); w gotowej postaci, to przed wykonaniem Obydwie metody przyjmują jako para-
} tej czynności wymagane jest dodanie do metry tablice asocjacyjne, które są wy-
bazy danych kilku testowych rekordów. korzytywane do przekazywania danych
Ponieważ komponent do obsługi szablo- Kod źródłowy kontrolera odpowiedzialne- w obydwie strony. Do poszczególnych
nów (eZ Template) nie jest jeszcze gotowy go za widok ogólny albumu umieszczo- właściwości modelu można odwołać się
(przewidujemy jego dołączenie w wersji 1.1 ny jest w pliku actions/listing.php. Kod ten na podstawie odpowiednich kluczy.
pakietu, który powinien pojawić się latem wymaga dwóch innych plików z naszej te- Zanim przeanalizujemy źródła kontro-
bieżącego roku), do obsługi widoku użyje- stowej aplikacji. W ramach niniejszego lera akcji “Listing”, musimy zapoznać się
my zwyczajnego kodu HTML z osadzonymi przykładu nie będziemy wykorzystywać z metodą ezcGallery::getSession(). Za-
wstawkami w języku PHP. Ten prosty me- mechanizmu automatycznego wczytywa- wartość tej metody przedstawiona jest na
chanizm obsługi szablonów zrealizowany nia, więc wspomniane pliki są dołączone Listingu 9.
jest w ramach metody display. Zawartość w sposób manualny: Omawiana metoda nie zwraca w bez-
tej metody przedstawiona jest na Listingu 6. pośredni sposób połącznia do bazy da-
Każdy roboczy kontroler obsługują- require_once 'interfaces/action.php'; nych. W zamian otrzymujemy tzw. sesję
cy konkretną akcję implementuje metodę require_once 'models/album.php'; obsługi składowania (ang. persistent ses-
getTemplateVars() która pozwala głów- sion). Między tymi dwoma klasami obiek-
nemu kontrolerowi pobrać odpowiednie Model reprezentujący album (models/ tów istnieje istotna różnica. Połączenie do
obiekty wymagane do wygenerowania album.php) jest dziecinnie prosty i odpo- bazy danych (czyli obiekt klasy definiu-
odpowiedniego kodu HTML. Do obsłuże- wiada strukturze rekordu, który opraco- jącej komponent eZ Database) pozwala
nia wypełnionej zawartości szablonu uży-
jemy wewnętrznego mechanizmu buforo- Listing 8. Struktura danych reprezentująca album
wania wyjścia języka PHP, aby uniknąć
brzydkich raportów o błędach wyświetla- class Album
nych jako zawartość stron WWW. Każ- { public $id = null;
public $title = '';
dy plik szablonowy ma dostęp do tablicy
public $description = 'No description available.';
$template, gdzie może znaleźć wszyst- public function getState()
kie potrzebne zmienne. Kontroler kon- { return array(
kretnej akcji może również zwracać odpo- 'id' => $this->id,
wiednie szablony w zależności od stanu, 'title' => $this->title,
'description' => $this->description,
w którym się znajduje (na przykład w celu
); }
zaprezentowania rezultatu po wypełnieniu public function setState( array $properties )
i zaakceptowaniu danych na formularzu). { foreach ( $properties as $key => $val )
Wreszcie, jeśli nie wystąpił żaden błąd, { $this->$key = $val; } } }
renderowany jest wynikowy kod HTML.
Listing 9. Definicja statycznej metody getSession() w klasie ezcGallery
Na samym końcu w pliku gallery.php uru-
chamiamy głównego kontrolera. Kod od- public static function getSession()
{ if ( self::$persistentSession === null )
{ $db = ezcDbFactory::create(
Listing 7. Końcowa sekcja pliku gal-
self::getConfig()->getSetting( 'config', 'database', 'dsn' )
lery.php: uruchamiamy głównego
); ezcDbInstance::set( $db );
kontrolera
self::$persistentSession = new ezcPersistentSession(
try { ezcDbInstance::get(),
$gallery = new ezcGallery(); new ezcPersistentCodeManager(
$gallery->run(); self::getConfig()->getSetting( 'config', 'php', 'base' )
$gallery->display(); } . DIRECTORY_SEPARATOR
catch ( Exception $e ) . 'pos' ) );
{ die( $e->getMessage() ); } } return self::$persistentSession; }

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


Narzędzia eZ components

wykonywać na bazie danych bezpośred- wywołaniu metody getSession() wyko- sy ezcPersistentSession to tzw. me-
nie operacje przy pomocy języka SQL. rzystywana jest instancja istniejąca. Do nadżer kodu. Obiekt ten obsługuje pliki
Sesja obsługi składowania oferuje mecha- stworzenia sesji potrzebny jest obiekt re- zawierające konfigurację mechanizmu
nizm o wiele bardziej potężny, jako że po- prezentujący połącznie z bazą danych. składowania (pliki te umieszczone są
zwala działać na znacznie wyższym po- Obiekt ten jest tworzony przy pomocy w katalogu pos/). Bazowa część ścieżki
ziomie abstrakcji. Możemy przy jej pomo- dedykowanej fabryki ezcDbFactory. Da- dostępu do plików jest pobierana z kon-
cy zapisywać, odczytywać, modyfikować ne niezbędne do sfinalizowania tego pro- figuracji. Komponenty do obsługi konfi-
i kasować obiekty modelu, bez stosowa- cesu są pobierane z zewnętrznego pli- guracji obsługują czytanie i zapisywanie
nia języka zapytań w sposób bezpośredni. ku ini. Do pliku tego można odwołać się ustawień i opcji. W przypadku naszego
Wynikowy kod SQL jest generowany auto- przy pomocy mechanizmu obsługi konfi- projektu wykorzystujemy plik ini, gdzie
matycznie przez obiekt sesji. guracji. Prześledźmy szczegółowo cały zapisane są wszystkie ustawienia wyma-
Wewnątrz metody getSession()wyko- mechanizm tworzenia sesji. Na początku gane przez galerię. Przykładowy plik ini
rzystywana jest prywatna statyczna skła- wywoływana jest metoda ezcDbInstance: wygląda następująco:
dowa $persistentSession. W ramach tej :set() w celu zainicjalizowania kompo-
zmiennej zachowana jest pojedyncza in- nentu eZ Database. Przy pomocy no- [database]
stancja sesji oferującej abstrakcyjny in- wo uzyskanego obiektu reprezentujące- dsn=mysql://root:
terfejs dostępu do bazy. go połączenie z bazą danych, tworzymy iglowinthedark@localhost/test
Instancja jest tworzona przy pierw- sesję obsługi składowania. Drugi para- [php]
szym odwołaniu; przy każdym kolejnym metr przekazywany do konstruktora kla- base=./

Listing 10. Definicja statycznej metody getConfig() w klasie ezcGallery DSN to napis określający sposób połą-
czenia z bazą danych. Pierwsza część
public static function getConfig() napisu określa rodzaj używanego sil-
{ nika bazodanowego. Dalej, zgodnie
if ( self::$config === null )
z konwencją URI, umieszczona jest na-
{ self::$config = ezcConfigurationManager::getInstance();
self::$config->init( 'ezcConfigurationIniReader', dirname( __FILE__ ) ); zwa użytkownika i hasło. Informacje
} te są potrzebne, aby połączyć się
return self::$config; } z maszyną na, której uruchomiona
jest baza danych (w większości przy-
Listing 11. Definicja obiektu składowania dla albumu
padków jest to localhost). Po ostatnim
$def = new ezcPersistentObjectDefinition(); ukośniku (“/”) podana jest nazwa doce-
$def->table = "album"; lowego repozytorium. Druga stała okre-
$def->class = "Album"; śla miejsce w którym znajdują się pli-
$def->idProperty = new ezcPersistentObjectIdProperty();
ki nagłówkowe. Zmienna base określa
$def->idProperty->columnName = 'id';
$def->idProperty->propertyName = 'id';
ścieżkę, w której składowane są klasy
$def->idProperty->generator = new ezcPersistentGeneratorDefinition( PHP. Na Listingu 10 przedstawiona jest
'ezcPersistentSequenceGenerator' ); metoda getConfig().
$def->properties['title'] = new ezcPersistentObjectProperty(); W tym przypadku ponownie wyko-
$def->properties['title']->columnName = 'title';
rzystany jest wzorzec singleton (ten
$def->properties['title']->propertyName = 'title';
$def->properties['title']->propertyType = 'string';
sam wzorzec jest również zastosowany
$def->properties['description'] = new ezcPersistentObjectProperty(); w komponencie Config. Metoda init()
$def->properties['description']->columnName = 'description'; przyjmuje nazwę klasy służącej do czy-
$def->properties['description']->propertyName = 'description'; tania konkretnego rodzaju konfigura-
$def->properties['description']->propertyType = 'string';
cji (w naszym przypadku, pliki typu ini)
return $def;
oraz katalog w którym dane konfigura-
Listing 12. Wyszukiwanie albumów cyjne są składowane. Sposób interpre-
tacji drugiego parametru jest zależny
class Listing implements Action od klasy odpowiedzialnej za odczyty-
{ private $albums;
wanie konfiguracji. Przykładowo, gdyby
public function __construct()
{} konfiguracja była składowana w bazie
public function run() danych, drugi parametr mógłby być
{ $this->albums = ezcGallery::getSession()->find( obiektem reprezentującym połączenie.
ezcGallery::getSession()->createFindQuery( 'Album' ),'Album'); Wracając na moment do Listingu 9
}
(definicja metody getSession()), Czy-
public function getTemplate()
{ return 'listing.php'; } telnik może sobie przypomnieć, w jaki
public function getTemplateVars() sposób odczytywane są ustawienia kon-
{ return array( 'albums' => $this->albums ); figuracyjne. Wygląda to w ten sposób,
} } że wywołujemy metodę getSetting()
na stworzonym obiekcie, przy czym

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


eZ components Narzędzia

jako parametry podajemy: nazwę pliku


zawierającego konfigurację (bez roz- Listing 13. Prosty szablon
szerzenia), nazwę sekcji w pliku ini oraz
<?php include 'templates/head.php'; ?>
nazwę klucza, dla którego chcemy po- <table width="600">
brać wartość. <tr>
Jak wspominałem wcześniej, w ce- <td>[
lu zapisania naszego modelu w bazie <a href="gallery.php?action=create">Create album</a>]
</td>
danych potrzebujemy definicji klasy
</tr>
ezcPersistentCodeManager. Stwórzmy </table>
teraz w katalogu pos/ plik album.php <ul>
i umieśćmy w nim kod przedstawiony <?php foreach ( $template['albums'] as $album ) { ?>
na Listingu 11. <li><a href="gallery.php?action=show&album=<?php echo $album->id; ?>">
<?php echo $album->title; ?></a>
Każda definicja obiektu przezna-
</li>
czonego do składowania jest instancją <?php } ?>
klasy ezcPersistentObjectDefinition. </ul>
Klasa ta posiada trzy atrybuty służące <?php include 'templates/foot.php'; ?>
do określenia tabeli w bazie danych
Listing 14. Podstawowa struktura metody run() w klasie odpowiedzialnej za ope-
i powiązanej z nią klasy modelu oraz
rację tworzenia nowego albumu
mechanizmu generowania identyfika-
torów. Pierwsze dwa atrybuty są zwy- if ( ezcInputForm::hasPostData() )
czajnymi napisami, a ostatni jest struk- { // ...}
else {
turą (komponenty eZ emulują struktury
$this->template = 'create_form.php'; }
przy pomocy zwyczajnych klas) o na-
zwie ezcPersistentObjectIdProperty. Listing 15. Sprawdzanie danych żądania POST w metodzie run() obsługującej
Elementy tej struktury to nazwa kolum- operację tworzenia nowego albumu
ny i nazwa składowej oraz genera-
$definition = array(
tor odpowiedzialny za przydzielanie
'title' => new ezcInputFormDefinitionElement(
identyfikatorów dla nowo tworzonych ezcInputFormDefinitionElement::REQUIRED, 'string' ),
obiektów. W naszym przykładzie ja- 'description' => new ezcInputFormDefinitionElement(
ko generatora używamy instancji klasy ezcInputFormDefinitionElement::REQUIRED, 'string' ),
ezcPersistentSequenceGenerator, któ- ); $form = new ezcInputForm( INPUT_POST, $definition );

ra bazuje na mechanizmie auto_incre-


if ( !$form->hasValidData( 'title' ) ) {
ment oferowanym przez silnik MySQL. throw new Exception( 'Title missing.' ); }
W dalszej części pliku zdefiniowane są if ( !$form->hasValidData( 'description' ) ) {
pozostałe powiązania pomiędzy atrybu- throw new Exception( 'Desription missing.' ); }
tami klas modelu oraz kolumnami tabeli
Listing 16. Wstawianie nowego obiektu w metodzie run() obsługującej operację
w bazie danych (ten fragment kodu ma
tworzenia nowego albumu
wysoce samoopisowy charakter, więc
nie będę go już dodatkowo wyjaśniał). // ten fragment kodu wykonuje finalne wstawianie obiektu
Wreszcie, w ostatniej linii, zwracamy $this->album = new Album();
skonfigurowany obiekt definicji. $this->album->title = $form->title;
$this->album->description = $form->description;
W tym momencie spełnione są
ezcGallery::getSession()->save( $this->album );
wszystkie potrzebne warunki do rozpo- ezcGallery::getLog()->log(
częcia pracy z obiektami przeznaczo- "New album created with ID <{$this->album->id}>.", ezcLog::INFO,
nymi do składowania. W dalszej części array( 'category' => 'Create' ) );
artykułu zapoznamy się ze sposobami $this->template = 'create_submit.php';

używania tych obiektów.


Listing 17. Tworzenie instancji obiektu reprezentującego log (ezcGallery::ge-
Spójrzmy teraz na Listing 12, gdzie tLog())
zdefiniowany jest kontroler odpowiedzial-
ny za wyszukiwanie albumów. Na po- public static function getLog()
{ if ( self::$log === null )
czątku zdefiniowany jest pusty (i w rze-
{ self::$log = ezcLog::getInstance();
czywistości nieużywany) konstruktor. $mapper = self::$log->getMapper();
Sytuacja ta jest powodowana wymogiem $writer = new ezcLogUnixFileWriter( 'data/', 'gallery.log' );
narzuconym przez interfejs akcji. W me- $filter = new ezcLogFilter();
todzie run pobieramy wszystkie albumy $rule = new ezcLogFilterRule( $filter, $writer, true );
$mapper->appendRule( $rule );
z bazy danych i składujemy je w prywat-
self::$log->source = 'Gallery';
nej składowej o nazwie $albums. } return self::$log; }
Aby pobrać składowany obiekt,
wywołujemy metodę find() na obiekcie

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


Narzędzia eZ components

sesji. Metoda ta zwraca tablicę obiek- szablonu, jako że w przypadku pozosta- rzenia nowego albumu. Prześledź-
tów znalezionych na podstawie zapyta- łych akcji nie pojawia się w tym kontek- my implementację kontrolera odpowie-
nia. Przy wywołaniu metody specyfikuje- ście nic nowego. dzialnego za tę operację. Konstruktor
my też typ klasy modelu, której obiektów Pliki head.php i foot.php nie zawie- klasy jest ponownie pusty, najciekaw-
szukamy w bazie danych (drugi parametr rają żadnej interesującej zawartości, są szy fragment kodu znajduje się w cie-
przy wywołaniu find()). tam jedynie podstawowe tagi HTML. le metody run(). Ponieważ definicja
Zapytanie, którego używamy (find) Pliki te zostały wydzielone ze względu wspomnianej metody jest w tym przy-
jest fizycznie obsługiwane z poziomu na to, że są dołączane na każdej stro- padku dość rozległa, dlatego zdecy-
sesji składowania. W tym konkretnym nie naszej webaplikacji. Korzystając dowałem się zaprezentować ją w kliku
przypadku używamy bardzo uproszczo- z pętli foreach przeglądamy tablicę za- mniejszych krokach.
nej formy tego zapytania; automatycz- wierającą albumy (tę, którą jest zwra- Jak widać, główny szkielet meto-
nie wygenerowany kod SQL ma postać cana metodzie getTemplateVars()) dy stanowi instrukcja warunkowa if,
SELECT * FROM Album, jako że pobieramy i odwołujemy się poprzez publicz- w której sprawdzamy czy pojawiło się żą-
wszystkie dostępne albumy. Bardziej za- ne składowe do zawartości umiesz- danie POST (patrz Listing 14). Jeśli tak
awansowany przykład wykorzystania te- czonych w niej obiektów, aby wstrzyk- się nie stało, to powinniśmy wyświetlić
go mechanizmu będzie pokazany w dal- nąć do kodu HTML odpowiednie dane. formularz, poprzez który użytkownik bę-
szej części artykułu. Sądzę, że przedstawiony kod jest na dzie mógł wysłać wspomniane żądanie.
Metoda getTemplate() zwraca tyle prosty, iż nie wymaga dodatkowych Aby uzyskać taki a nie inny efekt, usta-
umieszczoną na stałe nazwę szablo- wyjaśnień. wiamy w odpowiedni sposób składową
nu, zaś getTemplateVars() udostępnia $template.
pobrane wcześniej albumy. Ostatnią Tworzenie nowego Fragment kodu przedstawiony na
czynnością, którą musimy wykonać jest albumu Listingu 15 pobiera dane żądania POST
implementacja szablonu. Pozwolę so- W poniższej sekcji opiszemy, w jaki i wykonuje na nich podstawowy test
bie zaprezentować tylko jeden przykład sposób realizowane jest żądanie two- sprawdzający. Podobny kod widzieliśmy
już wcześniej, w kontekście wykorzysta-
Listing 18. Konstruktor klasy reprezentującej akcję wczytywania obrazu nia komponentu UserInput. Jedyna róż-
nica między tymi dwoma fragmentami
public function __construct() polega na tym, że typy żądań są różne
{
(w poprzednim przykładzie obsługiwali-
if ( ezcInputForm::hasGetData() )
{ śmy żądanie typu GET). Kod przedsta-
$definition = array( wiony na Listingu 16 wykonuje finalną
'album' => new ezcInputFormDefinitionElement( operację wstawiania nowego obiektu do
ezcInputFormDefinitionElement::REQUIRED, 'int' bazy danych.
), );
Na początku tworzymy nowy obiekt
$form = new ezcInputForm( INPUT_GET, $definition );
if ( !$form->hasValidData( 'album' ) ) klasy modelu i zapamiętujemy go
{ throw new Exception( 'Album ID missing.' ); w kolejnej prywatnej składowej, zwa-
} $this->album = ezcGallery::getSession()->load( 'Album', $form->album ); nej album. Składowa ta przyda się nam
} później, przy generowaniu danych na
else
podstawie szablonu. W dalszej kolejno-
{ throw new Exception( 'Album ID missing.' ); }}
ści, na podstawie danych odebranych
Listing 19. Manipulating the uploaded images z formularza ustawiamy składowe
reprezentujące tytuł i opis albumu,
$this->photo = new Photo(); i tworzymy sesję składowania w celu
$this->photo->title = $form->title;
wstawienia obiektu do bazy. Dla uprosz-
$this->photo->description = $form->description;
$this->photo->create( $_FILES['photo']['tmp_name'], $this->album->id ); czenia projektu, nasza galeria nie będzie
ezcGallery::getLog()->log( oferować możliwości edycji. Funkcjonal-
"Added new photo with ID <{$this->photo->id}>.", ność taka jest stosunkowo łatwa w im-
ezcLog::INFO, plementacji; moglibyśmy ją uzyskać
array( 'category' => 'Upload' )
przy pomocy metod update(), delete()
); $this->template = 'add_submit.php';
i load() klasy ezcPersistentSession. In-
Listing 20. Tworzenie konwertera obrazów ne przydatne w tym kontekście metody
to loadIfExists() oraz saveOrUpdate().
self::$imageConverter = new ezcImageConverter(
Na końcu zapisujemy informa-
new ezcImageConverterSettings(
array(
cję o stworzeniu nowego albumu do lo-
new ezcImageHandlerSettings( 'imagemagick', 'ezcImageImagemagickHandler' gu. Sposób budowania mechanizmu
), logowania będzie opisany szczegółowo
new ezcImageHandlerSettings( 'gd', 'ezcImageGdHandler' ), w dalszej części artykułu; w tym miej-
) ));
scu pokażę tylko jak pobrać obiekt od-
powiedzialny za tę funkcjonalność i jak

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


eZ components Narzędzia

z niego korzystać. Metoda log() pobiera bowy ma tutaj ustawione prawa zapisu. pojedynczego pliku, dlatego wybieramy
jako pierwszy parametr wiadomość prze- W rzeczywistych zastosowaniach prak- filtr wyłapujący wszystkie wpisy. Ostatni
znaczoną do odnotowania. Drugi para- tyka taka jest zdecydowanie niezale- parametr przekazywany do konstrukto-
metr określa charakter wpisu (w prezen- cana, głownie ze względu na wymogi ra to flaga określająca, czy w przypadku
towanym przykładzie to jest charakter bezpieczeństwa: w logach mogą znajdo- dopasowania właściwej reguły, silnik ma
informacyjny). Jako ostatni parametr wać się cenne informacje. kontynuować poszukiwania. Flaga ta jest
przekazujemy dodatkowe opcje konfi- Kolejną cześć reguły stanowi kom- przydatna, jeśli planujemy logować do
gurujące wpis. W naszym przykładzie ponent LogFilter. Komponent ten odpo- wielu źródeł jednocześnie.
określamy dodatkową kategorię. Wyge- wiada za filtrowanie wpisów w pewien Na końcu dołączamy stworzoną
nerowany wpis wygląda następująco: określony sposób. Wiadomości mogą regułę do obiektu mapper i ustawiamy
być filtrowane według charakteru, źró- domyślne źródło dla obiektu logowania.
Apr 12 19:20:19 dła oraz kategorii wpisu. Ponieważ w na- Teraz Czytelnik może się łatwo domy-
[Info] [Gallery] [Create] szym przypadku będziemy logować do śleć dlaczego w przykładowym wpisie
New album created with ID <5>.
Listing 21. Dodawanie transformacji do konwertera obrazów
Jak widać, silnik logowania dodał auto-
self::$imageConverter->createTransformation(
matycznie znacznik czasowy, a także
'photo',
wypisał charakter i kategorię wiadomo-
ści. To, w jaki sposób we wpisie znala- array(
zła się fraza “[Gallery]”, niechaj chwilowo new ezcImageFilter(
pozostanie tajemnicą (obiecuję, że nie 'scale',
array(
na długo). Na samym końcu wpisu jest
'width' => 640, 'height' => 480,
umieszczona nasza wiadomość. 'direction' => ezcImageGeometryFilters::SCALE_DOWN,
Metody getTemplate() i getTemplate- ) ), ),
Vars() nie różnią się zbytnio od swoich array('image/jpeg',));
odpowiedników w klasie do obsługi ak-
cji “Listing”, więc pominiemy w tym miej- Listing 22. Transformacja odpowiedzialna za tworzenie miniaturek wczytywa-
nych obrazów
scu ich opis. Można jedynie nadmienić,
że szablon create_submit.php odpowia- self::$imageConverter->createTransformation(
da za wyświetlenie danych albumu, zaś 'thumb',
create_form.php zawiera definicję formu-
array(
larza służącego do pobierania danych od
new ezcImageFilter(
użytkownika. 'scale',
Spójrzmy teraz, w jaki sposób budo- array(
wany jest obiekt odpowiedzialny za logo- 'width' => 150, 'height' => 113,
wanie (Listing 17). 'direction' => ezcImageGeometryFilters::SCALE_DOWN,
)
Znowu mamy w tym miejscu do czy-
),
nienia z popularnym wzorcem projekto- new ezcImageFilter(
wym typu singelton. Jeśli żądana instan- 'border',
cja logu nie istnieje, to jest ona tworzo- array( width' => 5,
na. Na początku potrzebujemy instancji 'color' => array(255, 255, 255,), )
),
obiektu odpowiedzialnego za logowanie
new ezcImageFilter(
(warto zauważyć, że jest to singelton 'colorspace',
udostępniany przez komponent Log). array(
W dalszej kolejności pobieramy z uzy- 'space' => ezcImageColorspaceFilters::COLORSPACE_SEPIA,
skanej instancji tzw. mapper. Obiekt ten )
),
jest odpowiedzialny za przypisywanie
new ezcImageFilter(
różnych rodzajów logowanych wiado- 'border',
mości do różnych wynikowych plików. array(
Aby dodać do naszego obiektu lo- 'width' => 1,
gującego nową regułę, musimy określić 'color' => array(0, 0, 0,),
)
mechanizm zapisu. W naszym przypad-
),
ku wybraliśmy obiekt piszący do pliku ),
(w konwencji systemu Unix). Konstruk- array(
tor klasy tego obiektu pobiera na wej- 'image/jpeg',
ściu nazwę katalogu, w którym log ma )
);
być składowany oraz nazwę docelowego
pliku. W naszym przykładzie wykorzystu-
jemy katalog data/, jako że serwer we-

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


Narzędzia eZ components

pojawiła się fraza “[Gallery]”. W więk-


szych aplikacjach można ustawiać wiele Listing 23. Definicja metody ezcGallery::getConverter()
źródeł, jednak w naszym przypadku uży-
public static function getConverter()
jemy jednej domyślnej wartości. {
if ( !isset( self::$imageConverter ) )
Wczytywanie obrazów {
Jak dotąd mamy możliwość tworzenia self::$imageConverter = new ezcImageConverter(
new ezcImageConverterSettings(
nowych albumów, jednak to co napraw-
array(
dę nas interesuje, to funkcjonalność new ezcImageHandlerSettings( 'imagemagick', 'ezcImageImagemag
wczytywania obrazów. Operacja ta jest ickHandler' ),
zaimplementowana w ramach obsłu- new ezcImageHandlerSettings( 'gd', 'ezcImageGdHandler' ),
gi akcji „Add”, w pliku actions/add.php. )
)
Definicja klasy modelu obrazu jest prak-
);
tycznie taka sama jak w przypadku al-
bumu. Jedyną różnicę stanowi metoda self::$imageConverter->createTransformation(
create(), którą niedługo szczegółowo 'photo',
opiszemy. array(
new ezcImageFilter(
Rozważana akcja wymaga określe-
'scale',
nia konkretnego albumu, do którego bę- array(
dziemy wczytywać obraz. W zawiązku 'width' => 640, 'height' => 480,
z tym, w konstruktorze klasy umieścimy 'direction' => ezcImageGeometryFilters::SCALE_DOWN,
kod odpowiedzialny za wczytywanie al- )
),
bumu (Listing 18).
),
Ponownie wykorzystujemy kom- array(
ponent eZ UserInput w celu pobrania 'image/jpeg',
identyfikatora albumu wybranego przez )
użytkownika, jednak w odróżnieniu od );

pozostałych przykładów, tym razem


self::$imageConverter->createTransformation(
oczekujemy wartości liczbowej, a nie 'thumb',
napisu. Jeśli udało się pobrać identyfi- array(
kator, próbujemy pobrać z bazy danych new ezcImageFilter(
obiekt reprezentujący album. Warto za- 'scale',
array(
uważyć, że w tym miejscu komponent
'width' => 150, 'height' => 113,
ezcPersistentSession może rzucić wy- 'direction' => ezcImageGeometryFilters::SCALE_DOWN,
jątek ezcPersistentException, jeśli oka- )
że się, iż docelowy album nie istnieje. ),
W dalszej kolejności będziemy ob- new ezcImageFilter(
'border',
sługiwać wczytywany obraz. Mecha-
array(
nizm obsługi znajduje się w metodzie 'width' => 5, 'color' => array(255, 255, 255,),
run, w klasie reprezentującej rozważaną )
akcję. Kod podobny do źródła przedsta- ),
wionego na Listingu 19 opisywałem już new ezcImageFilter('colorspace',
array(
wcześniej, więc w tym miejscu podaru-
'space' => ezcImageColorspaceFilters::COLORSPACE_SEPIA,
jemy sobie jego dokładną analizę. )
Obsługa wczytywania plików nie ),
jest (jeszcze) obsługiwana z poziomu new ezcImageFilter(
komponentu UserInput. Z tej przyczyny 'border',
array(
musimy odczytać surowe dane z tabli-
'width' => 1, 'color' => array(0, 0, 0,),
cy FILES. Najpierw tworzymy instancję )
obiektu reprezentującego obraz i prze- ),
kazujemy do niego dane podstawowe ),
(tytuł i opis). Pozostałe wymagane ope- array(
'image/jpeg',
racje będą wykonane w ramach metody
)
create() , włącznie z zapisaniem obra- );
zu do bazy danych. }
Zanim zanurkujemy w kod źró- return self::$imageConverter;
dłowy metody create(), zróbmy ma- }

łą retrospekcję i powróćmy do defini-


cji głównego kontrolera, gdzie znaj-

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


eZ components Narzędzia

duje się między innymi operacja wyglądać jak stare, podniszczone zdję- Przeglądamy obrazy
getConverter() . Jako że metoda ta cia. Na samym końcu dodajemy jesz- Mamy już zdefiniowane albumy wypeł-
jest dość obszerna, przeanalizujemy cze jedną, czarną obwódkę o szeroko- nione wczytanymi zdjęciami. Przyjem-
ją w kliku krokach: ści jednego piksela – jako wykończenie nie byłoby mieć jeszcze możliwość ich
(patrz: Listing 23). Spójrzmy teraz, w ja- przejrzenia. Akcja powiązana z tym ele-
public static ki sposób nasz konwerter obrazów wy- mentem funkcjonalności jest zdefiniowa-
function getConverter() korzystany jest w praktyce (Listing 24). na w pliku show.php wewnątrz katalogu
{ W przedstawionej metodzie pobie- actions/. W konstruktorze klasy pobiera-
if ( !isset( ramy ścieżkę wczytanego obrazu oraz my identyfikator albumu, który chcemy
self::$imageConverter ) ) identyfikator powiązanego z nim albu- przeglądać i wyciągamy go z bazy da-
{ mu. Następnie przypisujemy identyfika- nych (Listing 26).
// ... tor albumu i definiujemy tablicę pomoc- Ponieważ zaprezentowany fragment
} niczych ścieżek. Potrzebne nam bę- kodu jest dla nas już bardzo znajomy,
return dą dwie: jedna dla wczytanego obrazu dlatego przejdziemy od razu do definicji
self::$imageConverter; i druga dla miniaturki. Wczytany plik jest metody run() (Listing 27).
} i tak przechowywany w tymczasowej W ramach wspomnianej metody
lokacji, więc możemy wykorzystać ist- realizujemy operację pobierania wszyst-
Implementacja wzorca singelton ma niejącą ścieżkę i wygenerujemy nową kich zdjęć dla konkretnego albumu.
identyczną postać jak w przypadku tylko dla miniaturki. Wewnątrz bloku try W tym niewielkim fragmencie kodu po-
metody getSession(). wykonujemy dwufazową transformację kazana jest kolejna ciekawa możliwość,
Fragment kodu przedstawiony na obrazów. oferowana przez komponent Database.
Listingu 20 jest odpowiedzialny za two- Na Listingu 25 pokazany jest kod Jak na razie widzieliśmy, w jaki sposób
rzenie nowego konwertera obrazów. źródłowy odpowiedzialny za zapisywanie można wykonać proste zapytanie przy
W trakcie tworzenia konwertera wyko- wczytanego obrazu w bazie danych. Po użyciu find. W tym miejscu możemy za-
rzystujemy specjalne obiekty do obsłu- pomyślnym wykonaniu tej operacji otrzy- obserwować, jak można zmodyfikować
gi zewnętrznych mechanizmów filtro- mujemy identyfikator. Dla uproszczenia to zapytanie przy pomocy dodatkowe-
wania obrazów, takich jak dedykowane przykładowego kodu wczytywane obra- go wyrażenia where. W tym celu two-
dla PHP rozszerzenie biblioteki GD lub zy są przechowywane pod nazwami od- rzymy obiekt reprezentujący zapytanie
program ImageMagick. W naszym przy- powiadającymi identyfikatorom w bazie i wywołujemy na nim metodę where, spe-
kładzie korzystamy z tego drugiego (z rozszerzeniem _thumb dla miniatu- cyfikując nazwę kolumny oraz poszuki-
rozwiązania. Jeśli z jakichś powodów rek). Ponieważ wszystkie wynikowe ob- waną wartość. Teraz możemy przeka-
wymagany program nie może być zain- razy otrzymywane po transformacji będą zać zapytanie do metody find wywołanej
stalowany, to wystarczy usunąć obiekt do zapisane w jednolitym formacie, dlatego w ramach sesji obsługi składowania.
jego obsługi i skorzystać z biblioteki GD. możemy bezpiecznie zaszyć rozszerze- Pozostała część definicji klasy do ob-
Ponieważ wiemy z góry, jaki filtr bę- nie plików w kodzie źródłowym. sługi akcji wygląda jak na poprzednich
dzie nam potrzebny, zdefiniujmy od razu
odpowiednią transformację (Listing 21). Listing 24. Wykorzystanie konwertera obrazów
Konwerter obrazów może być skon-
figurowany do obsługi tak zwanych
transformacji. Każda transformacja public function create[ $imagePath, $album )
jest identyfikowana przez nazwę i za- {
$this->album = $album;
wiera jeden lub więcej filtrów, oraz typ
$tmpData['photoPath'] = $imagePath;
MIME określający format wyjścia. $tmpData['thumbPath'] = tempnam( '', 'thumb' );
Nasza transformacja będzie zaapliko- try
wana dla wszystkich wczytanych ob- {
razów. W jej efekcie wszystkie obrazy ezcGallery::getConverter()->transform(
'photo',
o rozmiarach większych niż 640x480
$tmpData['photoPath'],
będą przeskalowane w dół. Dodatkowo $tmpData['photoPath'] );
wszystkie obrazy będą konwertowane ezcGallery::getConverter()->transform(
do formatu JPEG. Kolejna transforma- 'thumb',
cja będzie odpowiedzialna za tworze- $tmpData['photoPath'],
$tmpData['thumbPath'] );
nie miniaturek obrazów (Listing 22).
}
Przy generowaniu miniaturek, po- catch ( ezcImageTransformationException $e )
nownie skalujemy obrazy w dół, tym ra- {
zem do rozdzielczości 150x113 pikse- throw new Exception(
li. Następnie dodajemy białą obwódkę 'Image conversion error: <' . $e->getMessage() . '>'
);
i konwertujemy zawartość do przestrze-
} }
ni kolorów Sepia. Ta ostatnia transfor-
macja sprawi, że nasze obrazy będą

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


Narzędzia eZ components

przykładach. Szablony HTML są zdefi- tego typu zadnia zajmuje się kontroler cję obiektu reprezentującego obraz, ma-
niowane w taki sposób, że prezentują obsługujący akcję „Archive”. Konstruk- my tu wywołanie dwóch metod:
listę miniaturek z odnośnikami do peł- tor klasy wykonuje dokładnie takie same
nych wersji obrazów. operacje jak w przypadku obsługi akcji $tmpPath = $this->extractArchive(
„Add”. Metoda run też wygląda w tym $_FILES['archive']
Obsługa skompresowa- przypadku niemalże identycznie. Różni- ['tmp_name'] );

nego archiwum ca polega na tym, że przy wczytywaniu $this->addPhotos(


Wczytywanie pojedynczych plików do archiwum mamy możliwość zdefiniowa- $tmpPath, $form->title,
naszej galerii działa już całkiem nieźle. nia szablonu tytułu i opisu dla zawartych $form->description );
Jednak wczytywanie dużej liczby obra- wewnątrz obrazów. Szablon ten może
zów jest bardzo niewygodne. Dobrze zawierać znaczniki „%s”, które będą za- Metoda extractArchive() rozpakowu-
byłoby posiadać możliwość wczytania mienione na oryginalną nazwę obrazu je wczytane archiwum do roboczego ka-
całego archiwum obrazów do galerii, (z usuniętym rozszerzeniem). Spójrzmy talogu i zwraca jego ścieżkę. Metoda
gdzie byłyby one rozpakowane i doda- teraz na interesujący nas fragment me- addPhotos() dodaje poszczególne obrazy
wane do określonego albumu. Obsługą tody run. Zamiast tworzyć jedną instan- do galerii. Na Listingu 28 przedstawione
są pierwsze kroki przygotowujące do eks-
Listing 25. Zapisanie wczytanego obrazu w bazie danych trakcji plików z archiwum.
Na początku przeprowadzamy test
ezcGallery::getSession()->save( $this ); poprawności – sprawdzamy, czy prze-
if ( rename( $tmpData['photoPath'], 'data/' . $this->id . '.jpg' ) === false ) kazana ścieżka rzeczywiście odnosi
{
się do pliku. Dalej musimy posłużyć się
throw new Exception( 'Unable to store photo.' );
} małą sztuczką programistyczną w ce-
if ( rename( $tmpData['thumbPath'], 'data/' . $this->id . '_thumb' . '.jpg' ) === false ) lu stworzenia roboczego katalogu. Po-
{ nieważ język PHP nie posiada funkcji
throw new Exception( 'Unable to store thumbnail.' ); odpowiedzialnej za przeprowadzenie te-
}
go typu operacji w sposób bezpośred-
Listing 26. Pobieranie identyfikatora albumu i wyciągnie go z bazy danych ni, dlatego musimy skorzystać z funkcji
tempnam(), po czym skasować powstały
public function __construct() pomocniczy plik i na jego miejscu stwo-
{
rzyć katalog.
if ( ezcInputForm::hasGetData() )
{
Teraz możemy rozpakować archiwum
$definition = array( (Listing 29). Wywołanie EzcArchive::
'album' => new ezcInputFormDefinitionElement( open() zwraca instancję obiektu odpo-
ezcInputFormDefinitionElement::REQUIRED, 'int' wiedzialnego za obsługę wczytanego
),
archiwum (lub rzuca wyjątek jeśli typ ar-
);
$form = new ezcInputForm( INPUT_GET, $definition );
chiwum nie zastał poprawnie rozpozna-
if ( !$form->hasValidData( 'album' ) ) ny). Składniki archiwum możemy w pro-
{ sty sposób przejrzeć przy pomocy pętli
throw new Exception( 'No valid album ID.' ); foreach. Metoda extractCurrent() roz-
}
pakowuje aktualnie wybrany składnik
$this->album = ezcGallery::getSession()->load( 'Album', $form->album );
i zapisuje go w określonej lokacji. No,
} to było naprawdę proste! Czas obsłużyć
else rozpakowane obrazy (Listing 30).
{ Przedstawiona metoda przyjmuje na
throw new Exception( 'No album selected.' );
wejściu roboczą ścieżkę, w której znaj-
}
}
dują się rozpakowane obrazy, a także
szablon tytułu i opisu dla poszczegól-
Listing 27. Pobieranie wszystkich zdjęć z zadanego albumu nych zdjęć. Metoda zbudowana jest na
bazie dużej pętli while, która iteruje po
public function run()
kolejnych składnikach w docelowym ka-
{
$query = ezcGallery::getSession()->createFindQuery talogu. W trakcie iteracji pomijamy kata-
( 'Photo' ); logi “.” oraz “..”, a także w rekurencyjny
$query->where( sposób przeglądamy podkatalogi, jeśli
$query->expr->eq takowe istnieją.
( 'album', $this->album->id )
Jeśli napotkamy plik, to próbujemy
);
$this->photos = ezcGallery::getSession()->find wykonać na nim operację, którą oma-
( $query, 'Photo' ); wialiśmy w trakcie analizy kodu obsługi
} żądania wstawienia nowego obrazu do
galerii. Jedyna różnica polega na dołą-

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


eZ components Narzędzia

Listing 28. Przygotowanie do eks- Listing 30. Definicja metody addPhotos()


trakcji plików z wczytanego archi-
wum. private function addPhotos( $path, $title, $description )
{
if ( !is_uploaded_file( $file ) ) $d = dir( $path );
{ while ( ($entry = $d->read() ) !== false )
throw new Exception( {
'Illegal file upload.' ); if ( $entry == '.' || $entry == '..' )
} {
continue;
// Create a temp dir }
$path = tempnam( 'data/', $fullPath = $path . DIRECTORY_SEPARATOR . $entry;
'upload' ); if ( is_dir( $fullPath ) )
unlink( $path ); {
mkdir( $path ); $this->addPhotos( $fullPath, $title, $description );
}
Listing 29. Ekstrakcja obrazów
z archiwum else
{
try $name = substr( basename( $fullPath ), 0, strrpos( basename( $fullPath
{ ), '.' ) );
$archive = ezcArchive::open( $file try
); {
} $photo = new Photo();
catch ( ezcArchiveException $e ) $photo->title = sprintf( $title, $name );
{ $photo->description = sprintf( $description, $name );
throw new Exception( $photo->create( $fullPath, $this->album->id );
'Archive invalid.' ); $this->photos[] = clone( $photo );
} ezcGallery::getLog()->log(
foreach ( $archive as $entry ) "Added new photo with ID <{$photo->id}> from archive.",
{ ezcLog::INFO,
$archive-> array( 'category' => 'Upload' )
extractCurrent( $path ); );
} }
return $path; catch ( Exception $e )
{
$this->errors[] = $e;
// Cleanup
czeniu obsługi szablonu nazwy i opisu
@unlik( $fullPath );
dla poszczególnych zdjęć. Cała resz- }
ta kodu wygląda identycznie. W bloku }
catch składowane są wyjątki, aby na }
końcu przedstawić użytkownikowi kom- rmdir( $path );
}
pleksowy raport opisujący potencjalne
błędy. W tym samym miejscu kasujemy
również niepoprawnie obsłużony plik Podsumowanie dziane. Jeśli mielibyście ochotę w ja-
(musimy to zrobić, gdyż ze względu na Udało nam się zbudować w pełni funk- kiś sposób przyczynić się do rozwoju
powstanie sytuacji wyjątkowej funkcja cjonalną galerię obrazów w postaci tego projektu – to nic nie stoi na prze-
Photo::create() prawdopodobnie nie aplikacji webowej – i co ważne – do- szkodzie. Wystarczy napisać pod adres
obsłużyła tego pliku w poprawny spo- konaliśmy tego przy użyciu relatywnie components@lists.ez.no. n
sób). Na wszelki wypadek wyciszyłem niewielkiej ilości kodu (w porównaniu
wywołanie funkcji unlink(); nie polecam do podobnych rozwiązań, w których
stosowania tej techniki w aplikacjach kla- wszystko jest pisane od podstaw). Co
sy produkcyjnej, jednak w naszym przy- więcej, zbudowany kod cechuje się bar- O autorze
padku jest to dobry (i tani) sposób na dzo dobrą organizacją i spójną struk-
Tobias Schlitt pracuje dla eZ systems
unikniecie brzydkich komunikatów o błę- turą. Mam nadzieję, że przedstawiony jako główny programista. Do jego pod-
dach. Ostatnie wywołanie w metodzie tekst przekonał czytelników do korzy- stawowych obowiązków należy rozwi-
kasuje roboczy katalog. stania z komponentów eZ i rozjaśnił nie- janie projektu eZ components. Tobias
Szablon wykorzystany do prezen- co koncepcje na których te komponenty studiuje też informatykę na uniwersyte-
cie w Dortmund, mając za sobą praktyki
tacji wyników działania opisywanej zostały zbudowane. w Deutsche Bank, na stanowisku spe-
akcji różni się trochę od pozostałych Na koniec, gorąco zachęcam czy- cjalisty IT. Tobias jest znanym ekspertem
przykładów, jednak nadal nie wno- telników do dalszych eksperymentów w środowisku prgramistów PHP, ku cze-
si żadnego istotnego elementu nowo- z tą technologią. Zainteresowanych mu przyczyniły się jego prace nad pro-
jektem PEAR (w którym nadal zresztą
ści – dlatego nie będę go w tym miej- zapraszam na nasze forum dyskusyj- uczestniczy).
scu opisywał. ne – wszelkie komentarze są mile wi-

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


Techniki

Dekorator: wzorzec projektowy


na każdą bolączkę
Stopień trudności: lll
Paweł Kozłowski

Nazwa wzorca projektowego dekorator jest


nieco myląca, ponieważ sugeruje, że będziemy
coś wzbogacać, dekorować czy upiększać. Nic
bardziej błędnego! Omawiany wzorzec znajduje
szerokie zastosowanie, niezależnie od tego, czy
projektujemy warstwę dostępu do bazy danych,
logikę biznesową lub kontroler MVC.

J
ednym z najczęściej omawianych duje szerokie zastosowanie, niezależnie
i używanych wzorców projektowych od tego, czy projektujemy warstwę do-
jest dekorator. Z punktu widzenia stępu do bazy danych, logikę bizneso-
programisty ma same zalety: jest bardzo wą lub kontroler MVC. Jego istotą jest
użyteczny, nieskomplikowany w imple- możliwość wzbogacenia funkcjonalności
mentacji i łatwy do przyswojenia. Obok obiektów danych klas w sposób dyna-
singletona jest to prawdopodobnie jeden miczny, bez konieczności modyfikowania
z pierwszych wzorców, z którymi stykamy oryginalnych obiektów.
się, gdy zaczynamy studiować przykła- Definicje jak zwykle brzmią zbyt su-
dy i literaturę poświęconą zasadom pro- cho, spójrzmy więc na Listing 1, gdzie
jektowania. O ile jednak singleton jest ob-
winiany (często słusznie!) o promowanie
Co należy wiedzieć...
fatalnych rozwiązań, to dokładne pozna- Przydatna będzie znajomość dwóch po-
W SIECI nie dekoratora przyczyniło się do powsta- przednich artykułów z naszej serii Wzor-
nia wielu ciekawych rozwiązań architekto- ce projektowe. Czytelnik powinien mieć
nicznych. wiedzę z obiektowych technik projekto-
• http://flexi.sourceforge.net/
wania aplikacji.
Nazwa wzorca projektowego „deko-
– budowany przez nas fra-
mework rator” jest nieco myląca, ponieważ su- Co obiecujemy...
Z artykułu dowiesz się, kiedy i dlaczego
• http://www.picocontainer.org/ geruje, że będziemy coś wzbogacać, de- warto stosować wzorzec projektowy de-
Ports – Pico
• http://www.martinfowler.com/ korować czy upiększać. Stąd może po- korator. Na przykładach pokażemy, jak
articles/injection.html – cie- wstać błędne przeświadczenie, że ma- stosując dekorator, bardzo łatwo i w ele-
kawe artykuły
my do czynienia z tworem przydatnym gancki sposób dodać nową funkcjonal-
• http://www.phppatterns.com/ ność do aplikacji bez zbędnej modyfika-
– ciekawe artykuły jedynie w warstwie prezentacji. Nic bar- cji kodu.
dziej błędnego! Omawiany wzorzec znaj-

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


Wzorce projektowe: dekorator Techniki

znajduje się elementarny przykład („jest”, ang. IS-A), jak przy dziedziczeniu. du i doprowadziła do powstania nowej
użycia dekoratora. W przedstawio- Istotnie, przykład z Listingu 2 można by funkcjonalności. Niestety, w przypadku
nym przypadku użyliśmy dekoratora do zapisać tylko i wyłącznie przy pomocy dziedziczenia mamy dużo trudniejsze za-
wzbogacenia istniejącej funkcjonalno- tworzenia nowych podklas (Listing 3). danie: nie obędzie się bez dosyć istotne-
ści. Nie musimy jednak zbyt ściśle su- Jaki jest więc sens wprowadzania zu- go przemeblowania napisanego kodu.
gerować się nazwą wzorca projektowe- pełnie nowej konstrukcji programistycz- Powodem jest fakt, iż formując hie-
go i spokojnie możemy pozwolić sobie nej i opisywania jej jako wzorca projekto- rarchię dziedziczenia tworzymy dość
na całkowitą zmianę funkcjonalności wego? Przecież to samo można uzyskać sztywne, statyczne powiązanie pomię-
dekorowanego obiektu. przy użyciu podstawowych konstrukcji dzy poszczególnymi klasami.
Dwa proste przykłady, przedstawio- programowania obiektowego. Jest jed- W przypadku dekoratorów nie ma-
ne na wspomnianych listingach, obrazują na, dosyć istotna różnica pomiędzy dwo- my takiego ograniczenia: możemy do-
praktycznie wszystkie istotne cechy wzor- ma wspomnianymi podejściami. Aby ją wolnie przestawiać kolejność, dodawać
ca, z którym się zaznajamiamy. Szczegól- łatwo pokazać, przemodelujmy przykład nowe elementy do łańcucha i usuwać już
ne istotny do odnotowania jest jeden fakt: z Listingu 2, zmieniając kolejność zasto- istniejące. Takie zamiany są możliwe nie
dla klienta korzystającego z udekorowa- sowanych dekoratorów (Listing 4). Pro- tylko w momencie ustalania struktury ko-
nego (często mówimy również – „opako- szę bardzo: cała operacja była wyjąt- du, ale w dowolnym momencie, nawet
wanego”) obiektu obecność dekoratora kowo prosta, odbyła się bez koniecz- w czasie wykonywania skryptu. Dzięki
jest zupełnie przezroczysta! Klient nie jest ności modyfikowania istniejącego ko- dekoratorom można uzyskać wszystkie
w stanie stwierdzić, czy korzysta z usług
oryginalnego obiektu, czy też z jego ude- Listing 1. Prosty przykład użycia dekoratora
korowanej wersji. Jest to możliwe dzię-
ki zachowaniu przez dekorator interfejsu <?php
oryginalnego obiektu. Jeśli chcielibyśmy /**
*interfejs, ktory może być implementowany przez wiele klas
zapamiętać jakąś jedną zasadę czy zda-
*ponieważ dekoratory również implementują ten interfejs
nie opisujące dekoratory, mogłoby to być: *klient korzystający z udekorawanej klasy nie jest w stanie
Dokorator zachowuje interfejs oryginalne- *rozróżnić, czy ma do czynienia z pierwotną, czy udekorowaną wersją
go obiektu i najczęściej przyjmuje dekoro- */
wany obiekt jako parametr w swoim kon- interface UserDAO {
public function save(User $user);
struktorze.
public function findByLoginNamePassword($loginName, $password);
Kolejną bardzo miłą cechą dekoratora }
jest możliwość użycia więcej niż jednego class UserDAOImpl implements UserDAO {
„opakowania” dla podstawowego obiek- public function save(User $user) {
tu. Oznacza to, iż możemy składać no- // standardowa funkcjonalność zapisywania
// danych użytkowników do DB
wą funkcjonalność z wielu już istniejących
}
elementów, bez konieczności ingerencji public function findByLoginNamePassword($loginName, $password){
w raz stworzony kod. Jest to dokładnie ta // standardowa funkcjonalność odnajdywania
sama filozofia, jaką spotykamy przy pracy // danych użytkowników po nazwie
z powłoką w systemach uniksowych. Do- }
}
skonale wiemy, jak wiele pożytecznych
class UserDAOUpperCaseLoginDecorator implements UserDAO {
operacji można wykonać, zaprzęgając do private $_decoratedDao;
wspólnej pracy elementarne narzędzia. To public function __construct(UserDAO $decoratedDao){
samo odnosi się do dekoratorów: sprytnie $this->_decoratedDao = $decoratedDao;
łącząc wiele prostych dodatków możemy }
public function save(User $user) {
uzyskać funkcjonalność, o której nie śniło
//zadaniem dekoratora jest zapisywanie loginów
się nawet twórcom piszącym klasy czy też //tylko wielkimi literami
dekoratory (Listing 2). $user->setLoginName(strtoupper($user->getLoginName()));
Zanim przejdziemy do omówie- //wywołanie oryginalnej funkcjonalności
nia bardziej skomplikowanych przykła- $this->_decoratedDao->save($user);
}
dów, zatrzymajmy się jeszcze na chwilę
public function findByLoginNamePassword($loginName, $password) {
nad własnością dekoratorów, dzięki któ- //implementacja wyszukiwania pozostaje bez zmian
rej klient nie jest w stanie rozróżnić, czy return $this->_decoratedDao->findByLoginNamePassword(
ma do czynienia z obiektem podstawo- $loginName, $password);
wym, czy też z opakowaną jego wersją. }
}
Uważny Czytelnik zauważy, że ta nie-
//konstruowanie udekorowanej wersji obiektu
rozróżnialność jest możliwa, ponieważ $dao = new UserDAOUpperCaseLoginDecorator(new UserDAOImpl());
pomiędzy dekoratorem i obiektem de- ?>
korowanym zachodzi taka sama relacja

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


Techniki Wzorce projektowe: dekorator

dobrodziejstwa dziedziczenia, ale w spo- wersjach tej samej, podstawowej apli- koratorów do dodania obsługi uprawnień
sób bardziej elastyczny. kacji. Ta mnogość i wygoda zastosowań na poziomie warstwy kontrolerów MVC.
Nie oznacza to oczywiście, iż nama- powoduje, że w złożonej aplikacji może- Listing 6 pokazuje dwa przykładowe kon-
wiamy Was, by od dzisiaj nie używać dzie- my mieć wiele dekoratorów dla jednego trolery oraz opakowanie z dekoratorów
dziczenia i pisać tylko dekoratory. Jak obiektu. Pisanie takiej ilości dekoratorów sprawdzających uprawnienia. Przedsta-
każdy wzorzec projektowy, dekorator nie i zarządzanie ich konfiguracją stanowi wy- wiony schemat dodawania obsługi upraw-
powinien być stosowany tylko w celu zwanie samo w sobie. W dalszej części nień do aplikacji jest niezwykle efektyw-
otrzymania bardziej elastycznej architek- artykułu zastanowimy się więc, jak szybko ny. Po pierwsze, pozwala uruchamiać tą
tury, ale w przypadkach, w których będzie pisać i konfigurować dekoratory. samą aplikację ze sprawdzaniem upraw-
naprawdę pomocny. Uzbrojeni w solidne Zatrzymajmy się na dłużej nad przy- nień i bez, w zależności od wymagań
podstawy teoretyczne możemy przyjrzeć wołanym przykładem zastosowania de- klienta. Z drugiej strony, taka architektura
się wreszcie przykładom pokazującym, jak
w łatwy i efektywny sposób rozbudować Listing 2. Łączymy dekoratory
nasze aplikacje bez konieczności modyfi-
kacji ich podstawowego kodu. <?php
Wróćmy zatem do przykładów. Jed- class UserDAOPasswordHashingDecorator implements UserDAO {
private $_decoratedDao;
ną z dobrych praktyk architektonicznych
public function __construct(UserDAO $decoratedDao){
jest dzielenie kodu aplikacji na warstwy. $this->_decoratedDao = $decoratedDao;
W typowej aplikacji WWW, najniższą }
warstwę stanowi baza danych (najczę- public function save(User $user) {
ściej w postaci relacyjnej bazy danych //zadaniem dekoratora jest zapisywanie loginów
//tylko wielkimi literami
lub zwykłych plików). Powyżej umiesz-
$user->setPassword(md5($user->getPassword()));
czamy wartwę dostępu do danych w po- //wywołanie oryginalnej funkcjonalności
staci obiektów DAO (ang. Data Access $this->_decoratedDao->save($user);
Objects). Już ta jedna wspomniana war- }
stwa jest okazją do zaimplementowaniu public function findByLoginNamePassword($loginName, $password) {
//przy wyszukiwaniu również szyfrujemy hasła
wielu funkcjonalności w postaci deko-
return $this->_decoratedDao->findByLoginNamePassword(
ratorów. Wyobraźmy sobie, że chcemy $loginName, md5($password));
zapisywać czas wyszukiwania danych }
w DB. Nic prostszego: zamiast mody- }
fikować istniejący kod, otoczmy go do- //konstruowanie wielokrotnie udekorowanej wersji obiektu
$dao = new UserDAOUpperCaseLoginDecorator(new UserDAOPasswordHashingDecorator(
datkową klasą (Listing 5). A może mamy
new UserDAOImpl()));
DAO służące do zapisu danych użyt- ?>
kowników i w pewnych przypadkach
chcemy szyfrować hasła. Proszę bar- Listing 3. Przykład z Listingu 2 zapisany przy pomocy nowych podklas
dzo: zamiast utrzymywać dwie zbliżone
<?php
wersje tego samego DAO, dużo efek- class UserDAOUpperCaseLogin extends UserDAOImpl {
tywniej rozwiążemy problem stosując public function save(User $user) {
prosty dekorator – bez modyfikowania $user->setLoginName(strtoupper($user->getLoginName()));
i duplikowania istniejącego kodu. parent::save($user);
}
Przykłady można mnożyć również
}
w innych warstwach sytemu. Jeśli pomy- class UserDAOPasswordHashing extends UserDAOUpperCaseLogin {
ślimy o dowolnej logice biznesowej zapi- public function save(User $user) {
sującej dane, to dekoratory znajdą zasto- $user->setPassword(md5($user->getPassword()));
sowanie przy wersjonowaniu danych, za- parent::save($user);
}
pisywaniu logów zmian, kontroli dostępu
public function findByLoginNamePassword($loginName, $password) {
itd. Przesuwając się jeszcze jedną war- return parent::findByLoginNamePassword($loginName, md5($password));
stwę wyżej, do kontrolerów MVC, szybko }
odkryjemy kolejne zastosowania: śledze- }
nie zachowań użytkowników (np. ścież- //konstruowanie wersji obiektu z dziedziczeniem
$dao = new UserDAOPasswordHashing();
ki poruszania się po serwisie WWW) czy
?>
sprawdzanie uprawnień. Zastosowania
i przykłady można by mnożyć w nieskoń- Listing 4. Zmieniona kolejność zastosowanych dekoratorów w stosunku do
czoność. Poprzestańmy więc na stwier- Listingu 2. Tworzymy nową funkcjonalność bez modyfikacji istniejącego kodu.
dzeniu, że wzorzec dekoratora jest nie-
<?php
zwykle użyteczny i podpowiada eleganc-
$dao = new UserDAOPasswordHashingDecorator(new UserDAOUpperCaseLoginDecorator(
kie rozwiązania dla często spotykanych new UserDAOImpl()));
problemów związanych z dodawaniem ?>
specyficznej funkcjonalności w różnych

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


Techniki Wzorce projektowe: dekorator

Aby obejść zidentyfikowany problem,


�������������������
posłużymy się prostą koncepcyjnie me-
todą – generowaniem kodu w locie.
Spójrzmy na Listing 8, gdzie pokazujemy
���������������� użycie specjalnie przygotowanej biblio-
teki (PHPProxy, dostępnej na sourcefor-
ge.net) do generowania kodu. Jak widać,
samo jej użycie jest niezwykle proste
����������� – sprowadza się do zaimplementowania
tylko tej logiki, która ma się znaleźć w de-
koratorze – nie musimy powielać ani jed-
nego znaku kodu. Tak prosta implemen-
Rysunek 1. Uproszczony graf obiektów w naszej aplikacji tacja jest możliwa dzięki istnieniu inter-
umożliwia niezależne pisania kodu biz- kretną metodą, a wprzypadku pozosta- fejsu ProxyInvocationHandler. W inter-
nesowego i odpowiedzialnego za bez- łych metod polegamy na __call. Oma- fejsie tym zdefiniowana jest tylko jedna
pieczeństwo. Wreszcie, możemy sobie wiana właśnie metoda ma w zasadzie tyl- metoda – invoke($method, $args). Nie
wyobrazić zastosowanie różnych silni- ko jedną wadę – w ten sposób wygene- trzeba być potomkiem Sherlocka Holme-
ków do sprawdzania uprawnień (np. wła- rowane dekoratory nie mogą być użyte w sa by wydedukować, że metoda ta jest
sny lub GACL). Niestety, nasze wszystkie wywołaniach funkcji i metod, dla których uruchamiana w momencie wywoływania
zachwyty trochę przybledną, jeśli uświa- określono typ argumentu (a więc dla kon- metod na dekoratorze, a w argumetach
domimy sobie, że dla każdego kontrole- strukcji $obiekt->nazwaMetody(TypArgume otrzymujemy wszystkie niezbędne infor-
ra trzeba stworzyć osobny dekorator. Nie ntu $agrument);). Jest to dość poważna macje o wywołaniu – tj. nazwę metody
jest to może wielkim problemem przy pię- wada, ponieważ na początku podkreśla- i jej argumenty. Korzystając z implemen-
ciu czy dziesięciu kontrolerach, ale sta- liśmy, iż jedną z cech dekoratora jest za- tacji interfejsu ProxyInvocationHandler
je się koszmarem przy dużych aplika- chowanie interfejsu oryginalnego obiek- z opisywanej biblioteki (Listing 9), może-
cjach, gdzie często spotkamy setki klas ty- tu. Zastosowanie __call do generowania my łatwo zdecydować, czy chcemy tyl-
pu kontroler. Nasze miny staną się jesz- dekoratorów wyklucza silniejszą kontrolę ko udekorować oryginalną funkcjonal-
cze bardziej nietęgie, jeśli uświadomimy typów, a tym samym – interfejsów imple- ność (a więc wywołać ją podczas uru-
sobie, że każdy z dekoratorów będzie do mentowanych przez podstawową i udeko- chamiania dekoratora), czy też stwo-
siebie bardzo podobny. Ponowny rzut oka rowaną klasę. rzyć zupełnie nowy kod: cały sekret tkwi
na Listing 6 faktycznie ujawnia, że w kolej-
nych dekoratorach różnią się tylko nazwy Listing 5. Dodajemy nową funkcjonalność do zapisywania czasu wyszukiwania
klas i metod – zasadnicza logika pozosta- danych w DB
je praktycznie bez zmian.
Bezmyślne pisanie niemal iden- <?php
class PerformanceLoggingUserDAO implements UserDAO {
tycznych dekoratorów nie jest zajęciem
private $_decoratedDao;
szczególnie ciekawym czy produktyw- private $_startTime;
nym, a dodatkowo – to oczywista dupli- public function __construct(UserDAO $decoratedDao){
kacja kodu. Musimy więc znaleźć jakiś $this->_decoratedDao = $decoratedDao;
sposób na zautomatyzowanie żmudne- }
public function save(User $user) {
go zajęcia. PHP5, jako język niezwykle
$this->startMethodExecution();
dynamiczny daje nam szerokie możli- $this->_decoratedDao->save($user);
wości generowania szablonowego kodu $this->stopMethodExecution('save');
w locie. Spośród wielu opcji dwie wydają }
się najciekawsze: użycie magicznej me- public function findByLoginNamePassword($loginName, $password) {
$this->startMethodExecution();
tody __call lub generowanie całego ko-
return $this->_decoratedDao->findByLoginNamePassword(
du dekoratora w czasie działania skryp- $loginName, $password);
tu. Obie metody mają swoje wady i za- $this->stopMethodExecution('findByLoginNamePassword');
lety, które za chwilę omówimy, ale naj- }
pierw spójrzmy na Listing 7, gdzie znaj- private function startMethodExecution(){
$this->_startTime = microtime();
dują się przykładowe dekoratory (z Li-
}
stingu 6), zaimplementowane przy uży- private function stopMethodExecution($methodName){
ciu obu wspomnianych metod. $executionTime = microtime() - $this->_startTime;
Dekorator stworzony przy pomocy echo "Wykonanie metody '$methodName' zajęło $executionTime <br>";
__call jest dosyć prosty i pozwala pre- }
}
zycyjnie regulować, które metody w de-
//konstruowanie udekorowanej wersji obiektu
koratorze są generowane automatyczne, $dao = new PerformanceLoggingUserDAO(new UserDAOImpl());
a które ręcznie. Po prostu dla tych, któ- ?>
re chcemy napisać sami, tworzymy kon-

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


Wzorce projektowe: dekorator Techniki

w metodzie getDelegate(), dzieki której


możemy pobrać dekorowany obiekt i wy- �������������������
wołać na nim dowolną metodę.
Użycie biblioteki PHPProxy pozwala
generować dekoratory przy minimalnym ���
�����������
nakładzie pracy ze strony programisty ����������������

i przy eliminacji powtórzeń w kodzie.


Niestety, nie jest to metoda pozbawiona
���
wad. Po pierwsze, generowanie kodu ��������������� �����������
w czasie działania programu może być
zbyt czasochłonne w przypadku mocno
obciążonych serwisów internetowych, ���
gdzie wydajność jest rzeczą krytyczną. ��������������������

Drugi, trochę mniej uciążliwy pro-


blem, związany jest z przypadłością Rysunek 2. Dodając dekoratory, wprowadzamy do omawianego grafu kolejny stopień
nękająca wszystkie rozwiązania oparte skomplikowania, opakowując wybrane obiekty aplikacji
o generowanie kodu. Otóż podczas wy-
konania programu uruchamiany jest Listing 6a. Dwa przykładowe kontrolery oraz opakowanie z dekoratorów
również kod, który... nie jest nigdzie sprawdzających uprawnienia
zapisany na stałe. Oznacza to, że
w pewnych warunkach śledzenie wyko- <?php
class useraction {
nania programu (np. podczas wyszuki-
private $_userService;
wania błędów) może być nieco utrud- public function __construct(UserService $userService) {
nione. Tak czy inaczej, mimo przedsta- $this->_userService = $userService;
wionych wad, jest to zdecydowanie naj- }
łatwiejsza metoda tworzenia wielu deko- public function listall(HttpRequest $request, ModelAndView $mv){
$users = $this->_userService->findAll();
ratorów.
$mv->addToModel('users',$users);
Podsumujmy naszą dotychczaso- $mv->setView('userslist');
wą wiedzę o dekoratorach. Na wstę- return $mv;
pie stwierdziliśmy, że jest to bardzo po- }
żyteczny wzorzec, który może być sto- public function listwithexpensivequery(
HttpRequest $request, ModelAndView $mv){
sowany praktycznie w każdej warstwie
$users = $this->_userService->findUserByExpensiveQuery();
systemu. Jest to alternatywa do dziedzi- $mv->addToModel('users',$users);
czenia obiektów, umożliwiająca wzbo- $mv->setView('userslist');
gacanie lub zmianę istniejącej funkcjo- return $mv;
nalności, bez konieczności modyfikacji }
public function addform(HttpRequest $request, ModelAndView $mv){
raz napisanego kodu. Dekorator pada
$mv->setView('useraddform');
jednak trochę ofiarą własnego sukcesu return $mv;
– mnogość jego zastosowań powodu- }
je, że w bardziej rozbudowanej aplikacji public function add(HttpRequest $request, ModelAndView $mv){
możemy wykorzystać setki obiektów im- $user = new User(
$request->getParam('login'),
plementujących wzorzec dekoratora.
$request->getParam('pass'),
Aby nie zginąć w tym gąszczu po- $request->getParam('firstname'),
dobnych obiektów, znaleźliśmy dwie $request->getParam('lastname'));
metody na dynamiczne generowanie $mv->addToModel('user',$user);
dekoratorów. Niestety, to nie koniec pro- try {
$this->_userService->addUser($user);
blemów, które musimy rozwiązać, aby
$mv->setView('useraddconfirm');
efektywnie wykorzystać dekoratory ja- } catch (UserExistsException $e) {
ko integralną część rozwiązania archi- $mv->addToModel('adduser_error','User exists!');
tektonicznego. $mv->setView('useraddform');
Załóżmy, że Rysunek 1 przedsta- }
return $mv;
wia uproszczony graf obiektów w naszej
}
aplikacji. Obiekty te są oczywiście uło- }
żone w warstwy, natomiast obiekty są class homepage {
połączone między sobą siecią zależ- public function show(HttpRequest $request, ModelAndView $mv){
ności. Już samo poprawne skonstru- $mv->setView('homepage');
return $mv;
owanie takiej sieci obiektów może być
}
nie lada wyzwaniem, o czym Czytelnik }
mógł się przekonać, śledząc mój artykuł

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


Techniki Wzorce projektowe: dekorator

Obiektowa linia montażowa, czyli przej-


�����������������������������������������������
rzyste i elastyczne aplikacje w PHP5,
z numeru 1/2006. Dodając dekoratory,
�������������� wprowadzamy do omawianego grafu ko-
lejny stopień skomplikowania, opakowu-
jąc wybrane obiekty aplikacji (Rysunek 2).
����������������������� �����������������������
����������������������� ����������������������� Warto przy tym zauważyć, że udekorowa-
niu będą podlegały głównie obiekty infra-
�������������
strukturalne (kontrolery, DAO itd.), a nie
domenowe. Musimy więc znaleźć jakiś ła-
Rysunek 3. W Pico zawarta jest cała konfiguracja związana z połączeniami pomiędzy
twy sposób na dodanie wielu dekoratorów
obiektami. Aby wprowadzić dodatkowy obiekt pośredniczący, musimy jedynie
przekonfigurować połączenia. w całej aplikacji, bez konieczności ręczne-
go przebudowywania połączeń.
Przy składaniu skomplikowanych
Listing 6b. Dwa przykładowe kontrolery oraz opakowanie z dekoratorów grafów obiektów doskonale sprawdza
sprawdzających
się wzorzec architektoniczny IoC (ang.
// Klasa pomocnicza dla dekoratorów, gdzie odbywa się Inversion of Control), o którym również
// właściwe sprawdzanie uprawnień pisaliśmy w wyżej wspomnianym artyku-
abstract class AbstractActionSecurityDecoratorImpl { le. Mamy szczęście, ponieważ ten sam
private $_decoratedAction;
wzorzec również znakomicie ułatwia do-
public function __construct($decoratedAction) {
$this->_decoratedAction = $decoratedAction; dawanie dekoratorów. Dlaczego? Przy-
} pomnijmy, że w przypadku stosowania
public function executeTargetActionWithSecurityCheck( wzorca IoC bardzo pomocne są bibliote-
$targetAction, HttpRequest $request, ModelAndView $mv){ ki typu kontener IoC. Te lekkie kontene-
ry biorą na siebie cały ciężar tworzenia
//tutaj tylko proste sprawdzanie, czy zalogowany,
//ale równie łatwo zintegrować np. GACL skomplikowanych grafów obiektów, dba-
session_start(); jąc przy tym o właściwe rozwiązanie za-
if ($_SESSION['user'] == null) { leżności pomiędzy obiektami. Kontener
$mv->setView('loginform'); IoC jest więc jednym, centralnym miej-
return $mv;
scem, gdzie powoływane są do życia
} else {
return $this->_decoratedAction->$targetAction($request, $mv); instancje obiektów infrastrukturalnych.
} Doskonale, o to nam właśnie chodziło.
} To scentralizowane miejsce tworzenia
} obiektów pozwala nam łatwo dodać na-
//Mimo, iż obie akcje są zupełnie inne,
sze „opakowania”. Spójrzmy na Listing
//to dekoratory są niemal identyczne!
//Oczywista duplikacja kodu! 10, gdzie znajdziemy przykład wykorzy-
class UserActionSecurityDecoratorImpl stania Pico dla PHP – lekkiego konte-
extends AbstractActionSecurityDecoratorImpl { nera IoC – do dodania dekoratorów do
public function listall(HttpRequest $request, ModelAndView $mv){ obiektów aplikacji. Jak widać, cała ope-
return $this->executeTargetActionWithSecurityCheck(
racja jest stosunkowo prosta – wystar-
'listall', $request, $mv);
} czą dwie linijki kodu.
public function listwithexpensivequery( Przeanalizujmy dokładniej przykład
HttpRequest $request, ModelAndView $mv){ z Listingu 10. Widzimy na nim początko-
return $this->executeTargetActionWithSecurityCheck( wo dwa współpracujące ze sobą obiek-
'listwithexpensivequery', $request, $mv);
ty: serwisowy i DAO. Są to obiekty infra-
}
public function addform(HttpRequest $request, ModelAndView $mv){ strukturalne, podczas dzialania aplikacji
return $this->executeTargetActionWithSecurityCheck( potrzebny jest zwykle tylko jeden egzem-
'addform', $request, $mv); plarz takiego obiektu. W tym przypadku
} to Pico dba o powołanie do życia instan-
public function add(HttpRequest $request, ModelAndView $mv){
cji obiektów oraz ich połączenie. Aby za-
return $this->executeTargetActionWithSecurityCheck('add', $request, $mv);
} stosować dekorator zliczający czas wy-
} konania poszczególnych metod, musimy
class HomepageActionSecurityDecoratorImpl w jakiś sposób rozerwać ścisłe połącze-
extends AbstractActionSecurityDecoratorImpl { nie pomiędzy obiektem DAO i obiektem
public function show(HttpRequest $request, ModelAndView $mv){
z DAO korzystającym. Przy klasycznym
return $this->executeTargetActionWithSecurityCheck('show', $request, $mv);
} podejściu do budowy programów, gdyby-
} śmy użyli operatora new, metod statycz-
?> nych lub fabryk, mielibyśmy małe szan-
se na wprowadzenie trzeciego obiektu

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


Wzorce projektowe: dekorator Techniki

w łańcuch powiązań. W przypadku kon-


Listing 7. Dekorator (z Listingu 6), zaimplementowany przy użyciu __call oraz tenera IoC sprawa jest o wiele prostsza,
generowanie całego kodu dekoratora w czasie działania skryptu ponieważ w Pico zawarta jest cała konfi-
<?php guracja związana z połączeniami pomię-
// implementacja dekoratora z wykorzystaniem metody __call dzy obiektami. Jedyne, co musimy zrobić,
class UserActionSecurityDecoratorCallImpl to przekonfigurować te połączenia w taki
extends AbstractActionSecurityDecoratorImpl { sposób, by wprowadzić dodatkowy obiekt
public function __call ($methodName, $args ) {
pośredniczący (Rysunek 3). W Pico robi-
$request = $args[0];
$mv = $args[1]; my to przy pomocy parametrów kompo-
return $this->executeTargetActionWithSecurityCheck( nentu, tak jak pokazaliśmy w drugiej części
$methodName, $request, $mv); }} Listingu 10. Oczywiście pokazany sposób
class HomepageActionSecurityDecoratorCallImpl można wykorzystać do użycia więcej niż
extends AbstractActionSecurityDecoratorImpl {
jednego dekoratora (Listing 11).
public function __call ($methodName, $args ) {
$request = $args[0]; Kontenery IoC w znaczący sposób
$mv = $args[1]; ułatwiają stosowanie dekoratorów w zło-
return $this->executeTargetActionWithSecurityCheck( żonych aplikacjach. Dzieki nim możemy
$methodName, $request, $mv); }} w jednym, centralnym miejscu, skonfi-
?>
gurować połączenia między obiektami
Listing 8. Działanie PHPProxy do dynamicznego generowania kodu i tym samym dodać nowy element do ta-
kiego połączenia. Sposób jest dość pro-
<?php sty i nie wymaga zmian w kodzie PHP
// biblioteka do dynamicznego generowania klas
– jedynie w konfiguracji kontenera IoC.
require_once(dirname(__FILE__).'/phpproxy/src/proxygenerator.inc.php');
// wprowadzamy interfejs na fragment kodu sprawdzający uprawnienia, aby mieć
// możliwość stosowania różnych sposobów i bibliotek Listing 9. Korzystając
interface SecurityChecker { z implementacji interfejsu
function hasRightsFor(User $user, $actionToCheckRightsFor); } ProxyInvocationHandler
class SecurityCheckerIsNotNull implements SecurityChecker { z opisywanej biblioteki możemy
function hasRightsFor(User $user, $actionToCheckRightsFor){ łatwo zdecydować, czy chcemy
if ($user != null) { tylko udekorować oryginalną
//tu tylko proste sprawdzanie, ale chcemy pokazać, funkcjonalność, czy też stworzyć
//że kod odpowiedzialny za sprawdzanie uprawnień zupełnie nowy kod
//jest wydzielony i może być użyty w dekoratorach
return true; } else { return false; } } } <?php
// Część wspólna dla wszystkich dekoratorów. Nie ma tu duplikacji kodu jak na interface ProxyInvocationHandler {
// Listingu 7.Cała logika związana ze sprawdzeniem uprawnień i wyowołaniem public function
// (lub nie) dekoratora zamknięta jest w tej jednej klasie invoke($method, $args);
class SecurityCheckerMethodInvImpl extends DelegatingInvocationHandler { }
private $_securityChecker; class DelegatingInvocationHandler
private $_decoratedAction; implements
public function __construct(SecurityChecker $securityChecker) { ProxyInvocationHandler {
$this->_securityChecker = $securityChecker; } private $_delegate = null;
public function invoke($method, $args) { public function __construct(
$request = $args[0]; $delegate){
$mv = $args[1]; $this->_delegate =
$user = getLoggedInUser(); $delegate;
if ($this->_securityChecker($user, $method)){ }
return parent::invoke($method, $args); public function invoke(
} else { $method, $args){
$mv->setView('loginform'); $reflectionClass =
return $mv; } } new ReflectionClass(
public function getLoggedInUser(){ get_class($this->
session_start(); getDelegate()));
return $_SESSION['user']; } } $reflectionMethod =
$proxyGenerator = new ProxyClassGenerator(); $reflectionClass->
$secureMethodInvocation = new SecurityCheckerMethodInvImpl( getMethod($method);
new SecurityCheckerIsNotNull()); return $reflectionMethod->
// dynamiczne wygenerowanie dowolnego dekoratora sprowadza się do jednej linii invokeArgs(
// kodu! $this->getDelegate(),$args);
$useractionDecorated = $proxyGenerator->getProxy( }
'useraction', $secureMethodInvocation); public function getDelegate(){
$homepageDecorated = $proxyGenerator->getProxy( return $this->_delegate;
'homepage', $secureMethodInvocation); }
}
?> ?>

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


Techniki Wzorce projektowe: dekorator

Rozwiązanie jest prawie idealne. Pra- – lewniwego programisty to zdecydowa- nam pewna refleksja. Otóż w opisywa-
wie, bo w dalszym ciągu dla każde- nie zbyt duży wysiłek. nym przypadku stosujemy dekoratory do
go dekoratora trzeba dodać nowy wpis Jeśli pomyślimy całościowo o sty- globalnych zmian w całej aplikacji. Pa-
w konfiguracji i zmodyfikować połą- lu programowania, w którym napisany zo- trzymy na działające skrypty i myślimy:
czenia między obiektami. Dla twórczo stał Listing 10, to powinna nasunąć się teraz chcielibyśmy dodać logowanie do
wszystkich obiektów DAO, żeby zobaczyć,
Listing 10. Przykład wykorzystania Pico dla PHP – lekkiego kontenera IoC – do gdzie jest wąskie gardło wydajnościowe,
dodania dekoratorów do obiektów aplikacji albo: teraz udekorujmy wszystkie kontro-
lery, żeby sprawdzać uprawnienia. Nie-
<?php
stety, wypracowana do tej pory metoda
$parentPico = new DefaultPicoContainer();
skazuje nas na żmudne deklarowanie po-
$parentPico->regComponentImpl('FrontController', 'FrontControllerImpl');
$parentPico->regComponentImpl( jedynczych dekoratorów. Gdyby tylko ist-
'ActionResolvingStrategy', //componentKey niała konstrukcja programistyczna, pozwa-
'PicoActionResolvingStrategy', //componentClass lająca łatwo wyrazić żądania typu: obiekty
array ('paramName' => 'action')
określonego typu udekoruj danym kodem,
);
moglibyśmy dosłownie w kilku linijkach
$parentPico->regComponentImplWithIncFileName(
dirname(__FILE__).'/lib/SmartyViewResolver.php', kodu wprowadzić do aplikacji logowa-
'ViewResolvingStrategy', nie czy sprawdzanie uprawnień. Zupełnie
'SmartyViewResolvingStrategy', niezależnie od ilości obiektów do ude-
array (
korowania. Na pocieszenie chcemy po-
array('compile_dir' => dirname(__FILE__).'/work/templates_c'),
wiedzieć, że takie metody już powstają
'file:'.dirname(__FILE__).'/templates/smarty/',
'.tpl') (przykładowy Listing 11)! Jest to idea bar-
); dzo zbliżona do programowania aspek-
$pico = new DefaultPicoContainer(null, $parentPico); towego (ang. Aspect Oriented Program-
$pico->regComponentInstance($pico, 'PicoContainer');
ming), ale to już temat na zupełnie inny ar-
$pico->regComponentImplWithIncFileName(dirname(__FILE__).
tykuł.
'/../shared/model/model.inc.php','UserService','UserServiceImpl');
$pico->regComponentImplWithIncFileName(dirname(__FILE__).
'/../shared/model/model.inc.php','UserDAO','UserDAOPDOImpl'); Podsumowanie
$pico->regComponentImpl('PDO','PDOPicoAdapter',array W artykule pokazaliśmy, że dekorator jest
( 'pgsql:dbname=ditalkdb;host=localhost', 'postgres', 'postgres'));
prostym i niezwykle pożytecznym wzor-
//actions
cem projektowym. Jest tak użyteczny,
$pico->regComponentImplWithIncFileName(dirname(__FILE__).
'/actions/loginaction_action.php','loginaction','loginaction'); że w pewnym momencie możemy mieć
$pico->regComponentImplWithIncFileName(dirname(__FILE__). ochotę zastosować go na bardzo szero-
'/actions/useraction_action.php','useractionTarget', 'useraction'); ką skalę. Aby jednak zrobić to efektywnie,
$pico->regComponentImpl('useraction', 'UserActionSecurityDecoratorImpl', array
musimy zadbać o rozwiązanie dwóch pro-
('decoratedAction' => new BasicComponentParameter('useractionTarget')));
blemów: automatycznego generowania
$pico->regComponentImplWithIncFileName(dirname(__FILE__).
'/actions/homepage_action.php','homepageTarget','homepage'); dekoratorów oraz ich konfiguracji w całej
$pico->regComponentImpl('homepage', 'HomepageActionSecurityDecoratorImpl', array aplikacji. Z pierwszym problem doskonale
('decoratedAction' => new BasicComponentParameter('homepageTarget'))); poradzimy sobie dynamicznie generując
$fc = $pico->getComponentInstance('FrontController');
powtarzalny kod dekoratora. Zastosowa-
$fc->doService(new HttpRequest());
nie kontenera IoC wpłynie dodatnio na ar-
?> chitekturę naszej aplikacji i umożliwi łatwe
konfigurowanie dekoratorów. Jeśli zrozu-
Listing 11. Przykład rozwiązania, które nie skazuje nas na żmudne deklarowanie miemy i opanujemy przedstawione powy-
pojedynczych dekoratorów żej triki, świat programowania aspektowe-
<?php
go nie będzie miał przed nami tajemnic. n
//rejestracja poprzednich komponentów
//jak na Listingu 10
$pico->regComponentImplWithIncFileName(dirname(__FILE__).
'/actions/useraction_action.php','useraction', 'useraction'); O autorze:
$pico->regComponentImplWithIncFileName(dirname(__FILE__).
'/actions/homepage_action.php','homepage','homepage'); Paweł Kozłowski jest pracownikiem SU-
$pico->regComponentImplWithIncFileName(dirname(__FILE__). PERMEDIA, gdzie od roku 2000 projek-
'/actions/loginaction_action.php','loginaction','loginaction'); tuje i tworzy złożone aplikacje WWW w
//security decorators PHP. Obecnie zajmuje się rozwijaniem
$pico->regComponentImpl('SecurityMethodInterceptor'); frameworków i bibliotek ORM opartych
$pico->registerAspect(new Aspect(new RegExpNameMatchingPointcut( na PHP5. Jest autorem portu PicoCon-
'/^[a-km-z]+action$/'),new MatchingAllPointcut(), tainer dla PHP5 i wielu publikacji poświę-
'SecurityMethodInterceptor')); conych PHP.
?> Kontakt: pkozlowski@phpsolmag.org

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


Techniki

Metoda aktywnego
rekordu
Stopień trudności: lll
Jakub Sacha

Tworząc aplikacje internetowe na codzień,


często zdarza się nam operować na listach
danych np. artykułów, autorów, wpisów w
księdze gości czy nowości na stronie. Każda
z list organizowana jest w mniej lub bardziej
złożone tabele w bazie danych. Dużą część
czasu poświęcanego na przygotowanie aplikacji
zajmuje pisanie zapytań wstawiających lub
edytujących rekordy dla różnego rodzaju
danych, różniących się od siebie nazwami oraz
ilością kolumn.

W
przeważającej części jest to Przeanalizujmy te trzy proste funk-
mechaniczna praca, którą po- cje. Konstruktor zawiera pętle, przez po-
staramy się w tym artykule daną (opcjonalnie) w argumencie tablicę.
przenieść na PHP. Stwórzmy sobie listę Pozwala nam to sprytnie i bezproblemo-
artykułów. Po przygotowaniu odpowiedniej wo wpisać wartości do obiektu używając
tabeli w bazie (jak na Listingu 1), zajmijmy new Article($data). Musimy oczywiście
się obiektem, który będzie odpowiadał jed- uprzednio uzupełnić $data danymi z ba-
nemu rekordowi w tabeli (Listing 2). zy lub formularza – w zależności od po-
W naszym przykładzie wykorzysta- trzeb. Metoda Update() w zależności od
my wzorzec architektoniczny (dla źró- tego, czy wartość id jest uzupełniona,
dła danych) o nazwie Aktywny Rekord przygotowuje i wykonuje zapytanie wsta-
(ang. Active Record). Zakłada on, że wiające lub modyfikujące dane. Przy-
obiekt odpowiada jednemu wierszowi
w bazie.
Do klasy Article przedstawionej na Co należy wiedzieć...
W SIECI Listingu 2 dopiszemy teraz metody od-
Powinieneś znać podstawy programo-
wania obiektowego w PHP5. Przydatna
powiedzialne za jej update w bazie (nie będzie również lektura artykułu wprowa-
będziemy używać warstwy pośredniczą- dzającego w tematykę wzorców projek-
• http://wiki.rubyonrails.com/ towych, który ukazał się w PHP Solu-
cej w dostępie do bazy, aby nie kompli-
rails/pages/ActiveRecord tions 2/2006.
– Active record – strona do- kować przykładów, pomimo tego w pro-
mowa projektu jektach pisanych na codzień silnie prefe- Co obiecujemy...
• http://en.wikipedia.org/wiki/ Poznasz nowy ważny wzorzec projekto-
Active_record - Active record ruję użycie np. PDO). Przedstawione na wy związany z projektem Ruby On Rails
w Wikipedii: Listingu 3 funkcje wstawiamy w ciało kla- i jego praktyczne wykorzystanie.
sy Article.

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


Metoda aktywnego rekordu Techniki

gotowaliśmy prostą klasę, która będzie bra i przejrzystości przykładu będziemy dzimy opisywaną metodę oraz dodat-
pośredniczyć w naszym kontakcie z ba- operować na zwykłych tablicach. W ce- kowo metodę FindById – założyłem, że
zą danych, pozwoli nam zapomnieć o pi- lu wygenerowania listy stworzymy funk- metody zaczynające się od Get zwracają
saniu zapytań usuwających, dodających cję GetList i jako parametry podajmy jej tablicę, a Find konkretny obiekt.
i edytujących dane. Chcemy teraz wyge- limit oraz offset (przesunięcie). Pozwo- Naszemu Finderowi brakuje jesz-
nerować listę rekordów w bazie. Mogli- li nam to na łatwe operowanie listą i po- cze wiele do ideału. Nie mamy wpły-
byśmy zrobić to standardowo wykonu- dzielenie jej na porcje. Na Listingu 5 wi- wu na sortowanie wyników, ale nie to
jąc zapytanie, a następnie wpisując do
obiektów wartości używając przygotowa-
Listing 3. Metody odpowiedzialne za modyfikację bazy danych
nej wcześniej sztuczki i podając kolejne
wiersze pobrane z bazy do konstruktora. function __construct($data = null){
Przykładowy kod zaprezentowany został
na Listingu 4. foreach($data as $label=>$val){
$this->$label = $val;
Dobrym nawykiem jest oddziele-
}}
nie zapytań w wygodny dla nas spo-
function Update(){
sób. Przygotujmy więc obiekt, nazwijmy
go Finder – albo lepiej – ArticleFinder. // Jeśli id jest oznaczone, to generujemy update
W nim zawrzemy ciało potrzebnych if(!empty($this->id)){
$SQL = "UPDATE articles SET
w aplikacji metod, które będą zgodnie
categories = '".mysql_escape_string($this->categories)."',
z nazwą odpowiedzialne za odnajdywa-
title = '".mysql_escape_string($this->title)."',
nie artykułów lub ich kolekcji. Dla do- name = '".mysql_escape_string($this->name)."',
surname = '".mysql_escape_string($this->surname)."',
date = '".mysql_escape_string($this->date)."',
Listing 1. Struktura tabeli artykułów body = '".mysql_escape_string($this->body)."'
WHERE
CREATE TABLE `articles` id = '".mysql_escape_string($this->id)."'";
}
(
`id` int(11) NOT NULL default '0',
// W przeciwnym wypadku generujemy
// zapydanie wstawiające nowy wiersz do bazy
`title` varchar(255)
else{
NOT NULL default '',
$SQL = "INSERT INTO articles
(id, categories, title, name, surname, date, body) VALUES
`name` varchar(30)
(default,
NOT NULL default '',
'".mysql_escape_string($this->categories)."',
'".mysql_escape_string($this->title)."',
`surname` varchar(30)
'".mysql_escape_string($this->name)."',
NOT NULL default '',
'".mysql_escape_string($this->surname)."',
'".mysql_escape_string($this->date)."',
`date` datetime
'".mysql_escape_string($this->body)."')";
NOT NULL default
}
'0000-00-00 00:00:00',
// Wykonujemy zapytanie
`body` text NOT NULL,
return mysql_query($SQL);
PRIMARY KEY (`id`)
}

) TYPE=MyISAM;
/** Funkcja kasuje wpis o obiekcie w bazie
* @return bool
Listing 2. Przykładowy obiekt */
odpowiadający rekordom w bazie function Delete(){

<?php
if(!empty($this->id)){
class Article{
/**
* zapytanie usuwające
public $id;
*/
public $categories;
$SQL = "DELETE FROM articles WHERE
public $title;
id = '".mysql_escape_string($this->id)."' LIMIT 1";
public $name;
mysql_query($SQL);
public $surname;
return (bool)mysql_affected_rows();
public $date;
}
public $body;
return false;
}
}
?>

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


Techniki Active Record

Listing 4. Przykładowe uzupełnienie obiektów danymi z bazy W PHP4


W starszej wersji PHP4 brakuje funk-
$SQL = "SELECT * FROM articles ORDER BY name ASC"; cji magicznych (__set() oraz __get())
$RES = mysql_query($SQL); więc automatyczny lazy init nie
/** jest do wykonania. Cała reszta klasy
* przygotowujemy tablicę, do której będziemy wpisywać ActiveRecord jest podobna. Przy PHP w
* obiekty utworzone z nowo pobranych wierszy wersji do 4.2 funkcja get_class_vars()
*/ i get_object_vars() zachowuje się in-
$aArticles = array(); aczej – nie zwraca niezadeklarowanych
while($AFR = mysql_fetch_assoc($RES)){ zmiennych, dlatego trzeba im nadawać
$aArticles[] = new Article($AFR); wartości przy tworzeniu klasy (np. var
} $id = '';). Oczywiscie z przedrostków
/** private, protected, public, abstract itd.
* Aby sprawdzić, czy wynik naszej pracy musimy zrezygnować. To samo tyczy się
* jest poprawny, proponuję
wyjątków w konstruktorze – możemy je
spokojnie zastąpić funkcją die().
* użyć następującego kodu:
* echo '<pre>'.var_dump($aArticles, 1).'</pre>';
*/ jest naszym największym problemem.
Zauważmy, że w zapytaniu występu-
Listing 5. Implementacja klasy ArticleFinder odpowiedzialnej za pobieranie danych
je ciąg SELECT * – pobieramy wartości
i zwracanie obiektów typu Article
z wszystkich pól w bazie. Załóżmy teraz,
class ArticleFinder{ że nasze artykuły posiadają dużo treści.
// Zwraca tablicę obiektów typu Article, posortowanych wg nazwy Generując zwykłą listę do wyświetle-
// @param integer $iLimit
nia na stronie potrzebujemy co najmniej
// @param integer $iOffset
nazwy oraz id, a ewentualnie dodatko-
// @return array
function GetList($iLimit = false, $iOffset = false){ wo danych na temat autora lub daty do-
$SQL = "SELECT * FROM articles ORDER BY name ASC"; dania artykułu. Treść jest pożądana do-
// Doklejamy do zapytania limit i przesunięcie - jeśli podano piero w momencie, gdy nasz Czytelnik
if($iLimit!=false)
kliknie w link więcej i zdecyduje się na
$SQL .= "LIMIT ";
lekturę naszych wypocin. Nie mamy po-
if($iOffset != false ){
$SQL .= $iOffset.', '; trzeby pobierania zawartości pola body,
} bo nie jest ono używane. Moglibyśmy
$SQL .= $iLimit; zwyczajnie nie uzupełniać go, modyfi-
$RES = mysql_query($SQL);
kując zapytanie w następujący sposób:
// przygotowujemy tablicę, do której będziemy wpisywać
SELECT id, categories, title, name,
// obiekty utworzone z nowo pobranych wierszy
$aArticles = array(); surname ... Zauważmy jednak, co sta-
while($AFR = mysql_fetch_assoc($RES)){ ło by się, gdybyśmy przypadkowo wyko-
$aArticles = new Article($AFR); nali metodę Update() na nie uzupełnio-
}
nym do końca obiekcie. Wszystkie dane,
return $aArticles;
które są w bazie, ale nie zostały wpisa-
}
ne do obiektu zostały by nadpisane pu-
// Zwraca obiekt typu Article wypełniony danymi z bazy, o id podanym stymi wartościami. Na szczeście z po-
// w parametrze lub false w przypadku ,gdy nie znajdzie rekordu. mocą przychodzą funkcje __set() oraz
//
__get(), które otrzymaliśmy razem z 5
// @param integer $iId
odsłoną PHP. Usuńmy więc pole body ze
// @return Article
function FindById($iId){ zmiennych klasy Article (pozostają tyl-
$SQL = "SELECT * FROM articles WHERE id = ".mysql_escape_ ko zdefiniowane wcześniej id, title,
string((int)$iId)." LIMIT 1"; name, surname, date – wszystkie jako
$RES = mysql_query($SQL);
public). Następnie zmodyfikujmy zapy-
$AFR = mysql_fetch_assoc($RES);
tanie w funkcji GetList() wpisując zdefi-
if(!empty($AFR)){
return new Article($AFR); niowane w klasie pola, a pomijając body.
} Wykonajmy teraz kod odpowiedzialny za
return false; pobieranie listy artykułów. Otrzymuje-
}
my tablicę artykułów, w każdym uzupeł-
}
nione wszystkie pola oprócz body. O to
// Aby sprawdzić, czy wynik naszej pracy jest poprawny, proponuję nam chodziło, brakuje jeszcze tylko ob-
// użyć następującego kodu (mozna zastosować opcjonalne argumenty sługi brakującego pola. Do jej wykona-
// funckji GetList czyli limit oraz offset): nia użyjemy wyżej wspomnianych funk-
// $aArticles = ArticleFinder::GetList();
cji magicznych __get() oraz __set(),
// echo '<pre>'.var_dump($aArticles, 1).'</pre>';
sposób implementacji przedstawiono na
Listingu 6.

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


zamów prenumeratęPHP Solutions a otrzymasz prezent!
niższa cena: 135zł

w prezencie otrzymasz
Archiwum PHP Solutions 2005 w PDF

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

* cena prenumeraty rocznej w promocji


oferta ważna do wyczerpania zapasów;

szczegółowe informacje: www.phpsolmag.org/prenumerata lub pren@software.com.pl


Techniki Active Record

Gdybyśmy w momencie, gdy pole zowanym polu body. Wszystko działa jak śmy na celu tego osiągnąć – należy więc
body nie zostało zainicjalizowane, za- powinno, dane są uzupełniane, a rezultat uważać, by nie wpaść w taką pułapkę,
żądali jego wartości, zostanie wykonana znakomity. Przemyślmy teraz następują- bo nasze cele zaoszczędzenia pracy ba-
metoda __get. W niej pobieramy, wpisu- cy przypadek. Pobieramy listę artykułów zy danych w bardzo prosty sposób mogą
jemy do obiektu oraz zwracamy brakują- pomijając pole body, a następnie w liście zadziałać zupełnie odwrotnie. Najłatwiej
cą wartość. Gdybyśmy natomiast spró- wyświetlamy dodatkowo skrót treści w poradzić sobie z tego typu problema-
bowali zmienić wartość pola body kiedy postaci np. echo substr($aArticle[$i]- mi monitorując ilość oraz treść zapytań
nie jest zainicjalizowane, klasa zajmie się >body, 0, 100). Okazuje się, że wyko- w trakcie przygotowywania aplikacji
pobraniem automatycznie starej wartości nujemy zapytanie pobierające listę. Za- (warto używać lub przygotować warstwę
i to na niej wykonywane będą zmiany. łóżmy 20 pozycji, a następnie wyświe- kontaktu z bazą danych, obsługującą
Przetestujmy pobraną uprzednio listę np. tlając je w pętli doczytujemy 20 razy po- monitorowanie zapytań).
używając strtoupper() na niezainicjali- le body – razem 21 zapytań. Nie mieli- Na początku mówiliśmy o mecha-
nicznej pracy, którą wykonujemy pisząc
zapytania, a w poprzednich przykładach
Listing 6. Implementacja metod __set oraz __get klasy Article – odpowiedzialne za
metoda Update() i tak zawierała kod
lazy init.
wpisany przez nas. Zmieńmy lekko przy-
function __get($field){ kład – klasa dalej będzie opisywać te sa-
me dane w bazie, będzie jedynie ina-
/** czej skonstruowana od strony technicz-
* Jeśli żądanie dotyczy pola body pobieramy jego wartość z bazy
nej tak, aby pozwolić programiście za-
*/
if($field=='body'){ pomnieć, jak pisze się zapytania wsta-
$SQL = "SELECT body FROM articles WHERE id = '".mysql_escape_ wiające, edytujące oraz usuwające wier-
string($this->id)."' LIMIT 1"; sze z bazy. Strukturę naszej tabeli mogli-
$RES = mysql_query($SQL); byśmy opisać w następujący sposób. Ta-
$AFR = mysql_fetch_assoc($RES);
bela znazywa się articles, zawiera pola
/**
* wpisujemy wartość do obiektu, aby przy kolejnych żądaniach nie o nazwach id, name, author, date oraz
* wywoływać już zapytań body przy czym pole id jest kluczem
*/ głównym, a pole body – treścią, którą nie
$this->body = $AFR['body']; zawsze chcemy pobierać, ponieważ nie
/**
zawsze jej potrzebujemy. Wpiszmy te da-
* Zwracamy nowo pobraną wartość
*/ ne do przykładowej klasy – Listing 7.
return $this->body; Logicznie rzecz biorąc, mamy
} wszystkie dane potrzebne do wygene-
} rowania zapytań modyfikujących da-
/**
ne w bazie. Przyjmijmy Listing 7 jako
* nadajemy wartość nie zainicjalizowanemu polu
*/ schemat implementacji klas dziedziczą-
function __set($field, $value){ cych z ActiveRecord. Wiemy, gdzie wpi-
$this->$field = $value; sywać, znamy nazwy pól, do których
} mamy wstawiać dane. Do tego wiemy,
jaki jest klucz główny (primary key) – to
Listing 7. Reimplementacja klasy Article
on potrzebny nam będzie, aby usuwać
class Article extends ActiveRecord{ oraz edytować poszczególne rekordy.
/** Zajmiemy się teraz przygotowaniem abs-
* nazwa tabeli, w której przechowywane są dane
trakcyjnej klasy ActiveRecord, która bę-
* @var string
*/
dzie odpowiedzialna za kontakt z bazą
protected $_tableName = 'Articles'; oraz czytanie danych w razie potrzeby,
/** czyli wspomniany wczesniej lazy init.
* klucz podstawowy; w 90% przypadków to pole 'id'. Przedstawia to Listing 8.
* Po wykonaniu zapytania wstawiajacego 'insert' zostanie
Kilka słów komentarza odnośnie tego
* wpisana wartość mysql_insert_id() do pierwszego z podanych poniżej;
* @var array
Listingu. Abstrakcyjna klasa ActiveRecord
*/ przechowuje nazwy zainicjalizowanych
protected $_pk = array('id'); pól @var array. Domyślny typ leniwej
/** inicjalizacji to AR_LAZY_ALL_AT_ONCE. Je-
* nazwy wszystkich pól w bazie danych
śli podamy: AR_ALL_AT_ONCE – po żądaniu
* @var array
*/
pola niezainicjalizowanego, wszystkie po-
protected $_fields = la z tabeli, które nie są zainicjalizowane,
array( 'id', 'title', 'name', 'surname', 'date', 'body' ); zostaną automatycznie pobrane.
} Warto jeszcze zaznaczyć, że może-
my chcieć przy doczytywaniu brakują-

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


Metoda aktywnego rekordu Techniki

cego pola, doczytać od razu wszystkie,


Listing 8a. Klasa abstrakcyjna ActiveRecord które nie są jeszcze zainicjalizowa-
ne – do tego posłuży nam zmien-
<?php
define("AR_LAZY_ALL_AT_ONCE", 1); na _lazyType. Metody Update() oraz
define("AR_LAZY_PER_ONE", 2); Delete() zawarte w ActiveRecord pod-
abstract class ActiveRecord{ czas pobierania i odpowiedniego mani-
// Przechowuje nazwy pol ktore zostaly zainicjalizowane @var array pulowania danymi potrafią wygenerować
private $_initalized = array();
zapytania, których w dalszej części apli-
protected $_lazyType = AR_LAZY_ALL_AT_ONCE;
// w konstruktorze sprawdzamy poprawnosc klasy, kacji używa nasza klasa. Na szczególną
// ktora dziediczy po ActiveRecord. uwagę zasługują funkcje AfterSelect()
function __construct($param = null){ oraz BeforeQuery() – to je nadpisujemy
if(empty($this->_tableName)){ w klasach potomnych w celu poszerze-
throw new Exception('Nie zdefiniowano
nia funkcjonalności.
_tableName w klasie \''.get_class($this).'\'') ; }
if(empty($this->_pk)){ Pierwsza z nich, zgodnie ze swoją na-
throw new Exception('Nie zdefiniowano zwą, wywoływana jest po każdym zapy-
_pk w klasie \''.get_class($this).'\'') ; } taniu pobierającym dane. Co nam to da-
if(empty($this->_fields) or !is_array($this->_fields)){ je? Możemy po każdym pobraniu któ-
throw new Exception('Nie zdefiniowano
regoś z pól np. categories wykonać na
_fields w klasie \''.get_class($this).'\'') ; }
if(count($param)>0){ nim operację, dla przykładu explode()
$this->SetData($param); } } na przecinku, aby otrzymać tablicę
//wykonywana przy nie zainicjalizowanych zmiennych zawierającą kategorie, do których na-
//@param string $variable oraz @param mixed $value leży artykuł. Rozdzieloną według prze-
function __set($variable, $value){
cinka tablicę edytujemy, np. dodając do
if(!in_array($variable, $this->_initalized)
and in_array($variable, $this->_fields)){ niej kolejną kategorię (edytujemy działa-
$this->_initalized[] = $variable; } jąc na tablicach, a nie tak jak dane prze-
$this->$variable = $value; } chowywane są w bazie – czyli ciągu)
function __get($name){ i BeforeQuery() wykonuje implode(),
// jesli ustawiono primary keys, robimy lazy inicjalizację
a do bazy zapisuje się ciąg znaków, roz-
if($this->AllPrimeryKeyIsset() and in
_array($name, $this->_fields)){ dzielony przecinkami. Ułatwia to znacz-
// Pobiera wszystkie niezainicjalizowane, czy jeden nie życie w przypadku gdy mamy potrze-
if($this->_ bę modyfikacji danych podczas kontaktu
lazyType == AR_LAZY_ALL_AT_ONCE){ ze źródłem danych, a chcemy zapomnieć
$sFields = implode
o ograniczeniach, które niesie użycie ba-
(', ', $this->GetUninitalizedVars()); }
else{ $sFields = $name; } zy danych (płaskie przechowywanie).
Podany przykład dość prosto zilustrował
// musimy wiedziec, z ktorego rekordu dane pobrac problem – w bardziej skomplikowanych
foreach($this->GetPrimaryKeys() as $sKey=>$sVal){ przypadkach można użyć serialize – je-
$aKeys[] = $sKey.' = '.$this->BeforeQuery($sKey, $sVal); }
śli nie chcemy wprowadzać dodatkowych
$SQL = 'SELECT '.$sFields.' FROM '.$this->_
tableName.' WHERE '.implode(', ', $aKeys); relacji w bazie. Możemy też pokusić się
$RES = mysql_query($SQL); o nadpisanie w potomku metody
foreach(mysql_fetch_assoc($RES) as $sKey=>$sVal){ Delete(), która miałaby przykładowo kilka
$this->SetData(array($sKey => $sVal)); } zadań; najpierw ustawienie pola deleted
return $this->$name; } else{ return null; }}
w tabeli na status true, a dopiero po po-
// Zwraca tablicę, klucze to nazwy primary keys, wartosci to ich wartosci nownym wykonaniu oraz spełnionym wa-
function GetPrimaryKeys(){ runku if($this->deleted==true) usuwać
$mRet = array(); // przygotowyjemy array do zwrocenia całkowicie rekord z bazy. Bardzo łatwo
foreach ($this->_pk as $sVal){ moglibyśmy wygenerować w ten sposób
$mRet[$sVal] = $this->$sVal; }
znany wszystkim kosz.
return $mRet; }//sprawdza, czy primary klucze są ustawione
// @return bool Dobrym przykładem jest również
function AllPrimaryKeyIsset(){ przechowywanie danych na temat zu-
foreach ($this->_pk as $sVal){ ploadowanych plików w bazie. Przy usu-
if( !isset($this->$sVal) ){ return false; } } waniu rekordu z tabeli, stosujemy me-
return true; }
todę unlink() i zapominamy o ręcznym
// zwraca tablicę, jako klucze nazwy pól inicjalizowanych później, usuwaniu kasowanych plików. Dla
// wartości to przypisane do danych pól wartosci wiersza w bazie szczególnie leniwych, w konstruktorze
// @return string naszej klasy możemy sprawdzać, czy
function GetInitalizedVars(){ podana wartość jest tablicą (wtedy do-
return $this->_initalized; }
konujemy wpisania wartości) czy np.
liczbą i wykonywać finder::findById();

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


Techniki Active Record

Listing 8b. Przykładowa implementacja klasy abstrakcyjnej ActiveRecord Listing 9. Przykładowe


zastosowanie SetData oraz Validate
// Wpisuje wartości tablicy do obiektu.
// @param array $arr oraz @return false null if(isset($_POST)){
function SetData($aArr){
foreach (@(array)$aArr as $sId=>$sVal){ // Tworzymy nowy obiekt
$this->__set($sId, $this->AfterSelect($sId, $sVal)); } } $oArticle = new Article();
// Zwraca wszystkie nie zainicjalizowane pola
// @return array // wpisujemy wartości
function GetUninitalizedVars(){ // z formularza
$aRet = array(); $oArticle->SetData($_POST);
foreach($this->_fields as $val){ // sprawdzamy czy są okey
if(!in_array($val, $this->_initalized)){ if($oArticle->Validate()){
$aRet[] = $val; } } return $aRet; } // jesli tak, wpisujemy
// Kasuje wiersz z bazy @return bool // do bazy
function Delete(){ $oArticle->Update();
/* jesli obiekt ma ustawione klucze glowne, czyli nie jest nowy, to }
* kasujemy go z bazy */ }
if($this->AllPrimeryKeyIsset()){
foreach($this->GetPrimaryKeys() as $sKey=>$sVal){
$aKeys[] = $sKey.' = '.$this->BeforeQuery($sKey, $sVal); }
$SQL = 'DELETE FROM '.$this->_tableName.' a ostatecznie przypisać wartości. Po-
WHERE '.implode(', ', $aKeys); branie wtedy rekordu o id równym 7
mysql_query($SQL); będzie wyglądało następująco: $article
return true; } = new article(7). MySQL 5 niesie ze
return false; }
sobą support przechowywania XML-a
function Update($forceInsert = false){
// jesli obiekt nie ma ustawionych kluczy glownych, nie jest nowy, to wewnątrz pól. Przy pobieraniu moż-
// wykonujemy insert w bazie na przeparsować dane, a przy insercie
if(!$this->AllPrimeryKeyIsset() or $forceInsert){ wstawić XML-a wygenerowanego przez
$aVals = array(); BeforeQuery(). Pomysłów i przykładów
foreach ($this->GetInitalizedVars() as $sID=>$sVal){
przytoczyć możnaby wiele, ogranicza
$aVals[$sVal] = $this->BeforeQuery($sID, $this->$sVal); }
// jesli jest co wstawiac nas jedynie własna pomysłowość.
if(count($aVals)){
$SQL = 'INSERT INTO '.$this->_tableName.' ('. Walidacja
implode(', ', array_keys($aVals)).') VALUES('. Nic nie stoi także na przeszkodzie przed
''.implode(', ', $aVals).')'; }
dodaniem metody – Validate(), która
else{ return false; } }
// jesli obiekt ma ustawione klucze glowne, czyli nie jest nowy to określałaby, czy dane w obiekcie nada-
// wykonujemy update w bazie ją się do wpisania do bazy. W połączeniu
else{ z metodoą SetData() mogą tworzyć na-
// przygotowujemy klucze do zapytania prawdę sprawne narzędzie – Listing 9.
foreach($this->GetPrimaryKeys() as $sKey=>$sVal){
W tym momencie warto zwrócić
$aKeys[] = $sKey.' = '.$this->BeforeQuery($sKey, $sVal); }
// przygotowujemy wartosci do zapytania, uwagę na dość istotną kwestię. Nig-
// update tylko tych zainicjalizowanych dzie w omawianym przykładzie nie uży-
foreach($this->GetInitalizedVars() as $sVal){ to nazw pól w bazie. Oznacza to, że do-
$aVals[] = $sVal.' = '.$this->BeforeQuery($sVal, $this->$sVal); danie w formularzu dodatkowego pola
} // jeśli jest co updatować
oraz uzupełnieniu o nie definicji obiek-
if(count($aVals)>1){
$SQL = 'UPDATE '.$this->_tableName.' SET tu i struktury bazy danych, to wszyst-
'.implode(', ', $aVals).' WHERE '.implode(', ', $aKeys); kie czynności wymagane do poszerze-
} else{ return false; } } nia zakresu działania naszego obiektu
mysql_query($SQL); – a przecież prawdopodobnie każdy
if(!$this->AllPrimeryKeyIsset() or $forceInsert){
z nas zna poszukiwania wszystkich za-
$AI = end(array_reverse($this->_pk));
$this->$AI = mysql_insert_id(); } return true; } pytań, w celu uzupełnienia o brakujące
function GetFields(){ return $this->_fields; } pole – tutaj problem znika.
function BeforeQuery($name, $value = null){ Nic nie stoi również na przeszko-
if(strtoupper($value) == 'NOW()') { dzie, aby po wykonaniu select zamie-
return 'NOW()'; }
niać przykładowe pole autor zawierają-
if(strtoupper($value) == 'DEFAULT') {
return 'default'; } ce id autora na obiekt user zawierający
return '\''.mysql_escape_string($value).'\''; } dane, które zapisane są w innej tabeli.
function AfterSelect($name, $value = null){ W metodzie BeforeQuery natomiast
return $value; }} możemy wykonać update na obiekcie
?>
user i zamienić ten obiekt z powrotem na
id, aby zachować zgodność zawartości.

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


Metoda aktywnego rekordu Techniki

Listing 10. Przykładowe zastosowanie SetData oraz Validate Listing 11. Przykład użycia klasy
articleFinder
<?php
abstract class Finder{ class articleFinder extends Finder {
// pola do pobrania @var array private $tableName = 'articles';
protected $defaultfields = array('*'); }
protected $fields = null; protected $limit = 0; $finder = new articleFinder();
protected $offset = 0; protected $order = 0; $finder->SetWhere('name = '.'Nazwa');
protected $where = null; $finder->SetLimit(1);
public function SetWhere($sWhere){ $finder->Get();// zwróci tablicę
$this->where = $sWhere; } $finder->SetWhere('id = '.'1');
protected function GetWhere(){ $finder->GetOne();// zwróci obiekt
$aRet = $this->where;
$this->where = null;
return $aRet; }
public function SetLimit($iLimit){ Pobieranie danych
$this->limit = $iLimit; } W przypadku pobierania danych oraz
protected function GetLimit(){ uzupełniania obiektów – można iść dwie-
$aRet = $this->limit; ma drogami. Pierwsza z nich to przygo-
$this->limit = 0;
towywanie wszystkich kombinacji funk-
return $aRet; }
public function SetOffset($iOffset){ cji w finderach, czyli GetByCountry(),
$this->limit = $iLimit; } FindById(), FindByName(), FindByName-
protected function GetOffset(){ AndSurname(). Możemy również przygo-
$aRet = $this->offset; tować interfejs, podobny do tego z Listin-
$this->offset = 0;
gu 10.
return $aRet; }
public function SetFields($aFields){ W takiej sytuacji utworzenie nowe-
$this->fields = $aFields; } go findera to zazwyczaj kwestia doda-
protected function GetFields(){ nia extends Finder. Do dyspozycji ma-
if(empty($this->fields)){ my wówczas SetWhere(), SetLimit(),
return $this->defaultfields; }
SetOffset(), a dopisujemy tylko bar-
$aRet = $this->fields;
$this->fields = null; dziej skomplikowane zapytania, któ-
return $aRet; } re nie ograniczają się do użycia WHE-
function __construct(){ RE oraz LIMIT. Nic nie stoi na prze-
if(empty($this->tableName)){ szkodzie, aby rozwinąć funkcje, dodać
throw new Exception
obsługę tablic jako parametrów wej-
('Nie zdefiniowano tableName w klasie \''.get_class($this).'\'') ;
} } ściowych oraz czegokolwiek, co może
// zwraca tablice obiektów @return array ułatwić pracę.
function Get(){
$iLimit = $this->GetLimit(); Na koniec
$iOffset = $this->GetOffset();
Alternatywą dla Active Record jest
$where = $this->GetWhere();
$SQL = pakiet PEAR::DB_DataObject. Wyko-
'SELECT '.implode(', ',$this->GetFields()).' rzystuje on część z opisanych w ar-
FROM '.$this->tableName.' tykule technik i sprawdziłby się jako
'.( !empty($where)?('WHERE '.$where.'') : ('') ).' alternatywa dla naszej implementacji.
'.($iLimit > 0?('LIMIT '.$iLimit.','.$iOffset):'');
Zachęcamy jednak do samodzielnego
$RES = mysql_query($SQL);
$aRet = array(); ekperymentowania, co pozwoli na do-
while($AFR = mysql_fetch_assoc($RES)){ stosowanie narzędzia do naszych po-
$aRet[] = new $this->returnType($AFR); } trzeb, a nie odwrotnie. n
return $aRet; }
// @return object
function GetOne(){
$iLimit = $this->GetLimit();
$iOffset = $this->GetOffset();
$where = $this->GetWhere();
O autorze
$SQL =
'SELECT '.implode(', ',$this->GetFields()).' Jakub Sacha z php ma styczność od
FROM '.$this->tableName.' około 3-4 lat. Początkowo programowa-
'.( !empty($where)?('WHERE '.$where.'') : ('') ).' nie traktował jako pasję, ale aktualnie
LIMIT 1'; pracuje jako programista w bytomskiej
$RES = mysql_query($SQL); firmie NylonCoffee. Przepełnia go chęć
return new $this->returnType(mysql_fetch_assoc($RES));} rozwoju.
?> Kontakt z autorem: jakub@sacha.pl

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


www.buyitpress.com

Zaprenumeruj swoje ulubione magazyny


i zamów archiwalne numery!

Już teraz w kilka minut możesz zaprenumerować swoje ulubione pismo.


Gwarantujemy:
- preferencyjne ceny
- bezpieczną płatność on-line
- szybką realizację Twojego zamówienia
Bezpieczna prenumerata on-line wszystkich tytułów Wydawnictwa Software!
zamówienie prenumeraty

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

Imię i nazwisko............................................................................................ ID kontrahenta..........................................................................................

Nazwa firmy................................................................................................. Numer NIP firmy.......................................................................................

Dokładny adres....................................................................................................................................................................................................................

Telefon (wraz z numerem kierunkowym)................................................... Faks (wraz z numerem kierunkowym) ....................................................

E-mail (niezbędny do wysłania faktury)............................................................................................................................................................................


automatyczne przedłużenie prenumeraty

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

Linux+ (2 płyty CD)


Miesięcznik o systemie Linux
12 199/1791

Linux+DVD (2 płyty DVD)


Miesięcznik o systemie Linux
12 199/1791

Linux+Extra! (od 1 do 7 płyt CD lub DVD)


Numery specjalne z najpopularniejszymi dystrybucjami Linuksa
8 232/1982

PHP Solutions (1 płyta CD)


Dwumiesięcznik o zastosowaniach języka PHP
6 135

Hakin9, jak się obronić (1 płyta CD)


Dwumiesięcznik o bezpieczeństwie i hakingu
6 135

.psd (1 płyta CD + film instruktażowy)


Dwumiesięcznik użytkowników programu Adobe Photoshop
6 140

Aurox Linux (4 płyty CD + 1 płyta DVD)


Magazyn z najpopularniejszym polskim Linuksem
4 1193

Suma

Jeżeli chcesz zapłacić kartą kredytową, wejdź na


stronę naszego sklepu internetowego:
1
Cena prenumeraty rocznej dla osób prywatnych
2
Cena prenumeraty rocznej dla osób prenumerujących już Software Developer’s Journal lub Linux+
3
Cena prenumeraty dwuletniej Aurox Linux
www.buyitpress.com
W następnym numerze
PHP Solutions 5/2006(16)

TECHNIKI
PHPUnit2 – Testy aplikacji PHP są niezwykle ważne, a możliwo-
ści jakie niesie PHPUnit2 pozwolą zaoszczędzić wiele cennego
czasu, który możemy spożytkować na znacznie przyjemniejsze
rzeczy niż wyszukiwanie kolejnych błędów w naszym kodzie.
PHPUnit2 to kolejna odsłona doskonałego programu to wykony-
wania testów jednostkowych (ang. unit tests) naszych aplikacji.

XML w praktyce – Guillame Ponçon przedstawi nowe możli-


wości współpracy między PHP, a XML. Pokażemy m.in. Opera-
cje na dokumencie OpenOffice z wykorzystaniem SAX, czy od-
czytywaniu diagramu z ganttproject za pomocą SimpleXML.

NARZĘDZIA
TYPO3 – Jean-Gael Rouchon, jeden z deweloperów projektu,
przedstawi nam praktyczne wykorzystanie jednego z najważniej-
szych CMS-ów typu portalowego. Nowa, czwarta już wersja sys-
temu zaskakuje elastycznością, rozszerzalnością oraz ciekawym
frameworkiem, do tworzenia na bazie TYPO3 własnych aplikacji.

Test systemów CMS – Wybranie odpowiedniego systemu


CMS, to nie lada wyzwanie dla każdego dewelopera PHP.
W artykule dokonamy porównania najpopularniejszych syste-
mów i powiemy, których i w jakich zastosowaniach warto uży-
wać, a które należy omijać szerokim łukiem.

PROJEKTY
PHP-Qt – Aplikacje okienkowe w PHP? Pisaliśmy już o PHP-
GTK2, ale nikt nie przypuszczał, że PHP ożeni się z Qt...
Rozwiązanie już działa, a my pokażemy, jak wykorzystać je
w praktyce.

Ponadto planujemy: W sprzed


aży
od 15 sier


Mojavi
Winbinder
pnia!
■ DB2 i PHP
■ Video Streaming

■ MSSQL i PHP

oraz ciąg dalszy artykułów poświęconych bezpieczeństwu aplikacji oraz wy-


korzystaniu wzorców projektowych

You might also like