Professional Documents
Culture Documents
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.
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
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ą.
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
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/
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
Materiały dodatkowe
Narzędzia w dystrybucji
Kompletne środowisko LAMP
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
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
Programowanie obiektowe
w PHP na przykładzie IRCBota
Stopień trudności: lll
Marcin Staniszczak
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,
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. ?>
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 {}
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-
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);
$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).
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):
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-
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.
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). ������������������
��������������������
(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-
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; }
) ) ?>
�����
��������������������
����������������
�������������������� ��������������������
������������� ��������������
�������������������� ��������������������
��������������
�������������������� �������������������� ��
������
�� ���
�����������
�������������������� �������������������� ����
�������������������� ��������������������
��
�������������������� ���
�������� ��
������� ������������������
��������������������
��
������������
�����������
�������������������� ��������������������
���
��� ���������������� ��������������������
��������������������
��������������������
�������������������� ��������������������
�������������������� ��������������������
�������������������� ��������������������
��������
��������������������
����������������
������������������
�����������
��������������������
����������������
�������������������� ��������������������
������������
���������������� ��������������������
��������������������
�������������������������������
�����������������������������������������������������
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);
?>
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-
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.
Kryptografia w PHP
Stopień trudności: lll
Łukasz Lach, Michał Stanowski
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
• 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-
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;
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)
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.
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
Zend Framework
Stopień trudności: lll
Piotr Szarwas
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-
��
�� ��
�� ��
��
��
��
��
�� ��
�� ��
�� ��
�� ��
�� ��
�� ��
�� ��
�� ��
�� ��
��
��
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,
�������������
�����������������
����������
�������������������
������������������������
����������������
�������������
�����������������
�������������������������
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
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-
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
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-
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
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-
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; }
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
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
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-
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ą
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'] );
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-
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
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
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
Metoda aktywnego
rekordu
Stopień trudności: lll
Jakub Sacha
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.
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;
}
}
?>
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
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ą-
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
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
Dokładny adres....................................................................................................................................................................................................................
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
Suma
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.
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.
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.
■ MSSQL i PHP