PHP Solutions 06 2006 PL

84

description

* Testy wydajności i profilowanie aplikacji PHP* Savant – pogromca Smarty?* RSA w PHP: chronimy dane przy użyciu kryptografii asymetrycznej* XML_FastCreate* Rozwiązywanie problemów przekrojowych z użyciem IoC* Przyjazne URL-e w PHP, czyli zaprzęgamy mod_rewrite do pracy* Tajniki freelancingu* PHPUnit w praktyce* Wywiad - Michał Małecki

Transcript of PHP Solutions 06 2006 PL

Page 1: PHP Solutions 06 2006 PL
Page 2: PHP Solutions 06 2006 PL
Page 3: PHP Solutions 06 2006 PL
Page 4: PHP Solutions 06 2006 PL

Spis treści

www.phpsolmag.org4 PHP Solutions Nr 6/2006

Spis treści

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

Testy konsumenckie w PHP Solutions

Rwolucje mają to do siebie, że wprowadzają zmiany. Nieważne, czy owe zmiany są tylko czasowe czy przyjmą się czy nie. Każda zmiana jednak

wprowadza powiew świeżości.Nowy zespół pracujący aktualnie nad naszym magazynem gwarantuje

Wam powrót do źródeł. I sporo zmian.I tak, od następnego numeru pisma PHP Solutions zaczniemy publikację

cyklu testów konsumenckich. W każdym wydaniu będziemy zamieszczać re-cenzje kliku produktów z tej samej grupy. Recenzje będą przygotowywane przez użytkowników, którzy znają produkt od podszewki i korzystają z niego wystarczająco długo, żeby znać nie tylko zalety ale też wady. Recenzje będą obejmować nie tylko aspekty techniczne produktu, ale też jakość wsparcia, proces zakupu i wiele innych ważnych elementów.

Zapraszamy wszystkich chętnych do napisania recenzji. Prosimy o kon-takt z nami, przekażemy Ci wszystkie szczegóły.

Zapraszamy do współpracy!

AKTUALNOŚCI 6

Krzysztof Trynkiewicz

OPIS CD 10

DLA POCZĄTKUJĄCYCHTesty wydajności i profilowanie aplikacji PHP 12

Łukasz WitczakŁukasz omawia jak testować wydajność zarów-no całej aplikacji jak i wybranych fragmentów kodu. Uczy również jak jak znajdować wąskie gardła w systemie, przez które można zopty-malizować aplikacje i w miarę niewielkim kosz-tem podnieść wydajność całej witryny.

Savant – pogromca Smarty? 24

Tomasz GarbiakTomasz w swoim artykule opisuje jak korzystać z systemu Savant – zorientowanego obiekto-wo systemu, który wykorzystuje samo PHP ja-ko język szablonów. Przedstawia również wady i zalety tego zorientowanego obiektowo syste-mu wyszukujacego samo PHP jako język sza-blonów.

BEZPIECZEŃSTWORSA w PHP: chronimy dane przy użyciu kryptografii asymetrycznej 32

Kamil KarczmarczykKamil przedstawia działanie algorytmu asyme-trycznego RSA, który jest obecnie najpopu-larniejszym algorytmem szyfrowania asyme-trycznego,używanym powszechnie np. w han-dlu elektronicznym czy też w celu podpisywa-nia emaili. Autor wskazuje, jak przy jego użyciu stworzyć system bezpiecznego logowania.

PROJEKTYXML_FastCreate 38

Guillaume LecanuGuillame pokazuje jak tworzyć prawidłowy kod XML za pomocą XML_FastCreate, sposób doko-nywania transformacji znaczków XML-a, spraw-dzania DTD , wykrywania błędów składni i two-rzenia dokumentów w XHTML-u.

niemieckim francuskimpolskim

Nasz magazyn ukazuje się w trzech 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

Tabela 1. Kalendarium tematów testów konsumenckich 2007

TematyUsługi hostingowe

Relokacja serwerów

Sprzęt – Serwery

Łącza internetowe

Statystyki zewnętrzne

UPC

Zapraszamy również firmy, które chciałyby, aby ich produkty lub usługi zostały objęte naszymi testami.

Sylwia [email protected]

Page 5: PHP Solutions 06 2006 PL

Spis treści

www.phpsolmag.org4 PHP Solutions Nr 6/2006

Spis treści

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

DLA ZAAWANSOWANYCHRozwiązywanie problemów przekrojowych z uzyciem IoC 44

Piotr SzarwasPiotr obrazuje rozwiązania niektórych proble-mów przekrojowych, których nie można przy-pisać do żadnej z warstw za pomocą konte-nera IoC- zwyczajnie konfigurowanej fabryki obiektów, która potrafi przywołać do życia ca-łe ich drzewa.

Przyjazne URL-e w PHP, czyli zaprzęgamy mod_rewrite do pracy 50

Michał GackiMichał ilustruje zabezpieczenia dostępu do pli-ków, pokazuje jak za pomocą Mod_Rewrite za-mienić nawet największą plątaninę linków i pa-rametrów na czytelne adresy WWW . Przybliża też podstawy wyrażeń regularnych.

KASA DLA WEBMASTERATajniki freelancingu 62

Krzysztof TrynkiewiczKrzysztof kontynuuje artykuł o freelancingu. W dzisiejszym numerze skupia się na szczegółach definiowania zleceń i składania ofert zlecenio-dawcom. Pokazuje też uzyteczne praktyki sto-sowane podczas tworzenia portfolio i resume do celów freelancingowych.

TECHNIKAPHPUnit w praktyce 66

Marcin StaniszczakMarcin pokazuje jakstosować testy jednostkowe za pomocą frameworka PHPUnit2w celu odna-lezienia błędu w aplikacji składającej się z kilku-dziesięciu-kilkuset klas.

FELIETON 78

Michał Małecki

ZAPOWIEDZI 82

Zapowiedzi artykułów, które planujemy następ-nego wydania naszego pisma

Listingi wszystkich opisywanych programów zostały zamieszczone na naszej stronieinternetowej www.phpsolmag.org/pl.

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

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

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

Kontakt z redakcjąe-mail: [email protected] Wydawnictwo Sp. z o.o.Redakcja PHP Solutionsul. Bokserska 102-682 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.

Redaktor naczelny: Sylwia PogroszewskaAsystent redaktora: Patrycja Wądołowska [email protected]

Kierownik produkcji: Marta Kurpiewska [email protected] okładki: Agnieszka MarchockaSkład i łamanie: Robert Zadrożny [email protected]

Stali współpracownicy: Krzysztof Sobolewski [email protected] Krzysztof Trynkiewicz [email protected]

Dział reklamy: [email protected]: Marzena Dmowska [email protected]ład: 6 000 egz.

Adres korespondencyjny: Software-Wydawnictwo Sp. z o.o., ul. Bokserska 1, 02-682 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

Page 6: PHP Solutions 06 2006 PL

Aktualności

PHP Solutions Nr 6/2006www.phpsolmag.org6

PHP-GTK2 dostępny w wersji alfaWielkimi krokami zbliża się pierwsza stabil-na wersja PHP-GTK2. W połowie lipca autorzy udostępnili wersję zeta projektu (którą potem musieli z przyczyn technicznych przemianować na alpha). Blisko połowa z 202 klas i 2 900 me-tod została już ujęta w dokumentacji. Autorzy nie zdradzają planowanej daty wydania pierw-szej stabilnej wersji PHP-GTK2, ale już teraz, w oparciu o wersje testowe, możemy tworzyć wła-sne aplikacje graficzne i poznawać jej zapowia-daną funkcjonalność.http://gtk.php.net

