PHPSolutions_04_2006_PL

84

Transcript of PHPSolutions_04_2006_PL

Page 1: PHPSolutions_04_2006_PL
Page 2: PHPSolutions_04_2006_PL
Page 3: PHPSolutions_04_2006_PL
Page 4: PHPSolutions_04_2006_PL

Spis treści

www.phpsolmag.org4 PHP Solutions Nr 4/2006

Spis treści

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

Deweloperzy poszukiwani?... bardzo dobra znajomość PHP4/5 oraz MySQL, do-bra znajomość HTML oraz JavaScript, doświadczenie w tworzeniu aplikacji WWW z wykorzystaniem Zend Fra-mework...

Tak już niedługo może brzmieć ogłoszenie poten-cjalnego pracodawcy poszukującego Programisty PHP.

Zend, firma „od PHP”, wypuszcza „prawdziwy” framework dla PHP – 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-ta, zyskał już setki zwolenników, którzy ze zniecierpliwieniem czekają na kolejne wersje rozwojowe, jak również na te stabilne. A wsparcie gwa-rantują mu najwięksi deweloperzy PHP, sama firma Zend oraz ogrom-na społeczność PHP. Można byłoby więc zaryzykować twierdzenie, że Zend Framework już wkrótce stanie się pewnego rodzaju standardem. Taki stan rzeczy z pewnością ułatwiłby i ustandaryzował tworzenie oraz rozwijanie aplikacji WWW. Wielu programistów zaczęłoby poniekąd roz-mawiać wspólnym językiem, a znajomość Frameworka byłaby dodatko-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ą na to pytanie musimy jeszcze trochę poczekać. Już teraz jednak, w arty-kule Piotra Szarwasa, przedstawimy Wam możliwości nowego narzędziai praktyczne przykłady jego użycia.

W obecnym numerze przedstawimy też konkurencyjne dla Zend Fra-mework rozwiązanie: eZ components, będące platformą do tworzenia zaawansowanych aplikacji klasy Enterprise. Główny deweloper projek-tu, Tobias Schlitt, przedstawi Wam praktyczne wykorzystanie najważniej-szych komponentów frameworka.

Na koniec prawdziwa niespodzianka – Kryptografia w PHP. Zdziwie-ni? Zaskoczeni? Nic więcej nie zdradzę.

Serdecznie zapraszam do lektury!Dariusz Pawłowski

AKTUALNOŚCIZe świata PHP 6Przedstawiamy garść najciekawszych wiadomo-ści dla deweloperów PHP.

Opis CDZawartość CD 10Prezentujemy zawartość płyty i sposób działania najnowszej wersji naszej dystrybucji PHP Solu-tions Live.

WYWIADRozmowa z Dmitrim Stogovem 12

Dariusz Pawłowski

Dmitry Stogov jest jednym z inżynierów rozwojo-wych w firmie Zend i szefem rosyjskiego zespo-łu tej firmy. Należy do grona twórców Zend Engi-ne 2. Stworzył też Turck MMCache i szereg roz-szerzeń SOAP i Perl dla PHP5.

POCZĄTKIObiektowość w PHPna przykładzie IRCBota 14Marcin StaniszczakW dalszym ciągu wiele osób nie próbowało pro-gramowania obiektowego. Oto artykuł, który po-wstał, by wprowadzić w świat obiektowości i PHP5 . Krok po kroku piszemy aplikację wpro-wadzającą do tego modelu programowania.

APIlity: zarządzanie reklamami Google AdWords z poziomu PHP 26Thomas SteinerZałóż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 procesów. Naprzeciw naszym oczekiwaniom wy-chodzi Google AdWords API.

BEZPIECZEŃSTWOKryptografia w PHP 34Łukasz Lach, Michał StanowskiPrawdopodobnie 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 to wszystko z wykorzystaniem PHP.

niemieckim włoskimfrancuskimpolskim

Nasz magazyn ukazuje się w czterech językach!

Jeśli jesteś zainteresowany zakupem licencji na wydawanie naszych pism prosimy o kontakt:Monika Godlewska [email protected] tel.: 48 22 887 12 66, fax: 48 22 887 10 11

Page 5: PHPSolutions_04_2006_PL

Spis treści

www.phpsolmag.org4 PHP Solutions Nr 4/2006

Spis treści

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

NARZĘDZIAZend Framework 42Piotr SzarwasPojawienie się Zend Frameworka wzbudziłow świecie PHP wiele emocji. Zend, jedyna praw-dziwa firma od PHP, wypuściła – tak jak należa-ło – prawdziwy Framework dla PHP. Wszyscy oczekiwali po tym rozwiązaniu bardzo wiele i ... nie zawiedli się.

eZ Components w akcji, czyli galeria zdjęć krok po kroku 50Tobias SchlittKomponenty eZ stanowią w ogólnym ujęciu opartą na PHP platformę do budowy rozwiązań biznesowych. Stosowanie tej wysokiej klasy ko-lekcji niezależnych bloków budulcowych znacz-nie przyśpiesza proces tworzenia oprogramo-wania we wspomnianym języku, zmniejszając jednocześnie ryzyko załamania się projektu.

TECHNIKIDekorator: wzorzec projektowyna każdą bolączkę 62Paweł KozłowskiNazwa wzorca projektowego dekorator jest nie-co myląca, ponieważ sugeruje, że będziemy coś wzbogacać, dekorować czy upiększać. Nic bardziej błędnego! Omawiany wzorzec znajduje szerokie zastosowanie, niezależnie od tego, czy projektujemy warstwę dostępu do bazy danych, logikę biznesową lub kontroler MVC.

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

ZAPOWIEDZIW następnym numerze 82Zapowiedzi, artykułów, które planujemy do na-stępnego wydania naszego pisma.

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, T. Malinowski, P. Plewa, W. Andruszkiewicz, A. Polit

Pytania dotycząceprenumeratytel. (22) 887 14 44e-mail: [email protected] Wydawnictwo Sp. z o.o.dział prenumeratyul. Piaskowa 301-067 Warszawa

CDtel. (22) 887 14 44e-mail: [email protected] Wydawnictwo Sp. z o.o.Defekty CD/DVDul. Piaskowa 301-067 Warszawa

Zamówienia /Numery archiwalnetel. (22) 887 14 44e-mail: [email protected] on-line: www.buyitpress.com

Kontakt z redakcjąe-mail: [email protected] Wydawnictwo Sp. z o.o.Redakcja PHP Solutionsul. Piaskowa 301-067 Warszawa

Strona WWW/Forumstrona www: www.phpsolmag.orgTu znajdą Państwo informacjedotyczące aktualnych i przyszłychnumerów magazynu PHP Solutions.

Forum: www.phpsolmag.org/newforumZachęcamy do dyskusji na naszymforum. Czekamy na propozycjetematów, które chcieliby Państwoznaleźć w najbliższym numerze pisma. Zapraszamy także do wymianypoglądów z innymi fanami PHP.

Cena Prenumerata: 135 złPrzelew na konto nr:46 1440 1299 0000 0000 0391 8238 Nordea Bank Polska S.A.II Oddział w Warszawie

PHP Solutions jest wydawany przez Software-Wydawnictwo Sp. z o.o.

Dyrektor Wydawniczy: Jarosław SzumskiMarket Manager: Sylwia Tuśnio [email protected] Manager: Maciej Krawcewicz [email protected] prowadzący: Dariusz Pawłowski [email protected] : Krzysztof Sobolewski [email protected] współpracownicy: Paweł Kozłowski [email protected], Kierownik produkcji: Marta Kurpiewska [email protected] okładki: Agnieszka MarchockaSkład i łamanie: Sławomir Zadrożny [email protected]ł reklamy: [email protected]: Marzena Dmowska [email protected]ład: 6 000 egz.

Adres korespondencyjny: Software-Wydawnictwo Sp. z o.o., ul. Piaskowa 3, 01-067 Warszawa, Polskatel. +48 22 887 10 10, fax +48 22 887 10 11www.phpsolmag.org [email protected]

Dołączoną do magazynu płytę CD przetestowano programem AntiVirenKit firmy G DATA Software Sp. z o.o.

Redakcja dokłada wszelkich starań, by publikowane w piśmie i na towarzyszących mu nośnikach informacjei programy były poprawne, jednakże nie bierze odpowiedzialności za efekty wykorzystania ich; nie gwarantujetakż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 firmi zostały użyte wyłącznie w celach informacyjnych.

Redakcja używa systemu automatycznego składu Do tworzenia wykresów i diagramów wykorzystano program firmy

Osoby zainteresowane współpracą prosimy o kontakt: [email protected]

Druk: ArtDruk

Wysokość nakładu obejmuje również dodruki. Redakcja nie udziela pomocy technicznej w instalowaniui użytkowaniu programów zamieszczonych na płytach CD-ROM dostarczonych razem z pismem. Sprzedaż aktualnych lub archiwalnych numerów pisma po innej cenie niż wydrukowana na okładce– bez zgody wydawcy – jest działaniem na jego szkodę i skutkuje odpowiedzialnością sądową.

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

Page 6: PHPSolutions_04_2006_PL

Aktualności

PHP Solutions Nr 4/2006www.phpsolmag.org6

Najpopularniejsze frameworki dla PHPNa stronie http://www.phpit.net/article/ten-different-php-frameworks/ znajdziemy ze-stawienie dziesięciu najpopularniejszych frameworków dla PHP. Branych pod uwa-gę jest kilkanaście cech w tym m.in: wyko-rzystanie PHP5, MVC, ORM, wsparcie dla różnych baz danych czy AJAX-a. Z kolei na stronie http://www.phpwact.org/php/mvc_frameworks znajduje się zestawienie ok. 40 frameworków implementujących wzorzec MVC. Wybierajmy jednak z głową (i maga-zynem PHP Solutions).

Seagull 0.6.0RC2Pojawiła się nowa wersja frameworkaSeagull, który oferuje możliwości z pograni-cza zwykłego frameworka i CMS-a. Seagull po zainstalowaniu zawiera kilka praktycz-nych modułów, co oszczędza czas na pisa-nie tych komponentów samodzielnie.Framework jest kompatybilny z PHP4i PHP5, korzysta z pakietów PEAR-a i jest łatwo integrowany z innymi aplikacjami. Je-go dużym plusem jest silne wsparcie przez międzynarodową społeczności i dostępno-ści w wielu językach (nawet w chińskim). W najnowszej wersji m.in. naprawiono wie-le błędów, zapewniono pełne wsparcie dla PostgreSQL-a.http://seagullproject.org/

Praca dla programistów PHPPHP-freelancers.com to portal dla ludzi po-szukujących programistów do szybkiego wy-konania małych i dużych projektów w PHP oraz dla samych deweloperów PHP zainte-resowanych pracami dorywczymi – zlece-niami na wykonanie projektu lub jego czę-ś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 i wyświetlania różnic pomiędzy nimi moż-na otrzymać od 10 do 300 dolarów. Do pro-jektu zgłasza się z reguły kilku dewelope-rów oferując swoje ceny oraz czas realiza-cji zadania. http://www.php-freelancers.com/

Integracja Propela z Zend FrameworkNa stronie Zend Developer Zone pojawiły sie nowe ciekawe tutoriale i artykuły. Na uwagę zasługuje tekst opisujący integrację Prope-la (bardzo popularne narzędzie ORM) z Zend Frameworkiem. O Propelu pisaliśmy w nu-merze 5/2005, a o Zend Framework możecie przeczytać w obecnym wydaniu naszego ma-gazynu. Wśród innych materiałów na stronie znajdziemy też tutoriale o połączeniuFrameworka ze Smarty (Zend Framework nie ma póki co wbudowanego systemu szablo-nów) i eZ components.http://devzone.zend.com/node/view/id/184

phpDocumentor 1.3.0RC6Ukazała się nowa wersja najbardziej popular-nego oprogramowania do generowania doku-mentacji aplikacji na podstawie kodu PHP.Na temat phpDocumentora opublikowali-śmy obszerny artykuł w numerze 5/2004 (www.buyitpress.com). Wersja 1.3.x ma być ostatnią z pierwszej linii, bo już zapowie-dziano wcielenie 2.x. Najnowsza wersja w pełni wspiera PHP5, wykorzystuje PEAR:: XML_Beautifier do upiększania (formatowa-nie komentarzy, dodawania znaków końca li-nii itp.). kodu, koloruje kod HTML/XML czy pozwala na wygodniejszą pracę z grafikami.Licencja: GPLhttp://pear.php.net/package/PhpDocumentor/

Wybierz najlepszy CMS

Wybór odpowiedniego CMS-a do stworzenia portalu/witryny może

spędzić sen z powiek nie jednemu z nas, szczególnie, że jest w czym przebierać,a różnice i zastosowania poszczególnych aplikacji są znaczne. Jak sprawdzić, któ-ry system odpowiada naszym potrze-bom i będzie najlepszy do wykorzystania właśnie w naszym przypadku? Tego do-wiecie się w kilku ciekawych miejscachw Sieci.

Na stronie www.cmsmatrix.org znaj-dziecie nie tylko wykaz najpopularniej-szych systemów CMS wraz z ich szcze-gółowym opisem. Najciekawsze jestnarzędzie do generowania zestawień – porównań wybranych CMS-ów. Li-sta wyboru jest oszałamiająca i zawiera 570 pozycji!!! Wystarczy zaznaczyć od 2 do 10 produktów i wygenerować tabelkę,w której znajdzie się porównanie. Do ze-stawienia pod uwagę wzięto m.in.: wyma-gania systemowe, bezpieczeństwo, ła-twość użycia, wydajność, zarządzanie, elastyczność czy obecność aplikacji/mo-dułów wbudowanych, takich jak kalen-darz czy system do wysyłki newslettera.

Poza generowaniem porównań możemy też oceniać wybrane rozwiązania i dysku-tować na forum.

Szukając CMS-a warto zajrzeć rów-nież na www.opensourcecms.com. Au-torzy nie oferują wprawdzie żadnego po-równania systemów, ale za to znajdzie-my tam bardzo efektywny podział pro-duktów na kategorie (CMS-y typu porta-lowgo, blogi itd.) i świetne forum, na które trzeba zajrzeć przed wdrożeniem nowego (nieznanego) CMS-a.

Dla ciekawości dodamy, że już niedłu-go w naszym magazynie pojawi się test systemów CMS, który powstanie przy współpracy z cmsmatrix.org i php.pl. Już teraz gorąco polecamy!

http://www.cmsmatrix.org

WebGUI

WebGUI to CMS/framework do bu-dowania aplikacji klasy Enterpri-

se. Autorzy systemu chcieli stworzyć CMS-a z możliwie najprostszą obsługąi administracją, tak aby zbędny okazał się helpdesk IT. Program zawiera in-terfejs administracyjny z funkcją dra-g&drop, wbudowany edytor WYSWIG działający pod IE i Mozillą, przechowuje historie zmian treści serwisu, co umoż-liwia ich cofanie, jak również oferuje HELP w JavaScript, który osobom nie-znającym HTML-a pomoże odpowiednio sformatować dokument.

WebGUI mile zaskakuje pod wzglę-dem bezpieczeństwa: posiada certyfi-kat bezpieczeństwa (OSI Certified OpenSource Software), bezpieczne logowanie (SSL), system kopii zapasowych/wersji, co umożliwia łatwe przywrócenie po-przednio zapisanych treści oraz komer-cyjne wsparcie.

Aplikacja nie rozczaruje również sa-mych deweloperów: system jest modu-larny i bardzo łatwo rozszerzalny, pozwa-la na łatwe tworzenie wersji językowych czy nowych modułów. Wprowadzono też możliwość tworzenia indywidualnej nawi-

gacji w zbudowanym serwisie oraz przy-jazne URL-e.

WebGUI zawiera szereg gotowych do użycia modułów: newsy, artykuły, fo-ra, blogi, sondy, FAQ, galerie zdjęć, ka-lendarze czy nawet komponent agre-gujący i prezentujący oferty pracy. Sys-tem posiada również bardzo elastycz-ny sklep internetowy z wbudowanymi modułami płatności. Dostęp do różnych usług/produktów, które będziemy ofero-wać, może być sprzedawany np. w po-staci kodów dostępu na określony okres (np. tydzień).

Licencja: GPLhttp://www.webgui.org

Page 7: PHPSolutions_04_2006_PL

Aktualności

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

MobiLogMobiLog to opensourcowe narzędzie do mo-bloggingu (mobile weblogging), czyli zarzą-dzania zawartością witryny z poziomu urzą-dzenia przenośnego takiego jak telefon ko-mórkowy czy PDA. Wystarczy pod wskazany adres mailowy wysłać wiadomość, aby zaktu-alizować dane na stronie WWW. Do działania oprogramowanie potrzebuje: MySQL 4.1.1, Perl 5.8, PHP 4.2 oraz kilka dodatkowych mo-dułów Perla.Licencja: BSDhttp://www.accliptic.com/products/opensource/ml/

phcphc to kompilator, który konwertuje kod PHP na asemblera pod Linuksa. Może być uży-ty jako np. framework C++ do rozwijania róż-nych narzędzi (np. obfuskatory, narzędzia re-factoringu).phc oferuje także interfejs do modyfikacji kodu powstałego z PHP oraz jego konwersji z powro-tem do PHP.Licencja: BSDhttp://www.phpcompiler.org

Zend Framework wyżej niż Microsoft .NET FrameworkNa razie tylko w wyszukiwarce, ale co cieka-we w tej Microsoftu – msn.com :). Po wpisa-niu w wyszukiwarce słowa „framework” pro-dukt Zenda ukazywał się jako pierwszy,a framework Microsoftu jako drugi. Fakt ten uchwycił Endi Gutmans i opublikował zrzutz ekranu na swoim blogu.http://andigutmans.blogspot.com/

NuSphere PhpED 4.5To jeden z najlepszych IDE dla PHP. W nowej wersji udoskonalono podświetlanie składni dla PHP4, PHP5, XML, XHTML, HTML, CSS, Perl, Python, Javascript, ASP, SQL, C/C++i Smarty.Mamy też możliwość dynamicznego wyboru sposobu/szablonu podświetlania składni, któ-re możemy wcześniej zdefiniować. Dodano również wsparcie dla SQLite i Firebird (MySQL, MSSQL, Oracle i PostgreSQL wspierane są już od dawna).Licencja: komercyjna, 299$http://www.nusphere.com

PRADO 3.0.0Po dziesięciu miesiącach oczekiwania po-jawiła się nowa wersja frameworka PRADO (o frameworku tym pisaliśmy w numerze 5/2005). Przypomnijmy: jest to oparty na komponentach (component-based)i na zdarzeniach (event-driven) framework dla PHP5, który zwyciężył w konkursie or-ganizowanym przez firmę Zend ponad rok temu. W nowej wersji system został całko-wicie przepisany, a deweloperzy położy-li szczególny nacisk na wydajność, nieza-wodność oraz elastyczność. PRADO zo-stał wielokrotnie nazwany RAD-em dla pro-gramistów PHP5 i pod wieloma względami przypomina ASP.NET.Licencja: BSDhttp://www.pradosoft.com/

E-mail InjectionsWarto przyjrzeć się budowie formularzy kontaktowych w naszych aplikacjach i za-bezpieczyć przed wysłaniem spamuz naszego serwera WWW. Atak polega na dołączeniu dowolnych nagłówków do wysy-łanego maila, co pozwala np. na spamowa-nie. Aby się zabezpieczyć, wystarczy od-powiednio filtrować dane wprowadzane do formularza.http://securephp.damonkohler.com/index.php/Email_Injection

eZ publish Conference 2006

Magazyn PHP Solutions został głównym sponsorem konferencji

organizowanej przez firmę eZ systems, producenta takich znanych rozwiązań jak eZ publish i eZ components. Pierw-sza, tegoroczna edycja odbędzie sięw Norwegii w dniach 21-23 czerwca.

W ciągu trzech dni imprezy bę-dzie można posłuchać wykładów na te-mat m.in.: eZ components (Derick Re-thans, Tobias Schlitt), eZ publish (Bard Farstad), Open Source (Zak Greant),MySQL (Kristofer Petterson), OOPw PHP (Marcus Börger), testowania apli-kacji z wykorzystaniem PHPUnit3 (Seba-stian Bergman) czy zwiększania wydaj-ności aplikacji PHP (Ilia Alshanetsky).

Poza uczestnictwem w ciekawych wykładach, będzie również okazja do spotkania się z najlepszymi dewelope-rami i firmami związanymi z PHP z ca-łego świata.

http://ez.no/company/events/ez_publish_conference_2006

TYPO3 w wersji 4

TYPO3 to popularny CMS, który po-dobnie jak eZ publish zaliczany

jest do oprogramowania klasy Enter-prise. W nowej wersji systemu pojawi-ło się sporo zmian. CMS pracuje teraz na PHP5 i posiada cały szereg nowych funkcjonalności.

Panel administracyjny został całko-wicie przebudowany, dzięki czemu za-rządzanie serwisem będzie dużo ła-twiejsze. Standardowo do dyspozycji mamy edytory WYSWIG (HTMLArea), opcję drag and drop do przenosze-nia obiektów pomiędzy okienkami czy możliwość tworzenia tzw. workspaces, do pracy z grupą obiektów. Tworzenie workspaces pozwala na edycję i zmia-ny nie tylko wersji live, ale i wersji ro-boczych, które mogą być potem do-wolnie wykorzystane.

Grupy użytkowników mogą admi-nistrować innymi grupami, a zarzą-dzanie dostępem do poszczególnych części serwisu jest teraz dużo bardziej elastyczne.

Dodano tablicę z odniesieniami,w której zapisane są nazwy linków oraz obiekty na które wskazują. To po-

zwala na kontrolę i np. uniknięcie usu-nięcia podstrony, do której linkujemyz innego miejsca.

Polepszono silnik do wyszukiwa-nia danych, dodano wsparcie dla m.in. Oracle'a i PostgreSQL-a oraz wprowa-dzono nowy oparty o XML system sza-blonów – TemplaVoila. Dokonano rów-nież refaktoryzacji kodu.

W ramach nowości warto jeszcze wymienić możliwość generowania gra-fiki za pomocą rozszerzenia GIFBU-ILDER TypoScript, przechowywaniehistorii zmian czy pewne ulepszeniaw zakresie bezpieczeństwa.

Dużym plusem TYPO3 była za-wsze 100% kompatybilność z po-przednimi wersjami. Tym razem prze-chodząc do nowej wersji niestety nie obejdziemy się bez małych modyfi-kacji w zakresie CSS (content rende-ring). Dobra wiadomość jest taka, że można włączyć tryb kompatybilności z wersją 3.8 i nie przejmować się ty-mi zmianami.

Licencja: GPLhttp://typo3.com/

Page 8: PHPSolutions_04_2006_PL

Aktualności

PHP Solutions Nr 4/2006www.phpsolmag.org8

Stypendium na wakacje – Google sponsoruje projekty Open SourceGoogle Summer of Code to program oferu-jący stypendia studentom, którzy zdecydują się na wykonywanie w czasie wakacji pro-jektu pod opieką organizacji np. php.net. Drupal czy Debian. Można otrzymać 5000$, z czego 4500$ dostaje student,a 500$ opiekun projektu. Lista opiekunów na edycję 2006 jest już zamknięta i liczy kilkadziesiąt pozycji. W dalszym ciągu jed-nak mogą zgłaszać się studenci. php.net już zgłosiło kilka propozycji np. konwerter z PHP4 na PHP5 (PHP6) (http://php.net/ideas.php). W ramach projektu można roz-wijać własne (rozpoczęte) projekty. Na stronie Google znajdują się wyczerpujące informacje mówiące o tym jak przystąpić do programu i jakie są jego zasady.http://code.google.com/soc/

php.MVCphp.MVC to w pełni obiektowy framework MVC. Korzysta z Pear::DB Database Abs-traction Layer, jest wyjątkowo bezpieczny (aplikacje posiadają jeden tzw. entrypoint). Oferuje możliwość łatwego zarzą-dzania sekwencją akcji i statycznymi tre-ściami. Każda aplikacja posiada XML-owy plik konfiguracyjny. Framework implemen-tuje klasę LookupDispatchAction(), która mapuje butony z formularzy HTML na odpowiednie metody logiki biznesowej (co umożliwia ich wykonywanie). Projektpowstał na bazie znanego ze świata Javy frameworka Struts, co stanowi jego dosko-nałą rekomendację.Licencja: LGPLhttp://www.phpmvc.net/

Zend Guard 4Zend Guard to oprogramowanie, któ-re zapewnia kompleksową ochronę ko-du źródłowego aplikacji PHP, które chcemy rozpowszechniać/sprzedawać. W skład pa-kietu wchodzi enkoder (kodowanie źródła), obfuskator (zaciemnianie kodu) oraz mene-dżer licencji pozwalający np. na ograniczanie czasu działania oprogramowania czy bloko-wanie adresów IP. Dodatkowo pakiet zwięk-sza wydajność aplikacji dzięki zastosowaniu nowych technik optymalizacji kodu.Licencja: komercyjna, $995http://www.zend.com/products/zend_guard

Maguma za darmoMaguma znana jest z dwóch komercyjnych, profesjonalnych i silnie rozwojowych produk-tów: Maguma Workbench orazMaguma Studio. Na sourceforge.net(http://sourceforge.net/projects/openstudio) można pobrać darmową wersję Maguma Open Studio, która różni się od Magumy Stu-dio brakiem menedżera CVS oraz pojawiają-cym się okienkiem z informacjami o licencji. Niestety, tak jak jej komercyjny odpowiednik, IDE dostępne jest jedynie pod Windows.Licencja: MPL 1.1 (Mozilla Public License)http://www.maguma.comhttp://sourceforge.net/projects/openstudio

Zend/PHPConference&Expo 2006W dniach 29 października – 2 listopada 2006 w San Jose, USA odbędzie się kolejna edycja konferencji PHP organizowanej przez firmę Zend. Częścią imprezy będą również warsz-taty oraz szkolenia przygotowujące do certyfi-katu Zend Certified Engeenier.http://zendcon06.kbconferences.com/

Joomla

Joomla to system powstały na bazie Mambo – bardzo popularnego, ale

też uważanego przez wielu za NIEprofe-sjonalnego i mało bezpiecznego CMS-a. Mambo posiada ogromną społeczność i jest używany przez tysiące serwisów WWW. Jest bardzo łatwy we wdrożeniu oraz utrzymaniu. Bez problemu można pobrać gotowy, nowy szablon graficzny czy wersję językową systemu. Wraz z wy-daniem 4.5.3, w sierpniu 2005 roku, część deweloperów odeszła z Mambo, by roz-począć nowy projekt – tak powstał Joom-la. Nowe Mambo wygląda na projekt sil-nie rozwojowy – powstają już wersje be-ta, które oferują tzw. Multisite CMS (jedna instalacja pod wiele domen) czy wsparcie dla 11 serwerów baz danych. Szczególny nacisk położono na kwestie bezpieczeń-stwa naprawiając wiele poważnych błę-dów w aplikacji (zlikwidowano m.in. podat-ności na SQL Injections w module Sondi podczas aktywacji użytkownika oraz Email Injections w funkcji „Email from Friend”). Powstało nawet centrum dla de-

weloperów Joomla – Forge.Joomla.org. Dokonuje się częściowej refaktoryzacji ko-du, polepsza dokumentację, tworzy sekcje online dla testerów – wszystko po to, aby zapewnić lepszy rozwój projektu. Road-map na stronie Joomla mówi o wielu po-ważnych zmianach, które mają nastąpićw niedalekiej przyszłości: dalsza praca nad funkcją Multisite, system kontroli wer-sji, wirtualny system plików, menedżer ak-tualizacji wersji czy wsparcie dla MySQL5.

Licencja: GPLhttp://www.joomla.orghttp://www.mamboserver.org

Zarządzanie projektami PHP w PHP

Panowanie nad dużą liczbą projektów i przypisanych im zadań to nie la-

da wyzwanie dla kierownika zespołu lubsamego programisty. W PHP zosta-ło stworzonych kilka dobrych systemów, które usprawniają zarządzanie projektamii zadaniami. Mamy do dyspozycji progra-my zaawansowane takie jak dotProject(http://www.dotproject.net/) czy phpCol-lab (http://www.php-collab.org/), któ-re oferują bardzo wiele opcji: dzielenie projektów na etapy (wykres Gantta), do-dawanie użytkowników z rożnymi upraw-nieniami, fora dyskusyjne, kalendarze czy menedżery plików. Wymienione pro-gramy można łatwo rozwijać budując własne moduły, zmieniając szablony czy dodając kolejną wersję językową. Sam dotProject oferuje wsparcie dla kilkudzie-sięciu języków.

Jeśli jednak potrzebujemy czegoś znacznie prostszego – możliwości doda-wania projektu (wraz z kategoriami) oraz przypisywania im zadań – to dobrym rozwiązaniem może być lekki program o nazwie Getting Things Done (http://gtd-php.sourceforge.net/). Do działaniapotrzebuje PHP4/5 oraz MySQL w wer-sji 4.x. Korzystanie z programu jest bardzo proste i intuicyjne. Do każde-

go dodanego projektu dodajemy zada-nie wraz z terminem zakończenia i opi-sem. Do dyspozycji mam także m.in. li-sty, checklisty czy możliwość przeglą-dania raportów i podsumowań. Pro-jekt GTD-PHP jest opensourcową wer-sją komercyjnego oprogramowania GTD(http://www.davidco.com/).

Przed ostatecznym wyborem syste-mu zachęcamy do wypróbowania wersji DEMO obecnych na stronach poszcze-gólnych projektów.

Licencja: http://gtd-php.sourceforge.net/gtd/about.phphttp://www.dotproject.net/http://www.php-collab.org/http://gtd-php.sourceforge.net

Page 9: PHPSolutions_04_2006_PL
Page 10: PHPSolutions_04_2006_PL

Opis CD

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

Tę wersję PHP Solutions Live zbudo-waliśmy, opierając się o dystrybucję

Aurox i skrypty automatycznej generacji (www.aurox.org/pl/live). Narzędzia dystry-bucji, które nie znajdują się na dołączonej do pisma płycie, instalowane są z repozy-torium Auroxa za pomocą programu yum.

W porównaniu z poprzednią wersją PHP Solutions Live zasadniczą zmianą jest przejście z Fluxboksa na środowisko

Rysunek 1. Pulpit PHP Solutions Live ze skrótami do aplikacji.

Materiały dodatkowe zostały umiesz-czone w następujących katalogach:

l Tutoriale video – trzecia część multi-medialnego kursu, zatytułowana user signup,

l Aplikacje – hity numeru, w bieżącym numerze: CaRP Koi 3.5.5 – konwerter RSS do HTML oraz Komodo IDE.

l e-books – książki i inne dokumentyw formacie PDF

Program CaRP Koi 3.5.5 firmy GeckoTri-be dostępny jest w pełnej wersji komer-cyjnej w licencji dla jednego Webmaste-ra. Jest to w pełni funkcjonalna wersja, której można używać w komercyjnychi prywatnych zastosowaniach, a jedynym ograniczeniem jest to, by każdej kopii z pisma używał tylko jeden Webmaster. Zalecamy zapoznanie się z warunkami

Materiały dodatkowe

PHP Solutions Live

licencyjnymi, które są dokładnie sprecy-zowane w pliku Readme, dostarczanym wraz z dystrybucją aplikacji. W tym sa-mym pliku opisano szczegółowo różni-ce pomiędzy wersją 3.5.5 oraz najnow-szymi, dostępnymi na stronie producen-ta – firmy Gecko Tribe. CaRP Koi 3.5.5 jest napisany w PHP dzięki czemu łatwo można go wykorzystać na serwerze. We wspomnianym pliku Readme, znajduje się także opis funkcjonalności programu, instrukcja instalacji oraz zbiór odpowie-dzi na najczęściej zadawane przez użyt-kowników pytania.

Druga komercyjna aplikacja za-mieszczona na płycie to Komodo 3.5 Professional, czyli profesjonalne śro-dowisko IDE dla programistów. Na CD zamieściliśmy 30 – dniowy trial, któ-ry można uruchomić przy pomocy kodu dostępnego pod adresem: http://www.

activestate.com/Products/Download/Download.plex?id=Komodo

Dla tych czytelników PHP Solutions, którzy zainteresowani są pełną wer-sją programu, firma ActiveState oferuje zniżkę w wysokości 100$ (standardowa cena programu to 295$). Aby skorzy-stać ze zniżki należy do 21 lipca;

l odwiedzić stronęhttp://www.ActiveState.com/PHPSolutions

l wybrać opcję „Buy”l wpisać kod „PHPSOL”

W przypadku przeglądania płyty z pozio-mu uruchomionego PHP Solutions Live,a nie systemu operacyjnego zainstalowa-nego na dysku komputera, wymienione aplikacje i materiały dostępne są z podka-talogu /mnt/cdrom.

graficzne KDE oraz dodanie wielu przy-datnych programistom aplikacji. Teraz, oprócz kompletnego środowiska LAMP (Linux, Apache, MySQL, PHP) w syste-mie gotowe do użycia są środowiska pro-gramistyczne, takie jak: Eclipse, Nvu, Qu-anta, BlueFish oraz wiele innych charak-terystycznych dla KDE i Linuksa narzędzi.

PHP Solutions Live – to w pełni funk-cjonalny system operacyjny, który znajdu-

je się na płycie dołączonej do pisma. Jest to bootowalna dystrybucja Auroxa, zawie-rająca bogaty zestaw przydatnych pro-gramistom i webmasterom narzędzi, do-kumentację, tutoriale i materiały dodat-kowe do artykułów. System został przy-gotowany w taki sposób, by zaraz po uru-chomieniu systemu można było testować i tworzyć nowe skrypty.

Aby zacząć pracę z PHP Solutions Li-ve, wystarczy uruchomić komputer z CD. Po uruchomieniu systemu zostaniemy automatycznie zalogowani w systemie,a po krótkiej chwili powinna uruchomić się przeglądarka Mozilla Firefox wraz z Menu przedstawiającym zawartość płyty zwią-zaną z bieżącym numerem.

Rysunek 2. Ekran powitalny PHP Solutions Live z opcjami uruchomienio-wymi

Page 11: PHPSolutions_04_2006_PL

Na CDNa CD

HITPełna wersja komercyjnego programu CaRP Koi firmy Gecko Tribe o wartości około $20

Nowy kurs multimedialnyuser signup

Aplikacje PHP CompilerKomodo Professional 3.5 PHP IDE

Wsz

ystk

ie lis

tingi

z a

rtyku

łów

zos

tały

zam

iesz

czon

ena

nas

zej s

troni

e in

tern

etow

ej p

od a

dres

em www.phpsolmag.org/pl

3 nowe książki elektroniczneAuditing your web site securityAn Introduction to Web Services Enabled with PHPPHP5 Power Programming

Wsz

ystk

ie lis

tingi

z a

rtyku

łów

zos

tały

zam

iesz

czon

ena

nas

zej s

troni

e in

tern

etow

ej p

od a

dres

em www.phpsolmag.org/pl

Przetestuj dowolne skryptybez instalacji!

Narzędzia w dystrybucji Kompletne środowisko LAMP

Opensource’owe IDE dla developerów: Nvu, Bluefish, Quanta, Eclipsei wiele innych aplikacji

Page 12: PHPSolutions_04_2006_PL

www.phpsolmag.org12

Wywiad

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

Wywiad

PHP Solutions Nr 4/2006

Wywiad z Dmitrim Stogovem – programistą Zend i twórcą Turck MMcache

Dariusz Pawłowski: Jak zaczęła się Two-ja przygoda z PHP?

Dmitry: To było w roku 2000, kiedy pracowałem w Sankt Petersburgu w firmie Turck Software (wbrew nazwie firmie nie-mieckiej, nie tureckiej). Miałem już wtedy jakieś 10 lat doświadczenia w programo-waniu, ale nigdy nie programowałem dla Internetu – nie znałem języków HTML, Ja-vaScript czy Perl, a o PHP nawet nie sły-szałem. Gdy pewnego dnia otrzymałem zadanie napisania aplikacji internetowej, zacząłem próbować różnych możliwych technologii. Spróbowałem JSP, skryp-tów Perla, jakiegoś komercyjnego CMS-a opartego na TCL, aż w końcu stanęło na PHP (wtedy było to PHP3).

Moją pierwszą aplikacją w PHP był system śledzenia czasu pracy. Całość mieściła się z 500 wierszach kodu, chodzi-ła na Linuksie i używała bazy IBM DB2 do składowania danych. W ciągu dwóch lat w Turck Software opracowaliśmy kilka apli-kacji internetowych, z których najciekaw-

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

szy technicznie był projekt www.guestbo-oks4all.com. Aplikacja pozwalała na two-rzenie własnych ksiąg gości dostosowa-nych do potrzeb konkretnej witryny bez konieczności programowania.

DP: Jesteś autorem Turck MMCache. Skąd wziął się pomysł projektu i jakie były jego pierwotne założenia?

Dmitry: Potrzeba stworzenia MMCa-che pojawiła się wraz z rosnącą popular-nością serwisu ksiąg gości, kiedy to zaczy-naliśmy nawet rozważać możliwość wpro-wadzenia klastra serwerów. Na szczęście znaleźliśmy czysto programistyczny spo-sób wydajnego obsłużenia rosnącej licz-by wizyt: składowanie skompilowanychkodów stron PHP. Zaczęliśmy szukać ist-niejących, darmowych rozwiązań. Postano-wiliśmy odrzucić rozwiązania o zamkniętym kodzie źródłowym, więc pozostało nam je-dyne wówczas oprogramowanie open sour-ce tego typu, czyli Afterburner. Niestety, da-wał on zaledwie połowę przyrostu wydaj-ności produktów Zend i ionCube, nie uży-

wał pamięci współdzielonej i nie miał żad-nego optymalizatora. Po dłuższym namyślepostanowiłem więc stworzyć projekt MMCa-che na wewnętrzne potrzeby naszego ser-wisu ksiąg gości. Do moich głównych zain-teresowań programistycznych należały bu-dowa kompilatorów i optymalizacja kodu, więc MMCache powstawał szybko i w krót-kim czasie zaczął dawać wyniki porówny-walne z produktami komercyjnymi. Uzna-liśmy, że nierozsądnie byłoby trzymać tak dobre oprogramowanie wyłącznie do użyt-ku wewnętrznego, a zarazem nie chcieli-śmy go sprzedawać, więc postanowiliśmy udostępnić MMCache na licencji GPL.

Do końca mojej pracy w Turck Softwa-re utrzymywałem projekt MMCache, a je-go popularność gwałtownie rosła. Pod ko-niec 2003 r. właściciel Turck Software po-stanowił zakończyć działalność w branży oprogramowania, a dla mnie nadeszła po-ra rozejrzenia się za nową pracą.

DP: Obecnie jesteś szefem rosyjskie-go zespołu firmy Zend. Jak trafiłeś do

Page 13: PHPSolutions_04_2006_PL

www.phpsolmag.org12

Wywiad

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

Wywiad

PHP Solutions Nr 4/2006

Zenda? Czy to prawda, że Zend „kupił” Cię, gdyż Turck MMCache stanowił kon-kurencję dla własnych produktów Zenda?

Dmitry: I tak, i nie. Do pracy w Zen-dzie zgłosiłem się sam. Oczywiście wie-działem, że znają mnie jako twórcę MMCache, więc był to dla mnie dodat-kowy atut w negocjacjach, zwłaszcza że wtedy nie prowadzili jeszcze działalności na terenie Rosji. Wiedziałem też, że bę-dę musiał zakończyć prace nad projek-tem MMCache, ale i tak nie widziałem już dla niego przyszłości – Zend Enginew PHP5 zmieniał się zbyt szybko, więc dalsze prace nad MMCache polegałyby raczej na ciągłej adaptacji do zmian niż na faktycznych pracach rozwojowych. Uzna-łem więc, że więcej dobrego zdziałam pra-cując nad samym Zend Engine.

Po kilku miesiącach pracy w Zendzie otrzymałem taką okazję i zająłem się wpro-wadzaniem ulepszeń modułu wykonaw-czego w PHP-5.1 dających niemal dwu-krotnie szybsze wykonywanie surowegokodu PHP niż w przypadku starszych wersji. Oczywiście nie ja sam wymyśliłem wszyst-kie usprawnienia, ale ich implementacja jest moim dziełem. Jestem więc bardzo zado-wolony z pracy w Zendzie i mam nadzieję, że firma jest równie zadowolona ze mnie.

Pod koniec 2004 r. powstał projekt Zend Core, którego część Zend postano-wił realizować w Rosji. Tak powstał rosyjski zespół Zend, którego szefem zostałem ja.

DP: Nad czym obecnie pracujesz dla Zenda?

Dmitry: Wszystkich tajemnic nie mo-gę ujawnić, ale mniej więcej połowę cza-su zajmuje mi praca nad PHP6 (obsłu-ga Unikodu) i wprowadzanie ulepszeńi poprawek w istniejących wersjach PHPi w moich rozszerzeniach. Oczywiście uczestniczę też w komercyjnych projek-tach Zenda. W przeszłości miałem swój udział w tworzeniu rodziny produktów Zend Enabler (nie WinEnabler, ale na przykład SunOne Enabler i Shared Ho-sting Enabler), byłem przez jakiś czas szefem technicznym projektu Zend Core i wprowadziłem nieco ulepszeń w projek-tach Zend Optimizer i Zend Accelerator.

DP: Jak wygląda Twoja codzienna praca? Mieszkasz i pracujesz w Rosji, więc jak kontaktujesz się z Zendem? Jak często masz spotkania i wyjazdy?

Dmitry: Pracuję w domu, w Sankt Pe-tersburgu. To piękne i bardzo przyjemne

miasto (może z wyjątkiem mrozu zimą, brudu na wiosnę, komarów latem i desz-czu na jesieni) i nie chciałbym mieszkać nigdzie indziej.

Najczęściej zajmuję się zadaniami dłu-goterminowymi, więc nierzadko nie kon-taktuję się z Zendem nawet przez kilkatygodni. Gdy kontakt jest konieczny, poro-zumiewamy się za pośrednictwem pocz-ty elektronicznej, MSN lub Skype’a. Głów-ne biura Zenda znajdują się Izraelu i USA, więc nawet zwykli pracownicy firmy komu-nikują się przede wszystkim tymi drogami.

Zend jest główną siłą napędową roz-woju PHP, ale w końcu nie tworzy gow pojedynkę. Często rozmawiam ze świetnymi programistami należącymi do społeczności twóców PHP. Z niektórymispotkałem się w Paryżu na ostatnim PHP Developers Meeting, ale często nic nie wiem o osobie na drugim końcu łącza (nie wiem nawet, czy to on, czy ona).

Zespół programistów rosyjskich tow dużej mierze moi starzy znajomi (niektó-rzy również z Turck Software). Z począt-ku spotykaliśmy się raz w tygodniu, ale po doszlifowaniu procesu prac nad pro-jektami jesteśmy już w stanie rozwiązy-wać większość trudności bez konieczno-ści spotykania się. Obecnie spotykamy się więc w czasie wolnym, a nie w pracy.

Uruchomienie nowego projektu wyma-ga niekiedy wizyt w biurze Zenda w Izra-elu. W styczniu tego roku wszystkich pra-cowników zaproszono do Eilat na obcho-dy szóstych urodzin firmy.

DP: Co Twoim zdaniem będzie naj-bardziej potrzebne, by PHP osiągnąłstatus platformy klasy Enterprise z praw-dziwego zdarzenia, porównywalnej na przykład z .NET?

Dmitry: PHP nie da się bezpośred-nio porównać ze środowiskami w rodza-ju .NET czy J2EE. PHP to po prostu język programowania specjalizowany dla aplika-cji internetowych, więc bardziej na miej-scu byłoby porównywanie go z Java Se-rver Pages czy ASP.NET, i moim zdaniemw tym towarzystwie PHP jest najlepsze.

Platformę klasy Enterprise powinny jednak cechować takie zalety, jak skalo-walność, niezawodność, łatwość utrzyma-nia, łatwość i szybkość tworzenia aplikacji ze sprawdzonych komponentów, dostęp-ność licznych bibliotek i narzędzi wspoma-gających programowanie, solidne wspar-cie techniczne i tym podobne. Zend Tech-

nologies i inne firmy przez cały czas mają te cechy na uwadze i pracują nad stopnio-wym zbliżaniem PHP do statusu rozwiąza-nia klasy Enterprise.

Jedynym istotnym pojęciem, jakiego brakuje w samym języku PHP, jest coś w rodzaju modułu lub pakietu, coś co po-zwoliłoby projektować i tworzyć rozbudo-wane aplikacje bez konieczności zajmo-wania się rozwiązywaniem konfliktów po-między poszczególnymi podsystemami.

DP: Dlaczego Twoim zdaniem tak wie-le firm unika PHP, wybierając na przykład ASP.NET, JSP lub J2EE? Czy znasz jakiś bank internetowy wykorzystujący PHP ja-ko główną technologię?

Dmitry: Tu się nie zgodzę. Wiele firm korzysta z PHP, ale używa go przede wszystkim zgodnie z jego pierwotnym przeznaczeniem, czyli jako technologii tworzenia interfejsu WWW. Mniejszym fir-mom nierzadko wystarcza samo PHP, ale większe przedsiębiorstwa na ogół mają bardziej rozbudowane systemy informa-tyczne, których poszczególne elementy korzystają z bardziej odpowiednich tech-nologii i języków. Nie sądzę, by PHP miał kiedykolwiek nadawać się do absolutnie wszystkich zadań.

Co do banków internetowych, to na pewno wiem, że takie istnieją. Na przy-kład Dresdner Bank korzysta z aplika-cji PHP do dostarczania klientom usług finansowych i informacji. Z grona użyt-kowników PHP mogę też wymienić wieleznanych firm: Hewlett-Packard, Boeing, Lufthansa, Disney Online, Yahoo!, Lycos, Sprint, T-Mobile, Orange, Wall Street On-line, Siemens...

Dlaczego tak wiele firm nadal używa JSP, ASP czy nawet CGI, Perla, TCL i C++ do generowania treści WWW? Nie wiem, może nie chcą mieszać różnych techno-logii lub dopiero zaczęli rozważać PHP :). Pewnie PHP jest jeszcze za młode.

DP: Jakie masz plany na przyszłość?Dmitry: Podczas pracy nie mam cza-

su na rozmyślanie nad nowymi pomysła-mi. Większość moich wysiłków skupia się obecnie na PHP6 (obsługa Unikodu, ulepszenia języka, dodatkowa optymali-zacja). Gdy zacznie się zbliżać premiera PHP6, wtedy wraz z całą społecznością twórców PHP zaczniemy zapewne zbie-rać nowe pomysły, które być może trafią do PHP7.

DP: Dziękuję za rozmowę. n

Page 14: PHPSolutions_04_2006_PL

www.phpsolmag.org14

Początki

PHP Solutions Nr 4/2006

IRCBot – programowanie obiektowe

www.phpsolmag.org 15

Początki

PHP Solutions Nr 4/2006

Wraz z nadejściem PHP5 otrzy-maliśmy w końcu możliwość pisania programów obiekto-

wych z prawdziwego zdarzenia. Co wię-cej, ta wersja PHP jest oferowana przez coraz większą liczbę providerów, co za-chęca coraz większą liczbę programistów do korzystania z niej w swoich projektach.

Warto jednak pamiętać, że progra-mowanie obiektowe wiąże się ze zmia-ną sposobu myślenia o aplikacji. Terazw mniejszym stopniu myślimy o funkcjach (zwanych w klasach metodami), czyli ze-stawie instrukcji do wykonania konkret-nych zadań, a w coraz większym o obiek-tach modelujących tworzony system.

Czym jest programowanie obiektowe?O programowaniu obiektowym już pisali-śmy na naszych łamach, choćby w arty-kułach Erika Zoltana „Po co nam PHP5”, „Medoty magiczne w PHP5”, zamiesz-czonych w numerze 5/2005. Tym z Was,

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

którzy ich nie czytali, przybliżamy w skró-cie ideę obiektowości. Jej podstawą jest obiekt – odrębny, zamknięty fragment ko-du, zawierający metody (czyli funkcje) mo-gące przeprowadzić określone czynności oraz pola (czyli zmienne) zwane też atry-butami lub cechami, które przechowują dane (np. łańcuchowe, liczbowe czy in-ne obiekty). To, że obiekt jest zamknięty, uniemożliwia przypadkowe interakcje je-go metod i pól z innymi elementami pro-gramu (np. innymi obiektami), pozwalając jednocześnie wykorzystać obiekt w dowol-nym miejscu aplikacji (dając tym samym

Programowanie obiektowew PHP na przykładzie IRCBotaMarcin Staniszczak

W SIECI

l http://www.faqs.org/rfcs/rfc2812.html – RFC 2812 opisujące IRC

l http://www.cybernexus.biz/irc/default.htm – informacje o IRC w przystępnej formie

l http://www.phppatterns.com – strona o programowaniu obiektowym i wzorcach pro-jektowych w PHP

l http://www.php.net/manual/en/language.oop5.php – ofi-cjalny manual PHP, rozdział nt. obiektowości w PHP5

l http://en.wikipedia.org/wiki/Anti-pattern – antywzorce, czyli jak NIE należy progra-mować

Stopień trudności: lll

Co należy wiedzieć...Należy znać podstawy programowania w PHP. Przydatna będzie również znajo-mość protokołu i usług IRC.

Co obiecujemy...Pokażemy, jak stworzyć bota IRC-owe-go, kładąc szczególny nacisk na demon-strację możliwości obiektowych PHP5.

Page 15: PHPSolutions_04_2006_PL

www.phpsolmag.org14

Początki

PHP Solutions Nr 4/2006

IRCBot – programowanie obiektowe

www.phpsolmag.org 15

Początki

PHP Solutions Nr 4/2006

dużą elastyczność) i nazywa się enkapsu-lacja, którą omówimy później.

Każdy obiekt należy do jakiejś klasy i jest jej instancją (czyli egzemplarzem), przykładowo obiekt lampkaPrzyMoimŁóżku może należeć do klasy lampkaNocna. Sko-jarzenia ze światem materialnym nasuwa-ją się same: dzięki obiektom łatwo zasy-mulujemy przedmioty i zjawiska występu-jące w naszej rzeczywistości. Modelowa-nie rzeczywistości zaczynamy od wdraża-nia głównych cech, a następnie przecho-dzimy do kolejnych detali.

Kontynuujmy nasz przykład z lampką nocną. Możemy utworzyć klasę o nazwie lampkaNocna, która będzie zawierała me-tody zgaś() i zapal(). Pominiemy mniej istotne szczegóły, takie jak np. kolor czy kształt obudowy lampki. Następnie, chcąc umieścić lampkę nocną w naszym progra-mie, po prostu utworzymy instancję klasy lampkaNocna.

Co jednak będzie, jeżeli zechcemy za-symulować również lampę biurową, lam-pę wiszącą czy latarnię uliczną? Czy bę-dziemy za każdym razem tworzyć no-wą klasę od podstaw (np. lampaWiszącai latarniaUliczna)? Mijałoby się to z sen-sem, gdyż:

• część elementów tych klas (jak meto-dy zgaś() i zapal()) będzie się pokry-wała,

• jeśli wymodelujesz sobie człowieka (lub robota) obsługującego lampkę, będziesz musiał implementować mu obsługę każdej lampki z osobna.

Lepiej więc byłoby zacząć od utworzenia bardziej ogólnej klasy lampka, będącej ba-zą dla wszelkiego rodzaju dziedziczących po niej lamp, lampek, reflektorów, latarń czy latarek kieszonkowych. Ta podstawo-wa lampa powinna zawierać wyłącznie najbardziej ogólne pola i metody: te, które

wystąpią we wszystkich klasach pochod-nych. W przypadku lampy będą to metody zapal() oraz zgaś(). Moglibyśmy dodać do niej również pole żarówka, ale co zrobić z lampami, które zawierają np. świetlów-kę? Rozsądnym wyjściem byłoby utworze-nie drugiej klasy, np. źródłoŚwiatła, z któ-rej będzie korzystać klasa lampka i która również będzie posiadała klasy pochodne.

Dodane właśnie metody zapal()

i zgaś() istnieją wewnątrz klasy lampka. Inna, całkowicie odrębna klasa, np. znicz, mogłaby mieć metody o takich samych nazwach i nie kolidowałyby onew żaden sposób z metodami lampki. To samo dotyczy pól. Dostęp do nich jest możliwy jedynie przy podaniu nazwy kla-sy (lub jej obiektu), w której zostały zde-finiowane. To jest właśnie enkapsulacja (lub hermetyzacja): zamknięcie metodi pól wewnątrz klasy. Bardzo ułatwia ona tworzenie oprogramowania, szczegól-nie dużych projektów (wystarczy sobie wyobrazić jakikolwiek interfejs graficz-ny, w którym najmniejszy przycisk ma kil-kadziesiąt własnych cech i funkcji) i jest

jedną z najmocniejszych zalet programo-wania obiektowego.

Teraz bazując na klasie lampka mo-żemy utworzyć klasy pochodne (dziedzi-czące): lampkaNocna oraz lampkaBiurowa. Zwróćmy uwagę, jak bardzo jest to wy-godne. Możemy teraz zasymulować jed-nego człowieka (klasa człowiek) potra-fiącego obsługiwać wszystkie utworzone przez nas lampki. Co więcej, podczas pi-sania klasy człowiek nie musimy nawet wiedzieć, jakie lampki będzie on obsługi-wał, ani ile ich będzie. Istotne jest tylko, aby dziedziczyły one po klasie lampka.

Niestety, nie jest powiedziane, że nasz człowiek będzie potrafił w pełni wy-korzystać każdą nową lampkę: klasa po-chodna może bowiem rozszerzać li-stę metod i pól o własne, o których nasz człowiek nie będzie nic wiedział (pamię-tajmy, że zna on jedynie klasę lampka). Zobacz Rysunek 1. Prezentujemy tam klasy wszystkich lampek włącznie z no-wą, którą jest klasa LampkaPrzyciemniana, symulująca lampkę z regulacją jasno-ści (sciemniaczem). Obok standardo-wych metod zapal() oraz zgaś() posia-da ona także metody rozjaśnij() oraz sciemnij(). Nasz człowiek potrafiący ob-służyć obiekty klasy lampka, po której dziedziczy nasza lampkaPrzyciemniana, będzie potrafił ją zapalić oraz zgasić, ale nie będzie umiał jej przyciemnić czy rozja-śnić. Niestety, nie można mieć wszytego. Istnieją co prawda sposoby obejścia tego problemu, nie są one jednak tak elastycz-ne i nie są w pełni zgodne z podejściem obiektowym.

Rysunek 1. Klasa lampka i jej klasy pochodne

Programowanie obiektowe – historia i teraźniejszośćProgramowanie obiektowe jest już dojrzałą metodologią projektowania oprogramowa-nia komputerowego. Stworzona już w 1967 roku Simula posiadała pewne cechy języka obiektowego, zaś w latach 70. ubiegłego wieku Xerox pracował nad językiem Smalltalk – do dziś niedoścignionego wzorca w dziedzinie obiektowości. Z bardziej znanych, często stosowanych języków wystarczy wymienić C++ czy Javę. Przed pojawieniem się PHP5, w PHP było znacznie gorzej: teoretycznie już w PHP4 można było korzystać z pewnych technik obiektowych, ale było to niewydajne i zawodne. Prawdziwy, rewolucyjny przełom nastąpił wraz z nadejściem PHP5, która oferuje nowy, solidny silnik obiektowy oraz wie-le użytecznych cech, których poprzednio brakowało, a które czynią z PHP język obiektowyz prawdziwego zdarzenia.

Page 16: PHPSolutions_04_2006_PL

IRCBot – programowanie obiektowe

www.phpsolmag.org16

Początki

PHP Solutions Nr 4/2006

Podsumowując, najistotniejsze cechy programowania obiektowego to:

• Abstrakcja – oznacza, że każda kla-sa w programie opisuje pewien model rzeczywistości, będąc najczęściej swo-istą abstrakcją obiektów rzeczywistych (jak nasza lampka). Dzięki temu progra-mista może operować bezpośrednio na modelowanych obiektach, modyfiko-wać oraz poznawać ich aktualny stan.

• Enkapsulacja (hermetyzacja) – za-mknięcie metod i pól wewnątrz klasy, co wyklucza ich ewentualne kolizje z metodami i polami innych klas. Dzięki niej każda klasa jest odrębnym bytem.W połączeniu z możliwością określe-nia widoczności zmiennych (publicz-ne, prywatne lub chronione – omówi-my to później), enkapsulacja stanowi jedną z najmocniejszych zalet progra-mowania obiektowego.

• Polimorfizm – zapewnia możliwość wykonywania różnych działań przez tę samą metodę, np. w zależności od

klasy (tego, czy będzie to klasa ba-zowa, czy określona klasa pochod-na – można to sprawdzić) czy od ilo-ści lub typu parametrów (przeciąże-nie parametrów). Przykładowo, meto-da zapal() może działać zupełnie ina-czej w przypadku lampki nocnej z ża-rówką, niż w przypadku lampy sufito-wej ze świetlówką.

• Dziedziczenie – możliwość tworzenia klas pochodnych opartych o klasę ba-zową.

Obiektowość w PHP5Dzięki nowemu silnikowi PHP, Zend II, modelo obiektowy PHP5 jest niezawod-ny i wydajny i ma wiele nowych cech, ta-kich jak:

• możliwość określenia widoczności (ang. visibility) każdej metody czy po-la, czyli dostępu do niej, podobnie jak ma to miejsce np. w Javie czy C++. Cecha ta określa, czy metoda (lub po-le) ma być publiczna (dostępna dla

wszystkich), prywatna (dostępna tyl-ko dla klasy, w której została zdekla-rowana), czy też chroniona (dostępna dla klasy, w której została zdeklarowa-na oraz jej klas pochodnych).

• wprowadzenie do klas destruktorów, które pozwalają posprzątać po likwi-dowanym obiekcie (powiemy o nich później),

• ujednolicenie nazw konstrukto-rów (__construct()) i destruktorów(__destruct()) dla wszystkich klas,

• możliwość automatycznego ładowa-nia klas, bez konieczności używania instrukcji typu include czy require (nie jest to co prawda związane bez-pośrednio z programowaniem obiek-towym, jednak często bardzo ułatwia pracę),

• wprowadzenie klas abstrakcyjnych oraz interfejsów (opiszemy je dokład-niej w dalszej części artykułu),

• możliwość deklarowania pól oraz me-tod statycznych (static), dostępnych bezpośrednio w klasie, a nie jej instan-

Czym jest IRCIRC jest jedną z usług internetowych. Protokół IRC został stworzony w roku 1988 roku przez Jarkko Oikarinena. Umożliwia prowadzenie pogawędek internetowych, będąc proto-plastą dzisiejszych czatów, które są dostępne na wielu witrynach internetowych. W przeci-wieństwie jednak do czatów, IRC nie jest w żaden sposób powiązany ze stronami WWW, lecz ma swoje serwery i oprogramowanie klienckie, którego potrzebujemy do prowadzenia konwersacji. Najpopularniejsze programy klienckie IRC to:

• mIRC – komercyjny, dość wygodny w obsłudze program dla systemu Windows (http://www.mirc.com/)

• ViRC – darmowy, całkiem dobry i rozbudowany klient IRC dla Windows (http://www.visualirc.net/)

• XiRCON – inny darmowy klient IRC dla Windows (http://www.xircon.com/)• ShadowIRC – klient IRC dla Macintosha (http://www.shadowirc.com/)• ircII – klient IRC dla systemu UNIX (http://www.eterna.com.au/ircii/)• Chatzilla – IRC w Twojej przeglądarce, czyli plugin dla Firefoksa oraz Mozilli (http://

chatzilla.hacksrus.com/)

Po połączeniu się z serwerem, korzystamy z komend wydawanych w linii poleceń progra-mu klienckiego, z których najważniejsze to:

• /server nazwa _ serwera – łączy się z wybranym serwerem,• /join #nazwa _ kanału – wchodzi na określony kanał (jeżeli jesteśmy połączeni

z serwerem),• /msg nick wiadomość – wysyła prywatną wiadomość do użytkownika o określonym

nicku,• /say wiadomość – wysyła informację do aktywnego okna, dla wszystkich użytkowni-

ków (w większości klientów wpisywanie say nie jest potrzebne).

Komend IRC jest o wiele więcej. Ich pełną listę znajdziemy na stronach większości progra-mów klienckich oraz w ich manualach.

Bardzo dużo jest również serwerów IRC i są one rozsiane po całym świecie. Więk-szość z nich jest połączona w większe sieci (będąc podłączonymi do jednego serwe-ra z takiej sieci możemy rozmawiać z osobami podłączonymi do innych), takie jak bar-dzo popularny w Polsce IRCNet (w każdej chwili jest tam ok. 12000 polskich użytkowni-ków), zobacz www.irc.pl. Oto kilka polskich serwerów należących do tej sieci IRC: – war-szawa.irc.pl, krakow.irc.pl, poznan.irc.pl, lublin.irc.pl. Z serwerami tymi łączymy się korzy-stając z portów od 6661 do 6667. Warto także odwiedzić serwer irc.php.pl, a na nim kanał #php.pl – oficjalny serwer IRC polskiego serwisu o PHP – www.php.pl. Serwer ten dodat-kowo oferuje kanał #test, na którym możemy swobodnie testować swojego bota.

Szybki start IRCBotaJeśli chcesz sprawdzić, jak działa nasz bot, pobierz jego źródła z naszej strony WWW. Możesz go uruchomić z linii pole-ceń lub w przeglądarce internetowej.

Przed uruchomieniem bota musisz go skonfigurować. Ustawienia znajdują się w pliku Configuration.class.php. Po-nadto musisz zmodyfikować wpis w pli-ku admins – zmień tam nicka oraz host, spod którego będziesz się łączył z ser-werem IRC (nie dodawaj ręcznie nowych użytkowników – po zalogowaniu się mo-żesz nimi zarządzać za pomocą komend add_admin: oraz remove_admin: – pa-miętaj o dwukropku).

Aby uruchomić bota z linii poleceń, przejdź do jego katalogu i wydaj pole-cenie: php ConsoleBOT.class.php. Pa-miętaj, że aby to polecenie zadziała-ło, potrzebujesz odpowiednio ustawio-nej ścieżki PATH: musi ona wskazywać na katalog z PHP. Alternatywnie, możesz podać pełną ścieżkę dostępu do progra-mu php (lub php.exe pod Windows).

Aby uruchomić bota w przeglądar-ce internetowej, skopiuj jego pliki do ka-talogu, do którego ma dostęp twój ser-wer HTTP (np. Apache lub IIS), a następ-nie wywołaj w przeglądarce skrypt Web-BOT.class.php. Po uruchomieniu bota możesz się do niego zalogować – wyślij do niego prywatną wiadomość o treści: login: test. Wszelkie komendy wyda-wane botowi mają postać: komenda: pa-rametry oddzielone spacjami. Dwukro-pek po instrukcji jest wymagany nawet, gdy nie przyjmuje ona parametrów, np. disconnect:.

Page 17: PHPSolutions_04_2006_PL

IRCBot – programowanie obiektowe

www.phpsolmag.org 17

Początki

PHP Solutions Nr 4/2006

cji. Teoretycznie było to możliwe jużw PHP4, ale wersja 4 pozwalała na ta-kie traktowanie dowolnych metod i pól, co nie jest wskazane,

• możliwość deklarowania klas oraz me-tod jako finalne (po klasach finalnych nie można dziedziczyć, zaś metod fi-nalnych przykryć (przedefiniować), czyli dopasować ich zachowania do wymagań klasy pochodnej),

• wprowadzenie obsługi błędów w po-staci wyjątków, których tu nie opisze-my (poświęciliśmy im inne artykuły,w tym „Obsługa błędów za pomocą wyjątków SPL”, z numeru „PHP Solu-tions 2/2005).

Nowości dotyczących programowania obiektowego jest znacznie więcej. Już te wymienione pokazują, jak zaawansowa-nym językiem obiektowym stał się PHP5. Pokażemy te możliwości na przykładzie praktycznej aplikacji: stworzymy bota dzia-łającego w sieciach IRC.

Tworzymy obiektowego IRCBotaNaszego bota nazwiemy IRCBot. Będzie to skrypt, który działa na kanale IRC uda-jąc zwykłego użytkownika i pełniąc rozma-ite funkcje potrzebne administratorowi, ta-kie jak piilnowanie porządku na tym ka-nale, nadawanie statusu Opa (operato-ra) wybranym osobom, czy ich wyrzucanie (wykopywanie, ang. kick) z kanału.

Jeśli nie znasz świata IRC, zajrzyj do Ramki Czym jest IRC; szczegóły do-

tyczące operatorów i botów wyjaśniamyw Ramce Opy i boty na IRC.

Dodajmy, że bot jest skryptem działa-jącym jak klient IRC. Przeważnie pracuje w linii poleceń, np. na tej samej maszynie, na której stoi serwer IRC (choć nie jest to konieczne), a wywołujemy go korzystając z telnetu czy ssh. Nasz bot będzie mógł działać zarówno w taki sposób, jak i za pośrednictwem przeglądarki internetowej, w której będzie aktywny aż do zamknię-cia jej okna.

Czego jeszcze potrzebujemy? Zna-jomości podstaw specyfikacji protokołu, na którym oparte są aplikacje klienckie IRC. Jest on dosyć dobrze opisany (m.in.w RFC 2812), nie powinieneś więc mieć problemu z zagłębieniem się w jego tajniki (zob. Ramka Protokół IRC w sieci).

Koncepcja IRCBotaZanim przystąpimy do tworzenia naszego IRCBot-a, zastanówmy się, jakimi cechami powinien się on wyróżniać. Są wśród nich:

• odbieranie i wykonywanie poleceń wy-dawanych wyłącznie przez zalogowa-nych do niego użytkowników,

• śledzenie wydarzeń (ang. events) za-chodzących na wybranym kanale,

• możliwość pracy na wielu serwerachi kanałach IRC jednocześnie,

• modularna budowa, pozwalająca na dodawanie:

Listing 1. Klasa konfiguracyjna bota

<?php

class Configuration { static public $arrConfiguration = array ( 'bot' => array ( /* Konfiguracja bota */ 'Nick' => 'AlaMaKota', /* Nick bota na serwerach */

'Password' => '', /* Hasło do serwera – najczęściej puste */

'Ident' => 'MSBot', /* identyfikator bota */

// Prawdziwa nazwa użytkownika (nazwa i wersja bota)

'Realname' => 'MSBot R1',

'QuitMessage' => 'Idę sobie...', // informacja pożegnalna

'Host' => 'localhost', /* Nazwa hosta z pod jakiego łączy się bot */

),

// serwery, z którymi bot ma się połączyć po uruchomieniu

'Servers' => array ( array ( 'Host' => 'irc.php.pl', // host serwera

// Tablica z portami obsługiwanymi przez serwer

'Ports' => array ('6667'), // tablica z kanałami serwerów, na które bot ma wejść

'Channels' => array ('#test'), // czy komunikaty idą z/do serwera mają być logowane

'Logging'=>true,

),

),

/* Klasy eventy */

'Events' => array ( 'KickReJoinEvent',

),

'System' => array ( /* Plik z informacjami o administartorach */

'AdminsFile' => 'Configuration\admins',

'LoggingClass' => 'FileLogging', /* klasa zapisująca logi */

'LogsDir'=>'logs/', // Katalog w którym mają być zapisywane logi

),

);

}

?>

Protokół IRC w sieciOficjalny opis protokołu klienta IRC zaj-muje 63 strony i jest opisany w dokumen-cie RFC 2812 pt. Internet Relay Chat: Client Protocol. Dokument ten znajdzie-my w wielu miejscach w Internecie, m.in. pod adresem http://www.faqs.org/rfcs/rfc2812.html.

Suchy opis protokołu nie jest jed-nak zbyt przyjazny. Zachęcamy więc do rozpoczęcia lektury od strony http://www.cybernexus.biz/irc/default.htm. Znajdziesz tam wprowadzenie do pro-tokołu oraz przykładowe logi komunika-cji klienta IRC z serwerem, wraz z wyja-śnieniami.

Opy i boty na IRCW IRCNecie (a także w wielu innych sie-ciach IRC, które nie posiadają możliwo-ści rejestrowania kanałów; dziś wiele no-wych sieci oferuje taką funkcję), pierw-sza osoba wchodząca na kanał otrzymu-je status tzw. Opa (operatora czy admi-nistratora).

Jego uprawnienia są ogromne – mo-że on ustalać temat kanału (np. Dysku-sja o PHP6), nadawać status Opa innym użytkownikom kanału, wyrzucać z niego niechciane osoby czy zabraniać im po-nownego wstępu udzialając tzw. bana (ang. ban znaczy zakaz) na wybranego nicka lub hosta.

Niestety, w momencie opuszcze-nia kanału, Op traci swoje uprawnienia. Aby temu zaradzić, administratorzy sto-sują boty. Jak już wspomnieliśmy, bot to 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.

Page 18: PHPSolutions_04_2006_PL

IRCBot – programowanie obiektowe

www.phpsolmag.org18

Początki

PHP Solutions Nr 4/2006

• obsługi nowych zdarzeń (eventów wy-stępujących na danym kanale (np. wy-kopania użytkownika). Każde zdarze-nie będzie obsługiwane przez odręb-ny moduł,

• nowych komend – bot będzie odbierał komendy wysyłane do niego jako pry-watne wiadomości,

• możliwość pracy zarówno w linii pole-ceń, jak i w oknie przeglądarki.

Nasza aplikacja ma być w pełni obiekto-wa, czyli składać się z klas realizujących określoną funkcjonalność. Zastanówmy się teraz, jakich klas potrzebujemy.

Klasy IRCBotaBot będzie się składał z następujących klas:

• Configuration – zawrzemy w niej spis wszystkich ustawień naszego progra-mu (m.in. nazwę użytkownika, adresy i porty serwerów, listę obsługiwanych wydarzeń, itd.),

• IRCBot – główna klasa IRCbota, odpo-wiadająca za inicjalizację i komunika-cję z serwerami, a także obsługę wy-darzeń i komend,

• Server – będzie ona zapewniała ko-munikację z wybranym serwerem IRC (m.in. nawiązanie i zakończenie połą-czenia),

• Channel – klasa odpowiadająca jedne-mu kanałowi, na którym przebywa bot,

• Event – klasa bazowa dla klas obsłu-gujących zdarzenia,

• ILogging – nie jest klasą, lecz interfej-sem, który jest potrzebny do logowa-nia komunikatów,

• Admin – odpowiada za zapamiętywa-nie i zarządzanie zalogowanymi użyt-kownikami.

Konfiguracja– klasa ConfigurationZaczniemy od klasy Configuration. Jak już wiemy, jej zadaniem będzie prze-chowywanie ustawień naszego bo-ta: będzie się ono odbywało w zmien-nej statycznej (ang. static) o nazwie $arrConfiguration. Nazwa celowo roz-poczyna się od arr (skrót od array), aby ułatwić rozpoznawanie typu zmiennej. Stosowanie takiej zasady ułatwia póź-niejsze modyfikacje i pielęgnację kodu. Zmiennej mogącej przechowywać da-ne różnego typu nadalibyśmy przedro-stek mix.

Listing 2. Klasa IRCBot

abstract class IRCBot { static public $strVersion = '0.1.0'; protected $arrServers = array(); private $arrServerLogs = array(); private $arrEvents = array(); private $objAdmins;

public function __construct() { $this->objAdmins = Admin::getInstance($this);

$this->registerEvent('CommandEvent');

$this->registerEvent('AdminLogoutEvent');

foreach (Configuration::$arrConfiguration['Events'] as $strEvent) { $this->registerEvent($strEvent); }

}

public function __destruct() { $this->show(' ', ' ', '--- END --- '); } public function run() { foreach (Configuration::$arrConfiguration['Servers'] as $arrServer) { $this->connect($arrServer); }

for (;;) { if (count($this->arrServers)===0) { return; } usleep(50000); $this->ircProc(); // przetwarzamy komunikaty z serwera

}

}

public function connect($arrServer) { if (isset($arrServer['Logging']) && $arrServer['Logging']===true) { $this->arrServerLogs[$arrServer['Host']] = new FileLogging($arrServer[

'Host']); }

$this->arrServers[$arrServer['Host']] = new Server($arrServer, Configuration::$arrConfiguration['bot'], $this);

$this->arrServers[$arrServer['Host']]->connect();

}

public function getServers() { return $this->arrServers; } protected function ircProc() { foreach ($this->arrServers as $objServer) { $mixData = null;

if (($objServer !== null) && (($arrData = $objServer->serverProc()) !== false) &&

$arrData !== true) { $this->executeEvents($objServer, $arrData); }

elseif ($objServer !== null && $arrData !== true) { $objServer->disconnect();

$this->arrServers = array_diff_key(

$this->arrServers,array($objServer->getName()=>'')); }

}

}

public function executeEvents($objServer, $arrData) { if (isset($this->arrEvents[$arrData['type']])) { foreach ($this->arrEvents[$arrData['type']] as $objEvent) { $objEvent->execute($objServer, $arrData); }

}

}

public function log($strServer, $strDirection, $strContent) { } public function registerEvent($strEventName) { if(file_exists(ROOT_DIR.'Events'. DIRECTORY_SEPARATOR.$strEventName.'.class.php')) {

require_once(ROOT_DIR.'Events'.

DIRECTORY_SEPARATOR.$strEventName.'.class.php');

$objEvent = new $strEventName($this); if (!($objEvent instanceof Event)) { $this->show(' ',' ',"Nieznany typ klasy obsługującej zdarzenia

($strEventName)"); }

foreach ($objEvent->getEventNames() as $strEvent) { $this->arrEvents[$strEvent][] = $objEvent; }

} else { $this->show(' ', ' ', "Brak klasy zdarzenia ($strEventName)"); } }

abstract public function show($strServer, $strChannel, $strContent);}

Page 19: PHPSolutions_04_2006_PL

www.phpsolmag.org 19

Początki

PHP Solutions Nr 4/2006

Na Listingu 1 znajduje się przykłado-wa konfiguracja naszego bota. Jak widzi-my, w zmiennej $arrConfiguration określa-my dane bota (klucz bot, m.in. nick, hasło, identyfikator, prawdziwą nazwę czy ho-sta, spod jakiego łączy się bot), serwerów (klucz Servers), z którymi się łączy, zestaw obsługiwanych zdarzeń (klucz Events) czy dane systemowe (klucz System), m.in. ścieżka do pliku z informacjami o admi-nistratorach, katalog zawierający logi czy nazwa klasy, która je zapisuje.

Dlaczego użyliśmy zmiennej sta-tycznej? Ze względu na łatwość dostępu do niej: zamiast tworzyć instancję klasy Configuration (co w tym przypadku byłoby jedynie dodatkowym, zbędnym krokiem do wykonania, gdyż konfiguracja jest jed-na dla całego programu), odwołujemy się bezpośrednio do samej klasy. Przykłado-wo, aby odczytać nick bota, wpiszemy:

Configuration::$arrConfiguration[’bot’]

[’Nick’];

Czyli podajemy najpierw nazwę klasy za-wierającej zmienną statyczną (Configura-tion), a następnie, po znakach ::, nazwę

tej zmiennej ($arrConfiguration) oraz (po-nieważ jest to tablica asocjacyjna ) jej wy-brany klucz. Gdybyśmy chcieli odwołać się do zmiennej statycznej bezpośrednio z wnętrza klasy, w której ona została zde-finiowana, użylibyśmy słowa kluczowego self, wskazującego na bieżącą klasę:

self::$arrConfiguration[’bot’][’Nick’];

Zauważmy, że zmienne statyczne tworzy-my z wykorzystaniem słowa kluczowego static. Wyraz public oznacza natomiast, że jest to zmienna publiczna, czyli dostęp do niej jest możliwy również spoza klasy. Słowo to możemy pominąć (zmienna jest domyślnie typu public), ale nie należy te-go robić, gdyż jego użycie porządkuje kod i ułatwia pracę programistom korzystają-cym z innych języków, takich jak Java.

Klasa IRCBotJak już wiemy, IRCBot to główna klasa na-szego bota. Będzie ona odpowiadała za nawiązywanie połączeń z serwerami, ob-sługę (w pętli) komunikatów przychodzą-cych z serwerów, rejestrowanie klas zda-rzeń (eventów), a także zapisywanie lo-

Listing 3. Szablon klasy Server

class Server { private $arrConfiguration, $arrServerConfig, $resFP, $objBot;

private $arrChannels = array(), $strPort = ''; public function __construct($arrServerConfig,$arrConfiguration,IRCBot $objBot){ if (!isset($arrServerConfig['Host']) || !isset($arrServerConfig['Ports'])) { throw new Server_Exception('Server configuration error!'); }

$this->arrServerConfig = $arrServerConfig;

$this->arrConfiguration = $arrConfiguration;

$this->objBot = $objBot;

}

public function connect() { } public function disconnect($strReason=null) { } public function join($strChannel) { $this->arrChannels[$strChannel]=new Channel( $this, $this->objBot, $strChannel);

$this->arrChannels[$strChannel]->join(); return $this->arrChannels[$strChannel]; }

public function getChannel($strChannel) { if (isset($this->arrChannels[$strChannel])) { return $this->arrChannels[$strChannel]; } else { return false; } }

public function getName() { return $this->arrServerConfig['Host']; } public function getPort() { return $this->strPort; } public function getChannels() { return array_keys($this->arrChannels); } public function send($strCommand) { } public function serverProc() { }}

class Server_Exception extends Exception {}

Page 20: PHPSolutions_04_2006_PL

IRCBot – programowanie obiektowe

www.phpsolmag.org20

Początki

PHP Solutions Nr 4/2006

gów w plikach oraz wyświetlanie komu-nikatów w konsoli lub na stronie WWW. Przyjrzyjmy się Listingowi 2.

Pierwszym, co rzuca się w oczy, jest słowo abstract w linii deklaracji klasy. Określa ono, że klasa jest abstrakcyjna, co oznacza, że nie można bezpośrednio utworzyć jej instancji. Tym określeniem opatrzona jest również metoda show().

Po co używamy abstract? Jak pamię-tamy, chcemy móc używać naszego bo-ta w konsoli lub na stronie WWW. W tym celu utworzymy klasy pochodne wobec IRCBot, które będą się od niej różniły głów-nie zawartością metody show(). Zadekla-rowanie tej ostatniej jako abstract wymu-sza jej zaimplementowanie (przypisanie jej bloku kodu, choćby pustego) w klasie pochodnej. Zastosowanie abstract chroni nas więc przed utworzeniem instancji nie-kompletnej klasy IRCBot, co wymusza pe-wien porządek w kodzie PHP5.

Zauważmy również, że implementa-cja większości metod jest pusta: to rów-nież jest celowe i spowodowane tym, że samo wdrożenie komunikacji bota z ser-werem nie jest tematem tego artykułu,a kompletny kod źródłowy można zoba-czyć na naszej stronie WWW.

Przyjrzyjmy się teraz metodzie __construct(), czyli konstruktorowi kla-sy IRCBot. Metoda ta jest automatycznie wywoływana podczas instancji klasy. Jej przeznaczenie zależy wyłącznie od nas – przeważnie dokonujemy w niej inicjali-zacji klasy, np. przypisywania wartości do-myślnych określonym polom. Możemy też wykonywać w konstruktorze czynności bę-dące celem istnienia klasy – wybór nale-ży do nas.

Instancję klasy tworzymy natomiast za pomocą operatora new:

$zmienna = new NazwaKlasy($parametry);

Po operatorze new podajemy nazwę tworzonej klasy, a w nawiasie parame-try, których oczekuje konstruktor (al-bo pusty nawias, jeżeli ich nie wymaga).W przypadku klasy IRCBot, konstruktor inicjalizuje prywatne pole $objAdmins (jakwskazuje jego nazwa, pole to ma przecho-wywać obiekt pewnej klasy, w tym przy-padku Admin), rejestruje standardowe zda-rzenia, które muszą istnieć: CommandEvent (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-

nie w pętli foreach rejestruje inne zda-rzenia, które zostały zapisane w zmien-nej $arrConfiguration['Events'] w klasie Configuration. Pamiętajmy, że te ostatnie są opcjonalne, a ich dobór zależy od nas.

Przyjrzyjmy się zdarzeniu Command-

Event. Ma ono swoją klasę, CommandEvent, która jest wykonywana w wyniku zdarze-nia PRIVMSG (wysłanie komunikatu prywat-nego do bota).Analogicznie, jak przy two-rzeniu obiektu, podczas jego niszczenia PHP5 również wywołuje metodę: jej nazwą jest zawsze __destruct(). Nie posiada ona (i nie może posiadać) żadnych parame-trów. Wykorzystujemy ją do sprzątania po likwidowanym obiekcie klasy (np. zamyka-nia lub kasowania plików). W klasie IRCBot użyjemy jej, aby poinformować użytkowni-ka o końcu pracy bota – w tym celu wywo-

łamy metodę show(), wskutek czego w kon-soli lub w oknie przeglądarki pojawi się na-pis ---- END ----. Zwróćmy teraz uwagę na słowo kluczowe $this, używane równieżw konstruktorze klasy IRCBot. Jest ono specjalną zmienną symbolizującą obiekt bieżącej klasy i umożliwia odwoływanie się z jego wnętrza do jego pól i metod:

$this->objAdmins()

$this->run()

Aby odwołać się do atrybutów lub me-tod danego obiektu z zewnątrz (z po-ziomu kodu, w którym została utworzo-na instancja klasy, np. z programu głów-nego czy wnętrza innej klasy) zamiast $this używamy nazwy tego obiektu, np. $instancja->run();.

Listing 4. Klasa Channel

class Channel { private $strChannelName, $objServer, $objBot;

public function __construct(Server $objServer, IRCBot $objBot, $strChannelName) {

$this->strChannelName = $strChannelName;

$this->objServer = $objServer;

$this->objBot = $objBot;

}

public function join() { $this->objBot->show($this->objServer->getName(), '',

"Joint to {$this->strChannelName} channel");

$this->objServer->send("JOIN {$this->strChannelName}");

}

public function leave() { $this->objBot->show($this->objServer->getName(), $this->strChannelName,

"Leave {$this->strChannelName} channel");

$this->objServer->send("PART {$this->strChannelName}");

}

}

Listing 5. Klasa Event

abstract class Event { protected $objBot;

public function __construct(IRCBot $objBot) { $this->objBot = $objBot;

}

abstract public function getEventNames(); abstract function execute(Server $objServer, $arrData);}

Listing 6. Przykładowa klasa obsługująca zdarzenie KICK

class KickReJoinEvent extends Event { public function getEventNames() { return array('KICK'); } public function execute(Server $objServer, $arrData) { $strChannel = substr($arrData['message'], 0, strpos($arrData['message'], ' ')+1); $objServer->join($strChannel); $this->objBot->show($objServer->getName(), $strChannel, 'ReJoin');

}

}

Page 21: PHPSolutions_04_2006_PL

IRCBot – programowanie obiektowe

www.phpsolmag.org 21

Początki

PHP Solutions Nr 4/2006

Warto wiedzieć, że niektóre zaawan-sowane edytory programistyczne gro-madzą dane o strukturze zadeklarowa-nych klas i umożliwiają podpowiadanie pól i metod po wpisaniu $this-> czy na-zwy obiektu.

Po stworzeniu naszej klasy musimy wywołać metodę run(), która urucha-mia bota i zawiera jego główną petlę. Nawiązuje ona połączenie z serwerami znajdującymi się w tablicy $arrConfiguration['Servers'] w klasie Configuration oraz uruchamia pętlę obsługującą zda-rzenia pochodzące od poszczególnych serwerów.

Do nawiązywania połączenia z ser-werami wykorzystywana jest metoda connect() klasy IRCBot, do której prze-kazujemy (przez parametr) ustawienia danego serwera:

$this->connect($arrServer);

Sama metoda connect() tworzy obiek-ty klasy Server i zapamiętuje je w tablicy asocjacyjnej $this->arrServers:

$this->arrServers[$arrServer['Host']]=

new Server(

$arrServer,Configuration::

$arrConfiguration['bot'],$this);

Jak widzimy, indeksami tej tablicy są ad-resy hostów poszczególnych serwerów – ułatwi to odwoływanie się do obiektów konkretnych serwerów. Zauważmy też, że zanim metoda connect() nawiąże po-łączenie, sprawdza, czy ma ono być lo-gowane. Jeżeli tak, tworzona jest instan-cja odpowiedniej klasy (podanej w kla-sie konfiguracyjnej, albo domyślnej klasy FileLogging), do której wrócimy w dalszej części artykułu.

Obsługa komunikatów odbieranych z serwerów przebiega w nieskończo-nej pętli for(;;), którą moglibyśmy też zapisać jako while(true). Zaczynamyw niej od sprawdzenia, czy tablica $this->arrServers zawiera jakiekolwiek ser-wery (jej rozmiar, odczytany za pomocą count() jest różny od zera): jeżeli nie, pro-gram kończy pracę. Następnie wywołuje-my metodę ircProc(), która dla każdego z serwerów (w pętli foreach) znajdujących się w tablicy $arrServers wywoła metodę serverProc() obiektu klasy Server, a po otrzymaniu komunikatów z serwera wy-kona odpowiednie dla tych komunikatów zdarzenia.

Kolejną ważną metodą jest register-Event(). Służy ona do rejestrowania no-wych typów zdarzeń, czyli dodawania klas je obsługujących. Nazwa klasy zdarzenia jest przekazywana przez parametr. Meto-da registerEvent() sprawdza następnie, czy w katalogu Events znajduje się plik o

nazwie NazwaKlasy.class.php. Jeśli nie, wyświetla informację o błędzie.

W przeciwnym wypadku metoda registerEvent() dołącza ten plik do skryptu (require_once), a następnie two-rzy obiekt klasy o nazwie przekazanejw następującym parametrze:

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

class FileLogging implements ILogging { private $resFP = null;

public function __construct($strFile) { // otwieramy plik do zapisu logów

$strDir = isset(Configuration::$arrConfiguration['System'] ['LogsDir']) ?Configuration::$arrConfiguration['System']

['LogsDir'] :'';

if (($this->resFP=fopen($strDir.urlencode($strFile).'.log','a')) === false) {

throw new IRCBot_Exception('Failed to open log file('. $strDir.urlencode($strFile).'.log'.')'); }

$strDate = date('\[Y-m-d H:i:s\]'); fwrite($this->resFP, "\n\n$strDate\t \t==> BEGIN <==\n\n"); }

public function __destruct() { fclose($this->resFP); }

public function log($strServer, $strDirection, $strContent) { // jeżeli istnieje komunikat, zapisujemy go w logu

if (strlen(trim($strContent))<1) { return; } $strDate = date('\[Y-m-d H:i:s\]'); fwrite($this->resFP, "$strDate\t$strDirection\t$strContent\n"); }

}

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

class Admin { private static $objThis = null; private function __construct(IRCBot $objIRCBot) { $this->objIRCBot = $objIRCBot;

$this->reloadAdminsList();

}

static public function getInstance(IRCBot $objIRCBot) { if (!isset(self::$objThis)) { self::$objThis = new self($objIRCBot); } return self::$objThis; }

}

Listing 9. Funkcja __autoload wczytująca automatycznie pliki z naszymi klasami

function __autoload($strClassName) { static $arrClassesMap = array ( 'Configuration' => 'Configuration/Configuration.class.php',

'IRCBot' => 'IRCBot.abstract.class.php',

'Server' => 'Server.class.php',

'Channel' => 'Channel.class.php',

'ILogging' => 'Logging.interface.php',

'FileLogging' => 'FileLogging.class.php',

'Event' => 'Event.abstract.class.php',

'Command' => 'Command.abstract.class.php',

'Admin' => 'Admin.class.php',

);

if (isset($arrClassesMap[$strClassName])) { require($arrClassesMap[$strClassName]); }

}

Page 22: PHPSolutions_04_2006_PL

IRCBot – programowanie obiektowe

www.phpsolmag.org22

Początki

PHP Solutions Nr 4/2006

$objEvent = new $strEventName($this);

Zwróćmy uwagę na przekazanie zmiennej $this jako parametru dla konstruktora kla-sy zdarzenia: w ten sposób przesyłamy do niej bieżący obiekt klasy IRCBot, z którego następnie będziemy mogli w pełni korzy-stać w tej klasie.

Po utworzeniu instancji klasy obsłu-gującej zdarzenia musimy sprawdzić, czy obiekt ten jest odpowiedniego typu. Użyje-my w tym celu operatora instanceof, któ-ry sprawdza, czy obiekt podany po jego le-wej stronie jest instancją klasy określonej po prawej (wyrażenie ma wartość true je-żeli tak, false jeżeli nie). W naszym przy-padku, wszystkie klasy zdarzeń będą mu-siały pochodzić od Event:

if (!($objEvent instanceof Event)){

$this->show(' ',' ',

"Nieznany typ klasy obsługującej

zdarzenia ($strEventName)");

}

Spójrzmy teraz na metodę show() kla-sy IRCBot. Nie posiada ona ciała (kodu) i została zadeklarowana jako abstrakcyj-na (abstract). Możemy się do niej odwo-ływać w innych metodach (a także z ze-wnątrz obiektu), ale jej kod utworzymy dopiero w klasach pochodnych wobec IRCBot – jak wiemy, umożliwi nam to pod-mianę tej metody w zależności od tego, czy chcemy, aby nasz skrypt działał w linii poleceń, czy w przeglądarce WWW oraz zapobiegnie uruchomieniu niekompletnej klasy. Pamiętajmy, że w klasie dziedziczą-cej po IRCBot będziemy musieli utworzyć ciało metody show(), chyba, że będzie to również klasa abstrakcyjna.

Na koniec wyjaśnijmy, czym są sło-wa public, private oraz protected.Pozwalają one określić dostęp (zwa-ny widocznością) do metod i pól naszej klasy, przed którymi zostały umieszczo-ne. Znaczenie pierwszego z nich już zna-my: swobodny dostęp zarówno z wnę-trza ($this->nazwaPola), jak i z zewnątrz obiektu ($nazwaObiektu->nazwaPola).

Z kolei private pozwala wyłącznie na dostęp do pola lub metody w klasie, w któ-rej zostały zadeklarowane (na zewnątrz obiektu oraz w klasach pochodnych już nie). Ostatnie słowo, protected, umożli-wia dostęp do pola lub metody wyłącznie dla naszej klasy oraz jej klas pochodnych (jego zasięg jest więc szerszy niż w przy-padku private).

Klasa ServerNadszedł czas na klasę Server, która również należy do najważniejszych klas naszego bota. Na Listingu 3 przedsta-wiamy, jak poprzednio, schemat klasy (większość metod jest pusta). W przeci-wieństwie jednak do klasy IRCBot, klasa Server nie jest abstrakcyjna. Odpowia-da ona za komunikację z konkretnym serwerem IRC, a więc między innymi za nawiązywanie (connect()) oraz koń-czenie (disconnect()) połączenia. Znaj-duje się w niej również metoda obsługu-jąca żądania nadchodzące z serwera(serverProc()) oraz umożliwiająca wy-

syłanie do niego komend (send()). Wej-ście na kanał zapewnia metoda join().

W konstruktorze klasy Server zainicja-lizujemy jej konfigurację oraz utworzymy $objBot, instancję klasy IRCBot (przekaza-ną przez parametr).

Sama idea działania tej klasy jest dość prosta: tworzymy jej instancję we-wnątrz klasy IRCBot (zob. Listing 1),a następnie z tego samego miejsca wy-wołujemy metodę connect(), aby na-wiązać połączenie z serwerem. Meto-da ta sprawdza kolejne porty z konfigu-racji danego serwera (pobrane ze wcze-śniej ze zmiennej $arrConfiguration

Listing 10. Implementacja klasy głównej bota, wyświetlająca komunikaty w oknie konsoli

require_once('autoload.inc.php');

class ConsoleBOT extends IRCBot { public function __construct() { parent::__construct(); } public function show($strServer, $strChannel, $strContent) { $strDate = date('\[Y-m-d H:i:s\]'); echo "$strDate\t$strServer\t$strChannel\t$strContent\n"; }

}

$objBot = new ConsoleBOT();$objBot->run();

Listing 11. Implementacja klasy głównej bota, wyświetlająca komunikaty w oknie przeglądarki internetowej

require_once('autoload.inc.php');

class WebBOT extends IRCBot { public function __construct() { echo <<<EOT<html><head>

<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />

<title>IRCBot</title>

</head>

<body>

<table border="1" style="color: white;"><colgroup>

<col span="1" style="background-color: navy"/>

<col span="1" style="background-color: blue"/>

<col span="1" style="background-color: red"/>

<col span="1" style="background-color: gray"/>

</colgroup>

<tr><th>Data</th><th>Serwer</th><th>Kanał</th>

<th>Treść wiadomości</th></tr>\nEOT;

parent::__construct();

}

public function __destruct() { echo "</table></body></html>"; foreach ($this->getServers() as $objServer) { $objServer->disconnect(); } }

public function show($strServer, $strChannel, $strContent) { $strDate = date('Y-m-d H:i:s'); echo "<tr><td>$strDate</td><td>$strServer</td><td>$strChannel

</td><td>$strContent</td></tr>\n";

flush(); ob_flush();

}

}

$objBot = new WebBOT();$objBot->run();

Page 23: PHPSolutions_04_2006_PL

IRCBot – programowanie obiektowe

www.phpsolmag.org 23

Początki

PHP Solutions Nr 4/2006

z klasy Configuration i przekazane kla-sie Server przez parametr konstruktora)i łączy się przez pierwszy dostępny. Na-stępnie informuje użytkownika o połą-czeniu i wysyła kolejno IRC-owe po-lecenia PASS, NICK i USER do serwera, aby się do niego zalogować. Każdemupoleceniu towarzyszą odpowiednie da-ne (hasło, nick oraz zestaw: identyfika-tor, host i prawdziwa nazwa).

Następnie wywoływana jest meto-da join(), która tworzy obiekty klasy Channel dla każdego kanału, z którym łą-czy się nasz bot. Ponieważ możemy się łączyć z wieloma serwerami, więc za-rządzanie kanałami w klasie odpowia-dającej każdemu serwerowi jest bardzo dobrym wyjściem (każdy serwer może mieć przecież wiele przypisanych do sie-bie kanałów).

W konstruktorze klasy Server pojawi-ła się nowość – podawanie oczekiwane-go typu parametrów w linii nazwy (dotyczy parametru $objBot):

public function __construct(

$arrServerConfig,

$arrConfiguration, IRCBot $objBot)

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

Klasa ChannelJest to bardzo prosta klasa odpowia-dająca jednemu kanałowi, na którymprzebywa bot (jeśli dobrze pamiętasz, klasa Server tworzy po jednej instancji klasy Channel dla każdego). Przedsta-wiamy ją na Listingu 4. Przez konstruk-tor przekazujemy jej dwa obiekty: klasy Server oraz IRCBot, a także nazwę ka-nału. Poza konstruktorem, klasa Channel ma tylko dwie metody: join() (przyłącze-nie się do kanału) oraz leave() (opusz-czenie go). Każda z nich wywołuje zdefi-niowaną w klasie IRCBot metodę send(), która z kolei wysyła odpowiednią komen-dę do serwera IRC: $this->objServer->send(komenda);

Dla join() jest to IRC-owa komenda JOIN, a dla leave() – PART.

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

Klasa EventO 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 i inicjujemy instancję klasy IRCBot oraz dwóch metod abstrakcyjnych:

• getEventNames() – zwraca tablicęz nazwami zdarzeń, których wystąpie-nie spowoduje wywołanie obiektu tej klasy (pochodnej od Event),

• execute() – metoda ta jest wywoły-wana, gdy wystąpi zdarzenie, które obsługuje dana klasa (pochodna od Event).

Ponieważ budowa tak prostej klasy abs-trakcyjnej niewiele nam powie, przyj-rzyjmy się dziedziczącej po niej klasie KickReJoinEvent (dzięki extend Event), ob-sługującej zdarzenie KICK, czyli wyrzuce-nie z kanału (Listing 6). Klasa ta powodu-je ponowne wejście bota na kanał, z którego został wyrzucony. Obie metody abstrak-

Rysunek 2. IRCBot w konsoli

Rysunek 3. IRCBot w przeglądarce internetowej

Page 24: PHPSolutions_04_2006_PL

IRCBot – programowanie obiektowe

www.phpsolmag.org24

Początki

PHP Solutions Nr 4/2006

cyjne klasy Event zostały zaimplementowa-ne w KickReJoinEvent: getEventNames() zwraca tablicę zawierającą jeden ele-ment: KICK, a execute() ponownie wywo-łuje metodę join() obiektu $objServer kla-sy Server oraz informuje o tym użytkownika (za pomocą metody show() obiektu $this->objBot klasy IRCBot).

Interfejs ILoggingPodczas nawiązywania połączenia z ser-werem, klasa IRCBot uruchamia logowa-nie komunikatów pochodzących z ser-wera oraz wychodzących do niego (jeślizostało to włączone w konfiguracji). Logo-waniem zajmuje się osobna klasa, której nazwa jest również zapisana w konfigura-cji. Zrobiliśmy tak, bo chcemy mieć możli-wość wyboru sposobu zapisu logów – np. w bazie danych czy w pliku. Klasa logu-jąca musi mieć konstruktor oraz metodę log(), która będzie zapisywała komunikat i przyjmowała trzy argumenty: nazwę ser-wera, kierunek wysyłania komunikatu (OUT dla komend wychodzących z naszego bo-ta oraz IN dla wiadomości przychodzących z serwera) oraz treść komunikatu.

Aby wymusić podstawową zgodność struktury tej klasy z naszymi oczekiwania-mi, utworzymy interfejs o nazwie ILogging. Klasy zajmujące się logowaniem będąmusiały go implementować. Interfejs jest bardzo podobny do klasy, tyle że nie za-pewnia implementacji żadnych metod, sta-nowiąc jedynie swego rodzaju zapowiedź ich wystąpienia. Do pewnego stopnia mo-żemy patrzeć na interfejsy jak na klasyw pełni abstrakcyjne, czyli posiadające wy-łącznie metody abstrakcyjne. Trzeba pa-miętać, iż wszystkie metody zadeklaro-wane w interfejsie muszą być publiczne (oczywiście słowo kluczowe public można pominąć). A oto kod naszego interfejsu:

interface ILogging {

public function __construct($strFile);

public function log($strServer,

$strDirection,

$strContent);

}

Każda klasa może implementować dowol-ną liczbę interfejsów (podczas gdy dzie-dziczyć może tylko po jednej klasie). Nic nie stoi również na przeszkodzie, aby da-na klasa C implementowała interfejs Bi dziedziczyła po klasie A.

Na Listingu 7 przedstawiamy klasęFileLogging, implementującą interfejs

ILogging. Zapisuje ona logi w pliku (przy pomocy metody log()), którego otwarcie następuje w jej konstruktorze, a zamknię-cie – w destruktorze.

Klasa AdminKlasa Admin odpowiada za zapamiętywa-nie użytkowników zalogowanych do bota oraz zarządzanie nimi. Jest ona więc swo-istą implementacją sesji. Tym, co ją wyróż-nia od innych, jest fakt, że musi ona mieć jedną i tylko jedną instancję w całej apli-kacji. Aby to zapewnić, stworzymy klasę korzystając z wzorca projektowego (ang. pattern design: gotowego przepisu na roz-wiązanie ogólnie znanego problemu pro-gramistycznego) o nazwie Singleton. Taka klasa ma prywatny konstruktor, co unie-możliwia utworzenie jej instancji za pomo-cą operatora new, a także posiada statycz-ną metodę zwracającą utworzoną instan-cję klasy. Spójrzmy na Listing 8.

Jak widzimy, konstruktor klasy jest prywatny. Do stworzenia instancji klasy wykorzystywana jest zaś statyczna (sta-tic) metoda getInstance(), przyjmująca te same parametry co konstruktor. Spraw-dza ona, czy w statycznym polu $objThis znajduje się już utworzony obiekt naszej klasy. Jeśli nie, to go tworzy, wykorzystu-jąc do tego słowo kluczowe self (wskazu-jące na aktualną klasę). Następnie zwraca obiekt $objThis. Oto, jak należy pobrać in-stancję klasy typu Singleton:

$obj = Admin::getInstance($parametry);

Pozostałe metody klasy są już typowe: login() służy do oznaczenia użytkow-nika na liście jako zalogowanego do bo-ta, logout() do wylogowania z niego, serverLogout() do wylogowania botaz serwera, a reloadAdminsList() do od-świeżenia informacji o administratorach. Zadaniem addAdmin() jest dodanie admina do listy, zaś do removeAdmin() należy usu-nięcie go z niej. Wreszcie, metoda check() sprawdza uprawnienia dostępu danego administratora na określonym serwerze.

Automatyczne ładowanie klasJak wspomnieliśmy na początku arty-kułu, PHP5 umożliwia automatyczne ła-dowanie klas. Aby z niego skorzystać,należy utworzyć specjalną funkcjęo nazwie __autoload(), przyjmującą ja-ko parametr nazwę klasy, która powinnazostać załadowana. Następnie w tej funk-

cji wczytujemy odpowiedni pliku z klasą (na podstawie nazwy tej ostatniej). Na Li-stingu 9 znajduje się implementacja funk-cji __autoload() stworzona specjalnie na potrzeby naszego projektu.

Implementacja klasy abstrakcyjnej IRCBotNa zakończenie musimy jeszcze utwo-rzyć klasę, która będzie implementowa-ła IRCBot. Musimy w niej zaimplemen-tować metodę show(), wyświetlającąinformacje pochodzące od bota oraz kon-struktor. Na Listingu 10 znajduje się wersja tej klasy dla konsoli (ConsoleBOT), a na 11 – dla przeglądarki WWW (WebBOT). W obu przypadkach, w konstruktorze znajduje się następująca linia: parent::__construct();. Jest to wywołanie konstruktora klasy-rodzi-ca (parent), po której ConsoleBOT i WebBOT dziedziczą. Przypomina ono nieco wywoła-nie metody statycznej. Na Rysunkach 2 i 3 przedstawiamy zrzuty ekranów z przykła-dowych sesji z IRCBotem.

PodsumowanieNasz IRCBot jest gotowy. Można go te-raz uruchomić i przetestować (patrz ramka Szybki Start). Warto również na własną rę-kę poznawać RFC 2812 oraz inne materia-ły na temat IRC, a także analizować klasy odpowiadające za obsługę wydarzeń. Po-zwoli to nam pogłębić wiedzę na temat taj-ników IRC oraz sztuczek i kruczków przy-datnych przy stosowaniu botów. Powin-niśmy również nadmienić, że względna prostota tworzenia naszej aplikacji wyni-ka właśnie z obiektowości: z tego, że mo-żemy logicznie podzielić funkcje przez nią wykonywane na odrębne, łatwe do odróż-nienia obiekty, w których będą bezpieczne przed przypadkową modyfikacją. Zachęca-my do dalszego poznawania programowa-nia obiektowego (włączając w to bardziej zaawansowane obszary, jak wzorce pro-jektowe), w czym zawsze chętnie służymy pomocą w postaci naszych artykułów. n

Marcin Staniszczak jest studentem pierw-szego roku informatyki studiów uzupełnia-jących magisterskich na WSHE w Łodzi. W PHP programuje od wielu lat. Jest autorem kilkunastu publikacji o tematyce PHP i In-ternetowej. Jest autorem frameworka MVC dla PHP5 (web.framework) oraz systemu szablonów dla PHP5 (web.template).

O autorze

Page 25: PHPSolutions_04_2006_PL
Page 26: PHPSolutions_04_2006_PL

www.phpsolmag.org26

Początki

PHP Solutions Nr 4/2006

Google Apility

www.phpsolmag.org 27

Początki

PHP Solutions Nr 4/2006

Korzystając z wyszukiwarek inter-netowych określamy dokładnie, czego szukamy. Powiedzmy, że

mieszkamy w Warszawie i chcemy ku-pić nowy rower. Wpisujemy więc sklep rowerowy Warszawa. Załóżmy teraz, że Alicja prowadzi sklep rowerowy w tym mieście: dzięki reklamom tekstowym Google (Google text ads) możemy się nawzajem znaleźć. Za każdym razem, gdy podajemy przytoczoną wcześniej frazę, podobnie jak w przypadku zwro-tów rowery, tanie rowery, wycieczka ro-werowa Warszawa, a nawet (celowa po-myłka w pisowni) sklep RWoerowy, po-winna się pojawić reklama zamieszczo-na przez Alicję. Weźmy jednak pod uwa-gę, że w Warszawie istnieje wiele skle-pów rowerowych: przykładowo, oprócz Alicji prowadzą je jeszce Robert i Ka-mila, którzy również ogłaszają się ko-rzystając z Google text ads. Kto lub co decyduje o tym, czyj tekst ukaże się na najwyższej pozycji w rankingu (wg klik-

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

nięć)? Obliczanie pozycji reklamy odby-wa się wg następującej formuły: Ad Rank = koszt kliknięcia * jakość wyników.

Reklamodawcy mogą ustalić maksy-malny koszt kliknięcia (CPC, ang. cost per click), czyli kwotę, którą są goto-wi za nie zapłacić. Na jakość wyników (ang. quality score) wpływ ma nato-miast clickthrough rate (CTR) każdego słowa kluczowego (procent kliknięć ba-nera w stosunku do odwiedzin na stro-nie, na której jest on zamieszczony), dopasowanie reklamy do wyszukiwa-nej treści, dotychczasowa (historyczna)

APIlity: zarządzanie reklamami Google AdWords z poziomu PHPThomas Steiner

W SIECI

l http://google-apility.sourceforge.net– strona domowa APIlity

l http://google-apility.sourceforge.net/APIlity_Reference_Standalone.html – opis elementów biblioteki APIlity

l http://groups-beta.google.com/group/adwords-api-php – grupa dyskusyjna użytkowników APIlity

l http://www.google.com/apis/adwords/index.html – strona domowa AdWords API

l http://groups.google.com/group/adwords-api – grupa dyskusyjna użytkowników AdWords API

l http://www.google.com/support/adwordsapi/bin/answer.py?answer=21996 – taryfa zużycia jednostek kwoty

Stopień trudności: lll

Co należy wiedzieć...Należy znać podstawy programowania obiektowego w PHP. Przydatna będzie również wiedza na temat SOAP i AJAX.

Co obiecujemy...Pokażemy, jak stworzyć prostą aplikację do okresowego sterowania kontem Go-ogle AdWords.

Page 27: PHPSolutions_04_2006_PL

www.phpsolmag.org26

Początki

PHP Solutions Nr 4/2006

Google Apility

www.phpsolmag.org 27

Początki

PHP Solutions Nr 4/2006

wydajność słowa kluczowego oraz wiele innych czynników.

Struktura konta Google AdWordsKonto Google AdWords jest podzie-lone na kampanie (reklamowe). Wra-cając do naszego przykładu, Alicjamoże wprowadzić różne strategie mar-ketingowe dla rowerów górskich i ko-lażówek. Każdej kampanii przypisa-na jest jedna lub więcej grup reklam. Przykładowo, w kampanii dotyczącej kolażówek mogą istnieć osobne gru-py dla każdego producenta. Na koniec, każda grupa zawiera listę słów klu-czowych lub kryteriów opisujących wi-trynę internetową oraz listę creatives(reklam tekstowych, które pokazują się w wyszukiwarce internetowej). Każde-mu słowu Alicja może przypisać mak-symalne CPC, które jest gotowa zapła-cić; może też użyć domyślnego CPC dla każdej grupy. Analogicznie, z każ-dym słowem można skojarzyć odręb-nego URL-a, pod który jest kierowany użytkownik klikający na reklamę, albo po prostu wybrać ogólny adres (np. wi-trynę internetową sklepu rowerowego).

Na podobniej zasadzie można do-kładnie dopasować kampanie i grupy do potrzeb reklamodawcy. Przykłado-wo, Google pozwala na prowadzenie kampanii regionalnych oraz zawiesza-

nie lub całkowite kasowanie grup re-klam (np. przy zerwaniu współpracyz określonym reklamodawcą). Raporty wydajności umożliwiają natomiast śle-dzenie suckesów konta AdWords.

AdWords API: wprowadzenieGoogle dobrze wie, że do zarządzania tak rozbudowanym systemem reklamo-dawcom nie wystarcza zwykła strona in-ternetowa: potrzebują API (ang. Applica-tion Programming Interface), które po-zwala agencji reklamowej na przesłanie serii słów kluczowych za jednym zama-chem i które można wykorzystać w swo-ich własnych aplikacjach. Inne pożądane cechy takiego API to np.: opcja automa-

tycznego zawieszenia całej serii reklamw reakcji na określone wydarzenie czy automatyzacja ustalania cen przypisa-nych do słowa kluczowego na podstawie zapasów magazynowych firmy.

Technologia wykorzystywanaw AdWordsTechnologią odpowiedzialną za przesy-łanie danych w AdWords API jest SO-AP – protokół oparty na XML-u i HTTP, szeroko stosowany przez twórców opro-gramowania. Pisaliśmy o nim w artyku-łach: Mariaż Pythona i PHP. Tworzy-my interfejs graficzny z wykorzystaniemSOAP (poprzedni numer) oraz XUL-owy interfejs dla PHP (3/2005). AdWords API używa SOAP 1.1 o stylu document/literal. W największym skrócie, jest to proto-kół klient-serwer, gdzie po stronie serwe-ra istnieje aplikacja udostępniająca swo-je funkcje lub klasy (to drugie jest możliwew nowszych implementacjach SOAP),a po stronie klienta – program, któryz nich korzysta tak samo, jak ze swo-ich własnych funkcji lub klas: wywołuje je(z ew. parametrami) i uzyskuje zwraca-ny rezultat. Zestaw oferowanych funkcji lub klas jest nazywany usługami sieciowy-mi lub webserwisem (ang. Web Services). Dodatkowo, SOAP pozwala na komunika-cję między serwerem a klientem niezależ-nie od tego, w jakich językach są napisane aplikacje po obu stronach. Przykładowo, aplikacja serwerowa może być stworzona w PHP, a klient w Javie czy Pythonie.

Zestaw udostępnianych funkcji, ich dokładne parametry, konwersja typów zmiennych między językami, pliki, w któ-rych te funkcje są umieszczone, itd. moż-na określić w XML-owym pliku WSDL

Instalacja APIlityPobieramy APIlity spod adresu http://google-apility.sourceforge.net i rozpakowujemy do katalogu, do którego PHP ma dostęp.

WymaganiaAPIlity działa zarówno na PHP4, jak i PHP5. Aby móc uzywać funkcji pobierania ra-portów (report downloading), musimy włączyć rozszerzenie CURL (zobacz http://www.php.net/curl). Użytkownicy PHP4 (tylko oni) potrzebują też rozszerzenia DOM XML (http://www.php.net/domxml). W PHP5 rozszerzenie to zostało zastąpione wbudowaną biblioteką DOM. Podczas dołączania APIlity sprawdzi, czy te warunki są spełnione: jeże-li nie, ujrzymy odpowiednie ostrzeżenia.

Konfiguracja bibliotekiKażde żądanie wysłane do AdWords API wymaga uwierzytelnienia oraz pokazania tzw. identyfikatora programisty (ang. developer token), który określa każdego dewelopera (reklamodawcę korzystającego z API). Zakładamy, że już masz konto wraz ze wszystki-mi wymaganymi informacjami dostępowymi. Aby ułatwić proces uwierzytelniania, APIlity używa pliku authentication.ini.

Ogólnie, agencja reklamowa ma pewną liczbę klientów, z których każdy przyznał jej dostęp do swojego konta przez API. Informacje o tych klientach są zgromadzone w nale-żącym do agencji My Client Center (MCC). Szef tego centrum jest nazywany menadże-rem klientów (client manager). Jego główny adres emailowy identyfikuje agencję, pod-czas gdy jego klientów identyfikują ich własne adresy emailowe. Aby więc uzyskać do-stęp do wybranego klienta, musimy się uwierzytelnić przy użyciu swojego emaila mena-dżera klientów hasła, identyfikatora programisty oraz adresu email tego klienta: wystar-czy wprowadzić te dane w pliku authentication.ini i gotowe.

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

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

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

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

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

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

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

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

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

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

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

������

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

�����������

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

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

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

Rysunek 1. Struktura klas APIlity

Page 28: PHPSolutions_04_2006_PL

Google Apility

www.phpsolmag.org28

Początki

PHP Solutions Nr 4/2006

(Web Services Definition Language), któ-ry znajduje się na serwerze. Tak też czy-ni Google AdWords. W naszym przypad-ku, Google udostępnia zestaw funkcjiopisanych jaki AdWords API

Utworzymy więc klienta, który bę-dzie się łączył z AdWords przez SOAP(w PHP4 musimy w tym celu dołączyć odpowiednią bibliotekę; w PHP5 jest ona wbudowana) i wywoływał funkcjeAdWords API, podzielone na odrębne usługi, takie jak TrafficEstimatorService czy AdGroupService.

APIlity, czyli łatwe i przyjemne tworzenie klienta AdWords.Aby uprościć tworzenie klienta AdWords API w PHP, firma Google utworzyłabibliotekę APIlity, która ukrywa wszyst-kie szczegóły techniczne jego usług sie-ciowych. Dla Javy istnieje analogicz-na biblioteka google-adwords-api-client, dostępna również jako projekt openso-urcowy (http://sourceforge.net/projects/goog-ad-api-cli/). Biblioteka APIlity umo-żliwia łatwe tworzenie obiektowych apli-kacji klienckich korzystających z Google AdWords API. Pokażemy Wam, jak jej używać.

System kwotowyAdWords API wprowadza system kwoto-wy, który służy do zarządzania ruchem przysyłanych do serwera żądań. Każdy reklamodawca dostaje pewną liczbę jed-nostek kwotowych (quota units) do wyko-rzystania w ciągu danego miesiąca. Każ-de żądanie wysłane do serwera AdWords (np. zapytanie o listę słów kluczowych) powoduje zużycie określonej liczby jedno-stek (różniącej się w zależności od działa-nia). Kwota miesięczna podlega zmianom i zależy od różnych czynników, m.in. od ilości kont, które posiadamy w systemie oraz stopnia, w jakim ich używamy. Sys-tem ten wprowadzono w celu ochrony serwerów Google AdWords przed prze-ciążeniem przez nadmierny ruch.

Struktura APIlityDo komunikacji z AdWords API, APIli-ty używa zmodyfikowanej wersji bibliote-ki NuSoap. Strukturę jej klas przedstawia-my na Rysunku 1. Centralna klasa całego projektu jest zawarta w pliku apility.php (oznaczona na zielono) i dołącza (inklu-duje) wszystkie inne klasy. Każda Usługa sieciowa ma swoją własną klasę (żółta)– ułatwia to szybkie odnalezienie szcze-

Listing 1. Plik authentication.ini biblioteki APIlity

...

Email = "[email protected]"

Password = "y0uR5ecR3t"

Developer_Token = "yOurD3v3l0p3RT0k3n"

Client_Email = "[email protected]"

Listing 2. Hello World w APIlity

<?php

require_once('apility.php');

// pobierz wszystkie kampanie klienta

$allCampaigns = getAllCampaigns();

// wyświetlanie tablicy kampanii

echo "<pre>";print_r($allCampaigns);echo "</pre>";?>

Listing 3. Przykładowy wynik działania "Hello world"

Array

( [0] => Campaign Object

(

[name] => Some Name

[id] => 12345678

[status] => Active

[startDate] =>

2006-03-31T11:03:21.000Z

[endDate]=>

2007-01-01T07:59:59.000Z

[dailyBudget] => 567

[languages] => Array

( [0] => en

[1] => pl

[2] => fr )

[geoTargetType] => countries

[geoTargets] => Array

( [0] => UK

[1] => PL

[2] => GB )

[isEnabledOptimizedAdServing]=>

[networkTargeting] => Array

( [0] => SearchNetwork

[1] => ContentNetwork )

[isEnabledSeparateContentBids]=>1 ) )

Listing 4. Pobieranie informacji o grupach reklam

// odczytaj pierwszą kampanię i pobierz jej grupy reklam

$allAdGroups=

$allCampaigns[0]->

getAllAdGroups();

echo "<pre>"; print_r($allAdGroups); echo "</pre>";

gółów implementacji wykonywanych przez nią zadań.

Plik Clients.php służy jako łącznik po-między wysokopoziomowymi obiektami APIlity, a biblioteką NuSoap i przetwa-rzanymi przez nią komunikatami SOAP. Pozostałe klasy w bibliotece APIlity zaj-mują się m.in. uwierzytelnianiem i obsłu-gą błędów.

Obiekty w świecie APIlityJak już powiedzieliśmy, konta AdWords są podzielone na kampanie, którym są przypisane grupy reklam zawierające cre-atives i np. słowa kluczowe lub kryteria opisujące witrynę.

Każda kampania, podobnie jak gru-pa reklam jest opisana przez unikalne w skali światowej ID. Natomiast identyfika-

Page 29: PHPSolutions_04_2006_PL

Google Apility

www.phpsolmag.org 29

Początki

PHP Solutions Nr 4/2006

cja creatives, słów kluczowych oraz kry-teriów opisujących witrynę odbywa się za pomocą ID, które są unikalne na skalędanego konta. Kampanie są obiekta-mi niezależnymi, podczas gdy wszyst-kie pozostałe obiekty posiadają odwoła-nia do miejsc, do których należą. Zgodniez tą zasadą, grupy reklam przechowu-ją ID kampanii, w ramach których istnie-ją, creatives pamiętają ID swojej grupy re-klam, itd. Kompletną hierarchię obiektów przedstawiamy na Rysunku 2.

„Hello World” w APIlityJeżeli pobraliśmy i zainstalowaliśmy API-lity oraz wpisaliśmy dane uwierzytelnia-jące w pliku authentication.ini (Listing 1), możemy pokazać jej działanie na przy-kładzie prostej aplikacji typu Hello World.Zaczniemy od dołączenia biblioteki APIlity w kodzie: require _ once('apility.php'); i umieszczenia jej w tym samym folderze, co nasza aplikacja. Nasz przykład, który przedstawiamy na Listingu 2, będzie po-bierał mającą postać tablicy asocjacyjnej

listę wszystkich naszych kampanii wy-wołując funkcję getAllCampaigns(); oraz wyświetlał ją w przeglądarce interneto-wej. Jeżeli Twoje konto zawiera tylko jed-ną kampanię, wynik powinien wyglądać jak na Listingu 3.

No dobrze, a jak zobaczyć wszyst-kie grupy reklam? To proste: jak wie-my, każda grupa należy do jakiejśkampanii. Wystarczy więc zapytać kam-panie o przypisane im grupy reklam: słu-ży do tego metoda getAllAdGroups() utworzonego poprzednio przez wywo-

łanie funkcji GetAllCampaigns() obiektu $allCampaigns (Listing 4. Pamiętajmy, że mamy już tablicę kampanii z poprzednie-go przykładu. Podobnie jak poprzednio, otrzymany wynik będzie miał postać tabli-cy asocjacyjnej (zob. Listing 5).

Moglibyśmy rozszerzać nasz przy-kład wywołując getAllAdGroups() na wszystkich obiektach kampanii, a na-stępnie, rekurencyjnie getAllCreatives() i getAllCriteria() na każdym obiek-cie grupy reklam, aż otrzymalibyśmykompletne drzewo odzwierciedlające strukturę konta klienta. Zostało to zade-monstrowane w dołączonym do APIlity przykładowym projekcie.

Obsługa błędówi programowanie defensywneGdy mamy do czynienia z działający-mi zdanie Usługami sieciowymi, obsłu-ga i usuwanie błędów staje się szczegól-nie istotne. APIlity pozwala zarówno na straight-forward programming, jak i pro-gramowanie defensywne. Przyjęta w tej bibliotece konwencja mówi, że w wypad-ku niepowodzenia (błędu) funkcja zwra-ca fałsz (false), jeśli natomiast wszystko jest OK:

• funkcja zwraca żądany rezultat (jeśli powinna go zwracać),

• funkcja zwraca prawdę (true), jeżeli nie ma zwracać niczego innego.

Listing 6. Krok 1: tworzymy listę kryteriów słów kluczowych dla naszego konta

<?php

require_once('apility.php');

function getAllCriteriaOfAccount() { // pobieramy wszystkie kampanie

// w wypadku niepowodzenia tego podstawowego kroku zwracamy false

if ( !$allCampaigns = getAllCampaigns() ) return false; // pobieramy wszystkie grupy reklam przypisane do danej kampanii

for($i=0; $i<sizeOf($allCampaigns); $i++) { $campaign = $allCampaigns[$i];

// w wypadku błędu omiń aktualną iterację

if ( !$allAdGroupsOfCampaign[$i] = $campaign->getAllAdgroups() ) continue; }

// inicjalizujemy tablicę do przechowywania kryteriów słów kluczowych

$allCriteriaOfAccount = array(); // pobierz słowa kluczowe zawarte w każdej kampanii i każdej grupie reklam

for($i=0; $i<sizeOf($allAdGroupsOfCampaign); $i++) { for($j=0; $j<sizeOf($allAdGroupsOfCampaign[$i]); $j++) { $adGroup = $allAdGroupsOfCampaign[$i][$j];

// w wypadku błędu po prostu pomijamy aktualną iterację

if ( !$allCriteria = $adGroup->getAllCriteria() ) continue; foreach($allCriteria as $criterion) { array_push($allCriteriaOfAccount, $criterion);

} }

} return $allCriteriaOfAccount; }?>

Listing 5. Przykładowy wynik "rozszerzonego hello world"

Array

( [0] => AdGroup Object

( [maxCpc] => 1

[maxCpm] =>

[maxContentCpc] =>

[name] => sdsdsd

[id] => 279945007

[belongsToCampaignId]=>10838947

[status] => Active

) )

��������

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

�������

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

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

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

�������

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

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

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

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

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

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

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

Rysunek 2. Hierarchia obiektów APIlity

Page 30: PHPSolutions_04_2006_PL

Google Apility

www.phpsolmag.org30

Początki

PHP Solutions Nr 4/2006

Odstępstwem od tej reguły są pro-ste funkcje pobierania obiektów (object get-functions), ponieważ wymagają one poprawnie utworzonego obiektu. Przy-kładowo, funkcja addCampaign() doda-je i zwraca jako rezultat nową kampa-nię. Jeśli więc wszystko poszło dobrze, funkcja powinna zwrócić obiekt kampa-nii jako rezultat. Jeśli dodawanie się nie powiodło (np. AdWords API nie chce przyjąć naszej kampanii), zwróconym wynikiem będzie false.

Możemy więc programować na dwa sposoby. Pierwszym jest programowanie defensywne:

if($campaignObject=addCampaign(...)){

// obsługa błędu

}

print_r($campaignObject);

a drugim straight-forward programming:

$campaignObject = addCampaign(...);

print_r($campaignObject);

Wszystkie błędy są wrzucane na stos błędów, który przechowuje informacjeo nich w standardowy sposób, jako obiekty błędów. Daje to dużą elastycz-ność gromadzenia i przetwarzania da-nych o błędach, gdyż na podstawie tych obiektów łatwo możemy np. wyge-nerować plik tekstowy, HTML czy XML zawierający potrzebne informacje, co umożliwi sporządzanie raportów.

Dalsze możliwościNa podstawie dotychczasowej wie-dzy, używając APIlity stworzysz wy-godną stronę WWW (frontend) do za-rządzania swoim kontem, narzędzie doprzesyłania całego zestawu słów klu-czowych za jednym zamachem (bulk uploader), aplikację pozwalającą spo-rządzać raporty jednym kliknięciem i wiele innych. Warto też wspomnieć o możliwości eksportu słów kluczo-wych do plików Excela lub ich importuz Excela do bazy AdWords.

Wielu reklamodawcom to wystarcza: prawdziwe możliwości zaczynają się jed-nak dopiero przy wprowadzeniu elemen-tów interakcji i automatyzacji.

Budujemy większy projektW języku osób zajmujących się rekla-mami (nie tylko internetowymi), day-

Listing 7. Krok 2: odpowiednie wydarzenie powoduje zmianę maksymalnego CPCs

<?php

// zmienne globalne określające początek i koniec przerwy na lunch (pory lunchu)

$lunchStartTime = "11:00:00";

$lunchEndTime = "13:00:00";

$sleepTime = 15; // czekaj 15 minut pomiędzy cyklami sprawdzenia

function startDaypartingDaemon($lunchStartTime, $lunchEndTime, $sleepTime,$allCriteria=null,$bidsRaised=false,$bidsLowered=false){

/* przy pierwszym uruchomieniu demona daypartingu odczytaj kryteria słów

/* kluczowych przez wywołanie API. Potem słowa kluczowe będą przekazywane

/* i zwracane przy każdym wywołaniu rekurencyjnym */

if ( is_null($allCriteria) ) { echo "Dayparting Daemon started.<br />\n"; ob_flush(); flush();

// jeśli poniższe zawiedzie, nie mamy dostępu do konta

if (!$allCriteria=getAllCriteriaOfAccount()){ exit("Brak połączenia z kontem"); }else{echo "Dane konta zostały pomyślnie załadowane.<br />\n"; } }

// sprawdź, czy nadeszła pora lunchu

$now = time(); // nadeszła pora lunchu

if (($now >= makeUnixTimeStamp($lunchStartTime)) && ($now <= makeUnixTimeStamp($lunchEndTime))) {

// jeśli stawki nie zostały podniesione, to je podnieś

if (!$bidsRaised) { if ( !raiseBids($allCriteria)) { exit("Błąd podnoszenia maksymalnego CPC dla słowa kluczowego."); }else{ echo "Pora lunchu rozpoczęta, <font color='red'> podnoszę</font> stawki.<br />\n";

$bidsRaised = true;

$bidsLowered = false;

}

} else echo "Stawki zostały już podniesione. Pora lunchu nadal trwa.<br />\n";

// czas pracy; jeśli stawki nie zostały jeszcze obniżone, to je obniż

}else{ if (!$bidsLowered) { if (!lowerBids($allCriteria)) { exit("Błąd obniżania maksymalnego CPC dla słów kluczowych."); }else{ echo "Pora lunchu zakończona, <font color='green'> obniżam</font> stawki.<br />\n";

$bidsLowered = true; $bidsRaised = false;

}

}else echo "Stawki został już obniżone. Czas pracy wciąż trwa.<br />\n"; }

// wypisz zawartość bufora i poczekaj

ob_flush();

flush();

sleep($sleeptime * 60);

// wywołanie rekurencyjne; uwaga: talblica $allCriteria jest już wypełniona,

// a bity statusu przechowują informację o tym, czy stawki zostały zmienione

startDaypartingDaemon($lunchStartTime, $lunchEndTime, $sleepTime,

$allCriteria, $bidsRaised, $bidsLowered);

}

// ta funkcja konwertuje czas w formacie (12:00:00) na uniksowy timestamp

function makeUnixTimeStamp($isoTime) { $timeComponents = split(":", $isoTime); return mktime($timeComponents[0], $timeComponents[1],$timeComponents[2], date("m"), date("d"), date("Y")); }

// uruchom demona (będzie działał “w nieskończoność”)

startDaypartingDaemon($lunchStartTime, $lunchEndTime, $sleepTime);

?>

Page 31: PHPSolutions_04_2006_PL

Google Apility

www.phpsolmag.org 31

Początki

PHP Solutions Nr 4/2006

parting polega na dzieleniu dnia na określone części, różniące się rodza-jem wyświetlanych reklam. Reklamy te są często adresowane do określo-nego odbiorcy, z uwzględnieniem wy-konywanych przez niego o określonejporze zajęć. Pamiętacie o Alicji, któ-ra prowadzi sklep rowerowy? Więk-szość rowerzystów cięzko pracuje (lub się uczy) w ciągu dnia, z małą przerwą na odpoczynek (lunch) i surfowanie po Sieci. Dobrym pomysłem byłoby więc podnoszenie maksymalnego CPC w tej porze dnia, aby zwiększyć krąg od-biorców reklamy oraz obniżanie CPC (a nawet całkowite zawieszanie reklam)w momentach o większej stagnacji.

KoncepcjaNaszym celem jest stworzenie najprost-szego demona zajmującego się daypar-tingiem. Będzie on działał przez całą do-bę, a w godzinach pomiędzy 11 a 13 pod-nosił CPC każdego kryterium słowa klu-czowego np. o 20 groszy.

Krok 1: nasze zamierzenie zrealizu-jemy na poziomie kryteriów słów kluczo-wych konta Alicji, co jest bardzo proste: wystarczy rozszerzyć nasz przykład Hel-lo World (Listing 6). Ponieważ potrzebna nam jest jedynie lista wszystkich kryte-riów słów kluczowych, nie musimy prze-chowywać pochodzenia każdego z nich ani tworzyć w tym celu rozbudowanej ta-blicy (informację o pochodzeniu możemy zresztą bardzo łatwo odtworzyć na pod-stawie pola belongsToAdGroupID każdego obiektu kryterium). Tworząc nasz skrypt, wykorzystamy taktykę programowania defensywnego.

Wykonanie kroku 1. jest niezbęd-ne, aby odczytać dane po raz pierw-

szy. Nie musimy jednak tego robić każ-dego dnia, o ile nie zmieniamy naszych słów kluczowych. W zastosowaniach praktycznych, dane byłyby przechowy-wane w lokalnej bazie danych (działają-cej po stronie naszego klienta AdWords API) i okresowo aktualizowane na pod-stawie informacji z AdWords. W naszej prostej aplikacji, jednorazowo pobiera-my kryteria słów kluczowych w funk-cji, która omówimy w następnym kroku. Funkcja ta następnie będzie wywoływa-na rekurencyjnie (ze swojego wnętrza). Lista słów kluczowych zostanie pobra-na z serwera jedynie za jej pierwszym wywołaniem, natomiast przy kolejnych będziemy ją przekazywać przez para-metr tej funkcji.

Krok 2: gdy już mamy wszystkie kryteria słów kluczowych, musimy zde-finiować wydarzenia powodujące pod-niesienie maksymalnego CPC i jego ponowne obniżenie. U nas są nimi po-czątek i koniec pory lunchu. Demon

Listing 9. Przykładowy plik pośredniczący w PHP

<?php header("Content-type:application/xml; Charset=UTF-8"); ?><?php

echo utf8_encode("<?xml version='1.0' encoding='UTF-8'?>\n"); include('./apility/apility.php'); $authenticationContext->setEmail($_POST['email']);

$authenticationContext->setPassword($_POST['password']);

$authenticationContext->setToken($_POST['developerToken']);

$authenticationContext->setClientEmail($_POST['clientEmail']);

$campaignObject = createCampaignObject($_POST['id']);

if (!$campaignObject) { $fault = array_pop($faultStack);

$fault->printFault();

}else { $xml .= $campaignObject->toXml(); } echo utf8_encode($xml);?>

Listing 8. Krok 3: funkcje modyfikujące obiekty

<?php

function raiseBids($allCriteria){ foreach($allCriteria as $criterion) {

if(!$criterion-> setMaxCpc(

$criterion->

getMaxCpc()+0.2))

return false; }

return true; }

function lowerBids($allCriteria){ foreach($allCriteria as $criterion) {

if(!$criterion->setMaxCpc( $criterion->

getMaxCpc()-0.2))

return false; } return true; }?>

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

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

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

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

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

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

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

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

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

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

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

������

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

�����������

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

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

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

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

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

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

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

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

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

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

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

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

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

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

������

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

�����������

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

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

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

��������

��������

�������

���

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

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

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

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

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

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

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

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

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

Rysunek 3. Zasada działania APIlitAx

Page 32: PHPSolutions_04_2006_PL

Google Apility

www.phpsolmag.org32

Początki

PHP Solutions Nr 4/2006

zajmujący się daypartingiem będzie re-gularnie sprawdzał, czy ta pora nade-szła, czy nie. Jeśli nadeszła, demon podnosi stawki (tylko raz danego dnia), a jeśli się skończyła, obniża je (znów, tylko raz dziennie). We wszystkich in-nych przypadkach demon powinien po prostu kontynuować swoje działanie. Aby sprawdzić aktualną godzinę, użyje-my PHP-owej funkcji time(), natomiast do porównywania czasu wykorzysta-my uniksowy format timestamp (zobaczListing 7).

Krok 3: pozostało już tylko utworzenie funkcji modyfikujących maksymalne CPC: raiseBids(), która służy do jego podno-szenia oraz lowerBids(), która będzie je obniżała (Listing 8).

Wady tego podejściaNasz demon zajmujący się daypartingiem jest gotowy. Jako, że jest to prosty przy-kład, moglibyśmy w nim wiele poprawić: przykładowo, moglibyśmy użyć bazy da-nych do lokalnego przechowywania kry-teriów słów kluczowych.

Pamiętajmy, że musimy przechowy-wać listę wszystkich kryteriów, a pro-tokół HTTP jest bezstanowy (ang. sta-teless), co znaczy, że przy przełado-waniu strony tracimy wszystkie dane.Moglibyśmy zapisywać je w zmiennych sesyjnych, lecz nie jest to dobrym roz-wiązaniem, gdyż powoduje spadek wy-dajności wraz ze wzrostem ilości da-nych. Dałoby się również każdorazow ściągać wszystkie dane, ale jest to zły pomysł, gdyż jest czasochłonny oraz oznacza konieczność wysyłania ko-lejnych zapytań do serwera AdWords,a każde z nich zmniejsza dostępną kwo-tę. Krótko mówiąc, potrzebujemy trwałej warstwy danych (ang. persistence layer) – zespół twórców APIlity ma ją w swoich planach. Powinna ona pozwalać na ła-twą integrację z bazą danych. Pod adre-sem http://www.adoptimize.de/glossar/mysql-4.1.11.sheme.apility.0.4.html obej-rzymy wstępną wersję schematu ba-zy danych (w postaci kwerend tworzą-cych tablice i wprowadzających przykła-dowe dane).

Istnieje jeszcze inna wada obecne-go podejścia: każda operacja zajmuje czas i gdy jest wykonywana przez Ad-Words API, użytkownik musi czekać. Choć nie możemy przyspieszyć odpo-wiedzi AdWords API, powinniśmy móc wysłać do niego skumulowaną listę żą-dań (zapytań) i pozwolić użytkowniko-wi na wykonywanie innych czynności w tym czasie. Przykładowo, jedno żą-danie może pobierać obszerny raport, podczas gdy drugie będzie wysyłało zestaw nowych kampanii. Wprawdzie PHP nie jest wielowątkowe, ale uży-wając kombinacji różnych technik mo-żemy emulować wielowątkowe zapyta-nia do API.

APIlity + AJAX = APIlitAxJak sama nazwa wskazuje, AJAX (Asynchronous Javascript And XML) – jedno z najmodniejszych w tym ro-ku słów ze świata PHP (o którym pisa-liśmy w numerze 1/2006 w artykułach: AJAX – wyjąkowo interaktywne i wy-

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

Page 33: PHPSolutions_04_2006_PL

Google Apility

www.phpsolmag.org 33

Początki

PHP Solutions Nr 4/2006

dajne aplikacje WWW oraz advAJAX, czyli praktyczne zastosowanie techno-logii AJAX), pozwala na asynchronicz-ne przesyłanie danych (stąd A w na-zwie). X z kolei oznacza wykorzystanie XML-a. APIlity pozwala na łatwą kon-wersję obiektów PHP-owych na XML: $someAPIlityObject->toXml();. Umoż-liwia to przesyłanie obiektów APIlity ja-ko odpowiedzi na zapytania AJAX-a. Wreszcie, J oznacza JavaScript.

Jak połączyć AJAX-a i APIlity? Jest to całkiem proste: po stronie ser-wera mamy AdWords API, a po stronie klienta –przeglądarkę WWW. Pomię-dzy nimi jest APIlity. W przeglądarce WWW mamy zupełnie zwyczajną stro-nę internetową zawierającą formularze i tekst. Za każdym razem, gdy użyt-kownik wysyła zmiany, raport, itd., uru-chamiane jest żądanie XML HTTP Re-quest : przesyła ono parametry funk-cji APIlity jako zwyczajne dane HTTP POST.

Istnieje też pośrednicząca w trans-misji strona PHP, która przetwarza dane POST i wywołuje odpowiednią funkcję natywną APIlity. Dostarcza ona również rezultaty funkcji w forma-cie XML lub JSON, co pozwala na ła-twe przetwarzanie odpowiedzi przez przeglądarkę internetową. Ponieważ ten skrypt PHP znajduje się na lokal-nym serwerze WWW, na którym zain-stalowano również APIlity, wirtualizu-je on w pewnym sensie AdWords API, pozwalając jednemu użytkownikowi wysyłać wiele żądań.

WymaganiaAby APIlitAx w ogóle działało, APIli-ty nie może być w trybie verbous (wy-pisywanie komunikatów na temat wy-konywanych czynności, błędów, itd.); zwracanie przez PHP komunikatówo błędach również musi być wyłączone, gdyż każda taka wiadomość zniszczy-łaby strukturę XML tworzoną w pośred-niczącym skrypcie PHP. Aby tego unik-nąć, APIlity oferuje tzw. Silence Stealth Mode, w którym wszystkie ostrzeżenia PHP, jak i komunikaty APIlity zostają wyłączone.

Błędy AdWords API są natomiast cały czas przechwytywane, ale nie wyświetlane. Jeżeli używamy APIli-ty jako biblioteki osadzonej (ang. em-bedded), dobrym pomysłem jest prze-chowywanie informacji o tych błędach w XML-u.

Dodatkowo, z natury Silence Ste-alth Mode wynika, że błędy niezwiąza-ne z API są trudne do znalezienia. Po-winniśmy więc przyłożyć szczególną uwagę do zapobiegania im: pośredni-czący skrypt PHP powinien działaći przynajmniej generować poprawnego XML-a – nawet, jeżeli nie wysłano pa-rametru POST.

Najłatwiej to sprawdzić uruchamia-jąc ten plik jako niezależny, poza na-szym systemem. W znalezieniu typo-wych błędów, takich jak variable undec-lared (niezadeklarowana zmienna) po-może nam też wyłączanie i włączanie Silence Stealth Mode podczas debu-gowania skryptu.

Przykładowy skrypt pośredniczący PHPOpiszemy przykładowy skrypt pośred-niczący PHP (zob. Listing 9). Rozpo-czynamy go od wysłania nagłówka, (funkcją header()), w którym zdeklaru-jemy zawartość strony wynikowej ja-ko XML (a nie, jak domyślnie, HTML) i ustawimy zestaw znaków na UTF-8. Od tej chwili musimy ściśle przestrze-gać reguł XML-a: każdy tekst wypisa-ny przez PHP (np. komunikat o błędzie czy ostrzeżenie) spowoduje błąd jego przetwarzania. W rezultacie, żądanie XHML HTTP Request nigdy nie przy-niesie wyniku i będzie czekało w nie-skończoność – jest to jeszcze jeden powód dla stosowania programowania defensywnego.

Następnie dołączamy bibliotekę APIlity (apility.php) i dokonujemy uwie-rzytelnienia na serwerze AdWords. Uwierzytelnienie to odbywa się w spo-sób dynamiczny, przy użyciu obiek-tu globalnego $authenticationContext oraz danych HTTP POST. Jeśli wystą-pi błąd, wyświetlony zostanie komuni-kat o błędzie AdWords API (komuni-katy o błędach muszą być w formacie XML). Jeżeli natomiast wszystko pój-dzie dobrze, odebrany obiekt kampa-nii APIlity zostanie przekonwertowa-ny na XML.

Niezbędny kod źródłowy JavaScript oraz działająca aplikacja przykładowa zostały zamieszczone na stronach pro-jektu na licencji BSD.

Podsumowanie Możliwości Google AdWords API są ogromne: możemy śmiało powiedzieć, że jego wprowadzenie stworzyło nową jakość w zarządzaniu reklamami in-ternetowymi. Wiedza o tym systemie, którą Wam przekazaliśmy, stanowi so-lidną podstawę do tworzenia bardziej rozbudowanych projektów: zachęcamy do własnych eksperymentów i życzy-my samych sukcesów w biznesie. n

Tworzymy konto AdWordsZaczynamy od założenia konta AdWords. W tym celu idziemy na stronę głównąAdWords i postępujemy wg wskazówek kreatora: https://adwords.google.com/select/starter/signup/Fork.

Po utworzeniu i potwierdzeniu konta AdWords, musimy je aktywować: w tym ce-lu trzeba wybrać opcje płatności i wysłać szczegółowe informacje z nimi związane(billing information). Odtąd mamy możliwość zarządzania reklamami, ale jeszcze nie przez API: do tego ostatniego musimy jeszcze uzyskać dostęp. W tym celu wchodzi-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 kontaMy Client Center.. Następnie, w zakładce My Account pojawi się nowa strona zatytu-łowana AdWords API Center: będzie ona zawierała identyfikator programisty (develo-per token) oraz informacje o tym, jak używamy API (w tym kwota). W zaczęciu pracyz AdWords API pomogą nam następujące tutoriale:

● http://services.google.com/awp/en_us/breeze/264836/index.html,● http://services.google.com/awp/en_us/breeze/267718/index.html,● http://services.google.com/awp/en_us/breeze/264848/index.html.

Odpowiedzi na wiele spośród naszych pytań uzyskamy również na grupie dyskusyjnej de-veloperów AdWords API: http://groups.google.com/group/adwords-api.

Thomas Steiner pracuje dla Google, Inc. jako inżynier oprogramowania i po-pularyzator AdWords API. Odpowiada za opensourcowy projekt APIlity.Kontakt z autorem: [email protected]

O autorze

Page 34: PHPSolutions_04_2006_PL

Bezpieczeństwo

www.phpsolmag.org34 PHP Solutions Nr 4/2006

Kryptografia w PHP Bezpieczeństwo

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

Wikipedia definiuje kryptogra-fię jako „naukę zajmującą się układaniem szyfrów”. W prak-

tyce jest to konwersja jednej informacjiw drugą za pomocą złożonych obliczeń ma-tematycznych (szyfrowanie) oraz ewentual-na konwersja tejże informacji do stanu pier-wotnego (deszyfrowanie). W kontekście aplikacji internetowych jest to dość często spotykana dziedzina, zarówno w małych, jak i w dużych, komercyjnych serwisach – podczas logowania się do banku interne-towego wpisywane przez nas dane przesy-łane są bezpiecznym, szyfrowanym łączem SSL, a wysyłając poufne wiadomości e-ma-il korzystamy z systemu PGP, chroniącego ich treść przed osobami trzecimi.

PHP zawiera szereg funkcji pozwala-jących na szyfrowanie i generowanie ha-szów dowolnych łańcuchów znaków czy zawartości plików. Część z nich dostęp-na jest natywnie, jednak duża większość przy wykorzystaniu zewnętrznego rozsze-rzenia, dołączonego do PHP. W tym ar-

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

tykule pokażemy, w jaki sposób możnaefektywnie korzystać ze wspomnianych funkcji na podstawie trzech praktycznych przykładów.

System bezpiecznego logowaniaZacznijmy od stworzenia bezpieczne-go systemu logowania. Jest to idealny przykład na wykorzystanie funkcji kryp-tograficznych dostępnych w PHP. W na-

Kryptografia w PHPŁukasz Lach, Michał Stanowski

W SIECI

l http://php.net/mcrypt – MCrypt

l http://php.net/mhash – MHash

l http://phpsec.org/ – PHP Security Consortiuml http://php.net/security

– Bezpieczeństwo PHPl http://pajhome.org.uk/crypt/

md5– funkcje haszujące dla JS

Stopień trudności: lll

Co należy wiedzieć...Przydatna będzie podstawowa znajo-mość zagadnień kryptografii oraz PHP.

Co obiecujemy...Z artykułu dowiesz się, jak zbudować bezpieczny system logowania oraz apli-kację do składowania i szyfrowania pli-ków na serwerze WWW. Na przykła-dach tych przedstawimy najpopularniej-sze i najlepsze rozwiązania kryptogra-ficzne w PHP.

Page 35: PHPSolutions_04_2006_PL

Bezpieczeństwo

www.phpsolmag.org34 PHP Solutions Nr 4/2006

Kryptografia w PHP Bezpieczeństwo

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

szym, bezpiecznym systemie hasło wpisa-ne przez użytkownika przesyłane będziew formie funcji skrótu, nawet dwukrotnie: za pierwszym razem przez algorytm MD5, a następnie HMAC-MD5.

Przesyłana wartość będzie prawidłowa tylko i wyłącznie raz, ponieważ klucz pu-bliczny, potrzebny w procesie szyfrowania, będzie za każdym razem unikalny – dyna-micznie tworzony po stronie serwera.

Taki system okaże się bardzo pożą-dany, kiedy na serwerze nie posiadamy SSL, a chcemy przesyłać zaszyfrowane dane. W przeciwieństwie do tradycyjnego systemu logowania, ten skutecznie ochro-ni użytkowników przez atakami typu snif-fing, polegających na podsłuchaniu infor-macji wymienianych pomiędzy klientema serwerem, a co w konsekwencji pozwa-la na przejęcie wpisanej nazwy użytkowni-ka oraz hasła i wykorzystanie ich do za-logowania. Dodatkowo, wszystkie hasła przechowywane będą jedynie w postaci skrótu MD5, także ich ewentualne pozna-nie przez osoby niepowołane jest całkowi-cie niegroźne (przyjmując oczywiście, że hasła mają przyzwoitą długość, czyli przy-najmniej 10 znaków). Podstawowe infor-macje dotyczące funkcji haszujących za-wiera Ramka Algorytmy i funkcje haszu-jące. Na Rysunku 1 przedstawiliśmy sche-mat tworzonego systemu bezpiecznego logowania.

W naszym przykładzie skorzystamy również z wersji HMAC algorytmu MD5. HMAC-MD5 jest rozszerzeniem funkcji skrótu MD5 i pozwala na dołączenie do-datkowego, dowolnego klucza. Wygenero-wany w ten sposób łańcuch znaków mo-że być wówczas przesłany do odbiorcy, który, posiadając owy sekretny klucz, mo-że sprawdzić poprawność przesłanegociągu. W naszym systemie logowania al-

gorytm HMAC wykorzystamy do wygene-rowania skrótu hasła, dołączając do nie-go losowy klucz wygenerowany po stro-nie serwera. PHP nie posiada implemen-tacji HMAC algorytmu MD5, także musimy ją najpierw napisać opierając się o dostęp-ną dokumentację (http://www.ietf.org/rfc/rfc2104.txt). Kod źródłowy funkcji hmac_md5 przedstawiliśmy na Listingu 1.

Dodatkowym atutem przedstawio-nej implementacji jest to, że pozwala nie tylko na wygenerowanie skrótu HMAC-MD5, ale również HMAC-SHA1, tak więc mamy do dyspozycji dwa dodatkowe al-gorytmy do wykorzystania w naszych projektach.

W naszym systemie zakładamy, że przesłane przez użytkownika hasło w po-staci skrótu HMAC-MD5 będzie prawdzi-we tylko i wyłącznie raz. Ponowne otwar-cie czy przeładowanie strony spowoduje

wygenerowanie nowego klucza, a co za tym idzie, stworzony w oparciu o niego hasz będzie miał całkiem inną wartość. Co nam to daje? Otóż nawet w przypad-ku podsłuchania danych przez osobę trzecią, zdobyte w ten sposób informa-cje będą całkowicie bezużyteczne. Skup-my się najpierw na części HTML kodu źródłowego, która umieszczona jest naListingu 2. W pierwszych linijkach skryp-tu dołączamy plik języka JavaScripto nazwie md5.js, który zawiera dwie klu-czowe dla nas funkcje – hex_md5 oraz hex_hmac_md5, będące odpowiednika-mi funkcji md5 i hmac_md5, dostępnych od strony skryptu PHP. Poniżej umieszczo-ny jest kod źródłowy funkcji parseForm, która wywoływana jest tuż przed przesła-niem formularza. Formularz ten zawarty jest w części body dokumentu i zawiera cztery istotne pola:

• key – pole typu hidden zawierają-ce wygenerowany po stronie serwe-ra unikalny klucz, wykorzystywany na etapie logowania przez wersję HMAC funkcji haszującej,

• js – pole typu hidden, którego wartość określa, czy przeglądarka użytkowni-ka obsługuje JavaScript. Jego domyśl-ną wartością jest 0 (zero), zamieniane przed przesłaniem na 1 (jeden) w koń-cowych linijkach funkcji doLogin,

• username – pole typu text, zawierają-ce wpisaną nazwę użytkownika,

Algorytmy i funkcje haszująceFunkcją haszującą (czy też funkcją skrótu) nazywamy funkcję, która dowolnemu łańcu-chowi znaków przyporządkowuje inną, unikalną wartość nazywaną powszechnie haszem (ang. hash) lub skrótem. Algorytm skrótu jest jednokierunkowy, otrzymanie pierwotnej war-tości na podstawie wcześniej wygenerowanego skrótu wymaga ogromnej mocy oblicze-niowej i jest praktycznie niemożliwe. Istotną właściwością funkcji skrótu jest to, że zmiana choć jednego znaku łańcucha wejściowego, bez względu na jego długość, powoduje wy-generowanie całkiem innej wartości skrótu.

Nie posiadając dołączonego do PHP rozszerzenia MHash mamy do dyspozycji pięć standardowo dostępnych funkcji pozwalających generować hasze dla trzech, najbardziej popularnych algorytmów – CRC32, MD5 oraz SHA1. Najnowsza wersja PHP5 udostęp-niać będzie ponadto rozszerzenie Hash (http://php.net/hash), które zawiera praktycznie wszystkie algorytmy dostępne w MHash, a przy tym jest domyślnie zintegrowane z PHP i nie wymaga żadnych dodatkowych bibliotek czy konfiguracji. Jednak póki co rozszerze-nie Hash jest dostępne wyłącznie do testów i nie powinno być powszechnie wykorzysty-wane tym bardziej, że zarówno sama zasada jego działania, jak i lista dostępnych algoryt-mów może się jeszcze zmienić.

Mimo to, dostępne natywnie w PHP algorytmy haszowania są w zupełności wystar-czające, bez różnicy na rozmiar tworzonego projektu. Algorytmy takie jak CRC32 i MD5 są powszechnie i z powodzeniem wykorzystywane do tworzenia sum kontrolnych plików lub poufnych danych, które muszą być dostępne jedynie w postaci umożliwiającej walida-cję (np. hasła). Tabela 1 zawiera algorytmy dostępne natywnie w PHP oraz poprzez roz-szerzenie MHASH.

�����

���������

�����

�����

�����������

����������

����������

���������

���������

����������

�����������

���������

��� ��������

������

������

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

Rysunek 1. Schemat działania systemu bezpiecznego logowania

Page 36: PHPSolutions_04_2006_PL

Kryptografia w PHPBezpieczeństwo

www.phpsolmag.org36 PHP Solutions Nr 4/2006

• password – pole typu password, zawie-rające wpisane hasło.

Rola pola js jest bardzo ważna – w przy-padku, gdy jego wartość jest równa 0, wie-my, że dane nazwy użytkownika i hasła przesłane zostały w postaci jawnej, jed-nak mimo to nadal jesteśmy się w stanie bezproblemowo zalogować. Wróćmy jed-nak do kodu źródłowego omawianej funk-cji. W przypadku nie podania nazwy użyt-kownika lub hasła, wyświetlamy stosowny komunikat i przenosimy kursor do pustego pola. W innym wypadku modyfikujemy za-wartość pola zawierającego hasło na hasz HMAC-MD5 skrótu MD5 wpisanego hasła oraz unikalnego klucza, dostępnego w po-lu key. Dodatkowo ustawiamy wartość po-la js na 1 i zezwalamy na wysłanie formu-larza zwracając wartość true.

Zobaczmy teraz, co dzieje się po stro-nie serwera – pełny kod PHP opisywane-go przykładu znajduje się na Listingu 3. Główna część skryptu to funkcja doLogin, której zadaniem jest sprawdzenie nazwy użytkownika i hasła. W przypadku nie po-dania którejkolwiek z tych danych zwraca-my wartość -1. Następnie sprawdzamy po-prawność przesłanego z poziomu formula-rza klucza (pole key) z tym zapisanymw sesji. Jeśli do tego momentu wszyst-ko zakończyło się pomyślnie, nawiązuje-my połączenie z bazą danych i pobiera-my nazwę użytkownika i skrót MD5 hasła. Jeśli osoba o poszukiwanej nazwie użyt-kownika nie istnieje, zwracamy wartość -3.W innym wypadku dokonujemy sprawdze-nia poprawności przesłanego hasła w za-leżności od tego, czy przeglądarka użyt-kownika wykonała funkcję JavaScript. Je-śli hasło się zgadza, zwracamy nazwę użytkownika, jeśli nie, zwracamy wartość -4. Wspomniane ujemne wartości liczbo-we są wykorzystywane w głównej czę-ści kodu do wyświetlenia odpowiedniego komunikatu o błędzie. W przypadku, gdy zwrócona przez funkcję doLogin wartość jest łańcuchem znaków, traktujemy ją ja-ko nazwę użytkownika i zapisujemy do zmiennej sesji przekierowując jednocze-śnie użytkownika na właściwą stronę pa-nelu administracyjnego. Na końcu modyfi-kujemy wartość zmiennej klucza zapisując w niej nową, unikalną wartość.

Reasumując: z pomocą jednej funkcji haszującej stworzyliśmy mały objętościo-wo skrypt o wielkich możliwościach, za-pewniający użytkownikom logującym się do naszego serwisu wysoki poziom bez-

pieczeństwa. Przyjrzyjmy się teraz innej funkcji dostępnej natywnie w PHP, pozwa-lającej na szyfrowanie łańcuchów znaków za pomocą takich algorytmów jak DES czy Blowfish. Mowa tu o funkcji crypt

Podstawy kryptografii – cryptWbrew przypuszczeniom funkcja crypt pozwala na szyfrowanie w jedną stronę, nie ma funkcji decrypt, która pozwoliła-

Listing 1. Implementacja algorytmu HMAC-MD5 dla PHP

<?php

define('HMAC_MD5', 1);

define('HMAC_SHA1', 2);

function hmac($string, $key, $algorithm = HMAC_MD5) { $algorithm = $algorithm == HMAC_MD5 ? 'md5' : 'sha1';

if (function_exists('hash_hmac')) return hash_hmac($algorithm, $string, $key); if (isset($key{64})) $key = pack('H*', $algorithm($key));

$key = str_pad($key, 64, "\0");

$iPad = str_repeat("\x36", 64);

$oPad = str_repeat("\x5c", 64);

return $algorithm(($key ^ $oPad).pack('H*', $algorithm(($key ^ $iPad) . $string)));

}

function hmac_md5($string, $key) { return hmac($string, $key, HMAC_MD5); }

?>

Listing 2. Kod HTML systemu bezpiecznego logowania

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"

"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="PL">

<head>

<title>PHP Solutions :: hmac_md5</title>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<script type="text/javascript" src="md5.js"></script>

<script type="text/javascript">

function $(id) { return document.getElementById(id) } function parseForm() { var u = $('username'), p = $('password'); if (u.value == '') { alert('Podaj nazwę użytkownika.');

u.focus(); return false; }

if (p.value == '') { alert('Podaj hasło.');

p.focus(); return false; }

p.value = hex_hmac_md5($('key').value, hex_md5(p.value));

$('js').value = 1; return true; }

</script></head><body>

<?php

if (!empty($errorString)) echo '<div id="error">'.$errorString.'</div>';?>

<form method="post" action="login.php" onsubmit="return parseForm()"><div>

<input type="hidden" name="key" id="key" value="<?php echo $_

SESSION['key'] ?>" />

<input type="hidden" name="js" id="js" value="0" />

<label for="username">Nazwa użytkownika:</label>

<input type="text" name="username" id="username" /><br />

<label for="password">Hasło:</label>

<input type="password" name="password" id="password" /><br />

<input type="submit" value="OK" />

</div></form></body></html>

Page 37: PHPSolutions_04_2006_PL

Kryptografia w PHP Bezpieczeństwo

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

by na odszyfrowanie informacji. W prakty-ce więc jest to kolejna funkcja haszująca. Zobaczmy, jak na jej podstawie możemy zbudować znacznie prostszy skrypt uwie-rzytelniania, oparty o protokół HTTP i al-gorytm DES.

Zasada działania każdego systemu uwierzytelniania jest niemal identyczna. Po podaniu nazwy użytkownika i hasła skrypt dokonuje porównania tych danych z informacjami przechowywanymi w bazie danych lub pliku. Najczęściej, ze wzglę-dów bezpieczeństwa, hasło użytkownika nie jest przechowywane w postaci jawnej – do bazy zapisujemy hasz hasła. Do two-rzenia tego rodzaju haszy możemy użyć funkcji crypt. Jest ona standardowo do-stępna w każdej instalacji PHP, więc nie ma potrzeby ponownej komplikacji PHP, czy modyfikacji pliku konfiguracyjnego.

Spróbujmy na początek stworzyć pro-sty przykład oparty o funkcję crypt. Jego kod źródłowy znajduje się na Listingu 4.

Przy każdym przeładowaniu strony/skryptu uzyskamy unikalną wartość ha-sza. Dzieje się tak dlatego, że za każdym razem tworzony jest losowy ciąg znaków – $salt – niezbędny w procesie tworze-nia skrótu. Od drugiego argumentu wywo-łania omawianej funkcji zależy jaki algo-rytm zostanie wykorzystany. I tak dla dwu-znakowej wartości zmiennej $salt będzie to standardowy algorytm DES, dla dzie-więcioznakowej – algorytm DES w wersji rozszerzonej. Aby wykorzystać algorytm MD5, za zmienna $salt musi składać się z dwunastu znaków z początkowym łań-cuchem $1$, zaś dla algorytmu Blowfish będzie to szesnaście znaków rozpoczy-nając od $2$ lub $2a$. Zmienna $salt jest później dołączana na początek wygenero-wanego skrótu.

Wróćmy do naszego przykładu,w którym chcemy stworzyć skrypt uwie-rzytelniania poprzez protokół HTTP. Na-zwy użytkowników i ich hasła trzymane będą w pliku tekstowym (Listing 5).

Warto wspomnieć najpierw o cieka-wej właściwości funkcji crypt. Jeśli spró-bujemy stworzyć hasz informacji przesła-nej przez użytkownika (przykładowo ha-sła) podając jako $salt hasz trzymanyw bazie danych lub pliku (wzorzec hasła), funkcja powinna zwrócić nam identyczny skrót. Sytuacja ta ma miejsce tylko wtedy, gdy podane hasło jest zgodne z tym wy-korzystywanym przy tworzeniu skrótu po raz pierwszy. Wykorzystamy tę własność w naszym skrypcie uwierzytelniania.

Listing 3. Kod PHP systemu bezpiecznego logowania

<?php

function doLogin() { // Nie podano nazwy użytkownika lub hasła

if (empty($_POST['username']) || empty($_POST['password'])) return -1; $js = $_POST['js'];

$key = $_SESSION['key'];

// Klucz przesłany w rządaniu HTTP nie zgadza się z tym

// zapisanym w sesji

if ($key != $_POST['key']) return -2;

// Wyszukanie rekordu administratora

// o podanej nazwie użytkownika

$mysql_handle = mysql_connect('host', 'username', 'password')

or die(mysql_error()); mysql_select_db('test', $mysql_handle) or die(mysql_error()); $mysql_result = mysql_query('SELECT username, password FROM admin

WHERE username = "'.mysql_real_escape_string( $_POST['username'], $mysql_handle).'"');

// Brak rekordu administratora

// o podanej nazwie użytkownika

if (mysql_num_rows($mysql_result) == 0) return -3; $row = mysql_fetch_assoc($mysql_result);

// Walidacja hasła w zależności od tego,

// czy wykorzystany został JavaScript

if ($js) { require 'hmac_md5.php'; if (hmac_md5($row['password'], $key) != $_POST['password']) return -4; } else { if (md5($_POST['password']) != $row['password']) return -4; }

// Logowanie powiodło się, zwracamy nazwę użytkownika

return $row['username'];}

session_start();

$errorString = '';

$loginResult = doLogin();

switch ($loginResult) { case -1: break; case -2: $errorString = 'Nieprawidłowa wartość klucza'; break; case -3: $errorString = 'Nieprawidłowa nazwa użytkownika'; break; case -4: $errorString = 'Nieprawidłowe hasło'; break; default: $_SESSION['username'] = $loginResult;

header('Location: http://server.com/admin/panel.php');

die(); }

$_SESSION['key'] = md5(uniqid(rand(), true));

?>

Page 38: PHPSolutions_04_2006_PL

Kryptografia w PHPBezpieczeństwo

www.phpsolmag.org38 PHP Solutions Nr 4/2006

Pamiętajmy, że uwierzytelnianie oparte na protokole HTTP dostępne jest jedynie wte-dy, jeśli nasze PHP działa jako moduł Apa-che. Jeżeli jest inaczej (czyli PHP działają-ce jako CGI), nasz przykład nie zadziała.

Przyjrzyjmy się Listingowi 6. Na po-czątku musimy sprawdzić, czy użytkownik przesłał już swój login i hasło. Jeżeli nie, wysyłamy odpowiednie nagłówki HTTP, które poinformują przeglądarkę, by wy-świetlić odpowiednie okno logowania. Te same nagłówki będą wysyłane, jeżeli po-damy błędne dane logowania. Gdy użyt-kownik przesłał nam już odpowiednie da-ne, musimy pobrać zawartość pliku, któ-ry pełni funkcje naszej bazy i przechowuje hasła użytkowników. Przy pomocy funkcji explode podzielimy pobrane dane, oddzie-lając nazwy użytkowników i hasła. Ostat-ni krok to pętla, która przechodzi przez wszystkie rekordy, sprawdzając, czy użyt-kownik może być dopuszczony do danej strony. Aby sprawdzić poprawność hasła, wykorzystujemy właściwość funkcji crypt opisaną wcześniej.

Jak widać może być ona bez proble-mu wykorzystania w celu stworzenia pro-stego systemu uwierzytelniania. Mimo wszystko jednak lepszym pomysłem jest korzystanie z algorytmów takich jak MD5 czy SHA1 (czyli funkcji md5, sha1), ponie-waż algorytm DES ma znacznie mniejszą złożoność, a przy tym podczas tworzenia skrótu wykorzystywane jest tylko 8 pierw-szych znaków.

Zaawansowana kryptografia – MCryptMCrypt jest rozszerzeniem PHP udostęp-niającym metody pozwalające na szyfro-wanie danych przy użyciu algorytmów ta-kich jak DES, TripleDES, Blowfish, RC2i wielu innych, lista najpopularniejszych al-gorytmów dostępnych dla tego rozszerze-nia umieszczona została w Tabeli 2. Opis instalacji i konfiguracji biblioteki znajdu-je się w Ramce Instalacja rozszerzenia MCrypt.

MCrypt jest potężnym narzędziem, dlatego też na jego podstawie stworzy-my dość rozbudowany przykład. Napi-szemy aplikację, która pozwoli nam na przechowywanie na serwerze plików dowolnego typu w postaci zaszyfrowa-nej, a pobranie ich na lokalny dysk moż-liwe będzie jedynie po podaniu właści-wego hasła, identycznego z tym uży-tym w procesie szyfrowania dokumen-tu. Wszystkie dane (czyli zaszyfrowa-

ne pliki i szczegółowe informacje o nich,takie jak wielkość czy typ MIME)przechowywać będziemy w bazie da-nych. Zapytanie SQL tworzące odpo-wiednią tabelę przedstawiliśmy na Li-stingu 7. W odpowiednich kolumnach przechowywać będziemy nazwę pliku, datę umieszczenia w bazie, typ MIME,a także długość oryginalnego pliku, klucz i sam plik w postaci zaszyfrowanej.

Na początek coś prostego – szyfrujemy fragment tekstuZanim przejdziemy do implementacji na-szej bezpiecznej składnicy danych, po-znajmy schemat działania i możliwości rozszerzenia MCrypt. Na Listingu 8 przed-stawiony został przykładowy kod PHP, któ-rego zadaniem jest szyfrowanie podanego łańcucha znaków przy pomocy klucza,a następnie jego odszyfrowanie.

Listing 4. Przykład wykorzystania funkcji crypt

<?php

$password = 'qwerty';

$salt = '$1$abcde$';

$hash = crypt($password, $salt);

echo $hash;?>

Listing 5. Format danych pliku z danymi użytkowników

admin:$1$Lp5.2//.$UMLhAWJJB9fLR.FU4gSAw1

user:$1$hI3.Wd0.$thbtd1at0auCNfn7s5Bdy1

Listing 6. Skrypt walidacji danych podczas autoryzacji

<?php

define('USERS_FILE', 'users.txt');

if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {

$users = explode("\n", file_get_contents(USERS_FILE)); foreach($users as $user) {

$user = explode(':', trim($user), 2); if($user[0] == $_SERVER['PHP_AUTH_USER']) {

if(crypt($_SERVER['PHP_AUTH_PW'], $user[1]) != $user[1]) {

header('WWW-Authenticate: Basic realm="Private"'); header('HTTP/1.0 401 Unauthorized'); exit; }

break; }

}

}

else {

header('WWW-Authenticate: Basic realm="Private"'); header('HTTP/1.0 401 Unauthorized'); exit; }

?>

Listing 7. Struktura tabeli przechowującej dane zaszyfrowanych plików

CREATE TABLE 'secure_files' ( 'id' int(11) unsigned NOT NULL auto_increment, 'name' varchar(100) default NULL, 'mime_type' varchar(30) default NULL, 'date' int(10) unsigned default '0', 'length' varchar(255) default '0', 'key' varchar(32) default NULL, 'data' text,

PRIMARY KEY ('id')) TYPE=MyISAM;

Page 39: PHPSolutions_04_2006_PL

Kryptografia w PHP Bezpieczeństwo

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

Aby zainicjować mechanizmy bi-blioteki Mcrypt, musimy wywołać funk-cję mcrypt_module_open, która jako pierw-szy argument przyjmuje nazwę algoryt-mu, z którego chcemy skorzystać. Drugii czwarty parametr to ścieżki do odpowied-nich modułów, pozwalających na skorzy-stanie z danego algorytmu. Pozostawienie ich pustych, jak w przykładzie, powodu-je wykorzystanie ścieżek zdefiniowanych w pliku konfiguracyjnym php.ini. Trzeci ar-gument definiuje tryb, w jakim ma odby-wać się szyfrowanie – my skorzystamyz trybu ecb. Wszystkie możliwe tryby szy-

frowania wraz z opisem umieszczone zo-stały w Tabeli 3. Wynik działania omawia-nej funkcji zapisujemy w zmiennej pomoc-niczej $td. Kolejnym krokiem jest utworze-nie za pomocą funkcji mcrypt_create_iv tzw. wektora inicjującego. Funkcja tawymaga podania rozmiaru wektora oraz źródła danych losowych. Najlepiej skorzy-stać ze stałej MCRYPT_RAND, ponieważ spo-sób ten działa zarówno pod systememLinux jak i Windows. W tym miejscu bi-blioteka MCrypt jest gotowa na przyjęciedanych do zaszyfrowania. Najpierw musi-my zarezerwować odpowiednią ilość miej-

sca w pamięci. Korzystamy w tym celuz funkcji mcrypt_generic_init, przekazu-jąc jej utworzone wcześniej uchwyty do modułu i wektora inicjującego, jak rów-nież wartość klucza, który będzie wyko-rzystywany w procesie szyfrowania. Klucz został przycięty do maksymalnej długo-ści obsługiwanej przez wybrany algorytm. Następnie wywołujemy funkcję mcrypt_

generic, której wynikiem jest zaszyfrowa-ny łańcuch znaków. Po wszystkim zwal-niamy zarezerwowaną pamięć funkcją mcrypt_generic_deinit i kończymy pra-cę z biblioteką MCrypt poprzez wywoła-nie mcrypt_module_close. Deszyfrowanie informacji przebiega niemal analogicznie, z tą różnicą, że zamiast funkcji mcrypt_generic stosujemy mdecrypt_generic,która przyjmuje identyczne argumenty co poprzednia.

Bezpieczna składnica danychPosiadając już niezbędną wiedzę na te-mat wykorzystania biblioteki MCrypt, na-piszmy odpowiednie funkcje do naszej aplikacji. Kod źródłowy skryptu szyfrują-cego przesyłany plik przedstawiony jest na Listingu 9.

Przed dodaniem rekordu do tabe-li sprawdzamy czy użytkownik podałwymagany klucz oraz czy sam plik został pomyślnie przesłany na serwer. Następ-nie tworzymy skrót klucza wykorzystując algorytm MD5, aby później przy pobie-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 funkcjapobierania wcześniej dodanych plików.

Rysunek 2. Strona domowa rozszerzenia MCrypt dla PHP (http://php.net/mcrypt)

Instalacja rozszerzenia MCryptJeż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.

Tabela 1. Algorytmy dostępne natywnie w PHP oraz poprzez rozszerzenie MHASH

Nazwa algorytmu Natywnie / Nazwa funkcji MHASH / Nazwa stałejALDER32 X – MHASH_ADLER32

CRC32 X – crc32( ) X – MHASH_CRC32

GOST X – MHASH_GOST

HAVAL128 X – MHASH_HAVAL128

HAVAL160 X – MHASH_HAVAL160

HAVAL192 X – MHASH_HAVAL192

HAVAL256 X – MHASH_HAVAL256

MD4 X – MHASH_MD4

MD5 X – md5( ), md5_file( ) X – MHASH_MD5

RIPEMD160 X – MHASH_RIPEMD160

SHA1 X – sha1( ), sha1_file( ) X – MHASH_SHA1

SHA256 X – MHASH_SHA256

TIGER X – MHASH_TIGER

TIGER128 X – MHASH_TIGER128

TIGER160 X – MHASH_TIGER160

Page 40: PHPSolutions_04_2006_PL

Kryptografia w PHPBezpieczeństwo

www.phpsolmag.org40 PHP Solutions Nr 4/2006

Najpierw dokonujemy porównania klu-cza nadesłanego przez użytkownikaz tym przypisanym do danego pliku. Jeśli klucze (a w praktyce ich skróty MD5) są identyczne możemy rozszyfrować zwar-tość pliku. Cały ten proces nie różni się znacznie od zwykłego szyfrowania i zostałjuż wcześniej zaprezentowany. Ponieważ użytkownik ma mieć możliwość zapisaniapliku na swój dysk, koniecznie staje się wysłanie odpowiednich nagłówków do je-go przeglądarki. Wykorzystamy do tego funkcję header oraz nagłówki Content-

Type i Content-Disposition. Na koniec, jeszcze przed wysłaniem zawartości pliku, musimy usunąć ewentualne puste znaki, które funkcje szyfrujące mogły pozostawić na końcu pliku, a które mogłoby spowodo-wać późniejsze błędy w jego odczycie. Ja-ko że znamy długość oryginalnego pliku (została zapisana w bazie danych), może-my to bez problemu wykonać przy użyciu funkcji substr.

Przedstawiony przykład pokazał, jak szybko możemy zaszyfrować nasze dane i jak przechowywać je w bazie. Mogą to być zarówno hasła jak i zawar-tości całych plików. Pamiętajmy jednak, że proces szyfrowania 40MB pliku mo-że dość znacznie obciążyć procesor,a także spowodować przepełnienie pa-mięci przydzielonej dla PHP, co w efek-cie spowoduje wyświetlenie komuni-

Listing 8. Przykład wykorzystania rozszerzenia MCrypt

<?php

// Nasz klucz i łańcuch znaków do zaszyfrowania

$key = 'tajny klucz';

$plain_text = 'bardzo poufne dane, które trzeba zaszyfrować';

// Inicjacja mechanizmów szyfrujących

$td = mcrypt_module_open('blowfish', '', 'ecb', '');

$key = substr($key, 0, mcrypt_enc_get_key_size($td)); $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);

// Szyfrowanie

mcrypt_generic_init($td, $key, $iv);

$encrypted = mcrypt_generic($td, $plain_text);

mcrypt_generic_deinit($td);

echo $encrypted; // Deszyfrowanie

mcrypt_generic_init($td, $key, $iv);

$decrypted = mdecrypt_generic($td, $encrypted);

mcrypt_generic_deinit($td);

echo $decrypted; mcrypt_module_close($td);

?>

Listing 9. Skrypt szyfrujący przesłany plik

<?php

$file = $_FILES['file'];

if($file['error'] == UPLOAD_ERR_OK && !empty($_POST['key'])) {

if(!empty($_POST['name'])) $file['name'] = $_POST['name'];

$key = md5($_POST['key']); $data = file_get_contents($file['tmp_name']);

$td = mcrypt_module_open('des', '', 'ecb', '');

$subkey = substr($key, 0, mcrypt_enc_get_key_size($td)); $iv_size = mcrypt_enc_get_iv_size($td);

$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);

mcrypt_generic_init($td, $subkey, $iv);

$encrypted_data = mcrypt_generic($td, $data);

mcrypt_generic_deinit($td);

mcrypt_module_close($td);

mysql_query("INSERT INTO 'secure_files' ('name', 'mime_type', 'date', 'length', 'key', 'data')

VALUES ('{$file['name']}', '{$file['type']}', UNIX_TIMESTAMP(),

'{$file['size']}', '$key', '".mysql_real_escape_string

($encrypted_data)."')") or die(mysql_error()); }

?>

Tabela 2. Najpopularniejsze algorytmy szyfrowania dostępne w rozszerzeniu MCrypt

Nazwa stałej Algorytm Rozmiarklucza

Rozmiar bloku

MCRYPT_3DES Potrójny DES 168 64

MCRYPT_THREEWAY 3way 96 96

MCRYPT_BLOWFISH Blowfish do 448 64

MCRYPT_CRYPT Szyfrowanie unikowe 104 8

MCRYPT_DES DES 56 64

MCRYPT_GOST Sowiet Gosudarswiennyj 256 64

MCRYPT_IDEA International Data Encryption Algorithm

128 64

MCRYPT_SERPENT Serpent 128, 192lub 256

128

MCRYPT_TWOFISH Twofish 128, 192lub 256

128

Łukasz Lach studiuje informatykę na wydziale Cybernetyki Wojskowej Aka-demii Technicznej w Warszawie. Od kilku lat zajmuje się tworzeniem apli-kacji internetowych z wykorzystaniem takich technologii jak PHP, XHTML, JavaScript czy XML. Posiada certyfi-kat Zend Certified Engineer. Jest au-torem licznych publikacji prasowych w czasopismach polskich i zagra-nicznych na temat tworzenia aplikacjiinternetowych i bezpiecznym progra-mowaniu w PHP.

Michał Stanowski opanowałw stopniu zaawansowanym umiejęt-ność obiektowego tworzenia aplikacji w PHP5. Interesuje się bezpieczeń-stwem aplikacji internetowych oraz ich użytecznością. Swój wolny czas spę-dza zwiedzając Polskę na rowerze.

O autorach

Page 41: PHPSolutions_04_2006_PL

Kryptografia w PHP Bezpieczeństwo

41PHP Solutions Nr 4/2006

katu o błędzie i zakończenie działania skryptu. Wydłużenie czasu szyfrowania może być również spowodowane wybra-niem algorytmu o dużej złożoności obliczeniowej. Dobrym rozwiązaniem jest w tym wypadku szyfrowanie pojedynczych bloków danych. O tym, jak duży może być blok dla danego algorytmu szyfrującego, informuje nas funkcja mcrypt_get_block_size lub mcrypt_enc_get_block_size.

PodsumowanieW 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

Rysunek 3. Aplikacja bezpiecznego przechowywania plików na serwerze w postaci zaszyfrowanej

Tabela 3. Tryby szyfrowania wspierane przez rozszerzenie MCrypt

Nazwa stałej Opis

MCRYPT_MODE_ECB Tryb książek elektronicznych

MCRYPT_MODE_CBC Tryb łańcuchowego łączenia blo-ków

MCRYPT_MODE_CFB Tryb sprzężenia zwrotnego szy-frowania

MCRYPT_MODE_OFB Tryb sprzężenia zwrotnego wyj-ścia z 8 bitami sprzężenia

MCRYPT_MODE_NOFB Tryb sprzężenia zwrotnego wyj-ścia z liczbą bitów sprzężenia równą rozmiarowi bloku algo-rytmu

MCRYPT_MODE_STREAM Tryb szyfrowania strumienio-wego

Page 42: PHPSolutions_04_2006_PL

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org42

Zend Framework

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org 43

Mimo że cały projekt jest jeszcze mocno rozwojowy, zdobywa co-raz większą popularność i uzna-

nie tysięcy deweloperów z całego świata. Czy zatem Zend Framework to rewolu-cyjne rozwiązanie? Z wielu względów na pewno tak: na razie można jedynie przy-puszczać, że stanie się on pewnym stan-dardem, przez co tworzenie i rozwijanie aplikacji w PHP stanie się dużo wydajniej-sze i prostsze. Wielu deweloperów może zacząć budować aplikacje używając tego samego, solidnego narzędzia i rozmawiać tym samym językiem. Wsparcie Frame-worka przez firmę Zend i jej najlepszych deweloperów może być gwarantem suk-cesu i powszechności użycia.

Mimo to, za wcześnie jeszcze na peł-ną ocenę i porównywanie projektu do tak znanych rozwiązań jak eZ components, WACT, Solar czy Seagull. W trakcie pisa-nia tego artykułu Zend Framework dostęp-ny był w wersji 0.1.2, której daleko jest do bycia kompletną i stabilną, więc walka by-

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

łaby nierówna. Porównanie to z pewno-ścią przedstawimy w jednym z kolejnych artykułów. Póki co zacznijmy od samego początku.

Czy potrzebujemy kolejnego frameworka?Pytanie to zadawało wielu programistów, kiedy usłyszeli o pomyśle Zenda. Wieluz Was wie, że dla PHP istnieje już wiele frameworków, wystarczy zajrzeć na stronę

Zend FrameworkPiotr Szarwas

W SIECI

• http://framework.zend.com/ – strona główna Zend Fra-mework

• http://www.zend.com/php_collaboration_project – stro-na główna PHP Collabora-tion Project.

• http://devzone.zend.com/ – strona główna Zend Deve-loper Zone.

• http://www.zend.com/phpide/ – z tej strony można pobrać Eclipse PHP IDE

• http://framework.zend.com/svn/framework/ – dostęp do repozytorium kodu Zend Framework poprzez WWWi SVN.

Stopień trudności: lll

Co należy wiedzieć...Przydatna będzie podstawowa znajo-mość budowy frameworków i programo-wania obiektowego w PHP.

Co obiecujemy...Z artykułu dowiesz się, jak zbudować prostą aplikację do agregacji newsówz kanałów RSS z wykorzystaniem Zend Frameworka. Pokażemy Wam najważ-niejsze komponenty Frameworka i po-wiemy, czy warto używać go w środowi-sku produkcyjnym.

Page 43: PHPSolutions_04_2006_PL

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org42

Zend Framework

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org 43

http://www.phpwact.org/php/mvc_frame-works i sprawdzić, że ich ilość w przypad-ku frameworków MVC przekracza 40.

Czy warto więc pisać kolejny i odkry-wać Amerykę na nowo? Pomysł jest jesz-cze za „świeży”, aby móc powiedzieć, czy jest potrzebny społeczności PHP, czy też nie. Warto dodać, że Zend Framework nie jest zwykłym framoworkiem MVC. Jak twierdzą jego twórcy, ma rozwiązy-wać 80% wszystkich zagadnień pojawia-jących sie podczas pisania aplikacji webo-wych, dlatego między innymi w jego skład wchodzą takie komponenty jak Zend_Db, Zend_Mail, Zend_Pdf, Zend_Log czy też Zend_Search. Wszystkie one mają przy-spieszyć i ułatwić pracę programisty, jak żaden inny framework do tej pory.

Założenia i motywacjeGłównym celem projektu Zenda jest stwo-rzenie frameworka, w którym będzie się pisać równie łatwo jak w samym PHPi promowanie PHP 5, a w szczególności jego cech obiektowych. Szczególny nacisk położono tu na bezpieczeństwo. Frame-work ten w założeniach będzie mógł rywa-lizować z takimi frameworkami jak Ruby on Rails czy Spring, będzie posiadał testy jednostkowe (Unit Tests), dobre przykłady, dokumentację i tutoriale. Wszystko to nie jest niczym innowacyjnym. To co jest jed-nak wyjątkowe w przypadku Zend Frame-owrka, to społeczność, która ma uczestni-czyć w budowaniu kodu. Już teraz należą do niej największe firmy IT i wielu znanych specjalistów PHP.

Ogólna architekturai najważniejsze składnikiZend Framework ma budowę kompo-nentową. Został tak zaprojektowany, aby praktycznie każdy z jego elementów da-ło się wykorzystać osobno. Oznacza to, że jeżeli korzystamy już z jakiegoś frame-worka nie musicie się w całości przesia-dać na Zend Framework możecie wyko-rzystać tylko te z jego elementów, które są dla was użyteczne.

Wielu osobom framework kojarzy się z kawałkiem kodu, który w pewnej mniej lub bardziej skończonej formie implementuje wzorzec architektoniczny MVC. Tak jak wcześniej wspomnieliśmy, Zend Framework to nie tylko imple-mentacja wzorca MVC, to również sze-reg bardzo przydatnych i praktycznych

komponentów. Zaliczają sie do nich przede wszystkim: Zend_Db, Zend_Feed, Zend_InputFilter, Zend_Mail, Zend_Pdf, Zend_Search, Zend_Log i Zend_Service. Pełna lista komponentów wraz z krótkim opisem znajduje się w Tabeli 1.

Praktycznie każdy z modułów jest na-pisany tak, że jeżeli tylko istnieje taka po-trzeba możemy nie ingerując w kod frame-worka dodawać do niego potrzebne nam funkcjonalność. Możliwe jest to dzięki sta-rannemu projektowaniu i wykorzystywaniu takich technik programowania obiektowe-go jak pisanie do interfejsu czy też kompo-zycja. Wraz z podstawowymi komponen-tami dostajemy również te, które znajdu-

ją się w specjalnym katalogu “incubator”. Są to moduły, które nie weszły jeszczew skład Frameworka, ale prawdopodobnie niedługo się w nim znajdą. Ich kod jest już jednak na tyle stabilny, że można próbo-wać je wykorzystywać we własnych pro-jektach i co najważniejsze wpływać na ich funkcjonalność, chociażby poprzez wysy-łanie maili do autorów kodu.

Piszemy pierwszą aplikacjęW artykule postanowiłem skupić się na kil-ku komponentach, które moim zdaniem mogą być najczęściej wykorzystywane w codziennej pracy. Zaliczyłem do nich

PHP Collaboration ProjectPodczas konferencji Zend/PHP Conference and Expo 2005, która obyła się w drugiej poło-wie października 2005 w San Francisco świat usłyszał o projekcie PHP Collaboration Pro-ject. Celem projektu miałoby być utworzenie społeczności składającej się z firm i niezależ-nych programistów, którzy razem stworzą narzędzia pozwalające PHP zaistnieć na poważ-nie w świecie wielkich projektów informatycznych. Głównym motorem tego projektu stały się trzy składniki Zend Framework, PHP Eclipse IDE i Zend Developer Zone. Zend Frame-work miałby stać się podstawowym narzędziem zawierającym wszystkie potrzebne kom-ponenty do napisania aplikacji webowej, PHP Eclipse IDE miałoby stanowić profesjonal-ne środowisko programistyczne (niestety część jego składników ma być płatna), natomiast Zend Developer Zone to miejsce wymiany idei i pomysłów dla deweloperów. Wszystko to dostępne za darmo. Tuż po ogłoszeniu tej inicjatywy wielu programistów w swoich blo-gach i wypowiedziach na różnych forach komentowało ten fakt i, jak to było do przewidze-nia, świat podzielił się na kilka frakcji. Byli tacy, którzy twierdzili, że Zend chce zdominować wolny świat PHP, byli umiarkowani sceptycy, którzy mówili, że pomysł jest dobry, ale prze-cież jest już tyle dobrych frameworków, byli też tacy, którzy twierdzili, że to świetny pomysł i kolejny sukces PHP. Tych, którzy chcieliby prześledzić historię tych wypowiedzi odsyłam do archiwum serwisu phpdeveloper.org.

Zend Framework – szybki startKod frameworka można pobrać ze strony http://framework.zend.com/download. Aktual-na wersja ma numer 0.1.2. Dodatkowo należy jeszcze tak skonfigurować PHP, aby ścież-ka gdzie umieścimy katalog z frameworkiem, była uwzględniana przez funkcje requirei include przy poszukiwaniu plików. Można to zrobić na trzy sposoby w zależności od możliwości konfiguracyjnych, do których macie dostęp:

• zmodyfikować zmienną include _ path w pliku php.ini, • utworzyć plik .htaccess w katalogu głównym aplikacji i umieścić w nim wpis php_va-

lue include_path = '/ścieżka/do/katalogu' • umieścić w pliku index.php wywołanie systemowej funkcji: ini _ set( 'include _ path', ini _ get('include _ path').PATH _ SEPARATOR.'/

ścieżka/do/katalogu' );.

Aby móc korzystać z części MVC frameworka, potrzebny jest Apache z modułem mod_rewrite. W przypadku problemów z instalacją niedziałających komponentów, czy też innych problemów odsyłam Was do stron FAQ Frameworka (http://framework.zend.com/faq), strony informującej o bugach (http://framework.zend.com/bugs) i dokumentacji (http://framework.zend.com/manual). Dodatkowo wiele ciekawych informacji na temat frame-worka możecie znaleźć w Zend Developer Zone znajdującej się pod adresem http://devzone.zend.com/. Zawartość serwisu Zend Develor Zone jest o tyle ciekawa, że znaj-dziecie tak szereg tutoriali i artykułów wprowadzających w tajniki programowania w PHPi Zend Frameworka.

Warto jeszcze dodać, że jeżeli chcielibyście mieć dostęp do najświeższej wersji kodu Zend Frameworka, od niedawna można pobrać jego kod z repozytorium SVN. Instrukcja zawierająca informację, jak należy to zrobić dostępna jest na stronie Frameworka. Opcja ta, jest niezwykle atrakcyjna, gdyż z mojej obserwacji wynika, że praktycznie każdego dnia w repozytorium pojawiają się jakieś zmiany poprawiającej funkcjonalność kodu i nie tylko.

Page 44: PHPSolutions_04_2006_PL

Zend Framework

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org44

Tabela 1. Najważniejsze komponenty Zend Frameworka

Zend_Controller

Zawiera klasy implementujące wzorzec MVC. Głównymi składnikami komponentu są:

Zend_Controller_Front Implementacja wzorca Front Controller wraz z wzorcem Intercepting Filter.

Zend_Controller_Router Odpowiada za dekompozycję URL-a do nazwy kontrolera i akcji, którą należy wykonać.

Zend_Controller_Dispatcher Odpowiada za znalezienie właściwego kontrolera i wywołanie w nim odpowiedniej akcji.

Zend_Controller_Action Podstawowa implementacja kontrolera akcji.

Zend_ViewKomponent do budowy obiektów widoku wzorca MVC.

Zend_DbZestaw klas do wykonywania wszelkich operacji typu CRUD na bazach danych. Implementacja opiera się na PDO:

Zend_Db_Adabter Warstwa abstrakcji uniezależniająca od różnych dialektów baz danych.

Zend_Db_Select Klasa ułatwiająca budowanie złożonych zapytań SQL przy pomocy interfejsu obiektowego. Jest to bar-dzo prosta implementacja tzw. Criteria API znanego od dawna w świecie Javy.

Zend_Db_Table,Zend_Db_Table_Row

Implementacja wzorca Active Row, niestety w obecnej chwili nie działa poprawnie z PDO, wspierany jest jedynie driver mysqli.

Zend_FeedKomponent do przetwarzania wiadomości RSS i Atom.

Zend_FilterKlasa zawierająca szereg statycznych metod do służących do walidacji. Przed wykorzystaniem tej klasy należy koniecznie zapoznać się z jej kodem. Powodów jest kilka po pierwsze część metod nie ma jeszcze implementacji (np. isEmail i isUri). Cześć metod do poprawnego dzia-łania wymaga ustawienia odpowiedniego setlocale. W szczególności tyczy się to tych wszystkich metod, które korzystają z klas znakowych ([:alpha:],[:alnum:]). Implementacja metody isPhone nie jest dostosowana do standardów wielu krajów Europy i nie tylko.

Zend_HttpClientProsty klient HTTP

Zend_InputFilterKlasa do filtrowania danych wejściowych (np. Tablice $_GET i $_POST). Klasa korzysta ze statycznych metod klasy Zend_Filter. Warte zapa-miętania jest, że w podstawowej konfiguracji tablice $_GET i $_POST są ustawiane na null, a do danych w nich zawartych można odwoływać się jedynie poprzez obiekty klasy Zend_InputFilter.

Zend_JsonKlasa serializująca zmienne PHP do formatu JSON. Więcej o formacie JSON możecie przeczytać na stronie http://www.json.org.

Zend_LogZestaw klas do logowania wszelkiego rodzaju operacji, z możliwością jednoczesnego logowania do wielu źródeł (np. plik i ekran).

Zend_MailZestaw klas do budowania i wysyłania maili wraz z załącznikami. Posiada możliwość wysyłki maili zarówno przy pomocy funkcji mail jaki bezpośredniego połączenia z serwerem SMTP.

Zend_MimeKlasa zawierająca szereg użytecznych metod do współpracy z wiadomościami MIME.

Zend_PdfKomponent zawiera klasy do dynamicznej budowy plików PDF. Niestety wspierane są jedynie znaki ISO-8859-1, tak więc dla wielu ta klasa jest póki co bezużyteczna.

Zend_SearchJest to implementacja znanego z Javy silnika do wyszukiwania Lucene. Komponent napisany jest całkowicie w PHP, nie wymaga do swojego działania bazy danych. Wszystkie potrzebne mu do działania dane przechowywane są w plikach. Niestety podobnie jak Zend_Pdf rozwiązanie to wspiera w obecnej chwili jedynie znaki ISO-8859-1. Autorzy piszą, że dodanie wsparcia dla innych stron kodowych jest na ich liście TODO.

Zend_Service

Zestaw klas wpierających komunikację z Web Serwisami. Najważniejsze składniki komponentu to:

Zend_Service_Rest Klasa ogólnego zastosowania dla Web Serwisów typu REST

Zend_Service_Yahoo Klasa ułatwiająca korzystanie z wielu Web Serwisów Yahoo

Zend_Service_Amazon Klasa ułatwiająca korzystanie z wielu Web Serwisów Amazon

Zend_Service_Flickr Klasa ułatwiająca korzystanie z wielu Web Serwisów Flickr

Zend_XmlRpcKomponent ułatwiający zdalne wywoływanie procedur w standardzie XML-RPC

Zend_CacheKod tego komponentu znajduje się w tzw. inkubatorze nie jest więc podstawowym składnikiem Frameworka. Służy do cachowania wszelkie-go rodzaju danych, w plikach lub bazie SQLite.

Page 45: PHPSolutions_04_2006_PL
Page 46: PHPSolutions_04_2006_PL

Zend Framework

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org46

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

��

Zend_Controller, Zend_View, Zend_Db, Zend_Log i Zend_Mail.

Wykorzystując te moduły napisze-my prostą aplikację do agregacji newsówz kanałów RSS. Pominiemy cześć apli-kacji do agregacji newsów, a skupimy sięjedynie na części prezentacyjnej. Zakłada-my, że każdy news należy do jednego ka-nału i jednej kategorii, a kategorie są od siebie niezależne. Struktura bazy danych pokazana jest na Rysunku 1. Funkcjonal-ność tej części jest następująca: użytkow-nik może wyświetlić wszystkie newsy dla jednej kategorii i wysłać do kogoś ema-il z powiadomieniem o ciekawym newsie. Każda akcja wysłania emaila jest logowa-na w celu zbierania statystyk.

Tworzenie kodu rozpoczniemy od pli-ku index.php, jego zawartość pokazana jest na Listingu 1. Skrypt ten jako jedy-ny widoczny jest przez WWW. Odpowia-da za załadowanie wszystkich potrzeb-nych klas Zend Frameworka przy pomo-cy statycznej metody loadClass znajdu-jącej się w klasie Zend. Klasa ta posia-da jeszcze kilka pomocnych metod, któ-re zaprezentuję w dalszej części artykułu. Kolejnym krokiem jest konfiguracja logge-ra, bazy danych i front kontrolera. W przy-padku loggera zakładamy, że do logowa-nia będzie wykorzystywany adapter logu-jący dane do pliku log.txt znajdującego się

w katalogu log. Poza logowaniem do pli-ku można jeszcze wysyłać logi na ekrani do bazy danych. Jeżeli jest taka potrzeba możemy napisać własny mechanizm logo-wania, wystarczy zaimplementować inter-fejs Zend_Log_Adapter_Interface i zare-jestrować swój adapter tak, jak zrobiłem to w przykładzie. Kolejny krok to konfigu-racja bazy danych. Jest ona bardzo pro-sta wymaga jedynie wywołania statycznej metody factory na klasie Zend_Db. Me-toda ta zwróci obiekt/sterownik połącze-nia do bazy danych, na którym będzie-my wykonywać wszystkie operacje zwią-zanie z dostępem do danych. W naszym przypadku zwrócony obiekt opakowuje mechanizm PDO. Zend Framework do-starcza jeszcze dwa dedykowane sterow-niki dla Oracla i MySQL ten drugi działaw oparciu o rozszerzenie mysqli. Po uru-chomieniu połączenia do bazy danych po-zostało nam jedynie skonfigurowanie kla-sy Zend_Controller_Front stanowiącej implementację wzorca Front Controlleri klasy Zend_View. Jedynymi parametrami konfiguracyjnymi dla tych klas są ścież-ki, w których znajdować się będą utwo-rzone w późniejszej części artykułu kon-trolery akcji i widoki. Podczas powyższe-go procesu konfiguracji utworzyliśmy trzy bardzo ważne obiekty: sterownik bazy da-nych, widok i front kontroler. Dwa z nich

musimy przekazać do niższych warstw aplikacji. Najlepiej zrobić to wykorzystu-jąc dwie statyczne metody znajdujące się w klasie Zend: register i registry. Meto-dy te wspólnie implementują powszechnie znany wzorzec Registry, pierwsza z nimzapisuje obiekty w rejestrze, a druga pobie-ra je z niego. Sam rejestr jest Sigletonem dostępnym w każdym miejscu warstwy apli-kacji. Ostatnim elementem pliku index.php jest wywołanie metody dispatch na obiek-cie front kontrolera, która rozpoczyna pro-ces przetwarzania, w wyniku którego od-naleziona zostanie odpowiednia akcja i wi-dok (Rysunek 2). Dla nas najważniejsza jest wiedza, że dzięki modułowi mod_rew-rite wszystkie odwołania do aplikacji prze-chodzą przez index.php. Zend Framework wyposażony jest w prosty dispatcher tych-że odwołań na kontrolery i akcje, jego dzia-łanie najlepiej wyjaśnić na podstawie odwo-łań pojawiających się mojej aplikacji:

• / przekierowuje do akcji indexActionw kontrolerze IndexController,

• /index/ przekierowuje do akcji inde-xAction w kontrolerze IndexController,

• /index/index/ przekierowuje do akcjiindexAction w kontrolerze IndexCon-troller,

• /mail/ przekierowuje do akcji index-Action w kontrolerze IndexController,

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

Page 47: PHPSolutions_04_2006_PL

Zend Framework

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org 47

• /mail/send przekierowuje do akcji sen-dAction w kontrolerze MailController,

• /index/index/id/2 przekierowuje do ak-cji indexAction w kontrolerze indexz parametrem id równym 2.

Podsumowując: jeżeli nie został wy-brany żaden kontroler, dispatcherwywoła akcję indexAction w kontrole-rze IndexController, jeżeli została po-dana nazwa kontrolera, a nie została podana nazwa akcji, dispatcher wywołaakcje indexAction w podanym kontrole-rze. Aby przekierowania zadziałały, pli-ki, w których znajdują się klasy kontro-lerów, muszą mieć takie same nazwy jak kontrolery.

Nasza aplikacja będzie składała się z dwóch kontrolerów IndexControlleri MailController. Pierwszy odpowiada za wyświetlanie wiadomości RSS, drugi za wysyłanie maili. Kod tych dwóch kontro-lerów znajduje się na Listingach 2 i 3. Wszystkie dziedziczą po abstrakcyjnej klasie Zend_Controller_Action. Kontro-ler IndexController zawiera dwie meto-dy indexAction i noRouteAction. Metoda noRouteAction ma specjalne zastoso-wanie wywoływane przez front kontroler (tylko w kontrolerze IndexController) w sytuacji, gdy nie zostanie znalezio-ny kontroler, który miał obsłużyć dane żądanie. Każdy kontroler dziedziczący

Listing 1. Plik index.php – fundament naszej aplikacji

// dodajemy ścieżkę do Zend Framework

ini_set( 'include_path', ini_get('include_path').PATH_SEPARATOR.'../lib' );

// ładujemy główną klasę Zend (Zend.php),

// klasa ta zawiera jedynie statyczne metody.

include 'Zend.php';

// ładujemy wszystkie niezbędne klasy

Zend::loadClass('Zend_Controller_Front');

Zend::loadClass('Zend_Controller_Action');

Zend::loadClass('Zend_View');

Zend::loadClass('Zend_Db');

Zend::loadClass('Zend_Filter');

Zend::loadClass('Zend_InputFilter');

Zend::loadClass('Zend_Log');

Zend::loadClass('Zend_Log_Adapter_File');

Zend_Log::registerLogger(new Zend_Log_Adapter_File('../logs/info.txt'));

$dbParams = array ('host' => '127.0.0.1', 'username' => 'postgres', 'password' => '', 'dbname' => 'myrss');

$db = Zend_Db::factory('pdoPgsql', $dbParams);

$controller = Zend_Controller_Front::getInstance();

$controller->setControllerDirectory('../app/controllers');

$view = new Zend_View(); $view->setScriptPath('../app/views');

// zapisywanie obiektów w rejestrze

Zend::register('view',$view);

Zend::register('db',$db);

$controller->dispatch();

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

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

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

����������

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

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

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

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

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

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

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

Page 48: PHPSolutions_04_2006_PL

Zend Framework

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org48

po klasie Zend_Controller_Action mu-si implementować metodę indexAction, w przypadku kontrolera IndexControl-ler metoda ta odpowiada za wyświetle-nie listy wszystkich kategorii RSS-ów i listy RSS-ów należących do katego-rii wybranej przez użytkownika aplika-cji. Jeżeli żadna kategoria nie zosta-ła wybrana zakładamy wybór pierwszej z listy. Obiekt bazy danych i widoku, na którym operuje akcja pobierane sąz rejestru. Dane z bazy pobieramy przy pomocy metody query, która ma dwa parametry, pierwszym jest zapytanie SQL, a drugim opcjonalnym lista pa-rametrów przekazywana do zapytania. Działanie metody query z oboma para-metrami prezentuje jej drugie wywołanie w akcji index. Dzięki takiemu przekazy-waniu parametrów są one zawsze qu-otowane, co powoduje, że kod odpor-ny jest na ataki SQL Injection. Metodaquery zwraca obiekt PDOStatement. Da-ne pobrane z bazy przekazywane są do widoku dzięki mechanizmowi ma-gicznych metod __get i __set, który jest wyjątkową cechą PHP. Identyczny efekt można uzyskać wywołując metodę assign na obiekcie widoku.

Ostatnią operacją wykonywanąw akcji index jest wyświetlenie pliku wi-doku. Zend Framework nie posiada me-chanizmu szablonów, nie jest też sprzę-żony z żadnym znanym rozwiązaniem tego typu, dlatego jako szablony wyko-rzystujemy standardowe pliki PHP, gdzie językiem szablonów jest również PHP. Przykładowy szablon pokazany jest na Listingu 4. Szablon renderowany jest wewnątrz obiektu widoku stąd wszystkie dane przekazane do widoku dostępne są pod zmienną $this, dodatkowo dzię-ki takiemu podejściu możemy w szablo-nie korzystać ze wszystkich metod klasy Zend_View, należy do nich np. metoda escape. Drugi kontroler naszej aplikacji posiada również dwie metody: pierw-sza, prostsza – indexAction – wyświe-tla jedynie formularz do wysyłania ma-ili. Druga metoda jest już znacznie cie-kawsza, a jej głównym zadaniem jestwysłanie maila do osoby, której adres podamy w formularzu wysyłki i zalo-gowanie tego faktu do pliku logu. Kodzostał znacznie uproszczony – docelo-we rozwiązanie powinno zawierać cho-ciażby sprawdzanie, czy podany w for-mularzu email jest faktycznie emailem. Jak widać, wysłanie maila jest bar-

dzo proste, sprowadza się do utworze-nia obiektu klasy Zend_Mail, ustaleniuadresata, odbiorcy, treści i tematu wia-domości i w końcu wysłania maila.

Utworzenie logu jest jeszcze prost-sze, aby to zrobić wystarczy wywołać statyczną metodę log klasy Zend_Log.W najprostszym wydaniu metoda ta

Listing 2. Kod kontrolera InextController

// każda aplikacja musi posiadać kontroler o nazwie IndexController,

// jest on standardowo wywoływany wraz z akcją index w sytuacji, gdy użytkownik

// nie wymienił w URL-u ani kontrolera, ani akcji.

class IndexController extends Zend_Controller_Action {

// metoda indexAction jest wykonywana w sytuacji, gdy nie została podana

// żadna akcja w ramach kontrolera lub użytkownik wywołał akcję index.

public function indexAction() { // pobieramy z rejestru wcześniej utworzone obiekty widoku

// i połączenia do bazy danych

$view = Zend::registry('view');

$db = Zend::registry('db');

// metoda query odpowiada za wykonywanie zapytań SQL do bazy, zwraca tzw.

// statement. Wywołanie na obiekcie statementu metody fetchAll powoduje,

// że zwrócone zostają rekordy w postaci ponumerowanej od zera tablicy

// tablic asocjacyjnych, w których kluczami są nazwy kolumn zwróconych

// w zapytaniu.

$categories = $db->query("SELECT * FROM feeds_categories

ORDER BY fc_name" )->fetchAll();

// klasa View implementuje magiczne metody __set i __get,

// dzięki którym w prosty sposób możemy w obiekcie widoku tworzyć nowe

// atrybuty, dane zapisane w tych atrybutach będą dostępne w szablonie pod

// zmienną $this->nazwa_atrybutu.

$view->categories = $categories;

// wszystkie przekazane do kontrolerów dane z zapytania dostępne są

// poprzez metodę _getParam, ma ona dwa parametry, pierwszym jest

// nazwa parametru, drugim wartość, która zostanie zwrócona w przypadku

// gdy data o podanej nazwie nie istnieje. Drugi parametr jest

// opcjonalny. Przed zapisaniem do zmiennej pomocniczej $categoryId

// parametru id filtrujemy jego zawartość, tak aby w $categoryId

// zapamiętać jedynie część liczbową id.

$categoryId = Zend_Filter::getDigits( $this->_getParam( 'id' ) );

if ( !$categoryId ){ $categoryId = $view->categories[0]['fc_id'];

}

$view->items = $db->query("SELECT feeds_items.* FROM feeds_channels

LEFT JOIN feeds_items

ON ( feeds_channels.fch_id = feeds_items.fch_id )

WHERE feeds_channels.fc_id = :fc_id

ORDER BY feeds_items.fi_pub_date",

array( "fc_id" => $categoryId ) )->fetchAll();

// metoda render odpowiada za wygenerowanie i wyświetlenie szablonu

echo $view->render('IndexView.php'); }

// metoda ta jest wywoływana w sytuacji, gdy użytkownik chciał odwołać się

// do nieistniejącego kontrolera. Metodę te wywołuje front kontroler.

public function noRouteAction() {

// w przypadku gdy zostanie wywołany nieistniejący kontroler

// przekierowujemy użytkownika na stronę główną aplikacji, czyli do

// kontrolera IndexController i metody indexAction.

$this->_redirect('/');

}

}

Page 49: PHPSolutions_04_2006_PL

Zend Framework

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org 49

Listing 3. Kod kontrolera MailController

class MailController extends Zend_Controller_Action {

public function indexAction() { $view = Zend::registry('view');

echo $view->render('MailView.php'); }

public function sendAction() { $view = Zend::registry('view');

// klasa Zend_InputFilter opakowuje statyczne metody klasy

// Zend_Filter, wywołana w formie takiej jak poniżej czyści tablicę

// $_POST, wszystkie znajdujące się w niej parametry dostępne są

// poprzez filtrujące metody klasy Zend_InputFilter. Czyszczenie

// tablicy można wyłączyć ustawiając drugi parametr konstruktora na false

$post = new Zend_InputFilter($_POST);

$emailTo = $post->getAlpha('emailTo');

$emailFrom = $post->getAlpha('emailFrom');

// przygotowujemy i wysyłamy mail. Robimy to przy pomocy klasy Zend_Mail.

Zend::loadClass('Zend_Mail');

$mail = new Zend_Mail('iso-8859-2'); $mail->addTo($emailTo);

$mail->setFrom($emailFrom);

$mail->setBodyText('tekst powiadomienia');

$mail->setSubject('TestSubject');

$mail->send();

// rejestrujemcy w logu fakt wysłania maila. To gdzie zostanie wysłany log

// zależy od tego, jaki appender został zarejestrowany (plik index.php)

Zend_Log::log('Został wysłany mail');

$this->_redirect('/');

}

}

Listing 4. Przykładowy plik szablonu

<!-- Nagłówek -->

<table width="90%" cellspacing="0" cellpadding="0" border="0" align="center">

<tr>

<td>

<table width="100%" cellspacing="0" cellpadding="2" border="0">

<?php foreach( $this->items as $item ) { ?> <tr>

<td>

<?= $this->escape( $item['fi_title'] ) ?>

</td>

</tr>

<? } ?>

</table>

</td>

</tr>

</table><!-- Stopka -->

wymaga podania jednego parametru, treści logu, jeżeli jest taka potrzeba,jako drugi parametr możemy podać poziom logu. Dostępne jest sześć po-ziomów, wszystkie są zdefiniowanew klasie Zend_Log jako stałe, standar-

dowym poziomem logowania jest DE-BUG. Po wysłaniu maila i zalogowaniu tego faktu, akcja sendAction przy po-mocy metody _redirect przekierowuje nas automatycznie na stronę główną. Dzięki temu nie będzie możliwe ponow-

ne wysłanie maila, jeżeli ktoś odświeży widok strony w przeglądarce.

Czy powinniśmy przesiąść się na Zend Framework?Pozostałe komponenty Frameworka, któ-rych nie omówiliśmy w artykule, są rów-nie proste w użyciu – do części z nich znajdziecie przykłady w dokumentacji lubw kodzie, w katalogu demos.

Pomimo że kod w Zend Framewor-ku pisze się bardzo szybko i prosto, zde-cydowanie za wcześnie jest na produk-cyjne wykorzystanie tego narzędzia. Co prawda wiele z jego elementów jest już w pełni funkcjonalnych, ale dewelope-rzy Frameworka mają jeszcze sporo do zrobienia. Najważniejsze braki, i nie je-dyne, to niedziałający dobrze kompo-nent Zend_Db. Moduły Zend_Searchi Zend_Pdf wspierają jedynie kodowanie ISO-8859-1, co czyni je bezużyteczny-mi dla wielu programistów. Brak mecha-nizmu szablonów, czy też integracji ze Smarty lub PHPTAL jest dodatkowym mi-nusem. Sama dokumentacja ma jeszcze spore braki. Dlatego na dzień dzisiejszy proponuję wam pozostać przy rozwią-zaniach, które wykorzystujecie obecnie. Nie zapominajcie jednak o Zend Frame-worku – wydaje się, iż jest skazany jest na sukces: w jego rozwój zaangażowani są znani deweloperzy i największe firmy związane z PHP. Na pewno wrócimy do tego tematu w kolejnych wydaniach ma-gazynu, gdyż już niedługo Zend Frame-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-sku programisty PHP. n

Piotr Szarwas jest pracownikiemSUPER-MEDIA Interactive i dokto-rantem na wydziale Fizyki Politechniki Warszawskiej. Od 2003 roku projektuje aplikacje WWW w oparciu o PHP4/5. Obecnie zajmuje się tworzeniem fra-meworka dla PHP opartego na rozwią-zaniach Hibernate i Spring.Kontakt z autorem:[email protected]

O autorze

Page 50: PHPSolutions_04_2006_PL

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org50

eZ components

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org 51

Aplikacja webowa, którą będziemy analizować w ramach niniejsze-go artykułu, to prosta galeria ob-

razów. Funkcjonalność, którą będziemybudować to tworzenie albumów oraz wczytywanie zdjęć do tych albumów. Ga-leria będzie ponadto skalować wczyty-wane obrazy i tworzyć ich pomniejszone wersje w celach prezentacyjnych.

Prezentowana aplikacja ma charak-ter edukacyjny, jednak przy odrobinie do-datkowego wysiłku ze strony czytelnika, może być z powodzeniem zastosowanaw rozwiązaniach klasy produkcyjnej.

Zaczynamy!Struktura albumów w naszej galerii, oraz dane na temat zdjęć będą przechowywa-ne w bazie danych. W prezentowanym przykładzie użyłem bazy MySQL, jed-nak czytelnik może równie dobrze sko-rzystać z innych technologii wspieranych przez komponenty eZ (http://ez.no/doc/components/overview/latest). Na Listin-

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

gu 1 pokazane jest, w jaki sposób moż-na stworzyć tabelę do składowania albu-mów (prezentowana składnia jest specy-ficzna dla MySQL, jednak nie powinno być problemu z zaadoptowaniem jej do innych rozwiązań bazodanowych).

Struktura albumu jest bardzo prosta. Mamy tutaj pola tytuł (ang. title) i opis (ang. description) oraz numeryczny iden-tyfikator. Warto zwrócić w tym miejscu uwagę na użycie modyfikatora auto_incre-ment. Jeśli wykorzystywany silnik bazoda-

eZ componets w akcji, czyli galeria zdjęć krok po krokuTobias Schlitt

W SIECI

l http://ez.no/doc/components/overview– dokumentacja API kompo-nentów eZ

l http://ez.no/doc/components/view/latest/(file)/tutorials.html – materiały do nauki obsługi komponentów eZ

l http://ez.no/community/forum/ez_components– forum użytkowników kom-ponentów eZ

l http://ez.no/community/articles/image_manipula-tion_with_ez_components – artykuł na temat kompo-nentów eZ

l http://pecl.php.net/package/filter– rozszerzenie do filtrowa-nia PECL

l http://www.phppatterns.com/docs/start– wzorce projektowe dla PHP

Stopień trudności: lll

Co należy wiedzieć...Czytelnik powinien znać język PHP (przynajmniej na poziomie podstawo-wym) oraz ogólne zasady projektowania orientowanego obiektowo.

Co obiecujemyCzytając niniejszy artykuł czytelnik na-uczy się budować aplikację webową (ga-lerię obrazów) z wykorzystaniem kompo-nentów eZ.

Page 51: PHPSolutions_04_2006_PL

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org50

eZ components

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org 51

nowy nie obsługuje tego mechanizmu, to w prezentowanym dalej kodzie PHP na-leżałoby wprowadzić pewne modyfikacje. Zakładam jednak, że opcja ta jest dostęp-na i aby nie powodować zamętu, nie będę opisywał tego szczególnego przypadku. Generalnie, sugeruję aby w trakcie prak-tycznego badania rozpatrywanego przy-kładu – szczególnie przy pierwszym czy-taniu – pracować z bazą MySQL. Zaosz-czędzi to czytelnikowi niepotrzebnych kło-potów.

Na Listingu 2 zaprezentowana jest ko-lejna tabela, tym razem służąca do prze-chowywania informacji odnośnie poszcze-gólnych zdjęć. Tabela ta jest bardzo pro-sta: oprócz własnego identyfikatora zawie-ra ona dodatkowo identyfikator powiąza-nego albumu. Można tu również znaleźć pola tytuł i opis.

Stwórzmy teraz podstawową strukturę katalogów dla naszej aplikacji – propozy-cja znajduje się w Tabeli 1.

InstalacjaKolejnym krokiem przy realizacji naszego zadania jest pobranie komponentów eZ. Najprościej byłoby użyć w tymcelu insta-latora PEAR (www.pear.php.net). Mając to narzędzie wystarczy wpisać:

$ pear channel-discover components.ez.no

$ pear install ezc/eZComponents

Jeśli instalacja PEAR jest odpowiednio skonfigurowana (warto zwrócić uwagę na parametr include_path), to sprawa jest praktycznie zakończona.

Oczywiście nie każdy użytkow-nik ma ochotę używać PEAR. Inny spo-sób instalacji polega na pobraniu archi-wum ze strony domowej projektu (http://ez.no/download/ez_components) i roz-pakowaniu go w wybranym katalogu (po-nownie należy zwrócić uwagę, aby pa-rametr include_path zawierał docelo-wy katalog). Komponent UserInput (http://

ez.no/doc/components/view/latest/(file)/classtrees_UserInput.html) jest zależny od filtra PECL. Najprostszym sposobem po-brania tego dodatkowego pakietu jest po-nownie użycie instalatora PEAR:

$ pecl install filter-beta

Jeśli użytkownik i w tym przypadku nie chce korzystać z PEAR, to istnieje moż-liwość pobrania odpowiedniego pakietu źródłowego (http://pecl.php.net/package/filter) ze strony PECL i ręczna kompilacja rozszerzenia. Po wykonaniu tych kroków trzeba jeszcze odpowiednio skonfiguro-wać plik php.ini.

Kodowanie:pierwsze krokiMożemy wreszcie rozpocząć kodowanie. Na początku tworzymy plik gallery.php, który powinien znajdować się w głów-nym katalogu naszego projektu. Plik ten będzie punktem wejścia do naszej ga-lerii: do niego będziemy odwoływać sięz przeglądarki. Wewnątrz pliku umie-ścimy podstawowe elementy nasze-go kodu: mechanizm automatycznego wczytywania (ang. autoload) (http://pl.php.net/autoload), klasę głównego kon-trolera i odwołania do kolejnych elemen-tów funkcjonalności.

Warto w tym miejscu spojrzeć na Li-sting 3. Na samym początku widać od-wołanie do pliku ezc/Base/base.php. Jest to jedyny plik powiązany z kom-ponentami eZ, do którego będziemy

Listing 1. Tabela do składowania albumów

CREATE TABLE album ( id int(11) unsigned NOT NULL auto_increment, title varchar(200) NOT NULL default '', description text NOT NULL, PRIMARY KEY (id)) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Listing 2. Tabela do przechowywania informacji na temat zdjęć

CREATE TABLE photo ( id int(11) unsigned NOT NULL auto_increment, album int(11) NOT NULL default '0', title varchar(200) NOT NULL default '', description text NOT NULL, PRIMARY KEY (id)) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Listing 3. Pierwszy kod w naszym projekcie

require_once 'ezc/Base/base.php';

function __autoload( $className ){ return ezcBase::autoload( $className );

} require_once 'config.php';

Tabela1. Struktura katalogów naszej aplikacji

Directory Descriptionactions/ Katalog actions/ będzie zawierał źródła kontrolerów dla akcji obsługi-

wanych z poziomu naszej aplikacji (używam tutaj wzorca projektowe-go MVC).

data/ Katalog data/ będzie zawierał dane obrazów. Warto zauważyć, że ten katalog musi mieć ustawione prawa zapisu i odczytu z poziomu serwe-ra webowego. Trzeba pamiętać, że tego rodzaju uproszczenie nie po-winno występować w aplikacjach o charakterze produkcyjnym.

interfaces/ W katalogu interfaces/ będziemy przechowywać interfejsy kontrolerów dla poszczególnych akcji.

models/ Katalog models/ będzie zawierał klasy modelu, reprezentujące album i fotografię.

pos/ Katalog pos/ ma specjalne znaczenie, jako że bedzie zawierał de-finicje obiektów przeznaczonych do składowania (http://ez.no/doc/components/view/latest/(file)/classtrees_PersistentObject.html). Obiek-ty takie stanowią implementację wzorca “aktywny rekord” w katego-riach komponentów eZ.

templates/ Katalog templates/ będzie zawierał szablony HTML (w kategoriach MVC będzie to warstwa prezentacyjna naszego projektu).

Page 52: PHPSolutions_04_2006_PL

eZ components

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org52

odnosić się w sposób bezpośredni. Znaj-dują się tam podstawowe, wspólne me-chanizmy, wykorzystywane przez wszyst-kie komponenty eZ; między innymi wspo-mniany wcześniej mechanizm automa-tycznego wczytywania (aktywowany po-przez definiowanie funkcji __autoload()) oraz kilka podstawowych wyjątków.

Główny komponent sprawujący kon-trolę nad naszym projektem nazywa się ezcGallery (patrz Listing 4). Klasa ta za-wiera konstruktor i dwie metody: run()i display(). Pierwsza ze wspomnianych metod jest odpowiedzialna za przekazy-wanie żądanej akcji do odpowiedniego kontrolera roboczego, zaś druga pobiera

Listing 4. Szkielet klasy ezcGallery

class ezcGallery { public function __construct() {...}

public function run() {...}

public static function getConfig() {...}

public static function getLog() {...}

public function display() {...}

public static function getSession() {...}

public static function getConverter() {...} }

Listing 5. Konstruktor klasy ezcGallery

public function __construct(){ $action = 'listing';

if ( ezcInputForm::hasGetData() ) { $definition = array( 'action' => new ezcInputFormDefinitionElement( ezcInputFormDefinitionElement::REQUIRED, 'string'

), );

$form = new ezcInputForm( INPUT_GET, $definition ); $action = ( $form->hasValidData( 'action' ) ) ? $form->action : listing';

} if ( !isset( $this->actions[$action] ) ) { throw new Exception( "Invalid action <".htmlentities($action).">." ); }

require_once $this->actions[$action];

$this->currentAction = new $action;}

Listing 6. Metoda display w klasie ezcGallery realizuje prosty mechanizm szablo-nowania

public function display(){ $template = $this->currentAction->getTemplateVars();

ob_start();

$res = include 'templates/' . $this->currentAction->getTemplate(); $html = ob_get_contents();

ob_end_clean();

if ( $res === false ) { throw new Exception( 'Template error.' ); } echo $html;}

szablon związany z daną akcją i przeka-zuje do niego odpowiednie parametry.

Dodatkowo wspomniana klasa ma statyczne metody, które udostępniają wszystkie globalne obiekty w postaci sin-geltonów. W ten właśnie sposób obsługi-wane są mechanizmy konfiguracji i logo-wania, łączenie z bazą danych (getSes-sion()) i konwersja obrazów. Dzięki sto-sowaniu tego rodzaju podejścia możli-we jest bardzo proste odwoływanie się do tych mechanizmów w każdym punkcie naszej aplikacji – bez potrzeby tworzenia wielokrotnych instancji obiektów, na przy-kład: ezcGallery::getSession(). W tej części artykułu przeanalizujemy mecha-

nizm obsługi kontrolera. Obiekty typu sin-gelton zbadamy szczegółowo w dalszej części tekstu.

Na początek rozpatrzmy definicję tabli-cy odwzorowującej akcje na odpowiednie pliki z kodem źródłowym pomocniczych kontrolerów, które są wczytywane przy okazji występowania konkretnych żądań. Ze względów bezpieczeństwa pliki te są określone w sposób statyczny – umiesz-czone bezpośrednio w kodzie. Warto za-pamiętać prostą zasadę: nigdy nie defi-niujmy elementów logiki aplikacji na bazie napisu przychodzącego od użytkownika. Poniżej przedstawiona jest definicja wspo-mnianej tablicy:

private

$actions = array(

'listing' => 'actions/listing.php',

'show' => 'actions/show.php',

'create' => 'actions/create.php',

'add' => 'actions/add.php',

'archive' => 'actions/archive.php',

); private $currentAction;

Zmienna $currentAction przechowujeaktualnie wybrany obiekt reprezentujący akcję. Spójrzmy na Listing 5, aby zoba-czyć jak to wygląda.

Na początek w konstruktorze, przy-pisujemy do zmiennej $action wartośćlisting, która określa akcję domyślną.W dalszej kolejności sprawdzamy przy użyciu dedykowanego komponentu Use-rInput, czy pojawiło się żądanie GET. Je-śli tak, to definiujemy zbiór reguł dla Use-rInput w celu walidacji żądania. W tymprzypadku oczekujemy, że w nagłów-ku pojawi się pole action, zawierające ja-kiś napis. Dalej tworzymy instancję obiek-tu ezcInputForm, którego zadaniem jest obsłużenie żądania GET. W ostatnimkroku (końcowa sekcja bloku if), przypi-sujemy pobraną akcję do odpowiedniej zmiennej (jeśli dane były poprawne) lub ustawiamy akcję domyślną (jeśli proces walidacji przebiegł niepomyślnie).

W dalszej kolejności sprawdzamy, czy stworzona akcja może być obsłużona.Jeśli tak nie jest, to rzucamy wyjątek (nie możemy obsłużyć nieistniejącej akcji). Je-śli akcja jest poprawna, to odwołujemy się do powiązanego z nią pliku i uruchamiamy odpowiedni kontroler.

Proszę zwrócić uwagę, że w pre-zentowanym przykładzie używam wyjąt-ków, aby powiadomić użytkownika o sy-tuacji awaryjnej. Praktyka ta nie powin-

Page 53: PHPSolutions_04_2006_PL

eZ components

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org 53

na być stosowana w rozwiązaniach pro-dukcyjnych ze względu na kwestie bezpie-czeństwa – wyjątki mogą zostawić istotne z punktu widzenia ochrony aplikacji infor-macje! Metoda run jest dla odmiany bar-dzo prosta. W jej ciele wywołujemy jedynie metodę run, na stworzonym w konstrukto-rze obiekcie reprezentującym akcję:

public function run()

{

$this->

currentAction->run();

}

Ponieważ komponent do obsługi szablo-nów (eZ Template) nie jest jeszcze gotowy (przewidujemy jego dołączenie w wersji 1.1 pakietu, który powinien pojawić się latem bieżącego roku), do obsługi widoku użyje-my zwyczajnego kodu HTML z osadzonymi wstawkami w języku PHP. Ten prosty me-chanizm obsługi szablonów zrealizowany jest w ramach metody display. Zawartość tej metody przedstawiona jest na Listingu 6.

Każdy roboczy kontroler obsługują-cy konkretną akcję implementuje metodę getTemplateVars() która pozwala głów-nemu kontrolerowi pobrać odpowiednie obiekty wymagane do wygenerowania odpowiedniego kodu HTML. Do obsłuże-nia wypełnionej zawartości szablonu uży-jemy wewnętrznego mechanizmu buforo-wania wyjścia języka PHP, aby uniknąć brzydkich raportów o błędach wyświetla-nych jako zawartość stron WWW. Każ-dy plik szablonowy ma dostęp do tablicy $template, gdzie może znaleźć wszyst-kie potrzebne zmienne. Kontroler kon-kretnej akcji może również zwracać odpo-wiednie szablony w zależności od stanu, w którym się znajduje (na przykład w celu zaprezentowania rezultatu po wypełnieniu i zaakceptowaniu danych na formularzu). Wreszcie, jeśli nie wystąpił żaden błąd, renderowany jest wynikowy kod HTML. Na samym końcu w pliku gallery.php uru-chamiamy głównego kontrolera. Kod od-

powiedzialny za tę czynność przedstawio-ny jest na Listingu 7.

W tym momencie podstawowa struk-tura naszej aplikacji jest gotowa.

Obsługa albumówTeraz omówimy kod używany w celu wy-świetlenia strony z widokiem ogólnym al-bumu. Funkcjonalność ta biedzie stano-wić domyślną akcję w naszej aplikacji. Je-śli ktoś z Czytelników zainstalował oma-wiany przykład i chciałby go przetestowaćw gotowej postaci, to przed wykonaniem tej czynności wymagane jest dodanie do bazy danych kilku testowych rekordów. Kod źródłowy kontrolera odpowiedzialne-go za widok ogólny albumu umieszczo-ny jest w pliku actions/listing.php. Kod ten wymaga dwóch innych plików z naszej te-stowej aplikacji. W ramach niniejszego przykładu nie będziemy wykorzystywać mechanizmu automatycznego wczytywa-nia, więc wspomniane pliki są dołączone w sposób manualny:

require_once 'interfaces/action.php';

require_once 'models/album.php';

Model reprezentujący album (models/album.php) jest dziecinnie prosty i odpo-wiada strukturze rekordu, który opraco-

waliśmy w ramach przygotowań bazy da-nych dla naszej galerii. Kod źródłowy kla-sy Album przedstawiony jest na Listingu 8.

W klasie tej można zaobserwować trzy publiczne składowe, które odpo-wiadają polom rekordu w bazie danych.Jako że obiekty tej klasy są przeznaczo-ne do składowania, dlatego do interfejsu wprowadzamy dwie metody: setState()i getState(). Metody te będą wykorzysta-ne do serializacji i deserializacji modelu obiektu do bazy danych i z powrotem. Obydwie metody przyjmują jako para-metry tablice asocjacyjne, które są wy-korzytywane do przekazywania danych w obydwie strony. Do poszczególnych właściwości modelu można odwołać się na podstawie odpowiednich kluczy.

Zanim przeanalizujemy źródła kontro-lera akcji “Listing”, musimy zapoznać się z metodą ezcGallery::getSession(). Za-wartość tej metody przedstawiona jest na Listingu 9.

Omawiana metoda nie zwraca w bez-pośredni sposób połącznia do bazy da-nych. W zamian otrzymujemy tzw. sesję obsługi składowania (ang. persistent ses-sion). Między tymi dwoma klasami obiek-tów istnieje istotna różnica. Połączenie do bazy danych (czyli obiekt klasy definiu-jącej komponent eZ Database) pozwala

Listing 7. Końcowa sekcja pliku gal-lery.php: uruchamiamy głównego kontrolera

try {

$gallery = new ezcGallery(); $gallery->run();

$gallery->display(); }

catch ( Exception $e )

{ die( $e->getMessage() ); }

Listing 8. Struktura danych reprezentująca album

class Album{ public $id = null;

public $title = '';

public $description = 'No description available.';

public function getState() { return array( 'id' => $this->id,

'title' => $this->title,

'description' => $this->description,

); }

public function setState( array $properties ) { foreach ( $properties as $key => $val ) { $this->$key = $val; } } }

Listing 9. Definicja statycznej metody getSession() w klasie ezcGallery

public static function getSession(){ if ( self::$persistentSession === null ) { $db = ezcDbFactory::create(

self::getConfig()->getSetting( 'config', 'database', 'dsn' )

); ezcDbInstance::set( $db );

self::$persistentSession = new ezcPersistentSession( ezcDbInstance::get(),

new ezcPersistentCodeManager( self::getConfig()->getSetting( 'config', 'php', 'base' )

. DIRECTORY_SEPARATOR

. 'pos' ) );

} return self::$persistentSession; }

Page 54: PHPSolutions_04_2006_PL

eZ components

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org54

wykonywać na bazie danych bezpośred-nie operacje przy pomocy języka SQL. Sesja obsługi składowania oferuje mecha-nizm o wiele bardziej potężny, jako że po-zwala działać na znacznie wyższym po-ziomie abstrakcji. Możemy przy jej pomo-cy zapisywać, odczytywać, modyfikować i kasować obiekty modelu, bez stosowa-nia języka zapytań w sposób bezpośredni. Wynikowy kod SQL jest generowany auto-matycznie przez obiekt sesji.

Wewnątrz metody getSession()wyko-rzystywana jest prywatna statyczna skła-dowa $persistentSession. W ramach tej zmiennej zachowana jest pojedyncza in-stancja sesji oferującej abstrakcyjny in-terfejs dostępu do bazy.

Instancja jest tworzona przy pierw-szym odwołaniu; przy każdym kolejnym

wywołaniu metody getSession() wyko-rzystywana jest instancja istniejąca. Do stworzenia sesji potrzebny jest obiekt re-prezentujący połącznie z bazą danych. Obiekt ten jest tworzony przy pomocy dedykowanej fabryki ezcDbFactory. Da-ne niezbędne do sfinalizowania tego pro-cesu są pobierane z zewnętrznego pli-ku ini. Do pliku tego można odwołać się przy pomocy mechanizmu obsługi konfi-guracji. Prześledźmy szczegółowo cały mechanizm tworzenia sesji. Na początku wywoływana jest metoda ezcDbInstance::set() w celu zainicjalizowania kompo-nentu eZ Database. Przy pomocy no-wo uzyskanego obiektu reprezentujące-go połączenie z bazą danych, tworzymy sesję obsługi składowania. Drugi para-metr przekazywany do konstruktora kla-

sy ezcPersistentSession to tzw. me-nadżer kodu. Obiekt ten obsługuje plikizawierające konfigurację mechanizmu składowania (pliki te umieszczone sąw katalogu pos/). Bazowa część ścieżki dostępu do plików jest pobierana z kon-figuracji. Komponenty do obsługi konfi-guracji obsługują czytanie i zapisywanie ustawień i opcji. W przypadku naszego projektu wykorzystujemy plik ini, gdzie zapisane są wszystkie ustawienia wyma-gane przez galerię. Przykładowy plik ini wygląda następująco:

[database]

dsn=mysql://root:

iglowinthedark@localhost/test

[php]

base=./

DSN to napis określający sposób połą-czenia z bazą danych. Pierwsza część napisu określa rodzaj używanego sil-nika bazodanowego. Dalej, zgodniez konwencją URI, umieszczona jest na-zwa użytkownika i hasło. Informacjete są potrzebne, aby połączyć sięz maszyną na, której uruchomiona jest baza danych (w większości przy-padków jest to localhost). Po ostatnimukośniku (“/”) podana jest nazwa doce-lowego repozytorium. Druga stała okre-śla miejsce w którym znajdują się pli-ki nagłówkowe. Zmienna base określa ścieżkę, w której składowane są klasy PHP. Na Listingu 10 przedstawiona jest metoda getConfig().

W tym przypadku ponownie wyko-rzystany jest wzorzec singleton (ten sam wzorzec jest również zastosowany w komponencie Config. Metoda init() przyjmuje nazwę klasy służącej do czy-tania konkretnego rodzaju konfigura-cji (w naszym przypadku, pliki typu ini) oraz katalog w którym dane konfigura-cyjne są składowane. Sposób interpre-tacji drugiego parametru jest zależny od klasy odpowiedzialnej za odczyty-wanie konfiguracji. Przykładowo, gdyby konfiguracja była składowana w baziedanych, drugi parametr mógłby być obiektem reprezentującym połączenie.

Wracając na moment do Listingu 9 (definicja metody getSession()), Czy-telnik może sobie przypomnieć, w jaki sposób odczytywane są ustawienia kon-figuracyjne. Wygląda to w ten sposób, że wywołujemy metodę getSetting() na stworzonym obiekcie, przy czym

Listing 10. Definicja statycznej metody getConfig() w klasie ezcGallery

public static function getConfig(){

if ( self::$config === null ) { self::$config = ezcConfigurationManager::getInstance();

self::$config->init( 'ezcConfigurationIniReader', dirname( __FILE__ ) ); }

return self::$config; }

Listing 11. Definicja obiektu składowania dla albumu

$def = new ezcPersistentObjectDefinition();$def->table = "album";

$def->class = "Album";$def->idProperty = new ezcPersistentObjectIdProperty();$def->idProperty->columnName = 'id';

$def->idProperty->propertyName = 'id';

$def->idProperty->generator = new ezcPersistentGeneratorDefinition( 'ezcPersistentSequenceGenerator' );

$def->properties['title'] = new ezcPersistentObjectProperty();$def->properties['title']->columnName = 'title';

$def->properties['title']->propertyName = 'title';

$def->properties['title']->propertyType = 'string';

$def->properties['description'] = new ezcPersistentObjectProperty();$def->properties['description']->columnName = 'description';

$def->properties['description']->propertyName = 'description';

$def->properties['description']->propertyType = 'string';

return $def;

Listing 12. Wyszukiwanie albumów

class Listing implements Action{ private $albums;

public function __construct() {}

public function run() { $this->albums = ezcGallery::getSession()->find(

ezcGallery::getSession()->createFindQuery( 'Album' ),'Album');

}

public function getTemplate() { return 'listing.php'; } public function getTemplateVars() { return array( 'albums' => $this->albums ); } }

Page 55: PHPSolutions_04_2006_PL

eZ components

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org 55

jako parametry podajemy: nazwę pliku zawierającego konfigurację (bez roz-szerzenia), nazwę sekcji w pliku ini oraznazwę klucza, dla którego chcemy po-brać wartość.

Jak wspominałem wcześniej, w ce-lu zapisania naszego modelu w bazie danych potrzebujemy definicji klasy ezcPersistentCodeManager. Stwórzmy teraz w katalogu pos/ plik album.phpi umieśćmy w nim kod przedstawiony na Listingu 11.

Każda definicja obiektu przezna-czonego do składowania jest instancją klasy ezcPersistentObjectDefinition. Klasa ta posiada trzy atrybuty służące do określenia tabeli w bazie danych i powiązanej z nią klasy modelu oraz mechanizmu generowania identyfika-torów. Pierwsze dwa atrybuty są zwy-czajnymi napisami, a ostatni jest struk-turą (komponenty eZ emulują struktury przy pomocy zwyczajnych klas) o na-zwie ezcPersistentObjectIdProperty. Elementy tej struktury to nazwa kolum-ny i nazwa składowej oraz genera-tor odpowiedzialny za przydzielanie identyfikatorów dla nowo tworzonych obiektów. W naszym przykładzie ja-ko generatora używamy instancji klasy ezcPersistentSequenceGenerator, któ-ra bazuje na mechanizmie auto_incre-ment oferowanym przez silnik MySQL. W dalszej części pliku zdefiniowane są pozostałe powiązania pomiędzy atrybu-tami klas modelu oraz kolumnami tabeli w bazie danych (ten fragment kodu ma wysoce samoopisowy charakter, więc nie będę go już dodatkowo wyjaśniał). Wreszcie, w ostatniej linii, zwracamy skonfigurowany obiekt definicji.

W tym momencie spełnione są wszystkie potrzebne warunki do rozpo-częcia pracy z obiektami przeznaczo-nymi do składowania. W dalszej części artykułu zapoznamy się ze sposobami używania tych obiektów.

Spójrzmy teraz na Listing 12, gdzie zdefiniowany jest kontroler odpowiedzial-ny za wyszukiwanie albumów. Na po-czątku zdefiniowany jest pusty (i w rze-czywistości nieużywany) konstruktor.Sytuacja ta jest powodowana wymogiem narzuconym przez interfejs akcji. W me-todzie run pobieramy wszystkie albumyz bazy danych i składujemy je w prywat-nej składowej o nazwie $albums.

Aby pobrać składowany obiekt,wywołujemy metodę find() na obiekcie

Listing 13. Prosty szablon

<?php include 'templates/head.php'; ?>

<table width="600">

<tr>

<td>[

<a href="gallery.php?action=create">Create album</a>]

</td>

</tr>

</table>

<ul>

<?php foreach ( $template['albums'] as $album ) { ?>

<li><a href="gallery.php?action=show&album=<?php echo $album->id; ?>">

<?php echo $album->title; ?></a>

</li>

<?php } ?>

</ul>

<?php include 'templates/foot.php'; ?>

Listing 14. Podstawowa struktura metody run() w klasie odpowiedzialnej za ope-rację tworzenia nowego albumu

if ( ezcInputForm::hasPostData() ){ // ...}

else { $this->template = 'create_form.php'; }

Listing 15. Sprawdzanie danych żądania POST w metodzie run() obsługującej operację tworzenia nowego albumu

$definition = array( 'title' => new ezcInputFormDefinitionElement( ezcInputFormDefinitionElement::REQUIRED, 'string' ),

'description' => new ezcInputFormDefinitionElement( ezcInputFormDefinitionElement::REQUIRED, 'string' ),

); $form = new ezcInputForm( INPUT_POST, $definition );

if ( !$form->hasValidData( 'title' ) ) { throw new Exception( 'Title missing.' ); }if ( !$form->hasValidData( 'description' ) ) { throw new Exception( 'Desription missing.' ); }

Listing 16. Wstawianie nowego obiektu w metodzie run() obsługującej operację tworzenia nowego albumu

// ten fragment kodu wykonuje finalne wstawianie obiektu

$this->album = new Album();$this->album->title = $form->title;

$this->album->description = $form->description;

ezcGallery::getSession()->save( $this->album );

ezcGallery::getLog()->log(

"New album created with ID <{$this->album->id}>.", ezcLog::INFO, array( 'category' => 'Create' ) );$this->template = 'create_submit.php';

Listing 17. Tworzenie instancji obiektu reprezentującego log (ezcGallery::ge-tLog())

public static function getLog(){ if ( self::$log === null ) { self::$log = ezcLog::getInstance();

$mapper = self::$log->getMapper();

$writer = new ezcLogUnixFileWriter( 'data/', 'gallery.log' ); $filter = new ezcLogFilter(); $rule = new ezcLogFilterRule( $filter, $writer, true ); $mapper->appendRule( $rule );

self::$log->source = 'Gallery'; } return self::$log; }

Page 56: PHPSolutions_04_2006_PL

eZ components

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org56

rzenia nowego albumu. Prześledź-my implementację kontrolera odpowie-dzialnego za tę operację. Konstruktor klasy jest ponownie pusty, najciekaw-szy fragment kodu znajduje się w cie-le metody run(). Ponieważ definicja wspomnianej metody jest w tym przy-padku dość rozległa, dlatego zdecy-dowałem się zaprezentować ją w kliku mniejszych krokach.

Jak widać, główny szkielet meto-dy stanowi instrukcja warunkowa if,w której sprawdzamy czy pojawiło się żą-danie POST (patrz Listing 14). Jeśli tak się nie stało, to powinniśmy wyświetlićformularz, poprzez który użytkownik bę-dzie mógł wysłać wspomniane żądanie. Aby uzyskać taki a nie inny efekt, usta-wiamy w odpowiedni sposób składową $template.

Fragment kodu przedstawiony naListingu 15 pobiera dane żądania POST i wykonuje na nich podstawowy test sprawdzający. Podobny kod widzieliśmy już wcześniej, w kontekście wykorzysta-nia komponentu UserInput. Jedyna róż-nica między tymi dwoma fragmentami polega na tym, że typy żądań są różne (w poprzednim przykładzie obsługiwali-śmy żądanie typu GET). Kod przedsta-wiony na Listingu 16 wykonuje finalną operację wstawiania nowego obiektu do bazy danych.

Na początku tworzymy nowy obiekt klasy modelu i zapamiętujemy gow kolejnej prywatnej składowej, zwa-nej album. Składowa ta przyda się nam później, przy generowaniu danych na podstawie szablonu. W dalszej kolejno-ści, na podstawie danych odebranychz formularza ustawiamy składowereprezentujące tytuł i opis albumu,i tworzymy sesję składowania w celu wstawienia obiektu do bazy. Dla uprosz-czenia projektu, nasza galeria nie będzie oferować możliwości edycji. Funkcjonal-ność taka jest stosunkowo łatwa w im-plementacji; moglibyśmy ją uzyskać przy pomocy metod update(), delete()i load() klasy ezcPersistentSession. In-ne przydatne w tym kontekście metody to loadIfExists() oraz saveOrUpdate().

Na końcu zapisujemy informa-cję o stworzeniu nowego albumu do lo-gu. Sposób budowania mechanizmulogowania będzie opisany szczegółowow dalszej części artykułu; w tym miej-scu pokażę tylko jak pobrać obiekt od-powiedzialny za tę funkcjonalność i jak

Listing 18. Konstruktor klasy reprezentującej akcję wczytywania obrazu

public function __construct(){

if ( ezcInputForm::hasGetData() ) {

$definition = array( 'album' => new ezcInputFormDefinitionElement( ezcInputFormDefinitionElement::REQUIRED, 'int'

), );

$form = new ezcInputForm( INPUT_GET, $definition ); if ( !$form->hasValidData( 'album' ) ) { throw new Exception( 'Album ID missing.' ); } $this->album = ezcGallery::getSession()->load( 'Album', $form->album );

}

else { throw new Exception( 'Album ID missing.' ); }}

Listing 19. Manipulating the uploaded images

$this->photo = new Photo();$this->photo->title = $form->title;

$this->photo->description = $form->description;

$this->photo->create( $_FILES['photo']['tmp_name'], $this->album->id );

ezcGallery::getLog()->log(

"Added new photo with ID <{$this->photo->id}>.", ezcLog::INFO,

array( 'category' => 'Upload' )); $this->template = 'add_submit.php';

Listing 20. Tworzenie konwertera obrazów

self::$imageConverter = new ezcImageConverter( new ezcImageConverterSettings( array( new ezcImageHandlerSettings( 'imagemagick', 'ezcImageImagemagickHandler'

),

new ezcImageHandlerSettings( 'gd', 'ezcImageGdHandler' ), ) ));

sesji. Metoda ta zwraca tablicę obiek-tów znalezionych na podstawie zapyta-nia. Przy wywołaniu metody specyfikuje-my też typ klasy modelu, której obiektów szukamy w bazie danych (drugi parametr przy wywołaniu find()).

Zapytanie, którego używamy (find) jest fizycznie obsługiwane z poziomu sesji składowania. W tym konkretnym przypadku używamy bardzo uproszczo-nej formy tego zapytania; automatycz-nie wygenerowany kod SQL ma postać SELECT * FROM Album, jako że pobieramy wszystkie dostępne albumy. Bardziej za-awansowany przykład wykorzystania te-go mechanizmu będzie pokazany w dal-szej części artykułu.

Metoda getTemplate() zwraca umieszczoną na stałe nazwę szablo-nu, zaś getTemplateVars() udostępniapobrane wcześniej albumy. Ostatnią czynnością, którą musimy wykonać jest implementacja szablonu. Pozwolę so-bie zaprezentować tylko jeden przykład

szablonu, jako że w przypadku pozosta-łych akcji nie pojawia się w tym kontek-ście nic nowego.

Pliki head.php i foot.php nie zawie-rają żadnej interesującej zawartości, są tam jedynie podstawowe tagi HTML. Pliki te zostały wydzielone ze względu na to, że są dołączane na każdej stro-nie naszej webaplikacji. Korzystającz pętli foreach przeglądamy tablicę za-wierającą albumy (tę, którą jest zwra-cana metodzie getTemplateVars()) i odwołujemy się poprzez publicz-ne składowe do zawartości umiesz-czonych w niej obiektów, aby wstrzyk-nąć do kodu HTML odpowiednie dane.Sądzę, że przedstawiony kod jest na tyle prosty, iż nie wymaga dodatkowych wyjaśnień.

Tworzenie nowego albumuW poniższej sekcji opiszemy, w jaki sposób realizowane jest żądanie two-

Page 57: PHPSolutions_04_2006_PL

eZ components

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org 57

z niego korzystać. Metoda log() pobiera jako pierwszy parametr wiadomość prze-znaczoną do odnotowania. Drugi para-metr określa charakter wpisu (w prezen-towanym przykładzie to jest charakterinformacyjny). Jako ostatni parametr przekazujemy dodatkowe opcje konfi-gurujące wpis. W naszym przykładzieokreślamy dodatkową kategorię. Wyge-nerowany wpis wygląda następująco:

Apr 12 19:20:19

[Info] [Gallery] [Create]

New album created with ID <5>.

Jak widać, silnik logowania dodał auto-matycznie znacznik czasowy, a także wypisał charakter i kategorię wiadomo-ści. To, w jaki sposób we wpisie znala-zła się fraza “[Gallery]”, niechaj chwilowo pozostanie tajemnicą (obiecuję, że nie na długo). Na samym końcu wpisu jest umieszczona nasza wiadomość.

Metody getTemplate() i getTemplate-Vars() nie różnią się zbytnio od swoich odpowiedników w klasie do obsługi ak-cji “Listing”, więc pominiemy w tym miej-scu ich opis. Można jedynie nadmienić, że szablon create_submit.php odpowia-da za wyświetlenie danych albumu, zaś create_form.php zawiera definicję formu-larza służącego do pobierania danych od użytkownika.

Spójrzmy teraz, w jaki sposób budo-wany jest obiekt odpowiedzialny za logo-wanie (Listing 17).

Znowu mamy w tym miejscu do czy-nienia z popularnym wzorcem projekto-wym typu singelton. Jeśli żądana instan-cja logu nie istnieje, to jest ona tworzo-na. Na początku potrzebujemy instancji obiektu odpowiedzialnego za logowanie (warto zauważyć, że jest to singelton udostępniany przez komponent Log).W dalszej kolejności pobieramy z uzy-skanej instancji tzw. mapper. Obiekt ten jest odpowiedzialny za przypisywanie różnych rodzajów logowanych wiado-mości do różnych wynikowych plików.

Aby dodać do naszego obiektu lo-gującego nową regułę, musimy określić mechanizm zapisu. W naszym przypad-ku wybraliśmy obiekt piszący do pliku (w konwencji systemu Unix). Konstruk-tor klasy tego obiektu pobiera na wej-ś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-

bowy ma tutaj ustawione prawa zapisu.W rzeczywistych zastosowaniach prak-tyka taka jest zdecydowanie niezale-cana, głownie ze względu na wymogibezpieczeństwa: w logach mogą znajdo-wać się cenne informacje.

Kolejną cześć reguły stanowi kom-ponent LogFilter. Komponent ten odpo-wiada za filtrowanie wpisów w pewien określony sposób. Wiadomości mogą być filtrowane według charakteru, źró-dła oraz kategorii wpisu. Ponieważ w na-szym przypadku będziemy logować do

pojedynczego pliku, dlatego wybieramy filtr wyłapujący wszystkie wpisy. Ostatniparametr przekazywany do konstrukto-ra to flaga określająca, czy w przypadku dopasowania właściwej reguły, silnik ma kontynuować poszukiwania. Flaga ta jest przydatna, jeśli planujemy logować do wielu źródeł jednocześnie.

Na końcu dołączamy stworzonąregułę do obiektu mapper i ustawiamy domyślne źródło dla obiektu logowania. Teraz Czytelnik może się łatwo domy-śleć dlaczego w przykładowym wpisie

Listing 21. Dodawanie transformacji do konwertera obrazów

self::$imageConverter->createTransformation(

'photo',

array( new ezcImageFilter( 'scale',

array( 'width' => 640, 'height' => 480,

'direction' => ezcImageGeometryFilters::SCALE_DOWN,

) ), ),

array('image/jpeg',));

Listing 22. Transformacja odpowiedzialna za tworzenie miniaturek wczytywa-nych obrazów

self::$imageConverter->createTransformation(

'thumb',

array( new ezcImageFilter( 'scale',

array( 'width' => 150, 'height' => 113,

'direction' => ezcImageGeometryFilters::SCALE_DOWN,

)

),

new ezcImageFilter( 'border',

array( width' => 5, 'color' => array(255, 255, 255,), ) ),

new ezcImageFilter( 'colorspace',

array( 'space' => ezcImageColorspaceFilters::COLORSPACE_SEPIA,

)

),

new ezcImageFilter( 'border',

array( 'width' => 1,

'color' => array(0, 0, 0,), )

),

),

array( 'image/jpeg',

)

);

Page 58: PHPSolutions_04_2006_PL

eZ components

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org58

pojawiła się fraza “[Gallery]”. W więk-szych aplikacjach można ustawiać wiele źródeł, jednak w naszym przypadku uży-jemy jednej domyślnej wartości.

Wczytywanie obrazówJak dotąd mamy możliwość tworzenia nowych albumów, jednak to co napraw-dę nas interesuje, to funkcjonalność wczytywania obrazów. Operacja ta jest zaimplementowana w ramach obsłu-gi akcji „Add”, w pliku actions/add.php. Definicja klasy modelu obrazu jest prak-tycznie taka sama jak w przypadku al-bumu. Jedyną różnicę stanowi metoda create(), którą niedługo szczegółowo opiszemy.

Rozważana akcja wymaga określe-nia konkretnego albumu, do którego bę-dziemy wczytywać obraz. W zawiązkuz tym, w konstruktorze klasy umieścimy kod odpowiedzialny za wczytywanie al-bumu (Listing 18).

Ponownie wykorzystujemy kom-ponent eZ UserInput w celu pobrania identyfikatora albumu wybranego przez użytkownika, jednak w odróżnieniu od pozostałych przykładów, tym razem oczekujemy wartości liczbowej, a nie napisu. Jeśli udało się pobrać identyfi-kator, próbujemy pobrać z bazy danych obiekt reprezentujący album. Warto za-uważyć, że w tym miejscu komponent ezcPersistentSession może rzucić wy-jątek ezcPersistentException, jeśli oka-że się, iż docelowy album nie istnieje.

W dalszej kolejności będziemy ob-sługiwać wczytywany obraz. Mecha-nizm obsługi znajduje się w metodzie run, w klasie reprezentującej rozważaną akcję. Kod podobny do źródła przedsta-wionego na Listingu 19 opisywałem już wcześniej, więc w tym miejscu podaru-jemy sobie jego dokładną analizę.

Obsługa wczytywania plików nie jest (jeszcze) obsługiwana z poziomu komponentu UserInput. Z tej przyczyny musimy odczytać surowe dane z tabli-cy FILES. Najpierw tworzymy instancję obiektu reprezentującego obraz i prze-kazujemy do niego dane podstawowe (tytuł i opis). Pozostałe wymagane ope-racje będą wykonane w ramach metody create() , włącznie z zapisaniem obra-zu do bazy danych.

Zanim zanurkujemy w kod źró-dłowy metody create(), zróbmy ma-łą retrospekcję i powróćmy do defini-cji głównego kontrolera, gdzie znaj-

Listing 23. Definicja metody ezcGallery::getConverter()

public static function getConverter(){

if ( !isset( self::$imageConverter ) ) {

self::$imageConverter = new ezcImageConverter( new ezcImageConverterSettings( array( new ezcImageHandlerSettings( 'imagemagick', 'ezcImageImagemag

ickHandler' ),

new ezcImageHandlerSettings( 'gd', 'ezcImageGdHandler' ), )

)

);

self::$imageConverter->createTransformation(

'photo',

array( new ezcImageFilter( 'scale',

array( 'width' => 640, 'height' => 480,

'direction' => ezcImageGeometryFilters::SCALE_DOWN,

)

),

),

array( 'image/jpeg',

)

);

self::$imageConverter->createTransformation(

'thumb',

array( new ezcImageFilter( 'scale',

array( 'width' => 150, 'height' => 113,

'direction' => ezcImageGeometryFilters::SCALE_DOWN,

)

),

new ezcImageFilter( 'border',

array( 'width' => 5, 'color' => array(255, 255, 255,), )

),

new ezcImageFilter('colorspace', array( 'space' => ezcImageColorspaceFilters::COLORSPACE_SEPIA,

)

),

new ezcImageFilter( 'border',

array( 'width' => 1, 'color' => array(0, 0, 0,), )

),

),

array( 'image/jpeg',

)

);

}

return self::$imageConverter;}

Page 59: PHPSolutions_04_2006_PL

eZ components

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org 59

duje się między innymi operacja getConverter(). Jako że metoda ta jest dość obszerna, przeanalizujemy ją w kliku krokach:

public static

function getConverter()

{

if ( !isset(

self::$imageConverter ) )

{

// ...

}

return

self::$imageConverter;

}

Implementacja wzorca singelton ma identyczną postać jak w przypadkumetody getSession().

Fragment kodu przedstawiony naListingu 20 jest odpowiedzialny za two-rzenie nowego konwertera obrazów.W trakcie tworzenia konwertera wyko-rzystujemy specjalne obiekty do obsłu-gi zewnętrznych mechanizmów filtro-wania obrazów, takich jak dedykowanedla PHP rozszerzenie biblioteki GD lub program ImageMagick. W naszym przy-kładzie korzystamy z tego drugiego rozwiązania. Jeśli z jakichś powodówwymagany program nie może być zain-stalowany, to wystarczy usunąć obiekt do jego obsługi i skorzystać z biblioteki GD.

Ponieważ wiemy z góry, jaki filtr bę-dzie nam potrzebny, zdefiniujmy od razu odpowiednią transformację (Listing 21).

Konwerter obrazów może być skon-figurowany do obsługi tak zwanych transformacji. Każda transformacja jest identyfikowana przez nazwę i za-wiera jeden lub więcej filtrów, oraz typMIME określający format wyjścia.Nasza transformacja będzie zaapliko-wana dla wszystkich wczytanych ob-razów. W jej efekcie wszystkie obrazyo rozmiarach większych niż 640x480 będą przeskalowane w dół. Dodatkowo wszystkie obrazy będą konwertowane do formatu JPEG. Kolejna transforma-cja będzie odpowiedzialna za tworze-nie miniaturek obrazów (Listing 22).

Przy generowaniu miniaturek, po-nownie skalujemy obrazy w dół, tym ra-zem do rozdzielczości 150x113 pikse-li. Następnie dodajemy białą obwódkęi konwertujemy zawartość do przestrze-ni kolorów Sepia. Ta ostatnia transfor-macja sprawi, że nasze obrazy będą

wyglądać jak stare, podniszczone zdję-cia. Na samym końcu dodajemy jesz-cze jedną, czarną obwódkę o szeroko-ści jednego piksela – jako wykończenie (patrz: Listing 23). Spójrzmy teraz, w ja-ki sposób nasz konwerter obrazów wy-korzystany jest w praktyce (Listing 24).

W przedstawionej metodzie pobie-ramy ścieżkę wczytanego obrazu oraz identyfikator powiązanego z nim albu-mu. Następnie przypisujemy identyfika-tor albumu i definiujemy tablicę pomoc-niczych ścieżek. Potrzebne nam bę-dą dwie: jedna dla wczytanego obrazui druga dla miniaturki. Wczytany plik jest i tak przechowywany w tymczasowejlokacji, więc możemy wykorzystać ist-niejącą ścieżkę i wygenerujemy nową tylko dla miniaturki. Wewnątrz bloku try wykonujemy dwufazową transformację obrazów.

Na Listingu 25 pokazany jest kod źródłowy odpowiedzialny za zapisywanie wczytanego obrazu w bazie danych. Po pomyślnym wykonaniu tej operacji otrzy-mujemy identyfikator. Dla uproszczenia przykładowego kodu wczytywane obra-zy są przechowywane pod nazwami od-powiadającymi identyfikatorom w bazie (z rozszerzeniem _thumb dla miniatu-rek). Ponieważ wszystkie wynikowe ob-razy otrzymywane po transformacji będą zapisane w jednolitym formacie, dlatego możemy bezpiecznie zaszyć rozszerze-nie plików w kodzie źródłowym.

Przeglądamy obrazyMamy już zdefiniowane albumy wypeł-nione wczytanymi zdjęciami. Przyjem-nie byłoby mieć jeszcze możliwość ich przejrzenia. Akcja powiązana z tym ele-mentem funkcjonalności jest zdefiniowa-na w pliku show.php wewnątrz katalogu actions/. W konstruktorze klasy pobiera-my identyfikator albumu, który chcemy przeglądać i wyciągamy go z bazy da-nych (Listing 26).

Ponieważ zaprezentowany fragment kodu jest dla nas już bardzo znajomy, dlatego przejdziemy od razu do definicji metody run() (Listing 27).

W ramach wspomnianej metodyrealizujemy operację pobierania wszyst-kich zdjęć dla konkretnego albumu.W tym niewielkim fragmencie kodu po-kazana jest kolejna ciekawa możliwość, oferowana przez komponent Database. Jak na razie widzieliśmy, w jaki sposób można wykonać proste zapytanie przy użyciu find. W tym miejscu możemy za-obserwować, jak można zmodyfikować to zapytanie przy pomocy dodatkowe-go wyrażenia where. W tym celu two-rzymy obiekt reprezentujący zapytaniei wywołujemy na nim metodę where, spe-cyfikując nazwę kolumny oraz poszuki-waną wartość. Teraz możemy przeka-zać zapytanie do metody find wywołanejw ramach sesji obsługi składowania. Pozostała część definicji klasy do ob-sługi akcji wygląda jak na poprzednich

Listing 24. Wykorzystanie konwertera obrazów

public function create[ $imagePath, $album ){

$this->album = $album;

$tmpData['photoPath'] = $imagePath;

$tmpData['thumbPath'] = tempnam( '', 'thumb' );

try

{

ezcGallery::getConverter()->transform(

'photo',

$tmpData['photoPath'],

$tmpData['photoPath'] );

ezcGallery::getConverter()->transform(

'thumb',

$tmpData['photoPath'],

$tmpData['thumbPath'] );

}

catch ( ezcImageTransformationException $e )

{

throw new Exception( 'Image conversion error: <' . $e->getMessage() . '>'

);

} }

Page 60: PHPSolutions_04_2006_PL

eZ components

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org60

przykładach. Szablony HTML są zdefi-niowane w taki sposób, że prezentują listę miniaturek z odnośnikami do peł-nych wersji obrazów.

Obsługa skompresowa-nego archiwumWczytywanie pojedynczych plików do naszej galerii działa już całkiem nieźle. Jednak wczytywanie dużej liczby obra-zów jest bardzo niewygodne. Dobrze byłoby posiadać możliwość wczytania całego archiwum obrazów do galerii, gdzie byłyby one rozpakowane i doda-wane do określonego albumu. Obsługą

tego typu zadnia zajmuje się kontroler obsługujący akcję „Archive”. Konstruk-tor klasy wykonuje dokładnie takie same operacje jak w przypadku obsługi akcji „Add”. Metoda run też wygląda w tym przypadku niemalże identycznie. Różni-ca polega na tym, że przy wczytywaniu archiwum mamy możliwość zdefiniowa-nia szablonu tytułu i opisu dla zawartych wewnątrz obrazów. Szablon ten może zawierać znaczniki „%s”, które będą za-mienione na oryginalną nazwę obrazu (z usuniętym rozszerzeniem). Spójrzmy teraz na interesujący nas fragment me-tody run. Zamiast tworzyć jedną instan-

cję obiektu reprezentującego obraz, ma-my tu wywołanie dwóch metod:

$tmpPath = $this->extractArchive(

$_FILES['archive']

['tmp_name'] );

$this->addPhotos(

$tmpPath, $form->title,

$form->description );

Metoda extractArchive() rozpakowu-je wczytane archiwum do roboczego ka-talogu i zwraca jego ścieżkę. Metoda addPhotos() dodaje poszczególne obrazy do galerii. Na Listingu 28 przedstawione są pierwsze kroki przygotowujące do eks-trakcji plików z archiwum.

Na początku przeprowadzamy test poprawności – sprawdzamy, czy prze-kazana ścieżka rzeczywiście odnosi się do pliku. Dalej musimy posłużyć się małą sztuczką programistyczną w ce-lu stworzenia roboczego katalogu. Po-nieważ język PHP nie posiada funkcjiodpowiedzialnej za przeprowadzenie te-go typu operacji w sposób bezpośred-ni, dlatego musimy skorzystać z funkcji tempnam(), po czym skasować powstały pomocniczy plik i na jego miejscu stwo-rzyć katalog.

Teraz możemy rozpakować archiwum (Listing 29). Wywołanie EzcArchive::

open() zwraca instancję obiektu odpo-wiedzialnego za obsługę wczytanegoarchiwum (lub rzuca wyjątek jeśli typ ar-chiwum nie zastał poprawnie rozpozna-ny). Składniki archiwum możemy w pro-sty sposób przejrzeć przy pomocy pętli foreach. Metoda extractCurrent() roz-pakowuje aktualnie wybrany składniki zapisuje go w określonej lokacji. No, to było naprawdę proste! Czas obsłużyć rozpakowane obrazy (Listing 30).

Przedstawiona metoda przyjmuje na wejściu roboczą ścieżkę, w której znaj-dują się rozpakowane obrazy, a także szablon tytułu i opisu dla poszczegól-nych zdjęć. Metoda zbudowana jest na bazie dużej pętli while, która iteruje po kolejnych składnikach w docelowym ka-talogu. W trakcie iteracji pomijamy kata-logi “.” oraz “..”, a także w rekurencyjny sposób przeglądamy podkatalogi, jeśli takowe istnieją.

Jeśli napotkamy plik, to próbujemy wykonać na nim operację, którą oma-wialiśmy w trakcie analizy kodu obsługi żądania wstawienia nowego obrazu do galerii. Jedyna różnica polega na dołą-

Listing 25. Zapisanie wczytanego obrazu w bazie danych

ezcGallery::getSession()->save( $this );

if ( rename( $tmpData['photoPath'], 'data/' . $this->id . '.jpg' ) === false ){

throw new Exception( 'Unable to store photo.' );}

if ( rename( $tmpData['thumbPath'], 'data/' . $this->id . '_thumb' . '.jpg' ) === false ){

throw new Exception( 'Unable to store thumbnail.' );}

Listing 26. Pobieranie identyfikatora albumu i wyciągnie go z bazy danych

public function __construct(){

if ( ezcInputForm::hasGetData() ) {

$definition = array( 'album' => new ezcInputFormDefinitionElement( ezcInputFormDefinitionElement::REQUIRED, 'int'

),

);

$form = new ezcInputForm( INPUT_GET, $definition ); if ( !$form->hasValidData( 'album' ) ) {

throw new Exception( 'No valid album ID.' ); }

$this->album = ezcGallery::getSession()->load( 'Album', $form->album );

}

else {

throw new Exception( 'No album selected.' ); }

}

Listing 27. Pobieranie wszystkich zdjęć z zadanego albumu

public function run(){

$query = ezcGallery::getSession()->createFindQuery

( 'Photo' );

$query->where(

$query->expr->eq

( 'album', $this->album->id )

);

$this->photos = ezcGallery::getSession()->find

( $query, 'Photo' );

}

Page 61: PHPSolutions_04_2006_PL

eZ components

PHP Solutions Nr 4/2006

Narzędzia

www.phpsolmag.org 61

czeniu obsługi szablonu nazwy i opisu dla poszczególnych zdjęć. Cała resz-ta kodu wygląda identycznie. W bloku catch składowane są wyjątki, aby na końcu przedstawić użytkownikowi kom-pleksowy raport opisujący potencjalne błędy. W tym samym miejscu kasujemy również niepoprawnie obsłużony plik (musimy to zrobić, gdyż ze względu na powstanie sytuacji wyjątkowej funkcja Photo::create() prawdopodobnie nie obsłużyła tego pliku w poprawny spo-sób). Na wszelki wypadek wyciszyłem wywołanie funkcji unlink(); nie polecam stosowania tej techniki w aplikacjach kla-sy produkcyjnej, jednak w naszym przy-padku jest to dobry (i tani) sposób na unikniecie brzydkich komunikatów o błę-dach. Ostatnie wywołanie w metodzie kasuje roboczy katalog.

Szablon wykorzystany do prezen-tacji wyników działania opisywanej akcji różni się trochę od pozostałych przykładów, jednak nadal nie wno-si żadnego istotnego elementu nowo-ści – dlatego nie będę go w tym miej-scu opisywał.

PodsumowanieUdało nam się zbudować w pełni funk-cjonalną galerię obrazów w postaciaplikacji webowej – i co ważne – do-konaliśmy tego przy użyciu relatywnie niewielkiej ilości kodu (w porównaniu do podobnych rozwiązań, w których wszystko jest pisane od podstaw). Co więcej, zbudowany kod cechuje się bar-dzo dobrą organizacją i spójną struk-turą. Mam nadzieję, że przedstawiony tekst przekonał czytelników do korzy-stania z komponentów eZ i rozjaśnił nie-co koncepcje na których te komponenty zostały zbudowane.

Na koniec, gorąco zachęcam czy-telników do dalszych eksperymentów z tą technologią. Zainteresowanych zapraszam na nasze forum dyskusyj-ne – wszelkie komentarze są mile wi-

dziane. Jeśli mielibyście ochotę w ja-kiś sposób przyczynić się do rozwoju tego projektu – to nic nie stoi na prze-szkodzie. Wystarczy napisać pod adres [email protected]. n

Listing 28. Przygotowanie do eks-trakcji plików z wczytanego archi-wum.

if ( !is_uploaded_file( $file ) ){

throw new Exception( 'Illegal file upload.' );

}

// Create a temp dir

$path = tempnam( 'data/',

'upload' );

unlink( $path );mkdir( $path );

Listing 29. Ekstrakcja obrazówz archiwum

try

{

$archive = ezcArchive::open( $file

);

}

catch ( ezcArchiveException $e )

{

throw new Exception( 'Archive invalid.' );

}

foreach ( $archive as $entry ){

$archive->

extractCurrent( $path );

}

return $path;

Listing 30. Definicja metody addPhotos()

private function addPhotos( $path, $title, $description ){

$d = dir( $path ); while ( ($entry = $d->read() ) !== false ) {

if ( $entry == '.' || $entry == '..' ) {

continue; }

$fullPath = $path . DIRECTORY_SEPARATOR . $entry;

if ( is_dir( $fullPath ) ) {

$this->addPhotos( $fullPath, $title, $description );

}

else {

$name = substr( basename( $fullPath ), 0, strrpos( basename( $fullPath ), '.' ) );

try

{

$photo = new Photo(); $photo->title = sprintf( $title, $name ); $photo->description = sprintf( $description, $name ); $photo->create( $fullPath, $this->album->id );

$this->photos[] = clone( $photo );

ezcGallery::getLog()->log(

"Added new photo with ID <{$photo->id}> from archive.", ezcLog::INFO,

array( 'category' => 'Upload' ) );

}

catch ( Exception $e )

{

$this->errors[] = $e;

// Cleanup

@unlik( $fullPath );

}

}

}

rmdir( $path );}

Tobias Schlitt pracuje dla eZ systems jako główny programista. Do jego pod-stawowych obowiązków należy rozwi-janie projektu eZ components. Tobias studiuje też informatykę na uniwersyte-cie w Dortmund, mając za sobą praktykiw Deutsche Bank, na stanowisku spe-cjalisty IT. Tobias jest znanym ekspertem w środowisku prgramistów PHP, ku cze-mu przyczyniły się jego prace nad pro-jektem PEAR (w którym nadal zresztą uczestniczy).

O autorze

Page 62: PHPSolutions_04_2006_PL

www.phpsolmag.org62

Techniki

PHP Solutions Nr 4/2006

Wzorce projektowe: dekorator

www.phpsolmag.org 63

Techniki

PHP Solutions Nr 4/2006

Jednym z najczęściej omawianychi używanych wzorców projektowych jest dekorator. Z punktu widzenia

programisty ma same zalety: jest bardzo użyteczny, nieskomplikowany w imple-mentacji i łatwy do przyswojenia. Obok singletona jest to prawdopodobnie jeden z pierwszych wzorców, z którymi stykamy się, gdy zaczynamy studiować przykła-dy i literaturę poświęconą zasadom pro-jektowania. O ile jednak singleton jest ob-winiany (często słusznie!) o promowaniefatalnych rozwiązań, to dokładne pozna-nie dekoratora przyczyniło się do powsta-nia wielu ciekawych rozwiązań architekto-nicznych.

Nazwa wzorca projektowego „deko-rator” jest nieco myląca, ponieważ su-geruje, że będziemy coś wzbogacać, de-korować czy upiększać. Stąd może po-wstać błędne przeświadczenie, że ma-my do czynienia z tworem przydatnym jedynie w warstwie prezentacji. Nic bar-dziej błędnego! Omawiany wzorzec znaj-

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

duje szerokie zastosowanie, niezależnie od tego, czy projektujemy warstwę do-stępu do bazy danych, logikę bizneso-wą lub kontroler MVC. Jego istotą jest możliwość wzbogacenia funkcjonalności obiektów danych klas w sposób dyna-miczny, bez konieczności modyfikowania oryginalnych obiektów.

Definicje jak zwykle brzmią zbyt su-cho, spójrzmy więc na Listing 1, gdzie

Dekorator: wzorzec projektowy na każdą bolączkęPaweł Kozłowski

W SIECI

• http://flexi.sourceforge.net/ – budowany przez nas fra-mework

• http://www.picocontainer.org/Ports – Pico

• http://www.martinfowler.com/articles/injection.html – cie-kawe artykuły

• http://www.phppatterns.com/ – ciekawe artykuły

Stopień trudności: lll

Co należy wiedzieć...Przydatna będzie znajomość dwóch po-przednich artykułów z naszej serii Wzor-ce projektowe. Czytelnik powinien mieć wiedzę z obiektowych technik projekto-wania aplikacji.

Co obiecujemy...Z artykułu dowiesz się, kiedy i dlaczego warto stosować wzorzec projektowy de-korator. Na przykładach pokażemy, jak stosując dekorator, bardzo łatwo i w ele-gancki sposób dodać nową funkcjonal-ność do aplikacji bez zbędnej modyfika-cji kodu.

Page 63: PHPSolutions_04_2006_PL

www.phpsolmag.org62

Techniki

PHP Solutions Nr 4/2006

Wzorce projektowe: dekorator

www.phpsolmag.org 63

Techniki

PHP Solutions Nr 4/2006

znajduje się elementarny przykład użycia dekoratora. W przedstawio-nym przypadku użyliśmy dekoratora do wzbogacenia istniejącej funkcjonalno-ści. Nie musimy jednak zbyt ściśle su-gerować się nazwą wzorca projektowe-go i spokojnie możemy pozwolić sobie na całkowitą zmianę funkcjonalności dekorowanego obiektu.

Dwa proste przykłady, przedstawio-ne na wspomnianych listingach, obrazują praktycznie wszystkie istotne cechy wzor-ca, z którym się zaznajamiamy. Szczegól-ne istotny do odnotowania jest jeden fakt: dla klienta korzystającego z udekorowa-nego (często mówimy również – „opako-wanego”) obiektu obecność dekoratora jest zupełnie przezroczysta! Klient nie jest w stanie stwierdzić, czy korzysta z usług oryginalnego obiektu, czy też z jego ude-korowanej wersji. Jest to możliwe dzię-ki zachowaniu przez dekorator interfejsu oryginalnego obiektu. Jeśli chcielibyśmy zapamiętać jakąś jedną zasadę czy zda-nie opisujące dekoratory, mogłoby to być: Dokorator zachowuje interfejs oryginalne-go obiektu i najczęściej przyjmuje dekoro-wany obiekt jako parametr w swoim kon-struktorze.

Kolejną bardzo miłą cechą dekoratora jest możliwość użycia więcej niż jednego „opakowania” dla podstawowego obiek-tu. Oznacza to, iż możemy składać no-wą funkcjonalność z wielu już istniejących elementów, bez konieczności ingerencjiw raz stworzony kod. Jest to dokładnie ta sama filozofia, jaką spotykamy przy pracy z powłoką w systemach uniksowych. Do-skonale wiemy, jak wiele pożytecznych operacji można wykonać, zaprzęgając do wspólnej pracy elementarne narzędzia. To samo odnosi się do dekoratorów: sprytnie łącząc wiele prostych dodatków możemy uzyskać funkcjonalność, o której nie śniło się nawet twórcom piszącym klasy czy też dekoratory (Listing 2).

Zanim przejdziemy do omówie-nia bardziej skomplikowanych przykła-dów, zatrzymajmy się jeszcze na chwilę nad własnością dekoratorów, dzięki któ-rej klient nie jest w stanie rozróżnić, czy ma do czynienia z obiektem podstawo-wym, czy też z opakowaną jego wersją. Uważny Czytelnik zauważy, że ta nie-rozróżnialność jest możliwa, ponieważ pomiędzy dekoratorem i obiektem de-korowanym zachodzi taka sama relacja

(„jest”, ang. IS-A), jak przy dziedziczeniu. Istotnie, przykład z Listingu 2 można byzapisać tylko i wyłącznie przy pomocy tworzenia nowych podklas (Listing 3). Jaki jest więc sens wprowadzania zu-pełnie nowej konstrukcji programistycz-nej i opisywania jej jako wzorca projekto-wego? Przecież to samo można uzyskać przy użyciu podstawowych konstrukcji programowania obiektowego. Jest jed-na, dosyć istotna różnica pomiędzy dwo-ma wspomnianymi podejściami. Aby ją łatwo pokazać, przemodelujmy przykładz Listingu 2, zmieniając kolejność zasto-sowanych dekoratorów (Listing 4). Pro-szę bardzo: cała operacja była wyjąt-kowo prosta, odbyła się bez koniecz-ności modyfikowania istniejącego ko-

du i doprowadziła do powstania nowej funkcjonalności. Niestety, w przypadkudziedziczenia mamy dużo trudniejsze za-danie: nie obędzie się bez dosyć istotne-go przemeblowania napisanego kodu.

Powodem jest fakt, iż formując hie-rarchię dziedziczenia tworzymy dość sztywne, statyczne powiązanie pomię-dzy poszczególnymi klasami.

W przypadku dekoratorów nie ma-my takiego ograniczenia: możemy do-wolnie przestawiać kolejność, dodawaćnowe elementy do łańcucha i usuwać już istniejące. Takie zamiany są możliwe nie tylko w momencie ustalania struktury ko-du, ale w dowolnym momencie, nawetw czasie wykonywania skryptu. Dziękidekoratorom można uzyskać wszystkie

Listing 1. Prosty przykład użycia dekoratora

<?php

/**

*interfejs, ktory może być implementowany przez wiele klas

*ponieważ dekoratory również implementują ten interfejs

*klient korzystający z udekorawanej klasy nie jest w stanie

*rozróżnić, czy ma do czynienia z pierwotną, czy udekorowaną wersją

*/

interface UserDAO {

public function save(User $user); public function findByLoginNamePassword($loginName, $password);}

class UserDAOImpl implements UserDAO { public function save(User $user) { // standardowa funkcjonalność zapisywania

// danych użytkowników do DB

}

public function findByLoginNamePassword($loginName, $password){ // standardowa funkcjonalność odnajdywania

// danych użytkowników po nazwie

}

}

class UserDAOUpperCaseLoginDecorator implements UserDAO { private $_decoratedDao;

public function __construct(UserDAO $decoratedDao){ $this->_decoratedDao = $decoratedDao;

}

public function save(User $user) { //zadaniem dekoratora jest zapisywanie loginów

//tylko wielkimi literami

$user->setLoginName(strtoupper($user->getLoginName())); //wywołanie oryginalnej funkcjonalności

$this->_decoratedDao->save($user);

}

public function findByLoginNamePassword($loginName, $password) { //implementacja wyszukiwania pozostaje bez zmian

return $this->_decoratedDao->findByLoginNamePassword( $loginName, $password);

}

}

//konstruowanie udekorowanej wersji obiektu

$dao = new UserDAOUpperCaseLoginDecorator(new UserDAOImpl());?>

Page 64: PHPSolutions_04_2006_PL

Wzorce projektowe: dekorator

www.phpsolmag.org64

Techniki

PHP Solutions Nr 4/2006

dobrodziejstwa dziedziczenia, ale w spo-sób bardziej elastyczny.

Nie oznacza to oczywiście, iż nama-wiamy Was, by od dzisiaj nie używać dzie-dziczenia i pisać tylko dekoratory. Jak każdy wzorzec projektowy, dekorator niepowinien być stosowany tylko w celu otrzymania bardziej elastycznej architek-tury, ale w przypadkach, w których będzie naprawdę pomocny. Uzbrojeni w solidne podstawy teoretyczne możemy przyjrzeć się wreszcie przykładom pokazującym, jak w łatwy i efektywny sposób rozbudować nasze aplikacje bez konieczności modyfi-kacji ich podstawowego kodu.

Wróćmy zatem do przykładów. Jed-ną z dobrych praktyk architektonicznych jest dzielenie kodu aplikacji na warstwy. W typowej aplikacji WWW, najniższą warstwę stanowi baza danych (najczę-ściej w postaci relacyjnej bazy danych lub zwykłych plików). Powyżej umiesz-czamy wartwę dostępu do danych w po-staci obiektów DAO (ang. Data Access Objects). Już ta jedna wspomniana war-stwa jest okazją do zaimplementowaniu wielu funkcjonalności w postaci deko-ratorów. Wyobraźmy sobie, że chcemy zapisywać czas wyszukiwania danychw DB. Nic prostszego: zamiast mody-fikować istniejący kod, otoczmy go do-datkową klasą (Listing 5). A może mamy DAO służące do zapisu danych użyt-kowników i w pewnych przypadkach chcemy szyfrować hasła. Proszę bar-dzo: zamiast utrzymywać dwie zbliżone wersje tego samego DAO, dużo efek-tywniej rozwiążemy problem stosując prosty dekorator – bez modyfikowaniai duplikowania istniejącego kodu.

Przykłady można mnożyć równieżw innych warstwach sytemu. Jeśli pomy-ślimy o dowolnej logice biznesowej zapi-sującej dane, to dekoratory znajdą zasto-sowanie przy wersjonowaniu danych, za-pisywaniu logów zmian, kontroli dostępu itd. Przesuwając się jeszcze jedną war-stwę wyżej, do kontrolerów MVC, szybko odkryjemy kolejne zastosowania: śledze-nie zachowań użytkowników (np. ścież-ki poruszania się po serwisie WWW) czy sprawdzanie uprawnień. Zastosowaniai przykłady można by mnożyć w nieskoń-czoność. Poprzestańmy więc na stwier-dzeniu, że wzorzec dekoratora jest nie-zwykle użyteczny i podpowiada eleganc-kie rozwiązania dla często spotykanych problemów związanych z dodawaniem specyficznej funkcjonalności w różnych

wersjach tej samej, podstawowej apli-kacji. Ta mnogość i wygoda zastosowań powoduje, że w złożonej aplikacji może-my mieć wiele dekoratorów dla jednego obiektu. Pisanie takiej ilości dekoratorów i zarządzanie ich konfiguracją stanowi wy-zwanie samo w sobie. W dalszej części artykułu zastanowimy się więc, jak szybko pisać i konfigurować dekoratory.

Zatrzymajmy się na dłużej nad przy-wołanym przykładem zastosowania de-

koratorów do dodania obsługi uprawnieńna poziomie warstwy kontrolerów MVC. Listing 6 pokazuje dwa przykładowe kon-trolery oraz opakowanie z dekoratorów sprawdzających uprawnienia. Przedsta-wiony schemat dodawania obsługi upraw-nień do aplikacji jest niezwykle efektyw-ny. Po pierwsze, pozwala uruchamiać tą samą aplikację ze sprawdzaniem upraw-nień i bez, w zależności od wymagań klienta. Z drugiej strony, taka architektura

Listing 2. Łączymy dekoratory

<?php

class UserDAOPasswordHashingDecorator implements UserDAO { private $_decoratedDao;

public function __construct(UserDAO $decoratedDao){ $this->_decoratedDao = $decoratedDao;

}

public function save(User $user) { //zadaniem dekoratora jest zapisywanie loginów

//tylko wielkimi literami

$user->setPassword(md5($user->getPassword())); //wywołanie oryginalnej funkcjonalności

$this->_decoratedDao->save($user);

}

public function findByLoginNamePassword($loginName, $password) { //przy wyszukiwaniu również szyfrujemy hasła

return $this->_decoratedDao->findByLoginNamePassword( $loginName, md5($password)); }

}

//konstruowanie wielokrotnie udekorowanej wersji obiektu

$dao = new UserDAOUpperCaseLoginDecorator(new UserDAOPasswordHashingDecorator( new UserDAOImpl()));?>

Listing 3. Przykład z Listingu 2 zapisany przy pomocy nowych podklas

<?php

class UserDAOUpperCaseLogin extends UserDAOImpl { public function save(User $user) { $user->setLoginName(strtoupper($user->getLoginName())); parent::save($user);

}

}

class UserDAOPasswordHashing extends UserDAOUpperCaseLogin { public function save(User $user) { $user->setPassword(md5($user->getPassword())); parent::save($user);

}

public function findByLoginNamePassword($loginName, $password) { return parent::findByLoginNamePassword($loginName, md5($password)); }

}

//konstruowanie wersji obiektu z dziedziczeniem

$dao = new UserDAOPasswordHashing();?>

Listing 4. Zmieniona kolejność zastosowanych dekoratorów w stosunku do Listingu 2. Tworzymy nową funkcjonalność bez modyfikacji istniejącego kodu.

<?php

$dao = new UserDAOPasswordHashingDecorator(new UserDAOUpperCaseLoginDecorator( new UserDAOImpl()));?>

Page 65: PHPSolutions_04_2006_PL
Page 66: PHPSolutions_04_2006_PL

Wzorce projektowe: dekorator

www.phpsolmag.org66

Techniki

PHP Solutions Nr 4/2006

umożliwia niezależne pisania kodu biz-nesowego i odpowiedzialnego za bez-pieczeństwo. Wreszcie, możemy sobie wyobrazić zastosowanie różnych silni-ków do sprawdzania uprawnień (np. wła-sny lub GACL). Niestety, nasze wszystkie zachwyty trochę przybledną, jeśli uświa-domimy sobie, że dla każdego kontrole-ra trzeba stworzyć osobny dekorator. Nie jest to może wielkim problemem przy pię-ciu czy dziesięciu kontrolerach, ale sta-je się koszmarem przy dużych aplika-cjach, gdzie często spotkamy setki klas ty-pu kontroler. Nasze miny staną się jesz-cze bardziej nietęgie, jeśli uświadomimy sobie, że każdy z dekoratorów będzie do siebie bardzo podobny. Ponowny rzut oka na Listing 6 faktycznie ujawnia, że w kolej-nych dekoratorach różnią się tylko nazwy klas i metod – zasadnicza logika pozosta-je praktycznie bez zmian.

Bezmyślne pisanie niemal iden-tycznych dekoratorów nie jest zajęciem szczególnie ciekawym czy produktyw-nym, a dodatkowo – to oczywista dupli-kacja kodu. Musimy więc znaleźć jakiś sposób na zautomatyzowanie żmudne-go zajęcia. PHP5, jako język niezwykledynamiczny daje nam szerokie możli-wości generowania szablonowego koduw locie. Spośród wielu opcji dwie wydają się najciekawsze: użycie magicznej me-tody __call lub generowanie całego ko-du dekoratora w czasie działania skryp-tu. Obie metody mają swoje wady i za-lety, które za chwilę omówimy, ale naj-pierw spójrzmy na Listing 7, gdzie znaj-dują się przykładowe dekoratory (z Li-stingu 6), zaimplementowane przy uży-ciu obu wspomnianych metod.

Dekorator stworzony przy pomocy __call jest dosyć prosty i pozwala pre-zycyjnie regulować, które metody w de-koratorze są generowane automatyczne, a które ręcznie. Po prostu dla tych, któ-re chcemy napisać sami, tworzymy kon-

Aby obejść zidentyfikowany problem, posłużymy się prostą koncepcyjnie me-todą – generowaniem kodu w locie. Spójrzmy na Listing 8, gdzie pokazujemy użycie specjalnie przygotowanej biblio-teki (PHPProxy, dostępnej na sourcefor-ge.net) do generowania kodu. Jak widać, samo jej użycie jest niezwykle proste – sprowadza się do zaimplementowania tylko tej logiki, która ma się znaleźć w de-koratorze – nie musimy powielać ani jed-nego znaku kodu. Tak prosta implemen-tacja jest możliwa dzięki istnieniu inter-fejsu ProxyInvocationHandler. W inter-fejsie tym zdefiniowana jest tylko jedna metoda – invoke($method, $args). Nie trzeba być potomkiem Sherlocka Holme-sa by wydedukować, że metoda ta jest uruchamiana w momencie wywoływania metod na dekoratorze, a w argumetach otrzymujemy wszystkie niezbędne infor-macje o wywołaniu – tj. nazwę metodyi jej argumenty. Korzystając z implemen-tacji interfejsu ProxyInvocationHandler

z opisywanej biblioteki (Listing 9), może-my łatwo zdecydować, czy chcemy tyl-ko udekorować oryginalną funkcjonal-ność (a więc wywołać ją podczas uru-chamiania dekoratora), czy też stwo-rzyć zupełnie nowy kod: cały sekret tkwi

Listing 5. Dodajemy nową funkcjonalność do zapisywania czasu wyszukiwania danych w DB

<?php

class PerformanceLoggingUserDAO implements UserDAO { private $_decoratedDao;

private $_startTime;

public function __construct(UserDAO $decoratedDao){ $this->_decoratedDao = $decoratedDao;

}

public function save(User $user) { $this->startMethodExecution();

$this->_decoratedDao->save($user);

$this->stopMethodExecution('save');

}

public function findByLoginNamePassword($loginName, $password) { $this->startMethodExecution();

return $this->_decoratedDao->findByLoginNamePassword( $loginName, $password);

$this->stopMethodExecution('findByLoginNamePassword');

}

private function startMethodExecution(){ $this->_startTime = microtime(); }

private function stopMethodExecution($methodName){ $executionTime = microtime() - $this->_startTime; echo "Wykonanie metody '$methodName' zajęło $executionTime <br>"; }

}

//konstruowanie udekorowanej wersji obiektu

$dao = new PerformanceLoggingUserDAO(new UserDAOImpl());?>

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

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

�����������

Rysunek 1. Uproszczony graf obiektów w naszej aplikacji

kretną metodą, a wprzypadku pozosta-łych metod polegamy na __call. Oma-wiana właśnie metoda ma w zasadzie tyl-ko jedną wadę – w ten sposób wygene-rowane dekoratory nie mogą być użyte w wywołaniach funkcji i metod, dla którychokreślono typ argumentu (a więc dla kon-strukcji $obiekt->nazwaMetody(TypArgume

ntu $agrument);). Jest to dość poważna wada, ponieważ na początku podkreśla-liśmy, iż jedną z cech dekoratora jest za-chowanie interfejsu oryginalnego obiek-tu. Zastosowanie __call do generowania dekoratorów wyklucza silniejszą kontrolę typów, a tym samym – interfejsów imple-mentowanych przez podstawową i udeko-rowaną klasę.

Page 67: PHPSolutions_04_2006_PL

Wzorce projektowe: dekorator

www.phpsolmag.org 67

Techniki

PHP Solutions Nr 4/2006

w metodzie getDelegate(), dzieki której możemy pobrać dekorowany obiekt i wy-wołać na nim dowolną metodę.

Użycie biblioteki PHPProxy pozwala generować dekoratory przy minimalnym nakładzie pracy ze strony programistyi przy eliminacji powtórzeń w kodzie. Niestety, nie jest to metoda pozbawiona wad. Po pierwsze, generowanie kodu w czasie działania programu może być zbyt czasochłonne w przypadku mocno obciążonych serwisów internetowych, gdzie wydajność jest rzeczą krytyczną.

Drugi, trochę mniej uciążliwy pro-blem, związany jest z przypadłością nękająca wszystkie rozwiązania oparteo generowanie kodu. Otóż podczas wy-konania programu uruchamiany jest również kod, który... nie jest nigdziezapisany na stałe. Oznacza to, żew pewnych warunkach śledzenie wyko-nania programu (np. podczas wyszuki-wania błędów) może być nieco utrud-nione. Tak czy inaczej, mimo przedsta-wionych wad, jest to zdecydowanie naj-łatwiejsza metoda tworzenia wielu deko-ratorów.

Podsumujmy naszą dotychczaso-wą wiedzę o dekoratorach. Na wstę-pie stwierdziliśmy, że jest to bardzo po-żyteczny wzorzec, który może być sto-sowany praktycznie w każdej warstwie systemu. Jest to alternatywa do dziedzi-czenia obiektów, umożliwiająca wzbo-gacanie lub zmianę istniejącej funkcjo-nalności, bez konieczności modyfikacji raz napisanego kodu. Dekorator pada jednak trochę ofiarą własnego sukcesu – mnogość jego zastosowań powodu-je, że w bardziej rozbudowanej aplikacji możemy wykorzystać setki obiektów im-plementujących wzorzec dekoratora.

Aby nie zginąć w tym gąszczu po-dobnych obiektów, znaleźliśmy dwie metody na dynamiczne generowanie dekoratorów. Niestety, to nie koniec pro-blemów, które musimy rozwiązać, aby efektywnie wykorzystać dekoratory ja-ko integralną część rozwiązania archi-tektonicznego.

Załóżmy, że Rysunek 1 przedsta-wia uproszczony graf obiektów w naszej aplikacji. Obiekty te są oczywiście uło-żone w warstwy, natomiast obiekty sąpołączone między sobą siecią zależ-ności. Już samo poprawne skonstru-owanie takiej sieci obiektów może być nie lada wyzwaniem, o czym Czytelnik mógł się przekonać, śledząc mój artykuł

Listing 6a. Dwa przykładowe kontrolery oraz opakowanie z dekoratorów sprawdzających uprawnienia

<?php

class useraction { private $_userService;

public function __construct(UserService $userService) { $this->_userService = $userService;

}

public function listall(HttpRequest $request, ModelAndView $mv){ $users = $this->_userService->findAll();

$mv->addToModel('users',$users);

$mv->setView('userslist');

return $mv; }

public function listwithexpensivequery( HttpRequest $request, ModelAndView $mv){ $users = $this->_userService->findUserByExpensiveQuery();

$mv->addToModel('users',$users);

$mv->setView('userslist');

return $mv; }

public function addform(HttpRequest $request, ModelAndView $mv){ $mv->setView('useraddform');

return $mv; }

public function add(HttpRequest $request, ModelAndView $mv){ $user = new User( $request->getParam('login'),

$request->getParam('pass'),

$request->getParam('firstname'),

$request->getParam('lastname'));

$mv->addToModel('user',$user);

try {

$this->_userService->addUser($user);

$mv->setView('useraddconfirm');

} catch (UserExistsException $e) {

$mv->addToModel('adduser_error','User exists!');

$mv->setView('useraddform');

}

return $mv; }

}

class homepage { public function show(HttpRequest $request, ModelAndView $mv){ $mv->setView('homepage');

return $mv; }

}

Rysunek 2. Dodając dekoratory, wprowadzamy do omawianego grafu kolejny stopień skomplikowania, opakowując wybrane obiekty aplikacji

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

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

�����������

���

�����������

���

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

���

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

Page 68: PHPSolutions_04_2006_PL

Wzorce projektowe: dekorator

www.phpsolmag.org68

Techniki

PHP Solutions Nr 4/2006

Obiektowa linia montażowa, czyli przej-rzyste i elastyczne aplikacje w PHP5, z numeru 1/2006. Dodając dekoratory, wprowadzamy do omawianego grafu ko-lejny stopień skomplikowania, opakowu-jąc wybrane obiekty aplikacji (Rysunek 2). Warto przy tym zauważyć, że udekorowa-niu będą podlegały głównie obiekty infra-strukturalne (kontrolery, DAO itd.), a nie domenowe. Musimy więc znaleźć jakiś ła-twy sposób na dodanie wielu dekoratorów w całej aplikacji, bez konieczności ręczne-go przebudowywania połączeń.

Przy składaniu skomplikowanych grafów obiektów doskonale sprawdza się wzorzec architektoniczny IoC (ang. Inversion of Control), o którym również pisaliśmy w wyżej wspomnianym artyku-le. Mamy szczęście, ponieważ ten sam wzorzec również znakomicie ułatwia do-dawanie dekoratorów. Dlaczego? Przy-pomnijmy, że w przypadku stosowania wzorca IoC bardzo pomocne są bibliote-ki typu kontener IoC. Te lekkie kontene-ry biorą na siebie cały ciężar tworzenia skomplikowanych grafów obiektów, dba-jąc przy tym o właściwe rozwiązanie za-leżności pomiędzy obiektami. Kontener IoC jest więc jednym, centralnym miej-scem, gdzie powoływane są do życia instancje obiektów infrastrukturalnych. Doskonale, o to nam właśnie chodziło. To scentralizowane miejsce tworzenia obiektów pozwala nam łatwo dodać na-sze „opakowania”. Spójrzmy na Listing 10, gdzie znajdziemy przykład wykorzy-stania Pico dla PHP – lekkiego konte-nera IoC – do dodania dekoratorów do obiektów aplikacji. Jak widać, cała ope-racja jest stosunkowo prosta – wystar-czą dwie linijki kodu.

Przeanalizujmy dokładniej przykładz Listingu 10. Widzimy na nim początko-wo dwa współpracujące ze sobą obiek-ty: serwisowy i DAO. Są to obiekty infra-strukturalne, podczas dzialania aplikacji potrzebny jest zwykle tylko jeden egzem-plarz takiego obiektu. W tym przypadku to Pico dba o powołanie do życia instan-cji obiektów oraz ich połączenie. Aby za-stosować dekorator zliczający czas wy-konania poszczególnych metod, musimy w jakiś sposób rozerwać ścisłe połącze-nie pomiędzy obiektem DAO i obiektemz DAO korzystającym. Przy klasycznym podejściu do budowy programów, gdyby-śmy użyli operatora new, metod statycz-nych lub fabryk, mielibyśmy małe szan-se na wprowadzenie trzeciego obiektu

Listing 6b. Dwa przykładowe kontrolery oraz opakowanie z dekoratorów sprawdzających

// Klasa pomocnicza dla dekoratorów, gdzie odbywa się

// właściwe sprawdzanie uprawnień

abstract class AbstractActionSecurityDecoratorImpl { private $_decoratedAction;

public function __construct($decoratedAction) { $this->_decoratedAction = $decoratedAction;

}

public function executeTargetActionWithSecurityCheck( $targetAction, HttpRequest $request, ModelAndView $mv){

//tutaj tylko proste sprawdzanie, czy zalogowany,

//ale równie łatwo zintegrować np. GACL

session_start();

if ($_SESSION['user'] == null) { $mv->setView('loginform');

return $mv; } else { return $this->_decoratedAction->$targetAction($request, $mv); }

}

}

//Mimo, iż obie akcje są zupełnie inne,

//to dekoratory są niemal identyczne!

//Oczywista duplikacja kodu!

class UserActionSecurityDecoratorImpl extends AbstractActionSecurityDecoratorImpl { public function listall(HttpRequest $request, ModelAndView $mv){ return $this->executeTargetActionWithSecurityCheck( 'listall', $request, $mv);

}

public function listwithexpensivequery( HttpRequest $request, ModelAndView $mv){ return $this->executeTargetActionWithSecurityCheck( 'listwithexpensivequery', $request, $mv);

}

public function addform(HttpRequest $request, ModelAndView $mv){ return $this->executeTargetActionWithSecurityCheck( 'addform', $request, $mv);

}

public function add(HttpRequest $request, ModelAndView $mv){ return $this->executeTargetActionWithSecurityCheck('add', $request, $mv); }

}

class HomepageActionSecurityDecoratorImpl extends AbstractActionSecurityDecoratorImpl { public function show(HttpRequest $request, ModelAndView $mv){ return $this->executeTargetActionWithSecurityCheck('show', $request, $mv); }

}

?>

Rysunek 3. W Pico zawarta jest cała konfiguracja związana z połączeniami pomiędzy obiektami. Aby wprowadzić dodatkowy obiekt pośredniczący, musimy jedynie przekonfigurować połączenia.

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

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

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

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

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

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

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

Page 69: PHPSolutions_04_2006_PL

Wzorce projektowe: dekorator

www.phpsolmag.org 69

Techniki

PHP Solutions Nr 4/2006

Listing 7. Dekorator (z Listingu 6), zaimplementowany przy użyciu __call oraz generowanie całego kodu dekoratora w czasie działania skryptu

<?php

// implementacja dekoratora z wykorzystaniem metody __call

class UserActionSecurityDecoratorCallImpl extends AbstractActionSecurityDecoratorImpl { public function __call ($methodName, $args ) { $request = $args[0];

$mv = $args[1];

return $this->executeTargetActionWithSecurityCheck( $methodName, $request, $mv); }}

class HomepageActionSecurityDecoratorCallImpl extends AbstractActionSecurityDecoratorImpl { public function __call ($methodName, $args ) { $request = $args[0];

$mv = $args[1];

return $this->executeTargetActionWithSecurityCheck( $methodName, $request, $mv); }}

?>

Listing 8. Działanie PHPProxy do dynamicznego generowania kodu

<?php

// biblioteka do dynamicznego generowania klas

require_once(dirname(__FILE__).'/phpproxy/src/proxygenerator.inc.php');// wprowadzamy interfejs na fragment kodu sprawdzający uprawnienia, aby mieć

// możliwość stosowania różnych sposobów i bibliotek

interface SecurityChecker {

function hasRightsFor(User $user, $actionToCheckRightsFor); }class SecurityCheckerIsNotNull implements SecurityChecker { function hasRightsFor(User $user, $actionToCheckRightsFor){ if ($user != null) { //tu tylko proste sprawdzanie, ale chcemy pokazać,

//że kod odpowiedzialny za sprawdzanie uprawnień

//jest wydzielony i może być użyty w dekoratorach

return true; } else { return false; } } }// Część wspólna dla wszystkich dekoratorów. Nie ma tu duplikacji kodu jak na

// Listingu 7.Cała logika związana ze sprawdzeniem uprawnień i wyowołaniem

// (lub nie) dekoratora zamknięta jest w tej jednej klasie

class SecurityCheckerMethodInvImpl extends DelegatingInvocationHandler { private $_securityChecker;

private $_decoratedAction;

public function __construct(SecurityChecker $securityChecker) { $this->_securityChecker = $securityChecker; }

public function invoke($method, $args) { $request = $args[0];

$mv = $args[1];

$user = getLoggedInUser();

if ($this->_securityChecker($user, $method)){ return parent::invoke($method, $args); } else { $mv->setView('loginform');

return $mv; } } public function getLoggedInUser(){ session_start();

return $_SESSION['user']; } }$proxyGenerator = new ProxyClassGenerator();$secureMethodInvocation = new SecurityCheckerMethodInvImpl( new SecurityCheckerIsNotNull());// dynamiczne wygenerowanie dowolnego dekoratora sprowadza się do jednej linii

// kodu!

$useractionDecorated = $proxyGenerator->getProxy(

'useraction', $secureMethodInvocation);

$homepageDecorated = $proxyGenerator->getProxy(

'homepage', $secureMethodInvocation);

?>

Listing 9. Korzystającz implementacji interfejsu ProxyInvocationHandlerz opisywanej biblioteki możemy łatwo zdecydować, czy chcemy tylko udekorować oryginalną funkcjonalność, czy też stworzyć zupełnie nowy kod

<?php

interface ProxyInvocationHandler {

public function invoke($method, $args);

}

class DelegatingInvocationHandler implements

ProxyInvocationHandler {

private $_delegate = null;

public function __construct( $delegate){

$this->_delegate =

$delegate;

}

public function invoke( $method, $args){

$reflectionClass =

new ReflectionClass( get_class($this->

getDelegate()));

$reflectionMethod =

$reflectionClass->

getMethod($method);

return $reflectionMethod-> invokeArgs(

$this->getDelegate(),$args);

}

public function getDelegate(){ return $this->_delegate; }

}

?>

w łańcuch powiązań. W przypadku kon-tenera IoC sprawa jest o wiele prostsza, ponieważ w Pico zawarta jest cała konfi-guracja związana z połączeniami pomię-dzy obiektami. Jedyne, co musimy zrobić, to przekonfigurować te połączenia w taki sposób, by wprowadzić dodatkowy obiekt pośredniczący (Rysunek 3). W Pico robi-my to przy pomocy parametrów kompo-nentu, tak jak pokazaliśmy w drugiej części Listingu 10. Oczywiście pokazany sposób można wykorzystać do użycia więcej niż jednego dekoratora (Listing 11).

Kontenery IoC w znaczący sposób ułatwiają stosowanie dekoratorów w zło-żonych aplikacjach. Dzieki nim możemy w jednym, centralnym miejscu, skonfi-gurować połączenia między obiektamii tym samym dodać nowy element do ta-kiego połączenia. Sposób jest dość pro-sty i nie wymaga zmian w kodzie PHP – jedynie w konfiguracji kontenera IoC.

Page 70: PHPSolutions_04_2006_PL

Wzorce projektowe: dekorator

www.phpsolmag.org70

Techniki

PHP Solutions Nr 4/2006

nam pewna refleksja. Otóż w opisywa-nym przypadku stosujemy dekoratory do globalnych zmian w całej aplikacji. Pa-trzymy na działające skrypty i myślimy: teraz chcielibyśmy dodać logowanie do wszystkich obiektów DAO, żeby zobaczyć, gdzie jest wąskie gardło wydajnościowe,albo: teraz udekorujmy wszystkie kontro-lery, żeby sprawdzać uprawnienia. Nie-stety, wypracowana do tej pory metodaskazuje nas na żmudne deklarowanie po-jedynczych dekoratorów. Gdyby tylko ist-niała konstrukcja programistyczna, pozwa-lająca łatwo wyrazić żądania typu: obiekty określonego typu udekoruj danym kodem, moglibyśmy dosłownie w kilku linijkachkodu wprowadzić do aplikacji logowa-nie czy sprawdzanie uprawnień. Zupełnieniezależnie od ilości obiektów do ude-korowania. Na pocieszenie chcemy po-wiedzieć, że takie metody już powstają(przykładowy Listing 11)! Jest to idea bar-dzo zbliżona do programowania aspek-towego (ang. Aspect Oriented Program-ming), ale to już temat na zupełnie inny ar-tykuł.

PodsumowanieW artykule pokazaliśmy, że dekorator jest prostym i niezwykle pożytecznym wzor-cem projektowym. Jest tak użyteczny, że w pewnym momencie możemy mieć ochotę zastosować go na bardzo szero-ką skalę. Aby jednak zrobić to efektywnie, musimy zadbać o rozwiązanie dwóch pro-blemów: automatycznego generowania dekoratorów oraz ich konfiguracji w całej aplikacji. Z pierwszym problem doskonale poradzimy sobie dynamicznie generując powtarzalny kod dekoratora. Zastosowa-nie kontenera IoC wpłynie dodatnio na ar-chitekturę naszej aplikacji i umożliwi łatwe konfigurowanie dekoratorów. Jeśli zrozu-miemy i opanujemy przedstawione powy-żej triki, świat programowania aspektowe-go nie będzie miał przed nami tajemnic. n

Listing 10. Przykład wykorzystania Pico dla PHP – lekkiego kontenera IoC – do dodania dekoratorów do obiektów aplikacji

<?php

$parentPico = new DefaultPicoContainer();$parentPico->regComponentImpl('FrontController', 'FrontControllerImpl');

$parentPico->regComponentImpl(

'ActionResolvingStrategy', //componentKey

'PicoActionResolvingStrategy', //componentClass

array ('paramName' => 'action') );

$parentPico->regComponentImplWithIncFileName(

dirname(__FILE__).'/lib/SmartyViewResolver.php', 'ViewResolvingStrategy',

'SmartyViewResolvingStrategy',

array ( array('compile_dir' => dirname(__FILE__).'/work/templates_c'), 'file:'.dirname(__FILE__).'/templates/smarty/', '.tpl')

);

$pico = new DefaultPicoContainer(null, $parentPico);$pico->regComponentInstance($pico, 'PicoContainer');

$pico->regComponentImplWithIncFileName(dirname(__FILE__). '/../shared/model/model.inc.php','UserService','UserServiceImpl');

$pico->regComponentImplWithIncFileName(dirname(__FILE__). '/../shared/model/model.inc.php','UserDAO','UserDAOPDOImpl');

$pico->regComponentImpl('PDO','PDOPicoAdapter',array ( 'pgsql:dbname=ditalkdb;host=localhost', 'postgres', 'postgres'));

//actions

$pico->regComponentImplWithIncFileName(dirname(__FILE__). '/actions/loginaction_action.php','loginaction','loginaction');

$pico->regComponentImplWithIncFileName(dirname(__FILE__). '/actions/useraction_action.php','useractionTarget', 'useraction');

$pico->regComponentImpl('useraction', 'UserActionSecurityDecoratorImpl', array ('decoratedAction' => new BasicComponentParameter('useractionTarget')));$pico->regComponentImplWithIncFileName(dirname(__FILE__). '/actions/homepage_action.php','homepageTarget','homepage');

$pico->regComponentImpl('homepage', 'HomepageActionSecurityDecoratorImpl', array ('decoratedAction' => new BasicComponentParameter('homepageTarget')));$fc = $pico->getComponentInstance('FrontController');

$fc->doService(new HttpRequest());

?>

Listing 11. Przykład rozwiązania, które nie skazuje nas na żmudne deklarowanie pojedynczych dekoratorów

<?php

//rejestracja poprzednich komponentów

//jak na Listingu 10

$pico->regComponentImplWithIncFileName(dirname(__FILE__). '/actions/useraction_action.php','useraction', 'useraction');

$pico->regComponentImplWithIncFileName(dirname(__FILE__). '/actions/homepage_action.php','homepage','homepage');

$pico->regComponentImplWithIncFileName(dirname(__FILE__). '/actions/loginaction_action.php','loginaction','loginaction');

//security decorators

$pico->regComponentImpl('SecurityMethodInterceptor');

$pico->registerAspect(new Aspect(new RegExpNameMatchingPointcut( '/^[a-km-z]+action$/'),new MatchingAllPointcut(), 'SecurityMethodInterceptor'));

?>

Rozwiązanie jest prawie idealne. Pra-wie, bo w dalszym ciągu dla każde-go dekoratora trzeba dodać nowy wpisw konfiguracji i zmodyfikować połą-czenia między obiektami. Dla twórczo

– lewniwego programisty to zdecydowa-nie zbyt duży wysiłek.

Jeśli pomyślimy całościowo o sty-lu programowania, w którym napisany zo-stał Listing 10, to powinna nasunąć się

Paweł Kozłowski jest pracownikiem SU-PERMEDIA, gdzie od roku 2000 projek-tuje i tworzy złożone aplikacje WWW w PHP. Obecnie zajmuje się rozwijaniem frameworków i bibliotek ORM opartych na PHP5. Jest autorem portu PicoCon-tainer dla PHP5 i wielu publikacji poświę-conych PHP.Kontakt: [email protected]

O autorze:

Page 71: PHPSolutions_04_2006_PL
Page 72: PHPSolutions_04_2006_PL

www.phpsolmag.org72

Techniki

PHP Solutions Nr 4/2006

Metoda aktywnego rekordu

www.phpsolmag.org 73

Techniki

PHP Solutions Nr 4/2006

W przeważającej części jest to mechaniczna praca, którą po-staramy się w tym artykule

przenieść na PHP. Stwórzmy sobie listę artykułów. Po przygotowaniu odpowiedniej tabeli w bazie (jak na Listingu 1), zajmijmy się obiektem, który będzie odpowiadał jed-nemu rekordowi w tabeli (Listing 2).

W naszym przykładzie wykorzysta-my wzorzec architektoniczny (dla źró-dła danych) o nazwie Aktywny Rekord (ang. Active Record). Zakłada on, że obiekt odpowiada jednemu wierszowi w bazie.

Do klasy Article przedstawionej naListingu 2 dopiszemy teraz metody od-powiedzialne za jej update w bazie (nie będziemy używać warstwy pośredniczą-cej w dostępie do bazy, aby nie kompli-kować przykładów, pomimo tego w pro-jektach pisanych na codzień silnie prefe-ruję użycie np. PDO). Przedstawione na Listingu 3 funkcje wstawiamy w ciało kla-sy Article.

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

Przeanalizujmy te trzy proste funk-cje. Konstruktor zawiera pętle, przez po-daną (opcjonalnie) w argumencie tablicę. Pozwala nam to sprytnie i bezproblemo-wo wpisać wartości do obiektu używając new Article($data). Musimy oczywiście uprzednio uzupełnić $data danymi z ba-zy lub formularza – w zależności od po-trzeb. Metoda Update() w zależności od tego, czy wartość id jest uzupełniona, przygotowuje i wykonuje zapytanie wsta-wiające lub modyfikujące dane. Przy-

Metoda aktywnego rekorduJakub Sacha

W SIECI

• http://wiki.rubyonrails.com/rails/pages/ActiveRecord – Active record – strona do-mowa projektu

• http://en.wikipedia.org/wiki/Active_record - Active record w Wikipedii:

Stopień trudności: lll

Co należy wiedzieć...Powinieneś znać podstawy programo-wania obiektowego w PHP5. Przydatna będzie również lektura artykułu wprowa-dzającego w tematykę wzorców projek-towych, który ukazał się w PHP Solu-tions 2/2006.

Co obiecujemy...Poznasz nowy ważny wzorzec projekto-wy związany z projektem Ruby On Rails i jego praktyczne wykorzystanie.

Page 73: PHPSolutions_04_2006_PL

www.phpsolmag.org72

Techniki

PHP Solutions Nr 4/2006

Metoda aktywnego rekordu

www.phpsolmag.org 73

Techniki

PHP Solutions Nr 4/2006

gotowaliśmy prostą klasę, która będziepośredniczyć w naszym kontakcie z ba-zą danych, pozwoli nam zapomnieć o pi-saniu zapytań usuwających, dodającychi edytujących dane. Chcemy teraz wyge-nerować listę rekordów w bazie. Mogli-byśmy zrobić to standardowo wykonu-jąc zapytanie, a następnie wpisując do obiektów wartości używając przygotowa-nej wcześniej sztuczki i podając kolejne wiersze pobrane z bazy do konstruktora. Przykładowy kod zaprezentowany został na Listingu 4.

Dobrym nawykiem jest oddziele-nie zapytań w wygodny dla nas spo-sób. Przygotujmy więc obiekt, nazwijmy go Finder – albo lepiej – ArticleFinder.W nim zawrzemy ciało potrzebnychw aplikacji metod, które będą zgodniez nazwą odpowiedzialne za odnajdywa-nie artykułów lub ich kolekcji. Dla do-

Listing 1. Struktura tabeli artykułów

CREATE TABLE `articles`

(

`id` int(11) NOT NULL default '0',

`title` varchar(255)

NOT NULL default '',

`name` varchar(30)

NOT NULL default '',

`surname` varchar(30)

NOT NULL default '',

`date` datetime

NOT NULL default '0000-00-00 00:00:00',

`body` text NOT NULL, PRIMARY KEY (`id`)

) TYPE=MyISAM;

Listing 2. Przykładowy obiekt odpowiadający rekordom w bazie

<?php

class Article{

public $id;

public $categories;

public $title;

public $name;

public $surname;

public $date;

public $body;

}

?>

Listing 3. Metody odpowiedzialne za modyfikację bazy danych

function __construct($data = null){

foreach($data as $label=>$val){ $this->$label = $val;

}}

function Update(){

// Jeśli id jest oznaczone, to generujemy update

if(!empty($this->id)){ $SQL = "UPDATE articles SET

categories = '".mysql_escape_string($this->categories)."',

title = '".mysql_escape_string($this->title)."',

name = '".mysql_escape_string($this->name)."',

surname = '".mysql_escape_string($this->surname)."',

date = '".mysql_escape_string($this->date)."',

body = '".mysql_escape_string($this->body)."'

WHERE

id = '".mysql_escape_string($this->id)."'";

}

// W przeciwnym wypadku generujemy

// zapydanie wstawiające nowy wiersz do bazy

else{ $SQL = "INSERT INTO articles

(id, categories, title, name, surname, date, body) VALUES

(default,

'".mysql_escape_string($this->categories)."',

'".mysql_escape_string($this->title)."',

'".mysql_escape_string($this->name)."',

'".mysql_escape_string($this->surname)."',

'".mysql_escape_string($this->date)."',

'".mysql_escape_string($this->body)."')";

}

// Wykonujemy zapytanie

return mysql_query($SQL);}

/** Funkcja kasuje wpis o obiekcie w bazie

* @return bool

*/

function Delete(){

if(!empty($this->id)){ /**

* zapytanie usuwające

*/

$SQL = "DELETE FROM articles WHERE

id = '".mysql_escape_string($this->id)."' LIMIT 1";

mysql_query($SQL); return (bool)mysql_affected_rows(); }

return false;}

bra i przejrzystości przykładu będziemy operować na zwykłych tablicach. W ce-lu wygenerowania listy stworzymy funk-cję GetList i jako parametry podajmy jej limit oraz offset (przesunięcie). Pozwo-li nam to na łatwe operowanie listą i po-dzielenie jej na porcje. Na Listingu 5 wi-

dzimy opisywaną metodę oraz dodat-kowo metodę FindById – założyłem, że metody zaczynające się od Get zwracają tablicę, a Find konkretny obiekt.

Naszemu Finderowi brakuje jesz-cze wiele do ideału. Nie mamy wpły-wu na sortowanie wyników, ale nie to

Page 74: PHPSolutions_04_2006_PL

Active Record

www.phpsolmag.org74

Techniki

PHP Solutions Nr 4/2006

jest naszym największym problemem. Zauważmy, że w zapytaniu występu-je ciąg SELECT * – pobieramy wartości z wszystkich pól w bazie. Załóżmy teraz, że nasze artykuły posiadają dużo treści.Generując zwykłą listę do wyświetle-nia na stronie potrzebujemy co najmniej nazwy oraz id, a ewentualnie dodatko-wo danych na temat autora lub daty do-dania artykułu. Treść jest pożądana do-piero w momencie, gdy nasz Czytelnikkliknie w link więcej i zdecyduje się na lekturę naszych wypocin. Nie mamy po-trzeby pobierania zawartości pola body, bo nie jest ono używane. Moglibyśmy zwyczajnie nie uzupełniać go, modyfi-kując zapytanie w następujący sposób: SELECT id, categories, title, name,

surname ... Zauważmy jednak, co sta-ło by się, gdybyśmy przypadkowo wyko-nali metodę Update() na nie uzupełnio-nym do końca obiekcie. Wszystkie dane, które są w bazie, ale nie zostały wpisa-ne do obiektu zostały by nadpisane pu-stymi wartościami. Na szczeście z po-mocą przychodzą funkcje __set() oraz __get(), które otrzymaliśmy razem z 5 odsłoną PHP. Usuńmy więc pole body ze zmiennych klasy Article (pozostają tyl-ko zdefiniowane wcześniej id, title,

name, surname, date – wszystkie jako public). Następnie zmodyfikujmy zapy-tanie w funkcji GetList() wpisując zdefi-niowane w klasie pola, a pomijając body. Wykonajmy teraz kod odpowiedzialny za pobieranie listy artykułów. Otrzymuje-my tablicę artykułów, w każdym uzupeł-nione wszystkie pola oprócz body. O to nam chodziło, brakuje jeszcze tylko ob-sługi brakującego pola. Do jej wykona-nia użyjemy wyżej wspomnianych funk-cji magicznych __get() oraz __set(), sposób implementacji przedstawiono naListingu 6.

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

$SQL = "SELECT * FROM articles ORDER BY name ASC";

$RES = mysql_query($SQL);/**

* przygotowujemy tablicę, do której będziemy wpisywać

* obiekty utworzone z nowo pobranych wierszy

*/

$aArticles = array(); while($AFR = mysql_fetch_assoc($RES)){ $aArticles[] = new Article($AFR);}

/**

* Aby sprawdzić, czy wynik naszej pracy

* jest poprawny, proponuję

* użyć następującego kodu:

* echo '<pre>'.var_dump($aArticles, 1).'</pre>';

*/

Listing 5. Implementacja klasy ArticleFinder odpowiedzialnej za pobieranie danych i zwracanie obiektów typu Article

class ArticleFinder{ // Zwraca tablicę obiektów typu Article, posortowanych wg nazwy

// @param integer $iLimit

// @param integer $iOffset

// @return array

function GetList($iLimit = false, $iOffset = false){ $SQL = "SELECT * FROM articles ORDER BY name ASC";

// Doklejamy do zapytania limit i przesunięcie - jeśli podano

if($iLimit!=false) $SQL .= "LIMIT ";

if($iOffset != false ){ $SQL .= $iOffset.', ';

}

$SQL .= $iLimit;

$RES = mysql_query($SQL); // przygotowujemy tablicę, do której będziemy wpisywać

// obiekty utworzone z nowo pobranych wierszy

$aArticles = array(); while($AFR = mysql_fetch_assoc($RES)){ $aArticles = new Article($AFR); }

return $aArticles; }

// Zwraca obiekt typu Article wypełniony danymi z bazy, o id podanym

// w parametrze lub false w przypadku ,gdy nie znajdzie rekordu.

//

// @param integer $iId

// @return Article

function FindById($iId){ $SQL = "SELECT * FROM articles WHERE id = ".mysql_escape_

string((int)$iId)." LIMIT 1"; $RES = mysql_query($SQL); $AFR = mysql_fetch_assoc($RES); if(!empty($AFR)){ return new Article($AFR); }

return false; }

}

// Aby sprawdzić, czy wynik naszej pracy jest poprawny, proponuję

// użyć następującego kodu (mozna zastosować opcjonalne argumenty

// funckji GetList czyli limit oraz offset):

// $aArticles = ArticleFinder::GetList();

// echo '<pre>'.var_dump($aArticles, 1).'</pre>';

W PHP4W starszej wersji PHP4 brakuje funk-cji magicznych (__set() oraz __get()) więc automatyczny lazy init nie jest do wykonania. Cała reszta klasy ActiveRecord jest podobna. Przy PHP w wersji do 4.2 funkcja get_class_vars() i get_object_vars() zachowuje się in-aczej – nie zwraca niezadeklarowanych zmiennych, dlatego trzeba im nadawać wartości przy tworzeniu klasy (np. var $id = '';). Oczywiscie z przedrostków private, protected, public, abstract itd. musimy zrezygnować. To samo tyczy się wyjątków w konstruktorze – możemy je spokojnie zastąpić funkcją die().

Page 75: PHPSolutions_04_2006_PL

zamów prenumeratę PHP Solutions a otrzymasz prezent!

szczegółowe informacje: www.phpsolmag.org/prenumerata lub [email protected]

w prezencie otrzymasz Archiwum PHP Solutions 2005 w PDF

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

* cena prenumeraty rocznej w promocjioferta ważna do wyczerpania zapasów;

niższa cena: 135zł

jedna z czterech książek z Wydawnictw Naukowo-Technicznychjedna z czterech książek z Wydawnictw Naukowo-Technicznych

Page 76: PHPSolutions_04_2006_PL

Active Record

www.phpsolmag.org76

Techniki

PHP Solutions Nr 4/2006

Gdybyśmy w momencie, gdy pole body nie zostało zainicjalizowane, za-żądali jego wartości, zostanie wykonana metoda __get. W niej pobieramy, wpisu-jemy do obiektu oraz zwracamy brakują-cą wartość. Gdybyśmy natomiast spró-bowali zmienić wartość pola body kiedy nie jest zainicjalizowane, klasa zajmie się pobraniem automatycznie starej wartości i to na niej wykonywane będą zmiany. Przetestujmy pobraną uprzednio listę np. używając strtoupper() na niezainicjali-

zowanym polu body. Wszystko działa jak powinno, dane są uzupełniane, a rezultat znakomity. Przemyślmy teraz następują-cy przypadek. Pobieramy listę artykułów pomijając pole body, a następnie w liście wyświetlamy dodatkowo skrót treści w postaci np. echo substr($aArticle[$i]->body, 0, 100). Okazuje się, że wyko-nujemy zapytanie pobierające listę. Za-łóżmy 20 pozycji, a następnie wyświe-tlając je w pętli doczytujemy 20 razy po-le body – razem 21 zapytań. Nie mieli-

śmy na celu tego osiągnąć – należy więc uważać, by nie wpaść w taką pułapkę, bo nasze cele zaoszczędzenia pracy ba-zy danych w bardzo prosty sposób mogą zadziałać zupełnie odwrotnie. Najłatwiej poradzić sobie z tego typu problema-mi monitorując ilość oraz treść zapytańw trakcie przygotowywania aplikacji(warto używać lub przygotować warstwę kontaktu z bazą danych, obsługującą monitorowanie zapytań).

Na początku mówiliśmy o mecha-nicznej pracy, którą wykonujemy pisząc zapytania, a w poprzednich przykładach metoda Update() i tak zawierała kodwpisany przez nas. Zmieńmy lekko przy-kład – klasa dalej będzie opisywać te sa-me dane w bazie, będzie jedynie ina-czej skonstruowana od strony technicz-nej tak, aby pozwolić programiście za-pomnieć, jak pisze się zapytania wsta-wiające, edytujące oraz usuwające wier-sze z bazy. Strukturę naszej tabeli mogli-byśmy opisać w następujący sposób. Ta-bela znazywa się articles, zawiera polao nazwach id, name, author, date oraz body przy czym pole id jest kluczem głównym, a pole body – treścią, którą nie zawsze chcemy pobierać, ponieważ nie zawsze jej potrzebujemy. Wpiszmy te da-ne do przykładowej klasy – Listing 7.

Logicznie rzecz biorąc, mamy wszystkie dane potrzebne do wygene-rowania zapytań modyfikujących da-ne w bazie. Przyjmijmy Listing 7 jako schemat implementacji klas dziedziczą-cych z ActiveRecord. Wiemy, gdzie wpi-sywać, znamy nazwy pól, do którychmamy wstawiać dane. Do tego wiemy, jaki jest klucz główny (primary key) – to on potrzebny nam będzie, aby usuwać oraz edytować poszczególne rekordy. Zajmiemy się teraz przygotowaniem abs-trakcyjnej klasy ActiveRecord, która bę-dzie odpowiedzialna za kontakt z bazą oraz czytanie danych w razie potrzeby, czyli wspomniany wczesniej lazy init. Przedstawia to Listing 8.

Kilka słów komentarza odnośnie tego Listingu. Abstrakcyjna klasa ActiveRecord przechowuje nazwy zainicjalizowanych pól @var array. Domyślny typ leniwejinicjalizacji to AR_LAZY_ALL_AT_ONCE. Je-śli podamy: AR_ALL_AT_ONCE – po żądaniu pola niezainicjalizowanego, wszystkie po-la z tabeli, które nie są zainicjalizowane, zostaną automatycznie pobrane.

Warto jeszcze zaznaczyć, że może-my chcieć przy doczytywaniu brakują-

Listing 6. Implementacja metod __set oraz __get klasy Article – odpowiedzialne za lazy init.

function __get($field){

/**

* Jeśli żądanie dotyczy pola body pobieramy jego wartość z bazy

*/

if($field=='body'){ $SQL = "SELECT body FROM articles WHERE id = '".mysql_escape_

string($this->id)."' LIMIT 1";

$RES = mysql_query($SQL); $AFR = mysql_fetch_assoc($RES); /**

* wpisujemy wartość do obiektu, aby przy kolejnych żądaniach nie

* wywoływać już zapytań

*/

$this->body = $AFR['body'];

/**

* Zwracamy nowo pobraną wartość

*/

return $this->body; }

}

/**

* nadajemy wartość nie zainicjalizowanemu polu

*/

function __set($field, $value){ $this->$field = $value;

}

Listing 7. Reimplementacja klasy Article

class Article extends ActiveRecord{ /**

* nazwa tabeli, w której przechowywane są dane

* @var string

*/

protected $_tableName = 'Articles';

/**

* klucz podstawowy; w 90% przypadków to pole 'id'.

* Po wykonaniu zapytania wstawiajacego 'insert' zostanie

* wpisana wartość mysql_insert_id() do pierwszego z podanych poniżej;

* @var array

*/

protected $_pk = array('id'); /**

* nazwy wszystkich pól w bazie danych

* @var array

*/

protected $_fields =

array( 'id', 'title', 'name', 'surname', 'date', 'body' );}

Page 77: PHPSolutions_04_2006_PL

Metoda aktywnego rekordu

www.phpsolmag.org 77

Techniki

PHP Solutions Nr 4/2006

cego pola, doczytać od razu wszystkie,które nie są jeszcze zainicjalizowa-ne – do tego posłuży nam zmien-na _lazyType. Metody Update() oraz Delete() zawarte w ActiveRecord pod-czas pobierania i odpowiedniego mani-pulowania danymi potrafią wygenerować zapytania, których w dalszej części apli-kacji używa nasza klasa. Na szczególną uwagę zasługują funkcje AfterSelect() oraz BeforeQuery() – to je nadpisujemy w klasach potomnych w celu poszerze-nia funkcjonalności.

Pierwsza z nich, zgodnie ze swoją na-zwą, wywoływana jest po każdym zapy-taniu pobierającym dane. Co nam to da-je? Możemy po każdym pobraniu któ-regoś z pól np. categories wykonać na nim operację, dla przykładu explode()

na przecinku, aby otrzymać tablicęzawierającą kategorie, do których na-leży artykuł. Rozdzieloną według prze-cinka tablicę edytujemy, np. dodając do niej kolejną kategorię (edytujemy działa-jąc na tablicach, a nie tak jak dane prze-chowywane są w bazie – czyli ciągu)i BeforeQuery() wykonuje implode(),

a do bazy zapisuje się ciąg znaków, roz-dzielony przecinkami. Ułatwia to znacz-nie życie w przypadku gdy mamy potrze-bę modyfikacji danych podczas kontaktu ze źródłem danych, a chcemy zapomnieć o ograniczeniach, które niesie użycie ba-zy danych (płaskie przechowywanie).Podany przykład dość prosto zilustrował problem – w bardziej skomplikowanych przypadkach można użyć serialize – je-śli nie chcemy wprowadzać dodatkowych relacji w bazie. Możemy też pokusić sięo nadpisanie w potomku metody Delete(), która miałaby przykładowo kilka zadań; najpierw ustawienie pola deleted w tabeli na status true, a dopiero po po-nownym wykonaniu oraz spełnionym wa-runku if($this->deleted==true) usuwać całkowicie rekord z bazy. Bardzo łatwo moglibyśmy wygenerować w ten sposób znany wszystkim kosz.

Dobrym przykładem jest również przechowywanie danych na temat zu-ploadowanych plików w bazie. Przy usu-waniu rekordu z tabeli, stosujemy me-todę unlink() i zapominamy o ręcznymusuwaniu kasowanych plików. Dla szczególnie leniwych, w konstruktorze naszej klasy możemy sprawdzać, czy podana wartość jest tablicą (wtedy do-konujemy wpisania wartości) czy np. liczbą i wykonywać finder::findById();

Listing 8a. Klasa abstrakcyjna ActiveRecord

<?php

define("AR_LAZY_ALL_AT_ONCE", 1);

define("AR_LAZY_PER_ONE", 2);

abstract class ActiveRecord{// Przechowuje nazwy pol ktore zostaly zainicjalizowane @var array

private $_initalized = array(); protected $_lazyType = AR_LAZY_ALL_AT_ONCE;

// w konstruktorze sprawdzamy poprawnosc klasy,

// ktora dziediczy po ActiveRecord.

function __construct($param = null){ if(empty($this->_tableName)){ throw new Exception('Nie zdefiniowano _tableName w klasie \''.get_class($this).'\'') ; }

if(empty($this->_pk)){ throw new Exception('Nie zdefiniowano _pk w klasie \''.get_class($this).'\'') ; }

if(empty($this->_fields) or !is_array($this->_fields)){ throw new Exception('Nie zdefiniowano _fields w klasie \''.get_class($this).'\'') ; }

if(count($param)>0){ $this->SetData($param); } }

//wykonywana przy nie zainicjalizowanych zmiennych

//@param string $variable oraz @param mixed $value

function __set($variable, $value){ if(!in_array($variable, $this->_initalized) and in_array($variable, $this->_fields)){ $this->_initalized[] = $variable; }

$this->$variable = $value; }

function __get($name){ // jesli ustawiono primary keys, robimy lazy inicjalizację

if($this->AllPrimeryKeyIsset() and in _array($name, $this->_fields)){

// Pobiera wszystkie niezainicjalizowane, czy jeden

if($this->_ lazyType == AR_LAZY_ALL_AT_ONCE){

$sFields = implode (', ', $this->GetUninitalizedVars()); }

else{ $sFields = $name; }

// musimy wiedziec, z ktorego rekordu dane pobrac

foreach($this->GetPrimaryKeys() as $sKey=>$sVal){ $aKeys[] = $sKey.' = '.$this->BeforeQuery($sKey, $sVal); }

$SQL = 'SELECT '.$sFields.' FROM '.$this->_

tableName.' WHERE '.implode(', ', $aKeys); $RES = mysql_query($SQL); foreach(mysql_fetch_assoc($RES) as $sKey=>$sVal){ $this->SetData(array($sKey => $sVal)); } return $this->$name; } else{ return null; }}

// Zwraca tablicę, klucze to nazwy primary keys, wartosci to ich wartosci

function GetPrimaryKeys(){ $mRet = array(); // przygotowyjemy array do zwrocenia foreach ($this->_pk as $sVal){ $mRet[$sVal] = $this->$sVal; }

return $mRet; }//sprawdza, czy primary klucze są ustawione // @return bool

function AllPrimaryKeyIsset(){ foreach ($this->_pk as $sVal){ if( !isset($this->$sVal) ){ return false; } } return true; }

// zwraca tablicę, jako klucze nazwy pól inicjalizowanych później,

// wartości to przypisane do danych pól wartosci wiersza w bazie

// @return string

function GetInitalizedVars(){ return $this->_initalized; }

Page 78: PHPSolutions_04_2006_PL

Active Record

www.phpsolmag.org78

Techniki

PHP Solutions Nr 4/2006

Listing 8b. Przykładowa implementacja klasy abstrakcyjnej ActiveRecord

// Wpisuje wartości tablicy do obiektu.

// @param array $arr oraz @return false null

function SetData($aArr){ foreach (@(array)$aArr as $sId=>$sVal){ $this->__set($sId, $this->AfterSelect($sId, $sVal)); } }

// Zwraca wszystkie nie zainicjalizowane pola

// @return array

function GetUninitalizedVars(){ $aRet = array(); foreach($this->_fields as $val){ if(!in_array($val, $this->_initalized)){ $aRet[] = $val; } } return $aRet; } // Kasuje wiersz z bazy @return bool

function Delete(){ /* jesli obiekt ma ustawione klucze glowne, czyli nie jest nowy, to

* kasujemy go z bazy */

if($this->AllPrimeryKeyIsset()){ foreach($this->GetPrimaryKeys() as $sKey=>$sVal){ $aKeys[] = $sKey.' = '.$this->BeforeQuery($sKey, $sVal); }

$SQL = 'DELETE FROM '.$this->_tableName.'

WHERE '.implode(', ', $aKeys); mysql_query($SQL); return true; } return false; } function Update($forceInsert = false){ // jesli obiekt nie ma ustawionych kluczy glownych, nie jest nowy, to

// wykonujemy insert w bazie

if(!$this->AllPrimeryKeyIsset() or $forceInsert){ $aVals = array(); foreach ($this->GetInitalizedVars() as $sID=>$sVal){ $aVals[$sVal] = $this->BeforeQuery($sID, $this->$sVal); }

// jesli jest co wstawiac

if(count($aVals)){ $SQL = 'INSERT INTO '.$this->_tableName.' ('.

implode(', ', array_keys($aVals)).') VALUES('. ''.implode(', ', $aVals).')'; } else{ return false; } } // jesli obiekt ma ustawione klucze glowne, czyli nie jest nowy to

// wykonujemy update w bazie

else{ // przygotowujemy klucze do zapytania

foreach($this->GetPrimaryKeys() as $sKey=>$sVal){ $aKeys[] = $sKey.' = '.$this->BeforeQuery($sKey, $sVal); }

// przygotowujemy wartosci do zapytania,

// update tylko tych zainicjalizowanych

foreach($this->GetInitalizedVars() as $sVal){ $aVals[] = $sVal.' = '.$this->BeforeQuery($sVal, $this->$sVal);

} // jeśli jest co updatować

if(count($aVals)>1){ $SQL = 'UPDATE '.$this->_tableName.' SET

'.implode(', ', $aVals).' WHERE '.implode(', ', $aKeys); } else{ return false; } } mysql_query($SQL); if(!$this->AllPrimeryKeyIsset() or $forceInsert){ $AI = end(array_reverse($this->_pk)); $this->$AI = mysql_insert_id(); } return true; } function GetFields(){ return $this->_fields; } function BeforeQuery($name, $value = null){ if(strtoupper($value) == 'NOW()') { return 'NOW()'; } if(strtoupper($value) == 'DEFAULT') { return 'default'; } return '\''.mysql_escape_string($value).'\''; } function AfterSelect($name, $value = null){ return $value; }}?>

a ostatecznie przypisać wartości. Po-branie wtedy rekordu o id równym 7będzie wyglądało następująco: $article = new article(7). MySQL 5 niesie ze sobą support przechowywania XML-a wewnątrz pól. Przy pobieraniu moż-na przeparsować dane, a przy insercie wstawić XML-a wygenerowanego przez BeforeQuery(). Pomysłów i przykładów przytoczyć możnaby wiele, ogranicza nas jedynie własna pomysłowość.

WalidacjaNic nie stoi także na przeszkodzie przed dodaniem metody – Validate(), która określałaby, czy dane w obiekcie nada-ją się do wpisania do bazy. W połączeniu z metodoą SetData() mogą tworzyć na-prawdę sprawne narzędzie – Listing 9.

W tym momencie warto zwrócićuwagę na dość istotną kwestię. Nig-dzie w omawianym przykładzie nie uży-to nazw pól w bazie. Oznacza to, że do-danie w formularzu dodatkowego pola oraz uzupełnieniu o nie definicji obiek-tu i struktury bazy danych, to wszyst-kie czynności wymagane do poszerze-nia zakresu działania naszego obiektu– a przecież prawdopodobnie każdyz nas zna poszukiwania wszystkich za-pytań, w celu uzupełnienia o brakujące pole – tutaj problem znika.

Nic nie stoi również na przeszko-dzie, aby po wykonaniu select zamie-niać przykładowe pole autor zawierają-ce id autora na obiekt user zawierający dane, które zapisane są w innej tabeli.

W metodzie BeforeQuery natomiast możemy wykonać update na obiekcie user i zamienić ten obiekt z powrotem na id, aby zachować zgodność zawartości.

Listing 9. Przykładowe zastosowanie SetData oraz Validate

if(isset($_POST)){

// Tworzymy nowy obiekt

$oArticle = new Article();

// wpisujemy wartości

// z formularza

$oArticle->SetData($_POST);

// sprawdzamy czy są okey

if($oArticle->Validate()){ // jesli tak, wpisujemy

// do bazy

$oArticle->Update();

}

}

Page 79: PHPSolutions_04_2006_PL

Metoda aktywnego rekordu

www.phpsolmag.org 79

Techniki

PHP Solutions Nr 4/2006

Listing 10. Przykładowe zastosowanie SetData oraz Validate

<?php

abstract class Finder{ // pola do pobrania @var array

protected $defaultfields = array('*'); protected $fields = null; protected $limit = 0;

protected $offset = 0; protected $order = 0;

protected $where = null;

public function SetWhere($sWhere){ $this->where = $sWhere; }

protected function GetWhere(){ $aRet = $this->where;

$this->where = null;

return $aRet; } public function SetLimit($iLimit){ $this->limit = $iLimit; }

protected function GetLimit(){ $aRet = $this->limit;

$this->limit = 0;

return $aRet; } public function SetOffset($iOffset){ $this->limit = $iLimit; }

protected function GetOffset(){ $aRet = $this->offset;

$this->offset = 0;

return $aRet; } public function SetFields($aFields){ $this->fields = $aFields; }

protected function GetFields(){ if(empty($this->fields)){ return $this->defaultfields; } $aRet = $this->fields;

$this->fields = null;

return $aRet; } function __construct(){ if(empty($this->tableName)){ throw new Exception ('Nie zdefiniowano tableName w klasie \''.get_class($this).'\'') ;

} }

// zwraca tablice obiektów @return array

function Get(){ $iLimit = $this->GetLimit();

$iOffset = $this->GetOffset();

$where = $this->GetWhere();

$SQL =

'SELECT '.implode(', ',$this->GetFields()).' FROM '.$this->tableName.'

'.( !empty($where)?('WHERE '.$where.'') : ('') ).' '.($iLimit > 0?('LIMIT '.$iLimit.','.$iOffset):'');

$RES = mysql_query($SQL); $aRet = array(); while($AFR = mysql_fetch_assoc($RES)){ $aRet[] = new $this->returnType($AFR); } return $aRet; } // @return object

function GetOne(){ $iLimit = $this->GetLimit();

$iOffset = $this->GetOffset();

$where = $this->GetWhere();

$SQL =

'SELECT '.implode(', ',$this->GetFields()).' FROM '.$this->tableName.'

'.( !empty($where)?('WHERE '.$where.'') : ('') ).' LIMIT 1';

$RES = mysql_query($SQL); return new $this->returnType(mysql_fetch_assoc($RES));}?>

Pobieranie danychW przypadku pobierania danych orazuzupełniania obiektów – można iść dwie-ma drogami. Pierwsza z nich to przygo-towywanie wszystkich kombinacji funk-cji w finderach, czyli GetByCountry(), FindById(), FindByName(), FindByName-

AndSurname(). Możemy również przygo-tować interfejs, podobny do tego z Listin-gu 10.

W takiej sytuacji utworzenie nowe-go findera to zazwyczaj kwestia doda-nia extends Finder. Do dyspozycji ma-my wówczas SetWhere(), SetLimit(), SetOffset(), a dopisujemy tylko bar-dziej skomplikowane zapytania, któ-re nie ograniczają się do użycia WHE-RE oraz LIMIT. Nic nie stoi na prze-szkodzie, aby rozwinąć funkcje, dodaćobsługę tablic jako parametrów wej-ściowych oraz czegokolwiek, co może ułatwić pracę.

Na koniecAlternatywą dla Active Record jestpakiet PEAR::DB_DataObject. Wyko-rzystuje on część z opisanych w ar-tykule technik i sprawdziłby się jakoalternatywa dla naszej implementacji.Zachęcamy jednak do samodzielnego ekperymentowania, co pozwoli na do-stosowanie narzędzia do naszych po-trzeb, a nie odwrotnie. n

Listing 11. Przykład użycia klasy articleFinder

class articleFinder extends Finder {

private $tableName = 'articles';

}

$finder = new articleFinder();

$finder->SetWhere('name = '.'Nazwa');

$finder->SetLimit(1);

$finder->Get();// zwróci tablicę

$finder->SetWhere('id = '.'1');

$finder->GetOne();// zwróci obiekt

Jakub Sacha z php ma styczność od około 3-4 lat. Początkowo programowa-nie traktował jako pasję, ale aktualnie pracuje jako programista w bytomskiej firmie NylonCoffee. Przepełnia go chęć rozwoju.Kontakt z autorem: [email protected]

O autorze

Page 80: PHPSolutions_04_2006_PL

Zaprenumeruj swoje ulubione magazyny i zamów archiwalne numery!

Już teraz w kilka minut możesz zaprenumerować swoje ulubione pismo.Gwarantujemy:- preferencyjne ceny- bezpieczną płatność on-line- szybką realizację Twojego zamówienia Bezpieczna prenumerata on-line wszystkich tytułów Wydawnictwa Software!

www.buyitpress.com zamówienie prenumeraty

Page 81: PHPSolutions_04_2006_PL

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

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

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

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

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

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

zamówienie prenumeraty

1 Cena prenumeraty rocznej dla osób prywatnych 2 Cena prenumeraty rocznej dla osób prenumerujących już Software Developer’s Journal lub Linux+3 Cena prenumeraty dwuletniej Aurox Linux

Jeżeli chcesz zapłacić kartą kredytową, wejdź na stronę naszego sklepu internetowego:

www.buyitpress.com

automatyczne przedłużenie prenumeraty

Suma

Tytuł Ilość numerów

Ilość zamawianych prenumerat

Od numeru pisma lub miesiąca

Opłata w zł

z VATSoftware Developer’s Journal (1 płyta CD)– dawniej Software 2.0Miesięcznik profesjonalnych programistów

12 250/1801

SDJ Extra (od 1 do 4 płyt CD lub DVD)– dawniej Software 2.0 Extra!Numery tematyczne dla programistów

6 150/1352

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

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

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

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

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

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

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

Page 82: PHPSolutions_04_2006_PL

Ponadto planujemy:

■ Mojavi■ Winbinder■ DB2 i PHP

■ Video Streaming

■ MSSQL i PHP

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

TECHNIKI

NARZĘDZIA

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ć jew praktyce.

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

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

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

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

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

W następnym numerze

W sprzedaży od 15 sierpnia!

PHP Solutions 5/2006 (16)

Page 83: PHPSolutions_04_2006_PL
Page 84: PHPSolutions_04_2006_PL