Planeta PHP – wszystkie newsy w jednym miejscuPHP Planet prezentuje informacje powiązane z PHP, pochodzące z różnych źródeł dostępnych w sieci. Treści nowości są pobierane z dziesią-tek kanałów RSS najpopularniejszych blogów oraz wortali poświęconych temu językowi. Do-datkowo, na stronie projektu Planet Feed Re-ader (http://planetplanet.org) znajdziemy odno-śniki do innych witryn typu Planet: traktujących o MySQL-u, eZ publish, czy PRADO.http://planet-php.org

Symphony FrameworkSymphony to nowy framework, udostępniany na podstawie darmowej licencji MIT,komponu-je,zachowuje, dzieli i wykonuje meta programy ( SHA99, LOR02). Mimo, że wersja 1.0 jesz-cze się nie ukazała, już teraz projekt zaskakuje: jest skierowany na aplikacje klasy Enterprise, przy czym oferuje przyjazne linki, wsparcie dla technologii AJAX i caching. Całość została na-pisana w OOP PHP5 (architektura MVC). Pro-jekt rozwija się z dnia na dzień, zaś jego bardzo mocną stroną jest obszerny manual, wsparty fil-mami w formacie QuickTime.http://www.symphony-project.com

Wzorce projektowe dla PHP5Na witrynie Patterns for PHP możemy zapo-znać się z wieloma wzorcami projektowymi. Przykłady są napisane pod kątem PHP5. Auto-rzy reklamują, że nie znajdziemy tu opisów im-plementacji wzorców z języka Java, a gotowe rozwiązania, przygotowane ściśle dla develo-perów PHP. Zaprezentowane wzorce są bardzo ciekawe – warto poznać je, bowiem nie dla każ-dego projektu najodpowiedniejszy jest MVC.http://www.patternsforphp.com

phpDocumentor 1.3.0 stableZespół twórców phpDocumentora udostępnił wer-sję 1.3.0 swojego produktu. Poprzednia stabilna wersja tego przydatnego i popularnego narzędzia, które ułatwia pisanie dokumentacji kodu PHP i jest standardem w tej dziedzinie, została oddana do użytku w 2003 roku! Jak przyznają sami au-torzy, postęp w rozwoju projektu jest ogromny: obecnie phpDocumentor w pełni obsługuje PHP5. phpDocumentor należy do repozytorium PEAR.http://pear.php.net/PhpDocumentor

Open Power TemplatePo ponad roku kodowania udostępniona zosta-ła stabilna wersja 1.0.0 systemu szablonów o nazwie Open Power Template. System ten po-wstał jako podprojekt powstającego opensour-cowego forum dyskusyjnego (Open Power Bo-ard: http://openpb.net). Open Power Templa-te został napisany w PHP5 przy zastosowaniu technik obiektowych. Obsługuje caching, kom-presję gzip, posiada wbudowaną konsolę debu-gującą oraz wsparcie dla wielojęzycznych sza-blonów. Jest rozpowszechniany na zasadach określonych w licencji LGPL.http://opt.openpb.net

Achievo

Achievo to napisany w PHP system za-rządzania projektami, który łączy cechy

typowego PM-a, CRM-a, HRM-a i PIM-a. Możemy go łatwo wdrożyć do systemu ob-sługi klientów, oferując im podgląd postępu zamówionych prac w trakcie ich wykonywa-nia. Narzędzie działa na każdej platformie oferującej PHP i MySQL, jest też wieloję-zyczne i całkowicie darmowe. Sam Achievo oferuje wiele udogodnień: generowane za pomocą biblioteki GD2 statystyki czasu pra-cy, wykresy postępu rozwoju projektu, czy funkcję dodawania plików, które następnie może przejrzeć i skomentować nasz kon-trahent. Achievo oferuje także wspomaganie

podziału pracy w zespole programistów, po-zwala też na zarządzanie umowami zawar-tymi z pracownikami i innymi wykonawca-mi czy korzystanie z list zadań do wykona-nia (TODO). Do dyspozycji mamy wiele plu-ginów, w tym kalendarz, notatnik i genera-tor statystyk pracy. Na wsparcie techniczne projektu składa się także forum i dedykowa-na bugzilla. Opcjonalnie możemy poprosić o płatną konsultację z deweloperami Achievo, zaś postęp prac obejrzymy na blogu auto-rów (http://www.achievo.org/blog)

licencja: Open Source (własna)http://www.achievo.org

FeedCreator

Wraz z rozwojem aplikacji webowych nastała era czytników RSS. Sub-

skrybenci używają kanałów do czytania informacji o nowościach w ich ulubionych serwisach internetowych – standardowy sposób dostarczania newsów (na stronie głównej) ustępuje miejsca rosnącym w po-pularność rozwiązaniom. Czytniki RSS są implementowane także w komunikatorach internetowych (ang. instant messaging ap-plication).

Dodanie czytnika RSS lub tworzenie własnych kanałów wiadomości poszerzy również funkcjonalność naszej witryny internetowej. Same kanały mają postać plików XML, jednak ich generacja może przysporzyć trudności. Nie musimy jed-nak wyważać otwartych drzwi – z pomo-cą przyjdzie nam rozpowszechniana na licencji LGPL klasa FeedCreator. Skrypt oferuje generację najpopularniejszych ty-pów wątków RSS: 0.9, 1.0, 2.0, ale także

tych zgodnych z PIE 1.0, OPML 1.0, Unix mbox oraz Atom. Dodatkowo możemy wygenerować plik w formacie HTML lub JavaScript. Chociaż projekt nie jest już rozwijany przez głównego dewelopera, w Internecie znajdziemy wiele implementa-cji i rozszerzeń do tej klasy oraz jej doku-mentację i przykłady wykorzystania.

licencja: LGPLhttp://www.bitfolge.de/rsscreator-en.html

Page 7: PHP Solutions 06 2006 PL

Aktualności

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

phpBB 3.0 Olympus BetaWielkimi krokami zbliża się premiera wersji 3.0 forum dyskusyjnego phpBB, noszącej nazwę kodową Olympus. Autorzy projektu udostępnili już kilka wersji beta produktu. Spośród nowych możliwości phpBB 3.0 warto wymienić tworze-nie własnych kodów BBCode, usprawnione ke-szowanie, powiadamianie o nowych wiadomo-ściach przez Jabber lub XMPP oraz wysyłanie treści tematów emailem. Obecna wersja działa pod PHP4 i PHP5. Produkt jest dostępny na li-cencji GPL.http://phpbb.com

GnopeWraz z nadejściem PHP-GTK2, Christian We-iske rozpoczął prace nad projektem Gnope. Efektem jest pakiet działający na wielu platfor-mach, który ułatwia pisanie aplikacji PHP-GTK oraz ich dystrybucję na najpopularniejsze plat-formy systemowe. Narzędzie to, w połączeniu z Glade, stanowi absolutny niezbędnik dewelo-pera PHP-GTK. Projekt jest rozpowszechniany na licencji LGPL.http://www.gnope.org

Baza artykułów o PHPEioba to projekt, którego celem jest zgromadze-nie w jednym miejscu artykułów krążących w Sieci. W zasobach tego repozytorium znajdzie-my m.in. pozycje traktujące o PHP i bazach da-nych. Witryna dopiero nabiera wiatru w żagle, ale już teraz jej zasoby są bogate. Możliwość dodawania pozycji przez czytelników powoduje, że projekt rokuje szybki rozwój. Funkcjonalność strony obejmuje umieszczanie ocen, komenta-rzy oraz korzystanie ze sprawnej wyszukiwarki.http://www.eioba.com

Wikipedia dla każdegoMediaWiki to projekt pozwalający na założenie własnej witryny stanowiącej internetową ency-klopedię. Jest silnikiem słynnej Wikipedii (http://wikipedia.org). Autorzy MediaWiki oznajmili o rozpoczęciu nowej, stabilnej linii tego produktu – 1.7. Główną zmianą w stosunku do poprzed-nich edycji jest odejście od obsługi PHP4 i My-SQL-a 3.23.x. Lista wprowadzonych poprawek jest imponująca – programiści Mediawiki dokła-dają wszelkich starań, by ich produkt był bez-konkurencyjny. Mediawiki jest dostępne na li-cencji GNU GPL.http://www.mediawiki.org

Shoutbox w PHP i AJAX-ieYShout to znane z wielu witryn (w tym forów dyskusyjnych) rozwiązanie alternatywne dla chata: shoutbox, czyli okienko, w którym (pra-wie) na bieżąco możemy się komunikować z in-nymi użytkownikami. Rewolucją w rozwiąza-niach tego typu jest użycie technologii AJAX do odświeżania zawartości okienka – koniec z uży-ciem ramek! Można się spodziewać, że projekt ten szybko znajdzie uznanie i zostanie zaimple-mentowany w najpoplarniejszych CMS-ach i fo-rach dyskusyjnych.http://yurivish.com/yshout

CMS na prywatną witrynęFlux jest nowym projektem typu CMS, opartym na frameworku Popoon (http://www.popoon.org) i PHP5. Oferuje wsparcie dla wielu języków, posiada wbudowany edytor WYSIWYG, generator dokumentów PDF i wiele innych. Korzysta z opartego na XSLT systemu szablonów; pozwala też na instalację wielu plu-ginów, takich jak m.in. blog czy galeria. Nowo-ścią w rozwiązaniach tego typu jest użycie tech-nologii AJAX w polu wyszukiwania – wyniki wy-świetlane są w trakcie pisania, co bardzo ogra-nicza czas potrzebny na znalezienie pożąda-nej informacji. Flux jest dostępny na zasadach określonych w licencji GNU GPL.http://www.flux-cms.org

net2ftp – klient FTP dla WWW

net2ftp to bardzo popularne rozwią-zanie, stosowane głównie na witry-

nach firm hostingowych. Jego funkcjonal-ność nie ustępuje najlepszym okienkowym klientom FTP. Net2ftp umożliwia nawigację w katalogach, upload plików (także z auto-matycznym rozpakowywaniem archiwów) oraz ich pobieranie. Pozwala też na archi-wizację zbiorów do formatu ZIP, ich prze-noszenie, kopiowanie i usuwanie, zmianę nazw plików i katalogów, użycie komen-dy CHMOD (zmiana uprawnień dostępu) czy podgląd kodu pliku w edytorze (wraz z podświetlaniem składni i numerowaniem linii). Możemy też obliczać rozmiar katalo-

gów, szukać plików, używać załączonych do projektu edytorów HTML i PHP i two-rzyć nowe pliki tekstowe. Aplikacja wspiera transfer plików pomiędzy dwoma serwera-mi FTP. Projekt możemy łatwo wdrożyć do systemu CMS: na jego witrynie znajdziemy moduły dla Mambo, Drupala i XOOPS-a. Łatwy w obsłudze system szablonów za-pewnia prostą implementację do gotowe-go layoutu naszej strony. Cały system zo-stał przetłumaczony na ponad 10 języków i łatwo możemy dodawać kolejne.

licencja: GNU GPLhttp://www.net2ftp.com

Mambo wciąż żyje

Wraz z wydaniem Mambo o numerze 4.5.3, w sierpniu 2005 roku, część

deweloperów tego CMS-a odeszła, aby stworzyć Joomlę. Rozniosła się wieść, że Mambo przestało istnieć, a na jego miej-sce tworzony był nowy Multisite CMS. Nic bardziej mylnego – projekt Mambo wciąż się rozwija, a ilość jego zwolenników ro-śnie. Mambo jest bezpieczną aplikacją: bugi zostały naprawione, a dokumentacja rozwinięta. Na stronie projektu znajdzie-my m.in. tutoriale wykonane w technologii Macromedia Flash, obrazujące wdrożenie i wykorzystanie systemu. Ilość szablonów i pluginów dla Mambo jest imponująca.

Zalety Mambo: przyjazne URL-e, wątki RSS, podziały na kategorie, zarzą-dzanie użytkownikami, łatwa integracja z forami dyskusyjnymi, wielość wersji języ-kowych, profesjonalny edytor WYSIWYG i ogromna, liczona w dziesiątkach tysię-cy społeczność użytkowników, gwaran-tują pomoc i dostępność poszukiwanych rozwiązań. Dodatkowe pluginy zawiera-ją m.in. systemy ankiet, fora dyskusyjne, galerie, systemy uwierzytelniania w opar-ciu o LDAP, kalendarze, rozszerzone pro-

file użytkowników, czy systemy zarządza-nia reklamami. Mambo jest wyposażone w potężny system keszujący, który skraca czas generacji dokumentów, zachodzącej przy użyciu autorskiego systemu szablo-nów. Łącznie ilość modułów napisanych pod Mambo jest bliska 300, dodatkowo w repozytorium znajdziemy ponad 80 dar-mowych szablonów, 400 komponentów i dziesiątki artykułów na temat systemu. Jeśli mamy uprzedzenia do Mambo po rozłamie z 2005 roku, warto drugi raz się przyjrzeć temu rozwiniętemu projektowi.

Licencja: GNU GPL.http://www.mamboserver.org

Page 8: PHP Solutions 06 2006 PL

Aktualności

PHP Solutions Nr 6/2006www.phpsolmag.org8

PHP w WikibooksWikibooks to oparta na silniku MediaWiki wielo-języczna internetowa biblioteka swobodnie do-stępnych tekstów (m.in. książek). Dotyczy wie-lu dziedzin (w tym informatyki) i jest tworzo-na przez społeczność pasjonatów. W jej zaso-bach znajdziemy m.in. ciekawy podręcznik PHP oraz pozycje nt. technologii i rozwiązań takich, jak XHTML, CSS, XML Schema, CVS czy Sub-version. Serwis rozwija się bardzo dynamicznie – każdy podręcznik jest pisany przez kilku au-torów i nic nie stoi na przeszkodzie, by dopisać i swój rozdział.http://wikibooks.org

Biblioteka AJAX od AdobeSpry to AJAX-owy framework firmy Adobe. Ułatwia on tworzenie aplikacji wykorzystują-cych AJAX-a, m.in. upraszczając wstawianie elementów dynamicznych do kodu HTML i w dużej mierze zwalniając webmastera od ko-nieczności programowania. Spry oferuje też narzędzia graficzne pomocne w nawigacji i administracji na stronie WWW. Świetnie ob-razują to przykłady dostępne na witrynie pro-jektu, takie jak galeria ze skalowalnymi zdję-ciami, czy tabela, którą możemy sortować – oczywiście, bez potrzeby odświeżania stro-ny. W połączeniu z PHP i bazą danych, Spry stanowi potężne narzędzie, pomocne zwłasz-cza przy projektowaniu sklepów interneto-wych czy systemów klasy Enterprise. Frame-work jest dostępny na licencji BSD.http://labs.adobe.com/technologies/spry

advAJAX 1.1Advanced AJAX to obiekt języka JavaScript, pomocny przy tworzeniu dynamicznych witryn internetowych korzystających z AJAX-a. Uła-twia on m.in. ustawianie wartości argumen-tów zapytania do serwera, pełną kontrolę nad połączeniem klient-serwer i jego wznawianie oraz korzystanie z pamięci podręcznej (ang. cache). Możliwości advAJAX i specyfikę sa-mej technologii AJAX omówiliśmy w numerze 1/2006. Obecnie projekt rozwija się bardzo dynamicznie; rośnie też jego dokumentacja i przykłady zastosowań. Twórca advAJAX-a pozwala zarówno na jego prywatny, jak i ko-mercyjny użytek.http://advajax.anakin.us

Glade 3Po wielu miesiącach oczekiwania, Glade – narzędzie pozwalające na łatwe, intuicyj-ne tworzenie graficznych interfejsów użytkow-nika opartych na bibliotece GTK (dostępnej również dla języka PHP dzięki PHP-GTK) do-czekało się stabilnej wersji z linii 3. Znajdzie-my w niej mnóstwo nowych funkcji: obsługę ikon, przenoszenie i możliwość skalowania elementów GtkTable i GtkBox, nowe pale-ty kolorów, wspomaganie tworzenia pasków narzędzi (ang. toolbars) i menu oraz wiele in-nych. Do działania, Glade 3 wymaga GTK+ w wersji 2.8 oraz libxml2. Projekt jest rozpo-wszechniany na licencji GNU GPL.

http://glade.gnome.org

MagpieRSS

Prowadząc wortal często cytujemy in-formacje o nowościach z zaprzyjaź-

nionych źródeł. Dzięki wątkom RSS nie musimy już się męczyć z kopiowaniem informacji na naszą stronę – możemy użyć darmowej, rozpowszechnianej na li-cencji GNU GPL klasy MagpieRSS. Pro-jekt ten szybko zdobył sympatię dewelo-perów PHP, głównie dzięki przejrzyste-mu kodowi i dużej szybkości działania. Jego niewątpliwym atutem jest ilość ob-sługiwanych formatów wątków. Obecnie są to: RSS 0.9, RSS 1.0, RSS 2.0 oraz Atom.

Nowatorskim rozwiązaniem uży-tym w MagpieRSS jest zastosowanie keszowania oraz nagłówków HTTP w celu rozpoznania, czy dokument zo-stał zmodyfikowany (ang. conditio-nal GETs). Jakby tego było mało, ma-my możliwość korzystania z kompresji GZIP, uwierzytelniania HTTP oraz szy-frowania transmisji za pomocą SSL. Cały projekt jest podzielony na modu-

ły, co gwarantuje jego łatwe wdroże-nie oraz modyfikację. Na witrynie Mag-pieRSS znajdziemy wiele odnośników do aplikacji opartych o ten system, ta-kich jak: generator grafik PNG, agrega-tor wiadomości RSS, konwerter RSS do JavaScript, czy specjalne edycje dla blogów Wordpress. Dodatkowo mamy możliwość przejrzenia wielu artykułów i tutoriali na temat użycia tej klasy.

licencja: GNU GPL.http://magpierss.sourceforge.net

Framework Prototype w praktyce

Prototype to darmowy framework dla języka JavaScript, ułatwiający two-

rzenie aplikacji za pomocą technologii AJAX (a więc i PHP). Jego wszechstron-ne zastosowania możemy zobaczyć na stronie http://script.aculo.us. Ta ostatnia stanowi repozytorium przydatnych skryp-tów, używanych przy tworzeniu aplikacji klasy Enterprise, które z racji tego, że są rozbudowane i przesyłają dużo danych, generują poważne obciążenie serwera. Użycie technologii AJAX zmniejsza za-potrzebowanie na dostęp do serwera, gdyż – mówiąc w skrócie – dane są naj-pierw przenoszone do klienta (przeglą-darki internetowej), a następnie przetwa-rzane po jego stronie dopóki nie ulegną zmianie (można więc bez problemu np. sortować zawartość tabeli, ale już mody-fikacja zawartych w niej danych wymaga ich ponownego pobrania.

W Prototype znajdziemy technikę drag-and-drop, stosowaną w koszykach sklepów internetowych, automatyczne uzupełnianie pól (używane w wyszuki-warkach w celu dostarczenia wstępnych wyników bez potrzeby przeładowania strony), tabele, które możemy sortować oraz mnóstwo efektów wizualnych (na-jazdy, zanikanie, podświetlanie, itd.). Na szczególną uwagę zasługuje demo pro-

jektu o nazwie SortableFloats: użytkow-nik może zmienić kolejność elementów na liście poprzez ich przeciąganie – od-wieczny problem z klikaniem “przesuń niżej”, występujący np. przy zmianie ko-lejności wyświetlania wątków na forach dyskusyjnych powinien bezpowrotnie zniknąć.

Rozwiązania zaprezentowane na script.aculo.us są stosowane m.in. na stronach Apple, Digg, Feedburner, Base-camp, czy Mailroom, a przede wszystkim w bardzo innowacyjnym frameworku Ru-by on Rails.

http://script.aculo.ushttp://prototype.conio.nethttp://www.rubyonrails.org

Page 9: PHP Solutions 06 2006 PL
Page 10: PHP Solutions 06 2006 PL

www.phpsolmag.org10

Opis CD

PHP Solutions Nr 6/2006

2 filmy wideo: XML Development using Java oraz XML Based Web Applications

Firma KeyStone Learning przygoto-wała specjalnie dla czytelników ma-

gazynu PHP Solutions następujące fil-my: XML Development using Java oraz XML Based Web Applications. Filmy przydadzą się wszystkim deweloperom.

Pierwszy z filmów omawia zasto-sowanie Javy w technologii XML. Au-torzy wyjaśniają możliwości, które ofe-ruje Java deweloperom. Dowiesz się w jaki sposób zbudować wyszukany użyt-kowy program szybciej i po niższych kosztach.

Natomiast z filmu XML Based Web Applications znajdziesz odpowiedź na pytanie jaką rolę odgrywa XML w prze-twarzaniu danych w formacie B2B w aplikacjach internetowych. Życzymy przyjemnego oglądania i nauki. Rysunek 1. XML Development using Java

Dodatkowe materiały

Zmieściliśmy również dodatkowe apli-kacje:

• XAMPP – zestaw aplikacji umożliwiają-cy uruchomienie serwera WWW i bazy danych kilkunastoma kliknięciami. W jego skład wchodzą: Apache, MySQL, PHP 4.3.X i 5.0.X, Perl, FileZilla FTP Server, phpMyAdmin, OpenSSL, Fre-etype, Webalizer, mod_perl, Truck MM Cache, mcrypt, SQlite, JpGraph, Mer-cury Mail Transport System, PHPBlen-der, PHP Compiler;

• Wampserver – odpowiednik pakietu LAMP dla Linux i FAMP dla FreeBSD. Jest to pakiet do bsługi witryn interne-towych w środowisku MS Windows. Zawiera m.in.: serwer Apache, język skryptowy PHP, bazę danych MySQL i oprogramowanie uzupełniające;

• PHP-Qt – Qt to przenośne bibliote-ki dla języka C++. Klasy stanowią ich znaczną część i służą do budo-wy interfejsu graficznego dla pro-gramów komputerowych. Qt jest do-stępna dla wielu platform. Urządzeń wbudowanych. Qt jest podstawą dla wielofunkcyjnej przeglądarki interne-towej Opera i uniksowego środowi-ska KDE. Qt zawiera zhierarchizo-wany system zdarzeń,technologie programowania GUI i automatyczne rozmieszczanie widżetów.

• Nowe e-booki: Auditing Your Web Si-te Security, PHP Power Programming, OASIS OpenDocument Essentials.

Rysunek 3. XAMP

Rysunek 4. Wamp

Page 11: PHP Solutions 06 2006 PL

www.phpsolmag.org12 PHP Solutions Nr 6/2006

Dla początkujących

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

Profilowanie aplikacji PHP Dla początkujących

Zwiększenie wydajności jest mo-żliwe dzięki optymalizacji kodu. Problem w tym, że dzisiejsze

aplikacje pisane w PHP rozrosły się do takich rozmiarów, iż ciężko przej-rzeć cały kod i poprawić jego efektyw-ność. Znajdując wąskie gardło w syste-mie można je zoptymalizować i w mia-rę niewielkim kosztem podnieść wy-dajność całej witryny. W tym artyku-le pokażemy, jak testować wydajność zarówno całej aplikacji, jak i wybra-nych fragmentów kodu oraz jak iden-tyfikować te partie kodu, które są przy-czyną największego narzutu czaso-wego.

Testy wydajności z PEAR::BenchmarkBenchmark to pakiet z repozytorium PE-AR (http://pear.php.net) zawierający kla-sy umożliwiające testowanie wydajności i profilowanie kodu napisanego w PHP. Pakiet ten można zainstalować mając

Każda aplikacja – webowa czy dowolna inna charakteryzuje się określoną wydajnością. Cecha ta staje się szczególnie istotna w świecie aplikacji internetowych, z których korzystają czasem tysiące użytkowników...

uprawnienia administratora (środowisko Linux) poleceniem:

pear install benchmark

PEAR::Benchmark składa się z trzech klas: Benchmark _ Iterate, Benchmark _

Profiler i Benchmark _ Timer.

Benchmark_TimerBenchmark _ Timer to klasa pozwalająca nam mierzyć czas, jaki upłynął między kolejnymi tzw. markerami. Markery to miejsca pomiaru czasu, które ustawia-

Testy wydajności i profilowanie aplikacji PHPŁukasz Witczak

W SIECI

l http://pear.php.net/package/Benchmark – strona domowa pakietu PEAR::Benchmark

l http://www.linuxjournal.com/article/7213 – artykuł o profilowaniu przy pomocy APD

l http://wiki.cc/php/Apd– Wiki o APD

l http://www.omniti.com/~george/talks/Profiling-phpworks-2004.pdf– PDF o profilowaniu aplikacji w PHP

Stopień trudności: lll

Co należy wiedzieć...Powinieneś znać podstawy programo-wania proceduralnego i obiektowego w PHP5.

Co obiecujemy...Pokażemy, jak testować wydajność ca-łej aplikacji oraz jej poszczególnych fragmentów i nauczymy, jak identyfiko-wać wąskie gardła każdego programu.

Page 12: PHP Solutions 06 2006 PL

www.phpsolmag.org12 PHP Solutions Nr 6/2006

Dla początkujących

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

Profilowanie aplikacji PHP Dla początkujących

my wywołaniem metody setMarker(). Na Listingu 1 widzimy funkcję doSth(), której jedynym celem jest generowanie obciążenia.

Aby użyć klasy Benchmark _ Tim-

er, musimy ją dołączyć do bieżące-go skryptu w sposób typowy dla klas PEAR (zakładamy, że include _ path wskazuje położenie repozytorium PE-AR na dysku lokalnym). Następnie wywołujemy konstruktor klasy Bench-mark _ Timer. Jak już wiemy, zadaniem funkcji doSth() jest generowanie ob-ciążenia, co uzyskujemy poprzez wy-konywanie czasochłonnych operacji, takich jak tworzenie tablicy składają-cej się z 1000 elementów i jej sorto-wanie według kluczy w porządku od-wrotnym. Po utworzeniu tej funkji po-zostaje nam już tylko uruchomienie pomiaru czasu: $benchmark->start();. Od tego momentu będzie mierzo-ny czas, jaki upłynął między kolejny-mi markerami aż do momentu zakoń-czenia pomiarów wywołaniem $bench-mark->stop();.

Tuż przed i po wywołaniu doSth() ustawiamy nasze markery wywołu-jąc: $benchmark->setMarker( 'Marker

#1' );. Argumentem metody setMark-er() jest nazwa markera – może ona być dowolna, ale dobrze jest nadawać nazwy ułatwiające późniejszą identy-

fikację markerów w wynikach. Warto również zaznaczyć, że wywołanie me-tod start() i stop() również powoduje powstanie markerów, których nazwy to odpowiednio Start i Stop.

Metoda getProfiling() zwraca tabli-ce z wynikami. Tablica ta (dla naszego przykładu) wygląda jak na Listingu 2. Tablica ta jest ponumerowana od 0 do n-1, gdzie n to liczba markerów (pamię-tajmy, że wywołanie start() i stop() również liczy się jako marker). Każdy element tej tablicy to kolejna tablica za-wierająca cztery elementy: name – na-zwa markera, time – uniksowy znacz-nik czasu zapisany w momencie wy-wołania markera, diff – czas w se-kundach, jaki upłynął miedzy obec-nym a poprzednim markerem (w przy-padku Start jest to znak „-”, ponieważ nie było wcześniejszego pomiaru) oraz total – całkowity czas, jaki upłynął od wywołania pierwszego markera do bie-żącego.

No dobrze, ale po co nam tyle mar-kerów do pomiaru czasu wykonania jednej funkcji? Markery stosujemy, aby oznaczyć miejsca w kodzie, dla których mierzymy czas wykonania i dla jednej funkcji wystarczyłoby wywołać metody start() i stop(). My jednak chcemy zi-dentyfikować najwolniej działające par-tie kodu w większym skrypcie.

Pomiar czasu rejestracji użytkownikaZawartość Listingu 3 jest już bardziej po-dobna w działaniu do typowej aplikacji in-ternetowej. Mamy tam operacje na ba-zie danych i na ciągach. Przedstawiony skrypt odpowiada za rejestrację nowego użytkownika w naszym serwisie interne-towym. Darujemy sobie tworzenie formu-larza i przykładowe dane, które normal-nie wprowadziłby użytkownik, zapiszemy do zmiennych. Wcześniej jednak musi-my dołączyć do skryptu niezbędne klasy i funkcje: Benchmark/Timer.php posłuży do pomiaru wydajności, filter.php (Listing 4) zawiera funkcje filtrujące numer telefo-nu i komentarze, a w generators.php (Li-sting 5) deklarujemy funkcję generującą losowe hasło.

Po załadowaniu odpowiednich plików tworzymy nową instancję klasy Bench-mark _ Timer i uruchamiamy pomiar cza-su. Ustawiamy zmienne symulujące da-ne użytkownika. Następnie filtrujemy nu-mer telefonu tak, żeby zawierał same cy-fry wywołując funkcję telephone($phone) oraz oraz oczyszczamy komentarz ze znaczników HTML mogących po-służyć do ataku XSS: $comment =

comments($comment). Następnie generuje-my losowe hasło ($pass = passGen()), łą-czymy się z bazą danych i umieszczamy w niej przykładowe dane (tabela bazo-

Rysunek 1. Wynik działania klasy Benchmark_Profiler

Rysunek 2. Wynik profilowania skryptu

Page 13: PHP Solutions 06 2006 PL

Profilowanie aplikacji PHP

www.phpsolmag.org14

Dla początkujących

PHP Solutions Nr 6/2006

danowa, na której wykonujemy te opera-cje, została przedstawiona na Listingu 6). Na koniec skryptu wyświetlamy zmienne $phone i $comment zawierające przefiltro-wane dane wejściowe w celu sprawdze-nia, czy są poprawne. Oczywiście nie za-pominamy o podaniu wyników pomiarów poprzez wywołanie var _ dump( $bench-

mark->getProfiling()). Pomiędzy wspo-mnianymi operacjami wstawiamy punkty pomiaru czasu i sprawdzamy wyniki. Li-sting 7 przedstawia rezultaty, jakie uzy-skałem na mojej maszynie testowej. Naj-pierw widzimy efekt filtrowania zmiennych zawierających telefon i komentarz, a na-stępnie czasy wykonania poszczególnych części kodu.

Benchmark_ProfilerWyniki z Listingu 7 są trudne w analizie: zwłaszcza, jeżeli ustawimy wiele pułapek mierzących czas. Mamy jednak do dyspo-zycji klasę Benchmark _ Profiler, która po-wie nam, jaki jest procentowy udział po-szczególnych sekcji skryptu w czasie jego wykonania. Na Listingu 8 przedstawiamy zmodyfikowany kod z Listingu 3.

Przyjrzyjmy się bliżej zmianom w tym kodzie. Przede wszystkim ładujemy teraz plik z klasą Benchmark _ Profiler i tworzy-

Listing 2. Efekt wykonania skryptu z listingu 1

array(4) { [0]=> array(4) { ["name"]=> string(5) "Start" ["time"]=> string(19) "1153494385.64062800" ["diff"]=> string(1) "-" ["total"]=> string(1) "-" }

[1]=> array(4) { ["name"]=> string(9) "Marker #1" ["time"]=> string(19) "1153494385.64066500" ["diff"]=> string(8) "0.000037" ["total"]=> string(8) "0.000037" }

[2]=> array(4) { ["name"]=> string(9) "Marker #2" ["time"]=> string(19) "1153494385.64189600" ["diff"]=> string(8) "0.001231" ["total"]=> string(8) "0.001268" }

[3]=> array(4) { ["name"]=> string(4) "Stop" ["time"]=> string(19) "1153494385.64191900" ["diff"]=> string(8) "0.000023" ["total"]=> string(8) "0.001291" }

}

Listing 3. Typowe operacje w aplikacjach internetowych

require 'Benchmark/Timer.php';require './filter.php';require './generators.php';$benchmark = new Benchmark_Timer();$benchmark->start();

// przykładowe dane z formularza

$phone = '(33) 455-44-55';

$comment = 'Some comments <script> ... </script>, Some comments <script> ... </script>

<a href="www.somedangerouslink.com"> Safe text <a href="javascript:alert("XSS")">

ClickMe!</a> - <body onload=alert("vulnerable")> Safe text <iframe src=http://

www.notsafe.com/script.html>';

$benchmark->setMarker( 'Variables set' );

// filtrujemy telefon

$phone = telephone( $phone );

$benchmark->setMarker( 'Phone filtred' );

//filtrujemy komentarze

$comment = comments( $comment );

$benchmark->setMarker( 'Comments filtred' );

// generujemy losowe hasło

$pass = passGen();

$benchmark->setMarker( 'Password generated' );

// wykonujemy operacje na bazie danych

$conn = mysql_connect( 'localhost', 'root', '' );mysql_select_db( 'phpsolmag', $conn );$benchmark->setMarker( 'Connection established' );

$sql = 'INSERT INTO users(`pass`, `addDate`, `phone`, `comment`)

VALUES( "'. $pass .'", "'. date("Y-m-d H:i:s") .'", "'. $phone .'", "'. $comment .'" )';

mysql_query( $sql );$benchmark->setMarker( 'Inertion done' );

$benchmark->stop();

// wynik filtrowania

echo 'Phone: '. $phone . '<br />';echo 'Comments: '. $comment;// wynik pomiarów

echo '<pre>';var_dump( $benchmark->getProfiling() );

Listing 1. Prosty przykład wykorzystania klasy Benchmark_Timer

<?php

require 'Benchmark/Timer.php';$benchmark = new Benchmark_Timer();function doSth(){ $tmp = range(0, 999 );

krsort( $tmp );

return $tmp;}

// rozpoczynamy pomiar

$benchmark->start();

// ustawiamy marker #1

$benchmark->setMarker(

'Marker #1');

// wywołujemy f-cję doSth()

$result = doSth();

// ustawiamy marker #2

$benchmark->setMarker(

'Marker #2');

// kończymy pomiar

$benchmark->stop();

// wyświetlamy informacje uzyskane

// podczas testu

echo '<pre>';var_dump($benchmark-> getProfiling());

echo '</pre>';?>

Page 14: PHP Solutions 06 2006 PL

Profilowanie aplikacji PHP

www.phpsolmag.org 15

Dla początkujących

PHP Solutions Nr 6/2006

my instancję tej klasy. Podobnie jak po-przednio, pomiary rozpoczynamy i koń-czymy metodami start() i stop(). Tutaj podobieństwa z poprzednim przykładem się kończą, bowiem w przypadku Bench-mark _ Profiler zamiast markerów używa-my sekcji. Oprócz tego, że mierzony jest globalny czas wykonania między wywo-łaniami start() i stop(), osobne pomia-ry dokonywane są dla sekcji oznaczo-nych metodami enterSection() i leave-Section() oznaczającymi początek i ko-niec sekcji.

Argumentem każdej z tych metod jest nazwa sekcji, którą ustalamy we-dług naszych upodobań. W przypad-ku pomiaru czasu wykonania funkcji bądź metod można zastosować sztucz-kę, która wyłącza z pomiarów czas po-trzebny na samo wywołanie funkcji i po-nowne zwrócenie sterowania do miej-sca wywołania funkcji lub metody. W tym celu możemy wewnątrz funkcji za-deklarować, że zmienna zawierająca obiekt klasy Benchmark _ Profiler jest globalna i utworzyć sekcje wewnątrz tej funkcji (Listing 9). Wynik działania testu uzyskamy wywołując metodę display() pod koniec skryptu, a rezultat przedsta-wiamy na Rysunku 1.

Patrząc na Rysunek 1 od razu za-uważymy kolumnę oznaczoną znakiem „%”, która ilustruje procentowy udział każdej operacji w czasie wykonania skryptu między start() a stop(). Ostat-ni wiersz oznaczony jako Global ozna-cza cały skrypt (a w zasadzie cześć po-między początkiem a końcem pomia-rów), więc zajmie on 100% czasu wyko-nania. Kolumna #calls pokazuje, ile razy dana sekcja była wywołana, calls zawie-ra nazwy sekcji, jakie zostały wywołane wewnątrz sekcji odpowiedniej dla każde-go wiersza wyników.

Analizując wyniki widzimy, że naj-więcej czasu zajmuje połączenie się z bazą danych i wstawienie nowego re-kordu w tabeli bazodanowej. Nieste-ty, operacje wejścia/wyjścia są najbar-dziej kosztowne, stąd dobrą metodą na zwiększenie szybkości działania apli-kacji jest keszowanie (ang. caching). W naszym przypadku nie możemy użyć tej ostatniej techniki, gdyż przyspiesza ona odczyt, a my dodajemy dane do bazy. Jeśli bardzo zależy nam na wydajności, to możemy się zająć funkcjami filtrują-cymi nr telefonu i komentarz, co powin-no dać nam kilka procent zysku na cza-sie wykonania. Przyjrzyjmy się więc bli-żej funkcjom telephone() i comments().

Benchmark_IterateCzas wykonania skryptu i jego poszcze-gólnych części zależy od wydajności maszyny, na której odbywa się test oraz jej obciążenia. Łatwo zauważyć, że cza-sy te różnią się za każdym wywołaniem, ale najczęściej oscylują wokół jakiejś wartości, z rzadka odbiegając znaczą-co do normy. Aby pozbyć się tych nie-dogodności, powinniśmy wykonać test w pętli i uśrednić uzyskane czasy. Kla-sa Benchmark _ Iterate pozwala na wy-konywanie funkcji lub metody w pę-tli o zadanej liczbie iteracji. Na Listin-gu 10 pokazujemy przykład wykorzysta-nia tej klasy wobec funkcji filtrującej ko-mentarze.

Jak widać, nie jest to skomplikowa-ne: po stworzeniu instancji klasy Bench-mark _ Iterate uzyskujemy dostęp do jej metod służących testom i wyświe-tlaniu wyników. Kluczowa jest następu-jąca linia:

$benchmark->run( 1000, 'comments',

$comment );

Nakazuje ona uruchomić funkcję o nazwie podanej jako drugi argument i z parametrem przesłanym jako trze-ci argument. Liczbę iteracji (wywo-łań) określa pierwszy argument. My testujemy funkcję comments() z argu-mentem $comment 1000 razy. Wywo-łanie get() zwraca nam tablicę, która zawiera czas wykonania kodu w każ-dej iteracji (klucze są ponumerowane wg kolejnych liczb naturalnych) oraz czas średni (klucz mean) i liczbę itera-cji (klucz iterations). Ważną rzeczą podczas przygotowywania testu jest dobór liczby iteracji. Abyśmy mogli mówić o wiarygodnych testach, mu-simy przeprowadzić co najmniej 1000 cykli. Kod z Listingu 10 wyświetla tyl-ko czas średni, bo on jest dla nas naj-istotniejszy:

Mean time: 0.000047s for 1000

iterations of telephone()

Mean time: 0.000141s for 1000

iterations of comments()

Teraz mamy bardziej wiarygodne wy-niki i możemy przystąpić do optymali-zacji kodu funkcji comments() (Listing 11). Choć techniki optymalizacyjne nie są przedmiotem tego artykułu, to musi-my tu wspomnieć o kilku sposród nich, aby wiedzieć, jak uzyskać wzrost wy-dajności. Nasza funkcja widoczna na Listingu 4 wykorzystuje do swego dzia-łania wyrażenia regularne za pomocą funkcji ereg _ replace(). Pewnie więk-szość z Was wie, iż ereg _ replace() jest zdecydowanie wolniejsza niż per-eg _ replace() – możemy więc wyko-rzystać tę szybszą. Ponieważ jednak mamy tylko pozbyć się tagów, więc nie potrzebujemy w ogóle używać wyra-żeń regularnych: wystarczy wbudowa-

Listing 4. Funkcje filtrujące

<?php

function telephone( $telephone ){ $telephone = ereg_replace( " ", "", $telephone ); $telephone = ereg_replace( "-", "", $telephone ); $telephone = ereg_replace( "\(", "", $telephone ); $telephone = ereg_replace( "\)", "", $telephone ); return $telephone;}

function comments( $comment ){ $comment = ereg_replace( "<[^>]*>", "", $comment ); return $comment;}

?>

Listing 5. Funkcja generująca losowe hasło

<?php

function passGen($passLength=10, $startChr=33,$endChr=126){

$password = '';

while ( $passLength-- ){ $password.=chr(mt_rand( $startChr,$endChr));

}

return $password;}

?>

Page 15: PHP Solutions 06 2006 PL

Profilowanie aplikacji PHP

www.phpsolmag.org16

Dla początkujących

PHP Solutions Nr 6/2006

na do PHP funkcja strip _ tags(), któ-ra powinna być znacznie szybsza niż te pierwsze. W skrypcie z Listingu 10 do-dajemy plik z poprawioną funkcją filtrują (filter2.php).Na Listingu 12 przedstawia-my zmodyfikowany test wydajności i po-równanie funkcji comments() przed i po optymalizacji.

Porównując wyniki uzyskałem o ponad 60% krótszy czas wykonania funkcji comments(). Jest to dowód na to, że lepiej jest używać funkcji wbu-dowanych w PHP, niż samemu two-rzyć rozwiązania o podobnej funkcjo-nalności. Wniosek ten nie jest nowy i często można spotkać podobne w podręcznikach PHP. Jednak PHP za-wiera ponad 2000 funkcji wbudowa-nych (napisanych w C) i czasem moż-na pominąć tę, której potrzebujemy. Profilowanie i testy wydajności zmu-szają nas do poszukiwania bardziej optymalnych rozwiązań tam, gdzie przyniesie to największy przyrost wy-dajności.

Poprawianie dokładności wynikówProfilowanie przy pomocy programów działających w przestrzeni użytkowni-ka (w naszym przypadku napisanych w PHP) ma zasadniczą wadę, którą jest przekłamanie wynikające z narzutu cza-sowego na wykonanie testu. Postaramy się oszacować ten narzut, aby potem odjąć go od wyniku profilowania. W tym celu napiszemy własną klasę dziedzi-czącą po Benchmark _ Iterate. Na Li-stingu 13 przedstawiamy nową klasę My-Iterate. Nadpisujemy w niej tylko dwie metody oryginalnej klasy: run() i get(). W run() najpierw wywołujemy metodę klasy macierzystej:

parent::run( $iterationsCount,

$functionName, $arguments);

Listing 6. Tabela 'users' z bazy 'phpsolmag'

-- Baza danych: 'phpsolmag'

-- Struktura tabeli dla 'users'

CREATE TABLE 'users' ( 'ID' int(11) NOT NULL, 'pass' varchar(16) NOT NULL, 'addDate' datetime NOT NULL, 'phone' int(11) NOT NULL, 'comment' text NOT NULL, PRIMARY KEY ('ID')) TYPE=MyISAM AUTO_INCREMENT=115;

Listing 7. Wynik wykonania skryptu z Listingu 3

Phone: 334554455

Comments: Some comments ... , Some comments ... Safe text ClickMe! - Safe text

array(8) {

[0]=> array(4) {

["name"]=> string(5) "Start"

["time"]=> string(19) "1153815327.15625200"

["diff"]=> string(1) "-"

["total"]=> string(1) "-"

}

[1]=> array(4) {

["name"]=> string(13) "Variables set"

["time"]=> string(19) "1153815327.15628900"

["diff"]=> string(8) "0.000037"

["total"]=> string(8) "0.000037"

}

[2]=> array(4) {

["name"]=> string(13) "Phone filtred"

["time"]=> string(19) "1153815327.15635200"

["diff"]=> string(8) "0.000063"

["total"]=> string(8) "0.000100"

}

[3]=> array(4) {

["name"]=> string(16) "Comments filtred"

["time"]=> string(19) "1153815327.15654000"

["diff"]=> string(8) "0.000188"

["total"]=> string(8) "0.000288"

}

Listing 8. Użycie Benchmark_Profiler na przykładzie kodu z Listingu 3

<?php

require 'Benchmark/Profiler.php';require './filter_profiler.php';require './generators.php';$profiler = new Benchmark_Profiler();$profiler->start();

$phone = '(33) 455-44-55';

$comment = 'Some comments <script> ... </script>,

Some comments <script> ... </script>

<a href="www.somedangerouslink.com"> Safe text

<a href="javascript:alert("XSS")">ClickMe!</a> - <body

onload=alert("vulnerable")> Safe text

<iframe src=http://www.notsafe.com/script.html>';

$phone = telephone( $phone );

$comment = comments( $comment );

// tworzymy sekcje

$profiler->enterSection('passGen');

$pass = passGen();

$profiler->leaveSection('passGen');

// tworzymy sekcje

$profiler->enterSection('DB connection');

$conn = mysql_connect( 'localhost', 'root', '' );mysql_select_db( 'phpsolmag', $conn );$profiler->leaveSection('DB connection');

$sql = 'INSERT INTO users(`pass`, `addDate`, `phone`, `comment`)

VALUES( "'. $pass .'", "'. date("Y-m-d H:i:s") .'", "'. $phone .'", "'. $comment .'" )';

$profiler->enterSection('DB insertion');

mysql_query( $sql );$profiler->leaveSection('DB insertion');

$profiler->stop();

// wyświetlamy wynik

$profiler->display();

Page 16: PHP Solutions 06 2006 PL
Page 17: PHP Solutions 06 2006 PL

Profilowanie aplikacji PHP

www.phpsolmag.org18

Dla początkujących

PHP Solutions Nr 6/2006

z wcześniej przygotowanymi argumentami, a następnie w pętli obliczamy narzut czaso-wy, jaki powoduje wykonanie pomiarów:

$benchmark->setMarker( '_start_'.$i );

$benchmark->setMarker( '_stop_'.$i );

Wynik zapamiętujemy w zmiennej składo-wej $overhead. Metody setMarker() wy-glądają znajomo, ponieważ ich działa-nie jest identyczne, jak w przypadku klasy Benchmark _ Timer. Jest to w zasadzie ta sama metoda, gdyż klasa Benchmark _ It-

erate dziedziczy z klasy Benchmark _ Tim-

er. Przejdźmy do metody get(): używa-my jej aby uzyskać wyniki testów, więc naj-pierw wywołujemy tę samą metodę dla kla-sy Benchmark _ Iterate:

$result = parent::get();

a później od uzyskanego wyniku odej-mujemy wcześniej oszacowany narzut: $result['mean'] -= $this->overhead;

Na Listingu 14 pokazujemy wykorzysta-nie tej klasy na przykładzie naszej funkcji do filtrowania komentarzy.

Przy powtórzonych testach okaza-ło się, że zoptymalizowana funkcja com-ments() działa nawet 75% szybciej (bez eliminacji narzutu czasowego pomiarów było to ponad 60%). Widać więc, że sa-me testy mogą częściowo zafałszować rezultaty, więc dobrze jest to uwzględnić w wyniku końcowym.

WynikSprawdźmy teraz, jak bardzo wzrosła wy-dajność całej aplikacji po dokonanych zmianach. Na Listingu 15 pokazujemy wykorzystanie klasy Benchmark _ Profil-

er w celu zmierzenia wydajności aplika-cji ze zmodyfikowanym kodem. Porównu-jąc uzyskane rezultaty z wcześniejszymi

wynikami widać, że udział czasu przefil-trowania komentarzy spadł poniżej jedne-go procenta. Tym samym wydajność całej aplikacji wzrosła o kilka procent. To zbyt mało, żeby poczuć satysfakcję, ale pa-miętajmy, że nasz przykład jest specjal-nie uproszczony w celach dydaktycznych. Rzeczywista aplikacja ma więcej kodu, co daje nam więcej szans na znalezienie większego zysku czasu wykonania.

Zalety i wady pakietu BenchmarkInstalacja pakietu Benchmark jest bardzo łatwa; często stanowi on element stan-dardowej instalacji PHP, co pozwala nam szybko i bezproblemowo wykorzystać je-go potencjał.

Podstawową wadą klas Benchmark jest wspomniany wcześniej dodatkowy narzut czasowy podczas testów, który

Listing 9. Przykład wyłączenia czasu samego wywołania funkcji na przykładzie z Listingu 4

<?php

function telephone($telephone){ global $profiler; $profiler->enterSection(

'telephone');

$telephone=preg_replace(

"(\D)", "", $telephone );

$profiler->leaveSection(

'telephone');

return $telephone;}

function comments($comment){ global $profiler; $profiler->enterSection(

'comments');

$comment = strip_tags(

$comment );

$profiler->leaveSection(

'comments');

return $comment;}

?>

Rysunek 3. Wynik po zmianie flagi na „–u”

Parametry skryptu pprofpSkładnia pprofp jest następująca:

pprofp <opcje> <plik>

Oto opcje sortowania:

-a: sortowanie wg nazw procedur,-l: sortowanie wg liczby wywołań procedur,-r: sortowanie wg rzeczywistego czasu spędzonego w procedurach.-R: sortowanie wg rzeczywistego czasu spędzonego w procedurach z uwzględnieniem

wywołań procedur potomnych,-s: sortowanie wg czasu systemowego spędzonego w procedurze,-S: sortowanie wg czasu systemowego spędzonego w procedurach z uwzględnieniem

wywołań procedur potomnych,-u: sortowanie wg czasu użytkownika spędzonego w procedurach,-U: sortowanie wg czasu użytkownika spędzonego w procedurach z uwzględnieniem

wywołań procedur potomnych,-v: sortowanie wg średniego czasu spędzonego w procedurach,-z: Sortowanie (domyślne) wg czasu systemowego i użytkownika spędzonego w pro-

cedurach.

Opcje wyświetlania:

-c: wyświetlenie czasu rzeczywistego w całym drzewie wywołań.-i: wyłączenie z raportu funkcji wbudowanych w PHP,-O <cnt>: określa maksymalna liczbę wyświetlanych procedur (domyślnie 15),-t: wyświetla skompresowane drzewo wywołań,-T: wyświetla nieskompresowane drzewo wywołań.

Page 18: PHP Solutions 06 2006 PL

Profilowanie aplikacji PHP

www.phpsolmag.org 19

Dla początkujących

PHP Solutions Nr 6/2006

może zafałszować wyniki. Istnieje jed-nak jeszcze inna wada korzystania z programów profilujących, które działa-ją w przestrzeni użytkownika. Zapew-ne zauważyliście, ile dodatkowych linii kodu musimy dopisać w różnych miej-scach aplikacji, aby dało się ją prze-testować. Wielkość dodatkowego ko-du jest różna w zależności od tego, co chcemy konkretnie zbadać (pojedyn-czą funkcję, całość czy część aplika-cji) i od wybranego pakietu testujące-go (istnieją inne, niż te zawarte w PE-AR). Te wady są (przynajmniej czę-ściowo) eliminowane przez programy profilujące, które wykorzystują rozsze-rzenia ZEND.

APD (Advanced PHP Debugger)APD jest pakietem PECL (http://pecl.php.net), czyli rozszerzeniem PHP. Śledzi on uruchomienia i zakończenia zarówno funkcji użytkownika, jak i tych wbudowanych w PHP. Ponadto APD za-pisuje informacje o wywołaniach in-

clude i require. Instalacja pakietu za-leży od systemu operacyjnego i wersji

PHP i opisujemy ją w ramkach Instala-cja pod Windows oraz Instalacja pod Li-nuksem. Po pomyślnej instalacji powinni-śmy zobaczyć informacje o APD wywołu-jąc phpinfo().

Instalacja APD pod LinuksemAby skorzystać z APD pod Linuksem, mu-simy mieć zainstalowany pakiet php-de-vel, który zawiera podstawowe narzędzia do tworzenia rozszerzeń PHP. W więk-szości dystrybucji Linuksa zainstalujemy go używając apt-get:

apt-get php-devel

potem możemy zainstalować samo APD:

pear install APD

Teraz w pliku php.ini w sekcji [Zend] dopi-szemy ustawienia:

[Zend]

...

zend_extension = /lokalizacja/apd.so

apd.dumpdir = /katalog/z/plikiem/sledzenia

apd.statement_trace = 0

Zmienna zend _ extension wskazuje lo-kalizację APD, zaś apd.dumpdir określa miejsce na dysku, w którym będą zapisy-wane wyniki profilowania.

Instalacja APD pod WindowsInstalacja APD pod Windowsd zależy głownie od wersji PHP, z którą APD ma współpracować. Najprostsza jest instala-cja dla PHP5, którą tu opiszemy.

Pierwszym, co musimy zrobić, jest po-branie ze strony http://pecl4win.php.net/ext.php/php_apd.dll binarnej wersji biblio-teki (musimy wybrać odpowiedni plik wer-sji PHP) oraz jej skopiowanie do katalo-gu, w którym PHP szuka rozszerzeń. Fol-der ten jest określony przez zmienną ex-tension _ dir w pliku php.ini. Następnie w sekcji, w której dodajemy rozszerze-nia, musimy dopisać: extension=php _

apd.dll, a w sekcji [Zend]:

zend_extension_debug_ts =

"x:\lokalizacja\apd\php_apd.dll"

apd.dumpdir = x:\tmp

apd.statement_trace = 0

Zmienna zend _ extension _ debug _ ts określa lokalizację APD, a apd.dump-dir – katalog, w którym będą zapisywane wyniki profilowania. Musimy jeszcze zdo-być plik pprofp, który jest skryptem PHP i służy do analizowania tych wyników. Plik ten znajduje się w pakiecie źródło-wym, który można pobrać ze strony http://pecl.php.net/package/apd.

Instalacja APD dla PHP4 jest nieco bardziej skomplikowana, a jej opis w języ-ku angielskim można znaleźć pod adre-sem: http://wiki.splitbrain.org/php:apd.

APD w akcjiPo pomyślnej instalacji APD chce-my wypróbować jego działanie. Zmo-dyfikujemy więc prosty przykład z Li-stingu 1. Zmiany, które prezentujemy na Listingu 16 polegały na usunięciu wszelkich operacji związanych z kla-

Listing 10. Wykorzystanie klasy Benchmark_Iterate wobec funkcji filtrującej komentarze

<?php

require 'Benchmark/Iterate.php';require './filter.php';$benchmark = new Benchmark_Iterate();$comment = 'Some comments <script> ... </script>, Some comments <script> ...

</script>

<a href="www.somedangerouslink.com"> Safe text <a

href="javascript:alert("XSS")">ClickMe!</a> - <body

onload=alert("vulnerable")> Safe text <iframe src=http://www.notsafe.com/

script.html>';

// uruchamiamy test

$benchmark->run( 1000, 'comments', $comment );$commentResult = $benchmark->get();

// wyswietlenie wyniku

echo '<p>Mean time: ' .$commentResult['mean'] . 's for ' .$commentResult[ 'iterations'].' iterations of comments()</p>';

?>

Rysunek 4. Czas wykorzystania procesora przez poszczególne funkcje

Page 19: PHP Solutions 06 2006 PL

Profilowanie aplikacji PHP

www.phpsolmag.org20

Dla początkujących

PHP Solutions Nr 6/2006

są Benchmark _ Timer i dodaniu nowej funkcji apd _ set _ pprof _ trace() na początku skryptu, które rozpoczyna śledzenie działania skryptu. APD po-siada tez kilka innych funkcji, ale my wykorzystamy jedynie tę wymienioną. Jak widać, aby wykonać profilowanie, wymagana jest tylko minimalna mo-dyfikacja kodu aplikacji: wystarczy, że wywołanie na początku głównego pli-ku naszej aplikacji dodamy apd _ set _

pprof _ trace(). Po wykonaniu skryp-tu wynik profilowania zostaje zapisany w pliku o nazwie pprof.xxxxx.yy, gdzie xxxxx to PID procesu serwera, który uruchomił profilowany skrypt, a yy to kolejny numer porządkowy. Katalog, w którym ten plik zostanie zapisany, jest określony przez opcjonalny parametr funkcji apd _ set _ pprof _ trace().

Jeżeli ten ostatni nie został zde-finiowany, to położenie tego folderu określa zmienna apd.dumpdir z pliku php.ini. Struktura pliku pprof jest opi-sana w dokumentacji, ale odradzamy własnoręczne analizowanie jego za-wartości, ponieważ istnieje do tego proste i efektywne narzędzie – skrypt PHP o nazwie pprofp.

Wywołujemy go z linii poleceń w katalogu, w którym się on znajduje, podając ścieżkę dostępu do pliku z wynikami:

php pprofp –z /tmp/profiling/pprof.02596.8

Spowoduje to przeanalizowanie poda-nego jako parametr pliku, utworzone-go wcześniej przez funkcję apd _ set _

pprof _ trace(). Program pprofp ma szereg flag i opcji opisanych w Ramce Parametry skryptu pprofp. Nam najbar-dziej przydadzą się flagi –z i –r. Wyni-kiem interpretacji naszego pliku jest ta-bela pokazana na Rysunku 2.

Na Rysunku 2 pokazujemy kilka istotnych informacji. Na górze widać podstawowe dane, takie jak lokalizacja testowanego pliku i czas wykonania ca-

łego skryptu. Dalej znajduje się tabela podająca bardziej szczegółowe informa-cje, jak procentowy udział danej funk-cji w czasie wykonania całego skryp-tu, rzeczywisty czas, jaki minął (kolum-na Real), czas spędzony na wykony-waniu kodu użytkownika przez proce-sor (kolumna User) oraz czas spędzony na wywołaniach systemowych (kolum-na System). W kolumnie Calls zapisana jest informacja o liczbie wywołań danej funkcji (nazwa funkcji to ostatnia kolum-

na). Ostatni wiersz naszej tabeli opisu-je funkcję main(), a przecież na naszym listingu nie było funkcji o takiej nazwie! Skąd więc się ona wzięła? Otóż main oznacza tu cały skrypt od momentu roz-poczęcia śledzenia.

Przykład z życia wziętyNasz przykład jest dość prosty, więc czasy wykonania są minimalne. Weź-my więc większą aplikację: nie będzie-my jej tworzyć od zera, tylko skorzy-

Listing 11. Zoptymalizowana funkcja comments()

<?php

function comments2( $comment ){ $comment = strip_tags($comment);

return $comment;}

?>

Listing 12. Porównanie wydajności funkcji przed i po optymalizacji.

<?php

require 'Benchmark/Iterate.php';require './filter.php';require './filter2.php';$benchmark = new Benchmark_Iterate();$comment = 'Some comments <script> ... </script>,

Some comments <script> ... </script>

<a href="www.somedangerouslink.com"> Safe text <a

href="javascript:alert("XSS")">ClickMe!</a> - <body

onload=alert("vulnerable")> Safe text <iframe src=

http://www.notsafe.com/script.html>';

$benchmark->run( 1000, 'comments', $comment );

$commentResult = $benchmark->get();

$benchmark->run( 1000, 'comments2', $comment );

$comment2Result = $benchmark->get();

echo '<p>Mean time: '.$commentResult['mean'] . 's for '.$commentResult[ 'iterations']. ' iterations of comments()</p>';

$percent = round( (1-($comment2Result['mean']/$commentResult['mean']))*100, 1 );

echo '<p>Mean time: '.$comment2Result['mean'].'s ('. $percent .'% faster) for '. $comment2Result['iterations'].' iterations of comments2()</p>';

?>

Listing 13. Klasa MyIterate

<?php

require_once 'Benchmark/Iterate.php';

class MyIterate extends Benchmark_Iterate{ private $overhead;

public function run(){ $arguments = func_get_args();

$iterationsCount = array_shift( $arguments );

$functionName = array_shift( $arguments );

$arguments = array_shift( $arguments );

parent::run( $iterationsCount, $functionName, $arguments );

$benchmark = new Benchmark_Iterate(); for ( $i=0; $i<$iterationsCount; $i++ ){ $benchmark->setMarker( '_start_'.$i );

$benchmark->setMarker( '_stop_'.$i );

}

$result = $benchmark->get();

$this->overhead = $result['mean'];

}

public function get(){ $result = parent::get();

$result['mean'] -= $this->overhead;

return $result;

}

}

?>

Page 20: PHP Solutions 06 2006 PL

PHP Solutions Nr 6/2006

stamy z gotowego systemu portalowe-go, którym jest PHP-NUKE. W Inter-necie łatwo można znaleźć opis jego instalacji, załóżmy więc, że mamy już wszystko zainstalowane i skonfiguro-wane. Nie będziemy dokonywać żad-nych zmian w aplikacji, dodamy tylko na początku pliku index.php linijkę:

apd_set_pprof_trace();

Następnie wywołamy w przeglądarce tak zmodyfikowany plik i przeanalizujemy uzyskane wyniki poleceniem:

php pprofp –r /lokalizaja/pliku/pprof

Na mojej maszynie testowej najwięcej czasu zajęło wykonanie funkcji mysql _

query() oraz file(), co jest normal-ne, ponieważ wejście/wyjście jest wą-skim gardłem każdej aplikacji. Można-by znacznie przyspieszyć te operacje keszując wyniki zapytań wysyłanych do bazy danych: wiele interfejsów bazoda-nowych posiada taką funkcjonalność, pozwalającą na korzystanie z wcze-śniejszych zapytań.

Sprawdźmy teraz, które procedury trwały najdłużej (bez uwzględniania pod-procedur):

php pprofp –u /lokalizaja/pliku/pprof

Wynik powinien być podobny do przed-stawionego na Rysunku 3.

Spójrzmy na pierwsze cztery funk-cje, których wywołanie zdominowa-ło czas wykonania całego skryptu. Znowu znajdujemy tu operacje dostę-pu do bazy danych. Funkcja addslah-es(), wywoływana zapewne ze wzglę-dów bezpieczeństwa, jest koniecz-na. Spójrzmy teraz na trzecią na li-ście funkcję readdir() – została ona wywołana 633 razy, co wielokrotnie przekracza liczbę katalogów z plika-mi aplikacji. Gdyby to była nasza apli-kacja, to moglibyśmy każdorazowo za-pamiętac wynik odczytania zawartości katalogu w zmiennej, zamiast wielo-krotnie wywoływać readdir() dla tych samych katalogów. Kolejną funkcją na naszej liście jest eregi _ replace() i jak już wspomnieliśmy, można ją za-stąpić szybszym odpowiednikiem – preg _ replace().

Pomińmy teraz czas potrzeby na wykonanie operacji wejścia/wyjścia i

Page 21: PHP Solutions 06 2006 PL

Profilowanie aplikacji PHP

www.phpsolmag.org22

Dla początkujących

PHP Solutions Nr 6/2006

Łukasz WitczakAutor jest studentem piątego roku Wy-działu Informatyki Politechniki Szczeciń-skiej. Programowaniem w PHP zajmu-je się od 5 lat. Oprócz PHP, programu-je jeszcze w C/C++ i Javie.

Kontakt z autorem:[email protected]

O autorze

sprawdźmy, ile poszczególne funkcje zabrały czasu samego procesora:

php pprofp –z /lokalizaja/pliku/pprof

Wyniki przedstawiamy na Rysunku 4. Te-raz pierwsze miejsce zajmuje ini _ set(). Żeby sprawdzić, czy rzeczywiście jest po-trzebna i gdzie występują wywołania do tej funkcji, możemy się posłużyć opcją –t, która wyświetli nam drzewo wywołań funk-cji. Przeanalizowaliśmy pozostałe funk-cje spośród najbardziej czasochłonnych oprócz define(), która pojawiła się wyso-ko dopiero w tej klasyfikacji. Jest ona wy-woływana 294 razy i choć każde jej użycie niesie ze sobą minimalny narzut czasowy, to jednak podsumowując jej wszystkie wy-wołania możemy stwierdzić, że „pożarła” ona aż 11,1% czasu pracy procesora. Je-śli korzystamy z klas, możemy zamiast tej funkcji użyć stałych klasy definiowanych poleceniem const, co jest znacznie szyb-sze niż stosowanie define().

PodsumowanieNic nie zastąpi dobrych praktyk programi-stycznych oraz doświadczenia samego pro-gramisty. Jednak jesteśmy tylko ludźmi i cza-sem popełniamy błędy, które potem trzeba naprawić. Czasem trzeba poprawiać cudze błędy. Niedostateczna wydajność aplikacji (jeśli nie jest spowodowana słabym sprzę-tem) zazwyczaj jest winą błędu programisty lub projektanta systemu. Wiele z tych błę-dów można wychwycić wykorzystując profi-lowanie. Wskaże nam ono te miejsca w ko-dzie, które najbardziej opłaca się zoptyma-lizować, ponieważ dadzą stosunkowo du-ży przyrost wydajności małym kosztem (nie trzeba analizować całej aplikacji). Mam na-dzieje, że udało mi się Wam pokazać, w ja-ki sposób zidentyfikować wolne partie kodu. Pamiętajmy, aby testów dokonywać w od-izolowanym środowisku, bez zbędnych pro-cesów i najlepiej z odłączoną siecią, aby nic nie zafałszowało wyników testów. n

Listing 14. Wykorzystanie klasy MyIterate

<?php

require 'MyIterate.php';require './filter.php';require './filter2.php';$benchmark = new MyIterate();$comment = 'Some comments <script> ... </script>, Some comments <script> ... </script>

<a href="www.somedangerouslink.com"> Safe text

<a href="javascript:alert("XSS")">ClickMe!</a> -

<body onload=alert("vulnerable")> Safe text

<iframe src=http://www.notsafe.com/script.html>';

$benchmark->run( 1000, 'comments', $comment );

$commentResult = $benchmark->get();

$benchmark->run( 1000, 'comments2', $comment );

$comment2Result = $benchmark->get();

echo '<p>Mean time: ' . $commentResult['mean'] . 's for ' . $commentResult['iterations'] . ' iterations of comments()</p>';

$percent = round( (1-($comment2Result['mean']/$commentResult['mean']))*100, 1 );

echo '<p>Mean time: ' . $comment2Result['mean'] . 's ('. $percent .'% faster) for ' .

$comment2Result['iterations'] .

' iterations of comments()</p>';

?>

Listing 15. Sprawdzamy jak bardzo wzrosła wydajność naszej aplikacji.

<?php

require 'Benchmark/Profiler.php';require './generators.php';require './filter_profiler.php';require './filter2_profiler.php';$profiler = new Benchmark_Profiler();$profiler->start();

$phone = '(33) 455-44-55';

$comment = 'Some comments <script> ... </script>, Some comments <script> ... </script>

<a href="www.somedangerouslink.com"> Safe text <a href="javascript:alert(

"XSS")">ClickMe!</a> - <body onload=alert("vulnerable")> Safe text

<iframe src=http://www.notsafe.com/script.html>';

$phone = telephone( $phone );

$comment = comments2( $comment );

$profiler->enterSection('passGen');

$pass = passGen();

$profiler->leaveSection('passGen');

$profiler->enterSection('DB connection');

$conn = mysql_connect( 'localhost', 'root', '' );mysql_select_db( 'phpsolmag', $conn );$profiler->leaveSection('DB connection');

$sql = 'INSERT INTO users(`pass`, `addDate`, `phone`, `comment`)

VALUES( "'.$pass .'", "'.date("Y-m-d H:i:s").'", "'.$phone.'", "'.$comment.'" )';

$profiler->enterSection('DB insertion');

mysql_query( $sql );$profiler->leaveSection('DB insertion');

$profiler->stop();

$profiler->display();

?>

Listing 16. Prosty przykład użycia APD

<?php

apd_set_pprof_trace();

function doSth(){ $tmp = range( 0, 999 );

krsort( $tmp );

return $tmp;}

// wywołanie funkcji doSth()

$result = doSth();

?>

Page 22: PHP Solutions 06 2006 PL

Na CDNa CD

HITY2 kursy wideo KeyStone- XML Developing Using Java- XML Based Web Applications

E-BOOKSAuditing Your Web Site SecurityPHP Power ProgrammingOASSIS OpenDocument Essentials

Programy PHP-QtXAMPWampserwer

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

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!

Page 23: PHP Solutions 06 2006 PL
Page 24: PHP Solutions 06 2006 PL

www.phpsolmag.org24 PHP Solutions Nr 6/2006

Dla początkujących

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

Savant Dla początkujących

Wymienionych wad pozbawio-ny jest Savant – zorientowany obiektowo system wykorzystu-

jący samo PHP jako język szablonów. Zo-baczmy zatem, czym Savant tak napraw-dę różni się od Smarty. Spójrzmy na frag-ment kodu z Listingu 1, gdzie przedstawi-liśmy przykładowy szablon stworzony w Smarty.

Po kolei: Smarty najpierw wsta-wi odpowiednią wartość zmiennej $ti-tle, następnie, korzystając z pętli foreach przejdzie przez tablicę asocjacyjną $par-agrahps wstawiając wartości klucza ti-tle w HTML-owe znaczniki <h2>, zaś war-tości klucza content w znaczniki <p>. Nie ma tu ani śladu po kodzie PHP. W syste-mie Smarty szablony są kompilowane do tego języka.

Oczywiście nie za każdym razem – system sprawdza najpierw, czy dla dane-go szablonu nie istnieje już skompilowa-ny plik, a jeśli tak, to czy szablon nie zo-stał zmieniony od czasu ostatniej kompi-

Na rynku systemów szablonów dla PHP niepo-dzielnie króluje Smarty. Użytkownicy wysoko cenią sobie jego niezawodność i duże moż-liwości. Smarty stało się tak powszechne, że niemal kompletnie zapomniano o jego wadach: jest dość powolny, niezbyt łatwo rozszerzalny, a do tego wymaga od użytkownika opanowania nowej składni.

lacji. Jeżeli skompilowanego pliku jeszcze nie ma, bądź jest on nieaktualny, odbywa się proces kompilacji.

Stosując Smarty piszemy w myśl pa-radygmatu (wzorca) MVC (model-view-controller). Szablony, czyli warstwa pre-zentacji, są kompletnie oddzielone od warstwy logiki aplikacji, co ma czynić je (szablony i całe aplikacje) bardziej przej-rzystymi i niejako łatwiejszymi do napi-sania – teoretycznie szablony będzie mógł stworzyć grafik, który nigdy wcze-śniej nie miał do czynienia z PHP. Wadą takiego rozwiązania jest oczywiście do-

Savant – pogromca Smarty?Tomasz Garbiak

W SIECI

• http://www.phpsavant.com/– strona domowa Savanta

• http://smarty.php.net/ – strona domowa Smarty

• http://www.phppatterns.com/docs/design/templates_and_template_engines – ciekawe informacje o szablonach

• http://www.sitepoint.com/article/smarty-php-template-engine – artykul o Smarty

Stopień trudności: lll

Co należy wiedzieć...Czytelnik powinien znać podstawy PHP i HTML. Przyda się też znajomość Smarty.

Co obiecujemy...Z artykułu dowiesz się, jak korzystać z systemu Savant i czy warto się na nie-go przesiąść.

Page 25: PHP Solutions 06 2006 PL

www.phpsolmag.org24 PHP Solutions Nr 6/2006

Dla początkujących

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

Savant Dla początkujących

datkowa praca, jaką musi wykonać inter-preter języka kompilując szablony, czy choćby sprawdzać, czy zostały skompi-lowane. Wpływa to oczywiście w jakimś stopniu na szybkość działania aplikacji i obciążenie serwera. Wielu programi-stów uważa zatem, że wobec tego lepiej po prostu w szablonach używać PHP. Swoją drogą język PHP w swej najprost-szej formie można uznać za swego ro-dzaju system szablonów. Przecież chy-ba każdy z nas zaczynał jego naukę od prostych skryptów, podobnych do tego z Listingu 2.

I w ten sposób uzyskaliśmy ten sam efekt, który dałoby nam Smarty i – na pierwszy rzut oka – o wiele mniejszym kosztem. Uzyskany kod faktycznie jest całkiem czytelny. Ale problemy z jego przejrzystością zaczną się dopiero, kiedy wymieszamy go z rozbudowanym kodem HMTL. Widać zatem, że Smarty będzie całkiem praktycznym rozwiązaniem przy nieco większych projektach.

SavantPrzedstawiony wcześniej fragment kodu PHP jest bardzo podobny do szablonu Savanta. Nie ulega wątpliwości, że sys-tem ten znalazł już wielu zwolenników, a swój ogromny sukces zawdzięcza po-niekąd frameworkowi Ruby On Rails. Ja-ko jedną z największych zalet Ruby'ego od początku wymieniano fakt, że wszę-dzie, a więc także w szablonach, uży-wa się jednego i tego samego języka. Czy takie rozwiązanie jest lepsze o sys-temu Smarty? Odpowiemy na to pytanie

już za chwilę. Póki co, warto podkreślić, ze w Zend Frameworku tworzonym przez firmę Zend, system szablonów również będzie korzystał bezpośrednio z języ-ka PHP, a w pracach nad nim udział brał Paul M. Jones, twórca Savanta.

Szablony SavantaSavant to system zorientowany obiekto-wo, więc chcąc skorzystać w szablonie z jakiejś zmiennej, powinniśmy odwoływać się do niej poprzez obiekt klasy Savant. Jako że szablony same stają się w pew-nym momencie zmiennymi tego obiektu, wystarczy użycie słowa $this. Spójrzmy na prosty szablon Savanta na Listingu 3.

Oczywiście formuła $this->vari-

able _ name ma zastosowanie tylko dla zmiennych przypisanych wcześniej do obiektu Savanta (o tym później). Do zmiennych lokalnych, utworzonych już w samym szablonie odwołujemy się w zwy-kły sposób. W powyższym przykładzie ta-ką zmienną jest $item.

Co ciekawe, Savant nie zmusza nas do korzystania ze zmiennej $this. Wystarczy w tym celu ustawić w je-go konfiguracji (patrz Instalacja) opcjęexctract na wartość true. Nie jest to jednak zalecane – stracimy trochę cen-nego czasu oraz pamięci RAM, a sam kod stanie się mniej czytelny (trudniej

Listing 1. Przykładowy szablon w Smarty

<h1>{$title}</h1>

{foreach item=item from=$paragraphs}

<h2>{$item.title}</h2>

<p>{$item.content}</p>

{/foreach}

Listing 2. Język PHP jako system szablonów

<h1><?php echo $title ?></h1><?php foreach($paragraphs as $item): ?><h2><?php echo $item['title'] ?></h2><p><?php echo $item['content'] ?></p>

<?php endforeach; ?>

Listing 3. Szablon w systemie Savant

<h1><?php echo $this->title ?></h1><?php foreach($this->paragraphs as $item): ?><h2><?php echo $item['title'] ?></h2><p><?php echo $item['content'] ?></p><?php endforeach; ?>

����������

����������

��� ���

���

���

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

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

������

����

�������

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

���������

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

Rysunek 1. Schemat działania systemu Smarty

Page 26: PHP Solutions 06 2006 PL

Savant

www.phpsolmag.org26

Dla początkujących

PHP Solutions Nr 6/2006

będzie odróżnić, które zmienne w sza-blonie są lokalne, a które przekazywa-ne z warstwy logiki).

Zalety korzystaniaz PHP w szablonach, czyli Savant vs. SmartyZanim na dobre zaczniemy przygodęz praktycznymi przykładami, przybliżmy sobie najważniejsze zalety korzystaniaz PHP w szablonach. Stosując samo PHP, zyskujemy przede wszystkim wzrost szyb-kości działania strony (Ramka Benchmar-ki). Nie musimy także uczyć się nowej składni, a dodatkowo nie jesteśmy ograni-czeni do zaledwie dwóch pętli sterujących dostępnych w Smarty. Możemy na przy-kład skorzystać z pętli while lub do-while.

Nie chcielibyśmy jednak, aby użyt-kownik mający możliwość zmiany sza-blonów, mógł wpływać na kod strony. Tu przewagę ma Smarty – kompilacja sza-blonów umożliwia kontrolę tego, co się w nich znajduje. Dlatego też twórcy Sa-vanta zdecydowali się na dodanie do niego funkcjonalności pozwalającej do-łączać własne kompilatory (szczegóły w

Ramce Bezpieczeństwo). Savant to zde-cydowanie lżejszy system. Smarty ze swo-imi 45 pluginami wydaje się być przy nim prawdziwym kombajnem z takimi funkcja-mi jak choćby obsługa keszowania. Savant nie posiada podobnej usługi – zamiast tego jego twórca zaleca korzystanie z zewnętrz-nych bibliotek takich jak PEAR::Cache_Li-te, ewentualnie rozszerzeń do PHP zapew-niających keszowanie kodu pośredniego (np. biblioteka APC). Na końcu dokumen-tacja – korzystanie z PHP w szablonach daje możliwość umieszczenia w nich ko-mentarzy, a tym samym tworzenia auto-matycznej dokumentacji za pomocą po-pularnego phpDocumentora czy Doxyge-na. Plus dla Savanta.

InstalacjaZacznijmy od wyboru wersji. Możemy zdecydować na wersję 2.x napisaną jesz-cze dla PHP4, bądź na wersję 3 stworzo-ną z myślą o PHP5. Obie oferują te sa-me możliwości, wersja 3 różni się od po-przedniej innym zestawem wbudowanych pluginów oraz nieco zmienionym syste-mem filtrowania. Przede wszystkim jed-nak korzysta w pełni z wprowadzonych w PHP5 nowych cech obiektowych i działa bez problemów nawet pod najbardziej re-strykcyjnymi ustawieniami obsługi błędów (E_ALL & E_STRICT). Wadą nowszej wersji jest to, że jest jeszcze ciągle rozwi-jana, dlatego mogą pojawić się w niej po-ważne błędy itp. Oczywistą zaletą wersji 2.x jest spora ilość dodatków i dojrzałość. My skupimy się na wersji 3.

Instalacja polega na ściągnię-ciu pakietu i skopiowaniu go w odpo-wiednie miejsce. Plik można pobrać ze strony http://www.phpsavant.com/yawiki/index.php?area=Savant3. Należy go na-stępnie rozpakować i zawartość katalogu Savant3-3.0.0(plik Savant3.php oraz pod-katalog Savant3) umieścić w wybranym przez siebie miejscu, dostępnym dla uży-wanego przez nas serwera WWW, najle-piej tam, gdzie mamy ustawioną zmienną konfiguracyjną include _ path.

Można też, jeżeli się ma taką możli-wość, zainstalować Savanta korzystając z instalatora PEAR. W tym celu wystar-czy wykonać polecenie:

pear install

http://phpsavant.com/

Savant3-3.0.0.tgz.

Warstwa logikiZacznijmy od warstwy logiki, a więc od skryptu, w którym wykonywane będą właściwe operacje i który będzie przeka-zywał dane do szablonów. Na początek stwórzmy obiekt Savanta.

require_once 'Savant3.php';

$savant = new Savant3();

Benchmarki – Savant szybszy od SmartyCzy faktycznie Savant jest szybszy niż Smarty? Testy wydajności udzielają na to pytanie jednoznacznej odpowiedzi: TAK.

Zmierzyliśmy, ile razy na sekundę serwer zdoła wyświetlić strony wygenerowane przy pomocy obydwu systemów szablonów – Rysunek 3. W przypadku systemu Smarty zrobiliśmy pomiary dla skompilowanych i nieskompilowanych szablonów. Sprawdziliśmy również wyniki dla dla różnej ilości jednoczesnych zapytań (od 1 do 1000). Za każdym razem znaczną przewagę miał Savant. Brak kompilacji tylko w niewielkim stopniu wpły-nął na rezultat Smarty (Tabela 1).

����������

����������

���

���

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

������

����

�������

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

Rysunek 2. Schemat działania systemu Savant

Tabela 1. Wyniki testów (w liczbie odpowiedzi na sekundę)

Liczbajednoczesnych

zapytańSavant

Smarty (szablony nie skompilowane

wcześniej)

Smarty (szablony skompilowane

wcześniej)1 25,12 16,91 16,95

10 24,90 16,79 16,82

100 24,81 16,72 16,76

1000 21,49 15,60 15,73

Średnia 24,08 16,51 16,57

Page 27: PHP Solutions 06 2006 PL
Page 28: PHP Solutions 06 2006 PL

Savant

www.phpsolmag.org28

Dla początkujących

PHP Solutions Nr 6/2006

Jeżeli chcemy, możemy jako parametr dla konstruktora przekazać tablicę asocjacyj-ną zawierającą opcje konfiguracyjne. Nie ma sensu ich tu teraz wszystkich oma-wiać, zwłaszcza, że bardzo rzadko bę-dą nam one potrzebne. Opcja extract, o której mówiliśmy już wcześniej, umoż-liwia pozbycia się konieczności odwo-ływania się do zmiennych przez formu-łę $this->variable _ name. Możemy też ustawić ścieżki do folderów z szablonami. Domyślnie Savant przyjmuje, że szablony znajdują się tam, gdzie skrypt PHP, który z nich korzysta. Znacznie wygodniejszym rozwiązaniem będzie utworzenie osobne-go katalogu tylko dla szablonów, tak aby łatwiej było zarządzać projektem. W tym celu wykorzystamy zmienną konfigura-cyjną $ _ config['template _ path']. Ma ona postać tablicy, zawierającej ścieżki do katalogów, w których system ma szu-kać szablonów. Wartość tej tablicy może-my ustawić za pomocą konstruktora kla-sy, przekazując mu tablicę z konfiguracją dla klucza template _ path. Innym sposo-bem jest skorzystanie z metody setPath() (większość pozostałych zmiennych kon-figuracji ma podobne, właściwe dla sie-bie metody), w której pierwszym parame-trem jest typ ustalanej ścieżki. Typ przyj-mować może dwie wartości: resource, je-żeli chcemy ustalić położenie pluginów, czy też innych dodatków do Savanta lub template, jeżeli chcemy określić położe-nie szablonów. Drugi parametr może być łańcuchem znaków (jeżeli ustalamy poje-dyńczą ścieżkę) lub tablicą, jeżeli chcemy mieć kilka katalogów z szablonami.

Oba wymienione sposoby mają tą wadę, że nadpisują dotychczasową za-wartość wartości klucza template _ path w tablicy $ _ config. Może to nam utrudnić

pracę, jeśli w jakimś momencie będzie-my musieli dodać nową ścieżkę do już ist-niejących. Na szczęście możemy skorzy-stać z metody addPath(), która działa nie-co lepiej. Przyjmuje te same argumenty co setPath(), a więc najpierw określamy typ ścieżki (template), a następnie poda-jemy jedną lub więcej (korzystając z tabli-cy) ścieżek.

Jeżeli nasz szablon znajduje się w podkatalogu do już ustalonej ścieżki, nie musimy poszerzać listy katalogów o tą lokalizację. Wystarczy podczas wyboru szablonu (na ogół w momencie wywoła-nia metody display()) dopisać ten katalog przed nazwą pliku:

$savant->display('

podkatalog/plikSzablonu.tpl.php');.

Mając już ustalone ścieżki, możemy zająć się przygotowaniem danych, które przypi-szemy do szablonu. Przyjmijmy, że two-rzymy bardzo typową stronę, np. blog, w którym znajdzie się nagłówek z tytułem strony i jej opisem, menu złożone z kilku

elementów, przykładowa treść oraz stop-ka z informacjami o autorze. W rzeczywi-stej aplikacji te dane byłyby najpewniej po-bierane z bazy danych, tu zrezygnujemy z tak skomplikowanych procedur i wpiszemy je na sztywno do skryptu (patrz Listing 5).

Następnie dane musimy przekazać Savantowi korzystając z metody assign, co widać na Listingu 6. Niekiedy koniecz-ne będzie przekazanie do szablonu refe-rencji, a nie kopii zmiennej. Zadanie to re-alizuje metoda assignRef():

$ref = 'ref';

$savant->assignRef('ref', $ref);

Przejdźmy teraz do najważniejszego, czyli wybrania szablonu i wyświetlenia jego zawartości. Służy do tego metoda display():

$savant->display('template.tpl.php');

W ten sposób mamy już wszystko, czego nam trzeba od strony logiki. Nasz skrypt wygląda teraz tak jak na Listingu 7.

Szablon – warstwa widokuBrakuje nam już tylko samego szablo-nu. Kod gotowego szablonu można zo-baczyć na Listingu 8.

Konstrukcję if-else stosujemy po to, by nie wyświetlać odnośnika do bie-żącej strony.

Metoda eprint() służy do „bezpiecz-nego” wyświetlania zmiennych. Świetnie nadaje się do obrony przed atakami XSS i znajduje zastosowanie głównie przy ob-słudze formularzy. Jej działanie polega na odpowiednim przefiltrowaniu danych przed wyświeleniem za pomocą wybranej funk-cji. Domyślnie jest to funkcja htmlspecial-

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

���� ������

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

���

���

���

���

����

����

����

����

����

����

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

Rysunek 3. Prównanie wydajności Savanta i Smarty

BezpieczeństwoSzablony Savanta są zwykłymi plikami PHP, dlatego powinniśmy w większym stop-niu zadbać o ich bezpieczeństwo. Podobnie jak innych naszych skryptów, tak i szablo-nów Savanta nie możemy udostępniać anonimowym użytkownikom. Mogą jednak zaist-nieć przypadki, kiedy chcielibyśmy to zrobić – jeżeli na przykład prowadzimy serwis da-jący możliwość zakładania własnych blogów, a tym samym tworzenia i edycji szablonów. Wyjściem jest tutaj stosowanie szablonów kompilowanych. Można to zrobić w Savantcie korzystając z zewnętrznych kompilatorów. Przykładowy kompilator dołączony standardo-wo do wersji 2.x już w dużym stopniu podnosi bezpieczeństwo Savanta. Jeżeli przestawi-my go w tryb restrykcyjny, będziemy mogli określić, jakie funkcje i statyczyne metody mo-gą być wywoływane z poziomu szablonów. Więcej o tym opowiemy na końcu artykułu.

Pamiętajmy, aby postronny użytkownik nie miał dostępu do naszego systemu plików, a więc np. błędem byłoby uzależnianie nazwy używanego szablonu od wartości klucza w tablicy $ _ GET.

W ramach zabezpieczenia się przed atakami XSS Savant oferuje szerokie możliwo-ści filtrowania wyświetlonych zmiennych. Służy do tego głównie funkcja eprint(), o któ-rej również powiemy w dalszej części tego artykułu.

Page 29: PHP Solutions 06 2006 PL

Savant

www.phpsolmag.org 29

Dla początkujących

PHP Solutions Nr 6/2006

chars(), można jednak w zależności od po-trzeb samemu określić, jakie funkcje, czy metody mają być stosowane. Najprościej zrobić to bezpośrednio w szablonie poda-jąc wybrane funkcje jako kolejne parame-try dla metody eprint(). Parametry są typu callback: mogą to więc być nie tylko nazwy funkcji, ale także tablice, w których pierw-szym elementem jest statyczna klasa bądź obiekt, a drugim metoda dla podanej kla-sy lub obiektu. Jeżeli chcemy ustalić listę stosowanych funkcji wspólną dla wszyst-kich wywołań metody eprint, powinniśmy skorzystać z metody setEscape(). Podob-nie jak eprint(), przyjmuje ona jako para-metry nazwy wybranych funkcji wymienio-ne po przecinku. Savant wydaje się zatem elastyczniejszy w tym zakresie. W systemie Smarty, aby dodać własną funkcję, musimy napisać wtyczkę do systemu.

Warto wspomnieć również o Savan-towskich filtrach. Ich działanie jest podob-ne do metody eprint(), tyle że nie doty-czy pojedyńczych zmiennych, ale całości wyświetlanej strony – czyli wygenerowa-ny przez Savanta wynik jest filtrowany za-nim zostanie wysłany do klienta. Lista funk-cji filtrujących jest przechowywana w tabli-cy $ _ config pod kluczem filters i można ją zmienić metodami setFilters() (nadpi-suje dotychczasową zawartość tablicy) lub addFilters() (dodaje filtr do listy). Podob-nie jak w przypadku eprint() czy setEs-cape(), tak i tu kolejne funkcje można poda-wać po przecinku jako parametry dla tych metod. Funkcje te mogą być standardowy-mi funkcjami PHP, jednak wraz z Savantem dostarczona jest też abstrakcyjna klasa Sa-vant3 _ Filter, która służyć ma do tworze-nia własnych filtrów. Tym zagadnieniem nie będziemy się tu zajmować, warto jedynie wspomnieć, że tworzenie filtrów jest podob-ne do tworzenia pluginów, o których powie-my za chwilę.

Smarty w tym zakresie oferuje po-dobną funkcjonalność, dodatkowo po-zwalając na filtrowanie szablonów przed i po kompilacji.

Kilka szablonówna jednej stronieObecnie mamy wspólny szablon dla cało-ści wyświetlanej strony. Chcielibyśmy jed-nak, aby inne strony wyglądały inaczej za-chowując jedynie wspólny nagłówek i stop-kę. Jak to zrobić? Szablon można podzie-lić na mniejsze fragmenty i umieścić je w osobnych plikach, tak jak na Listingach 10 i 11. Aby umieścić te fragmenty w głównym

Listing 4. Kilka sposobów na określenie położenia katalogu z szablonami

$savant = new Savant4(array('template_path' => array('/sciezka/do/katalogu/z/szablonami/')));

$savant->setPath('template', array('sciezka/do/katalogu/z/szablonami/', 'sciezka/do/innego/katalogu/z/szablonami/'));

$savant->addPath('template', 'nowa/sciezka/do/katalogu/z/szablonami/');

Listing 5. Plik warstwy logiki,który przekaże wartości zmiennych do szablonu

$title = 'Page about Savant';

$description = 'Example page that shows the possibilites of Savant 3';

$menu = array('home' => 'index.php', 'tutorial' => 'tutorial.php', 'contact' => 'contact.php');

$currentPage = 'home';

$content = 'Nunc purus odio, imperdiet eget, pharetra quis, consequat quis,

nulla. Phasellus at felis. Sed ac erat et est lacinia rhoncus. Maecenas urna

sapien, euismod vitae, aliquet sit amet, scelerisque vitae, velit. Curabitur

quis urna. Pellentesque habitant morbi tristique senectus et netus et malesuada

fames ac turpis egestas. Integer in risus ac orci lacinia volutpat.';

$info = 'Copyright 2006. Tomek Garbiak';

Listing 6. Przekazywanie wartości zmiennych szablonowi

$savant->assign('title', $title);

$savant->assign('description', $description);

$savant->assign('menu', $menu);

$savant->assign('currentPage', $currentPage);

$savant->assign('content', $content);

$savant->assign('info', $info);

Listing 7. Warstwa logiki w całej okazałości

require_once 'Savant3.php';

$savant = new Savant3();$savant->addPath('sciezka/do/katalogu/z/szablonami/');

$title = 'Page about Savant';

$description = 'Example page that shows the possibilites of Savant 3';

$menu = array('home' => 'index.php', 'tutorial' => 'tutorial.php', 'contact' => 'contact.php');

$content = 'Nunc purus odio, imperdiet eget, pharetra quis, consequat quis,

nulla. Phasellus at felis. Sed ac erat et est lacinia rhoncus. Maecenas urna

sapien, euismod vitae, aliquet sit amet, scelerisque vitae, velit. Curabitur

quis urna. Pellentesque habitant morbi tristique senectus et netus et malesuada

fames ac turpis egestas. Integer in risus ac orci lacinia volutpat.';

$info = 'Copyright 2006. Tomek Garbiak';

$savant->assign('title', $title);

$savant->assign('description', $description);

$savant->assign('menu', $menu);

$savant->assign('content', $content);

$savant->assign('info', $info);

$savant->display('template.tpl.php');

Page 30: PHP Solutions 06 2006 PL

Savant

www.phpsolmag.org30

Dla początkujących

PHP Solutions Nr 6/2006

szablonie należy skorzystać z metody tem-plate i instrukcji include, tak jak pokazano to na Listingu 12. Innym, nieco bardziej ele-ganckim sposobem jest uzyskanie metodą fetch() wyniku wyświetlenia mniejszych szablonów i przypisanie ich do zmiennych. Cała ta operacja odbywa się oczywiście w pliku warstwy logiki. W głównym szablonie wstawiamy już same zmienne:

$header = $savant->fetch(

'header.tpl.php');

$savant->assign('

header', $header);

$footer = $savant->fetch(

'footer.tpl.php');

$savant->assign(

'footer', $footer);

Drobnej modyfikacji wymagać teraz bę-dzie szablon główny – jego kod przedsta-wia Listing 13. Sposób postępowania wy-gląda tu zatem podobnie jak w Smarty.

Pluginy, czyli rozbudowywanie SavantaOgromną zaletą Savanta jest możliwość jego łatwej rozbudowy dzięki sprawnemu systemowi pluginów. Zanim jednak napi-szemy własne rozszerzenie, przyjrzyjmy się, jaka jest zasada ich działania. Plugi-ny to obiekty, które można wywołać w sza-blonie celem automatyzacji tworzenia pew-nych powtarzających się elementów, np. aby łatwiej było umieścić na stronie odno-śnik. Taką możliwość daje dostarczony z Savantem plugin ahref, którego używa się wpisując do szablonu następujący kod:

<?php echo

$this->ahref('Text',

'http://some.dummy.address.com') ?>,

co w efekcie daje nam taki oto rezulat:

<a href="http://some.dummy.

address.com">Text</a>

Kod pluginu znajdziemy w katalogu Savant3/resources. W tym miejscu będzie-my umieszczać nasze własne pluginy. Moż-na zdecydować się na inną lokalizację, ale wówczas będziemy musieli podać Savanto-wi odpowiednią ścieżkę posługując się zna-nymi już metodami setPath() lub addPath(). Jedyna różnica w ich wykorzystaniu pole-gać będzie na tym, że tym razem pierw-szym parametrem będzie nie template a

resource. Otworzywszy plik Savant3_Plu-gin_ahref.php przekonamy się, że plugin to w zasadzie klasa dziedzicząca po klasie Savant3 _ Plugin posiadająca zaledwie jed-ną metodę o nazwie takiej jak nazwa plu-ginu (pominąwszy początkową część: Sa-vant3 _ Plugin). Savant używa tu magicz-nej metody _ _ call(), która odpowiada za zachowanie się klasy podczas próby wy-wołania nieistniejącej funkcji. W takim przy-padku Savant będzie próbował znaleźć od-powiedni plugin (jego nazwa określana jest na podstawie nazwy metody) i wywołać da-ną metodę.

Napiszmy zatem własną wtyczkę, któ-ra wstawi do szablonu adres strony, z ja-kiej wszedł użytkownik. W tym celu tworzy-my w katalogu Savant3/resources plik o na-zwie Savant3_Plugin_referer.php, w którym umieszczamy kod jak na Listingu 14. Mamy za sobą najtrudniejszy fragment, czyli stwo-rzenie szkieletu klasy naszej wtyczki. Pozo-staje już tylko napisanie prostej funkcji, która zwróci odnośnik do strony, z której użytkow-nik wszedł do naszego serwisu – przykłado-wa implementacja widoczna jest na Listingu 15. Gotowe! Teraz wystarczy w szablonie wpisać <?php echo $this->referer() ?> i

Listing 8. Szablon w Savancie

<?xml version="1.0" encoding="iso-8859-1"?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://

www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

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

<head>

<title><?php echo $this->title ?></title><meta name="description"

content="<?php $this->eprint($this->description) ?>" />

</head>

<body>

<h1><?php echo $this->title ?></h1><ul><?php foreach($this->menu as $key => $val): ?> <li><?php if ($key != $this->currentPage): ?> <a href="<?php echo $val ?>"><?php echo $key ?></a> <?php else: echo $key; endif; ?></li></ul>

<p>

<?php echo $this->content ?>

</p>

<address><?php echo $this->info ?></address></body>

</html>

Listing 9. Przykład użycia funkcji eprint()

<input type="text" name="sampleInput"

value="<?php $this->eprint($this->variable, strip_tags,

htmlspecialchars, array($someObject, $method)) ?>" />

Listing 10. Szablon nagłówka

<?xml version="1.0" encoding="iso-8859-1"?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://

www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

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

<head>

<title><?php echo $this->title ?></title><meta name="description"

content="<?php $this->eprint($this->description) ?>" />

</head>

<body>

<h1><?php echo $this->title ?></h1><ul><?php foreach($this->menu as $key => $val): ?> <li>

<?php if ($key != $this->currentPage): ?> <a href="<?php echo $val ?>"> <?php echo $key ?></a><?php else: echo $key; endif; ?> </li>

</ul>

Page 31: PHP Solutions 06 2006 PL

Savant

www.phpsolmag.org 31

Dla początkujących

PHP Solutions Nr 6/2006

Kompilacja w SavancieWróćmy do wykorzystania w Savancie ze-wnętrznych kompilatorów. Włączenie kom-pilatora sprowadza się do napisania kilku linijek kodu, tak jak na Listingu 16.

W pierwszym kroku ładujemy pliki zarówno Savanta, jak i kompilatora. Na-stępnie tworzymy instancję kompilato-ra i ustalamy opcje konfiguracyjne – wy-starczy podać ścieżkę do katalogu, w któ-rym będą przechowywane skompilowane pliki. Trzeba pamiętać o tym, że serwer musi mieć możliwość zapisu w tym ka-talogu, trzeba mu więc nadać odpowied-nie prawa. Kompilator umożliwia korzy-stanie w szablonach ze składni zbliżonej do tej znanej ze Smarty, a więc na przy-kład, aby wyświetlić zmienną, należy wpi-sać {$variable}.

Jeśli kompilator ustawimy w tryb re-strykcyjny, co odbywa się przez nadanie parametrowi $strict wartości true, w sza-blonach będzie można korzystać tylko z tych funkcji i statycznych metod, które po-damy w parametrach $allowedFunctions i $allowedStatic. Funkcjonalność ta nie jest jeszcze w pełni sprawna, dlatego domyślnie jest wyłączona. Trzeba pamiętać, że przed-stawiony kompilator jest jedynie przykładem możliwości systemu Savant, nie zaś w pełni działającym produktem.

PodsumowanieCzy wobec wszystkich przedstawionych tu wad i zalet Savanta warto dla niego po-rzucić Smarty? Odpowiedź zależy w dużej mierze od indywidualnych upodobań pro-gramisty i współpracujących z nim grafików. Jeżeli potrzebny jest nam lekki i elastycz-ny system, Savant będzie idealny. Jeżeli potrzebujemy systemu bezpieczniejszego, z mnóstwem możliwości, to na dzień dzi-siejszy lepiej pozostać przy Smarty. Bar-dzo prawdopodobne jest jednak, że wkrót-ce Savant doścignie swojego konkurenta i jego funkcjonalności nie będzie można ni-czego zarzucić. n

Listing 11. Szablon stopki

<address><?php echo $this->info ?> </address>

Listing 12. Szablon główny

<?php include $this-> template('header.tpl.php') ?>

<p>

<?php echo $this->content ?>

</p>

<?php include $this-> template('footer.tpl.php') ?>

</body>

</html>

Listing 13. Szablon główny, tym razem nagłówek i stopka jako zmienne

<?php echo $this->header ?><p>

<?php echo $this->content ?>

</p>

<?php echo $this->header ?></body>

</html>

Listing 14. Szkielet dla naszego pluginu

class Savant3_Plugin_referer extends Savant3_Plugin { public function referer() { }

}

Listing 15. Pełny kod pluginu

public function referer() { if (!$_SERVER['HTTP_REFERER']) { return ''; } else { $html = '<a href="';

$html .= $_SERVER['HTTP_REFERER'];

$html .= '">You came from this page</a>';

return $html; }

}

Listing 16. Włączenie zewnętrznego kompilatora

require_once 'Savant2.php';

require_once 'Savant2/Savant2_Compiler_basic.php';

$compiler = & new Savant2_Compiler_basic();$compiler->compileDir = '/tmp';

$tpl = & new Savant2();$tpl->setCompiler($compiler);

Listing 17. Podnoszenie bezpieczeństwa przez włączenie trybu restrykcyjnego w kompilatorze

$compiler->strict = true;$compiler->allowedFunctions = array('htmlspecialchars', 'echo', 'print', 'strip_tags');

Tomek Garbiak łączy pracę jako pro-gramista w firmie MagneticPoint ze stu-diowaniem informatyki na Politechni-ce Szczecińskiej. Wolny czas i pienią-dze najchętniej poświęca podróżowaniu i chodzeniu na koncerty.

Kontakt z autorem:[email protected]

O autorze

uzyskamy zamierzony efekt. Pisanie podob-nych pluginów dla systemu Smarty również nie należy do rzeczy trudnych, jednakże

mamy tam do czynienia ze znacznie więk-szą ilością możliwych opcji (obsługa keszo-wania, dynamiczna rejestracja pluginu, itp.).

Page 32: PHP Solutions 06 2006 PL

Bezpieczeństwo

www.phpsolmag.org32 PHP Solutions Nr 6/2006

RSA w PHP Bezpieczeństwo

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

Nieodłączną częścią każdego me-chanizmu logowania użytkowni-ków jest przesyłanie hasła wpi-

sanego na komputerze klienta (w przy-padku aplikacji webowych – w przeglą-darce internetowej) na serwer, na którym (przeważnie w bazie danych) przechowy-wane są skróty MD5 haseł (zob. Ramka Podstawy MD5). Ponieważ te skróty są jednokierunkowe i odtworzenie haseł na ich podstawie nie jest możliwe, więc kra-dzież bazy nie da sprawcy wielu możli-wości włamania. Poważnym problemem jest natomiast wspomniane już przesyła-nie hasła jako czystego tekstu: wystarczy, aby intruz podsłuchał transmisję pomiędzy klientem a serwerem (np. za pomocą snif-fera), a będzie mógł bez problemu doko-nać agresji.

W poprzednim numerze PHP So-lutions, w artykule Kryptografia w PHP prezentowaliśmy rozwiązanie tego pro-blemu przy użyciu algorytmu HMAC-MD5 po stronie klienta na wpisywanym

Zabezpieczenie aplikacji PHP przed włamaniami to nie wsystko: jeżeli przesyłamy dane czystym tekstem, zawsze może się znależć ktoś, kto je przechwyci podsłuchując transmisję w Internecie. Aby zadbać o bezpieczeństwo danych, musimy więc sięgnąc po kryptografię asymetryczną RSA, która daje obecnie największą pewność jego ochrony i zastosować ją zarówno po stronie serwera, jak i klienta.

haśle. Algorytm ten (w naszym przy-padku zaimplementowany w języku Ja-vaScript i działający na przeglądarce internetowej) zamienia hasło na spe-cjalny hash, podczas generowania któ-rego używany jest również tajny klucz. Następnie, otrzymany skrót jest wysy-łany do serwera, gdzie – podobnie jak przy sprawdzaniu hasła przesyłanego czystym tekstem – jest porównywany z hashami zapisanymi w bazie danych. Korzystając z tej metody wyeliminuje-my ryzyko podsłuchu (nie przesyłamy

RSA w PHP: chronimy nasze dane przy użyciu kryptografii asymetrycznejKamil Karczmarczyk

W SIECI

1. http://pear.php.net/package/Crypt_RSA/ – klasa Crypt_RSA (PHP)

2. http://pear.php.net/package/Crypt_Blowfish/ – klasa Crypt_Blowfish (PHP)

3. http://ohdave.com/rsa/ – Im-plementacja RSA (JS)

4. http://en.wikipedia.org/wiki/RSA – Opis algorytmu RSA

5. http://en.wikipedia.org/wiki/Blowfish_(cipher) – Opis algorytmu Blowfish

6. http://advajax.anakin.us/ – Obiekt advancedAJAX

Stopień trudności: lll

Co należy wiedzieć...Należy znać podstawy programowania w PHP oraz AJAX. Przydatna będzie też podstawowa wiedza na temat kryptografii.

Co obiecujemy...Pokażemy zasadę działania algorytmu asymetrycznego RSA i zademonstruje-my, jak przy jego użyciu stworzyć system bezpiecznego logowania.

Page 33: PHP Solutions 06 2006 PL

Bezpieczeństwo

www.phpsolmag.org32 PHP Solutions Nr 6/2006

RSA w PHP Bezpieczeństwo

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

samego hasła, lecz jego skrót), ale mo-żemy jej użyć wyłącznie wobec już ist-niejących haseł, których skróty zostały utworzone i zapisane w bazie na ser-werze. W jaki sposób więc przeprowa-dzimy proces rejestracji nowego kon-ta na serwerze? Musimy w końcu prze-słać na serwer informacje o nowym ha-śle: z oczywistych powodów nie chce-my tego robić przy użyciu czystego tekstu; przesyłanie skrótu takiego ha-sła również nie uchroni nas przed nie-bezpieczeństwem podsłuchu: ktoś, kto w tym momencie przechwyci ten skrót (rozpozna, że chodzi o dodawanie no-wego konta np. po danych wysyłanych z formularza), będzie mógł go potem zwyczajnie wykorzystać przy logowa-niu się na serwerze. Szyfrowanie da-nych za pomocą klucza symetryczne-go (np.3DES lub twofish) również nie wchodzi w grę, gdyż taki klucz musiał-by być znany zarówno klientowi, jak i serwerowi, co oznacza konieczność je-go transmisji przez Internet oraz czy-ni go podatnym na podsłuch. Jedy-nym sensownym rozwiązaniem proble-mu zabezpieczenia hasła będzie uży-cie asymetrycznego algorytmu szy-frującego RSA. Algorytm ten działa w oparciu o dwa klucze: publiczny (który jest ogólnie dostępny) i prywatny (do-stępny wyłącznie na serwerze). Wię-cej informacji na temat algorytmu RSA zamieściliśmy w Ramce RSA: Zasada działania.

Tworzymy system bezpiecznego logowania dla aplikacji „Notatki osobiste”Pokażemy teraz, jak wykorzystać algorytm szyfrujący RSA w PHP, tworząc system bezpiecznego logowania dla aplikacji Notatki osobiste, która będzie służyła jako nasz osobisty notatnik online.

ZałożeniaChcemy, aby nasza aplikacja posiadała system logowania poszczególnych użyt-kowników oraz możliwość dodawania no-wych kont. Każdy posiadacz konta będzie mógł wyświetlać swoje notatki, a także do-dawać nowe. Cała komunikacja pomiędzy klientem a serwerem będzie szyfrowana.

PrzygotowaniaTworząc nasz system bezpiecznego logowania skorzystamy z gotowych implementacji algorytmów szyfrowania, zarówno tych działających po stronie serwera (w PHP), jak i klienta (w języku

JavaScript). W pierwszym przypadku, do szyfrowania za pomocą RSA posłuży nam klasa Crypt_RSA z repozytorium PEAR (pear.php.net). Po stronie klienta użyjemy natomiast implementacji algo-rytmu RSA w języku JavaScript umiesz-czonej na stronie http://ohdave.com/rsa/. Musimy z niej pobrać pliki: BigInt.js, Bar-rett.js oraz RSA.js. Wymianę danych będziemy obsługiwać przy pomocy technologii AJAX, a konkretnie projektu advAJAX (zarówno o tym projekcie, jak i o technologii AJAX pisaliśmy w nume-rze 1/2006). Pobieramy więc plik adva-jax.js ze strony http://advajax.anakin.us i zabieramy się do pracy.

Rejestracja kontObsługą procesu rejestracji nowych kont zajmą się trzy pliki:

� register.php – będzie odpowiadał za wyświetlenie formularza zakładania konta wraz z wygenerowanym wcze-śniej kluczem publicznym,Podstawy MD5

MD5 jest algorytmem haszującym, zwa-nym również jednokierunkową funkcjąskrótu. W wyniku jego działania, w oparciu o podstawione dane powstaje unikalny 128-bitowy skrót. Odtworze-nie danych na jego podstawie nie jest możliwe. Unikalność i jednokierunko-wość działania MD5 umożliwia jego za-stosowanie np. przy sprawdzaniu haseł (na serwerze zamieszczane są jedynie skróty haseł, aby uniemożliwić prze-chwycenie samych haseł w razie kra-dzieży danych), podpisywaniu plików – umieszczanym w wielu repozytoriach plikom towarzyszą skróty MD5: oblicza-jąc skrót pobranego pliku i porównując go z tym zamieszczonym na serwerze możemy sprawdzić, czy archiwum nie jest uszkodzone (to samo da się zasto-sować np. przy porównywaniu zawar-tości wypalonej płyty CD lub DVD z jej obrazem, choć trwa to dość długo).

RSA: Zasada działaniaRSA to pierwszy (wynaleziony w 1977 roku) i obecnie najpopularniejszy algorytm szyfro-wania asymetrycznego, używany powszechnie np. w handlu elektronicznym czy w celu podpisywania emaili. Zasadę działania algorytmu RSA przedstawiamy na Rysunku 1. Je-go idea (jak każdego szyfru asymetrycznego) polega na użyciu dwóch k luczy: publiczne-go i prywatnego. Oba z nich są generowane przez odbiorcę wiadomości, po czym klucz publiczny jest jawnie przesyłany nadawcy wiadomości, może też być zamieszczony np. na stronie WWW do pobrania. Nadawca szyfruje tekst za pomocą klucza publicznego i wysy-ła go odbiorcy. Zaszyfrowany tekst można z kolei odszyfrować tylko kluczem prywatnym, który posiada odbiorca. Nie wnikając w teorię matematyczną, która leży u podstaw RSA, warto zapamiętać, że klucz publiczny składa się z dwóch części: wykładnika publicznego (ang. public exponent) i modułu (ang. modulus), a klucz prywatny – z wykładnika prywat-nego (ang. private exponent) i tego samego modułu – te informacje będą nam potrzebne później, gdy będziemy korzystali z klas PEAR-owych do obsługi RSA.

Rysunek 1. Schemat działania RSA

Page 34: PHP Solutions 06 2006 PL

RSA w PHPBezpieczeństwo

www.phpsolmag.org34 PHP Solutions Nr 6/2006

� register.js – będzie w nim następo-wało szyfrowanie danych zawartych w formularzu oraz ich wysyłanie na serwer,

� register2.php – w tym pliku nastą-pi odszyfrowanie danych i założenie konta.

Generowanie kluczySpójrzmy na Listing 1, na którym za-mieściliśmy plik register.php. Zacznie-my od wygenerowania pary kluczy (publicznego i prywatnego) poprzez utworzenie obiektu $key_pair klasy Crypt_RSA_KeyPair oraz użycie jej me-tod getPublicKey() i getPrivateKey(). Oba wygenerowane klucze stanowią osobne obiekty, z których następnie wydobywamy oba wspomniane wcze-śniej (zob. Ramka RSA: zasada dzia-łania) wykładniki (getExponent()) oraz moduł (getModulus()). Następnie se-rializujemy (serialize()) i zapisujemy w sesji obiekt $key_pair do późniejszego wykorzystania oraz konwertujemy klucz publiczny (a właściwie jego wykładnik i moduł) z postaci binarnej na wartość szesnastkową (bin2hex()), ponieważ algorytm RSA, z którego będziemy ko-rzystać po stronie przeglądarki, wyma-ga podania danych dotyczących klucza właśnie w takim formacie. Po wykona-niu tych operacji przechodzimy do ge-nerowania formularza dodawania kon-ta (kod HTML). Jedynym dynamicznym elementem, który umieścimy w tym ko-dzie, będzie osadzony w prezentowa-nym pliku fragment skryptu JavaScript, który odpowiada za dołączenie plików: advajax.js, BigInt.js, Barrett.js i RSA.js oraz utworzenie zmiennych JavaScript enc_exp i modulus, przechowujących wartość wykładnika publicznego (szy-frującego) i modułu.

SzyfrowanieSpójrzmy teraz na Listing 2, na którym przedstawiamy napisany w języku Ja-vaScript obiekt updateObjects() (pa-miętajmy, że w JavaScript nie ma klas, tylko pojedyncze obiekty, definiowane analogicznie, jak funkcje). Będzie on wywoływany przy wystąpieniu zdarze-nia onload zdefiniowanego w znacz-niku <body>, czyli po każdym załado-waniu strony WWW umieszczonej po-między <body> a </body>. Wewnątrz updateObjects() tworzymy parę kluczy RSA, czyli obiekt RSAKeyPair, na pod-

Listing 1. Plik register.php – generowanie kluczy i wyswietlenie formularza

<?php

session_start();

// generowanie pary kluczy

require_once 'Crypt/RSA.php';

$key_pair = new Crypt_RSA_KeyPair(512);$public_key = $key_pair->getPublicKey();

$private_key = $key_pair->getPrivateKey();

$enc_exp = $public_key->getExponent();

$dec_exp = $private_key->getExponent();

$modulus = $public_key->getModulus();

// zapamiętanie danych do późniejszego dekodowania

$_SESSION['rsa']=serialize($key_pair);

// konwersja danych do szyfrowania na kod szesnastkowy (heksadecymalny)

$enc_exponent = bin2hex($enc_exp);

$mod = bin2hex($modulus);

?>

<html>

<head>

<title>MyNotes - registration</title>

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

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

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

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

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

<!-- skrypt obsługujący formularz za pomocą AJAX -->

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

<script type="text/javascript">

var enc_exp = "<?=$enc_exponent?>"; var modulus = "<?=$mod?>";</script>

</head>

<body onload="updateObjects()">

<b>MyNotes - Registration</b><br/>

<form method="post" action="register2.php" id="registerForm">

<label for="username">Login:

</label>

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

<label for="password">Password:

</label>

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

<label for="name">e-mail:

</label>

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

<label for="name">Name:

</label>

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

<label for="surname">Surname:

</label>

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

<input type="submit" value="Register" id="submitBtn" />

</form>

<div style="margin-top: 10px" id="response"></div>

</body>

</html>

Page 35: PHP Solutions 06 2006 PL

RSA w PHP Bezpieczeństwo

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

stawie publicznego wykładnika i modu-łu. Prywatny wykładnik nie jest nam po-trzebny, więc wpisujemy 0. Następnie wdrażamy obsługę formularza za po-mocą obiektu advAJAX. Metoda assign pobiera (jako parametr) nazwę formula-rza oraz obiekt zawierający metody je-go obsługi. Funkcja OnInitialization jest wykonywana jeszcze przed wysła-niem formularza. To w niej następuje

Listing 2. Plik register.js – szyfrowanie i wysyłanie danych

function updateObjects() { var key; key = new RSAKeyPair(enc_exp,0,modulus); function $(id) { return document.getElementById(id); } advAJAX.assign($("registerForm"), {

onInitialization : function(obj) { // szyfrowanie danych z formularza

obj.parameters["username"]=encryptedString(

key,obj.parameters["username"]);

obj.parameters["password"]=encryptedString(

key,obj.parameters["password"]);

obj.parameters["email"]=encryptedString(key,obj.parameters["email"]);

obj.parameters["name"]=encryptedString(key,obj.parameters["name"]);

obj.parameters["surname"]=encryptedString(key,obj.parameters["surname"]);

},

onSuccess : function(obj) { $("response").innerHTML=obj.responseText;

},

onError : function() { alert("Can't connect to server."); }

});

}

Listing 3. Plik register2.php – deszyfrowanie i zapisanie danych nowego usera

<?php

session_start();

require_once 'Crypt/RSA.php';

// odczytanie przechowywanego obiektu (kluczy)

$rsa_keys = unserialize($_SESSION['rsa']);

$priv = $rsa_keys->getPrivateKey();

$rsa_obj = new Crypt_RSA;// deszyfrowanie

$username=$rsa_obj->decryptBinary(hex2bin($_POST['username']),$priv);

$password=$rsa_obj->decryptBinary(hex2bin($_POST['password']),$priv);

$email = $rsa_obj->decryptBinary(hex2bin($_POST['email']),$priv);

$name = $rsa_obj->decryptBinary(hex2bin($_POST['name']),$priv);

$surname = $rsa_obj->decryptBinary(hex2bin($_POST['surname']),$priv);

// tworzenie nowego konta

$user = new user;$result = $user->register($username,$password,$email,$name,$surname);

if ($result) { echo "Your account are successfully created.";}else { echo "error: your account can't created!"; }?>

Listing 4. Funkcja hex2bin

function hex2bin($hex) { $str="";

for($i=0;$i<strlen($hex);$i=$i+2) { $str.=chr(hexdec(substr($hex,$i,2))); } return $str;}

szyfrowanie przesyłanych danych, czyli wartości wszystkich pól formularza (na-zwy użytkownika, hasła, adresu ema-il, imienia i nazwiska) za pomocą al-gorytmu RSA. Funkcja onSuccess wy-świetla wewnątrz znacznika <div> (o id=”response”) dane zwrócone przez plik register2.php, do którego wysłali-śmy wprowadzone w formularzu war-tości.

Deszyfrowanie i tworzenie kontaPrzejdźmy do omówienia zawartości wspomnianego już pliku register2.php (Listing 3). Otrzymuje on przekazane za pomocą advAJAX dane z formularza, które musimy teraz odszyfrować. Posłu-żymy się do tego zapisanym wcześniej w sesji jako rsa obiektem $key_pair, który nazwiemy $rsa_keys i zdeseria-lizujemy (unserialize()). Następnie wydobędziemy z tego obiektu klucz prywatny, który także będzie obiektem o nazwie $priv. Potem utworzymy nowy obiekt $rsa_obj klasy Crypt_RSA i wy-wołujemy jego metodę DecryptBinary() odpowiedzialną za deszyfrowanie danych (każdego z przesłanych pól for-mularza z osobna). Metoda ta przyjmuje dwa parametry: zaszyfrowany tekst oraz utworzony przez nas wcześniej klucz prywatny $priv (obiekt klasy Crypt_RSA_KeyPair). Ponieważ dane pochodzące z formularza zostały po zaszyfrowaniu przekonwertowane na system szesnast-kowy, więc musimy je przywrócić do formy binarnej przy użyciu funkcji he-x2bin(), którą napiszemy sami (Listing 4), gdyż nie została ona zaimplementowana w PHP.

Mając odszyfrowane dane użyt-kownika, możemy przystąpić do jego rejestracji w serwisie. Robimy to np. przy pomocy klasy user, której imple-mentację pominęliśmy, gdyż nie jest ona istotna dla ukazania technik kryp-tograficznych.

Kryptografia hybrydowaJako kryptografia hybrydowa rozumiane jest jednoczesne użycie kryptografii sy-metrycznej i asymetrycznej. Dotychczas szyfrowaliśmy dane tylko w jednym kie-runku: od użytkownika do serwera. Aby zrealizować nasz projekt, będziemy jed-nak potrzebowali szyfrowania dwukierun-kowego, gdyż w jedną stronę będziemy wysyłali nasz login i hasło oraz dodawali nowe notatki, a w drugą – wyświetlali ist-niejące notatki. Zabezpieczenie danych przy tych czynnościach za pomoca sa-mego RSA byłoby trudne, wykorzystamy więc dodatkowo symetryczny algorytm blowfish.

PrzygotowaniaBędziemy więc potrzebowali implemen-tacji algorytmu blowfish. W PHP posłu-żymy się do tego klasą Crypt_Blowfish z repozytorium PEAR. Po stronie prze-

Page 36: PHP Solutions 06 2006 PL

RSA w PHPBezpieczeństwo

www.phpsolmag.org36 PHP Solutions Nr 6/2006

glądarki internetowej, obsługą blowfisha zajmie się plik encrypt.js, który możemy pobrać ze strony www.farfarfar.com lub bezpośrednio z http://limak.blg.pl/crypto/encrypt.js. Cała nasza aplikacja, podob-nie jak to było w przypadku rejestracji, będzie się składała z trzech plików: index.php, index.js i index2.php, których zadanie jest analogiczne do plików z po-przedniego przykładu.

LogowanieSpójrzmy na Listing 5. Przedstawia-my na nim fragment pliku index.php – w miejscu, w którym różni się on od register.php. Dołączamy w nim też skrypt encrypt.js w języku JavaScript, który odpowiada za szyfrowanie i de-szyfrowanie algorytmem blowfish. Tym razem zagnieździmy nasz formularz wewnątrz znacznika <div>, któremu nadamy id html: później będziemy dy-namicznie zastępowali ten fragment kodu inną treścią.

Obsługa AJAXRóżnice między plikiem index.js a regi-ster.js są znacznie większe, niż pomię-dzy index.php a register.php. Na Listin-gu 6 prezentujemy obsługę praktycznie całej szyfrowanej wymiany pomiędzy plikami index.php i index2.php. Tworzy-my w nim zarówno klucz asymetrycz-ny dla algorytmu RSA, jak i losowy ciąg znaków będący kluczem symetrycznym blowfish.

Następnie korzystamy ze znanego już nam obiektu advAJAX, który wystę-puje trzykrotnie. Pierwszy raz odwołu-je się do formularza loginForm (advA-JAX.assign) i za pomocą RSA szyfru-je login, hasło oraz dodatkowo wyge-nerowany klucz blowfish, a następnie przekazuje je do pliku login2.php. Od tej pory, po otrzymaniu klucza blow-fish przez serwer, wymiana szyfrowa-nych danych odbywa się przy pomo-cy algorytmu symetrycznego. RSA było potrzebne jedynie do przesłania syme-trycznego klucza.

Gdy teraz będziemy chcieli wyświe-tlić wszystkie nasze notatki, wywoła-my funkcję goto() z parametrem note, aby w odpowiedzi z pliku index2.php otrzymać zaszyfrowany fragment ko-du odpowiedzialny za ich wyświetle-nie. To, w jaki sposób plik index2.php szyfruje dane, widzimy na Listingu 7. Aby odpowiednio zaszyfrować i odszy-

Listing 5. Formularz logowania

// ...

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

//...

<body onload="updateObjects()">

<b>MyNotes - Your own on-line notices</b><br/>

<div id="html">

<form method="post" action="index2.php" id="loginForm">

<label for="username">Login:</label>

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

<label for="password">Password:</label>

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

<input type="hidden" name="blowfish" id="blowfish" value="" />

<input type="submit" value="Login" id="submitBtn" />

</div>

Listing 6. index.js – obsługa AJAX

function updateObjects() {

// tworzenie klucza publicznego

var key; key = new RSAKeyPair(enc_exp,0,modulus); // tworzenie klucza symetrycznego

var blowfishKey = ""; var chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" var i;

for (i=0; i<25; i++) { blowfishKey += chars.charAt(Math.floor(Math.random()*62))

}

document.forms["loginForm"].elements["blowfish"].value=blowfishKey;

function $(id) { return document.getElementById(id); } advAJAX.assign($("loginForm"), {

onInitialization : function(obj) { // szyfrowanie danych z formularza (RSA))

obj.parameters["username"] = encryptedString(key, obj.parameters[

"username"]);

obj.parameters["password"] = encryptedString(key, obj.parameters[

"password"]);

obj.parameters["blowfish"] = encryptedString(key, blowfishKey);

},

onSuccess : function(obj) { // deszyfrowanie blowfish

$("html").innerHTML = secureDecrypt(obj.responseText, blowfishKey);

}

});

advAJAX.assign($("addNoteForm"), {

onInitialization : function(obj) { // szyfrowanie blowfish

obj.parameters["title"] = secureEncrypt(obj.parameters["title"],

blowfishKey);

obj.parameters["note"] = secureEncrypt(obj.parameters["note"],

blowfishKey);

},

onSuccess : function(obj) { $("html").innerHTML = obj.responseText;

}

});

}

function goto(page, pageid=0) { advAJAX.post({

url : "index2.php?page="+page+"&pageid="+pageid, onSuccess : function(obj) { $("html").innerHTML = secureDecrypt(obj.responseText, blowfishKey);

}

});

}

Page 37: PHP Solutions 06 2006 PL

RSA w PHP Bezpieczeństwo

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

frować dane, po stronie przeglądarki używamy w skrypcie JavaScript funk-cji secureEncrypt() i secureDecrypt(), natomiast w PHP wykorzystujemy PE-AR-ową klasę Crypt_Blowfish, która jest bardzo intuicyjna i prosta w uży-ciu. Z listingów możemy wywniosko-wać, że sam proces przesyłania nowej notatki w formie zaszyfrowanej (obiekt advAJAX z parametrem odwołującym się do formularza AddNoteForm) oraz jej deszyfrowanie z poziomu PHP jest praktycznie identyczny, jak w przypad-ku pobierania istniejącej notatki z ser-wera, tyle że kolejność wykonywania czynności jest odwrotna.

PodsumowanieW przedstawionej aplikacji pokazali-śmy, w jaki sposób można skorzystać z dwóch metod kryptograficznych (asy-metrycznej i symetrycznej) oraz poda-liśmy przykładowe zastosowanie tych technik. Potęga RSA w połączeniu z prostotą języka PHP jak też z całkiem niezłą funkcjonalnością JavaScrip-tu umożliwia tworzenie naprawdę bez-piecznych aplikacji, które będą odpor-ne na podsłuch i przechwytywanie da-nych.

Zachęcamy do korzystania z krypto-grafii we własnych projektach – zwłasz-cza tam, gdzie poufność przesyłanych i gromadzonych danych jest szczególnie istotna, poczynając od aplikacji do gro-madzenia prywatnych notatek i syste-mów przekazywania wiadomości osobi-stych, poprzez narzędzia używane we-wnątrz firmy (np. do zarządzania pro-jektem, danymi księgowymi czy biz-nesplanem), po dostępne dla tysię-cy użytkowników jednocześnie syste-my e-commerce, takie jak sklepy inter-netowe czy pasaże aukcyjne. Na pohy-bel intruzom! n

Kamil Karczmarczyk jest uczniem Li-ceum Ogólnokształcącego. Od kilku lat hobbystycznie zajmuje się programowa-niem, między innymi w PHP. Interesuje się bezpieczeństwem sieci, kryptografią oraz matematyką.

Kontakt z autorem:[email protected]

Listing 7. Zawartość pliku index2.php

<?php

session_start();

require_once 'Crypt/RSA.php';

require_once 'Crypt/Blowfish.php';

echo "<a href=\"javascript:goto('notes')\">my notes</a> | ";echo "<a href=\"javascript:goto('addnote')\">add note</a> | ";echo "<a href=\"javascript:goto('logout')\">logout</a><br/><br/>";

if(!isset($_GET['page'])) { // logowanie $rsa_keys = unserialize($_SESSION['rsa']);

$priv = $rsa_keys->getPrivateKey();

$rsa_obj = new Crypt_RSA; $username = $rsa_obj->decryptBinary(hex2bin($_POST['username']),$priv);

$password = $rsa_obj->decryptBinary(hex2bin($_POST['password']),$priv);

$blowfishKey = $rsa_obj->decryptBinary(hex2bin($_POST['blowfish']),$priv);

$_SESSION['username'] = $username;

$_SESSION['password'] = $password;

$_SESSION['blowfishKey'] = $blowfishKey;

$user = new user($username, $password); $notes = $user->getNotes();

$html="";

foreach ($notes as $note) { $html.="<a href=\"javascript:goto('note','".$note['id']."')\">";

$html.=$note['title']."</a><br/>";

}

$blowfish = new Crypt_Blowfish($blowfishKey); echo(base64_encode($blowfish->Encrypt($html)));}

else if ($_GET['page']=="addnote") { // wyświetlanie formularza $html = <<<heredochtml

<form method="post" action="index2.php=addnote2" id="addNoteForm">

<label for="title">title:</label>

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

<textarea name="note" id="note"></textarea><br/>

<input type="submit" value="add" id="submitBtn" />

</form>

heredochtml;

$blowfish = new Crypt_Blowfish($_SESSION['blowfishKey']); echo(base64_encode($blowfish->Encrypt($html)));} else if ($_GET['page']=="addnote2") { // dodawanie notatki $user = new user($_SESSION['username'], $_SESSION['password']);

// ustawienie klucza blowfish

$blowfish = new Crypt_Blowfish($_SESSION['blowfishKey']); //deszyfrowanie blowfish

$title = $blowfish->decrypt(base64_decode($_POST['title']));

$noteText = $blowfish->decrypt(base64_decode($_POST['note']));

//dodawanie notki

$user->addNote($title,$noteText);

echo "new note added";} else if (($_GET['page']=="note")&&(isset($_GET['pageid']))) { // wyświetlanie notki

$user = new user($_SESSION['username'], $_SESSION['password']); $note = $user->getNoteById($_GET['pageid']);

$html = "<b>".$note['title']."</b><br/>";

$html .= "<p>".$note['text']."</p>";

$blowfish = new Crypt_Blowfish($_SESSION['blowfishKey']); echo(base64_encode($blowfish->Encrypt($html)));}

?>

O autorze

Page 38: PHP Solutions 06 2006 PL

www.phpsolmag.org38

Projekty

PHP Solutions Nr 6/2006

XML_FastCreate

www.phpsolmag.org 39

Projekty

PHP Solutions Nr 6/2006

Format XML (eXtensible Markup Language) jest coraz częściej uży-wany na stronach internetowych,

gdzie zawitał przede wszystkim jako XHTML – nowy, promowany przez W3C standard zapisu stron WWW. Innym po-pularnym zastosowaniem XML-a są for-maty plików pakietów biurowych, w szcze-gólności stosowane od dawna w OpenOf-fice.org formaty SXW czy SXC oraz nowa-torskie i uznane za standard przez wiele firm instytucji ODT (tekst) czy ODG (grafi-ka). XML jest również wykorzystywany ja-ko format eksportu i importu danych wielu programów czy też komunikacji pomiędzy aplikacjami znajdującymi się na różnych maszynach (klient-serwer), np. w pro-tokołach typu SOAP czy XML-RPC. Ję-zyk PHP dysponuje dużymi możliwościa-mi tworzenia i przetwarzania dokumentów XML. W artykule pokażemy, jak użycie PEAR-owego pakietu XML_FastCreate (XFC) poszerza tę funkcjonalność, pozwa-lając m.in. na wymuszanie zgodności ge-

Technologia XML podbija świat, a PHP opiera swój sukces na wsparciu dla czołowych rozwiązań. Zastosowanie XML_FastCreate pozwala poszerzyć i tak bardzo zaawansowane możliwości PHP w dziedzinie tworzenia i manipulacji dokumentami XML...

nerowanego kodu XML z zasadami okre-ślonymi przez DTD, informowanie użyt-kownika o niezgodności, szybką konwer-sję na XHTML oraz bardzo łatwe tworze-nie rozbudowanych dokumentów, również jeśli chcemy je składać z wielu części.

InstalacjaJak już powiedzieliśmy, XFC jest pakietem należącym do repozytorium PEAR (PHP Extension and Application Repository,

XML_FastCreateGuillaume Lecanu

W SIECI

l http://pear.php.net/packages/XML_FastCreate – klasa XML_FastCreate

l http://xmlsoft.org – strona główna projektu libxml

l http://pear.php.net/packages/Cache_Lite– pakiet PEAR::Cache_Lite

l http://lya.fr/pear/XML_FastCreate/tests/ – przykła-dy użycia XML_FastCreate

Stopień trudności: lll

Co należy wiedzieć...Potrzebna będzie podstawowa znajo-mość zagadnień programowania obiekto-wego w PHP5. Przydatna będzie również ogólna wiedza na temat standardu XML.

Co obiecujemy...Pokażemy, jak za pomocą XML_Fast-Create utworzyć prawidłowy kod XML. Zademonstrujemy też, jak dokonywać transformacji znaczników XML-a, wy-muszać sprawdzanie DTD, wykrywać błędy składni czy tworzyć dokumenty w XHTML-u.

Page 39: PHP Solutions 06 2006 PL

www.phpsolmag.org38

Projekty

PHP Solutions Nr 6/2006

XML_FastCreate

www.phpsolmag.org 39

Projekty

PHP Solutions Nr 6/2006

http://pear.php.net). Aby go zainstalować, wystarczy w linii poleceń systemu ope-racyjnego użyć narzędzia pear. Jeżeli takowego nie posiadamy, jego instalacja jest bardzo prosta i została szczegółowo opisana na stronach PEAR-a.

Mając narzędzie pear jesteśmy gotowi do instalacji XFC. W tym celu wystarczy wpisać pear install XML_FastCreate. Pamiętajmy, że instalacja niektórych pa-kietów opcjonalnych XML_FastCreate, które są w wersji beta lub alpha, może wymagać użycia dodatkowego parametru narzędzia pear. Przykładowo, dla wersji beta będzie to:

pear -d preferred_state=beta

install nazwa_pakietu

Jeżeli ta składnia nie działa (co ma miejsce w przypadku starszych wersji narzędzia pear), to musimy sprawdzić na stronie pakietu, w jakiej fazie znajduje się jego najnowsza wersja (stable, alpha lub beta) i uwzględnić tę fazę, jeśli jest różna od stable, w sposób następujący:

pear install nazwa_pakietu-faza

np.:

pear install XML_Tree-beta

Wśród pakietów opcjonalnych współ-pracujących z XFC można wymienić np. XML_Tree, XML_DTD, XML_Beautifier czy XML_HTMLSax. Ich użycie jest zale-cane w celu pełnego wykorzystania możli-wości XML_FastCreate.

XFC: pierwsze krokiAby skorzystać z XML_FastCreate, do-łączamy plik XML/FastCreate.php (zob. Listing 1) i tworzymy obiekt $x, będący instancją klasy XML_FastCreate (uwaga: używamy w tym celu należącej do kla-sy XML_FastCreate metody statycznej factory()). Zainicjowanie $x wymaga po-dania dwóch parametrów: pierwszy z nich określa format wygenerowanego XML-a. My używamy formatu Text, co oznacza, że po użyciu metody toXML() (którą omó-wimy później) uzyskamy XML w formie tekstu (zserializowanej). Drugi parametr stanowi tablicę asocjacyjną opcji, którą również omówimy później.

Wspomnijmy, że jedną z najistotniej-szych spośród tych opcji jest właściwy na-główek doctype, który zostanie następnie umieszczony w pierwszej linii pliku XML i będzie określał DTD, na którym oparto dokument. Nagłówek ten możemy zdefi-niować ręcznie, ale XML_FastCreate daje nam do wyboru kilka gotowych definicji odpowiadających najczęściej używanym doctype. My użyjemy:

XML_FASTCREATE_DOCTYPE_

XHTML_1_0_STRICT

co w rezultacie daje nagłówek:

XHTML 1.0 Strict.

Stwórzmy teraz najprostszy dokument XML, który będzie stroną XHTML-ową zawierającą napis Hello World! w akapicie (<p>..</p>) oraz tekst XML_FastCreate ja-ko tytuł (tag <title>..</title>) i korzysta-

jącą z innych tagów typowych dla HTML-a i XHTML-a (<html>,<head> i <body>). Patrząc na kod z Listingu 2 zauważymy, że wykorzystane tam metody (oprócz toXML()) obiektu $x mają takie same nazwy, jak użyte przez nas tagi XHTML-a. Wstawienie każdej z tych metod do skryptu powoduje automatyczne utworze-nie znacznika o takiej samej nazwie. Jest to wielką zaletą XML_FastCreate, która ułatwia tworzenie nawet rozbudowanych dokumentów.

Zwróćmy też uwagę na zagnieżdżenie metod reprezentujących znaczniki: zaczy-namy od metody:

$x->html()

której parametrem jest:

$x->head()

która z kolei zawiera wywołania head() i body(). W ten sposób tworzymy strukturę znaczników w XML_FastCreate, która po użyciu metody toXML() zostanie przekształ-cona na gotowy dokument XML (zob. Listing 3). UWAGA: toXML() jedynie wypisuje XML na ekran; aby zapisać dokument w zmien-nej, trzeba użyć metody getXML(), np.:

$xml_out=$x->getXML();

W tym przykładzie nie określiliśmy atrybutów znaczników. Aby je dodać do określonego znacznika, wystarczy umieścić je w tablicy asocjacyjnej przekazanej jako pierwszy ar-gument metody reprezentującej ten znacz-nik. Przykładowo, aby utworzyć link (tag <a href>...</a>) prowadzący do strony głównej repozytorium PEAR, wpisujemy:

$x->a(array(‘href’=>’http://

pear.php.net’),’P.E.A.R.’)

Efektem będzie:

<a_href=”http://pear.php.net”>

P.E.A.R.</a>

Listing 1. Tworzenie instancji XML_FastCreate

<?php

require_once 'XML/FastCreate.php';

$x=&XML_FastCreate::factory('Text',array( 'doctype' =>

XML_FASTCREATE_DOCTYPE_XHTML_1_0_STRICT

)

);

?>

Listing 2. Hello World! w XML-u przy użyciu XML_FastCreate

$x->html(

$x->head(

$x->title('XML_FastCreate')), $x->body($x->p('Hello World !'))

);

$x->toXML(); Rysunek 1. Przykład błędu polegające-go na niezgodności XML-a z jego DTD

Page 40: PHP Solutions 06 2006 PL

XML_FastCreate

www.phpsolmag.org40

Projekty

PHP Solutions Nr 6/2006

Już na podstawie tych przykładów może-my się przekonać, jak łatwe i szybkie jest tworzenie XML-a przy użyciu XML_Fast-Create.

Wymuszanie i weryfikacja zgodności z DTDTworząc lub modyfikując dowolny doku-ment XML powinniśmy się upewnić, czy będzie on zgodny ze standardem DTD, do czego często używamy dodatkowych narzędzi. Użycie XML_FastCreate pozwa-la nam zaoszczędzić czas dzięki funkcji automatycznego wyświetlania błędów składni XML-a.

Aby wymusić sprawdzanie DTD, mu-simy mieć odpowiedni plik zawierający de-finicje DTD (niektóre z nich, np. XHTML, znajdują się w katalogu dtd zainstalowane-go XML_FastCreate) i uruchomić jego ła-dowanie podczas tworzenia instancji klasy XML_FastCreate. To drugie wykonamy do-dając opcję dtd do wspomnianej wcześniej tablicy asocjacyjnej (Listing 4). My załadu-jemy plik xhtml_1_0_strict.dtd, będący defi-nicją standardu XHTML 1.0 w wersji strict.

Jeżeli w składni XML-a wystąpi nie-zgodność z DTD, komunikat o błędzie zostanie wyświetlony podczas wywołania wspomnianej już metody toXML(). Po-zwala to np. na wyświetlenie go na końcu strony WWW.

Na Listingu 5 podajemy przykład skryp-tu generującego stronę XHTML-ową, w któ-rej celowo wstawiliśmy błąd polegający na pominięciu atrybutu alt w znaczniku <img src> (co było dopuszczalne w HTML-u, ale jest zabronione w XHTML-u). Po utworze-niu strony, konwertujemy ją do postaci do-kumentu XML jako zmienną $err. Następ-nie korzystając z metody statycznej PEAR::isError() sprawdzamy, czy zmienna ta zawiera komunikat o błędzie: jeżeli tak, to go wypisujemy przy pomocy metody $err->getMessage(). Przykład wykonania tego skryptu przedstawiamy na Rysunku 1.

Manipulacja dokumentami XMLPrzejdźmy teraz do omówienia rozmaitych metod generowania i manipulacji doku-mentami XML.

Jak już wiemy, użycie argumentu Text podczas tworzenia instancji klasy XML_FastCreate powoduje, że skutkiem użycia metody toXML() będzie wygenero-wanie XML-a w formie tekstowej (zseriali-zowanej). Gdybyśmy zamiast Text podali XML_Tree, utworzony zostałby obiekt klasy XML_Tree, która jest dostępna jako osobny pakiet PEAR-owy.

Co więcej, możemy złożyć dokument XML z kilku mniejszych bloków, co się często przydaje, np. gdy generujemy jego elementy korzystając z pętli czy instrukcji warunkowych. Jest to wręcz zalecane, gdyż umożliwia zachowanie przejrzysto-ści – zarówno kodu aplikacji generującej XML, jak i samego XML-a.

Bez ograniczeń możemy łączyć blo-ki wygenerowane z użyciem parametru Text podczas tworzenia instancji doku-mentu. Nie będzie również problemu, je-żeli format jest inny (np. XML_Tree), o ile wszystkie bloki są tego samego formatu. Spójrzmy na Listing 6: łączymy na nim dwa bloki typu Text, wygenerowane przy użyciu utworzonej wcześniej instancji $x klasy XML_FastCreate (nie musimy po-dawać żadnych parametrów oprócz ty-pu XML-a).

Transformacje XMLXFC posiada opcję szybkiej transforma-cji, której zadaniem jest zamiana wy-branych znaczników na inne. Przykła-

Listing 3. Rezultat XHTML przykładu z Listingu 2

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/

xhtml1/DTD/xhtml1-strict.dtd">

<html><head><title>XML_FastCreate</title></head>

<body><p>Hello World !</p></body></html>

Listing 4. Tworzymy instancję XML_FastCreate sprawdzającą zgodność XML z jego DTD

$x =& XML_FastCreate::factory('Text',

array( 'doctype' =>

XML_FASTCREATE_DOCTYPE_XHTML_1_0_STRICT,

'dtd' => 'xhtml_1_0_strict.dtd'

)

);

Listing 5. Sprawdzamy zgodność XML z jego DTD

<?php

require_once 'XML/FastCreate.php';

$x =& XML_FastCreate::factory('Text',

array( 'doctype' => XML_FASTCREATE_DOCTYPE_XHTML_1_0_STRICT,

'dtd' => 'xhtml_1_0_strict.dtd'

)

);

$x->html(

$x->head($x->title('XML_FastCreate')),

$x->body(

$x->p($x->img(array('src' => 'soleil.png')) )

)

);

if (PEAR::isError($err = $x->toXML())) { echo nl2br(htmlSpecialChars($err->getMessage()));}

?>

Listing 6. Przykład konkatenacji (łączenia) dwóch tagów

$hello = $x->p('Hello');

$world = $x->p('World');

$body = $x->body($hello,$world,);

$x->html($x->head($x->title('XML_FastCreate')),$body);Rysunek 2. Entytki umożliwiające zako-dowanie znaków specjalnych w doku-mentach XML

Page 41: PHP Solutions 06 2006 PL
Page 42: PHP Solutions 06 2006 PL

XML_FastCreate

www.phpsolmag.org42

Projekty

PHP Solutions Nr 6/2006

dowo, możemy w ten sposób uprościć sobie składnię XML-a czy XHTML-a, oznaczając tagi po swojemu, w spo-sób dostosowany do naszych prefe-rencji czy potrzeb programu, który z tych danych korzysta. Transformacja z XFC przyda się również przy konwersji z XML-a na HTML-a. Spójrzmy na Li-sting 7: przy tworzeniu instancji klasy

Listing 7. Przykład zastosowania opcji ‘translate’

<?php

require_once 'XML/FastCreate.php';

$x =& XML_FastCreate::factory('Text',

array( 'doctype' => XML_FASTCREATE_DOCTYPE_XHTML_1_0_STRICT,

'dtd' => 'xhtml_1_0_strict.dtd',

'translate' => array( 'news' => array('div'), 'desc' => array('p'), 'title' => array('<h1 class="title"><span>', '</span></h1>'), 'date' => array('<span class="date">', '</span>') )

)

);

$x->html(

$x->head($x->_title('XML_FastCreate')),

$x->body($x->news(

$x->title('News'),

$x->date('10-12-2005'),

$x->desc('blah blah blah')

)

)

);

if (PEAR::isError($err = $x->toXML())) { echo nl2br(htmlSpecialChars($err->getMessage()));}

?>

Listing 8. Wariant cytowany w Listing 7 bez opcji ‘translate’

$x->div(

$x->h1(array('class' => 'title'),$x->span('News')), $x->span(array('class' => 'date'), '10-12-2005'), $x->p('blah blah blah')

);

Listing 9. Przykład zastosowania metody cdata()

$x->style(array( 'type' => 'text/css', 'media' => 'all'),

$x->cdata("@import url('example.css');")

);

Listing 10. Tworzymy instancję XML_FastCreate sprawdzającą zgodność XML z jego DTD poprzez program zewnętrzny

$x =& XML_FastCreate::factory('Text',

array( 'doctype' =>

XML_FASTCREATE_DOCTYPE_XHTML_1_0_STRICT,

'dtd' => 'xhtml_1_0_strict.dtd',

'file' => '/tmp/XFC.xml',

'exec' => 'xmllint --valid --noout /tmp/XFC.xml 2>&1',

)

);

XML_FastCreate w tablicy asocjacyjnej wpisujemy opcję translate, która z ko-lei otwiera tablicę znaczników do tłuma-czenia.Mamy tam tagi opisujące notkę informacyjną (<news>, <desc>, <title> i <date>), które będą zamienione na oryginalne znaczniki XML-a. Zwróć-my szczególną uwagę na znacznik <title>: zostanie on przekonwertowany

na cały zestaw tagów (<h1><span>...</span></h1>).

Gdybyśmy nie korzystali z XFC w ce-lu ułatwienia konwersji znaczników, aby osiągnąć ten sam efekt co na Listingu 7 musielibyśmy ręcznie wstawić wartości do odpowiednich tagów XML-a, jak na Listin-gu 8. Wracając do Listingu 7 zauważmy, że po zdefiniowaniu tablicy transformacji tworzymy kod XML metodą tradycyjną. Tam również wystąpi znacznik <title>, tyle że nie jest on tytułem naszej wiado-mości, lecz standardowym tagiem <title> oznaczającym tytuł strony WWW i znanym z HTML-a oraz XHTML-a. Aby uniknąć je-go konwersji (która nastąpiłaby zgodnie z regułami przetwarzania znaczników naszych wiadomości), poprzedzamy jego nazwę podkreśleniem (_title). Dzięki temu generowany będzie standardowy znacznik <title>..</title>.

Opcje i metody XFCXFC zawiera kilka dodatkowych, przydat-nych metod:

� comment() – pozwala na dodawanie komentarzy w kodzie XML; ogranicz-nikami komentarza są oczywiście tagi <!-- i -->. Przykładowo, następujące użycie tej metody:

� $x->comment($x->p(‘Hello World

!’))

� spowoduje wygenerowanie kodu:� <!-- Hello World! -->.� cdata() –otacza wybraną zawartość

znacznikami CDATA. Jest to niezbędne, kiedy musimy wprowadzić zawar-tość, która niekoniecznie jest zgodna z DTD, np. kod typu JavaScript czy jakiś rodzaj importu stylów w ramach strony XHTML. Bez CDATA zwyczajnie nie da się umieścić takiej zawartości w dokumencie XML. Przykład użycia cdata() przedstawiamy na Listingu 9.

� quote() – metody tej używamy, aby przekonwertować wybrane znaki na entytki (ang. entities). Jest to koniecz-ne, gdyż umieszczenie pewnych zna-ków (np. &) w dokumentach XML jest niemożliwe i wygeneruje błąd. Metody quote() możemy używać w sposób zautomatyzowany, jeżeli zadeklaruje-my ją podczas definiowania obiektu klasy XML_FastCreate jako true. W takiej sytuacji, jeśli nie chcemy używać tej opcji wobec określonych części dokumentu, możemy wywołać opcję noquote() (Rysunek 2). UWA-

Page 43: PHP Solutions 06 2006 PL

XML_FastCreate Projekty

GA: jeżeli tworzymy dokument XHTML, wyszukiwarka Microsoft In-ternet Explorer (wersja 6 i niższe) nie rozpoznaje encji &apos;. Aby uniknąć jej konwersji, musimy umieścić opcję apos w false.

Należy też pamiętać o tym, że:

� Kod XML jest generowany bez for-matowania. Oznacza to, że wszystkie znaczniki są wklejane i zagnieżdżane bez przechodzenia do nowej linii czy stosowania tabulacji. Jeżeli chcemy uczynić kod XML bardziej czytelnym, dodajmy opcję indent do true. Takie formatowanie może jednak sprawić, że dokumenty XML będą niezgodne z DTD.

� Niektóre nowe DTD, jak na przykład XHTML 1.1, bazujące na kilku kar-totekach, jeszcze nie są tolerowane. Mimo to, za pomocą opcji file może-my wydać XFC polecenie utworzenia pliku tymczasowego i wykonania pro-gramu zewnętrznego za pomocą opcji exec, przeznaczonej do takiej analizy. Program xmllint doskonale sobie radzi z takimi problemami, a przykład jego użycia w ramach naszej aplikacji pre-zentujemy na Listingu 10.

� XFC ma kilka własnych, wbudowa-nych metod, co przy specyficznym sposobie traktowania znaczników (me-toda ma taką samą nazwę, jak znacz-nik, do którego się odnosi) powoduje problemy, jeżeli chcemy zdefiniować

znaczniki o nazwach pokrywających się z nazwami tych metod. Aby unik-nąć tego problemu i zmusić XFC do traktowania metody jako odnoszącej się do znacznika, przed nazwą tagu wstawiamy znak podkreślenia, np. _quote().

Wygodaużytkowania XFCAby uprościć tworzenie projektu korzy-stającego z XML_FastCreate, zalecane jest, aby inicjowanie instancji tej klasy odbywało się w pliku, który będziemy do-łączać do każdej ze stron. Inną opcją jest generowanie tego obiektu wewnątrz funk-cji, która będzie wywoływana na końcu każdej strony.

Pamiętajmy też, że generowanie XML-a zajmuje czas, przez co korzystanie z XML_FastCreate będzie wolniejsze, niż używanie gotowych, zapisanych na dysku (czy w bazie danych) dokumentów XML. Aby przyspieszyć dostęp do nich (tylko w przypadku plików), możemy użyć narzę-dzia keszującego, np. PEAR::Cache_Lite.

Konwersjadokumentów HTMLKonwersję dokumentu HTML na XML uła-twi załączony w pakiecie XML_FastCreate skrypt HTML2XFC.php. Za jego pomo-cą możemy przekształcić cały plik (skrypt wywołujemy wtedy w linii poleceń) albo wybrany fragment – uruchamiamy wte-dy HTML2XFC.php jako makro w wybra-nym edytorze programistycznym. W przy-

Guillaume Lecanu jest autorem pakie-tu programów XML FastCreate, rozwija-nego od przeszło 12 lat. Jego pasja pro-gramowania zaczęła się od asemblera, którego nauczył go na domowym kom-puterze brat, twórca programów typu de-mo. Niedawno Lecanu stworzył własną firmę Noovea, która prezentuje pełnię je-go możliwości.

Kontakt z autorem:[email protected]

O autorze

padku edytora vim wystarczy umieścić w ~/.vimrc linię:

map ,fc :!HTML2XFC.php<CR>

wybrać do konwersji kod HTML i urucho-mić makro wpisując ,fc.

PodsumowanieW tym artykule pokazaliśmy najciekaw-sze spośród podstawowych zastosowań XML_FastCreate. Jak widzimy, użycie tej biblioteki znacznie upraszcza tworzenie, manipulację i konwersję dokumentów XML. Nie bez znaczenia jest również to, że należy ona do repozytorium PEAR, do którego trafiają wyłącznie sprawdzone i działające projekty. Wraz z dodatkami ty-pu XML_Tree, XML_FastCreate powinna zająć miejsce wśród niezbędnych narzę-dzi każdego programisty, który korzysta z XML-a. n

R E K L A M A

Page 44: PHP Solutions 06 2006 PL

www.phpsolmag.org44 PHP Solutions Nr 6/2006

Dla zaawansowanych

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

Crosscutting concerns Dla zaawansowanych

Każdą aplikację można podzielić na logiczne warstwy. Najczę-ściej występują trzy warstwy:

warstwa danych, logiki biznesowej i prezentacji. Wszystkie spośród nich powinny być nałożone na siebie tak, aby wyższa wiedziała tylko o istnie-niu jednej warstwy niższej; wszystko, co znajduje się poniżej, powinno być dla niej niedostępne. Z drugiej strony, warstwa niższa nie wymaga dla swoje-go działania istnienia żadnej z warstw wyższych.

Podział ten ilustrujemy na Rysun-ku 1. Można jeszcze bardziej dopraco-wać ten schemat mówiąc, że aplikacja powinna posiadać aż pięć warstw: war-stwę danych, dostępu do danych, logi-ki biznesowej/usług, kontrolerów i pre-zentacji. Ten drugi podział wynika bez-pośrednio ze stosowania wzorców pro-gramistycznych i architektonicznych. Przykładowo, kontrolery i prezentacja stanowią element wzorca architekto-

W każdej aplikacji podzielonej na warstwy występują elementy, których nie można przypi-sać do żadnej z warstw i które stanowią twardy orzech do zgryzienia nawet dla dużego zespo-łu programistów. Istnieje jednak w miarę prosty sposób poradzenia sobie z nimi: użycie konte-nera IoC...

nicznego MVC. Pojawienie się warstwy dostępu do danych wynika z zastoso-wania wzorca DAO, który uniezależnia logikę biznesową od warstwy danych. Dzięki temu można łatwo wymieniać źródło danych. Schemat tego podziału pokazujemy na Rysunku 2.

Rozbicie systemu na warstwy zapew-nia szereg korzyści:

Każdej z warstw można wymie-nić implementację, nie zmieniając jed-nocześnie implementacji pozostałych warstw. Jak już wspomnieliśmy, moż-na na przykład wymienić warstwę DAO

Rozwiązywanie problemów przekrojowych z użyciem IoCPiotr Szarwas

Stopień trudności: lll

Co należy wiedzieć...Należy znać zasady programowania obiektowego w PHP5 z wykorzystaniem wzorców projektowych.

Co obiecujemy...Pokażemy, jak rozwiązać niektóre spo-śród problemów przekrojowych (ang. crosscutting concerns).

Page 45: PHP Solutions 06 2006 PL

www.phpsolmag.org44 PHP Solutions Nr 6/2006

Dla zaawansowanych

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

Crosscutting concerns Dla zaawansowanych

(dostępu do danych) tak, aby obsługi-wała inne źródło danych, nie zmienia-jąc jednocześnie logiki biznesowej.

Każda warstwa może być traktowa-na jako spójna całość. Pozwala to m.in. na tworzenie każdej warstwy przez osobny zespół programistów, który nie musi znać implementacji pozostałych warstw.

Jeśli zostanie dobrze zaprojektowa-na, każda warwstwa(warstwa) może być wykorzystywana wielokrotnie, w różnych projektach lub miejscach tego samego projektu.

Niestety, praktyka bywa czasem bardziej skomplikowana i nie wszyst-kie zagadnienia programistyczne mo-żemy umieścić na jednej wybranej warstwie. Te, których nie da się w ten sposób potraktować, nazywamy pro-blemami przekrojowymi (ang. Cros-scutting concerns). Najpopularniejsze z nich to:

l logowanie błędów i debugowanie,l bezpieczeństwo i uwierzytelnianie,

transakcje.

Problem logowania błędówRozważmy problem logowania błę-dów. Praktycznie każda aplikacja za-wiera elementy kodu odpowiadające za logowanie i debugowanie. Czy jed-nak możemy je przydzielić do którejś warstwy? Niestety nie. Ich kod jest roz-proszony po całej aplikacji i występu-je w każdej warstwie. Pamiętajmy, że z punktu widzenia logicznego podziału na warstwy, logowanie błędów nie jest ani częścią logiki biznesowej, ani innej warstwy. Bezpośrednie zakodowanie logiki logowania w każdej z warstw po-woduje, że niezależność, przenaszal-ność i elastyczność tej warstwy dra-

stycznie maleje. W przypadku logowa-nia problem nie jest tak wielki, ale już w sytuacji, gdy w aplikacji zaszyjemy reguły bezpieczeństwa, czy też zdefi-niujemy miejsca, w których rozpoczy-nają się i kończą transakcje, cały kod przestaje w zasadzie być przenaszal-ny i staje się mocno związany z regu-łami biznesowymi danej aplikacji. Ry-sunek 3 pokazuje schematycznie nasz problem.

Skoro problemy przekrojowe mogę sprawić takie trudności, czy da się je wydzielić do osobnych modułów, któ-re nie będą w ogóle powiązane z żad-ną z warstw? Okazuje się, że tak. Roz-wiązania są dwa. Pierwsze, którym się zajmiemy w tym artykule, będzie pole-gało na wykorzystaniu wzorca Deko-rator i kontenera IoC, który zbudowa-liśmy wspólnie w poprzednim artykule. Drugi sposób opiera się na zastosowa-niu programowania aspektowego (ang. aspect-oriented programming - AOP). Ten drugi sposób byłby dużo lepszym podejściem; niestety, obecnie dla PHP nie istnieje żaden framework AOP, któ-ry w wystarczającym stopniu imple-mentowałby wszystkie założenia pro-gramowania aspektowego. Tak jak po-przednio, cały kod zaprezentowany w artykule zostanie umieszczony na stro-nie http://flexi.sf.net/.

Rysunek 1. Podział aplikacji na trzy logiczne warstwy. Warstwa wyższa wie tylko o istnieniu warstwy znajdującej się bezpośrednio pod nią. Żadna z warstw niższych nie wie nic o wyższych

Listing 1. Implementacja klasy MailSender służącej do wysyłania maili. Kod klasy pozbawiony jest reguł problemów przekrojowych: logowania i bezpieczeństwa. Reguły te będą implementowane przez odpowiednie dekoratory

interface MailSender {

public function send($to, $from, $subject, $message);}

class MailSenderImpl implements MailSender { public function send($to, $from, $subject, $message) { return mail($to,$subject,$message,'From: '.$from); }

}

Listing 2. Przykładowa klasa dekorująca dowolną klasę implementującą interface MailSender

class MailSenderDecorator implements MailSender { private $mailSender;

public function __construct(MailSender $mailSender ){ $this->mailSender = $mailSender;

}

public function send($to, $from, $subject, $message) { // Kod dekoratora

$result = $this->send($to,$subject,$message,'From: '.$from);

// Kod dekoratora

return $result; }

}

Rysunek 2. Podział aplikacji na pięć logicznych warstw. Warstwa wyższa wie tylko o istnieniu warstwy znajdującej się bezpośrednio pod nią. Żadna z warstw niższych nie wie nic o wyższych

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

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

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

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

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

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

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

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

Page 46: PHP Solutions 06 2006 PL

Crosscutting concerns

www.phpsolmag.org46

Dla zaawansowanych

PHP Solutions Nr 6/2006

Wzorzec DekoratorZgodnie z definicją GoF (ang. Gang of Four – termin, który określa czwór-kę programistów odpowiedzialnych za zapoczątkowanie idei wzorców pro-jektowych), zadaniem wzorca Deko-rator jest wzbogacanie funkcjonalno-ści obiektów wybranych klas w sposób dynamiczny, bez konieczności modyfi-kowania oryginalnego kodu. Ponieważ ta definicja może być mało przejrzy-sta, posłużymy się przykładem. Zbu-dujemy prostą klasę MailSenderImpl, której zadaniem będzie wysyłanie ma-ili (Listing 1).

Wiemy, że klasa ta będzie na ty-le uniwersalna, że będziemy chcieli wykorzystać ją w kilku innych, już ist-niejących projektach. Niestety, każ-dy z tych projektów ma inne wyma-gania biznesowe związane z regułami logowania i bezpieczeństwa. Dlatego nie wyposażymy klasy MailSenderIm-pl w logikę związaną z tymi aspekta-mi, lecz zaimplementujemy tę ostatnią w dekoratorach (Listing 2). Za każdym razem, gdy w którymś z projektów za-

istnieje potrzeba wysłania maila, wy-korzystamy do tego odpowiedni deko-rator, przykładowo:

new MailSenderDecorator(

new MailSenderImpl())

Stosując wzorzec Dekorator udało się nam osiągnąć dwie rzeczy. Po pierw-sze, uniwersalna klasa MailSender pozo-stała niezmieniona – cała logika związa-na z wysyłaniem maili jest w jednym miej-scu. Po drugie, kod charakterystyczny dla każdej z aplikacji znajduje się w osobnych klasach.

Wykorzystując wzorzec Dekorator warto również pamiętać, że jest on bar-dzo użyteczny, gdy kod źródłowy klas nie jest dostępny: jego użycie stanowi wte-dy jedyną możliwość zmiany zachowa-nia obiektu.

Wzorzec Dekorator w połączeniu z kontenerem IoCWróćmy teraz do przedstawionego w po-przednim artykule kontenera IoC. Jak za-pewne pamiętamy, kontener IoC to zwy-czajnie konfigurowalna fabryka obiektów potrafiąca powołać do życia całe drzewa tych ostatnich. Pokażemy teraz, jak moż-na wykorzystać kontener IoC tak, aby po-za tworzeniem obiektów potrafił je także dekorować.

Wyobraźmy sobie następujący problem: klient, który zlecił nam pro-jekt, zażyczył sobie, aby każda ope-racja modyfikacji danych (kto, kiedy i co zmieniał) była logowana. Typowy programista zapewne rozwiązałby ten problem dokonując modyfikacji każdej metody zmieniającej dane. To podej-ście wydaje się najbardziej oczywiste, ale niestety jest najgorszym z możli-wych. Zmodyfikowane zostałyby bo-

Listing 3. Implementacja nowej wersji kontenera IoC obsługującej dekorowanie obiektów

class IoCContainerWithDecoratorSupport extends DefaultIoCContainter {

private $decoratorsSupport = array(); public function create($className) { $classObj = parent::create($className);

if (!$classObj instanceof IoCDecoratorSupport&&!$classObj instanceof IoCDecorator){

foreach( $this->getDecoratorsSupport() as $decoratorSupport ) { if ( $decoratorSupport->match($className,$classObj) ) { $decoratorObj = parent::create( $decoratorSupport->

getDecoratorName() );

$decoratorObj->setObject($classObj);

$classObj = $decoratorObj;

}

}

}

return $classObj; }

public function setDecoratorsSupport( array $decoratorsSupport ) { foreach($decoratorsSupport as $decorator) { if ( !$decorator instanceof IoCDecoratorSupport ) { throw new Exception('Class '.get_class($decorator). ' does not implement IoCDecoratorSupport interface');

}

$this->decoratorsSupport[] = $decorator;

}

}

private function getDecoratorsSupport(){ return $this->decoratorsSupport; }}

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

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

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

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

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

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

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

Rysunek 3. Problemy przekrojowe znajdują się zawsze z boku każdego podziału na warstwy, a ich kod jest rozproszony pomiędzy tymi ostatnimi

Page 47: PHP Solutions 06 2006 PL

Crosscutting concerns

www.phpsolmag.org 47

Dla zaawansowanych

PHP Solutions Nr 6/2006

wiem wszystkie kluczowe metody apli-kacji, co oznacza potrzebę przetesto-wania całego projektu od nowa. Dodat-kowym problemem byłaby konieczność przekazania do każdej z warstw aplika-cji informacji o tym, kto aktualnie wyko-nuje daną metodę. Ponadto, jeżeli mo-dyfikowany kod był wykorzystywany w innych projektach, programista musiał-

cego dane. Dlatego skonstruujemy kon-tener, który na podstawie odpowiednie-go wzorca składającego się z nazwy kla-sy oraz implementowanego przez nią in-terfejsu będzie umiał udekorować tworzo-ny obiekt.

Zabierzmy się więc do pracy. Pierwszym krokiem będzie modyfika-cja kontenera IoC tak, aby na podsta-wie zadanego wzorca mógł połączyć dekorowany obiekt z dekoratorem. W tym celu rozszerzymy klasę DefaultI-oCContainer i zmodyfikujemy jej meto-dę create(). W nowej wersji tej klasy, jej metoda create() najpierw wywołuje create() klasy nadrzędnej. Następnie sprawdza, czy powołany obiekt nie im-plementuje jednego z interfejsów klas pomocniczych dekoratorów, aby unik-nąć dekoracji tych ostatnich. Następ-nie kontener sprawdza przy pomocy klas pomocniczych, czy nazwa świeżo powołanej klasy pasuje do wzorca któ-regoś z dekoratorów. Jeżeli tak, to kla-sa pomocnicza zwraca nazwę deko-ratora. Każda klasa pomocnicza mu-si implementować interfejs IoCDecora-torSupport, który z kolei składa się z dwóch metod:

l match() – zwraca true, jeżeli wzorzec dekoratora pasuje do wzorca klasy,

l getDecoratorName() – zwraca nazwę dekoratora z pliku konfiguracyjnego IoC.

Klasy pomocnicze dostarczane są do kontenera poprzez metodę setDecora-tors(). Następnie kontener IoC powo-łuje do życia tenże dekorator i umiesz-cza w nim dekorowany obiekt. Dekora-tor musi implementować interfejs IoC-Decorator, który posiada jedną meto-dę – setObject(). Zauważmy, że w ten sposób możemy każdą z klas udekoro-wać wieloma dekoratorami. Warto jesz-cze zapamiętać, że zgodnie z imple-mentacją naszego kontenera, dekora-tor nie może być singletonem. To zna-czy: dla każdej dekorowanej klasy musi być powoływany do życia nowy deko-rator. Na Listingu 3 przedstawiamy kod nowej wersji kontenera, a na Listingach 4, 5 i 6 – kilka przykładowych klas po-mocniczych implementujących interfejs IoCDecoratorSupport. Pierwsza klasa odnajduje klasy według pełnej nazwy, druga wg wyrażenia regularnego, a trzecia wg konkretnego interfejsu, któ-ry implementuje dekorowana klasa.

Wróćmy teraz do naszego przykładu. Załóżmy dla uproszczenia, że aplikacja, w której trzeba dokonać zmian, ma trzy klasy modyfikujące dane: UserDAO, Order-DAO oraz ItemDAO. Wszystkie one imple-

Listing 4. Implementacja klasy dopasowującej dekoratory do klas na podstawie nazw klas

class ClassNameIoCDecoratorSupport implements IoCDecoratorSupport { private $decoratorName;

private $classNameToDecorator = array(); public function __construct($decoratorName,array $classNameToDecorator){ $this->decoratorName = $decoratorName;

$this->classNameToDecorator = $classNameToDecorator;

}

public function match($className,$classObject) { return (in_array($className,$this->classNameToDecorator))?true:false; }

public function getDecoratorName() { return $this->decoratorName; }}

Listing 5. Implementacja klasy dopasowującej dekoratory do klas na podstawie dowolnego wyrażenia regularnego

class RegExpIoCDecoratorSupport implements IoCDecoratorSupport { private $decoratorName;

private $pattern;

public function __construct($decoratorName,$pattern){ $this->decoratorName = $decoratorName;

$this->pattern = $pattern;

}

public function match($className,$classObject) { return (preg_match($this->pattern,get_class($classObject)))?true:false; }

public function getDecoratorName() { return $this->decoratorName; }}

Listing 6. Implementacja klasy dopasowującej dekoratory do klas na podstawie interfejsu

class InterfaceIoCDecoratorSupport implements IoCDecoratorSupport {

private $decoratorName;

private $interfaceName;

public function __construct( $decoratorName,$interfaceName){

$this->decoratorName=

$decoratorName;

$this->interfaceName=

$interfaceName;

}

public function match($className, $classObject) {

return ($classObject instanceof $this->interfaceName)?

true:false;

}

public function getDecoratorName(){

return $this->decoratorName; }

}

by przestać go współdzielić. Jak więc widać, podejście, które wydawało się najbardziej oczywiste, spowodowałoby całą lawinę problemów.

My na szczęście będziemy mądrzejsi i do rozwiązania problemu wykorzystamy kontener IoC. Nasze założenie jest nastę-pujące: nie możemy dokonać żadnej mo-dyfikacji kodu zapisującego i modyfikują-

Page 48: PHP Solutions 06 2006 PL

Crosscutting concerns

www.phpsolmag.org48

Dla zaawansowanych

PHP Solutions Nr 6/2006

mentują jeden wspólny interfejs DAO za-wierający metody setConnection(), find-ById(), save(), update(), delete(). Me-toda setConnection() ustawia połącze-nie do źródła danych, findById() zwraca obiekt na podstawie jego id, save() za-pisuje obiekt, update() go zmienia, a de-lete() kasuje. Dane są modyfikowane je-dynie przez metody save(), update() i de-lete(). Szczegóły implementacji klas *DAO nie mają znaczenia w naszym przykła-

dzie. Na Listingu 7 prezentujemy przykła-dową konfigurację kontenera bez dekora-torów. Jak widzimy, składa się ona z czte-rech wpisów, z których jeden dotyczy po-łączenia z bazą danych, a pozostałe trzy zawierają ustawienia klas DAO.

Zmodyfikujemy teraz przykład tak, aby spełniał on wymagania biznesowe naszego klienta. Nic prostszego: zada-nie rozpoczynamy od utworzenia klasy DAOLoggerDecorator. Klasa ta implemen-

tuje dwa interfejsy: IoCDecorator i DAO (Li-sting 8). Oczywiście, jej docelowa imple-mentacja powinna zawierać bardziej wy-rafinowany kod logujący. Teraz wykorzy-stując klasę z Listingu 6 modyfikujemy konfigurację kontenera. Nową konfigura-cję pokazujemy na Listingu 9. Zauważmy, że dodaliśmy tylko dwa wpisy i zgodnie z założeniami, kod klas UserDAO, Order-DAO oraz ItemDAO nie został zmieniony. W przykładzie pominęliśmy problem przeka-

Listing 7. Plik konfiguracji kontenera IoC dla opisanego w tekście przykładu bez wsparcia dekoratorów

$currentDir = dirname(__FILE__); $frameworkPath = realpath( $currentDir.'/../../flexi' );

ini_set( 'include_path', ini_get('include_path').

PATH_SEPARATOR.$frameworkPath.'/' );

require_once 'ioc/IoCContainerWithDecoratorSupport.

class.php';

require_once 'ioc/MappingBuilderFromArray.

class.php';

$iocMap = array( "connection" => array( "className" => "Connection",

"file" => $currentDir."/Connection.class.php",

"singleton" => true,

"properties" => array(), "constructorParams" => array() ),

"userDAO" => array( "className" => "UserDAO",

"file" => $currentDir."/UserDAO.class.php",

"singleton" => true,

"properties"=>array( "connection"=>"&connection"),

"constructorParams" => array() ),

"orderDAO" => array( "className" => "OrderDAO",

"file" => $currentDir."/OrderDAO.class.php",

"singleton" => true,

"properties" => array( "connection"=>"&connection"),

"constructorParams" => array() ),

"itemDAO" => array( "className" => "ItemDAO",

"file" => $currentDir."/ItemDAO.class.php",

"singleton" => true,

"properties" => array( "connection"=>"&connection"),

"constructorParams" => array() ),

);

$mappingBuilder = new MappingBuilderFromArray($iocMap);

$iocContainer=new IoCContainerWithDecoratorSupport( $mappingBuilder->getApplicationMap());

$userDAO = $iocContainer->create("userDAO");

$orderDAO = $iocContainer->create("orderDAO");

$itemDAO = $iocContainer->create("itemDAO");

var_dump($userDAO);

var_dump($orderDAO);

var_dump($itemDAO);

Listing 8. Klasa DAOLoggerDecorator jest dekoratorem klas UserDAO, OrderDAO i ItemDAO

class DAOLoggerDecorator implements IoCDecorator, DAO {

private $dao;

public function findById($id){ // Kod dekoratora

$result = $this->dao->findById($id);

// Kod dekoratora

...

return $result; }

public function save($object){ // Kod dekoratora

$result = $this->dao->save($object);

// Kod dekoratora

return $result; }

public function update($object){ // Kod dekoratora

$result = $this->dao->update($object);

// Kod dekoratora

...

return $result; }

public function delete($object){ // Kod dekoratora

$result = $this->dao->delete($object);

// Kod dekoratora

...

return $result; }

public function setConnection($connection){ // Kod dekoratora

$result = $this->dao->

setConnection($connection);

// Kod dekoratora

return $result; }

public function setObject($object){ if ( !$object instanceof DAO ){ throw new Exception('Class '.get_class($object). ' must implement DAO interface');

}

$this->dao = $object;

}

}

Page 49: PHP Solutions 06 2006 PL

Crosscutting concerns

www.phpsolmag.org 49

Dla zaawansowanych

PHP Solutions Nr 6/2006

zywania informacji o tym, kto modyfikuje dane. Gdyby kod (zupełnie inny niż w na-szym przykładzie) nie miał tak jasno zde-finiowanego interfejsu, jak nasze klasy DAO, do tworzenia dekoratorów mogliby-śmy wykorzystać metodę _ _ call(), któ-rej działanie zostało opisane w dokumen-tacji PHP.

W podobny sposób, jak reguły związane z logowaniem, moglibyśmy dodać do aplikacji kod odnoszący się do reguł bezpieczeństwa i praw dostę-pu użytkowników. Wyobraźmy sobie, że klient definiuje nowy wymóg: chce, aby modyfikacji danych mogli dokony-wać tylko administratorzy aplikacji. Mo-żemy tu zastosować podobne podej-ście, jak przy logowaniu. Utworzymy więc klasę DAOSecutiryDecorator, któ-ra przy wywoływaniu każdej metody save(), update() czy delete() będzie sprawdzała, czy wykonujący ją użyt-kownik ma prawa do jej użycia: jeżeli nie, aplikacja zgłosi wyjątek.

PodsumowanieStosowanie kontenera IoC i wzorca De-korator nie jest remedium na wszel-kie problemy przekrojowe. Aby wpro-wadzenie do kodu rzeczywistej aplikacji aspektów logowania i bezpieczeństwa było możliwe od samego początku, apli-kacja ta musi mieć jasny podział na war-stwy. Należy również ją tworzyć zgodnie z dobrymi praktykami programowania obiektowego, w szczególności kładąc nacisk na interfejsy i kompozycję obiek-tów. Warto też pamiętać, że reguły bez-pieczeństwa i logowania nie są jedynymi problemami przekrojowymi: zaliczają się do nich również transakcje, profilowanie, keszowanie (caching) czy walidacja. Na szczęście i te problemy można rozwią-zać przy pomocy kontenera IoC i odpo-wiednich dekoratorów. n

Listing 9. Nowa wersja pliku konfiguracyjnego kontenera

$currentDir = dirname(__FILE__); $frameworkPath = realpath( $currentDir.'/../../flexi' );

ini_set( 'include_path', ini_get('include_path').PATH_SEPARATOR.

$frameworkPath.'/' );

require_once 'ioc/IoCContainerWithDecoratorSupport.class.php';

require_once 'ioc/MappingBuilderFromArray.class.php';

$iocMap = array(

"connection" => array( "className" => "Connection",

"file" => $currentDir."/Connection.class.php",

"singleton" => true,

"properties" => array(), "constructorParams" => array() ),

"userDAO" => array( "className" => "UserDAO",

"file" => $currentDir."/UserDAO.class.php",

"singleton" => true,

"properties" => array("connection"=>"&connection"), "constructorParams" => array() ),

"orderDAO" => array( "className" => "OrderDAO",

"file" => $currentDir."/OrderDAO.class.php",

"singleton" => true,

"properties" => array("connection"=>"&connection"), "constructorParams" => array() ),

"itemDAO" => array( "className" => "ItemDAO",

"file" => $currentDir."/ItemDAO.class.php",

"singleton" => true,

"properties" => array("connection"=>"&connection"), "constructorParams" => array() ),

"interfaceIoCDecoratorSupport" => array( "className" => "InterfaceIoCDecoratorSupport",

"file" => $frameworkPath."/ioc/decorators/InterfaceIoCDecoratorSupport.

class.php",

"singleton" => true,

"properties" => array(), "constructorParams" => array("daoDecorator","DAO") ),

"daoDecorator" => array( "className" => "DAOLoggerDecorator",

"file" => $currentDir."/DAOLoggerDecorator.class.php",

"singleton" => false,

"properties" => array(), "constructorParams" => array() ),

);

$mappingBuilder = new MappingBuilderFromArray( $iocMap ); $iocContainer = new IoCContainerWithDecoratorSupport( $mappingBuilder->getApplicationMap() );

$iocContainer->setDecoratorsSupport( array( $iocContainer->create("interfaceIoCDecoratorSupport")));

$userDAO = $iocContainer->create("userDAO");

$orderDAO = $iocContainer->create("orderDAO");

$itemDAO = $iocContainer->create("itemDAO");

var_dump($userDAO);

var_dump($orderDAO);

var_dump($itemDAO);

Piotr Szarwas ma wieloletnie doświad-czenie w programowaniu i tworzeniu apli-kacji WWW (PHP, Java). Jest konsultan-tem w jednej z największych polskich firm IT, a także doktorantem na Wydziale Fizy-ki Politechniki Warszawskiej. Od dawna pisze artykuły dla PHP Solutions.

Kontakt z autorem:[email protected]

O autorze

Page 50: PHP Solutions 06 2006 PL

www.phpsolmag.org50 PHP Solutions Nr 6/2006

Dla zaawansowanych

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

mod_rewrite Dla zaawansowanych

Mod_Rewrite to moduł Apache'a, który jest domyślnie zainstalowa-ny na serwerze (choć nie zawsze

włączony w konfiguracji). Jego funkcją jest przepisywanie (ang. rewriting) URL-i, czy-li możliwość prezentowania plików i katalo-gów umieszczonych na witrynie przy użyciu innych nazw i ścieżek niż w rzeczywistości. Pozwala nam to m.in. na dynamiczne prze-pisywanie linków, przekazywanie do skryp-tów PHP dodatkowych zmiennych, które nie zostały podane w zewnętrznym URL-u (me-todą GET) ani przekazane metodą POST czy blokowanie zewnętrznych linków (zwa-nych też HotLinkami) do wybranych treści. Działanie Mod_Rewrite opiera się na sto-sowaniu reguł opisujących sposób przepisy-wania URL-i, które umieszczamy domyślnie w pliku .htaccess.

Czy możemy korzystać z Mod_Rewrite?Niestety, obecnie większość serwe-rów hostingowych nie wspiera modułu

Wszyscy lubimy przejrzyste i proste adresy stron WWW. Niestety, logika aplikacji PHP bywa dość skomplikowana – parę argumentów przekazywanych w URL-u wystarcza, aby skutecznie utrudnić życie użytkowników naszej witryny i obniżyć jej atrakcyjność dla wyszukiwarek. Problem ten rozwiążemy używając modułu Mod_Rewrite: dzięki niemu możemy zamienić nawet największą plątaninę linków i parametrów na czytelne i przyjazne adresy WWW.

Mod_Rewrite. Ponieważ jednak coraz więcej użytkowników oczekuje możliwo-ści korzystania z niego, jest on powoli wprowadzany jako standard. Na początek musimy się dowiedzieć, czy nasz serwer udostępnia ten moduł – możemy zapytać o to jego administratora albo sprawdzić to na własną rękę. Aby wykonać to drugie, przeprowadzimy prosty test: utworzymy plik test.php, w którym umieścimy nastę-pujący kod, po czym wykonamy go na serwerze:

Przyjazne URL-e w PHP,czyli zaprzęgamymod_rewrite do pracyMichał Gacki

W SIECI

l http://httpd.apache.org – strona główna Apache'a

l http://httpd.apache.org/docs/1.3/mod/mod_rewrite.html – oficjalna dokumentacja Mod_Rewrite

l http://phpnuke.org – strona domowa projektu PHP-Nuke

l http://www.postnuke.com/ – oficjalna strona CMS-a Po-stNuke

l http://perldoc.perl.org/perlre.html – oficjalne strony Perla o wyrażeniach regular-nych

l http://www.regular-expres-sions.info/ – witryna o wyra-żeniach regularnych

Stopień trudności: lll

Co powinieneś wiedzieć...Potrzebna będzie średniozaawansowana wiedza na temat konfiguracji i korzysta-nia z serwera Apache.

Co obiecujemy...Pokażemy, jak stosując Mod_Rewrite m.in. osiągnąć przejrzyste linki i za-bezpieczyć dostęp do plików. Przybli-żymy również podstawy wyrażeń regularnych.

Page 51: PHP Solutions 06 2006 PL

www.phpsolmag.org50 PHP Solutions Nr 6/2006

Dla zaawansowanych

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

mod_rewrite Dla zaawansowanych

<?php

phpinfo(INFO_MODULES);

?>

W tym skrypcie wywołujemy funkcję php_info(), która podaje parametry dotyczące środowiska PHP. Użycie parametru INFO_MODULES oznacza, że wyświetlone mają zostać wyłącznie informacje o zainstalo-wanych modułach (zob. Rysunek 1).

Jeżeli na liście widnieje nazwa mod_rewrite, którą zaznaczyliśmy na Rysunku 1, to mamy pewność, że Mod_Rewrite jest za-instalowany na naszym serwerze. Może się jednak zdarzyć, że funkcja nie zwróci infor-macji o modułach (zależy to od konfiguracji serwera) – aby sprawdzić obecność Mod_Rewrite, użyjemy wtedy skryptu przedsta-wionego na Listingu 1, który również umie-ścimy na serwerze pod nazwą test.php. Na-stępnie utworzymy plik .htaccess, którego zawartość pokazujemy na Listingu 2. Dzia-łanie tego tandemu jest proste: w pliku .htac-cess definiujemy przykładową regułę Mod_Rewrite, która przepisuje wszystkie odwoła-nia do pliku test.php jako test.php?tester=1, przez co do skryptu test.php przekazywana jest zmienna tester ($tester) z przypisaną wartością 1, co jest następnie sprawdzane. Jeśli nasz skrypt zwróci informację o braku Mod_Rewrite, możemy napisać do admini-stratora z prośbą o jego instalację (czy choć-by uruchomienie lokalne).

Pamiętajmy jeszcze o jednym: spraw-dzając obecność zmiennej $tester przy użyciu isset($tester) zakładamy, że w ustawieniach parsera PHP włączone są register_globals. Tymczasem, na wielu serwerach są one wyłączane ze względów bezpieczeństwa. Lepiej jest więc użyć kon-strukcji isset($_GET['tester']) w PHP5 lub isset($HTTP_GET_VARS['tester']) w PHP4. Kolejna uwaga dotyczy tworzenia pliku .htaccess w edytorze działającym pod systemem Windows w celu późniejsze-go przegrania go na serwer (np. za pomo-cą FTP). Pod tym systemem nie możemy utworzyć pliku, którego nazwa zaczyna się od kropki, co musimy obejść. W tym celu

utworzymy plik o nazwie np. test.htaccess i dopiero na serwerze zmienimy jego na-zwę na .htaccess. Gdy wgramy na serwer plik .htaccess z komendami Mod_Rewri-te, a sam moduł nie będzie działał, wpisu-jąc adres jakiegokolwiek pliku istniejącego w tym samym katalogu, co .htaccess, uj-rzymy informację o błędzie 404 (nie znale-ziono pliku).Inne funkcje katalogu nie bę-dą działać lub zobaczymy błąd 500 (In-ternal Server Error). Ten drugi zobaczymy zwłaszcza wtedy, gdy serwer zinterpretuje RewriteRule jako błąd składni .htaccess.

Jeżeli nie korzystamy z serwera hostin-gowego, tylko mamy własny, a przepisywa-nie linków nie działa, to musimy zmodyfiko-wać główny plik konfiguracyjny Apache'a noszący nazwę httpd.conf. W tym celu otworzymy go w dowolnym edytorze tekstu, odszukamy linie LoadModule

rewrite_module modules/mod_rewrite.so, ClearModuleList oraz AddModule mod_

rewrite.c i usuniemy znaki komentarza (#), od których się rozpoczynają. Należy też poszukać w tym pliku dyrektywy AccessFileName, która określa domyślną nazwę pliku mogącego przechowywać ustawienia w katalogach. Standardowo jest nią .htaccess, jeśli jednak jest inaczej, to wpisujemy .htaccess. Teraz wystarczy zre-startować Apache'a i ponownie przeprowa-dzić testy: przepisywanie powinno działać.

Mod_Rewrite: zaczynamyJeżeli jesteśmy szczęśliwymi posiadacza-mi modułu Mod_Rewrite, możemy teraz

przejść do bardziej zaawansowanych przy-kładów jego użycia. Poprzednio sprawdza-liśmy, czy zmienna przekazana do skryp-tu przy użyciu Mod_Rewrite istnieje: te-raz sprawdzimy również jej wartość (true lub false). W tym celu stworzymy plik in-dex.php, którego kod przedstawiamy na Li-stingu 3. Pamiętajmy o register_globals! Potrzebny nam też będzie plik .htaccess o następującej zawartości:

RewriteEngine On

RewriteRule ^index\.php$

index.php?menu=true [L]

Działanie tego zestawu index.php-.htac-cess jest następujące: po wpisaniu na-zwy index.php w przeglądarce WWW (al-bo przejściu do katalogu, w którym on się znajduje, jeżeli serwer automatycznie roz-poznaje indeksy), Mod_Rewrite przepi-sze index.php na index.php?menu=true, co spowoduje przesłanie zmiennej $menu do parsera PHP. Jeżeli się to uda, ujrzymy tekst informujący nas, że zmienna $menu istnieje. Wartość zmiennej $menu2 zosta-nie też zmieniona na true (zauważmy, że na początku skryptu jest ona ustawio-na na false, aby nie można jej było zmie-nić metodą GET) i zobaczymy resztę tek-stu. Ta część jest prosta, przejdźmy więc do omówienia pliku .htaccess. Po pierw-sze, aby móc tworzyć reguły Mod_Rew-rite, musimy najpierw umieścić instruk-cję RewriteEngine On, która – jak sama nazwa wskazuje – włącza przepisywa-nie. Następnie, korzystając z polecenia RewriteRule definiujemy poszczególne zasady. Jego składnia jest następująca:

RewriteRule ^adres_źródłowy$ adres

przepisany [FLAGI]

Pierwszy adres, czyli adres źródłowy, który zawsze rozpoczynamy znakiem ^, a koń-

Rysunek 1. Moduły serwera Apache w środowisku PHP

Listing 1. Sprawdzamy, czy Mod_Rewrite został zainstalowany na serwerze

<?php

if (isset($sprawdzacz)) {?> Mod_Rewrite jest zainstalowany na tym serwerze <?

}

else {?> Mod_Rewrite nie jest zainstalowany na tym serwerze lub jest błędnie

skonfigurowany <?

}

?>

Page 52: PHP Solutions 06 2006 PL

www.phpsolmag.org52 PHP Solutions Nr 6/2006

mod_rewriteDla zaawansowanych

czymy znakiem dolara ($), to oryginalna ścieżka pliku, którą chcemy przepisać ja-ko adres_przepisany. Pamiętajmy, że jeśli w którymkolwiek adresie używamy znaków specjalnych: dolara ($), kropki (.), dasz-ka (^), gwiazdki (*), plusa (+), pytajnika (?), backslasha (\) czy nawiasów klamrowych, okrągłych lub kwadratowych, to musimy je poprzedzać znakiem backslasha (\). Wyni-ka to stąd, że obie ścieżki są rozpoznawa-ne przy użyciu wyrażeń regularnych (ang. regular expressions), w których te zna-ki pełnią określone funkcje; poprzedzanie tych znaków backslashem nazywamy na-tomiast ich eskejpowaniem lub uwalnia-niem (ang. character escaping), które po-woduje, że każdy znak specjalny znajdują-cy się bezpośrednio po tym backslashu jest rozpoznawany jako zwykły znak.

Kolejna sprawa to tzw. flagi, które umieszczamy na końcu reguł. Nie użyli-śmy ich w dotychczasowych przykładach, ponieważ przydają się one wtedy, gdy korzystamy z większej ilości reguł. Najczę-ściej używane flagi to:

� NC – dzięki tej fladze reguła zadziała bez względu na wielkość liter, jakimi został napisany URL. Gdybyśmy w opisanym przykładzie dodali fla-gę NC, moglibyśmy wpisać adres w dowolny sposób, np. INDEX.PHP, inDeX.pHp, IndeX.php, itd.,

� OR – oznacza to samo, co słowo or w języku angielskim, czyli lub. Do-

dając tę flagę do reguły możemy być pewni, że jeżeli ta ostatnia nie zosta-nie wykonana, serwer spróbuje użyć reguły znajdującej się o linijkę niżej,

� L – skrót od angielskiego słowa last (ostatni). Wstawienie tej flagi powo-duje zatrzymanie sprawdzania kolej-nych reguł w .htaccess, jeżeli reguła ją zawierająca jest poprawna i zosta-nie użyta.

� R - skrót od redirect (po ang. prze-kierowanie). Dodanie tej flagi powo-duje przekierowanie adresu na adres przepisany. Gdybyśmy wstawili tę flagę w naszym drugim przykładzie, w pasku adresowym przeglądarki dopisany zostałby ciąg ?menu=true, co w tej sytuacji nie byłoby pożądane. Stosując flagę R możemy też wska-zać typ przekierowania, np. słynne przekierowanie 301 (Redirect 301) zapiszemy jako R=301.

Możemy dodać kilka flag jednocześnie – wystarczy je wymienić po przecinku między nawiasami kwadratowymi na koń-cu reguły. Przykład: [L,NC,OR].

W naszym przypadku ścieżka index.php zostanie przepisana na in-dex.php?menu=true. Rozwiązanie prze-kazywania zmiennych jest przydatne, gdy chcemy bez modyfikacji plików PHP przesłać dodatkowe wartości. Przykłado-wo, jeśli chcemy dodać promocję w na-szym sklepie internetowym, który korzysta

z plików (a nie z bazy danych), nie musi-my modyfikować za każdym razem tych ostatnich - wystarczy raz umieścić odpo-wiedni warunek w skrypcie PHP i wgrać plik .htaccess na serwer. Po zakończeniu promocji wystarczy usunąć .htaccess albo zmienić jego nazwę, np. na promo-cja.htaccess. Przy takich rozwiązaniach należy jednak pamiętać, że jeżeli ktoś wie, od jakiej zmiennej zależy warunek, może ją dopisać do adresu URL i tym sposobem obejrzeć ukrytą część strony!

Aby sprecyzować zakres działania na-szego pliku .htaccess, możemy pod linijką zawierającą komendę RewriteEngine On wstawić polecenie RewriteBase, które określa relatywną ścieżkę do aktualnego katalogu. Jej składnia jest następująca:

RewriteBase ścieżka

Jeśli chcemy, aby nasze reguły miały zasięg globalny, nie musimy wstawiać RewriteBase do pliku .htaccess.

Migracja domen przy użyciu Mod_RewriteCzęsto się zdarza, że zmieniamy dome-nę naszego serwisu i chcemy zachować starą. Przykładowo, możemy przenosić ją z darmowej na płatną (np. .com), na-tomiast likwidacja starej domeny ozna-czałaby dla nas utratę tysięcy linków do nas zamieszczonych na innych witrynach oraz pozycji w wyszukiwarkach, które zindeksowały wiele spośród naszych podstron ze starym adresem i związane z nimi słowa kluczowe. Na pierwszy rzut oka, rozwiązanie tego problemu migracji wydaje się proste – wystarczy podpiąć dwie domeny pod to samo konto. Wiążą się z tym jednak kolejne trudności, np. wyszukiwarka Google widzi takie domeny jako tzw. double content, czyli dwie witryny z identyczną treścią. Tu również pomoże nam Mod_Rewrite: w katalogu głównym starej domeny umieścimy plik .htaccess o następującej zawartości:

RewriteEngine On

RewriteCond %{HTTP_HOST} staradomena.com

RewriteRule ^(.*)$

http://www.nowadomena.com/$1 [R=301,L]

Jak widać, w kodzie pojawia się nowe po-lecenie RewriteCond. Określa ono zakres wykonywania dalszych reguł. W tym przy-padku ustala warunek, że reguły będą in-terpretowane tylko wtedy, gdy wejście na-

Listing 2. Plik .htaccess, którego używamy wraz z test.php, aby sprawdzić obecność Mod_Rewrite

Options +FollowSymLinks

<IfModule mod_rewrite.c>

RewriteEngine On

RewriteRule ^(test.php)$ test.php?tester=1 [QSA]

RewriteRule ^$ test.php?tester=1 [QSA]

</IfModule>

Listing 3. Sprawdzamy, jaką wartość ma zmienna $menu

<?

$menu2 = false;if (isset($menu) && $menu == true) { ?> <br /><b>Zmienna $menu istnieje, choć nie została podana w URL-u</b> <?

$menu2 = true;}

?>

<br />

<? if ($menu2 == true) { ?> Dzięki poprzedniej zmiennej wartość zmiennej $menu2 została zmieniona na

true, dlatego ten tekst jest widoczny.

<?

}

?>

Page 53: PHP Solutions 06 2006 PL
Page 54: PHP Solutions 06 2006 PL

www.phpsolmag.org54 PHP Solutions Nr 6/2006

mod_rewriteDla zaawansowanych

stąpi z adresu staradomena.com. Składnia komendy RewriteCond jest następująca:

RewriteCond %{WARUNEK} adres

Jedyna zdefiniowana przez nas tym razem reguła przepisuje wszystkie stare adresy na http://www.nowadomena.pl/[adres]. Do rozpoznawania wszystkich znaków służy wyrażenie regularne (.*) – kropka symbolizuje dowolny znak, a * – dowolną ilość znaków.

Użyte przez nas przekierowanie przyużyciu RewriteRule i RewriteCond jestznacznie lepsze od tradycyjnego Redirect301 / http://www.nowadomena.com, ponieważ to drugie przekierowuje użytkow-nika od razu na nową domenę niezależnie od tego, czy wpisał adres starej, czy no-wej. Co więcej, to stosując przytoczoną instrukcję Redirect301 (którą też umiesz-czamy w pliku .htaccess) można łatwo zapętlić serwer. Umieszczenie instrukcji Redirect301 przekierowującej ze starego adresu na serwerze, pod który podpięte są dwie domeny (staradomena.com i nowado-mena.com), grozi tym, że serwer będzie nas przekierowywał w nieskończoność, gdyż nie będzie wiedział, która domena jest stara, a która nowa – obie będą czytały ten sam plik .htaccess. Dzięki zastosowa-niu przedstawionego zestawu składają-cego się z przekierowania warunkowego RewriteCond i reguły RewriteRule tego unikniemy: serwer nie będzie miał proble-mu z ustaleniem, która domena jest nowa, a która stara, gdyż przekierowanie podziała tylko, jeżeli wejdziemy ze starej.

Skuteczna blokada HotLinkówChyba każdy, kto zamieszcza jakiekolwiek pliki do pobrania na stronie WWW, wie, co znaczy termin HotLink: oznacza on podpi-nanie się pod wybrane pliki (np. MP3 czy PNG) z innego serwisu. W praktyce wy-gląda to tak, że webmaster kupuje serwer, na którym umieszcza pliki, które z kolei są przeznaczone do pobrania z jego strony, a osoba prowadząca inny serwis po prostu podpina te same linki u siebie na stronie, czego następstwem jest wzrost transferu na koncie webmastera, który wykupił ser-wer. Powoduje to zwiększenie obciążenia łączy prowadzących do tego serwera; co więcej, niektóre firmy hostingowe ustalają górne limity miesięczne transferu z konta, po przekroczeniu których strona jest za-wieszana.

Aby się bronić przed tym zjawiskiem, należy zastosować blokadę HotLinków. Dotychczas opracowano i wdrożono wiele tego rodzaju blokad działających na pozio-mie PHP. Jednymi z najskuteczniejszych były te, które sprawdzały serwer pole-cający (tzn. serwer, z którego nastąpiło kliknięcie na link) i porównywały jego adres z adresem witryny, której właściciel umie-ścił swoje pliki oraz używały przepisywania linków w celu ukrycia plików do pobrania, zarazem uruchamiając skrypt. Niestety, blokady te miały jedną wadę – jeżeli ktoś znał prawdziwe ścieżki do plików, skrypt się nie uruchamiał i plik można było spo-kojnie pobrać.

Tu również z pomocą przychodzi Mod_Rewrite, pozwalając na udoskonale-nie opisanej metody. Wystarczy stworzyć plik .htaccess, który następnie umiesz-czamy w katalogu przeznaczonym do zabezpieczenia:

RewriteEngine On

RewriteCond %{HTTP_REFERER} !^http://

(.+\.)?strona\.com [NC]

RewriteRule ^.*\.(zip|exe|rar|gz|

tar.gz|tar)$ http://www.strona.com/

HotLink.html [L]

Dzięki poznanej wcześniej komendzie RewriteCond i wyrażeniom regularnym możemy w prosty sposób ustalić warunek wykonywania reguł dla serwerów polecają-

cych: następne reguły (które przekierowują wszystkie HotLinki na stronę informującą, że jest to zabronione) będą wykonywane tylko wtedy, jeżeli sprawdzany przez HTTP_REFERER adres serwera polecającego nie jest naszym adresem. Problem mogący się pojawiać przy użyciu przedrostka www rozwiązuje zastosowanie wyrażenia regu-larnego (.+\.)?, dzięki któremu nieważne jest, czy w adresie użyto tego prefiksu, czy nie. Zauważmy, że przed naszym adresem (strona.com) umieściliśmy znak wykrzykni-ka: w języku wyrażeń regularnych oznacza on przeczenie.

Umieszczona w następnej linijce re-guła sprawdza po rozszerzeniu pliku, czy wolno do niego linkować. Jeżeli rozsze-rzenie to (opisane wyrażeniem regularnym .*\., czyli dowolny_znak.znaki_po_kropce; zwróćmy uwagę na eskejpowanie znaku kropki) należy do grupy zabronionych (u nas zip, exe, rar, gz, tar.gz i tar), to link do pliku nie zadziała: użytkownik, który na niego kliknie, zostanie przekierowany na stronę HotLink.html.

Opisane rozwiązanie jest bardzo przy-datne, ale ma jedną wadę: gdy wpiszemy adres pliku w przeglądarce (otrzymany np. w mailu czy komunikatorze), blokada HotLink zadziała i nie będziemy mogli pobrać pliku. A przecież ideą blokowania HotLinków nie jest utrudnianie życia zwy-kłym użytkownikom, tylko właścicielom serwisów obciążających nasze łącza! Aby

Wyrażenia regularneW regułach RewriteRule stosowaliśmy wyrażenia regularne (ang. regular expressions). Mówiąc najprościej, są to wzorce w postaci łańcuchów symboli, których używamy w ce-lu zaawansowanego wyszukiwania wybranych fraz w tekście (w przypadku RewriteRule przeszukiwane są nazwy plików i katalogów). Wyrażenia te odnajdują tekst pasujący do wzorca. Ze względu na elastyczność, wyrażenia regularne stanowią wręcz standard wśród metod przeszukiwania tekstu. Warto wiedzieć, że dzielą się na składnię uniksową i perlo-wą (znaną jako PCRE czyli Perl Compatible Regular Expressions i stosowaną nie tylko w Perlu, ale także w PHP, Ruby i wielu innych językach, które korzystają z utworzonej w C biblioteki PCRE). Bez znajomości choćby podstaw wyrażeń regularnych ciężko mówić o korzystaniu z Mod_Rewrite. Oto kilka znaczników używanych w wyrażeniach regularnych, które ułatwią nam dalszą pracę z tym modułem:

� kropka (.) – dowolny znak,� tekst1|tekst2 – alternatywa: tekst1 lub tekst2,� daszek (̂ ) – rozpoczyna daną strefę znaków,� dolar ($) – kończy daną strefę znaków,� pytajnik (?) – zero lub jeden znak poprzedzający wyrażenie,� gwiazdka (*) – zero lub więcej znaków poprzedzających wyrażenie,� plus (+) – jeden lub więcej znaków poprzedzających wyrażenie,� (tekst _ w _ nawiasach) – grupuje tekst tekst _ w _ nawiasach.

Aby podstawić w adresie docelowym znalezione nazwy, które pasują do wyrażenia uży-tego w regule wobec adresu źródłowego, używamy oznaczającego rezultat wyszukiwa-nia dolara ($), bezpośrednio po którym stawiamy numer wyrażenia (licząc od pierwsze-go pozwalającego na wstawienie tekstu), czyli np. $1, $2 i $3. Numery rezultatów szuka-nia są przyporządkowane kolejnym wyrażeniom (zawartym w nawiasach), np. wyrażeniu ^([^-]+)/([^-]+)/([^-]+)$ zostaną przypisane $1, $2 i $3.

Page 55: PHP Solutions 06 2006 PL

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

mod_rewrite Dla zaawansowanych

zaradzić temu problemowi, możemy prze-robić kod na następujący:

RewriteEngine On

RewriteCond %{HTTP_REFERER} !^http://

(.+\.)?strona\.com [NC]

RewriteRule^ .*\.(zip|exe|rar|gz|tar.gz

|tar)$

http://www.strona.com/index.php?page=down

load&file=$1 [L]

Spowoduje on przekierowanie użytkow-nika pod adres: index.php?page=downlo-ad&file=nazwa_pliku pod którym zamiesz-czamy skrypt PHP służący do ściągania plików (nie podajemy go w artykule, gdyż może być to dowolny skrypt tego rodzaju dostępny w Internecie albo napisany przez nas). Użytkownik zobaczy komunikat zawierający link do pliku i będzie musiał go pobrać z naszej strony, więc blokada HotLinków mu w tym nie przeszkodzi. Oczywiście użyty w tym przykładzie adres jest testowy i należy go przystosować do własnego serwisu. Opisaną blokadę Hot-Linków można oczywiście wykorzystać także w przypadku plików graficznych, z czego korzystają zazwyczaj firmy udo-stępniające darmowy hosting.

Przepisywanieadresów URL(tzw. przyjazne linki)Mod_Rewrite pozwala także przepisywać linki. Niejedna osoba zdziwiła się pewnie widząc na dynamicznej stronie WWW ad-

resy typu gallery-photo-1.html, które tak naprawdę maskują właściwe URL-e (np. index.php?module=gallery&function=pho-to&id=351). Jak wiadomo, w dzisiejszych czasach dla sukcesu strony bardzo waż-na jest pozycja w wyszukiwarkach typuGoogle. Link zaczynający się od in-dex.php z ustawieniem wielu zmiennych nie jest przyjazny ani dla użytkownika, ani dla wyszukiwarki, ponieważ zawiera zna-ki, których te ostatnie nie lubią, a miano-wicie pytajnik i ampersand (&). Powoduje to, że strony zawierające takie adresy URL nie są w ogóle indeksowane lub są indeksowane bardzo powoli. Co więcej, systemy CMS (ang. Content Manage-ment Systems, czyli systemy zarządzania treścią) często korzystają z przekazywa-nia identyfikatora sesji w adresach URL (przesyłania tego ID przez cookies wy-szukiwarki i przeglądarki często w ogóle nie akceptują), przez co adres może wy-glądać niezbyt zachęcająco, np.:

index.php?module=gallery&function=photo&id=35&PHPSESSID=accfcc299077b36817dc534c90588253

Dzięki Mod_Rewrite możemy uczynić ad-res naszej strony bardziej atrakcyjnym dla wyszukiwarek (i tych użytkowników, któ-rzy lubią zapisywać bądź zapamiętywać URL-e) oraz usunąć doklejanie identyfika-tora sesji do linków.

Zacznijmy od tego, że nie ma gotowe-go kodu, który będzie działał na wszyst-kich stronach; posłużymy się więc praktycz-nym przykładem testowym, który trzeba bę-dzie przystosować do każdej strony. Załóż-my zatem, że chcemy zmienić zwykłe linki index.php?module=wartość1&function=war-tość2&id=wartość3 na przepisane adresy w stylu katalogowym (wartość1/wartość2/wartość3). Przykładowo, mając galerię zdjęć chcemy, żeby wpisując adres typu http://www.mojadomena.com/gallery/photo/21, użytkownik był kierowany na stronę http://www.mojadomena.pl/index.php?module=gallery&function=photo&id=21 nawet o tym nie wiedząc (w przeglądarce będzie cały czas widniał przyjazny URL). Nie chcemy i nie będziemy przy tym tworzyć katalogów o nazwach gallery, photo czy 21. Mod_Rewri-te pozwala zarówno na udawanie nazw pli-ków, jak i katalogów – stwórzmy plik .htac-cess o następującej zawartości:

RewriteEngine On

RewriteRule ^([^-]+)/([^-]+)/([^-]+)$

index.php?module=$1&function=$2&id=

$3 [L,NC,NS]

W powyższej regule użyliśmy trzech flag, oznaczających m.in. to, że wielkość li-ter w adresach nie będzie miała znacze-nia (flaga NC), a przepisywanie nie zakoń-czy się na tej jednej regule (flaga L), po-nieważ jest to dopiero początek nasze-go większego pliku .htaccess. Tak więc, poprawny będzie zarówno adres http:

Listing 4. Poprawny zestaw reguł do tworzenia przyjaznych URL-i w przykładowej galerii zdjęć

RewriteEngine On

RewriteRule ^([^-]+)/([^-]+)/([^-]+)/$ index.php?module=$1&function=$2&id=

$3 [L,NC,NS]

RewriteRule ^([^-]+)/([^-]+)/$ index.php?module=$1&function=$2 [L,NC,NS]

RewriteRule ^([^-]+)/$ index.php?module=$1 [L,NC,NS]

RewriteRule ^([^-]+)/([^-]+)/([^-]+)$ index.php?module=$1&function=$2&id=

$3 [L,NC,NS]

RewriteRule ^([^-]+)/([^-]+)$ index.php?module=$1&function=$2 [L,NC,NS]

RewriteRule ^([^-]+)$ index.php?module=$1 [L,NC,NS,QSA]

Tabela 1. Przepisywanie adresów zaczynających się od słowa gallery

Adres Adres prawdziwygallery/wartość2/wartość3lub gallery/wartość2/wartość3/

index.php?module=gallery&function=warto-ść2&id=wartość3

gallery/wartość2 lub gallery/wartość2/ index.php?module=gallery&function=wartość2

gallery lub gallery/ index.php?module=gallery

Tabela 2. Przepisywanie adresów zaczynających się od słowa news

Adres Adres prawdziwynews/wartość2/wartosć3lub news/wartość2/wartość3/

index.php?module=news&action=warto-ść2&id=wartość3

news/wartość2 lub news/wartość2/ index.php?module=news&action=wartość2

news lub news/ index.php?module=news

Tabela 3. Przepisywanie adresów zaczynających się od słowa, którym nie jest ani gallery, ani news

Adres Adres prawdziwywartość1/wartość2/wartość3lub wartość1/wartość2/wartość3/

index.php?module=wartość1&option=warto-ść2&id=wartość3

wartość1/wartość2lub wartość1/wartość2/

index.php?module=wartość1&option=war-tość2

wartość1 lub wartość1/ index.php?option=wartość1

Page 56: PHP Solutions 06 2006 PL

www.phpsolmag.org56 PHP Solutions Nr 6/2006

mod_rewriteDla zaawansowanych

//www.mojadomena.pl/gallery/photo/21, jak np. http://www.mojadomena.pl/galLeRy/phOtO/21. Natomiast wyrażenie regularne ([^-]+) pozwala na wstawienie dowolne-go słowa.

Wszystko działa, ale czy na pewno? Co będzie, jeśli w adresie wpiszemy tylko gallery/photo/, bez podawania numeru zdjęcia? Ujrzymy wtedy komunikat o braku katalogu, ponieważ przedstawiona reguła opisuje tylko trzypoziomowe adresy URL. Kod, który rozwiąże ten problem, będzie wyglądał jak na Listingu 4.

W tym przypadku ważna jest hierarchia reguł: reguły muszą być ułożone w takiej kolejności, aby sobie nawzajem nie prze-szkadzały. Przykładowo, gdyby pierwsza z nich była jednopoziomowa, to po wpisa-niu gallery/photo/21 adres wskazywałby na index.php?module=gallery/photo/21, z cze-go wynika, że w pierwszej kolejności powin-niśmy definiować reguły o największej licz-bie poziomów. Zwróćmy też uwagę na flagę QSA w ostatniej regule, która by bez niej nie działała, gdyżwyrażenie regularne ([^-]+) nie może występować samodzielnie, jeżeli nie zdefiniowano RewriteBase.

Można wykorzystać nasz zestaw reguł nieco inaczej, np. niepodanie ID zdjęcia może prowadzić do listy wszystkich obraz-ków w galerii (w galeriach o większej licz-bie poziomów może kierować np. na listę zdjęć o określonej tematyce) lub przekie-rować pod sam adres /gallery. Pozwala to niesamowicie uelastycznić funkcjonalność naszego serwisu.

Zauważmy też, że przy trzech pozio-mach zdefiniowaliśmy 6 reguł, podczas gdy wystarczy po jednej zasadzie na każdy z nich. Przyjrzyjmy się dokładnie trzem pierwszym regułom i sprawdźmy, czym różnią się od kolejnych trzech (nie licząc QSA w ostatnim wersie). W pierw-szej trójce na końcu ostatniego wyrażenia występuje znak slash (/), a w drugiej nie – dzięki temu poprawne będą zarówno adresy, które się na niego kończą (np. http://www.mojadomena.pl/gallery/photo/21/), jak i te, które nie mają takiego za-kończenia (np. http://www.mojadomena.pl/gallery/photo/21).

Nic nie stoi na przeszkodzie, aby zachować taki sam schemat linków (np. wartość1/wartość2/wartość3) przy róż-nych parametrach (np. gallery/wartość1/wartość2 kontra news/wartość2/wartość3).

Spójrzmy na Listing 5. Przedstawiona na nim zawartość pliku .htaccess pozwala na zachowanie wspomnianego schematu

wartość1/wartość2/wartość3 bez względu na to, jakie zmienne mają zostać przeka-zane. Jak widzimy, najwyżej w hierarchii reguł znajduje się zestaw zasad dla galerii. Jest on skonstruowany tak, że działa jedy-nie wtedy, gdy pierwszy pseudokatalog (w tym przypadku wartość1) wskazuje na sło-wo gallery. W tym przypadku Mod_Rew-rite przepisuje nam adresy wg schematu z Tabeli 1. Drugi zestaw działa tylko wtedy, gdy pierwszy pseudokatalog wskazuje na słowo news. W tym przypadku adresy wy-glądają jak w Tabeli 2.

Jak widać, zamiast zmiennej function przekazywana jest zmienna action, ale oczywiście można także zamiast zmien-nych module i id przekazywać cokolwiek innego – wszystko zależy od konstrukcji strony.

Dodatkowo, jeżeli pierwszym słowem w adresie nie jest ani gallery, ani news, to adresy są przepisywane w sposób przed-stawiony w Tabeli 3.

W tym przypadku w adresie wyróżnia się zmienna option. Wspomnijmy tu jeszcze raz o całej hierarchii reguł. Gdybyśmy przed zestawami news i gallery wstawili ostatni ze-staw z tego pliku, wszystkie adresy byłyby mu podporządkowane, czyli przekazywa-na byłaby zawsze zmienna option jako dru-gi parametr – tylko dlatego, że np. adresy gallery/photo/341 i news/read/53 pasują do tego schematu i byłyby z nim porównywa-ne na samym początku. Jednak gdy sche-mat ten jest na końcu pliku, adresy porów-nywane są najpierw z pierwszymi zestawa-mi, a później dopiero w przypadku niepowo-dzenia z ostatnim.

Listing 5. Zachowujemy schemat linków przy różnorodnych parametrach

RewriteEngine On

RewriteRule ^gallery/([^-]+)/([^-]+)/$ index.php?module=gallery&function=$1&id=

$2 [L,NC,NS]

RewriteRule ^gallery/([^-]+)/$ index.php?module=gallery&function=$1 [L,NC,NS]

RewriteRule ^gallery/$ index.php?module=gallery [L,NC,NS]

RewriteRule ^gallery/([^-]+)/([^-]+)$ index.php?module=gallery&function=$1&id=

$2 [L,NC,NS]

RewriteRule ^gallery/([^-]+)$ index.php?module=gallery&function=$1 [L,NC,NS]

RewriteRule ^gallery$ index.php?module=gallery [L,NC,NS]

RewriteRule ^news/([^-]+)/([^-]+)/$ index.php?module=news&action=$1&id=

$2 [L,NC,NS]

RewriteRule ^news/([^-]+)/$ index.php?module=news&action=$1 [L,NC,NS]

RewriteRule ^news/$ index.php?module=news [L,NC,NS]

RewriteRule ^news/([^-]+)/([^-]+)$ index.php?module=news&action=$1&id=

$2 [L,NC,NS]

RewriteRule ^news/([^-]+)$ index.php?module=news&action=$1 [L,NC,NS]

RewriteRule ^news$ index.php?modul=news [L,NC,NS]

RewriteRule ^([^-]+)/([^-]+)/([^-]+)/$ index.php?module=$1&option=$2&id=

$3 [L,NC,NS]

RewriteRule ^([^-]+)/([^-]+)/$ index.php?module=$1&option=$2 [L,NC,NS]

RewriteRule ^([^-]+)/$ index.php?module=$1 [L,NC,NS]

RewriteRule ^([^-]+)/([^-]+)/([^-]+)$ index.php?module=$1&option=$2&id=

$3 [L,NC,NS]

RewriteRule ^([^-]+)/([^-]+)$ index.php?module=$1&option=$2 [L,NC,NS]

RewriteRule ^([^-]+)$ index.php?module=$1 [L,NC,NS,QSA]

Listing 6. Zestaw reguł umożliwiający stosowanie adresów udających pliki html (np. gallery-photo-21.html)

RewriteEngine On

RewriteRule ^gallery-([^-]+)-([^-]+)\.html$ index.php?module=gallery&function=

$1&id=$2 [L,NC,NS]

RewriteRule ^gallery-([^-]+)\.html$ index.php?module=gallery&function=

$1 [L,NC,NS]

RewriteRule ^gallery\.html$ index.php?module=gallery [L,NC,NS]

RewriteRule ^news-([^-]+)-([^-]+)\.html$ index.php?module=news&action=$1&id=

$2 [L,NC,NS]

RewriteRule ^news-([^-]+)\.html$ index.php?module=news&action=$1 [L,NC,NS]

RewriteRule ^news\.html$ index.php?module=news [L,NC,NS]

RewriteRule ^([^-]+)-([^-]+)-([^-]+)\.html$ index.php?modul=$1&opcja=$2&id=

$3 [L,NC,NS]

RewriteRule ^([^-]+)-([^-]+)\.html$ index.php?module=$1&option=$2 [L,NC,NS]

RewriteRule ^([^-]+)\.html$ index.php?module=$1 [L,NC,NS]

Page 57: PHP Solutions 06 2006 PL

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

mod_rewrite Dla zaawansowanych

Adresy przepisywane wcale jednak nie muszą wyglądać jak katalogi. Można tak wykorzystać Mod_Rewrite, aby adresy były maskowane w postaci plików HTML, co wygląda jeszcze bardiej efektownie. Ogólnie wyglądałoby to tak, że schemat wartość1/wartość2/wartość3 wyglądałby np. następująco: wartość1-wartość2-war-tość3.html, np. gallery-photo-21.html. Aby uzyskać taki efekt, należy przebudować poprzedni zestaw reguł w pliku .htaccess, co już nie będzie trudne (Listing 6).

Jak widzimy, reguł jest teraz mniej, po-nieważ w poprzednim przykładzie występo-wały dodatkowe zasady, dzięki którym adres mógł się kończyć na slashu (/), ale nie mu-siał. Tym razem slashe nie są używane: zo-stały zastąpione myślnikami, a na końcu do-dajemy sufix .html, udający rozszerzenie pli-ku. Jak już wspomnieliśmy, przed znakami specjalnymi należy stawiać znak backslash (\), dlatego widnieje on przed kropką. Dzię-ki plikowi .htaccess z Listingu 6, adresy URL wyglądają jak w Tabelach 4, 5 i 6. W rezulta-cie wszystko działa tak, jak w przypadku ad-resów w stylu katalogowym.

Pozbywanie się identyfikatora sesjiJak już wspomnieliśmy, nawet najlepiej zbudowane adresy URL nic nie dadzą (w sensie pozycjonowania stron w wy-szukiwarkach), jeżeli do linków dokleja-ny będzie identyfikator sesji. W rezulta-cie nasze adresy, zamiast wyglądać jak gallery/photo/21 czy gallery-photo-21.html, będą wyglądały jak:

gallery/photo/21?PHPSESSID=

accfcc299077b36817dc534c90588253

czy też:

gallery-photo-21.html?PHPSESSID=

accfcc299077b36817dc534c90588253

Wyszukiwarka znajduje w nich pytajnik, znak równości oraz bezsensowny (dla wy-szukiwarki) ciąg znaków przedłużających URL-a. Problem ten oczywiście występu-je tylko i wyłącznie wtedy, gdy dany serwis korzysta z funkcji obsługi sesji w PHP. Aby wyszukiwarki nie doklejały numeru sesji do linków, należy w pliku .htaccess, za-raz pod RewriteEngine On, dodać nastę-pujące linijki:

Options FollowSymLinks

php_flag session.use_trans_sid off

Jeżeli zamiast strony zobaczymy błąd 500 (Internal Server Error), to znaczy, że opcja use_trans_sid jest ustawiona globalnie na on w konfiguracji serwera i nie możemy jej lokalnie zmienić. Pozostaje nam wtedy po-prosić administratora, aby ustawił tę opcję lokalnie na off.

Praktyczne zastosowaniaMod_Rewrite w systemach CMSW praktyce, Mod_Rewrite ma wiele zasto-sowań w systemach typu CMS. Wymieni-my i opiszemy najpopularniejsze z nich.

Tytuły podstronlub artykułów w adresach URLChoc przyjazne linki nie są kluczem do wielkiego sukcesu w wyszukiwarkach, mają duży wpływ na pozycję strony. Aby dodat-kowo umocnić się np. w Google, najlepiej jest przygotować adresy, w których widnieją nazwy podstron, tytuły newsów, itp. Jeżeli ktoś wtedy wpisze w Google hasło Bolek i Lolek na wycieczce, a my (np. mając witrynę poświęconą filmom animowanym) będziemy mieli zaindeksowaną podstronę o adresie bolek_i_lolek_na_wycieczce.html lub news/bolek_i_lolek_na_wycieczce, to możemy być pewni, że Google potraktuje naszą stronę lepiej niż inne. Uzyskanie takiego adresu wymaga jedynie dodania prostej reguły w pliku .htaccess:

RewriteRule ^news/(.*)$

index.php?module=news&name=$1

lub, w przypadku linków z sufiksem .html:

RewriteRule ^news-(.*)\.html$

index.php?module=news&name=$1

Wyrażenie regularne (.*) pozwala na za-pisanie dowolnego ciągu znaków (również spacji), więc teoretycznie adres mogliby-śmy zapisać jako news/Bolek i Lolek na wycieczce, aczkolwiek nie jest to zalecane, gdyż przeglądarki i wyszukiwarki różnie interpretują spacje. Powyższy przykład może już praktycznie działać, o ile system CMS na naszej witrynie potrafi odnaleźć artykuł po nazwie zawartej w zmiennej, nie potrzebując ID. Mało który system CMS ma jednak funkcje typu GetIdFromName(nazwa), a my nie będziemy się teraz zagłębiać w ich tajniki.

Jest jeszcze jeden problem dotyczący powyższego przykładu – mianowicie znaki specjalne oraz polskie znaki diakrytyczne, akcenty (np. włoskie czy francuskie), nie-mieckie umlauty, itd. Znaki specjalne naj-lepiej zamienić na myślniki, a narodowe – na wersje łacińskie. Spacje zastąpimy znakami podkreślenia (_), zaś wszystkie litery uczynimy małymi.

Dzięki napisanej w PHP funkcji replace_titles(), którą przedstawiamy na Listingu 7, możemy zamienić zdanie Mały jeż je sobie jabłko, które jest zepsute na maly_jez_je_sobie_jablko,_ktore_jest_ze-psute.

Działanie tej funkcji opisaliśmy w ko-mentarzach. Oczywiście, tablicę ze znaka-mi można powiększyć lub dostosować do

Tabela 4. Zasady przepisywania adresów galerii na pliki HTML

Adres Adres prawdziwygallery-wartość2-wartość3.html index.php?module=gallery&function=warto-

ść2&id=wartość3

gallery-wartość2.html index.php?module=gallery&function=wartość2

gallery.html index.php?module=gallery

Tabela 5. Zasady przepisywania adresów newsów na pliki HTML

Adres Adres prawdziwynews-wartość2-wartość3.html index.php?module=news&action=wartość2&i-

d=wartość3

news-wartość2.html index.php?module=news&action=wartość2

news.html index.php?module=news

Tabela 6. Zasady przepisywania pozostałych adresów (nie newsów i nie galerii) na pliki HTML

Adres Adres prawdziwywartość1-wartość2-wartość3.html index.php?module=wartość1&option=warto-

ść2&id=wartość3

wartość1-wartość2.html index.php?module=wartość1&option=wartość2

wartość1.html index.php?option=wartość1

Page 58: PHP Solutions 06 2006 PL

www.phpsolmag.org58 PHP Solutions Nr 6/2006

mod_rewriteDla zaawansowanych

innych języków (my jako przykładu użyli-śmy polskich znaków diakrytycznych). Za-stosowanie tej funkcji w systemie CMS jest proste. Załóżmy, że za wyświetlanie new-sów odpowiada plik news.php. Po pierw-sze, należy umieścić tę funkcję najlepiej zaraz na początku tego skryptu. Po drugie, wystarczy odszukać szablon dla linku (za-kładamy, że CMS nie korzysta z systemu szablonów w celu ustalenia wzorca dla linków, tylko ze zwykłych zmiennych PHP), który wygląda mniej więcej tak:

<a href=”index.php?module=news&amp;

id=<?=$id?>”><?=$title?></a>

Ten szablon należy zamienić na umożli-wiający tworzenie przyjaznych URL-i przy użyciu naszej funkcji, której dla adresów udających katalogi użyjemy w następujący sposób:

<a href=”news/<?=$id?>/<?=

replace_titles($title)?>”><?=

$title?></a>

Natomiast w przypadku tych, które udają pliki, szablon będzie wyglądał następująco:

<a href=”news-<?=replace_titles(

$title)?>-id<?=$id?>/”><?=$title?></a>

Nasz schemat przy adresach katalogo-wych będzie więc wyglądał tak: news/ID/nazwa_newsa, a przy plikowych: news-nazwa_newsa-idID.html. Teraz, aby ukończyć nasze dzieło, dodajemy do pliku .htaccess następującą regułę (dla sche-matu katalogowego):

RewriteRule ^news/([^-]+)/(.*)$

index.php?modul=news&id=$1 [L,NC,NS]

Przekazuje ona tylko ID do zmiennej id jako słowo. Drugi parametr może być dowolnym ciągiem znaków i nie musi być przekazywany, ponieważ do rozpoznawa-nia newsa wystarczy samo ID.

Ta sama reguła dla schematu plikowe-go będzie wyglądała następująco:

RewriteRule ^news-(.*)-([^-]+)\.

html$ index.php?modul=news&

id=$2 [L,NC,NS]

Również ta reguła przekazuje tylko ID do zmiennej id, lecz tym razem jest ono ustawione jako drugi parametr, w związku z czym jest przekazywane jako $2. Przy-

pomnijmy, że parametr $1 nie musi zostać użyty. Nie będziemy od nowa podawali ta-bel z przykładami przepisywania adresów, bo można to łatwo wywnioskować same-mu. Oczywiście, użyliśmy przykładów te-stowych – chcąc je wdrożyć w działających systemach CMS należy mieć jakąś wiedzę na temat PHP.

Zastosowanie przyjaznych linków w popularnych systemach CMSPokażemy teraz, jak wdrożyć przyjazne URL-e w popularnych CMS-ach opartych na platformie Nuke (PostNuke, MDPro, PHP-Nuke, enVolution, itp.). Są one kom-patybilne z modułem generowania layoutu o nazwie AutoTheme (MDPro i PHP-Nuke mają go nawet w standardowej instalacji). Jest on obecnie najczęściej używanym systemem szablonów dla tych CMS-ów. Moduł ten umożliwia już włączenie przyja-znych linków w ustawieniach. Niestety, są to tylko linki w stylu plikowym – dobrze, że możemy chociaż wybrać sufix (domyślnie do wyboru mamy .html, .htm i .phtml). Po uruchomieniu zamiany linków na przyja-zne, wystarczy przekopiować do katalogu głównego jeden z gotowych plików .htac-cess, które są dostarczane razem z mo-dułem. Nazwy tych plików zaczynają się od sufiksu, więc jeżeli wybierzymy .phtml, kopiujemy plik phtml.htaccess do głównego katalogu strony i zmieniamy jego nazwę na .htaccess. Po tym zabiegu domyślne sche-maty przyjaznych linków zaczną działać. Naszym celem jest jednak opisanie, jak te schematy przerobić i ustawić tak, aby w lin-kach do newsów były nazwy tych ostatnich.

Wspomniane systemy portalowe w katalogu modules/News/ mają plik funcs.php. Odpowiada on za generowa-nie linków w systemie newsów. Otwie-ramy go i wstawiamy w nim opisa-ną już funkcję zamiany tytułów (repla-ce_titles()) z Listingu 7, zaraz po ko-mentarzu autora. Następnie odnajduje-my w funcs.php komentarz:

// Allowed to read full article?

i wstawiamy pod nim:

$nazwa_newsa =

replace_titles($info[title]);

Zmienna $nazwa_newsa będzie wywoływa-ła funkcję replace_titles() i stosowała ją wobec tytułu newsa podanego w zmiennej $info[title]. Poniżej wstawionego przez nas kodu widnieje linijka:

if (pnSecAuthAction(0, $component,

$instance, ACCESS_READ)) {

Pod tym kodem powinna być umieszczo-na definicja zmiennej $fullarticle. Za-mieniamy całą linijkę z tą definicją na:

$fullarticle = $news_name.'-id'.

$info[sid].'.html';

Dzięki temu link do newsa będzie teraz wyglądał następująco: nazwa_newsa-id31.html, gdzie liczba 31 jest przykłado-wym ID newsa. Oczywiście sufix .html na-leży zamienić na wybrany wcześniej przez nas. Pozostało jedynie dodanie reguły

Listing 7. Funkcja replace_titles(), która zmienia wielkość liter i usuwa znaki narodowe

<?php

function replace_titles($text){ // tablice

$search =array(' ', '/', '\'', '&', '%', 'ć', 'ś', 'ą', 'ż', 'ó', 'ł', 'ś', 'ż', 'ń', 'ę',);

$replace=array('_', '-', '-', 'and', 'procent', 'c', 's','a', 'z', 'o', 'l', 's', 'z', 'n', 'e',);

// Zamiana wszystkich liter w stringu na małe

$text = strtolower($text);

// Zamiana znaków w stringu na znaki html

$text = html_entity_decode($text);

// Zamiana znaków z tablic

$text = str_replace($search, $replace, $text);

// Zwracanie tekstu oczyszczonego ze znakow specjalnych i narodowych

return $text;}

?>

Page 59: PHP Solutions 06 2006 PL

mod_rewrite Dla zaawansowanych

w naszym pliku .htaccess, która powinna być ustawiona jako pierwsza:

RewriteRule ^(.*)-id([0-9]+)\.html$

modules.php?op=modload&file=

article&sid=$2 [L,NC,NS]

Dzięki temu wszystko będzie już działać. Zauważmy, że w tym przypadku dla ID użyliśmy wyrażenia regularnego, które ze-zwala na wprowadzenie tylko danej liczby.

Można dodatkowo zmodyfikować domyślny schemat generowania linków w AutoTheme. Standardowo są to angiel-skie odpowiedniki modułów działających z tymi CMS-ami. Aby je zmodyfikować w dowolny sposób, należy otworzyć plik modules/AutoTheme/extras/nazwa_uży-wanego_systemu_cms/autourls.ext.php.

W pliku tym widzimydwie tablice - $search i $replace oraz funkcję preg_replace(), która zamienia wszystkie ad-resy według wyrażeń z tablic. Zatem aby zmienić schemat linków, należy zmody-fikować tablice, które sobie odpowiadają na zasadzie, że pierwsza pozycja z tablicy $search jest zamieniana na pierwszą z tablicy $replace, druga na drugą, itd.; dlatego też obie tablice mają tę samą ilość pozycji. Sposób edycji pokażemy na przy-kładzie jednej z pozycji tablicy $search:

$prefix . 'index.php\?newlang=

([\wd\.:\_/]+)”|',

Jak widzimy, w miejscu, w którym można wstawić wartość zmiennej w URL-u, znaj-duje się wyrażenie ([\w\d\.\:\_\/]+), którego rezultat po przetworzeniu przez funkcję preg_replace() jest oznaczony pod nazwą $1, którą musimy umieścić w tablicy $replace. Odpowiedni wpis w tablicy $replace będzie więc wyglądał następująco:

'”changelang-$1.'.$autourlsext.'”',

Zmienna $autourlsext oznacza sufix.Nie mamy jednak zamiaru opisywać

szczegółowo modyfikacji CMSów i ich modułów, dlatego też nie będziemy się w to dalej zagłębiać. Ograniczymy się do pokazania, jak powinny wyglądać no-we pozycje. Przykładowo, jeśli chcemy dodać pozycję index.php?module=war-tość1&file=wartość2, to przed znakiem zapytania należy wstawić slash, a znak & zastąpić wyrażeniem &(?:amp;)?, które jest warunkiem typu lub: zadziała i wtedy, gdy w linku jest znak & i wtedy, gdy znaj-duje się tam &amp; (poprawna wersja). Wartość1 i wartość2 należy zastąpić wy-rażeniem ([\w\d\.\:\_\/]+), a na końcu pozycji umieścić znaki "|. Całość powinna wyglądać następująco:

$prefix.'index.php\?module=

([\wd\.:\_/]+)&(?:amp;)?

file=([\wd\.:\_/]+)”|'

Na koniec, aby wszystko działało po-prawnie, należy zmodyfikować re-guły w pliku .htaccess tak, aby by-ły one zgodne z pozycjami pliku auto-urls.ext.php. Miejmy nadzieję, że nie bę-dzie z tym już problemów. Efekt zasto-sowania przyjaznych linków w systemie CMS można zobaczyć chociażby na ofi-cjalnej stronie CMS-a PostNuke, http://www.postnuke.com/ czy polskiej stro-nie www.underflip.pl.

PodsumowaniePrzedstawiliśmy Ci, jak w praktyce za-stosować reguły Mod_Rewrite i opisać je za pomocą wyrażeń regularnych. Uży-te przez nas przykłady powinny dać Ci wyraźne wskazówki, jak postępować. Wszystkie z nich zostały sprawdzone na kilku serwerach i działają, dlatego jeże-li którykolwiek z nich nie będzie zwracał oczekiwanego wyniku, może być to wy-łącznie winą serwera. Wszystko zależy bowiem od konfiguracji Mod_Rewrite na serwerze, która bywa błędna.

Jednocześnie pamiętajmy, że moduł Mod_Rewrite jest na tyle potężny, że nie sposób streścić wszystkie jego moż-liwości nie tylko w jednym artykule, ale nawet w całym numerze pisma. Życzymy zatem poprawnych reguł, zadowolenia użytkowników oraz wysokich pozycji w wyszukiwarkach dzięki zastosowaniu Mod_Rewrite. n

R E K L A M A

Page 60: PHP Solutions 06 2006 PL
Page 61: PHP Solutions 06 2006 PL
Page 62: PHP Solutions 06 2006 PL

www.phpsolmag.org62

Kasa dla Webmastera

PHP Solutions Nr 6/2006

Tajniki freelancingu Kasa dla Webmastera

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

Tajniki freelancinguKrzysztof Trynkiewicz

Już na początku swojej przygody z freelancingiem możemy dojść do wniosku, że mnogość zleceń wca-

le nie gwarantuje łatwej pracy, zaś po-zyskanie pierwszych klientów jest zada-niem trudnym. W większości przypadków zleceniodawcy wybierają droższych, lecz bardziej doświadczonych programistów. Ich przewaga leży jednak nie tylko w ilo-ści ukończonych projektów. Wraz z ro-snącym doświadczeniem, wypracowali oni własne schematy pisania kuszących ofert. Przyjrzyjmy się bliżej praktykom, które stosują.

Tworzymy portfolio i resumePierwszą rzeczą, jaką musimy zrobić, jest stworzenie strony informującej o naszych dokonaniach. Przy tworzeniu portfolio i re-sume na potrzeby freelancingowe, nale-ży zwrócić uwagę w pierwszej kolejności na konkurencję. Głównym źródłem naszej wiedzy będą szczegóły kont wiodących

W poprzednim numerze przedstawiliśmy zagadnienie freelancingu jako formy zarabiania przez programistów oraz szukania wykonawców projektów. Dziś skupimy się na szczegółach definiowania zleceń i składania ofert zleceniodawcom. Wskażemy także użyteczne praktyki stosowane podczas tworzenia swojego portfolio oraz resume do celów freelancingowych. Poznamy również opinię profesjonalnego freelancingowca w wywiadzie udzielonym specjalnie dla PHP Solutions.

freelancingowców. Pamiętajmy jednak, by nie kopiować treści, ani wyglądu – port-folio i resume muszą być inne – w końcu każdy ma swój styl i własne dokonania. Warto na wstępie wspomnieć kilka słów o sobie. Niech to jednak nie będzie opis mu-zyki, której słuchamy. Istotne dla zlecenio-dawcy jest: gdzie mieszkamy, dla kogo i w jakich porach pracujemy, jakie mamy do-świadczenie w innych stałych pracach, a wreszcie: jakie projekty ukończyliśmy. Ukończone projekty czołowych freelan-cingowców można liczyć w setkach, jed-nak żaden nie wspomina o więcej niż kilku w swoim portfolio – powinniśmy wybrać te najlepsze i najbardziej rozwinięte. Dobrze jest załączyć fragment własnego skryptu lub gotową klasę, opatrzoną komentarza-mi. Możemy wspomnieć nazwy naszych największych klientów oraz podać moż-liwe sposoby komunikacji – w grę wcho-dzi naturalnie e-mail, ale także komuni-katory oraz telefon. Nie zapomnijmy po-chwalić się znanymi nam językami obcymi

W SIECI

• http://allfreelance.com – kompendium linków fre-elancingowych

• http://guru.com• http://rentacoder.com• http://getafreelancer.com

Stopień trudności: lll

Page 63: PHP Solutions 06 2006 PL

www.phpsolmag.org62

Kasa dla Webmastera

PHP Solutions Nr 6/2006

Tajniki freelancingu Kasa dla Webmastera

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

– zleceniodawca w znakomitej większości przypadków chętniej spojrzy na wykonaw-cę, z którym może porozumieć się w oj-czystym języku. Schludną stronę informa-cyjną jednego z czołowych freelancingow-ców możemy zobaczyć na Rysunku 1.

Szukamy odpowiedniego projektuZanim złożymy swoją pierwszą ofertę (ang. bid), musimy znaleźć projekt ade-kwatny do naszych umiejętności. W przy-padku programistów PHP sprawa wyda-je się prosta: interesują nas zlecenia z tej właśnie kategorii. Warto jednak zwró-cić uwagę także na projekty, które zosta-ły zakwalifikowane do kategorii ASP, JSP, czy nawet Flash. Często bowiem znaj-dziemy tam swoich przyszłych klientów, którzy nie mając głębszej wiedzy o języ-kach webowych, błędnie zakwalifikowali projekt. Niektórych możemy także przeko-nać, że dana strona jest możliwa do stwo-rzenia przy użyciu darmowych technik, w naszym przypadku najpewniej połączenia PHP oraz bazy danych MySQL. Przy skła-daniu oferty zaznaczmy, że koszty wyko-nania będą niższe – nie musimy bowiem opłacać licencji za programy, w których wykonujemy projekt.

Składamy pierwszą ofertęZnaleźliśmy już odpowiedni dla nas pro-jekt, który będziemy w stanie ukończyć. Przyszedł czas na złożenie oferty. Za-poznajmy się najpierw dokładnie z wy-tycznymi projektu oraz wszelkimi za-łącznikami. Po treści wytycznych pozna-my stopień zaawansowania zlecenio-dawcy – odpowiedź napiszemy adekwat-nym językiem. Nie możemy przecież za-sypać laika mnóstwem skrótów i obiet-nicami zgodności z wciąż rosnącą licz-bą standardów. Projekt należy uważnie przemyśleć – dobrze jest, jeśli mamy ja-kąś wizję i ją przedstawimy w treści na-szej oferty. Wizję może stanowić dodat-kowa funkcjonalność, czy nawet układ i kolorystyka wykonywanej strony. Naszą ofertę zaczynamy, naturalnie, od przywi-tania. Warto zwrócić już tutaj uwagę, ja-kim stylem pisze zleceniodawca, czy jest to poważna firma, czy też jedna osoba. W tym drugim przypadku możemy poku-sić się o trochę mniej formalności, tym samym pisząc stylem internautów – per

“ty”. W treści oferty powinniśmy zamie-ścić coś przyciągającego – zwykłe “zro-bię to za $25” nie wystarczy. Przydatne jest tu przedstawienie swojego pomysłu na wykonanie projektu, naturalnie zgod-nego z wytycznymi. Jeśli ukończyliśmy podobne projekty, warto wspomnieć o kilku najistotniejszych oraz wskazać od-nośnik do swojej historii ukończonych prac. Warto w tym miejscu zajrzeć do hi-storii projektów zleceniodawcy – pozna-my jego wymagania, a w zależności od pośrednika freelancingowego, także styl i treść ofert, które zaakceptował. Nie mo-żemy krytykować wytycznych zlecenio-dawcy – zawsze mu przytakujmy, wska-zując opcjonalne rozwiązania. Koniecz-nie musimy wspomnieć o przewidywa-nym czasie wykonania zlecenia, najlepiej krótszym niż ustalony deadline. Jeżeli składamy ofertę na zlecenie wykonania skryptu bądź klasy, warto umieścić odno-śnik do przykładowych fragmentów wła-snego kodu. Pamiętajmy jednak, by kod ten był opatrzony odpowiednimi komen-tarzami! Również w ofercie poinformuj-my, że nasz kod będzie jasny, czytelny, a co najważniejsze – będzie zawierał ko-mentarze. Dobrym pomysłem jest wspo-mnienie o możliwości stworzenia doku-mentacji do projektu, np. przy użyciu na-rzędzia phpDocumentor. Na zakończe-nie zachęcamy do zadawania pytań i po-zdrawiamy w adekwatnym do opisu zle-cenia stylu. Pozostało nam tylko wyce-

nić projekt. W tym celu posłużymy się ponownie historią ukończonych projek-tów zleceniodawcy. Pamiętajmy, że naj-lepiej widziana jest cena najbardziej od-powiednia – jeżeli ustalimy za niską kwo-tę, zostaniemy wzięci za amatorów. Z ko-lei zbyt wysoka cena skreśla nas nawet przed przeczytaniem treści oferty. Ilość złożonych ofert wskaże nam, jak wyglą-da nasza konkurencja – jeśli jest ich wie-le, powinniśmy obniżyć swoją cenę.

Przekonujemy do siebie zleceniodawcęWiele pośredników freelancingowych udo-stępnia różnego typu narzędzia, pomocne w komunikacji ze zleceniodawcą. Powinni-śmy więc używać wbudowanych chatów i for dyskusyjnych, celem sprecyzowania zlecenia – nie bójmy się spytać o szcze-góły, w końcu zależy nam, by zlecenio-dawca dostrzegł nasze zainteresowanie. W kontaktach tego typu kluczowe jest sto-sowanie stylu podobnego do zleceniodaw-cy oraz bardzo szybkie, precyzyjne odpo-wiedzi. Jeśli pośrednik umożliwia złoże-nie pieniędzy do depozytu zabezpieczają-cego zleceniodawcę (ang. Seller Guaran-tee Deposit), skorzystajmy z niego! Ta-kiego typu zabezpieczenie finansowe da-je nam ogromne szanse na szybkie wybi-cie się spośród tłumu nowych freelancin-gowców, pieniądze zaś zostaną nam i tak zwrócone. Dodatkowo niektórzy pośredni-cy umożliwiają zamieszczenie w szczegó-

Rysunek 1. Fragment strony informacyjnej jednego z czołowych freelancingowców

Page 64: PHP Solutions 06 2006 PL

Tajniki freelancingu

PHP Solutions Nr 6/2006

Kasa dla Webmastera

www.phpsolmag.org64

Tajniki freelancingu

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

Kasa dla Webmastera

łach naszego konta informacji o ukończo-nych kursach i posiadanych certyfikatach – z pewnością ten od firmy Zend jest wart wspomnienia.

Szukamy wykonawcyWiemy już, jak złożyć kuszącą ofertę. Co jednak, jeśli to my mamy projekt, które-go wykonanie wymaga pomocy osoby trzeciej? Powinniśmy najpierw chwycić za kartkę i ołówek. Z ich pomocą stwo-rzymy wytyczne projektu oraz zarys gra-ficzny, który następnie możemy zeska-nować i dodać do opisu naszego zlece-nia. Istotne jest, by zastanowić się, w ja-kich technikach powinien być wykonany nasz projekt. Jeśli jest to strona interne-towa, pamiętajmy o problemach z dru-kowaniem stron z animacjami Flasha. Co jednak istotniejsze, sprecyzujmy ja-kie wersje PHP i MySQL mamy dostęp-ne u naszego hosting providera. Różnica między PHP 4 a 5 jest kolosalna, warto więc się dobrze zastanowić, a nawet roz-ważyć zakup serwera z obsługą nowszej wersji PHP, by nasz projekt był rozwijal-ny w przyszłości bez konieczności prze-pisywania kodu. Pamiętajmy też, że nie wszystko należy bezwzględnie opierać na klasach, a jeśli już ich chcemy, wyma-gajmy dokładnego ich opisu. Przy wybo-rze oferty wykonawcy zwracajmy uwagę na skończone zlecenia, a przede wszyst-kim, na fragmenty przykładowych skryp-tów. Na ich podstawie stwierdzimy, czy będziemy w stanie samodzielnie wnosić poprawki do kodu, czy też każda zmiana będzie wymagała konsultacji (zapewne odpłatnej). Zadajmy też przykładowe py-tanie, np. jakimi językami posługuje się wykonawca. Da nam to wiedzę o szyb-kości odpowiedzi, a przy okazji mamy szansę trafić na osobę znająca nasz ję-zyk ojczysty, co ułatwi komunikację. Ce-na nie jest tak istotną kwestią – nie nale-ży oszczędzać na programistach. Najle-piej określić pewien zakres – zbyt niska cena może oznaczać słabe wykonanie, zaś zbyt wygórowana wcale nie gwaran-tuje lepszego produktu końcowego.

Odpowiadamy na pytaniaPo publikacji pierwszego artykułu o fre-elancingu (PHP Solutions 5/2006) otrzy-maliśmy wiele zapytań odnośnie płatno-ści. Serwis Guru.com wskaże nam w tej kwestii wiele odpowiedzi. Szukając de-welopera PHP na szczycie rankingu, na-

tkniemy się na ich średnie wyceny pra-cy za godzinę: wahają się one od $10 do $70, zaś podsumowane zarobki wy-kraczają poza kwotę 100 000 USD. Z kolei na Rent A Coder znajdziemy in-formacje na temat samego rozwoju fre-elancingu: rocznie ilość zakończonych zleceń rośnie o 72%, obecnie miesięcz-nie zakańczanych jest około 15 000 pro-jektów, z czego aż 93% to oferty stałych zleceniodawców. Mamy tym samym od-powiedź na pytanie, czy jako freelance-rzy znajdziemy stałych klientów – z pew-nością tak.

Opinia profesjonalistyFreelancing może być sposobem na ży-cie. Jednym z ludzi, którzy swój sukces zawdzięczają tej formie zarabiania pienię-dzy, jest Sergey I. Grachyov, z którym mie-liśmy przyjemność rozmawiać.

PHP Solutions: Opisz siebie w kilku słowach – jakimi osiągnięciami freelancin-gowymi możesz się pochwalić? Jak zaczę-ła się Twoja przygoda z freelancingiem?

Sergey I. Grachyov: Mam 34 lata i mieszkam w St. Petersburgu, w Rosji. Swo-ich pierwszych zleceń szukałem na Rent A Coder, gdzie obecnie mam 745 ukończo-nych projektów i status Top Coder. Uzyska-łem także status Gold Member na GetAFre-elancer ze średnią oceną 9,95. Korzystam także z serwisu Scriptlance, na którym mo-ja ocena to 10. Projekty wykonuję głównie w PHP, Visual Basic, C# oraz ASP.

PS: Czy freelancing jest na tyle opła-calny, by wystarczał jako jedyna praca?

Ile czasu dziennie spędzasz na jej wyko-nywaniu?

SIG: Mógłbym żyć z samego freelan-cingu, ale obecnie mam także inną pra-cę. Dziennie na freelancing przeznaczam do 12 godzin, chociaż czasem robię sobie wolne – w zależności od ilości pracy oraz spraw do załatwienia poza domem.

PS: Jak porównasz freelancing z pra-cą na pełny etat?

SIG: We freelancingu mam możliwość wyboru zleceń – mogę zwyczajnie odmó-wić, dobierając sobie bardziej odpowiedni projekt. Dodatkowo, po zakończonym zle-ceniu, otrzymuję pozytywne opinie, które pomagają mi znaleźć kolejnych klientów – zamknięte koło. Z drugiej strony, cza-sami trudno jest wygrać aukcję i nie ma się pewności, że wpływy będą regularne. Warto mieć zabezpieczenie w postaci dru-giej pracy.

PS: Czy freelancing uważasz za spo-sób na życie, czy też jest to dla Ciebie for-ma zarobku na rozpoczęcie własnej dzia-łalności?

SIG: Nie planuję rozpoczęcia własnej działalności – nie lubię kierować innymi ludźmi. Narazie chcę wciąż działać jako freelancer, kierując swoją ofertę głównie na rynek amerykański – większość zlece-niodawców pochodzi właśnie z tego kra-ju. Oczywiście freelancing może być do-brym wyjściem na zarobienie pieniędzy koniecznych do otwarcia własnej firmy, głównie dla studentów, nie mających cza-su na regularną pracę. Ja jednak już lubię to, co robię.

Rysunek 2. ProfilSergeya I. Graychova na Rent A Coder

Page 65: PHP Solutions 06 2006 PL

Tajniki freelancingu

PHP Solutions Nr 6/2006

Kasa dla Webmastera

www.phpsolmag.org64

Tajniki freelancingu

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

Kasa dla Webmastera

PS: Jak freelancing wpływa na Twoje życie prywatne? Czy nie uważasz, że zaj-muje zbyt dużo czasu?

SIG: Freelancing nie zajmuje mi wię-cej czasu, niż każda inna praca. Ma taki sam wpływ na moje życie prywatne, jak praca na etat.

PS: Od kogo i jakiego typu zlecenia najczęściej przyjmujesz?

SIG: Przyjmuję każde zlecenia, któ-re potrafię wykonać. Przed zaakceptowa-niem zadania, zawsze sprawdzam, jakie komentarze o zleceniodawcy pozostawili poprzedni wykonawcy – negatywne opinie w zasadzie przekreślają szansę na zatrud-nienie mnie. Preferuję zadania z precyzyj-nie określonymi wytycznymi, w których dokładnie wiem, co mam zrobić. Zlecenio-dawca powinien wiedzieć, czego chce – ja wiem, co mogę zaoferować. Unikamy tym samym niedomówień.

PS: Korzystałeś z wielu serwisów fre-elancingowych. Który z nich mógłbyś po-lecić?

SIG: Zdecydowanie polecam Rent A Coder, głównie z racji bezpieczeństwa: wpłaty na Escrow są zobowiązujące, zaś w przypadku problemów, arbiter zawsze potrafi pogodzić obie strony. Odradzam serwisy, które wymagają wniesienia opła-ty za rejestrację, umieszczenie lub wyszu-kiwanie zleceń.

PS: Czy trudno było zdobyć zaufanie klientów na serwisach freelancingowych? Jak dużo czasu Ci to zajęło, i co okaza-ło się kluczem do sukcesu? Czy obecnie większość Twoich klientów to byli zlece-niodawcy?

SIG: Swoje pierwsze zlecenie wy-grałem dopiero po tygodniu pełnym od-powiedzi “Niestety, zleceniodawca wy-brał inną ofertę”. Wynagrodzenie za to zadanie wynosiło zaledwie $10. Do-świadczenie nauczyło mnie, że jako po-czątkujący freelancer, musisz próbować pozyskiwać zlecenia, umieszczając ko-rzystne propozycje cenowe. Na dzień dzisiejszy, większość moich klientów to nowi zleceniodawcy, chociaż często tra-fiają mi się propozycje wykonania pro-jektów dla byłych klientów. Sam dobie-ram swoje zlecenia – a wybieram tylko te pewne i jasno określone.

PS: Posiadasz również kilka certyfi-katów – jak one wpłynęły na ilość klien-tów? Czy uważasz, że warto było je zdo-bywać?

SIG: Zdobycie certyfikatów wydatnie wpłynęło na moją karierę freelancingową.

Mogę umieszczać trochę wyższe propo-zycje cenowe, ponieważ klienci mają pew-ność, że jest to koszt zatrudnienia profe-sjonalisty. Zdecydowanie polecam ukań-czanie kursów i zdobywanie certyfikatów wszystkim, którzy chcą być liczącymi się programistami.

PS: Co uważasz za najważniejsze we freelancingu: dobre rekomendacje, portfo-lio, korzystne wyceny, szybki kontakt, czy może coś innego?

SIG: Najważniejsze, to posiadać dużą ilość pozytywnie zakończonych projektów u swojego pośrednika freelancingowego. Ukończone projekty, to reputacja. Reputa-cja, to nowi klienci.

PS: Posiadanie tak wielu klientów z pewnością wymaga dobrego rozplanowa-nia czasu i zadań. Czy korzystasz z do-datkowych narzędzi poza tymi, które udo-stępniają pośrednicy?

SIG: Do przechowywania i zarządza-nia moimi pracami używam Visual So-urce Safe. Gdy chcę pokazać zlecenio-dawcy postęp projektu, niekiedy używam własnego systemu. Narzędzia oferowa-ne przez pośredników świetnie sprawdza-ją się do składowania kopii zapasowych oraz dowodów wykonania pracy, w przy-padku sporu.

PS: Czy musiałeś nabyć dodatkowe umiejętności, np. w zakresie marketingu, czy komunikacji z klientami, by stać się profesjonalnym freelancingowcem?

SIG: Nie, wystarczyło, że dokładnie przeczytałem zasady i zalecenia z Rent A Coder i zacząłem je stosować. Reszta (np. zdolności komunikatywne) przyszła sama z siebie.

PS: Brak nadzoru szefa z pewno-ścią nie motywuje. Jak poradziłeś so-bie ze zmuszaniem siebie do codzien-nej pracy?

SIG: Nie lubię, gdy ktoś obserwu-je moje ręce podczas pracy. Motywację do pracy zyskałem, gdy wmówiłem sobie, że jest to taka sama praca, jak na pełen etat. Teraz już nie zmuszam się do nicze-go – lubię to, co robię.

PS: Jak radzisz sobie ze stresem spo-wodowanym obowiązkowym dotrzymywa-niem terminów, wiedząc, że kolejne zlece-nie może nie nadejść prędko?

SIG: Jestem rozgrzany przez cały rok, jak po opalaniu, a mieszkam przecież w St. Petersburgu. Dotrzymywanie terminów nie sprawia mi problemu, zaś w przypad-ku braku zleceń – wciąż mam drugą pra-cę. Przestrzegam jednak nowych freelan-

cingowców: arbiter w pięć sekund przy-zna rację (i fundusze) zleceniodawcy, któ-ry zgłosi, że przekroczyliśmy ustalony de-adline, zaś otrzymany negatywny komen-tarz fatalnie wpłynie na naszą reputację. Terminy to kluczowa sprawa, należy ich bezwzględnie dotrzymywać.

PS: Czy sądziłeś, że freelancing bę-dzie trudniejszy lub łatwiejszy, niż się oka-zał?

SIG: Gdy zaczynałem, nie myślałem o trudnościach. Z obecnej perspektywy wy-daje się, że jest to praca o stopniu trudno-ści zależnym od zlecenia, ale przynosząca wielką satysfakcję. Nikt mnie nie zmusza, bym wykonywał trudne zadania – sam o tym decyduję, co jest ogromnym plusem.

PS: Jesteś zadowolony z formy zarob-ku, którą wybrałeś, czy też wolałbyś ina-czej spożytkować swój czas przeznaczo-ny na freelancing?

SIG: Jestem bardzo zadowolony z te-go, co robię. Gdybym mógł zmienić czas, zostałbym freelancingowcem pięć lat wcześniej.

PS: Jakie rady udzieliłbyś osobie, która planuje rozpocząć pracę jako freelancer?

SIG: Przede wszystkim: próbować. Próbować i nie poddawać się. Wraz z wy-grywaniem kolejnych zleceń, będzie ła-twiej o następne. Zalecam także intensyw-ną naukę języka angielskiego.

PS: Dziękujemy za rozmowę.

PodsumowanieNiezależnie od tego, czy jesteśmy wyko-nawcą, czy zleceniodawcą, naszym głów-nym źródłem informacji są bardziej doświad-czeni użytkownicy pośredników freelancin-gowych. Warto zwrócić uwagę na podobne do naszych oferty i na nich się wzorować. Ważne jest, by wybrać jeden serwis pośred-niczący i na nim zdobywać renomę oraz stałych klientów. Jak widzimy na przykładzie Sergeya, do sukcesu niedaleko! n

Krzysztof Trynkiewicz od wielu lat zaj-muje się tworzeniem witryn w technolo-gii PHP oraz Flash. Ściśle współpracuje z magazynem PHP Solutions, wykonu-je także zlecenia freelancingowe. Obec-nie rozwija kilka równoległych projektów autorskich dostępnych na witrynie http://eldoras.com

Kontakt z autorem:[email protected]

O autorze

Page 66: PHP Solutions 06 2006 PL

www.phpsolmag.org66 PHP Solutions Nr 6/2006

Technika

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

PHPUnit2 Technika

Przypomnij sobie jak często po napisaniu nawet wydawa-ło by się prostej klasy, odnaj-

dywałeś w niej błąd (i to często w naj-mniej spodziewanym miejscu)? Ile ner-wów i czasu kosztowało Cię odnalezie-nie błędnego fragmentu? Nawet jeśli jesteś szczęśliwcem używającym edy-tora z wbudowanym debugerem PHP, zadanie odnalezienia błędu w aplikacji składającej się z kilkudziesięciu – kil-kuset klas, często jest zadaniem bar-dzo trudnym.

W świecie Java-y od dawna stosowa-ne są tak zwane testy jednostkowe (ang. unit tests), przeprowadzane z wykorzy-staniem wyspecjalizowanych framewor-ków testowych – najpopularniejszym z nich jest JUnit. Na szczęście od pewnego czasu istnieje kilka podobnych framewor-ków dla PHP. My zajmiemy się tu frame-workiem o nazwie PHPUnit2. Jest on roz-wijany w ramach PEAR przez Sebastiana Bergmanna.

Odnalezienie błędu w nawet najprostrzej aplikacji składającej się z kilku klas zdarza się nawet tym, którzy mają wbudowany do edytora debuger PHP. Na szczęście od pewnego czasu istnieje framework o nazwie PHPUnit2.

Zanim przejdziesz do dalszej czę-ści artykułu, musisz zainstalować u sie-bie PHPUni2 – zapoznaj się w tym celu z ramką Instalacja PHPUnit2.

Krótki wstępPisanie testów jednostkowych nie jest niczym innym jak stworzeniem klas ma-jących na celu kompletne przetestowa-nie klas wchodzących w skład aplikacji. Przyjęło się, że na każdą klasę aplika-cji powinna przypadać jedna lub więcej klas testów. Zdarza się, że klasa testu-jąca posiada więcej kodu od klasy testo-

PHPUnit2 w praktyceMarcin Staniszczak

W SIECI

W Internecie znajduje się spo-ro materiałów na temat testów jednostkowych. Oto kilka wy-branych, interesujących odno-śników:

l http://www.phpunit.de/ – strona i wiki projektu PHPUnit;

l http://www.phpunit.de/pocket_guide/index.en.php – podręcznik autora frame-worka PHPUnit;

l http://www.onlamp.com/pub/a/php/2005/12/08/phpunit.html – krótki tutorial autora frameworka PHPU-nit2;

l http://simpletest.sourceforge.net/ – konkurencyjny w sto-sunku do PHPUnit2 frame-work do testów jednostko-wych klas PHP.

Stopień trudności: lll

Co musisz wiedziećW celu zrozumienia materiału zawarte-go w tym artykule, wymagana jest przy-najmniej podstawowa znajomość PHP5 oraz programowania obiektowego. Do-datkowo niezbędne są chęci – tworzenie testów jednostkowych początkowo może wydawać się zbędnym traceniem cenne-go czasu, jednak wraz ze wzrostem pro-jektu, szybko docenia się ich siłę.

Page 67: PHP Solutions 06 2006 PL

www.phpsolmag.org66 PHP Solutions Nr 6/2006

Technika

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

PHPUnit2 Technika

wanej. Dzieje się tak, gdyż klasa testu powinna przeprowadzić możliwie kom-pletny test. Tak więc pisząc testy, mu-simy także zwracać uwagę na sytuacje nietypowe – takie jak błędne dane, dane z granic zakresów etc.

Często poruszanym problemem jest pytanie kiedy zacząć pisać testy? Jedy-ną słuszną odpowiedzią jest - jak najwcze-śniej. Najlepiej jeszcze zanim zaczniemy kodować samą klasę naszej aplikacji. Za-stanawiasz się teraz zapewne jak masz napisać test czegoś co w rzeczywistości jeszcze nie istnieje. Przystępując do pi-sania testu, będziesz zmuszony dokład-nie przemyśleć i zaprojektować funkcjo-nalność tworzonej klasy. Dodatkowo gdy będziesz miał już gotowe testy, będziesz mógł się nimi posługiwać w trakcie kodo-wania klasy aplikacji, w celu sprawdzania jej poszczególnych, stopniowo dopisywa-nych fragmentów. W ten sposób wszyst-kie ewentualne problemy wykryjesz i roz-wiążesz już na etapie kodowania.

Testy jednostkowe pełnią jeszcze jed-ną ważną rolę – są one doskonałą doku-mentacją zbudowanej klasy. Wystarczy się z nimi zapoznać, aby dowiedzieć się w jaki sposób korzystać z danej klasy i jak ona działa.

Projekt naszej aplikacjiNa potrzeby tego artykułu stworzone zo-staną dwie klasy, zarządzające użytkow-nikami. Za ich pomocą będziemy mo-gli dodawać i usuwać użytkowników, oraz sprawdzać czy użytkownik podał popraw-ny login i hasło – innymi słowy, czy może się on zalogować.

Jedna z klas będzie odpowiedzialna za sprawdzanie uprawnień użytkowników. Będzie ona nosiła nazwę Authorization a jej główną metodą będzie check, przyj-mująca dwa parametry – login oraz hasło użytkownika. Dodatkowo metoda check będzie blokowała konto na jakiś czas je-śli nastąpią trzy nieudane próby logowania na nie pod rząd.

Druga klasa – User – będzie posiadała metody add, remove oraz login.

Tworzymy testyTyle informacji na początek wystarcza. Zacznijmy pisać testy dla pierwszej kla-sy. Każda klasa testu musi dziedziczyć po klasie PHPUnit2_Framework_Test-Case. Klasę testującą klasę Authoriza-

Instalacja PEARJeśli korzystasz z systemu Windows, prawdopodobnie musisz zainstalować u siebie pa-kiet PEAR. Zanim jednak to zrobisz, musimy najpierw odpowiednio skonfigurować PHP. W pliku php.ini ustaw zmienną include_path, tak aby wskazywała na katalog php/PEAR. U mnie zmienna ta wygląda następująco:

include_path=".;e:\PHP-5.1.4\PEAR"

Pamiętaj o kropce na początku! Informuje ona PHP, że plików includowanych w skryptach PHP ma szukać także w bieżącym katalogu. Bez tego większość Twoich skryptów prze-stanie działać.

Następnie uruchom skrypt go-pear.bat, znajdujący się w katalogu Twojego PHP. Wy-konuj wszystko zgodnie z poleceniami wyświetlanymi Ci przez skrypt instalacyny.

Gdy skrypt zakończy swoje działanie, kliknij dwukrotnie na pliku pear_env.reg. Spowo-duje to dodanie niezbędnych wpisów do rejestru systemowego Windows. Teraz możesz na wszelki wypadek zrestartować system i przystąpić do instalacji PHPUnit2.

Instalacja PHPUni2Framework testowy PHPUnit2 jest rozwijany w ramach PEAR więc jego instalacja jest zdumiewająco prosta. W celu zainstalowania go w oknie konsoli wydaj polecenie:

pear install –alldeps phpunit2

Polecenie to spowoduje zainstalowanie PHPUnit2 oraz w razie konieczności innych zależ-nych pakietów (jak na przykład Log i Benchmark).

Pamiętaj że aby działało u Ciebie polecenie pear, musisz mieć odpowiednio ustawio-ną ścieżkę w zmiennej systemowej PATH, oraz w pliku php.ini, zmienną include_path.

Instalacja XdebugMimo że Xdebug nie jest wymagany do wykorzystania większości oferowanych przez PHPUnit2 możliwości, jednak niektóre z bardziej zaawansowanych z nich wy-magają tego rozszerzenia do swojego działania. Warto więc pokusić się od razu o instalacje Xdebug na swoim komputerze. Aby zainstalować Xdebug, musisz pobrać rozszerzenie ze strony http://www.xdebug.org – pamiętaj przy tym aby pobrać je w wersji dla twojego PHP (w moim przypadku będzie to wersja dla PHP 5.1.2+ - + oznacza „i wyższe”).

Gdy pobrałeś już odpowiednią bibliotekę, przegraj ją do katalogu w którym znajdują się inne Twoje rozszerzenia PHP (najczęściej jest to katalog php/ext). Kolejnym krokiem jest dodanie następującego wpisu do pliku php.ini:

zend_extension_ts=”c:\php-5.1.4\ext\php_xdebug-5.1.2-2.0.0beta6.dll”

Pamiętaj że ścieżka tu podana musi odpowiadać Twojej instalacji PHP, podobnie na-zwę rozszerzenia musi odpowiadać nazwie biblioteki którą pobrałeś ze strony Xde-bug. Zrestartuj teraz swój serwer HTTP. Jeśli wszystko zrobiłeś dobrze, rozszerzenie Xdebug powinno być już dostępne dla Twoich skryptów, a co za tym idzie, także dla PHPUnit2.

Na koniec jeszcze jedna uwaga – powyższy opis instalacji Xdebug dotycz systemu Windows. Instalacja w systemie Linux jest bardziej złożona i możesz się z nią zapoznać na stronie http://www.xdebug.org/install.php. Dokładniej, zainstalowanie Xdebug na syste-mach unixowych, wymaga jego ręcznej kompilacji.

Rysunek 1. Nieudane uruchomienie testu

Page 68: PHP Solutions 06 2006 PL

www.phpsolmag.org68 PHP Solutions Nr 6/2006

PHPUnit2Technika

tion, nazwiemy AuthorizationTest (dalej będziemy się trzymali tego sposobu na-zewnictwa):

<?php

class AuthorizationTest

extends PHPUnit2_Framework_TestCase {

}

?>

W klasie tej umieścimy nasze testy. Każdy test to pojedyncza (publiczna) metoda o nazwie rozpoczynającej się wyrazem test. Na Listingu 1 znajduje się metoda testująca poprawność dzia-łanie metody auth po dodaniu prawidło-wego loginu oraz hasła. Oczekiwaną wartością jest Authorization::OK. Je-śli autoryzacja przebiegła poprawnie, sprawdzamy także podstawowe dane o użytkowniku, takie jak jego imię i na-zwisko.

Do sprawdzania poprawności da-nych używa się metod z serii assert. My w metodzie z Listingu 1 użyliśmy metod assertTrue oraz assertEquals. Pierwsza z nich sprawdza czy zmienna podana w pierwszym parametrze ma wartość true – jeśli nie, test kończy się błędem. Druga metoda porównuje war-tości z dwóch pierwszych parametrów i gdy nie są one takie same kończy test błędem. Dokładny opis wszystkich me-tod assert znajduje się w Tabeli 1. Jak widzisz zbiór metod testujących jest naprawdę potężny.

W metodzie tej korzystamy ze zmiennej objAuthorization, której jednak jeszcze nigdzie nie stworzyli-śmy, która jednak będzie wykorzysty-wana jeszcze wiele razy – przecho-wuje ona obiekt klasy Authorization. PHPUnit2 umożliwia stworzenie meto-dy inicjalizującej - setUp - oraz sprzą-tającej – tearDown. W naszym przypad-ku metoda setUp wygląda tak jak na Li-stingu 2.

Żadne z naszych testów nie wyko-rzystują metody tearDown. Przydatna może się ona okazać, wówczas gdy te-sty wykorzystują jakieś zewnętrzne za-soby, które należy zamknąć bądź usu-nąć (np. pliki, połączenia ze zdalnymi serwerami etc.).

Jak widzisz nasza klasa Authorization przyjmuje w konstrukto-rze połączenie z bazą danych – dokład-niej obiekt PDO. Jednak w celu ułatwie-nia tworzenia pozostałych testów, zbu-

dowałem specjalną klasę odpowiedzial-ną jedynie za nawiązywanie połączenia z bazą – zobacz Listing 3. Klasa ta po-siada prywatny konstruktor, tak aby nie dało się utworzyć jej instancji. Dodatko-wo posiada publiczną statyczną meto-dę, zwracającą połączenie z bazą. Me-

toda ta nawiązuje połączenie jedynie raz, przy pierwszym jej wywołaniu. Każ-de następne wywołanie powoduje jedy-nie zwrócenie obiektu PDO.

Klasa ta zabezpiecza nas przed ko-niecznością wielokrotnego nawiązy-wania połączenia w każdej z klas te-

Listing 1. Nasza pierwsza metoda testująca – sprawdza ona zachowanie metody auth klasy Authorization na poprawne dane

public function testLoginOK() { $intResult = null;

$blnResult = ($intResult = $this->objAuthorization->check('test',

'123456')) === Authorization::OK;

$this->assertTrue($blnResult, 'Test zwrócił '.$this->result($intResult));

$this->assertEquals('Jan', $this->objAuthorization->getName());

$this->assertEquals('Kowalski', $this->objAuthorization->getSurname());

}

Listing 2. Metoda inicjalizująca klasy testowej AuthorizationTest- setUp

public function setUp()

{

$this->objAuthorization = new Authorization(DBConnection::getDBConnection());

}

Listing 3. Klasa nawiązująca połączenie z bazą danych

<?php

class DBConnection { private static $objDBConnection = null;

private function __construct() { }

public static function getDBConnection() { if (self::$objDBConnection === null) { self::$objDBConnection = new PDO( 'mysql:dbname=auth;host=127.0.0.1',

'root',

''

);

}

return self::$objDBConnection;

}

}

?>

Rysunek 2. Klasa Authorization nie przeszła żadnego z naszych testów

Page 69: PHP Solutions 06 2006 PL
Page 70: PHP Solutions 06 2006 PL

www.phpsolmag.org70 PHP Solutions Nr 6/2006

PHPUnit2Technika

stów. Istnieją inne sposoby na rozwią-zanie tego problemu, ten jednak wy-daje mi się najprostszy i wystarczająco skuteczny.

Cała klasa testująca klasę Authorization znajduje się na Listin-gu 4. Na samym początku dołączamy plik z naszą nie istniejącą jeszcze kla-są Authorization. W tym miejscu war-to wspomnieć o organizacji plików apli-kacji i testów. Najsłuszniejszym wybo-rem wydaje się umieszczenie wszyst-kich testów w osobnym katalogu, na-zwanym np. tests, poza katalogiem te-stowanych klas. Dzięki temu unikniemy problemu mieszania się rzeczywistych klas aplikacji z klasami testów. Ponadto ułatwi to umieszczanie aplikacji na ser-werze z pominięciem plików testów jed-nostkowych.

Spróbujmy teraz uruchomić nasz test. Z poziomu konsoli wydaj polecenie:

phpunit AuthorizationTest

Oczywiście test nie ma prawa się powieść gdyż nie istnieje jeszcze klasa Authoriza-tion. Na konsoli wyświetlona zostanie in-formacja o błędzie zbliżona do tej z ry-sunku 1.

Zbierzmy się więc za budowę naszej klasy. Listing 5 przedstawia jej szablon – wszystkie niezbędne metody, jednak nie wypełnione jeszcze kodem.

Z taką namiastką klasy, nasz test uru-chomi się już poprawnie, jednak nasza klasa nie przejdzie żadnego z 4 testów – zobacz Rysunek 2.

Spójrz teraz na Listing 4 na metodę testLoginUnauthorized. Oczekuje ona że metoda check klasy Authorization zwróci wartość Authorization::ERROR_

UNAUTHORIZED. Dodajmy więc do metody check następującą linię:

return self::ERROR_UNAUTHORIZED;

Uruchom test ponownie. Widać (Rysu-nek 3) że klasa przeszła drugi test. Test ten jednak sam, w oderwaniu od pozo-stałych nie ma większego sensu. Nasza praca polega teraz na takim zaprogra-mowaniu wszystkich metod klasy Au-thorization, aby przeszła ona wszystkie cztery testy.

Zanim jednak zabierzemy się za pro-gramowanie, musimy utworzyć bazę da-nych oraz tabelę która będzie przecho-wywała informacje o użytkownikach. Po-

Listing 4. Klasa testująca klasę Authorization – plik AuthorizationTest.php

<?php

require_once('../classes/Authorization.class.php');

require_once('DBConnection.php');

class AuthorizationTest extends PHPUnit2_Framework_TestCase { protected $objAuthorization = null;

public function setUp() { $this->objAuthorization = new Authorization(DBConnection::

getDBConnection());

}

//----------------------------------------------

public function testLoginOK() { $intResult = null;

$blnResult = ($intResult = $this->objAuthorization->check('test',

'123456')) === Authorization::OK;

$this->assertTrue($blnResult, 'Test zwrócił wartość '.$this-

>result($intResult));

$this->assertEquals('Jan', $this->objAuthorization->getName());

$this->assertEquals('Kowalski', $this->objAuthorization->getSurname());

}

public function testLoginUnauthorized() { $intResult = null;

$blnResult = ($intResult = $this->objAuthorization->check('test2',

'123456')) === Authorization::ERROR_UNAUTHORIZED;

$this->assertTrue($blnResult, 'Test zwrócił '.$this->result($intResult));

}

public function testLogin3times() { for ($intI = 0; $intI<5; $intI++) { $this->objAuthorization->check('test', md5(rand())); }

$intResult = null;

$blnResult = ($intResult = $this->objAuthorization->check('test',

'123456')) === Authorization::ERROR_3TIMES;

$this->assertTrue($blnResult, "Dla testu z poprawnym hasłem zwrócona

wartość to ".$this->result($intResult));

$this->assertNotNull($this->objAuthorization->getLockFor(), 'Niepoprwany

czas blokady 1');

$blnResult = ($intResult = $this->objAuthorization->check('test',

md5(rand()))) === Authorization::ERROR_3TIMES; $this->assertTrue($blnResult, "Dla testu z losowym hasłem zwrócona wartość

to ".$this->result($intResult));

$this->assertNotNull($this->objAuthorization->getLockFor(), 'Niepoprwany

czas blokady 2');

}

public function testLoginUnknownIP() { $intResult = null;

$blnResult = ($intResult = $this->objAuthorization->check('testIP',

'123456')) === Authorization::ERROR_IP;

$this->assertTrue($blnResult, "Test zwrócił wartość ".$this-

>result($intResult));

}

private function result($intValue) { switch ($intValue) { case Authorization::OK: return 'OK'; break; case Authorization::ERROR_IP: return 'ERROR_IP'; break; case Authorization::ERROR_UNAUTHORIZED: return 'ERROR_UNAUTHORIZED'; break; case Authorization::ERROR_3TIMES: return 'ERROR_3TIMES'; break; default: return '??'; }

}

}

?>

Page 71: PHP Solutions 06 2006 PL

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

PHPUnit2 Technika

który poda prawidłowy login i hasło po-winien być akceptowanych przez meto-dę check. Musimy jeszcze dodać bloka-dę w przypadku zdefiniowanego adresu IP spod którego użytkownik może się logować (kolumna ip w tabeli users). Nasza metoda wzbogaciła się o kolej-ny warunek – Listing 7. Dzięki temu kla-sa poprawnie przechodzi większość te-stów. Do rozwiązania został tylko jeden problem – blokowanie konta użytkow-nika w przypadku trzykrotnego poda-nia nieprawidłowego hasła. Po krótkim przemyśleniu i zapisaniu swoich myśli w PHP, powstała klasa jak z Listingu 8. Wydaje się że wszystko powinno działa poprawnie. W przypadku niepoprawne-go hasła sprawdzamy ile było nieuda-nych prób logowania na dane konto pod rząd. Jeśli zbyt wiele blokujemy konto. Jeśli konto jest zablokowane, spraw-dzamy czy możemy je już odblokować. Jeśli nie możemy ustawiamy zmienną informującą jak długo konto pozosta-nie jeszcze zablokowane i zwracamy błąd. Wszystko wydaje się być popraw-ne. Nawet w przypadku logowania, czy-ścimy wpisy w bazie o nieudanych pró-bach logowania, tak aby były one zli-czane od początku. A więc uruchom-my test. I co się stało? Okazuje się że klasa nadal nie przechodzi trzeciego te-stu – metoda check w momencie poda-nia prawidłowego hasła po trzech nie-udanych próbach logowania na kon-ta zwraca wartość Authorization::OK., czyli pozwala się zalogować na konto które powinno być zablokowane. Po-wód? Spełnione zostały pierwsze dwa warunki – istnieje użytkownik o zada-nym loginie, oraz podanym haśle. Me-toda nie sprawdza więc już czy konto nie jest przypadkiem zablokowane. Zo-bacz Rysunek 4.

Miejsce błędu zostało odnalezione bardzo szybko dzięki zastosowaniu te-stów. Na szczęście naprawienie błędu jest bardzo proste. Wystarczy zmodyfiko-wać warunek:

if (strcmp($arrUser['password'],

md5($strPassword)) === 0) {

metody check, tak aby sprawdzał on tak-że liczbę nieudanych prób logowania oraz czy ewentualnie zablokowane kon-to można już odblokować (czyli czy upły-nął już czas na jaki konto zostało zablo-kowane)

Listing 5. Szablon klasy Authorization

<?php

class Authorization {

const OK = 0;

const ERROR_UNAUTHORIZED = 1;

const ERROR_3TIMES = 2;

const ERROR_IP = 3;

private $objDBConnection;

public function __construct(PDO $objDBConnection) { $this->objDBConnection = $objDBConnection;

}

public function check($strLogin, $strPassword) { }

public function getID() { }

public function getName() { }

public function getSurname() { }

public function getLastLogin() { }

public function getLockFor() { }

}

?>

Listing 6. Polecenie SQL tworzące odpowiednią tabelę w bazie MySQL

CREATE TABLE users ( iduser INT NOT NULL auto_increment PRIMARY KEY, name VARCHAR(50) NOT NULL, surname VARCHAR(80) NOT NULL, login VARCHAR(50) NOT NULL UNIQUE, password CHAR(32) NOT NULL, ip BIGINT DEFAULT NULL, lastlogin INT NOT NULL DEFAULT 0, lasttry INT NOT NULL DEFAULT 0, trycount INT NOT NULL DEFAULT 0,);

Listing 7. Dodajemy dane do tabeli

INSERT INTO users (iduser, name, surname, login, password, ip, lastlogin, lasttry, trycount) VALUES (101,'Jan','Kowalski','test', 'e10adc3949ba59abbe56e057f20f883e',NULL,0,0,0), (102,'Jan', 'Kowalski','testIP','e10adc3949ba59abbe56e057f20f883e',

-943208505,0,0,0);

lecenie SQL tworzące odpowiednią ta-belę w bazie MySQL przedstawia Li-sting 5.

Gdy istnieje już Tabela, musisz dodać do niej odpowiednie dane, które są wyma-gane przez nasz test. Przedstawia to Li-sting 6.

Pierwszym etapem będzie spraw-dzenie czy użytkownik podał prawdzi-

wy login i hasło, a może tylko login. Dodajemy do naszej metody kilka li-nii – zobacz Listing 6. Pobieramy tu użytkowników. Z taką metodą (oraz ze zmodyfikowanymi metodami getName i getSurname, tak aby zwracały one od-powiedni imię i nazwisko użytkownika), nasza klasa przechodzi już dwa pierw-sze testy. Jednak nie każdy użytkownik

Page 72: PHP Solutions 06 2006 PL

www.phpsolmag.org72 PHP Solutions Nr 6/2006

PHPUnit2Technika

if ((strcmp($arrUser['password'],

md5($strPassword)) === 0) &&

(($arrUser['trycount']

<$this->intLockAfter)

|| ($arrUser['lasttry']+$this->

intLockTime<time()))) {

Dzięki tej zmianie, nasza klasa przechodzi zaprojektowane wcześniej testy.

Najważniejsza klasa naszego proste-go pakietu jest już gotowa i według na-szych testów działa poprawnie. Jednak warto by zastanowić się czy nie da się jej przetestować dokładniej. Pomyśl nad tym i jeśli masz jakiś pomysł zbuduj dodatko-we testy.

Czas zabrać się za budowę te-stów klasy User. Klasa ta powinna za-wierać trzy metody: dodającą użytkow-nika do bazy – add, usuwającą użyt-kownika z bazy – remove, sprawdzają-cą poprawność podanego loginu i hasła – login. Metodę add proponuję zbudo-wać jako statyczną, dzięki temu będzie można dodać użytkownika bez tworze-nia instancji danej klasy. Na Listingu 9 znajduje się proponowany zbiór testów naszej klasy. Teraz sam spróbuj na ich podstawie zbudować w pełni działającą klasą, tj. taką, która przejdzie wszystkie testy z Listingu 9.

Łączenie testów w pakietyW rzeczywistości każda aplikacja skła-da się z wielu klas. Często jest ich na-wet kilkaset. Niezbyt wygodne wyda-je się być uruchamianie testu każdej z nich osobno, po wprowadzeniu zmian w aplikacji – można co prawda napi-sać sobie skrypt (np. w bash na syste-mach unix-owych lub plik .bat na Win-dowsach) który będzie uruchamiał po kolei wszystkie testy, jednak analizo-wanie wyników będzie wówczas utrud-nione. Najlepiej by było gdyby PHPU-nit2 dostarczał możliwości połączenia wszystkich testów jednostkowych w je-den pakiet, traktowanych jak test całej aplikacji. Na szczęście twórcy PHPU-nit2 przewidzieli taką możliwość. Spró-bujmy więc połączyć oba testy naszych klas w jeden pakiet. Musimy w tym ce-lu zbudować sobie klasę – nazwijmy ją UserTestSuite. Klasa ta musi posiadać metodę suite – to ona zostanie wywo-łana przez PHPUnit2 i to w niej musi-my ustalić jakie testy i w jakiej kolejno-ści chcemy przeprowadzić. Na Listin-

Listing 8. Wstępna postać metody check klasy Authorization

public function check($strLogin, $strPassword) { $strQuery = 'SELECT * FROM users WHERE login = :login';

$objStament = $this->objDBConnection->prepare($strQuery);

$objStament->bindParam(':login', $strLogin, PDO::PARAM_STR, 50);

if ($objStament->execute()===false) { return false; }

$arrUser = $objStament->fetchAll();

if (count($arrUser) === 1) { $arrUser = $arrUser[0];

if (strcmp($arrUser['password'], md5($strPassword)) === 0) { $this->strName = $arrUser['name'];

$this->strSurname = $arrUser['surname'];

return self::OK; } else { return self::ERROR_UNAUTHORIZED; }

}

return self::ERROR_UNAUTHORIZED; }

Listing 9. Metoda check przechodząc 3 z 4 testów

public function check($strLogin, $strPassword) { $strQuery = 'SELECT * FROM users WHERE login = :login';

$objStament = $this->objDBConnection->prepare($strQuery);

$objStament->bindParam(':login', $strLogin, PDO::PARAM_STR, 50);

if ($objStament->execute()===false) { return false; }

$arrUser = $objStament->fetchAll();

if (count($arrUser) === 1) { $arrUser = $arrUser[0];

if (strcmp($arrUser['password'], md5($strPassword)) === 0) { if ($arrUser['ip']!==null) { if (!isset($_SERVER['REMOTE_ADDR']) || strcmp(long2ip($arrUser[ 'ip']), $_SERVER['REMOTE_ADDR']) !== 0) {

$this->updateTry($arrUser['iduser'], $arrUser['trycount']+1);

return self::ERROR_IP; }

}

$this->strName = $arrUser['name'];

$this->strSurname = $arrUser['surname'];

return self::OK; } else { return self::ERROR_UNAUTHORIZED; }

}

return self::ERROR_UNAUTHORIZED; }

Rysunek 3. Klasa Authorization przeszła drugi test

Page 73: PHP Solutions 06 2006 PL

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

PHPUnit2 Technika

Listing 10. Niemal poprawna klasa Authorization – wydawało się że klasa powinna działać poprawnie, tymczasem okazuje się, że nie przechodzi ona trzeciego testu

<?php

class Authorization { const OK = 0; const ERROR_UNAUTHORIZED = 1; const ERROR_3TIMES = 2; const ERROR_IP = 3; private $objDBConnection; private $intLockAfter = 3; private $intLockTime = 180; private $strName = null; private $strSurname = null; private $intLastLogin = null; private $intLockFor = null;

public function __construct(PDO $objDBConnection) { $this->objDBConnection = $objDBConnection;

}

public function check($strLogin, $strPassword) { $strQuery = 'SELECT * FROM users WHERE login

= :login';

$objStament = $this->objDBConnection->

prepare($strQuery);

$objStament->bindParam(':login', $strLogin,

PDO::PARAM_STR, 50);

if ($objStament->execute()===false) { return false; }

$arrUser = $objStament->fetchAll();

if (count($arrUser) === 1) { $arrUser = $arrUser[0];

if (strcmp($arrUser['password'], md5($strPassword)) === 0) {

if ($arrUser['ip']!==null) {

if (!isset($_SERVER['REMOTE_ADDR']) || strcmp(long2ip($arrUser['ip']), $_SERVER['REMOTE_ADDR']) !== 0) {

$this->updateTry($arrUser['iduser'],

$arrUser['trycount']+1);

return self::ERROR_IP; }

}

$this->updateLogin($arrUser['iduser']);

$this->intIDUser = intval($arrUser['iduser']); $this->strName = $arrUser['name'];

$this->strSurname = $arrUser['surname'];

$this->intLastLogin = intval( $arrUser['lastlogin']);

return self::OK; } else { if ($arrUser['trycount']>=$this->intLockAfter

&& ($arrUser['lasttry']+

$this->intLockTime)>time()) { $this->intLockFor = intval(((($arrUser[ 'lasttry']+$this->intLockTime)-

time())/60)+1); return self::ERROR_3TIMES; } else { if ($arrUser['trycount']>=$this-

>intLockAfter && ($arrUser['lasttry'

]+$this->intLockTime)<time()) {

$this->updateTry($arrUser['iduser']);

} else { $this->updateTry($arrUser['iduser'],

$arrUser['trycount']+1);

}

return self::ERROR_UNAUTHORIZED; }

}

}

return self::ERROR_UNAUTHORIZED; }

public function getID() { return $this->intIDUser; }

public function getName() { return $this->strName; }

public function getSurname() { return $this->strSurname; }

public function getLastLogin() { return $this->intLastLogin; }

public function getLockFor() { return $this->intLockFor; }

private function updateLogin($intIDUser) { $strQuery = 'UPDATE users SET lastlogin=:lastlogin,

trycount=0, lasttry=0 WHERE iduser=:iduser';

$objStament = $this->objDBConnection->

prepare($strQuery);

$objStament->bindParam(':lastlogin', time(), PDO::PARAM_INT);

$objStament->bindParam(':iduser', $intIDUser,

PDO::PARAM_INT);

$objStament->execute();

$objStament->fetchAll();

}

private function updateTry($intIDUser, $intCount=1) { $strQuery = 'UPDATE users SET trycount=:trycount,

lasttry=:lasttry WHERE iduser=:iduser';

$objStament = $this->objDBConnection->

prepare($strQuery);

$objStament->bindParam(':lasttry', time(), PDO::PARAM_INT);

$objStament->bindParam(':trycount', $intCount,

PDO::PARAM_INT);

$objStament->bindParam(':iduser', $intIDUser,

PDO::PARAM_INT);

$objStament->execute();

$objStament->fetchAll();

}

}

?>

Page 74: PHP Solutions 06 2006 PL

www.phpsolmag.org74 PHP Solutions Nr 6/2006

PHPUnit2Technika

Listing 10. Niemal poprawna klasa Authorization – wydawało się że klasa powinna działać poprawnie, tymczasem okazuje się, że nie przechodzi ona trzeciego testu

<?php

class Authorization { const OK = 0; const ERROR_UNAUTHORIZED = 1; const ERROR_3TIMES = 2; const ERROR_IP = 3; private $objDBConnection; private $intLockAfter = 3; private $intLockTime = 180; private $strName = null; private $strSurname = null; private $intLastLogin = null; private $intLockFor = null;

public function __construct(PDO $objDBConnection) { $this->objDBConnection = $objDBConnection;

}

public function check($strLogin, $strPassword) { $strQuery = 'SELECT * FROM users WHERE login =

:login';

$objStament = $this->objDBConnection->

prepare($strQuery);

$objStament->bindParam(':login', $strLogin,

PDO::PARAM_STR, 50);

if ($objStament->execute()===false) { return false; }

$arrUser = $objStament->fetchAll();

if (count($arrUser) === 1) { $arrUser = $arrUser[0];

if (strcmp($arrUser['password'], md5($strPassword)) === 0) {

if ($arrUser['ip']!==null) {

if (!isset($_SERVER['REMOTE_ADDR']) || strcmp(long2ip($arrUser['ip']), $_SERVER['REMOTE_ADDR']) !== 0) {

$this->updateTry($arrUser['iduser'],

$arrUser['trycount']+1);

return self::ERROR_IP; }

}

$this->updateLogin($arrUser['iduser']);

$this->intIDUser = intval($arrUser['iduser']); $this->strName = $arrUser['name'];

$this->strSurname = $arrUser['surname'];

$this->intLastLogin = intval( $arrUser['lastlogin']);

return self::OK; } else { if ($arrUser['trycount']>=$this->intLockAfter && ($arrUser['lasttry']+$this->

intLockTime)>time()) { $this->intLockFor = intval(((($arrUser[ 'lasttry']+$this->intLockTime)-

time())/60)+1); return self::ERROR_3TIMES; } else { if ($arrUser['trycount']>=$this-> intLockAfter && ($arrUser['lasttry']+

$this->intLockTime)<time()) {

$this->updateTry($arrUser['iduser']);

} else { $this->updateTry($arrUser['iduser'],

$arrUser['trycount']+1);

}

return self::ERROR_UNAUTHORIZED; }

}

}

return self::ERROR_UNAUTHORIZED; }

public function getID() { return $this->intIDUser; }

public function getName() { return $this->strName; }

public function getSurname() { return $this->strSurname; }

public function getLastLogin() { return $this->intLastLogin; }

public function getLockFor() { return $this->intLockFor; }

private function updateLogin($intIDUser) { $strQuery = 'UPDATE users SET lastlogin=:lastlogin,

trycount=0, lasttry=0 WHERE iduser=

:iduser';

$objStament = $this->objDBConnection->

prepare($strQuery);

$objStament->bindParam(':lastlogin', time(), PDO::PARAM_INT);

$objStament->bindParam(':iduser', $intIDUser,

PDO::PARAM_INT);

$objStament->execute();

$objStament->fetchAll();

}

private function updateTry($intIDUser, $intCount=1) { $strQuery = 'UPDATE users SET trycount=:trycount,

lasttry=:lasttry WHERE iduser=:iduser';

$objStament = $this->objDBConnection->

prepare($strQuery);

$objStament->bindParam(':lasttry', time(), PDO::PARAM_INT);

$objStament->bindParam(':trycount', $intCount,

PDO::PARAM_INT);

$objStament->bindParam(':iduser', $intIDUser,

PDO::PARAM_INT);

$objStament->execute();

$objStament->fetchAll();

}

}

?>

Page 75: PHP Solutions 06 2006 PL
Page 76: PHP Solutions 06 2006 PL

www.phpsolmag.org76 PHP Solutions Nr 6/2006

PHPUnit2Technika

gu 10 znajduje się pełna klasa nasze-go pakietu.

Test buduje się tworząc instancję klasy PHPUnit2_Framework_TestSuite. Następnie korzystając ze stworzonego obiektu możemy dodawać kolejne testy do naszego pakietu. Służy do tego me-toda addTest(). Jako parametr tej me-tody podajemy obiekt testu który doda-jemy (konstruktor klasy przyjmuje na-zwę metody z testem który chcemy uru-chomić).

Plik zawierający klasę z pakietem mu-si posiadać nazwę klasy i rozszerzenie .php.

Uruchomienie pakietu testów przebie-ga analogicznie do uruchomienia pojedyn-czego testu:

phpunit UsertTestSuite

Na Rysunku 5 znajduje się przykła-dowy wynik działania pakietu z Listin-gu 10.

Testyniekompletne, anulowanie testuJeśli niektóre z testów nie są jeszcze ukoń-czone, można poinformować o tym frame-work, zwracając w metodzie testu wyjątek PHPUnit2_Framework_IncompleteTestError.

W jego konstruktorze można opcjonalnie podać dodatkową informację:

throw new PHPUnit2_Framework_

IncompleteTestError('

Listing 11. Klasa testująca klasę User

<?php

require('../classes/User.class.php');require('DBConnection.php');

class UserTest extends PHPUnit2_Framework_TestCase { protected $objUser = null; protected $objDBConnection = null;

public function setUp() { $this->objDBConnection = DBConnection::

getDBConnection();

$this->objUser = new User($this->objDBConnection); }

public function testAddUser1() { $arrParams = array( 'name' => 'Jan',

'surname' => 'Kowalski',

'login' => 'test',

'password' => '123456',

);

$blnResult = $this->objUser->add($arrParams);

$this->assertTrue($blnResult);

$strQuery = 'SELECT * FROM users WHERE login =

:login';

$objStament = $this->objDBConnection->

prepare($strQuery);

$strLogin = 'test';

$objStament->bindParam(':login', $strLogin,

PDO::PARAM_STR);

$objStament->execute();

$arrResult = $objStament->fetch(PDO::FETCH_ASSOC);

$arrParams['password'] = md5($arrParams['password']); $intArrCount = count(array_diff($arrParams, $arrResult));

$this->assertTrue($intArrCount==0,

'Dodano niepoprawne dane');

}

public function testAddUser2() { $arrParams = array( 'name' => 'Jan',

'surname' => 'Kowalski',

'login' => 'testIP',

'password' => '123456',

'ip' => '199.199. 199.199'

);

$blnResult = $this->objUser->add($arrParams);

$this->assertTrue($blnResult);

$strQuery = 'SELECT * FROM users WHERE login =

:login';

$objStament = $this->objDBConnection->

prepare($strQuery);

$strLogin = 'testIP';

$objStament->bindParam(':login', $strLogin,

PDO::PARAM_STR);

$objStament->execute();

$arrResult = $objStament->fetch(PDO::FETCH_ASSOC);

$arrParams['password'] = md5($arrParams['password']); $arrParams['ip'] = ip2long($arrParams['ip']);

$intArrCount = count(array_diff($arrParams, $arrResult));

$this->assertTrue($intArrCount==0,

'Dodano niepoprawne dane');

}

public function testRemoveUser1() { $this->objUser->remove('test', '123456');

$strQuery = 'SELECT * FROM users WHERE login =

:login';

$objStament = $this->objDBConnection->

prepare($strQuery);

$strLogin = 'test';

$objStament->bindParam(':login', $strLogin,

PDO::PARAM_STR);

$objStament->execute();

$arrResult = $objStament->fetchAll();

$this->assertTrue(count($arrResult)==0, 'Nie usuni─Öto usera');

}

public function testRemoveUser2() { $this->objUser->remove('testIP', '123456');

$strQuery = 'SELECT * FROM users WHERE login =

:login';

$objStament = $this->objDBConnection->

prepare($strQuery);

$strLogin = 'testIP';

$objStament->bindParam(':login', $strLogin,

PDO::PARAM_STR);

$objStament->execute();

$arrResult = $objStament->fetchAll();

$this->assertTrue(count($arrResult)==0, 'Nie usunięto usera testIP');

}

}

?>

Page 77: PHP Solutions 06 2006 PL

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

PHPUnit2 Technika

Test ten nie został jeszcze

ukończony');

Jeśłi chcesz skorzystać z klasy wyjątku PHPUnit2_Framework_IncompleteTestError, musisz dołączyć do klasy testu następu-jący skrypt, wchodzący w skład frame-worka PHPUnit2 - PHPUnit2/Framework/

IncompleteTestError.php.

require_once(‘PHPUnit2/Framework/

IncompleteTestError.php’);

Częściej spotykaną sytuacją jest zależ-ność wykonania testu od zainstalowa-nia jakiegoś rozszerzenia do PHP, czy obecności w systemie dodatkowych bi-bliotek, wymaganych do działania da-nej funkcjonalności projektowanej kla-sy, lub od wcześniejszego, poprawne-go wykonania się innych testów. Jeśli funkcjonowanie pewnych elementów naszej klasy jest zależne od czynników zewnętrznych, oraz gdy ich obecność możemy zbadać z poziomu testów, mo-żemy w razie konieczności poinformo-wać PHPUnit2 że chcemy dany test po-minąć (niestety ta metoda działa jedy-nie w wersji 3.0.0, która nie jest jesz-cze stabilna i nie jest w związku z tym standardowo pobierana w czasie insta-lacji z PEAR):

$this->markTestSkipped

('Wymagane rozszerzenie XYZ jest

niedostępnę!');

Inne możliwości PHPUnit2To co zostało zaprezentowane w tym artykule to jedynie wycinek możliwości PHPUnit2. Framework ten pozwala na przykład sprawdzić nasze testy pod kon-tem pokrycia testami testowanej klasy. Wystarczy wówczas nieco zmodyfiko-wać wywołanie phpunit:

phpunit --coverage-html coverage.html

UserTestSuite

Dzięki temu PHPUnit2 wygeneruje podsu-mowanie na temat przetestowanych frag-mentów naszej klasy, w postaci strony in-ternetowej. Jednak aby strona ta była dla nas czytelna, musimy przygotować so-bie plik CSS, formatujący ją w odpowied-ni sposób.

PHPUnit2 pozwala zapisać wy-nik pokrycia kodu także w pliku txt, lub w serializowanej tablicy, którą z łatwo-ścią można obrabiać z poziomu skryp-tów PHP.

PHPUnit2 pozwala także na łatwe wy-generowanie prostej dokumentacji testu w formacie HTML lub txt:

phpunit --testdoc-html doc.html

UserTestSuite

phpunit --testdoc-text doc.txt

UserTestSuite

Jeśli natomiast mamy już gotową kla-sę dla której chcemy przygotować te-sty, wystarczy wydać następujące pole-cenie w katalogu w którym znajduje się nasza klasa:

phpunit --skeleton NazwaKlasy

Spowoduje to wygenerowanie szkieletu klasy testowej o nazwie NazwaKlasyTest. PHPUnit2 wygeneruje zbiór metod, które powinieneś wypełnić kodem, tak aby prze-testowały każdą z metod testowanej klasy. Wygenerowane metody możesz swobod-nie modyfikować – od zmiany ich nazwy aż po całkowite usunięcie.

PodsumowanieMam nadzieję, że artykuł ten przyczynił się do zainteresowania Cię testami jed-nostkowymi. Mimo iż wiele osób na po-czątku podchodzi do nich bardzo scep-tycznie i nie chce „tracić czasu” na ich budowę, to jednak po kilku próbach sa-mi oni stwierdzają że to jest to. Aplika-cje internetowe bardzo często szybko się rozwijają.

Dodawanie nowych elementów po-woduje wzrost prawdopodobieństwa pojawienia się w nowych fragmentach błędów, które mogą się okazać trudne do odnalezienia, a co gorsze mogą po-wodować błędne działanie innych, sta-rych, części aplikacji. W takich wypad-kach testy jednostkowe bardzo często pomagają w szybkim wyłapaniu błę-dów, które w innym wypadku mogły by się ujawnić po pewnym czasie dopiero u klienta. n

Marcin Staniszczak jest studentem pierwszego roku informatyki studiów uzupełniających magisterskich na WSHE w Łodzi. W PHP programuje od wielu lat. Jest autorem kilkunastu publikacji o tematyce PHP i Interne-towej. Jest autorem framework MVC dla PHP5 (web.framework) oraz sys-temu szablonów dla PHP5 (web.tem-plate).

Kontakt z autorem:[email protected]

O autorze

Rysunek 4. Wynik testu przeprowadzonego na klasie z Listingu 8 jednoznacznie wskazuje miejsce wystąpienia błędu

Rysunek 5. Wynik działania pakietu testów z Listingu 10 – plik UserTestSuit.php

Page 78: PHP Solutions 06 2006 PL

www.phpsolmag.org78

Felieton

PHP Solutions Nr 6/2006

Paskudne technologieMichał Małecki

Ktoś, kto siedział przez dłuższy czas w biznesie związanym z oprogramo-waniem, zauważył już na pewno, ja-

ka jest różnica między informatyką, a inżynie-rią oprogramowania. Inżynieria, nie istniałaby bez nauki. Ale dopiero inżynieria oprogramo-wania jest w stanie uzasadnić, czy ten rozwój informatyki rozwojowi cywilizacji, czy też jest jedynie twórczym wysysaniem sobie z pal-ca. W końcu środowiska naukowe potrze-bują funduszy do swojego działania, a jeśli nowe technologie opracowane w tych śro-dowiskach nie dają się wykorzystać do ro-bienia pieniędzy, to nie ma mowy o pienią-dzach na badania. To z kolei, potrafi w wie-lu wypadkach prowadzić do zaniechania ba-dań nad tym, co potencjalnie mogłoby ca-łemu „biznesowi” przynieść korzyści, ale ja-ki jest „potencjał” tych korzyści nikt nie potra-fi ocenić. A przedsiębiorcy najczęściej ryzyku-ją wystarczająco dużo, żeby w kwestii tech-nologii pozwolić sobie na taktykę „lepszy wró-bel w garści”.

W każdym zawodzie istnieje kwestia „własnych upodobań” co do narzędzi pracy i informatycy nie są wyjątkiem. Prowadzi to często do dyskusji, w których rozdział mię-dzy własnym gustem a argumentami natury technicznej jest często bardzo płynny.Każdy wie, że takie kryterium istnieje, każdy intuicyj-nie wyczuwa, że jakoś-tam to jest określone, ale na pytanie, czy można zrobić jakiś wery-fikator kodu, który sprawdzałby czytelność programu, każdy programista odpowie jed-noznacznie przecząco. Oczywiście, mamy wiele narzędzi do weryfikacji kodu, różne lin-ty, klocworki itp. bajery, ale jeśli ktoś sądzi, że istnieje jakiś związek pomiędzy np. złożono-ścią cyklomatyczną, a czytelnością kodu...

Światową gospodarką rządzi biznes. To skądinąd banalne stwierdzenie do dużej części ludzi zaangażowanych w jej rozwój dociera tak trochę nie do końca. Fakty z tego wynikające są przyjmowane z trudem, zwłaszcza gdy są nie po czyjejś myśli.

Czytelność to przykład, i to jeszcze bardzo praktyczny. Gorzej z kryteriami ty-pu „piękno”, „estetyka”, czy „elegancja”. Co prawda ostatnio jak spytałem o to, czy mo-że to być ścisłe kryterium, zostałem odesła-ny do jakichś stron na Wikipedii (np. Mathe-matical beauty), które ze śmiertelną powagą twierdzą, że matematycy czerpią estetycz-ną przyjemność ze swojej pracy. Co prawda już samo porównanie piękna liczb do piękna 9-tej symfonii Beethovena powinno sprowa-dzić na ziemię tych, którym się wydaje, że te kryteria mają coś wspólnego z jakąkolwiek inżynierią, ale to wielu nie przeszkadza na-rzekać na wszystkie współcześnie używa-ne w inżynierii języki programowania, że są paskudne. Oto na przykład czytam, jakim to wspaniale eleganckim językiem jest Pro-log. Popatrzyłem na podpowiedziane przy-kłady aplikacji GUI w Prologu i stwierdziłem, że... są straszliwie nieczytelne. Gdzie im do takiego np. Tk! I co, mam znów powtarzać to banalne i oklepane stwierdzenie "kwe-stia gustu"? Przemysłu to i tak nie interesu-je, bo w przemyśle używa się takich techno-logii, jakie przynoszą zysk, a nie takich, jakie są „eleganckie”. A robienie interesów to nie jest czysta i piękna sprawa, tylko kombino-wanie i robienie wszystkich dookoła w bam-buko, dokładnie tak, jak to robi Microsoft, czy Sun. I wygrywa ten, komu się ta sztuka uda, a nie ten, kto stworzy lepszą, czy bar-dziej elegancką technologię.

To oczywiście powoduje, że często „pa-skudne technologie zwyciężają” i biorą gó-rę nad tymi, które są „estetyczne” i „eleganc-kie”, a gorsze technologicznie narzędzia bio-rą górę nad lepszymi. Oczywiście to gorsze za jakiś czas staje się lepsze, bo się rozwija,

a rozwija się, bo ma kto w niego inwestować. Tak przecież stało się z komputerem IBM PC, który u swojego zarania (XT/AT) był naj-gorszym szmelcem wśród sprzętu kompute-rowego, ale dzięki otwartości pokonał znacz-nie lepsze od siebie komputery Amiga i Ata-ri ST, o których dziś chyba mało kto pamięta, a i na „ogryzka” bym nie stawiał. Wcale nie jest powiedziane, że rozwój tego „gorszego” w efekcie kosztował więcej, niż kosztowałby rozwój tego „lepszego”. Jak przemysł nie do-rósł do określonych rozwiązań, to widocznie przemysłowi jest z tym dobrze. Tak jak to było z maszyną parową, która została wynalezio-na już w starożytnym Egipcie, ale praktyczne zastosowanie znalazła dopiero w XIX wieku, bo wcześniej nie było na nią zapotrzebowa-nia. Dlatego też w technologiach stosowa-nych w przemyśle gorąco polecam dale-ko posunięty konformizm i trzymanie się z daleka od wszelkich ideologii. Nie oznacza to oczywiście, że nie należy być twórczym i nie proponować dobrych pomysłów. Ale trzeba pamiętać, że „każdy chce robić to, co chce, ale chce, żeby za to, że on robi to, co chce, płacili mu inni, którzy nie chcą, żeby on robił to, co chce, ale chce, żeby robił to, czego oni chcą”. n

Autor interesuje się psychologią, progra-mowaniem, muzyką, publicystyką i ję-zykoznawstwem, a w wolnych chwilach pracuje etatowo jako programista.

Kontakt z autorem:[email protected]

O autorze

Page 79: PHP Solutions 06 2006 PL

Serwis prezentujący prace kobiet, zajmujących się zawodowo bądź dla własnej przyjemności grafiką, webdesignem, rysunkiem, fotografią, malarstwem, rzeźbą, itp.

womaninaction.net

Jeden z ważniejszych polskich portali dla we-bmasterów i programistów PHP. Istnieje już cał-kiem długo, co potwierdza zawartość zarówno serwisu jak i forum.

webcity.pl

Oficjalny serwis magazynu PHP Solutions. Można stąd pobrać przykładowe artykuły, li-stingi z artykułów, skorzystać z forum, bądź za-poznać się z aktualnościami.

www.phpsolmag.org

Serwis poświęcony zagadnieniom związanym z komputerami klasy PC, choć nie tylko. Prezen-towane są tam informacje związane z Open So-urce i światem Internetu.

pcmaniak.pl

Oficjalny serwis czytelników magazynu Softwa-re Developer’s Journal. Często aktualizowany, posiada wszystkie informacje przydatne czytel-nikom magazynu, ale nie tylko.

www.software20.org

Obszerny i bogaty w informacje serwis poświę-cony Sieci. Zawiera informacje dotyczące bez-pieczeństwa, programowania, sieci bezprze-wodowych i inne.

www.webhat.pl

Oficjalny serwis magazynu Hakin9. Można stąd pobrać darmowe artykuły, zapoznać się z zawartością aktualnych i archiwalnych nume-rów lub skorzystać z bogatego forum.

www.haking.pl

Serwis poświęcony aktualnej, piątej wersji ję-zyka PHP. Jego zwięzła i skromna forma nie rozprasza i pozwala szybko odnaleźć szukane informacje, które zawarto w serwisie.

www.php5.pl

Dziennik Internautów – czyli gazeta dla użyt-kowników Internetu, to bogaty i dobrze zapla-nowany serwis prezentujący informacje pogru-powane w działy.

www.di.com.pl

Recommended sites >>>

Recommended sites

Jeśli prowadzisz ciekawą stronę internetowąi chcesz abyśmy przedstawili ją w ramach akcji Recommended sites,

skontaktuj się z nami pod adresem [email protected]

Page 80: PHP Solutions 06 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: PHP Solutions 06 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., Bokserska 1, 02-682 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+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 12 1991/219

.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: PHP Solutions 06 2006 PL

Ponadto planujemy:

■ Ebay w PHP Solutions,

■ Nowinki i rozwiązania dla płatności internetowych

■ Kasa z Twojej strony,

■ Testy konsumenckie firm hostingowych.

ROZWIĄZANIA Wielojęzyczne portale z wykorzystaniem eZ publish Przedstawimy krok po kroku jak postawić 3-języczny

portal newsowy z wykorzystaniem eZ publish

W następnym numerze

W sprzedaży od 15 grudnia!

PHP Solutions 1/2007 (18)

NARZĘDZIA Flashowe interfejsy WWW, czyli PHP i Action Script w akcji Pokażemy, jak wykorzystać najnowsze możliwości

Flasha do budowania zaawansowanych GUI dla apli-kacji PHP. Przedstawimy zalety programowania zda-rzeniowego i frameworka AsWing.

ROZWIĄZANIA XAJAX – łatwy AJAX dla programistów PHP Pokażemy jak zrealizować formularz logowania, któ-

ry notyfikuje bez przeładowania o błędnym loginie i/lub haśle. Wskażemy też gdzie, po kliknięciu zakład-ki ładowania jest nowa strona. Oraz: dodaj/usuń do/z ulubionych–realizujemy za pomocą AJAX.

Page 83: PHP Solutions 06 2006 PL
Page 84: PHP Solutions 06 2006 PL