PHP Solutions 05 2006 PL

84

description

* Wywiad z Tobiasem Schlittem, jednym z głównych deweloperów platformy eZ components* Łączymy DB2 i PHP* Zaawansowane ataki SQL Injection* TYPO3 od kuchni, czyli wymarzony portal w zasięgu ręki* Freelancing – zostań wolnym strzelcem* Zend API – tworzymy własne rozszerzenie dla PHP* Budujemy własny kontener IoC, czyli jak to się robi w Hollywood?* XML i PHP w praktyce* Własne Google Video, czyli video streaming w PHP

Transcript of PHP Solutions 05 2006 PL

Spis treści

www.phpsolmag.org4 PHP Solutions Nr 5/2006

Spis treści

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

W Egipcie nas nie lubią!W redakcji uważamy, że lepiej jest robić pismo Dla deweloperów PHP, niż pismo O PHP. Różnica między Dla i O jest dość istotna. W pierwszym przy-padku pismo traktuje o wszystkim, co może potencjalnie zainteresować i przydać się programiście PHP. Natomiast pismo O PHP jest poświęcone wyłącznie (lub prawie) tej technologii.

My tworzymy magazyn w pierwszym wariancie. Dlatego w obecnym numerze znajdziecie artykuł o video streamingu, w którym omawiamy jak opublikować własną galerię filmów we Flashu na WWW, na wzór popular-nego Google Video. Dowiecie się też, jak zarobić na PHP i czy opłaca się zostać Freelancerem.

Jeśli interesuje Was, co IBM ma wspólnego z PHP, zachęcam do artyku-łu o łączeniu DB2 i PHP. Ten mariaż to dowód na ogromne zainteresowanie gigantów informatycznych naszą technologią. Przedstawimy też bardzo faj-nego i profesjonalnego CMS-a – TYPO3. System w nowej odsłonie oferuje naprawdę ogromne możliwości. Jeśli szukasz CMS-a do stworzenia własnej witryny, koniecznie musisz zapoznać się z TYPO3! Bardzo prawdopodobne, że to właśnie na niego padnie wybór.

Nie zabraknie też artykułów dla bardziej zaawansowanych: pokażemy, jak budować elastyczne aplikacje w oparciu o kontenter IoC, czy tworzyć własne rozszerzenia dla PHP z wykorzystaniem Zend API.

Na koniec ciekawostka: mapka zamieszczona poniżej przedstawia wy-korzystanie PHP w różnych regionach świata. Kolor zielony oznacza naj-większe (np. Ukraina – 69%), a czerwony najmniejsze zainteresowanie (np. Egipt 3,93 %) technologią PHP. Kolor żółty oznacza średnią. Z rysunku wyni-ka, że najbardziej lubią nas na Ukrainie i w Rosji. Kto wie, może zaczniemy wydawać w tych językach).

Przyjemnej lektury,Redakcja PHP Solutions

AKTUALNOŚCI 6

Przedstawiamy garść najciekawszych wiadomo-ści dla developerów PHP.

Opis CD 8

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

WYWIADWywiad z Tobiasem Schlittem, jednym z głównych deweloperów platformy eZ components 10

Dariusz Pawłowski

Tobias pracuje jako deweloper w firmie eZ sys-tems. Obecnie zajmuje się rozwijaniem platfor-my eZ components. Jest dobrze znanym eksper-tem PHP. Udziela się też m.in. w projekcie PE-AR, gdzie rozwija kilka pakietów.

DLA POCZĄTKUJĄCYCHŁączymy DB2 i PHP 12

Artur Wroński, Piotr Pietrzakk

Tworzysz rozbudowaną aplikację, którą bę-dziesz często rozbudowywał i chcesz uniknąć żmudnych, czasochłonnych i podatnych na błę-dy modyfikacji struktury tabel i relacji bazodano-wych. Przedstawiamy Ci DB2: solidną i rozbu-dowaną bazę, która umożliwia przechowywanie i operacje na danych w postaci XML-owej.

BEZPIECZEŃSTWOZaawansowane ataki SQL Injection 20

Mike Shema

Ataki SQL Injection są wymierzone w trzon apli-kacji webowej – bazę danych – i umożliwiają in-truzowi zdobycie, modyfikację lub usunięcie do-wolnych danych. Zrozumienie zasad działania SQL Injection jest konieczne do wypracowania odpowiednich metod obrony.

NARZĘDZIATYPO3 od kuchni, czyli wymarzony portal w zasięgu ręki 28

Jean-Gael Rouchon

Zarządzasz witryną internetową, która ma zhie-rarchizowaną strukturę stron, a ich zawartość jest uzupełniana przez wielu redaktorów. Zależy Ci na pełnej swobodzie projektowania tego sajtu oraz łatwości jego tworzenia i rozbudowy. Przed-stawiamy TYPO3: potężny, elastyczny i solidny system CMS klasy Enterprise, który jest łatwy w rozbudowie, co umożliwia ciągłe dostosowywa-nie go do Twoich potrzeb.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

Źródło: http://www.nexen.net/

Spis treści

www.phpsolmag.org4 PHP Solutions Nr 5/2006

Spis treści

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

KASA DLA WEBMASTERAFreelancing – zostań wolnym strzelcem 38

Krzysztof Trynkiewicz

Jesteś programistą i chcesz wziąć udział w cie-kawym projekcie i zarobić trochę pieniędzy? A może potrzebujesz kogoś, kto wykona dla Cie-bie witrynę internetową, aplikację dla księgowo-ści czy grafikę? Dzięki serwisom freelancingo-wym każdy z Was znajdzie to, czego potrzebuje przy minimalnym lub żadnym ryzyku.

DLA ZAAWANSOWANYCHZend API – tworzymy własne rozszerzenie dla PHP 42

Marcin Staniszczak

Twój skrypt działa zbyt wolno? Wydaje Ci się, że przyczyna tkwi w wydajności PHP? A mo-że chcesz połączyć się z inna aplikacją lub wy-korzystać swoją ulubiona bibliotekę z C? Roz-wiązaniem Twoich problemów może okazać się Zend API.

Budujemy własny kontener IoC, czyli jak to się robi w Hollywood? 54

Piotr Szarwas

Wyobraźmy sobie, że firma, dla której stworzy-liśmy aplikację, po jakimś czasie powiększyła się znacznie i poprosiła nas o migrację baz da-nych do jednej centralnej pracującej w oparciu o LDAP. Niestety, jeśli architektura naszej aplika-cji nie została zaprojektowana prawidłowo, cze-ka nas wyjątkowo żmudna i ciężka praca.

XML i PHP w praktyce 62

Guillaume Ponçon

Bazy danych, dokumenty biurowe, RSS: co-raz wicej formatów gromadzenia i przesyania danych opiera si na XML-u. Jego gówn zalet jest atwo tworzenia i przetwarzania dokumen-tów XML niezalenie od platformy sprzętowej i systemowej.

Własne Google Video, czyli video streaming w PHP 72

Rafał Malinowski

Zastanawiałeś się, jak działa odtwarzanie fil-mów z poziomu WWW? Podoba Ci się Go-ogle Video? Poznaj video streaming od kuchni i stwórz własną, webową galerię filmów. Wystar-czy podstawowa znajomość PHP. To wszystko!

Zapowiedzi 82

Zapowiedzi, artykułów, które planujemy do na-stępnego wydania naszego pisma.

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

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

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

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

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

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

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

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

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

Dyrektor Wydawniczy: Jarosław SzumskiProduct Manager: Adam Urbanowski [email protected] współpracujący: Dariusz Pawłowski [email protected], Krzysztof SobolewskiStali współpracownicy: Paweł Kozłowski [email protected], Krzysztof TrynkiewiczKierownik produkcji: Marta Kurpiewska [email protected] okładki: Agnieszka MarchockaSkład i łamanie: Sławomir Zadrożny [email protected]ł reklamy: [email protected]: Marzena Dmowska [email protected]ład: 6 000 egz.

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

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

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

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

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

Druk: ArtDruk

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

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

Aktualności

PHP Solutions Nr 5/2006www.phpsolmag.org6

IBM dla PHPIBM w coraz większym stopniu zauważa po-tencjał PHP i możliwości jego wykorzystania w oprogramowaniu przeznaczonym dla biznesu. Projekt PHP Integration Kit for WebSphere Ap-plication Server (WAS) Community Edition (CE) ma za zadanie zintegrowanie PHP z aplikacja-mi J2EE, w szczególności z architekturą SOA (service-oriented architecture). WAS Commu-nity Edition to lekki serwer aplikacji Java opar-ty o opensourcowy serwer Apache Geronimo. Darmowy PHP Integration Kit pozwoli programi-stom PHP swobodnie rozwijać swoje aplikacje na bazie WAS. Pozwoli to na wykorzystanie w jednym miejscu zalet PHP i Javy, wpływając na elastyczność i lepszy rozwój projektów.http://www.alphaworks.ibm.com/tech/phpintwasce

phundament – 100% czyste-go PRADOTo framework oferujący komponenty do budo-wy aplikacji WWW napisane w PRADO 3. Pro-jekt narodził się z idei stworzenia lekkiego, sta-bilnego i zorientowanego obiektowo kodu, który może być łatwo rozszerzany i wielokrotnie uży-wany. Poza PRADO phundament wykorzystu-je takie narzędzia jak: Propel (narzędzie ORM, o którym pisaliśmy w numerze 4/2005), Creole, phing, ImageMagic i oczywiście PHP5. Na stro-nie projektu znajdziemy dokumentację, wersję demo, a nawet tutoriale video.

ApacheCon Europe 2006W dniach 29-30 czerwca 2006 w Dublinie, w Ir-landii, odbyła się kolejna edycja konferencji po-święconej Apache'owi. Jej sponsorami byli tacy giganci jak Microsoft, Sun, Intel czy Google.Poza tematami stricte związanymi z Apache, poruszano też zagadnienia związane z budo-waniem aplikacji w oparciu o PHP (np. PHP 6 & Unicode: The Tower of Babel - Next Gene-ration, Building a Fast and Rich Web App with PHP 5, Agile PHP Testing).http://www.eu.apachecon.com/

DC PHP Conference 2006W dniach 18-20 października w Waszyngtonie, w USA odbędzie się konferencja poświecona w całości PHP. Przeglądając sesje tematyczne, można odnieść wrażenie, że poruszone zosta-ną niemal wszystkie najważniejsze zagadnie-nia związane z tworzeniem aplikacji w PHP (np. bezpieczeństwo, wydajność i skalowalność, na-rzędzia, biblioteki, techniki oraz zastosowania biznesowe PHP).http://dcphpconference.com/

Forum PHP 2006W dniach 9-10 listopada 2006 w Paryżu, odbę-dzie się już piąta edycja spotkania dewelope-rów PHP.Wśród speakerów wystąpią między innymi Ra-smus Lerdorf (twórca PHP), czy Wez Furlong (jeden z deweloperów PDO).http://www.afup.org/pages/forumphp/english.php

eZ Newsletter – coś więcej niż system newsletteraTo bardzo rozbudowany, kompletny system do email marketingu stworzony przez firmę eZ sys-tems. Program kontroluje wszystko, co związa-ne jest z wysyłką mailową: od przygotowania kampanii, przez samą wysyłkę, aż po integrację z systemami sprzedaży online czy ERP. Takie podejście ma zmaksymalizować efektywność marketingu bezpośredniego, zwiększając tzw. feedback rate (który obecnie wynosi ok. 5 %),a w konsekwencji sprzedaż na WWW. Dodat-kowo system współpracuje z telefonami komór-kowymi, faksem i PDA.http://ez.no/products/solutions/newsletter

phpMyVisites

phpMyVisites to darmowe (GNU/GPL) narzędzie do sporządzania i wyświe-

tlania statystyk. Trudno systemowi cokol-wiek zarzucić: oferuje wszystkie najważ-niejsze i przydatne informacje dotyczące oglądalności witryny. Interfejs jest prak-tyczny i intuicyjny phpMyVisites, a insta-lacja nie stanowi najmniejszego proble-mu. Działają tak, jak większość tego typu rozwiązań: na wybranej witrynie umiesz-czamy prosty kod JavaScript zliczający wizyty. Informacje prezentowane użyt-kownikowi są przedstawione w prosty i praktyczny sposób. Do programu może-my dodawać dowolną liczbę stron, pod które podepniemy statystyki.

W phpMyVisites możemy zarządzać użytkownikami, którzy otrzymują pra-wa do odczytu/administracji dla konkret-nych stron.

Statystki przedstawiają wiele in-teresujących informacji o odwiedzają-cych nasze witryny, m.in: lokalizacja geograficzna, dane techniczne (system operacyjny, rozdzielczość komputera),

Zend Studio 5

Nieprzerwany sukces języka PHP po-ciąga za sobą ciągły rozwój coraz

bardziej zaawansowanych środowisk pro-gramistycznych (rozbudowanych edyto-rów) zwanych IDE (Integrated Develop-ment Environment). Wymagania wobec tych narzędzi stale rosną, dlatego PHP-owe edytory rozwijają się w dość szyb-kim tempie.

Wśród IDE dla PHP prym wiedzie nieprzerwanie Zend Studio. Nowe wcie-lenie tego IDE przynosi nam kilka bardzo ważnych funkcji i udogodnień. Już pod-czas uruchamiania aplikacji możemy ją debugować i podglądać efekt działania w zintegrowanym z IDE Internet Explorerze. Żeby programiści nie wchodzili sobie na głowę, mogą korzystać nie tylko z CVS-a, ale również z Subversion, czyli z syste-mów kontroli wersji (Subversion jest now-szym i bardziej rozbudowanym narzę-dziem). Dodano również możliwość połą-czenia sFTP (secure FTP z wykorzysta-niem SSL) i polepszono funkcję podpo-wiadania składni języka PHP. Zend pod-powiada praktycznie wszystko to, co zna-leźć można w manualu PHP, łącznie z na-zwami własnych funkcji (wystarczy raz

stworzyć funkcję, aby potem była podpo-wiadana przez system), czy dokumenta-cją (która dostępna będzie właśnie pod-czas podpowiadania nazw funkcji). Drze-wiasta struktura plików w oknie IDE po-zwala na przeciąganie ich myszką np. z FTP do dowolnie wybranej lokalizacji. Na uwagę zasługuje też integracja Zend Stu-dio z Web Serwisami (generowanie pli-ków WSDL bezpośrednio z kodu PHP).

Nie ma jednak róży bez kolców: nowe IDE w wersji professional kosztuje $299 (cena wersji standardowej to $99).

Licencja: komercyjnahttp://www.zend.com/products/zend_studio/professional_edition

długość/częstość przebywania na stro-nie, strony odsyłające do naszej witryny czy nawet słowa kluczowe jakie były po-dawane podczas korzystania z wyszuki-warki na stronie. Dodatkowo, raport mo-że zostać wysłany mailem lub rozprowa-dzany za pomocą RSS.

Program dostępny jest w 29 wersjach językowych.

Licencja: GNU/GPLhttp://www.phpmyvisites.us/

Aktualności

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

eZ publish Online Editor – teraz za darmo!Edytor WYSIWYG, za który jeszcze nie dawno trzeba było zapłacić $99, teraz dostaniemy zu-pełnie za darmo. System początkowo sprzeda-wany był jako dodatkowo płatny komponent do CMS-a eZ publish. Produkt oferuje wszystkie funkcje dobrego WY-SIWYG-a. Obsługuje kilka języków w tym pol-ski, francuski, hiszpański i włoski. Pracuje z In-ternet Explorerem od wersji 5.5, jak również z przeglądarkami opartymi na Mozilli. Na stronie projektu można wypróbować wersje DEMO.http://ez.no/products/add_ons/ez_publish_on-line_editor

DutchPIPETo projekt, który oferuje nowe podejście two-rzenia stron dla większej grupy użytkowników. Strona staję się abstrakcyjnym światem zbu-dowanym na obiektach, które mogą być swo-bodnie przemieszczane między poszczególny-mi sajtami. Jednemu światu odpowiada jeden określony zestaw obiektów. Jeśli umieścisz jakiś obiekt na stronie, ktoś inny może z niego skorzystać i przenieść go do siebie. DutchPIPE może być wykorzystany do budo-wania różnych internetowych społeczności, dla chatroomów, gier RPG czy sklepów online. Ogranicza nas tylko wyobraźnia.Licencja: MIThttp://dutchpipe.org/

Zend Framework 0.1.4Pojawiła się kolejna wersja rozwojowa Zend Frameworka. Dodano nowe komponen-ty oraz dokumentację w 10 językach. Na stro-nie projektu stworzono między innymi Com-munity Wiki, system do zgłaszania własnych pomysłów/zmian, czy miejsce do śledzenia zmian (Issue tracker) w projekcie. Naprawiono też wiele błędów oraz dokonano wiele ulepszeń np. możliwość stosowania czcionki TrueType w generowanych dokumentach PDF czy wspra-cie dla bazy DB2.Licencja: BSDhttp://devzone.zend.com/node/view/id/606

PHP-QTDostępne jest już trzecie wydanie PHP-Qt, roz-szerzenia dla PHP5, które pozwala na wykorzy-stanie narzędzia Qt (Qt4 Framework) do two-rzenia aplikacji w PHP. Pakiet z nową wersją zawiera siedem tutoriali i przykładową aplikację. Została zaimplementowana też między innymi lepsza obsługa błędów.Licencja: GNU GPLhttp://php-qt.berlios.de/

Portale rankingoweSoftware-Wydawnictwo, wydawca magazy-nu PHP Solutions, uruchomiło pierwsze porta-le newsowo-rankingowe w języku angielskim. Obecnie działają już distrorankings.com oraz PDFdev.com. Pierwszy z nich to ranking dystrybucji Linuksa, drugi to portal dla deweloperów używających standardu PDF. Już niedługo, przy współpracy z naszym maga-zynem, uruchomione zostaną kolejne projekty (m.in. poświecone systemom CMS) i to w kilku wersjach językowych.http://distrorankings.comhttp://PDFdev.com

Nowości w Google Lab

Projekty Google ciągle zaskakują nas innowacyjnością. Przykładowo

Google Notebook pozwala na wygodne zapisywanie treści (teksty, obrazki, lin-ki) podczas przeglądania stron WWW w specjalnym miejscu, na stronie Google. W każdym momencie możemy po nie sięgać, wystarczy posiadać konto Go-ogle, np. Gmail. Dodatkowo mamy możli-wość publikacji zbieranych przez nas in-formacji na swojej stronie WWW.

Google Trends z kolei pokazuje, jak bardzo popularne są szukane informa-cje, np. możemy podać przyjkładową frazę XOOPS, Drupal, aby zobaczyć ja-kie było zainteresowanie tymi produkta-mi na przestrzeni ostatnich miesięcy, a nawet lat (co przedstawia Rysunek). Ko-lejnym bardzo ciekawym pomysłem jest Google Spreadsheets, rozwiązanie któ-re pozwala na tworzenie arkuszy kalku-

poMMo – dobry email marketing za darmo

poMMo to prosty i praktyczny system newslettera dla niewymagających. In-

stalacja jest zupełnie bezbolesna, a sam program nie przytłacza ilością opcji (jak na przykład PHPList), przez co administra-cja jest intuicyjna i łatwa. Narzędzie pomi-mo że nie skomplikowane, znakomicie sprawdza się w email marketingu. Moż-na stworzyć formularz z kilkoma newslet-terami i pytać użytkowników, które z nich chcą otrzymywać. Bez problemu zintegru-jemy też poMMo ze sklepem online, że-by powiadamiać użytkowników o naszych produktach. Użytkownicy mogą sami zmie-niać swój email, wypisywać się lub aktuali-zować preferencje.

Maile wysyłane są bardzo wydajnie. System może wykorzystywać 4 użytkow-ników serwera SMTP i kontrolować ruch podczas wysyłki (np. ilość wysłanych ma-

lacyjnych na WWW. Arkusze mogą być aktualizowane przez wiele osób jedno-cześnie. Bez problemu można też im-portować i eksportować dokumenty do formatów CSV i XLS. Jedna kopia do-kumentu na stronie WWW przyda się na pewno, nawet jeśli mielibyśmy pracować nad nim w pojedynkę. Bardzo przydatne i niezmiernie praktyczne rozwiązanie!

http://labs.google.com

ili na minutę). Mamy możliwość zatrzyma-nia, a następnie wznowienia procesu wy-syłki, a po utracie połączenia i jego wzno-wieniu, wysyłka jest kontynuowana. Im-port i eksport użytkowników odbywa się z wykorzystaniem formatu CSV. Wysyłając mailing, mamy do dyspozycji WYSIWYG (w przypadku maili w formacie HTML), a raz puszczony mail, może posłużyć ja-ko szablon przy kolejnych wysyłkach. Do wad programu na pewno trzeba zaliczyć brak opcji zbierania odbić (ang. bounces) i jeden poziom dostępu – Administrator.

Zanim zdecydujemy się na instalację, najlepiej zacząć od wersji DEMO dostęp-nej na stronie projektu.

Licencja: GPLhttp://www.iceburg.net/pommo/

www.phpsolmag.org8

Opis CD

PHP Solutions Nr 5/2006

PHP Solutions Live

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

cję Aurox 12 i skrypty automatycznej ge-neracji (www.aurox.org/pl/live). Narzędzia dystrybucji, które nie znajdują się na do-łączonej do pisma płycie, instalowane są

z repozytorium Auroxa za pomocą progra-mu yum.

Teraz, oprócz kompletnego środowi-ska LAMP (Linux, Apache, MySQL, PHP) w systemie gotowe do użycia są środo-wiska programistyczne, takie jak: Eclip-

se, Nvu, Quanta, BlueFish oraz wiele in-nych charakterystycznych dla KDE i Li-nuksa narzędzi.Na dołączonej do pisma płycie znajduje się PHP Solutions Live – bootowalna dystrybucja Auroxa, zawie-rająca przydatne narzędzia, dokumenta-cję, tutoriale i materiały dodatkowe do ar-tykułów. Aby zacząć pracę z PHP Solu-tions Live, wystarczy uruchomić kompu-ter z CD. Po uruchomieniu systemu zosta-niemy automatycznie zalogowani w syste-mie, a po krótkiej chwili powinna urucho-mić się przeglądarka Mozilla Firefox wraz z Menu przedstawiającym zawartość płyty związaną z bieżącym numerem.

Rysunek 1. Jeszcze więcej przydatnych narzędzi

Rysunek 2. Nowy atrakcyjny wygląd

Materiały dodatkowe

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

• install – hity numeru, w bieżącym nu-merze: PhpED - zintegrowane środo-wisko programowania dla języków: PHP, HTML, CSS, XML, SMARTY, XHTML; Maguma Workbench - IDP dla PHP i Python-a; PHP Edit 0.8 - edytor służący do przygotowywania skryptów PHP; Limbas jest aplikacją typu klient/serwer pozwalającą nam, na szybkie tworzenie aplikacji bez po-trzeby programowania

• ebook – książki i inne dokumenty w formacie PDF

• materialy – materiały do artykułu Ty-po3

PhpED jest zintegrowanym środowi-skiem programowania dla języków: PHP, HTML, CSS, XML, SMARTY, XHTML i pozostałych. PhpED jest uniwersal-nym edytorem zaspakajającym najbar-dziej wyszukane potrzeby programistów dzięki zastosowanym rozwiązaniom: za-awansowanemu edytorowi kodów, nie-

zawodnemu debagerowi dbg, produk-tywnemu klientowi połączeń do baz da-nych oraz szybkiemu i bezpiecznemu systemowi wdrożeniowemu. Oferuje-my Wam wersje 120-dniową NuSphere PhpED 5.5.1.

Maguma Workbench wychodzi po-za typowe IDE tworząc IDP dla PHP i Python. Szybkość, solidność i moduło-wanie powoduję że dostępne IDE jest bardziej dostosowane do potrzeb. Ma-guma Workbench włącza modułową ar-chitekturę plug-in, dzięki której flagowy produkt jest szybki, elastyczny i funk-cjonalny.

Dzięki współpracy z użytkownika-mi MC, wydobywamy z naszych pro-duktów to, co najlepsze, przyczyniając się tym samym do owocnej współpra-cy z klientami. Dzięki swojej elastyczno-ści, MW wychodzi naprzeciw oczekiwa-niom użytkowników, czytając niejako w ich myślach.

Żeby pobrać wersję 90-dnio-wą Magumy Workbench Core nale-ży zarejestrować się na stronie http://www.phpsolmag.org/maguma.

PHP Edit jest to edytor służący do przygotowywania skryptów PHP. Narzę-dzie koloruje składnię, ale użytkownik ma możliwość zdefiniowania własnych schematów. Możliwe jest także usta-wienie nowych skrótów klawiaturowych oraz reguł automatycznego poprawia-nia skryptów.

Limbas jest aplikacją typu klient/serwer pozwalającą nam, na szybkie tworzenie aplikacji bez potrzeby progra-mowania. Używa tylko tabel, form i i ta-kich modułów jak zarządzanie użytkow-nikami, manager plików czy interfejs SOAP.

Praca z Limbas jest wybitnie bezbo-lesna – do tworzenia aplikacji po stronie klienta potrzebna jest tylko przeglądar-ka WWW.

W przypadku przeglądania płyty z poziomu uruchomionego PHP Solutions Live wymienione aplikacje i wszystkie materiały dostępne są z podkatalogu /mnt/cdrom.

Na CDNa CD

HITPełna 120-dniowa wersja NuSphere PhpED 5.5.1.; Maguma Workbench 90 dniowa pełna wersja świetnego edytora; PHPEdit 0.8 – pełna wersja bardzo popularnego edytora; Limbas 1.8.8 – pełna wersja

Rozwiązanie z artykułu w PHP Solutions LiveTypo3

Wsz

ystk

ie lis

tingi

z a

rtyku

łów

zos

tały

zam

iesz

czon

ena

nas

zej s

troni

e in

tern

etow

ej p

od a

dres

em www.phpsolmag.org/pl

2 nowe książki elektroniczneCross-Platform GUI Programming with wxWidgetsSubversion Version Control

Wsz

ystk

ie lis

tingi

z a

rtyku

łów

zos

tały

zam

iesz

czon

ena

nas

zej s

troni

e in

tern

etow

ej p

od a

dres

em www.phpsolmag.org/pl

Przetestuj dowolne skryptybez instalacji!

Narzędzia w dystrybucji Kompletne środowisko do programo-wania w PHP Studio Developer

www.phpsolmag.org10

Wywiad

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

Wywiad

PHP Solutions Nr 5/2006

Wywiad z Tobiasem Schlittem,jednym z głównych deweloperów platformy eZ components

Dariusz Pawłowski: Jak rozpocząłeś swo-ją przygodę z eZ systems? Czy mógłbyś dać kilka wskazówek dla młodych progra-mistów PHP marzących o pracy dla takiej firmy jak Twoja?

Tobias Schlitt: Hehe, to był w pew-nym sensie przypadek. Kiedy przeprowa-dziłem się do Dortmund, mój przyjaciel Sandro zasugerował mi, że powinienem starać się o pracę w eZ. Tak też zrobiłem. Dostałem pracę. Na początku zapowiada-ło się na to, że będę pracował nad projek-tami klientów eZ systems, ale potem De-rick Rethans skierował mnie do eZ com-ponents. I tak się tu znalazłem.

Jeśli chcielibyście pracować dla eZ system, musicie spełniać trzy warunki: a) powinniście kochać PHP, bo jeśli kochasz to co robisz, robisz to dobrze, b) powinni-ście kochać Open Source i filozofię, któ-ra się za tym kryje; eZ systems jak najbar-dziej opiera się na tej idei, c) powinniście umieć pracować w zespole, zgranie się z całą firmą jest dla nas bardzo ważne. Ca-

Tobias pracuje jako deweloper w firmie eZ systems. Obecnie zajmuje się rozwijaniem platformy eZ components. Jest dobrze znanym ekspertem PHP. Udziela się też m.in. w projekcie PEAR, gdzie rozwija kilka pakietów.

ły czas szukamy dobrych programistów, więc śmiało przysyłajcie swoje aplikacje.

DP: Jak wygląda Twoja współpraca z eZ systems? Masz jakieś długotermi-nowe zadania? Jak się komunikujecie na co dzień?

TS: W chwili obecnej prawie wyłącz-nie pracuję dla eZ systems. Na początku kontynuowałem jeszcze prowadzenie mo-jej małej firmy konsultingowej, ale po ja-kimś czasie poczułem się tak związany i zidentyfikowany z eZ, że postanowiłem zawiesić własną działalność i w całości poświęcić się pracy dla eZ systems. Oczy-wiście, mam długoterminowe zadania. Je-stem członkiem teamu odpowiedzialnego za eZ components i zapowiada się na to, że tak przez najbliższy czas zostanie. Po-nieważ mieszkam w Dortmund, a prawie cała reszta zespołu w Skien, w Norwegii, przeważnie komunikujemy się przez email lub skype'a. Ten sposób współpracy działa całkiem dobrze, ponieważ cały team przy-wykł do tego rodzaju komunikacji, cho-

ciażby dzięki pracy nad różnymi projek-tami Open Source w przeszłości. Jakkol-wiek, dwa razy do roku (czasami częściej) przyjeżdżam do Norwegii na kilka tygodni, aby porozmawiać i podyskutować na ży-wo o nowych zadaniach i projektach.

DP: Jak rozpoczął się projekt eZ com-ponents? Jaka przyświecała mu idea i jak pracujecie nad nim obecnie?

TS: Idea stworzenia eZ components wzięła się z planu przepisania naszego CMS-a eZ publish z PHP4 na PHP5. eZ publish stał się bardzo rozbudowanym systemem, ze złożoną strukturą i więk-szość jego wewnętrznych bibliotek ma dziwne API, dodatkowo bez dobrej doku-mentacji. Tak więc postanowiliśmy prze-nieść kod wszystkich bibliotek do jedne-go niezależnego projektu – eZ compo-nents. W trakcie tworzenia komponen-tów dokonywana jest refaktoryzacja ko-du i tworzona nowa dokumentacja. Pro-jekt eZ components ma teraz na celu: a) stanowić fundament dla eZ publish,

www.phpsolmag.org10

Wywiad

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

Wywiad

PHP Solutions Nr 5/2006

b) stanowić solidną i profesjonalną plat-formę dla innych.

Tworzenie kodu oparte jest o testy i dokumentację. Oznacza to, że najpierw projektujemy wszystkie API, potem je do-kumentujemy, następnie piszemy testy jednostkowe, a na koniec przechodzimy do realnej implementacji. W chwili obec-nej mamy wersję 1.1, będącą już ulepsze-niem poprzedniej.

Po ukazaniu się kolejnej wersji ca-ły zespół spotyka się. Wtedy decyduje-my, które z ulepszeń zostaną zaimple-mentowane w następnych wersjach (wła-śnie takie spotkanie miało miejsce nie-dawno podczas konferencji eZ, gdzie dys-kutowaliśmy o wersji 1.2). Ustalamy nowe funkcjonalności i ulepszenia dla obecnych oraz nowych komponentów. Taki proces tworzenia oprogramowania działa bardzo dobrze biorąc pod uwagę, że stawiamy na elastyczność i rozszerzalność podczas implementacji. Jeśli macie jakieś pomysły i propozycje dla eZ components, śmiało możecie je zgłaszać na naszej liście ma-ilingowej. Zawsze jesteśmy otwarci na no-we pomysły.

DP: Czy według Ciebie Zend Frame-work stanowi konkurencję dla eZ compo-nents? Czy mógłbyś opowiedzieć o kil-ku najważniejszych funkcjach eZ compo-nents i odnieść się do Zend Framework?

TS: Nie wydaje mi się, że Zend Fra-mework stanowi konkurencję dla eZ com-ponents. Oczywiście, mają trochę podo-bieństw, ale tak naprawdę każde z nich działa inaczej. eZ components to nie fra-mework, podczas gdy nazwa Zend Fra-mework wskazuje już na framework. eZ components to kolekcja luźno powiąza-nych, przeważnie niezależnych kompo-nentów do budowy aplikacji. Korzystając z typowego frameworka, w mniejszym lub większym stopniu, jesteśmy zmuszeni do budowania aplikacji z wykorzystaniem je-go specyficznego podejścia itd. My daje-my Ci możliwość wyboru i sposobu uży-cia odpowiednich komponentów z naszej kolekcji. Możesz łączyć je z innymi roz-wiązaniami takimi jak PEAR. Dla przykła-du: jeśli chciałbyś użyć nasz system sza-blonów, ale nie podoba ci się komponent do internacjonalizacji, nie musisz stoso-wać ich razem.

Tak więc, najważniejsze cechy obu platform różnią się, gdyż w eZ compo-nents nie wymuszamy praktycznie żad-

nej integracji. Poza standardowymi kom-ponentami, które znajdziemy też w Zend Framework i w wielu innych framewor-kach, posiadamy wiele fajnych rozwiązań, które są jeszcze unikalne w świecie PHP. Jako przykład możemy podać ImageCo-nversion component (w przypadku trans-formacji Thumbnail dokonywane jest ska-lowanie obrazków, kompresowanie, kon-wersja i zapisanie w formacie JPEG)

Innym ciekawym rozwiązaniem jest Mail component. Nie tylko tworzy on zło-żone maile (załączniki czy kilka typów MIME mails), wysyła je za pomocą PHP-owej funkcji mail() lub serwera SMTP), ale także odczytuje maile z serwerów POP / IMAP. Mamy naprawdę wiele świet-nych komponentów, tak więc zachęcamy do zapoznania się z nimi.

DP: Lubisz Open Source, czy preferu-jesz swoje własne rozwiązania?

TS: Osobiście kocham Open Source. Od ponad 2 lat używam wyłącznie Linuksa i jestem z niego bardzo zadowolony. Głów-ny plus oprogramowania Open Source po-lega na tym, że jest tworzone przez ludzi, którzy faktycznie go używają i wiedzą, ja-kich funkcjonalności potrzebują. Dzięki te-mu powstaje dużo lepsze oprogramowa-nie w porównaniu do wytwarzanego przez zamkniętą grupę kilku deweloperów, któ-rzy nie są zainteresowani późniejszym wy-korzystaniem efektu swojej pracy. Poza tym lubię dewizę, która mówi: spraw, aby najlepsze oprogramowanie przetrwało, w przeciwieństwie do spraw, żeby przetrwała firma z najlepszym cash-flow. Ponadto ko-rzystając z Open Source mogę swobodnie samemu dodać brakujące funkcjonalności. Już kilkukrotnie zdarzało mi sie dodawać taką brakującą funkcjonalność.

DP: Czy wzorujesz się na rozwiąza-niach Javy czy ASP w swoich projektach?

TS: Nie, właściwie to nie lubię obu tych technologii. Kodowałem dużo w Ja-vie na uniwersytecie, ale nie przypadła mi ona do gustu. Jeśli chodzi o ASP, to nie mam potrzeby korzystania z tego języka, od kiedy znam i kocham PHP. Obecnie pracuję trochę w C# (i Mono). Język ten jest naprawdę fajny i używam go głów-nie do tworzenia GUI, nie dla aplikacji sie-ciowych.

DP: Jaki będzie według Ciebie kolejny krok w rozwoju języka PHP?

TS: To jest dość trudne pytanie. Obec-na wersja PHP jest już prawie komplet-

na jeśli chodzi o sam język programowa-nia. Tak naprawdę to nie widzę specjal-nie nowych funkcjonalności, jakie miały-by się pojawić w PHP w przyszłości. Ok, można pomyśleć o przestrzeni nazw (ang. name spaces), co było dyskutowane już w przeszłości czy o przeciążaniu operatorów (ang. operator overloading), co w końcu nie zostanie wdrożone. Dodanie obsługi Unicode w PHP6 jest bardzo ważnym kro-kiem, podobnie jak usunięcie takich relik-tów jak register_globals. Ale samo PHP6 nie będzie jakimś rewolucyjnym posunię-ciem, tak jak to miało miejsce pomiędzy PHP4 a PHP5, czy też PHP3 i PHP4. Nie chciałbym się też wypowiadać w kwestii PHP7, gdyż byłoby to wróżenie z fusów.

Jestem pewien, że praca nad języ-kiem będzie przebiegała w większym stopniu na tworzeniu fajnych rozszerzeń niż na rozwijaniu samego języka. Ale po-czekajmy, jak zdecydują ludzie odpowie-dzialni za rozwój PHP.

DP: Jaki jest roadmap dla eZ compo-nents i Twoje plany na najbliższy rok, dwa?

TS: Zakres prac nad eZ components ostatnio nieco się zmienił i poszerzył wraz z ulepszaniem eZ platform, które zapewni w przyszłości kompletną platformę do two-rzenia aplikacji w PHP. eZ platform będzie podstawą do zbudowania nowej wersji eZ publish Telemark. eZ components będą stanowiły najniższy poziom rozwijania apli-kacji. Na komponentach zbudowany zo-stanie eZ application server – kompletna platforma do budowania aplikacji. Na jego bazie powstanie właśnie nowe eZ publish. Oznacza to, że eZ components będą cią-gle rozwijane i już teraz spodziewajcie się wielu nowych i potrzebnych komponentów (takich jak eZ Graph czy eZ Feed).Ciągle też rozwijamy obecne komponenty doda-jąc określone funkcjonalności (jak wspie-ranie relacji dla PersistentObject).

Ogólnie rzecz biorąc, chcemy zrobić kompletną platformę dla każdego, gdzie ludzie będą mogli wybrać, czego chcą używać: eZ components jako część na-szych bibliotek, eZ application server ja-ko cała platforma lub eZ publish jako kom-pletny CMS. Moje osobiste plany na naj-bliższe 2 lata to: a) skończenie studiów, b) pomaganie eZ systems w robieniu świet-nych produktów. Myślę, że oba punkty są do wykonania.

DP: Dzięki za rozmowę!TS: To była przyjemność!

www.phpsolmag.org12 PHP Solutions Nr 5/2006

Dla początkujących

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

Łączymy DB2 i PHP Dla początkujących

Baza danych IBM DB2 była za-wsze kojarzona z obsługą du-żych, krytycznych dla działalno-

ści firmy systemów. Przez ostatnie lata IBM położył bardzo duży nacisk na do-pracowanie funkcji autonomicznych, któ-re umożliwiają stosowanie bazy także w małych i średnich systemach, w których głównym priorytetem jest bezobsługowa i niezawodna praca. Edycja DB2 Express jest przykładem takiego silnika baz da-nych – w pełni funkcjonalnego, nadają-cego się do zastosowania na kompute-rach mających maksymalnie po dwa pro-cesory i działających pod systemami Li-nux i Windows.

Dla wszystkich deweloperów aplika-cji PHP szczególnie interesujący jest dar-mowy odpowiednik DB2 Express: DB2 Express for Community, zwany również DB2 Express-C.

Darmowa edycja różni się od komer-cyjnej głównie brakiem całodobowego wsparcia technicznego przez 7 dni w ty-

Tworzysz rozbudowaną aplikację, którą będziesz często rozbudowywał i chcesz uniknąć żmud-nych, czasochłonnych i podatnych na błędy modyfikacji struktury tabel i relacji bazodano-wych. Przedstawiamy Ci DB2: solidną i rozbu-dowaną bazę, która umożliwia przechowywanie i operacje na danych w postaci XML-owej, a jej współpraca z PHP jest bezproblemowa i dobrze udokumentowana.

godniu. Co ważne, nie nakłada ograni-czeń na rozmiar bazy danych czy licz-bę obsługiwanych połączeń. Możemy również wykorzystywać DB2 Express-C do celów komercyjnych, a także roz-powszechniać go razem z naszymi apli-kacjami. Razem z bazą dostarczane są narzędzia graficzne do administra-cji. Najnowsza wersja DB2 zawiera tak-że DB2 Developer Workbench – opar-

Łączymy DB2 i PHPArtur Wroński, Piotr Pietrzak

W SIECI

• http://ibm.com/db2/v9/– strona domowa DB2 9

• http://ibm.com/developerworks/db2 – portal dla deweloperów korzystających z DB2

• http://www.redbooks.ibm.com/abstracts/sg247218.html – książka o tworzeniu aplikacji PHP dla IBM DB2

• http://www.ibm.com/developerworks/db2/library/techarticle/dm-0511singh/ a – tutorial o używaniu funkcji XML-owych DB2

• http://www.zend.com/core/ibm/ – Zend Core for IBM

Stopień trudności: lll

Co powinieneś wiedzieć...Potrzebna będzie znajomość skład-ni SQL-a i podstaw programowania w PHP (4 i 5) z wykorzystaniem baz da-nych. Przydatna będzie również wie-dza na temat PDO i prepared state-ments.

Co obiecujemy...Pokażemy, jak się łączyć z bazą da-nych DB2 Express oraz korzystać z nowego typu danych pozwalające-go na przechowywanie dokumentów XML.

www.phpsolmag.org12 PHP Solutions Nr 5/2006

Dla początkujących

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

Łączymy DB2 i PHP Dla początkujących

tą o platformę Eclipse darmową apli-kację do tworzenia procedur składowa-nych. Ułatwia ona tworzenie procedur składowanych i funkcji, a także pozwa-la na graficzne budowanie zapytań SQL oraz XQuery, przygotowywanie schema-tów XML, tworzenie obiektów bazodano-wych, czy podgląd przykładowej zawar-tości bazy danych.

W artykule omówimy metody łącze-nia się z bazami DB2 8.2 w aplikacjach PHP z oraz przybliżymy możliwości hie-rarchicznego silnika do obsługi danych XML, zaimplementowanego w nowej wersji DB2 9.

Szybki startDo rozpoczęcia pracy z DB2 i PHP po-trzebne są:

• działający serwer HTTP (Apache lub IIS),

• parser PHP (najlepiej w wersji 5.x),• zainstalowany na serwerze klient DB2,• działający serwer DB2.

Użycie Zend Core for IBM (http://www.zend.com/core/ibm/) pozwala na automatyzację całego procesu instala-cji i konfiguracji. Zend Core for IBM jest zintegrowanym środowiskiem do budo-wy aplikacji z użyciem PHP i baz DB2 lub Cloudscape (wersja opensourco-wa jest udostępniana pod nazwą Apa-che Derby). Jest to certyfikowana przez Zend i IBM wersja PHP dla serwerów baz danych IBM, zawierająca wszelkie niezbędne dodatki i sterowniki potrzeb-ne do komunikacji z wymienionymi ba-zami danych. Zend Core for IBM jest środowiskiem darmowym. Opcjonalnie można zamówić płatne wsparcie tech-niczne firmy Zend lub dokupić dodatko-we narzędzia do tworzenia oprogramo-wania w PHP.

Jeżeli nie posiadamy działającego serwera HTTP, możemy zainstalować Apache 2.0.55 w trakcie instalacji Zend Core for IBM.

Po instalacji DB2 na naszym serwe-rze umieszczona zostanie również przy-kładowa aplikacja o nazwie DB2 Sam-ple Application for PHP, która będzie dostępna pod adresem http://server:port/ZendCore/DB2Sample/. Natomiast zarządzanie i konfiguracja środowi-ska Zend Core for IBM będzie możliwe

przy użyciu konsoli dostępnej pod adre-sem http://server:port/ZendCore / (Ry-sunek 1).

Interfejs konsoli administracyjnej Zend Core for IBM jest czytelny i intu-icyjny (Rysunek 2). Pozwala na zmia-nę wszystkich niezbędnych parametrów konfiguracyjnych silnika PHP (php.ini) przy użyciu przeglądarki internetowej oraz włączanie i wyłączanie dostępnych w PHP rozszerzeń, np. obsługi SOAP czy XML-RPC.

Przydatna jest również możliwość podglądu bieżącego obciążenia serwera HTTP i silnika PHP oraz uzyskania infor-macji o liczbie błędów wygenerowanych w poszczególnych warstwach działania naszej aplikacji (Rysunek 3).

Serwer DB2 Express-C może zostać automatycznie pobrany przez program instalacyjny Zend Core for IBM. Możemy go też ściągnąć ze strony http://ibm.com/db2/express.

Dostęp do danych DB2 z poziomu aplikacji PHPDostęp aplikacji PHP-owych do baz DB2 lub Cloudscape jest możliwy dzię-ki rozszerzeniom korzystającym z API klienta DB2. Klient ten łączy się z silni-kiem bazy danych wykorzystując proto-

kół DRDA (Distributed Relational Data-base Architecture).

Komunikacja interfejsu bazodanowe-go po stronie PHP z DB2 następuje po-przez wykorzystanie innego, natywnego interfejsu CLI (Call Level Interface), któ-ry jest rozszerzeniem języka PHP napi-sanym w C, skompilowanym z biblioteka-mi obsługi DB2 i należącym do repozyto-rium PECL.

Dla PHP 5.x istnieje kilka rozsze-rzeń, które pozwalają na komunikację z silnikiem DB2 oraz korzystanie z da-nych i obiektów. Są to:

• Unified ODBC,• PDO_ODBC,• IBM_DB2.

Unified ODBCZanim powstały rozszerzenia IBM_DB2 i PDO_ODBC, jedynie Unified ODBC umożliwiało łączenie się z DB2 lub Clo-udscape z poziomu aplikacji PHP. Tak samo, jak w przypadku nowszych roz-szerzeń, obsługa DB2 w Unified ODBC opiera się na natywnych wywołaniach CLI; nie jest jednak możliwe uruchamia-nie procedur składowanych z parametra-mi OUT/INOUT. Niezależnie od bazy da-nych, z którą się łączymy (DB2 czy Clo-

Rysunek 1. Konsola konfiguracyjna Zend Core for IBM – strona powitalna

Łączymy DB2 i PHP

www.phpsolmag.org14

Dla początkujących

PHP Solutions Nr 5/2006

udscape), używamy tych samych metod dostępu i korzystania z danych. Zamiast Unified ODBC zaleca się obecnie korzy-stanie z rozszerzeń IBM_DB2 lub PDO_ODBC.

PDO_ODBCPDO_ODBC jest sterownikiem (ang. dri-ver) baz zgodnych z ODBC, wykorzy-stywanym przez będące już standardem rozszerzenie PDO (PHP Data Objects). PDO, o którym pisaliśmy już w artykule PDO – przyszły standard dostępu do baz danych w PHP Solutions 5/2005, umożli-wia zunifikowany dostęp do różnych źró-deł danych z wykorzystaniem tych sa-mych metod. W połączeniu z oprogramo-waniem klienta DB2, PDO_ODBC umoż-liwia dostęp do baz DB2, Cloudscape i Apache Derby. Podobnie, jak w pozosta-łych przypadkach, dostęp ten jest możli-wy dzięki CLI. Na Listingu 1 przedstawia-my przykład użycia PDO_ODBC do połą-czenia się z bazą DB2.

IBM_DB2Interfejs IBM_DB2 umożliwia współpra-cę z bazami DB2, Cloudscape i Apa-che Derby. Obsługuje dwa sposoby łą-czenia się z bazą danych: skatalogowa-ny (cataloged) i nieskatalogowany (un-cataloged).

W przypadku dostępu skatalogowa-nego wymagana jest obecność klienta DB2 na maszynie, na której działa ser-wer obsługujący PHP (jeżeli serwer ten jest zainstalowany na innym komputerze niż serwer DB2). Musimy też zdefinio-wać odpowiednie aliasy do serwera DB2, z którego będziemy korzystać. Możemy je utworzyć wpisując w linii poleceń klien-ta DB2 CLP (od Command Line Proces-sor) komendy: catalog tcpip node oraz catalog database.

Wygodniejsze jest jednak zastoso-wanie połączenia nieskatalogowanego, w którym wszelkie niezbędne informacje przekazujemy przy użyciu skryptu otwie-rającego połączenie z bazą danych. Na-

tomiast zastosowanie połączenia ska-talogowanego może być przydatne, gdy chcemy wykorzystać specyficzne funkcje możliwe do realizacji przy pomocy klienta DB2 (np. szyfrowanie komunikacji pomię-dzy klientem, a serwerem DB2).

Z bazą danych DB2 możemy się również łączyć korzystając ze starego, nieopartego na PDO interfejsu bazoda-nowego. Używamy w tym celu dwóch funkcji: db2 _ connect() i db2 _ pcon-

nect(). W przypadku db2 _ connect()

Listing 1. Łączenie się z bazą DB2 przy użyciu PDO_ODBC

<?php

try {

// przygotowanie danych potrzebnych do nawiązania połączenia z DB2

$constrng = 'odbc:DSN={IBM DB2 ODBC DRIVER};HOSTNAME=localhost;

PORT=50000;DATABASE=dlrshp;PROTOCOL=TCPIP;UID=db2inst1;PWD=123;';

// utworzenie obiektu klasy PDO, z którego będziemy korzystać

$dbh = new PDO($constrng); echo 'Connected';}

catch (PDOexception $exp) { // jeśli połączenie się nie udało

echo 'Exception: '.$exp->getMessage();}

?>

Listing 2. Łączymy się z DB2 przy użyciu db2_connect

<?php

// definiujemy parametry połączenia

$database = 'SAMPLE';

$user = 'db2inst1';

$password = 'ibmdb2';

$hostname = 'localhost';

$port = 50000;

// tworzymy łańcuch przy użyciu tych parametrów

$conn_string="DRIVER={IBM DB2 ODBC DRIVER};DATABASE=$database;".

"HOSTNAME=$hostname;PORT=$port;PROTOCOL=TCPIP;UID=$user;PWD=$password;";

// łączymy się z bazą danych

$conn = db2_connect($conn_string, '', '');

// jeżeli połączenie się udało

if ($conn) { echo 'Połączono z bazą danych.'; db2_close($conn);

}

// jeżeli połączenie się nie udało

else { echo 'Połączenie nie powiodło się.'; echo 'wartość SQLSTATE: ' . db2_conn_error(); echo 'komunikat błędu: ' . db2_conn_errormsg();}

?>

Rysunek 2. Dostępne panele administracyjne Zend Core for IBM

Łączymy DB2 i PHP

www.phpsolmag.org 15

Dla początkujących

PHP Solutions Nr 5/2006

połączenie (link) z bazą danych jest automatycznie zamykane po wykona-niu się skryptu, jeżeli natomiast stosu-jemy db2 _ pconnect(), połączenie bę-dzie cały czas aktywne (nie można go zamknąć nawet korzystając z polecenia db2 _ close()). Na Listingu 2 przedsta-wiamy przykład użycia db2 _ connect().

Parametry połączenia przekazujemy ko-rzystając z $conn _ string. Obsługę błę-dów zapewni nam sprawdzenie, czy ist-nieje zmienna $conn – jeśli tak, to połą-czenie się powiodło.

IBM_DB2 umożliwia nie tylko wy-syłanie kwerend SQL-owych, ale rów-nież pracę z dokumentami XML, proce-

durami składowanymi, widokami, itp. Co więcej, jest dostępny również dla wer-sji PHP wcześniejszych niż PHP5. Jak można się domyślić, interfejs IBM_DB2 opiera się więc na tradycyjnym progra-mowaniu proceduralnym. Ma również wiele wbudowanych funkcji służących m.in. do pobierania informacji o stanie i konfiguracji serwera DB2, co następuje poprzez wysyłanie do serwera zapytań dotyczących tabel słownika systemowe-go DB2 (system catalog tables).

Niewątpliwą nowością w IBM_DB2 jest możliwość natywnej obsługi doku-mentów i danych składowanych w for-macie XML.

IBM DB2 9: obsługa XML przez serwer bazodanowyPod koniec lipca tego roku (2006) po-jawi się nowa wersja DB2 nosząca nu-mer 9 i nazwę kodową Viper. Zastoso-wano w niej hierarchiczny silnik do ob-sługi danych XML (Rysunek 4), co jest zupełnie nowym podejściem w stosunku do dotychczasowych rozwiązań używa-nych w relacyjnych bazach danych. War-to nadmienić, iż już obecnie można po-brać darmową wersję DB2 9, która rów-nież nosi nazwę Express-C, z tym, że jest to wersja trial, z której możemy korzystać przez 90 dni.

W większości baz danych, doku-menty XML są przechowywane w po-staci dużych obiektów binarnych LOB (od ang. large objects) lub dekompono-wane do tabel relacyjnych (ang. shred-

Listing 3. Wstawiamy opis nowego produktu z aplikacji PHP

// nawiązujemy połączenie z bazą danych DB2

$conn =db2_connect($dbname, $dbuser, $dbpass);

// odczytujemy plik p1.xml i tworzymy na jego podstawie dokument XML $dom

$fileContents = file_get_contents("products/p1.xml");

$dom = simplexml_load_string($fileContents);

// odczytujemy id produktu (atrybut pid) z pola pid dokumentu XML

$prodID = (string) $dom["pid"];

// przygotowujemy i wykonujemy prepared statement umieszczający

// zawartość dokumentu $dom w bazie danych

$stmt =db2_prepare($conn, "INSERT INTO product VALUES (?, ?)");

db2_execute($stmt, array($prodID, $fileContents);

Listing 4. Utworzenie widoku Categories

CREATE VIEW Categories(Category) AS SELECT DISTINCT(XMLCAST(

XMLQUERY('for $i in $t/product/description/category return $i'

PASSING BY REF T.DESCRIPTION AS "t" RETURNING SEQUENCE)

AS VARCHAR(128))) FROM

product AS t

Listing 5. Użycie zapytania SQL w celu dostępu do danych XML-owych (XQuery)

$stmt = db2_exec($conn, "SELECT * FROM Categories");

while(list($cat) = db2_fetch_array($stmt)) { echo "<a href=\"catalog.php?category=".urlencode($cat). "\">$cat</a><br/>"; }

Rysunek 3. Monitorowanie aplikacji PHP ze środowiska Zend Core for IBM

Łączymy DB2 i PHP

www.phpsolmag.org16

Dla początkujących

PHP Solutions Nr 5/2006

ding). Zastosowanie obiektów LOB po-zwala na szybkie wstawienie i pobranie całego dokumentu XML, ale ma poważ-ną wadę, którą jest konieczność dyna-micznego parsowania dokumentu, któ-ra bardzo negatywnie odbija się na wy-dajności zapytań. Dokumenty XML są w całości umieszczane w polu binarnym. Struktura dokumentu jest przetwarzana dopiero w momencie realizacji zapyta-nia, w wyniku którego zwracane są ele-menty tego XML-a.

Wydajniejszą metodą przeszukiwa-nia dokumentów XML jest uprzednia de-kompozycja każdego z nich do posta-ci relacyjnej. Jest ona przeprowadza-na przez odpowiedni kod bazy danych podczas przesyłania dokumentu XML do bazy. Polega na wyodrębnianiu ele-mentów dokumentu XML oraz ich wsta-wianiu do odpowiednio przygotowanych tabel relacyjnych (dokument XML zosta-je pocięty na fragmenty, które stają się rekordami w bazie; dobrze oddaje to an-gielskie określenie shredding).

Zasadniczą wadą shreddingu jest natomiast brak elastyczności, wynikają-cy z konieczności uprzedniej znajomo-ści struktury przesyłanego dokumen-tu, utworzenia tabel bazodanowych za-wierających odpowiednie atrybuty oraz przypisania tych ostatnich do poszcze-gólnych elementów XML-a. Łatwo so-bie wyobrazić, jak skomplikowana sta-je się wtedy prosta modyfikacja struk-tury formularza na stronie WWW: razem z nim musimy zmieniać strukturę doku-mentu XML i bazy danych – to ostatnie jest bardzo uciążliwym i czasochłonnym procesem.

Ograniczeń obu metod (obiektów bi-narnych oraz shreddingu) możemy unik-nąć korzystając z dedykowanych baz XML-owych.

W DB2 9 zaimplementowano sil-nik pureXML®, który jest oparty o hierar-chiczną bazę danych i przechowuje dane XML w wewnętrznym formacie odpowia-dającym strukturze dokumentu XML. Ar-chitekci DB2 położyli bardzo duży nacisk nie tylko na wydajną pracę bazy XML-owej, ale także na jej integrację z silni-kiem relacyjnym. Język zapytań doku-mentów XML XQuery/XPath jest obsłu-giwany na równi z językiem SQL, a kom-pilator kwerend bazodanowych DB2 po-zwala na łączenie ze sobą zapytań do danych relacyjnych z tymi kierowanymi do danych XML.

Załóżmy, że chcemy utworzyć sklep internetowy, który będzie przechowywał informacje o produktach w bazie danych korzystającej z silnika DB2 9. Taka tabela mogłaby wyglądać następująco:

create table product (

pid int,

info xml

)

Atrybut pid byłby identyfikatorem pro-duktu, podczas gdy info zawierałby szczegółowy opis produktu, będący ze-stawem danych specjalnego typu XML. Opisy produktów dla omawianego syste-mu byłyby dostarczane w postaci doku-mentów XML. Wstawienie opisu nowego produktu z aplikacji PHP mogłoby prze-biegać jak na Listingu 3.

Zawartość dokumentu XML została wstawiona do tabeli product przy wyko-

rzystaniu instrukcji INSERT, tak samo, jak w przypadku zwykłych danych relacyj-nych. Ponieważ identyfikator produktu jest przechowywany w oddzielnym polu tej tabeli (pid), musimy go wyodrębnić z dokumentu XML. Możemy to zrobić wy-korzystując interfejs PHP SimpleXml lub wykorzystując mechanizmy DB2, takie jak funkcja XMLTABLE, polecenie DECOM-POSE XML DOCUMENT czy procedura xdb-DecompXML.

Zwróćmy uwagę na użycie tech-niki prepared statements (db2 _ pre-

pare()) przy wstawianiu danych do ba-zy: występujące we wzorcu znaki zapy-tania zostaną zastąpione przez id pro-duktu ($prodid) oraz zserializowaną za-wartość odczytanego pliku XML ($file-contents). W bazie danych dokument ten będzie miał postać hierarchiczne-go drzewa odzwierciedlającego struk-turę XML.

Listing 6. Przetwarzanie listy pobranej z dokumentu XML na format HTML (XQuery)

$xquery =for $i in $t/productlet $thumb := $i/description/images/image[@type="thumbnail"]

where $i/description/category = " . htmlentities($category) . "

return

<div class="float">

<a href="product.php?pid={$i/@pid}">

<img border="0" src="data/images/{$thumb}.jpg" height="100" width="100"/>

</a>

<p>

<a href="product.php?pid={$i/@pid}">{$i/description/name}</a>

</p>

</div>;>

$stmt = db2_prepare($conn, "SELECT XMLSERIALIZE(XMLQUERY(

$xquery' PASSING BY REF T.DESCRIPTION AS \"t\"

RETURNING SEQUENCE) AS CLOB(32K)) FROM xmlproduct AS t");

db2_execute($stmt);

while(list($product) = db2_fetch_array($stmt)){echo $product;}

Listing 7. Deklarujemy zbiór wynikowy c_cur z klauzulą WITH RETURN

CREATE PROCEDURE getProduct(IN id VARCHAR(10))

DYNAMIC RESULT SETS 1

LANGUAGE SQL

BEGIN

DECLARE c_cur CURSOR WITH RETURN FOR

SELECT XMLSERIALIZE(XMLQUERY /* treść zapytania XQuery */

OPEN c_cur;

END

Listing 8. Uruchamiamy procedurę składowaną przy użyciu instrukcji CALL

$stmt = db2_prepare($conn, "CALL getProduct(?)");

db2_execute($stmt, array($pid));

list($product) = db2_fetch_array($stmt);

echo $product;

Łączymy DB2 i PHP

www.phpsolmag.org 17

Dla początkujących

PHP Solutions Nr 5/2006

Łączymy DB2 i PHP

www.phpsolmag.org18

Dla początkujących

PHP Solutions Nr 5/2006

Korzystamy z XQueryDo pobrania i prezentacji kategorii każde-go produktu, przechowywanej jako ele-ment dokumentu XML, możemy użyć pro-stego zapytania XQuery:

for $i in db2-fn:xmlcolumn(

'PRODUCT.INFO')/product/description

return $i/category/text()

Pamiętajmy, że przedstawiony kod jest wykonywany na serwerze baz danych i nie jest fragmentem skryptu PHP (mimo podobnego zapisu nazw zmiennych).

Możemy utworzyć widok Categories, który będzie odpowiadał zapytaniu XQu-ery – pozwoli nam to używać zapytania SQL w celu wykonania operacji dostępu do danych XML (Listingi 4 i 5).

W DB2 9 XQuery pozwala nie tylko na prezentację listy pobranej z dokumen-tu XML, ale także na przetwarzanie jej w klauzuli return do formatu HTML na eta-pie zwracania wyników (zob. Listing 6).

Dla wygody możemy umieścić zapy-tanie XQuery w procedurze składowanej i przekazać jego wynik w postaci zbioru wynikowego.

Procedura powinna być zadeklaro-wana z klauzulą DYNAMIC RESULT SETS, która oznacza liczbę zwracanych zbio-rów wynikowych. By zwrócić zbiór wy-nikowy, kursor c _ cur musi zostać za-deklarowany z klauzulą WITH RETURN (Li-sting 7).

Jedynym, co nam pozostało do zro-bienia w aplikacji PHP jest uruchomienie procedury składowanej przy użyciu in-strukcji CALL oraz odczytanie wyników naszej kwerendy (Listing 8). Po raz ko-lejny, zwróćmy uwagę na użycie techniki prepared statements.

Indeksy w XML-uAby działanie i korzystanie z zapytań by-ło wydajne, potrzebne są indeksy. DB2 9 pozwala na tworzenie indeksów dotyczą-cych elementów dokumentu XML, któ-

re można wybrać odpowiednim wyraże-niem XPath, np.:

CREATE UNIQUE INDEX prod_pid ON

product(description) GENERATE KEY

USING XMLPATTERN /product/@pid'

AS SQL VARCHAR(10)

Odpowiednio przygotowane wyraże-nie XPath pozwoli jednym poleceniem utworzyć indeks, który może obejmować wszystkie elementy dokumentu XML. Zastosowanie hybrydowego, relacyjno-XML-owego silnika danych sprawia, że obsługa danych XML jest równie wygod-na, jak operacje na tabelach i relacjach między nimi w tradycyjnych bazach.

PodsumowanieSystem bazodanowy DB2 daje ogrom-ne możliwości, z których najważniejsze to te wprowadzone w wersji 9, które do-tyczą przechowywania danych w posta-ci dokumentów XML.

Korzystając z nich możemy znacznie uprościć i przyspieszyć tworzenie struk-tur danych oraz uczynić ich przetwarza-nie w naszych aplikacjach elastycznym. Będziemy o nich jeszcze pisali w następ-nych artykułach.

Funkcjonalność i solidność poprzed-niej, całkowicie darmowej wersji DB2 jest również warta uwagi. Biorąc pod uwa-gę te cechy oraz łatwość łączenia DB2 z PHP, warto się poważnie zastanowić nad jej zastosowaniem w swoich projek-tach. n

Artur Wroński: Jest pracownikiem działu oprogramowania IBM i jest od-powiedzialny za rozwiązania z zakre-su baz danych. Od 12 lat specjalizujesię w silnikach baz danych, migra-cjach oraz narzędziach dla hurtownidanych. Kontakt: [email protected]

Piotr Pietrzak: Jest architektem sys-temów w dziale Systems & Technology Group w polskim oddziale firmy IBM. Od ponad 11 lat związany jest z branżą IT. Jest autorem wielu tech-nicznych publikacji dotyczących archi-tektury sprzętu oraz programowania.Kontakt: [email protected]

O autorach

Rysunek 4. Dokumenty XML przechowywane są w DB2 9 w postaci hierarchicznej

ASP.NET STARTER KIT

29,80 zł

PHP STARTER KIT

29,80 zł

ORACLE

29,80 zł

AUROX 11.1

35,00 zł

GENTOO LINUX 2005.1

35,00 zł

PROGRAMOWANIE W JAVIE

29,80 zł

DEBIAN 3.1

37,00 zł

FREEBSD 6.0

35,00 zł

MANDRIVA LINUX

35,00 zł

SDJ EXTRA

Pismo przygotowane na premierę dwóchgrup najważniejszych produktów Microsoft. Omawia nowości a zatem TSQl, SQL serviceBroker, XML oraz aspekty bezpieczeństwaw SQL Server 2005. Dodatkowo zawiera krótki kurs Visual Studio 2005. Pismo zawiera 2 pły-ty DVD.

PHP Starter Kit krok po kroku wprowadzi Cięw świat PHP. Dowiesz się, jak skonfigurować pełne środowisko do tworzenia aplikacjiw PHP, napisać pierwszy program, stworzyć portal i sklep internetowy. Na DVD: pełnyzestaw narzędzi do tworzenia aplikacji WWW oraz bootowalna dystrybucja Linuksa z kom-pletnym środowiskiem do testowaniai tworzenia programów w PHP.

Java staje się coraz bardziej popularna. W piśmie prezentujemy technologie,narzędzia oraz biblioteki związane z tymjęzykiem programowania. Na DVD pełnewersje komercyjnych programów: Yoxos i Awoma oraz ciekawe książki elektroniczne.

Oto baza danych Oracle 10g. Przedstawiamy jej możliwości i wbudowane narzędzia. Dodatkowo: jak prawidłowozainstalować Oracle’a na swoim komputerzew systemie Windows i Linux. Na DVD pełna wersja bazy Oracle 10g z dodatkowymi narzędziami!

Linux+ Extra! z kultową dystrybucją Debian GNU/Linux 3.1 Sarge. Na 3 płytach DVDdystrybucja Debian 3.1 + dodatki (pełnawersja Pixel 32 i 6 komercyjnych aplikacji,w pełnych wersjach ograniczonych czasowo+ 3 książki w PDF).

Aurox to stabilny system operacyjnydedykowany dla posiadaczy urządzeń mobilnych. Celem zespołu rozwijającegoAuroksa jest zapewnienie dobrego działania urządzeń takich jak bezprzewodowe karty sieciowe (WiFi), modemy ADSL, karty telefonii komórkowej (Orange).

FreeBSD to system, który dzięki swojemu bez-pieczeństwu cieszy się ogromnym powodze-niem wśród administratorów sieci, a dzięki pro-stej instalacji oprogramowania, zjednał so-bie także sympatię użytkowników domowych. Sześć płyt CD + liczne dodatki.

Gentoo Linux to najszybsza ze wszystkichdostępnych dystrybucji Linuksa. W Linux+ Extra! przygotowaliśmy dla Was specjalnąedycję tego systemu, która podczas instalacji nie wymaga połączenia z Internetem.Dwie płyty DVD + dużo dodatków.

Jeśli chcesz rozpocząć swoją przygodęz Linuksem, Linux+ Extra! z Mandrivą będzie idealnym wyborem. Mandriva uważana jestza dystrybucję najprostszą w instalacji i użytkowaniu. Na 7 płytach CD znajdziecie kompletny system + dodatki.

Wydania specjalne SOFTWARE 2.0 EXTRA, SDJ EXTRA i LINUX+ EXTRA!Dla prenumeratorów Linux+, Linux+ Extra! oraz SDJtrzy dowolne archiwalne numerySDJ Extra bądź Linux+ Extra tylko za

tel. (22) 887-14-44, fax (22) [email protected] www.buyitpress.com

Oferta ważna do 31 maja 2006 r. lub do wyczerpania zapasów

25 PLN

29,80 zł

Uczymy programować w ASP.NET 2.0. W piśmie zamieściliśmy aż 14 artykułów instruktażowych. Nauczysz się jak stworzyć pierwszą aplikację a w przyszłości może stworzysz profesjonalny portal.

Bezpieczeństwo

www.phpsolmag.org20 PHP Solutions Nr 5/2006

Zaawansowane SQL Injection Bezpieczeństwo

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

Każdy administrator serwera WWW powinien znać techniki, które mo-gą być wykorzystane do identyfi-

kacji podatności na SQL Injection (patrz Artykuł Tobiasa Glemsera Ataki SQL In-jection na PHP i MySQL, hakin9 2/2005) i być świadomy ryzyka, jakie niosą. Pod-stawowa metodologia SQL Injection pole-ga na określeniu wektora ataku, a następ-nie jego wykorzystaniu za pomocą zmo-dyfikowanych zapytań SQL – wszystko przez przeglądarkę internetową.

Określenie potencjału luki jest waż-ne, ale jeszcze ważniejsza jest moż-liwość oceny jego wpływu. W niektó-rych przypadkach wektor SQL Injec-tion może nie wykraczać poza możli-wość wygenerowania błędów składni, takich jak próby przekształcenia łań-cuchów (strings) w wartości numerycz-ne. W innych sytuacjach wektor umoż-liwia atakującemu pełne przejęcie kon-troli nad informacjami z bazy danych. Chociaż nasze przykłady odnoszą się

Ataki SQL Injection są wymierzone w trzon aplikacji webowej – bazę danych – i umożliwiają intruzowi zdobycie, modyfikację lub usunięcie dowolnych danych. Zrozumienie zasad działania SQL Injection jest konieczne do wypracowania odpowiednich metod obrony.

do baz MySQL, techniki da się zasto-sować na każdej platformie bazodano-wej – w większości przypadków bez modyfikacji. Techniki te w swojej istocie są wymierzone w język SQL. Określo-ne rozszerzenia baz danych zwyczaj-nie ułatwiają zastosowanie omawia-nych technik.

Zaawansowane ataki SQL InjectionMike Shema

W SIECI

• http://www.spidynamics.com/papers/SQLInjectionWhitePaper.pdf – bardzo dobry ar-tykuł o atakach SQL Injec-tion,

• http://msdn.microsoft.com/msdnmag/issues/04/09/SQLInjection/ – Stop SQL Injection Attacks Befo-re They Stop You (strona MSDN),

• http://www.sqlsecurity.com/ – wszystko o słabych punk-tach SQL,

• http://www.imperva.com/application_defense_center/white_papers/sql_injection_signatures_evasion.html – automatyczne, samopro-gramujące ataki na SQL.

Stopień trudności: lll

Co należy wiedzieć...Musisz bardzo dobrze znać składnię ję-zyka SQL, do tego powinieneś znać ję-zyk PHP na średnio zaawansowanym poziomie.

Co obiecujemy...Po przeczytaniu artykułu nauczysz się w jaki sposób atakować składnię zapy-tań SQL, dowiesz się jak przeprowa-dzane są ataki na składnię języka SQL, poznasz również metody agresji na lo-gikę SQL, nauczysz się kilku dodatko-wych sztuczek SQL Injection oraz po-znasz ogólne zasady obrony przed ata-kami SQL Injection.

Bezpieczeństwo

www.phpsolmag.org20 PHP Solutions Nr 5/2006

Zaawansowane SQL Injection Bezpieczeństwo

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

Dla odświeżenia pamięciTesty SQL Injection można podzielić na trzy kategorie w oparciu o to, w który aspekt zapytań SQL są wymierzone:

• atak na składnię zapytania – wstawia-nie typowych znaków SQL mające na celu wygenerowanie błędów ułatwiają-cych identyfikację potencjalnego wek-tora ataku,

• atak na składnię języka – wycelowany w sam język SQL, zamierzeniem jest wygenerowanie błędów bazy danych lub wykonanie prostych kwerend po-przez manipulację konstruktami języ-ka i tożsamościami semantycznymi,

• atak na logikę zapytania – przepisanie zapytania mające na celu otrzymanie dowolnych danych z tabel, do których twórcy nie przewidzieli dostępu.

Techniki te mogą być łączone w celu oce-nienia aplikacji webowej i określenia jej podatności na ataki SQL Injection.

W następnych sekcjach przykłado-we ładunki (ang. payload) SQL Injec-tion będą prezentowane bez całego ad-resu (URL). Ułatwi to zrozumienie tech-nik bez niewygodnych parametrów i tek-stu. Innym powodem jest to, że wstrzyk-nięcie tych ładunków jest całkiem proste. Przy założeniu, że mamy URL w postaci http://witryna/page.cgi?a=foo&b=bar, atak SQL Injection zastępuje wartość podat-nego parametru swoim ładunkiem: http://witryna/page.cgi?a=<ładunek SQL Injec-tion>&b=bar. Dla dalszego przypomnie-nia: trzeba pamiętać o kodowaniu spacji i innych znaków w ładunku, aby nie zakłó-cały one składni adresu.

Ataki na składnię zapytańPojedynczy cudzysłów, zwany apostrofem (') – choć jest zdecydowanie najpopular-niejszym znakiem stosowanym do iden-tyfikacji wektorów SQL Injection – nie jest w żadnym wypadku jedynym znakiem po-trzebnym do wygenerowania błędu bazy danych. Ta technika obejmuje najbardziej podstawowe testy potencjalnych luk po-przez użycie metaznaków lub znaków for-matujących języka MySQL do zakłócenia składni oryginalnego zapytania. Na przy-kład następujące wyrażenia nie mogą być przetworzone do poprawnej postaci z po-

wodu źle sformatowanej składni (nieza-mknięty pojedynczy cudzysłów):

• SELECT foo FROM bar WHERE a =

''';,• SELECT foo FROM bar WHERE a = '/

*;,• SELECT foo FROM bar WHERE a = ';--

;,• SELECT foo FROM bar WHERE a = '#;.

Chociaż apostrof (ASCII 0x27) jest najpo-pularniejszym przykładem, wiele znaków może być użytych do zakłócenia składni, między innymi:

• niepasujący cudzysłów,• średnik,• znak komentarza – /*, #, lub --.

Filtry poprawności, które zabraniają wy-łącznie znaków pojedynczego cudzysłowu (lub tylko małego zestawu znaków) mogą zabezpieczać przed pełnym wykorzysta-niem podatności, ale zwykle są niewystar-czające. Mogą po prostu zaciemniać bar-dziej podstawowe problemy z architekturą połączeń aplikacji z bazą danych.

Cudzysłowy kontra ukośnikiPodczas tworzenia silnych filtrów popraw-ności programiści PHP stają przed kilkoma wyzwaniami i potencjalnie mylącymi za-leceniami. Funkcja magic_quotes() auto-matycznie kasuje (ang. escape) wszystkie apostrofy znakiem odwróconego ukośnika (\). Jeśli jednak ta możliwość jest połączo-na z wywołaniem funkcji strip_slashes(), znaki kasowania zostaną usunięte:

• SELECT foo FROM bar WHERE a = '\

''; – apostrof wykasowany,• SELECT foo FROM bar WHERE a = ''';

– odwrócony ukośnik wycięty, zapyta-nie źle sformatowane.

Innym niebezpieczeństwem nadmiernej koncentracji na pojedynczym cudzysłowie jest fakt, że programiści mogą nie znać pełnego zakresu znaków i technik, które atakujący mógłby wykorzystać przy atako-waniu zapytania SQL. Intruz może łączyć funkcje SQL, by generować błędy skład-ni zapytania.

Do wywołania błędów można również użyć wbudowanych funkcji SQL. Funkcja CHAR() drukuje odpowiednik argumentu w

ASCII. Atakujący może wstrzyknąć cudzy-słowy przez zastosowanie parzystej lub nieparzystej liczby powtórzeń łańcuchów CHAR(0x27) – szesnastkowa wartość 0x27 reprezentuje kod ASCII apostrofu. To waż-ne, ponieważ atak składa się ze znaków alfanumerycznych i nawiasów. W związ-ku z tym monitorowanie danych wejścio-wych pod kątem cudzysłowów nie zareje-struje ani nie zablokuje ataku.

Znaczenie zmiennychBłędy baz danych mogą być także wywo-łane przez ataki na typy zmiennych. Jest to najskuteczniejsze w przypadku warto-ści numerycznych, ale podatne są także zmienne czasu czy daty. Oto przykładowa lista różnych wartości, na których można wypróbowywać parametry spodziewające się liczb dziesiętnych:

• 8-, 16-, 32- i 64-bitowe wartości – 256, 65536, itd.,

• przepełnienia całkowite – 2^8 + 1, 2^16 +1, 2^32 + 1, or 2^64 + 1,

• wartości nienazwane kontra nazwane – wstawianie wartości ujemnych,

• przepełnienia zmiennoprzecinkowe – na przykład 3.40282346638528860e+38, 1.79769313486231570e+308,

• alternatywne przedstawienia – dwój-kowe, ósemkowe, szesnastkowe lub notacja naukowa.

Te ataki liczbowe często się udają (gene-rują błędy), ponieważ zmienne używane do śledzenia tych wartości nie mają ści-śle określonego typu. W języku PHP ty-pem parametru dla wszystkich zmiennych $_REQUEST jest łańcuch (string). Ozna-cza to, że – mimo możliwości wykonywa-nia operacji na zmiennych ($a = 1; $a++) – typ zmiennej powinien być traktowa-ny jako łańcuch liczbowy. Zmienna mo-że być nawet po cichu zmieniona z licz-by na łańcuch liczbowy, przy którym war-tość zwykle skutkowałaby przepełnieniem,wartością inf (ang. infinity – nieskoń-czoność) lub NaN (ang. not a number – nie jest liczbą). Na przykład funkcja PHP is_numeric("1e308") zwróci war-tość prawdziwą (bo jest to liczba), ale is_numeric("1e309") zwróci fałsz – nie jest to ani liczba, ani łańcuch liczbowy, po-nieważ znajduje się poza zakresem typu PHP double float. Zmienna musi być prze-stawiona w tryb numeryczny bezpośred-

Zaawansowane SQL InjectionBezpieczeństwo

www.phpsolmag.org22 PHP Solutions Nr 5/2006

nio za pomocą funkcji settype(), ale uwa-ga – duże wartości mogą zwracać wartość inf, co także może prowadzić do błędów w zapytaniu, jeśli spodziewa się ono wyra-żeń liczbowych.

Zwalczając synonimySolidne filtry poprawności danych wejścio-wych mogą być skuteczną obroną przed tymi technikami, ale niestety są niewy-starczające. Błędy baz danych i inne wy-jątki powinno się wyłapywać i zapobiegać ich wysyłaniu do przeglądarki. Szczegó-łowe informacje o błędach zwykle dostar-czają cennych danych złośliwym użytkow-nikom atakującym bazę. Jak zobaczymy

za chwilę, filtry poprawności danych mogą być nieodpowiednie. Na przykład przeko-naliśmy się już, że wartość 1e309 nie jest liczbą (dla większości języków i baz da-nych SQL) i wywoła błąd w słabo zabez-pieczonych aplikacjach, choć nie zawiera żadnych typowo szkodliwych znaków. To wartość wyłącznie alfanumeryczna.

Pamiętajmy, że SQL to bogaty ję-zyk, który pozwala atakującemu tworzyć wiele synonimicznych przekształceń. Na przykład CHAR(0x27) jest odpowiednikiem ASCII(0x27), które można też zapisać ja-ko x'27. Koncentrujemy się na łańcuchu CHAR(0x27), by uniknąć gołych cudzy-słowów, ale szczegóły każdego testu się

zmieniają. Oznacza to także, że filtrowa-nie oparte na składni – takie jak w firewal-lach warstwy aplikacji – musi być bardzo dokładne, aby zabezpieczyć przed tego typu atakami. W rzeczywistości kombina-cja alternatywnego schematu kodowania (kodowanie URL, Unicode) i kreatywnego SQL ominie większość filtrów bazujących na wzorcach. Pamiętajmy – CHAR(0x27) jest tym samym, co cH%41r(0x68-0x41).

Ataki na składnię językaW języku SQL zdanie Szekspira o różach wyglądałoby zupełnie niepoetycko:

SELECT name FROM roses

WHERE scent='sweet';

Czy róża zostanie nazwana butem, trzmielem, czy zegarkiem, jej atrybut pięk-nego zapachu pozostanie bez zmian. SQL posiada bogaty zestaw funkcji, któ-re mogą być użyte do tworzenia seman-tycznych ekwiwalentów zapytań, teksto-wo prezentujących się zupełnie inaczej. Ta zdolność pozwala atakującemu ziden-tyfikować i wykorzystać podatność na wstrzyknięcia, nawet gdy serwer nie po-kazuje informacji o błędach czy innych podobnych danych.

Podczas gdy psucie zapytań jest uży-teczne przy znajdowaniu potencjalnych podatności, warto także atakować zapy-tania za pomocą semantyki wbudowa-nych funkcji SQL. Z tego powodu, za-miast atakować interpreter języka aplikacji (PHP, JSP czy innych), agresja koncentru-je się na samym języku SQL. Ma to dodat-kowe zalety – nie tylko identyfikuje wek-tory ataku, lecz także dostarcza więcej informacji o filtrach poprawności wykorzy-stywanych przez aplikację. Innym skut-kiem ubocznym tej techniki jest możliwość przeprowadzania ślepych ataków SQL In-jection lub ataków, które nie wymagają ge-nerowania błędów do identyfikacji lub wy-korzystania.

Numeryczne typy danychNumeryczne typy danych to pierwsi kan-dydaci do przetestowania tej techniki. Ry-sunek 1 przedstawia oryginalny przykłado-wy URL, zaś Rysunki 2 i 3 zawierają zmo-dyfikowane adresy. Korzystamy ze starej, niezabezpieczonej wersji sklepu interne-towego FreznoShop – wersje nowsze niż 1.4 są odporne na te błędy.

Rozważmy następującą listę par nazwa/wartość:

Rysunek 1. Oryginalny przykładowy URL

Rysunek 2. Zmodyfikowany łańcuch z adresem

Zaawansowane SQL Injection Bezpieczeństwo

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

• rowid = 111,• rowid = 0x6f,• rowid = 0157 (reprezentacja ósem-

kowa),• rowid = 110+1 (w praktyce używajmy

110%2b1, ponieważ znak + w adresie oznacza spację),

• rowid = 112–1,• rowid = MOD(111,112),• rowid = REPEAT(1,3),• rowid = COALESCE(NULL,NULL,111).

Z punktu widzenia bazy danych, każde z tych żądań daje w rezultacie taką sa-mą wartość: 111. Zauważmy też, że żad-ne z nich nie korzysta z apostrofu. Pierw-sze trzy wyglądają na łańcuchy numerycz-ne lub alfanumeryczne, następne dwa ma-ją pozornie nieszkodliwe znaki dla symbo-li dodawania i odejmowania, zaś ostatnie trzy wartości zawierają nawiasy i przecin-ki. Jeśli filtry poprawności koncentrowały-by się na wycinaniu apostrofów, takie za-bezpieczenie aplikacji nie miałoby żadne-go efektu.

Surowe parametryTa technika, używająca semantycznych sobowtórów (ang. doppelganger) pozwa-la użytkownikowi na identyfikację wekto-rów SQL Injection. Jeśli rezultat każdego z żądań jest identyczny, można założyć, że silnik aplikacji przetworzył surową wartość parametru i umieścił ją w odpowiednim za-pytaniu SQL. Rozważmy na przykład takie zapytanie o numer wiersza:

SELECT foo FROM table

WHERE rowid = 110+1;

Baza danych oblicza 110+1 = 111 przed przetworzeniem reszty zapytania, zgod-nie ze swoim porządkiem operacji. Da-je to taki sam rezultat, jak oryginalne za-pytanie:

SELECT foo FROM table

WHERE rowid = 111;

Zanim wyjaśnimy jak rozbudować ten atak w celu otrzymania dowolnych danych, zbadajmy najpierw kilka innych przypad-ków, które można wykorzystać do wyge-nerowania błędów. Chociaż ta technika nie wymaga od nas wywołania błędów bazy danych, takie dane są użyteczne do okre-ślenia wersji i nazw tabel lub kolumn. Jeśli filtry danych wejściowych aplikacji wytną cudzysłowy lecz nie wyłapią błędów bazy

danych, śmiało możemy wziąć na muszkę składnię funkcji SQL. Na przykład:

• BIN(-1),• LIMIT a (przydatne, bo nie wymaga

nawiasów),• MOD(0,a).

Oczywiście wartości liczbowe również po-winny być przetestowane pod kątem wa-runków granicznych, jak wspomniano w poprzedniej sekcji.

Znaki przedwczesnego zakończeniaTa technika służy do tworzenia indywidual-nych zapytań SQL. Takie zapytania często nie potrzebują znaków cudzysłowu, ale zwykle wymagają znaków przedwczesne-go zakończenia. W związku z tym żądanie mogłoby wykorzystać znaki /* lub -- do przycięcia wyrażenia do żądanej postaci. Łańcuch SELECT foo FROM table WHERE

rowid = MOD(111,112)+UNION+SELECT+USE

R()/*; jest dobrym przykładem.Łańcuchy stanowią większe wyzwanie,

ponieważ tutaj istnieje mniej funkcji języka SQL dostarczających pomocnych sobo-wtórów semantycznych. W takich przypad-kach może się przydać funkcja CONCAT(). Kiedy argument zawiera jedynie litery od a do f, można użyć funkcji HEX():

• op=add,• op=HEX(2781),• op=REVERSE(dda),• LEAST(0x6d75736963,0x6e75736963),• GREATEST(0x61,0x6d75736963).

Znów świadomie postanowiliśmy unikać cudzysłowów, ponieważ zwykle wszczy-nają alarm lub lub mogą być blokowane. Nie przeszkadza nam to jednak tworzyć złożonych łańcuchów. Funkcje REVERSE(), LEAST(), i GREATEST() wymagają tylko na-wiasów i przecinków. Poniższe przykłady są identyczne znaczeniowo:

• page.cgi?category=music,• page.cgi?category=REVERSE(cisum),• page.cgi?category=GREATEST(0x61,0x

6d75736963),• page.cgi?category=LEAST(0x6d757369

63,0x6e75736963).

ObronaNajlepszą obroną przed tymi atakami jest korzystanie z filtrów poprawności danych wejściowych i używanie ścisłych typów da-nych przy przypisywaniu parametrom za-pytań danych dostarczanych przez użyt-kownika. Mimo że 0x27 jest poprawną war-tością szesnastkową, aplikacja powinna zabraniać jej stosowania (lub cicho prze-mienić w dziesiętne 27) – surowa wartość zawiera znak nienumeryczny. Na tej samej zasadzie wartość dziesiętna 0157 powin-na być albo odrzucona z powodu poprze-dzającego liczbę zera, albo obcięta tak, by stała się wartością dziesiętną 157, co da zupełnie inny numer wiersza. Wresz-cie, programiści powinni znać alternatywne systemy liczbowe i wiedzieć, w jaki sposób są interpretowane zarówno w języku apli-kacji, jak i w bazie danych.

Traktowanie wszystkich danych użyt-kownika jako łańcuchy jest bardzo łatwe,

Rysunek 3. Ten sam łańcuch zmodyfikowany za pomocą funkcji MOD()

Zaawansowane SQL InjectionBezpieczeństwo

www.phpsolmag.org24 PHP Solutions Nr 5/2006

ale jeśli dane mają być umieszczone w za-pytaniu, powinny być bezpośrednio przypi-sane (rzutowane) do odpowiedniego typu danych. W przypadku języków interpreto-wanych jak PHP, Perl, C# czy Visual Basic przypisanie powinno być bezpieczne lub generować błąd konwersji. Jeśli aplikacja webowa używa języka kompilowanego (C, C++), to rzutowanie typów musi być trak-towane ostrożnie i sprawdzane pod kątem wyjątków – uczulamy na ataki z wykorzy-staniem łańcuchów formatujących (patrz Artykuł Nadużycia z wykorzystaniem cią-gów formatujących, hakin9 5/2004).

Ataki na logikę zapytaniaManipulacje składnią zapytań są użytecz-ne przy identyfikacji podatności na SQL Injection, ale tylko wskazują na istnienie problemu. Prawdziwym ryzykiem związa-nym z atakami SQL Injection jest dostęp do dowolnych danych.

MySQL obsługuje charakterystyczne makro komentarzowe, które wyświetla wer-sję bazy danych: /*!<wersja> */, gdzie <wersja> jest pięciocyfrową wartością re-prezentującą wersję MySQL. Na przykład wersja 3.23.02 wygląda jak 32302, wer-sja 4.1.10 to 40110, zaś 5.0.3 wygląda jak 50003. Najszybszym sposobem sprawdze-nia możliwości wbudowanych ataków na MySQL jest połączenie komentarza z wyra-żeniem skutkującym błędem zapytania:

• /*!32302+AND+0+*/,• /*!32302+AND+0+*//* (może być ko-

nieczne zakończenie zapytania).

Następnie można odwrócić zapytanie tak, aby było poprawne, w ten sposób sprawdzając wektor wstrzyknięcia – /

*!32302+AND+1+*//* (może być konieczne zakończenie zapytania).

UNION SELECTPo identyfikacji parametru jako wektora dla ataków SQL Injection, następnym kro-kiem jest określenie tego, na co podatna jest baza danych. Osiągamy to przez ma-nipulowanie logiką oryginalnego zapyta-nia. Większość podstawowych zapytań ma postać SELECT foo FROM bar WHERE a=b;, gdzie b z równania a=b jest parametrem, którym można manipulować. Co za tym idzie, nowe zapytanie musi brać pod uwa-gę wcześniejsze SELECT. Najszybszą tech-niką jest użycie słowa kluczowego UNION.

Wyrażenie UNION łączy wiele wy-rażeń SELECT i jest obsługiwane przez większość systemów bazodanowych. Je-go podstawowa forma ma postać SELECT foo FROM bar WHERE a=b UNION SELECT

foo2 FROM bar2 WHERE c=d;. Użytecznym aspektem UNION jest wyświetlanie nazwy użytkownika, który nawiązał połączenie z bazą danych. W MySQL robi się to za po-mocą funkcji SELECT USER(). Żądanie mo-głoby wyglądać następująco:

SELECT text FROM articles

WHERE id=0

UNION SELECT USER();

Przy stosowaniu wyrażeń UNION w ata-kach SQL Injection pojawia się kilka pro-blemów:

• wyrażenie UNION powinno kończyć za-pytanie, aby składnia była poprawna – dodatkowa logika musi być usunię-ta,

• wyrażenia UNION wymagają odpowied-niej liczby kolumn w każdym wyraże-niu SELECT.

Pierwszy problem da się rozwiązać względnie łatwo. Wystarczy użyć jedne-go z typowych znaków zakończenia opisy-wanych w poprzedniej sekcji. Może to być ogranicznik komentarza (#, /*, --), połą-czony w razie potrzeby ze średnikiem lub apostrofem.

Liczenie kolumnDrugi problem także nie jest wielkim wyzwaniem, ale wymaga kilku powta-rzalnych kroków. Wstrzyknięte wyraże-nie UNION będzie miało albo za mało, albo zbyt dużo kolumn, nam zaś potrze-ba poprawnej liczby. Jeśli mamy możli-wość obserwacji komunikatów o błędach generowanych przez bazę, zobaczymy coś w rodzaju The used SELECT sta-tements have a different number of co-lumns (Użyte wyrażenia SELECT mają różną liczbę kolumn).

Niedoszacowanie kolumn można po-prawić poprzez dodanie do wyrażenia SELECT dodatkowych kolumn lub miejsc na nie (Rysunek 4), na przykład za pomocą następujących wyrażeń:

• SELECT user FROM mysql.user;• SELECT 1,user FROM mysql.user;

• SELECT 1,1,user FROM mysql.user;

• SELECT user,user,user,user FROM

mysql.user;

Każde z tych zapytań pobiera nazwę (lub nazwy) użytkownika z domyślnej ta-blicy mysql.user. W każdym z powyż-szych przykładów liczba kolumn wzrasta z 1 do 4. W praktyce lepiej powtórzyć na-zwę kolumny aby upewnić się, że wartość jest wyświetlana przez aplikację. Pierw-sze miejsce na kolumnę działa, ale trud-no powiedzieć, którą kolumnę wyświetli aplikacja.

Przeszacowania kolumn mogą być poprawione za pomocą wyrażenia CONCAT. Przeszacowania pojawiają się, gdy pierw-sze wyrażenie SELECT spodziewa się mniejszej liczby kolumn niż zawarto w za-pytaniu. Wyrażenie CONCAT rozwiązuje ten problem poprzez połączenie każdej ko-lumny w pojedynczy łańcuch – większa

Rysunek 4. Udany atak UNION SELECT

Zaawansowane SQL Injection Bezpieczeństwo

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

liczba kolumn zostaje więc połączona w jedną kolumnę. Na przykład:

SELECT foo FROM table

WHERE a=b

UNION SELECT CONCAT(*)

FROM mysql.user;

Jeśli istnieje potrzeba, można to połączyć z techniką używaną przy niedoszacowaniach:

SELECT foo,bar FROM table

WHERE a=b

UNION SELECT 1,CONCAT(*)

FROM mysql.user;

Najpoważniejszym ograniczeniem jest to, że wartość NULL w jednej z kolumn za-mieni łańcuch CONCAT na NULL.

Wiersze na muszceJeśli liczba kolumn w wyrażeniu już się zgadza, następnym krokiem jest zwy-kle określenie wiersza tablicy, który chce-my pobrać. Kiedy zapytanie zwraca wie-le wierszy, często wyświetlany jest tylko pierwszy z nich. Dobrze skonstruowane wyrażenie WHERE może w pewnym stopniu pomóc w zaatakowaniu konkretnego wier-sza, ale tylko wtedy, gdy znamy wcześniej ogólną strukturę (nazwy kolumn) tablicy.

Istnieje o wiele łatwiejsza metoda, pole-gająca na wykorzystaniu offsetów w wy-rażeniu LIMIT. Można oczywiście ograni-czyć wyniki do jednego wiersza za pomo-cą LIMIT 1, jednak wyrażenie umożliwia też sterowanie wyświetlanymi wierszami przez dodanie opcjonalnego offsetu za-czynającego się od 0. Na przykład:

• SELECT foo FROM table WHERE a=b

UNION (SELECT CONCAT(*) FROM mysql.

user LIMIT 0,1);,• SELECT foo FROM table WHERE a=b

UNION (SELECT CONCAT(*) FROM mysql.

user LIMIT 1,1);,• SELECT foo FROM table WHERE a=b

UNION (SELECT CONCAT(*) FROM mysql.

user LIMIT 2,1);.

Możemy tak postępować do momen-tu, kiedy zapytanie zwróci wiersz NULL.W przeciwieństwie do wcześniejszych przykładów prostych zapytań trzeba objąć nawiasami wyrażenie zawierające LIMIT – inaczej będzie ono niepoprawnie osa-dzone w zapytaniu.

Obrona przez wyrażeniaEfektywne sposoby obrony przed omawia-nymi technikami to wykorzystanie sprepa-rowanych wyrażeń lub zapisanych proce-

dur – oddzielają one logikę zapytań od da-nych. W konsekwencji ataki SQL Injection będą mogły uszkodzić oryginalne zapyta-nie SQL.

Potencjalną wadą takich zabezpie-czeń jest to, że wymagają one dodatko-wych zmian w aplikacji. To może prowa-dzić do spadku wydajności, jednak z dru-giej strony negatywny wpływ może być minimalny, a wzrost bezpieczeństwa tak czy inaczej będzie ogromny.

SeparowanieFiltry poprawności danych wejściowych są nieodłączną częścią systemów obrony przed atakami SQL Injection, ale takie dane nie zawsze stanowią największy problem.

Bardziej podstawowym problemem związanym z SQL Injection jest brak se-paracji między logiką zapytań a danymi. Logika jest definiowana przez programi-stę i powinna pozostać statyczna, zaś da-ne podaje użytkownik. Kiedy dane i logika się mieszają, tak jak w przypadku łączenia łańcuchów przy tworzeniu zapytań, da-ne dostarczane przez użytkownika mogą wpływać na logikę zapytania. Wiąże się z tym większe ryzyko niż to związane z po-prawnością danych wejściowych, ponie-waż zmodyfikowane zapytanie umożliwia dostęp do danych zawartych w bazie. Zło-śliwie umieszczony w zapisanej procedu-rze znak formatowania może skutkować błędem bazy, a nie wyświetleniem właści-wych danych. Nie chodzi o to, że spraw-dzanie poprawności danych nie jest istot-ne – chodzi o to, że każdy system obro-ny powinien koncentrować się zarówno na budowie, jak i na wykonywaniu zapytań.

Z punktu widzenia testów audytorzy, którzy niepoprawnie testują podatności na SQL Injection, prezentują niewłaściwe spojrzenie na ryzyko dla aplikacji. Jeśli te-sty polegają wyłącznie na wstrzyknięciach opartych na apostrofach, będą bezuży-teczne – do ataków SQL Injection wyko-rzystuje się wiele różnych znaków. n

Dodatkowe sztuczki SQLNaszym głównym celem jest zidentyfikowanie podatności na SQL Injection przez twórcze użycie znaków formatowania (składnię) lub funkcji SQL (semantykę), a potem wykorzysta-nie tych podatności za pomocą ataku na logikę SQL. Chociaż ten proces koncentruje się na manipulacjach łańcuchami i liczbami, do wygenerowania błędów można użyć (a raczej nadużyć) innych funkcji:

• INET _ ATON(),• INET _ NTOA(),• SOUNDEX().

Enumeracja jest kolejnym ważnym elementem SQL Injection, choć w tym artykule się nią nie zajmujemy. Mimo to, poniżej znajduje się kilka prostych zapytań, które można wykorzy-stać do dokładniejszego określenia informacji o bazie danych:

• SHOW VARIABLES,• SHOW STATUS,• SHOW DATABASES,• SHOW TABLES,• DESCRIBE <table>,• EXPLAIN <table>,• EXPLAIN SELECT <foo> FROM <table>,• SHOW FULL COLUMNS FROM <table>,• SELECT USER(),• SELECT SESSION _ USER(),• SELECT CURRENT _ USER(),• SELECT SYSTEM _ USER(),• SELECT SUBSTRING _ INDEX(USER(),'@',1),• SHOW CHARACTER SET,• SELECT CURDATE(),• SELECT CURTIME().

Mike Shema pracuje jako dyrektor ds. bezpieczeństwa w zajmującej się bezpie-czeństwem aplikacji webowych firmie NT Objectives.

Kontakt z autorem:[email protected]

O autorze

Zaprenumeruj swoje ulubione magazyny i zamów archiwalne numery!

Już teraz w kilka minut możesz zaprenumerować swoje ulubione pismo.Gwarantujemy:- preferencyjne ceny- bezpieczną płatność on-line- szybką realizację Twojego zamówienia Bezpieczna prenumerata on-line wszystkich tytułów Wydawnictwa Software!

www.buyitpress.com zamówienie prenumeraty

Już teraz w kilka minut możesz zaprenumerować swoje ulubione pismo.

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

Imię i nazwisko............................................................................................ ID kontrahenta..........................................................................................

Nazwa firmy................................................................................................. Numer NIP firmy.......................................................................................

Dokładny adres....................................................................................................................................................................................................................

Telefon (wraz z numerem kierunkowym)................................................... Faks (wraz z numerem kierunkowym) ....................................................

E-mail (niezbędny do wysłania faktury)............................................................................................................................................................................

zamówienie prenumeraty

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

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

www.buyitpress.com

automatyczne przedłużenie prenumeraty

Suma

Tytuł Ilość numerów

Ilość zamawianych prenumerat

Od numeru pisma lub miesiąca

Opłata w zł

z VATSoftware Developer’s Journal (1 płyta CD)– dawniej Software 2.0Miesięcznik profesjonalnych programistów

12 250/1801

SDJ Extra (od 1 do 4 płyt CD lub DVD)– dawniej Software 2.0 Extra!Numery tematyczne dla programistów

6 150/1352

Linux+ (2 płyty CD)Miesięcznik o systemie Linux 12 199/1791

Linux+DVD (2 płyty DVD)Miesięcznik o systemie Linux 12 199/1791

Linux+Extra! (od 1 do 7 płyt CD lub DVD)Numery specjalne z najpopularniejszymi dystrybucjami Linuksa 8 232/1982

PHP Solutions (1 płyta CD)Dwumiesięcznik o zastosowaniach języka PHP 6 135

Hakin9, jak się obronić (1 płyta CD)Dwumiesięcznik o bezpieczeństwie i hakingu 6 135

.psd (1 płyta CD + film instruktażowy)Dwumiesięcznik użytkowników programu Adobe Photoshop 6 140

Aurox Linux (4 płyty CD + 1 płyta DVD)Magazyn z najpopularniejszym polskim Linuksem 4 1193

PHP Solutions Nr 5/2006

Narzędzia

www.phpsolmag.org28 PHP Solutions Nr 5/2006

Narzędzia

www.phpsolmag.org 29

TYPO3 jest potężnym i elastycz-nym systemem zarządzania treścią (CMS, ang. Content Management

System) napisanym w PHP. Jest aplikacją opensourcową, dostępną na licencji GNU GPL (General Public License). Jest CMS-em wielojęzycznym (48 wersji językowych w maju 2006) i wielowitrynowym (potrafią-cym obsłużyć wiele witryn z jednej instala-cji). TYPO3 daje użytkownikowi ogromną swobodę tworzenia strony, a jego architek-tura jest zorientowana na strony (ang. page-centric paradigm). Oferuje rozwinięte moż-liwości zarządzania grupami oraz dosto-sowywania funkcjonalności pod kątem au-torów i gości. Posiada również wbudowa-ny system wersjonowania oraz funkcje przekierowywania URL-i (ang. URL rewri-ting). Jedną z największych zalet TYPO3 jest system rozszerzeń, który pozwala do-dawać nowe funkcje bez modyfikacji rdze-nia systemu. Obecnie dostępnych jest po-nad 1400 rozszerzeń (maj 2006). Dodatko-wo, twórcy TYPO3 udostępniają framework

Zarządzasz witryną internetową, która ma zhierarchizowaną strukturę stron, a ich zawartość jest uzupełniana przez wielu redaktorów. Zależy Ci na pełnej swobodzie projektowania tego sajtu oraz łatwości jego tworzenia i rozbudowy. Przedstawiamy TYPO3: potężny, elastyczny i solidny system CMS klasy Enterprise, który jest łatwy w rozbudowie, co umożliwia ciągłe dostosowywanie go do Twoich potrzeb.

pozwalający na łatwe dodawanie kolejnych możliwości.

Historia TYPO3 zaczęła się od wspo-magania rozwoju portali korporacyjnych w Internecie (głównie B2C, ang. Busi-ness-to-Customer, czyli przeznaczonych do kontaktu między firmą a jej klientami). Dzięki systemowi rozszerzeń i wspomnia-nemu frameworkowi, system może rów-nież obsługiwać witryny B2B, intranety, extranety, a także wspomagać tworzenie pojedynczych aplikacji.

TYPO3 od kuchni, czyli wymarzony portal w zasięgu rękiJean-Gael Rouchon

W SIECI

• http://www.typo3.org – głów-na strona TYPO3

• http://wiki.typo3.org – oficjal-na encyklopedia TYPO3

• http://web.inselhof-triemli.ch/index.php?id=98&type=3 – wprowadzenie do Typo3 Testsite

• http://opensourcecms.com – witryna poświęcona CMS-om

Stopień trudności: lll

Co powinieneświedzieć...Przydatna będzie podstawowa znajo-mość konfigurowania PHP oraz wiedza na temat systemów CMS.

Co obiecujemy...Pokażemy, jak przy pomocy TYPO3 stworzyć od podstaw kompletną witrynę internetową, zawierającą forum dyskusyj-ne i moduł newsów.

PHP Solutions Nr 5/2006

Narzędzia

www.phpsolmag.org28 PHP Solutions Nr 5/2006

Narzędzia

www.phpsolmag.org 29

Zaczniemy od instalacji umieszczo-nej na naszych stronach internetowych (www.phpsolmag.org) witryny demonstra-cyjnej oraz dostosowania BackEndu. Na-stępnie dowiemy się, jak dodawać i mody-fikować zawartość witryny. Jeszcze póź-niej zobaczymy, w jaki sposób utworzyć własną witrynę i zainstalować na niej apli-kacje (forum dyskusyjne i moduł newsów). Na końcu uwypuklimy niektóre spośród zaawansowanych możliwości Typo3.

Łączymy się z BackEndemPodobnie jak większość systemów CMS, TYPO3 został podzielony na FrontOffi-ce (witryna widoczna dla użytkownika) i BackOffice (interfejs administracyjny). W TYPO3 witryna jest nosi nazwę Fron-tEnd (FE), a interfejs administracyjny, w którym modyfikujemy zawartość – Bac-kEnd (BE). Między tymi warstwami istnie-je wyraźny podział, gdyż dane użytkowni-ków FE i BE znajdują się w osobnych ta-belach bazodanowych (istnieje możliwość połączenia tych tabel, ale nie jest to dla nas ani niezbędne, ani pożądane).

Zaczniemy od zalogowania się jako zwykły redaktor zawartości witryny, który korzysta z konta o nazwie redactor (hasło redactor), który ma ograniczone możliwo-ści modyfikacji sajtu. UWAGA: nazwa kon-ta redactor obowiązuje jedynie dla witryny demonstracyjnej i nie działa w przypadku instalacji tradycyjnej; w tej drugiej sytuacji korzystaj z konta administratora (login: ad-min, hasło: password; w zastosowaniach produkcyjnych należy zmienić hasło).

Interfejs BE jest podzielony na 3 czę-ści umieszczone w 2 ramkach (zob. Ry-sunek 1). Po lewej mamy główne menu, z którego wybieramy czynność do prze-prowadzenia (edycja treści, zmiana usta-wień użytkownika, zarządzanie plikami, itd.). W środkowej ramce (jeżeli jest ona wyświetlana), pokaże się rozwijalne drze-wo przedstawiające hierarchię zawartości naszej witryny. Wreszcie, w prawej czę-ści ekranu ujrzymy efekty działań wybra-nych w części środkowej. Wrócimy do te-go później – zacznijmy od zmiany języka używanego w BackEnd.

Zmiana języka interfejsu BackEndW menu po lewej stronie ekranu wybiera-my opcję User–>Setup, aby dojść do swo-

ich ustawień. Na górze strony z ustawie-niami możemy zmienić język (mamy do wyboru wiele wersji językowych, włącznie z angielską, polską, niemiecką, francuską i włoską). Dostępność każdego z języków zależy od tego, czy zainstalowaliśmy Lan-guage Pack (możemy to zrobić korzysta-jąc z opcji Ext Manager w sekcji User tego samego menu). Na stronie konfiguracyj-nej możemy też zmienić ustawienia zwią-zane z rekurencyjnym kopiowaniem i ka-sowaniem stron (określa ono, do którego poziomu mają być kopiowane lub kasowa-ne podstrony wybranej strony). Zmienimy tu też nasze dane osobowe i dane konta (włącznie z hasłem). Aby zaakceptować zmiany, musimy przewinąć formularz i na jego końcu kliknąć przycisk Save Confi-guration, po czym się wylogować i zalogo-wać ponownie: to wszystko.

Zarządzanie treściąNastępnie, nadal zalogowani jako redac-tor lub admin, dodamy nową stronę do wi-tryny, po czym umieścimy w niej zawar-tość. Później utworzymy 3 kolejne strony i zainstalujemy aplikację (forum dyskusyj-ne) na jednej z nich.

Modyfikacja treściZacznijmy pracę z witryną demonstracyj-ną. Zalogowani jako redaktor, klikniemy na opcję Page w menu po lewej stronie ekranu (mała ikona oznacza tryb edy-cji). Następnie, korzystając z drzewa na środku ekranu, wybieramy stronę, któ-

rą chcemy wyedytować (w naszym przy-padku Test).

Po prawej stronie ekranu pokażą się przyciski i menu ułatwaiające nam wyko-nywanie rozmaitych operacji na wybranej stronie: oglądanie historii jej zmian, edycja właściwości, przenoszenie strony, doda-wanie nowej strony lub jej zawartości oraz przeszukiwanie strony.

Wyedytujmy teraz treść strony: w tym celu musimy po prostu kliknąć przykła-dowy tekst pokazany poniżej nagłówka NORMAL lub przycisk Edit in Rich Text Editor umieszczony tuż pod tym tekstem. Najlepiej wybrać pierwszy sposób, gdyż po kliknięciu na Edit in Rich Text Editor nie wszystkie opcje i funkcje edytora będą do-stępne (m.in. nie będziemy mogli zmienić tytułu i typu treści ani przełączyć się na tryb pełnoekranowy). Dobrym pomysłem jest kliknięcie przycisku Open in new win-dow: jest on umieszczony na samym dole całej ramki i ma kształt rombu.

W prawej części ekranu widzimy teraz zawartość strony Test: znajduje się ona w edytorze HTML-owym typu WYSIWYG, będącym aplikacją pochodną wobec edy-tora HTMLArea, zmodyfikowaną poprzez dodanie funkcji TYPO3 do obsługi linków i obrazów. Edytor ten pozwala nam na swobodne wprowadzanie tekstu, bieżą-cą zmianę ustawień czcionki, wstawianie i zarządzanie tabelami oraz listami numero-wanymi i wypunktowanymi, cofanie i przy-wracanie przeprowadzonych działań, wy-szukiwanie i zastępowanie tekstu, itd. Kli-

Rysunek 1. BackEnd TYPO3 (istotne kroki zostały podświetlone na czerwono)

PHP Solutions Nr 5/2006

Narzędzia

www.phpsolmag.org30

kając wyglądający jak <> przycisk Toggle HTML Source możemy również zobaczyć wersję HTML (źródło) edytowanego tek-stu, a wciskając przycisk Remove format-ting (wyglądający jak połowa litery A) usu-niemy formatowanie tekstu (np. wklejone-go z aplikacji biurowej do okienka nasze-go edytora).

Pobawmy się trochę tym edytorem i wprowadźmy jakiś tekst. Jeśli potrzebuje-my więcej miejsca do edycji, możemy klik-nąć przycisk Full Screen po prawej stronie obszaru edycyjnego. Pamiętajmy jednak, że pozbawia nas to niektórych pewnych opcji edytora. Na koniec, możemy zapisać zmodyfikowany tekst korzystając ze znaj-dujących się na szczycie strony naszego edytora ikon symbolizujących dyskietki.

Dodawanie obrazków do tekstuMożemy również wstawiać obrazy: tuż pod obszarem edycyjnym mamy narzędzie po-zwalające nam wybierać i uploadować pli-ki graficzne. Jest tam również lista umożli-wiająca nam wybranie obrazu znajdującego się już na serwerze (np. Typo3V4_box.gif) lub uploadowanie obrazu znajdującego się na dysku lokalnym bezpośrednio do prze-strzeni plikowej konta redactor lub admin. To ostatnie wykonamy klikając ikonę sym-bolizującą folder i umieszczoną po prawej stronie listy. Tuż pod opisaną przeglądarką plików mamy formularz pozwalający nam edytować właściwości wybranego obrazu: ustawiać jego pozycję na stronie, a także szerokość, wysokość, nagłówek go opisują-cy, tekst alternatywny (ukazuje się, gdy nie można wyświetlić obrazu i odpowiada wła-ściwości alt HTML-owego znacznika <img src>), tytuł obrazu oraz URL do pełnego opisu grafiki.

Poniżej opcji dotyczących obrazu wi-dzimy General Options – ustawienia do-tyczące całej zawartości i pozwalające na ustawianie dat Start i Stop, określają-cych okres, w którym zdefiniowana zawar-tość ma być widoczna. Możemy też zde-cydować, czy dana strona będzie mogła zawierać podstrony i ustawiać jej widocz-ność dla zalogowanych i niezalogowanych użytkowników. Opcje te obowiązują dla wszystkich typów zawartości.

Wstawianie hiperlinkówAby wstawić hiperlinka w tekście, musi-my zaznaczyć w edytorze słowo lub ob-raz i kliknąć przycisk Insert Web Link (iko-na symbolizująca Ziemię) w pasku narzę-dzi edytora. Ukaże się przeglądarka lin-

ków TYPO3, za pomocą której możemy utworzyć odnośnik do strony (lub treści) wewnętrznej, którą można wybrać korzy-stając z drzewa, do pliku w przestrzeni pli-kowej TYPO3 lub do zewnętrznego URL-a czy adresu emailowego: wszystko, co mu-simy zrobić, to kliknięcie w odpowiednią zakładkę i wprowadzenie wymaganych danych. Domyślnie linki zewnętrzne uka-zują się w osobnym okienku, a wewnętrz-ne w tym samym oknie. Ważne jest to, że możemy ukryć adresy emailowe przed ro-botami spamerskimi.

Po kliknięciu lewym przyciskiem my-szy na ikonie zawartości (pierwsza ikonaw lewym górnym rogu ramki edycyjnej) ukaże sie menu kontekstowe pozwalające na dokonanie wybranych operacji na za-wartości, takich jak skopiowanie lub wycię-cie bloku. Jest to prosty sposób na prze-noszenie zawartości lub stron w ramach hierarchii witryny. Wykonywanie tych dzia-łań nie zmienia linków pomiędzy stronami ani zakładek w przeglądarce WWW, gdyż TYPO3 używa stałych identyfikatorów (ID) dla każdej strony. Możemy też przenosić

WymaganiaTYPO3 wymaga parsera PHP (PHP4), bazy danych MySQL-a i serwera Apache lub IIS. Dodatkowo, musisz zwiększyć ilość pamięci przydzielanej procesom PHP. W tym celu wy-edytuj plik php.ini (/etc/php4/apache/php.ini) i ustaw zmienną memory limit na 32M (memo-ry_limit = 32M) zamiast na 8M.

Instalacja tradycyjnaAby zainstalować TYPO3 w sposób tradycyjny, pobierz ze strony produktu (www.typo3.org) jego źródło (typo3_src-x.y.tar.gz lub typo3_src-x.y.zip, gdzie x.y oznacza numer wersji, w naszym przypadku 4.0) oraz przykładową, pustą witrynę (dummy-x.y.tar.gz lub dummy-x.y.zip). Następnie stwórz folder dla TYPO3 (np. typo3) w katalogu dostępnym dla serwera WWW i rozpakuj do niego oba archiwa (możesz nadpisać pliki powtarzające się pliki).

Następnie w przeglądarce WWW wpisz adres, pod którym rozpakowałeś TYPO3 (np. http://localhost/typo3) i postępuj zgodnie z instrukcjami instalatora 1-2-3 Install. Będziesz musiał podać informacje dotyczące bazy danych (nazwę użytkownika i jego hasło (jeżeli jest) oraz nazwę hosta) oraz nazwę nowej bazy, z której będzie korzystał TYPO3 (lub wy-brać istniejącą, jeśli chcesz ją nadpisać) i utworzyć tę bazę. To wszystko, co jest wymaga-ne: możesz przejść do BackEndu lub FrontEndu witryny, choć warto najpierw przeczytać uwagi instalatora dotyczące bezpieczeństwa. Aby przejść do BackEndu, wejdź do katalo-gu http://localhost/typo3/typo3/.

UWAGA: nie należy używać pakietów TYPO3 oznaczonych jako testsite, gdyż nie są one od dawna aktualizowane i zawierają stare wersje TYPO3, w których brakuje wielu z opisywanych przez nas możliwości.

Korzystamy z witryny demonstracyjnejZamiast instalować TYPO3 w sposób tradycyjny, możesz użyć specjalnej witryny demon-stracyjnej, którą omawiamy w tym artykule. Zawiera ona wstępnie zdefiniowaną treść, parę zainstalowanych rozszerzeń i domyślny szablon. Dołączyliśmy do niej również plik HTML, którego użyjemy, aby utworzyć design strony internetowej.

Możesz uruchomić witrynę demonstracyjną z PHP Solutions Live CD lub zainstalować ją na swoim komputerze. Zakładamy, że Apache, PHP i MySQL są zainstalowane i urucho-mione. Tworzymy więc folder dla TYPO3 w przestrzeni serwera WWW (w Linuksie jako ro-ot wpisujemy polecenie mkdir /var/www/t3test i przechodzimy do tego katalogu komen-dą cd /var/www/t3test. Ścieżka serwera WWW może się różnić od /var/www, trzeba oczywiście podać obowiązującą). Następnie rozpakowujemy do tego katalogu archiwum witryny demonstracyjnej (typo3demosite.zip). Aby kontynuować instalację, w przeglądar-ce WWW przechodzimy pod adres http://localhost/3test/install/index.php. Uruchomi się na-rzędzie 1-2-3 Install, które opisywaliśmy dla instalacji tradycyjnej i przeprowadzi nas przez pozostałą część procesu instalacyjnego, który wygląda niemal identycznie jak poprzednio. Instalacja rozpoczyna się od konfiguracji i utworzenia bazy danych. Istotna różnica polega na tym, że na etapie inicjalizacji bazy danych, zamiast wybrać opcję Create the default my-sql database table, decydujemy się na import bazy ze skryptu database_demo.sql. Po za-kończeniu instalacji można zacząć korzystać z witryny: jej FrontEnd znajduje się pod adre-sem http://localhost/t3test/index.php, a BackEnd pod http://localhost/t3test/typo3/.

Uwaga na temat specyfiki demaW wersji demonstracyjnej wykorzystaliśmy szablon strony WWW o nazwie sunflower pobrany z witryny oswd.org. Znajduje się on w domenie publicznej (ang. public doma-in). Zmieniliśmy go nieznacznie w stosunku do oryginału, m.in. poprzez dodanie CSS-a dla drugiego menu i poprawienie brakującego clear:both w stopce strony. Ponadto w in-dex.html umieściliśmy znaczniki (tagi) wymagane przez TYPO3. Poprawilismy też rozsze-rzenie TYPO3 o nazwie tt_board, aby było zgodne z css_styled_content.

WAŻNE: wersja demonstracyjna nie jest przeznaczona do zastosowań produkcyjnych, a jedynie do eksperymentów i testów.

PHP Solutions Nr 5/2006

Narzędzia

www.phpsolmag.org 31

strony w hierarchii drzewa metodą prze-ciągnij i upuść.

Używanie i czyszczenie cache'aTYPO3 ma zintegrowany system cache'a,używany w celu minimalizacji obciążenia serwera. Gdy dokonujemy aktualizacji, musimy również wyczyścić cache. W tym celu na liście rozwijalnej znajdującej się na szczycie ramki edycyjnej, w której pra-cujemy, wybieramy [Menu] –> [Clear Ca-che] –> This page.

Dodawanie stronyTYPO3 jest CMS-em zorientowanym na strony (ang. page-centric CMS). Ozna-cza to, że pojedyncza strona internetowa jest w nim podstawą całej witryny, umoż-liwiając nawigację i przechowując zawar-tość. Jest to zupełnie inne rozwiązanie niż stosowane w takich CMS-ach jak Mambo, gdzie zawartością zarządzamy przy uży-ciu osobnego modułu, a potem linkuje-my ją do wybranych stron. Koncepcja i co-dzienne zarządzanie witryną są więc zu-pełnie inne niż w tamtych CMS-ach.

Zależnie od swoich uprawnień, redak-tor może dodać stronę w wybranej części witryny. Drzewo stron jest całkowicie nie-zależne od innych czynników, takich jak zainstalowane moduły (w przeciwieństwie np. do phpNuke i pochodnych). Każda no-wa strona jest automatycznie dodawana do menu (jeżeli istnieje menu odpowied-niego poziomu).

Dodajmy teraz parę stron do naszej witryny: będzie to trochę podobne do mo-dyfikacji zawartości strony.

W tym celu klikamy opcję Page w menu po lewej stronie ekranu i wybie-ramy stronę Test w znajdującym się po-środku ekranu drzewie stron. W ramce po prawej klikamy przycisk Create New Record: pojawi się w niej Wizard doda-wania stron. Wybieramy w nim opcję Pa-ge(Inside) – Click here for Wizard, a na-stępnie wybieramy docelowe położenie strony.

Pojawi się formularz dodawania stro-ny, w którym wpiszemy jej tytuł (np. Mo-ja Strona) – będzie on używany w ta-gu <title> strony WWW oraz jako na-zwa strony w drzewie. Jak widzimy, strona jest domyślnie ukryta (poprzez zaznacze-nie checkboksa Hide page), gdyż nie ma jeszcze żadnej zawartości: chcemy, aby była widoczna na witrynie i w menu, więc odhaczymy tego checkboksa. Następnie klikamy na drugiej ikonie z dyskietką (Sa-

ve document and view page): strona jest gotowa.

Zanim umieścimy jakąkolwiek treść na naszej stronie, warto wiedzieć kilka rze-czy. Po pierwsze, istnieje wiele sposo-bów na dodawanie stron, m.in. użycie iko-ny Create new page w Page view czy też opcji new w menu uruchamianym po klik-nięciu dowolnej ikony w ramach drzewa pośrodku ekranu. Po drugie, istnieją in-ne typy stron (wybieramy je z listy rozwi-jalnej Type w opisywanym Wizardzie), ta-kie jak skróty do innych części witryny, lin-ki zewnętrzne lub strony zaawansowane (advanced mode; dodajemy w nich słowa kluczowe, opisy, itd). Możemy też dodać stronę typu Sysfolder: gromadzimy na niej dane (zawartość, newsy, ustawienia, da-ne użytkowników FE, itd), których będzie-my mogli użyć w dowolnej części witryny. Po trzecie, możemy tworzyć wiele stron jednocześnie korzystając z menu Func-tions po lewej stronie ekranu (jest to bar-dzo przydatne, gdy budujemy witrynę od podstaw, gdyż skraca czas).

Dodawanie bloku tekstowegoDobrze, mamy już naszą stronę, do któ-rej chcemy dodać zawartość: zróbmy to. W tym celu wybieramy Page –> Mypage –> Create new content. W wizardzie, któ-ry się pojawi, wybieramy Page Content –> Click Here For Wizard. Otrzymamy li-stę typów zawartość dostępnych dla użyt-kownika redactor (lub admin). Gdybyśmy wybrali Regular Text, moglibyśmy ustalić, w której kolumnie chcemy umieścić za-wartość (wybierzemy kolumnę Normal). Na koniec widzimy ten sam formularz, któ-rego używaliśmy podczas modyfikacji ist-niejącej treści. Przejdźmy teraz do listy rozwijalnej Type i wybierzmy header: po-zwala nam to na wstawienie samego na-główka, bez treści strony.

Użycie typu Text w/ Images pozwoli-łoby nam na wstawienie tekstu zawierają-

cego obrazy. Moglibyśmy ustawić pozycję, rozmiar, nagłówek i tytuł każdego obraz-ka. Aby zobaczyć więcej opcji, przewijamy formularz i na jego samym dole zaznacza-my checkbox Show secondary options: w formularzu pojawią się takie opcje, jak ja-kość obrazu, efekty (np. obroty czy regula-cja kontrastu) czy click to enlarge. Użycie ostatniej opcji spowoduje, że po kliknięciu na obraz na stronie TYPO3 wyświetli go w nowym oknie, co jest bardzo przydatne w prostej galerii zdjęć.

Zmiana jakości (resampling) czy de-korowanie obrazka zachodzi automatycz-nie wobec wszystkich obrazów z zestawu przy użyciu kombinacji bibliotek GD oraz ImageMagick.

Kolejnym typem zawartości jest File-links, którego używamy w celu wyświe-tlenia listy plików, z których każdy zawie-ra krótki opis. Typ ten jest użyteczny, jeśli chcemy umieścić na stronie listę plików do pobrania (np. dokumentację, tutoriale czy skompresowane aplikacje). Możemy zde-finiować wygląd takiej listy korzystając z kilku gotowych wzorów.

Wracając do klasycznej zawartości strony, mamy Menu Site Map, czyli ma-pę witryny. Używamy jej, aby umieścić na stronie mapę całego sajtu lub jego czę-ści (korzystając z pola Starting Point: ma-my do dyspozycji drzewo, które pozwa-la nam wybrać miejsce witryny, od które-go zaczniemy tworzenie mapy). Możemy też umieścić menu podstrony na poziomie o jeden niższym niż sama strona. Jeżeli podstrony są typu Advanced, i wypełnisz pole opisu, możesz sporządzić np. pod-sumowanie (resume) podstron. Inną po-żyteczną sztuczką, którą możemy zasto-sować na mapie witryny jest sporządze-nie spisu treści stron na stronie aktywnej. Przydatne jest również zaznaczenie bok-su To top w formularzu edycyjnym każ-dego elementu strony (opcja secondary options, umieszczona tuż poniżej listy roz-

Dokumentacja TYPO3TYPO3 ma rozbudowaną dokumentację, zamieszczoną pod adresem http://typo3.org/documentation. Jest ona dostępna w formatach PDF i SXW oraz w postaci plików wideo (ok. 5 godzin). Na początek polecamy manuale:

• Getting Started (doc_tut_quickstart – podręcznik dla redaktora witryny)• TS by example (przykłady TypoScriptu dla początkujących)• Modern Template Building 1 (tworzenie szablonów)• Futuristic Template Building (tworzenie szablonów przy użyciu TemplaVoila)

Istnieje również książka, która została napisana przez członków zespołu tworzącego pod-stawy TYPO3. Jest ona dostępna w niemieckiej, angielskiej i francuskiej wersji językoweji pozwala na bardzo dobre poznanie TYPO3.

PHP Solutions Nr 5/2006

Narzędzia

www.phpsolmag.org32

wijalnej umożliwiającej wybór typu treści, musi być włączona). W ten sposób umoż-liwimy nawigację pomiędzy blokami tek-stowymi a spisem treści.

Ostatnim klasycznym typem zawarto-ści, który omówimy, jest Insert Record. Po-zwala on na wyświetlanie treści zgroma-dzonej w innej części witryny. Znaczy to, że zawartość ta będzie się cały czas znaj-dowała w oryginalnym położeniu, a każ-da jej modyfikacja będzie natychmiastowo i automatycznie widoczna na naszej stro-nie. Insert Record jest więc nie tyle typem treści, co referencją do innego bloku.

Dodajemy aplikacjęJedną z najpotężniejszych cech TYPO3 jest system rozszerzeń. Umożliwia on nam łatwe dodawanie nowych funkcji. Każde rozszerzenie może dodawać no-we typy zawartości lub funkcje BackEn-du albo zawierać gotowe aplikacje (np. fo-rum dyskusyjne). Te ostatnie będą widocz-ne we FrontEndzie i noszą nazwę wtyczek (pluginów, ang. plugins). Dla TYPO3, fo-rum dyskusyjne jest złożonym typem za-wartości korzystającym z wielu rekordów (którymi są wypowiedzi) oraz narzędzia wyświetlającego (samo forum). Zainstalu-jemy rozszerzenie tt_board, które jest bar-dzo prostym forum dyskusyjnym.

Zacznijmy od sprawdzenia, czy tt_bo-ard jest zainstalowane w TYPO3. W tym celu przechodzimy do Extension Mana-gera (Ext manager w menu po lewej stro-nie ekranu) i w liście rozwijalnej Menu: wy-bieramy Loaded extensions. Nawet, jeśli

tt_board nie jest załadowane, istnieje szan-sa, że mamy je gotowe do instalacji w sys-temie TYPO3: aby to sprawdzić, wybierz-my opcję Install extensions. Jeżeli nadal nie możemy znaleźć tt_board, to znaczy, że nie jest ono zainstalowane – musimy więc je pobrać i zainstalować w TYPO3. W tym celu wchodzimy do repozytorium rozsze-rzeń TYPO3 (TYPO3 Extensions Reposi-tory), które znajduje się pod adresem http://www.typo3.org/extensions/, klikamy na za-kładkę Full list, odnajdujemy tt_board i po-bieramy je na dysk lokalny. Następnie wra-camy do Extension Managera i wybiera-my opcję Import extensions. Zwróćmy uwa-gę na nagłówek UPLOAD EXTENSION FI-LE DIRECTLY (.T3X). Tuż pod nim znajdu-je się przycisk przeglądarki plików: klikamy go, wybieramy pobrany właśnie plik .t3x i klikamy Upload extension file. Zostaniemy poprowadzeni przez Wizarda i będziemy musieli jedynie postępować zgodnie z je-go instrukcjami. W ten sposób zainstaluje-my tt_board.

Aby umieścić zainstalowane w TYPO3 tt_board na naszej stronie, przechodzimy do Page -> My Page -> Create new con-tent, przewijamy listę typów treści i w sek-cji Plugins wybieramy Discussion forum (na samym końcu listy). Ta ostatnia ope-racja jest identyczna z wybraniem General plugin w tej samej sekcji, następującej po nim selekcji Insert plugin jako typu zawar-tości, a następnie wyborem Board, tree z listy rozwijalnej Plugin. Możemy też (jeśli chcemy) wprowadzić nagłówek forum (np. Dyskusje). Nie musimy wypełniać żad-

nych innych pól, również tego oznaczo-nego jako CODE, które w obecnej wersji TYPO3 jest poprostu przestarzałe. Zapi-sanie forum poprzez kliknięcie przycisku Save document and view (ikona dyskiet-ki) kończy naszą pracę nad nim. Mamy gotowe, małe forum dyskusyjne na naszej stronie, które nadaje się świetnie jako na-rzędzie do komentowania zawartości stro-ny przez użytkowników.

Jeżeli wybierzemy opcję List z menu po lewej stronie ekranu, a następnie stro-nę My Page w drzewie pośrodku ekra-nu, zobaczymy listę wypowiedzi (postów) z forum zamieszczonych na stronie. Plu-gin tt_board pobiera wypowiedzi z danej strony, a następnie je segreguje i wyświe-tla w postaci drzewa. Udostępnia również formularz, za pomocą którego dopisujemy nowe wiadomości. Listę zamieszczonych wiadomości zobaczymy w BE, tuż poniżej zawartości strony.

Potęga szablonówMożemy już tworzyć i zarządzać zawarto-ścią strony – świetnie. Jeszcze lepiej by-łoby jednak, gdybyśmy mogli tworzyć wła-sną witrynę w oparciu o szablon. Chcemy, aby zawierał on 2 menu, z których pierw-sze jest poziome, a drugie jest pionowe i istnieje na drugim i trzecim poziomie wi-tryny. Chcemy, aby zawartość była wy-świetlana w dwóch strefach: pierwsza z nich będzie duża i ulokowana w cen-trum, a druga mała i umieszczona po le-wej, przeznaczona do zamieszczania nie-wielkich notatek.

Użyjemy szablonu sunflower (zob. Ry-sunek 2) – szablon znajdujący się w dome-nie publicznej i pobrany z www.oswd.org. Nieznacznie zmodyfikowaliśmy plik HTML, dodając do niego znaczniki TYPO3 (zob. Listing 1). Strukturą naszego sajtu będą strony Test: zmienimy tylko ich wygląd. Aby tego dokonać, musimy się przełączyć do profilu admin (jeżeli dotychczas byliśmy zalogowani jako redactor). Wylogujmy się więc z BE klikając opcję w menu po le-wej stronie ekranu i zalogujmy się na kon-to admin (domyślne hasło to password). Nasz interfejs ma teraz więcej opcji: został wzbogacony o te, do których dostęp ma tylko administrator.

Jak działają szablony w TYPO3?Istnieją dwie główne metody wykorzysta-nia szablonów w TYPO3: Modern Templa-te Building (nazwa pochodzi z manuala) oraz Templavoila. Pierwsza z nich, której

Rysunek 2. Szablon, który utworzymy

PHP Solutions Nr 5/2006

Narzędzia

www.phpsolmag.org 33

użyjemy na naszej witrynie, jest klasycz-na i opiera się na umieszczaniu specjal-nych znaczników w plikach HTML. Meto-da ta jest prosta do zrozumienia i znajduje zastosowanie przy tworzeniu rozszerzeń. Użycie MTB pozwoli nam się przyzwycza-ić do języka TypoScript. Natomiast druga metoda, Templavoila, pozwala na stoso-wanie szablonów przy użyciu interfejsu ty-pu point-and-click.

Dla szablonów w TYPO3 przeznaczo-no osobny typ zawartości o nazwie Tem-plate oraz specjalny język TypoScript. Nie jest on językiem programowania, lecz ję-zykiem opisowym, za pomocą którego definiujemy, jak szablony powinny dzia-łać – określamy sposób renderowania wi-tryny, opisujemy obiekty dynamiczne (np. menu) i konfigurujemy niektóre pluginy. TypoScript używa notacji obiektowej w celu przedstawienia logiki drzewa (podobnie jak

Rejestr Windows). Język został o pisany w osobnym manualu (TS Reference manual), a także w instrukcjach załączanych do każ-dego rozszerzenia (gdzie opis dotyczy kon-figuracji tego rozszerzenia).

Tworzymy własny szablonAby utworzyć swój własny szablon sko-rzystamy z menu List po lewej stronie, wy-bierzemy stronę Test w drzewie pośrodku ekranu, klikniemy Create new record i ja-ko typ treści wybierzemy Template. Uka-że nam się formularz dotyczący szablo-nu, w którym wypełnimy jedynie tytuł (bę-dący nazwą szablonu). Możemy też usta-wić nazwę witryny: zostanie ona użyta w tagu <title> przy nazwie strony (np. Moja-Witryna : Strona1).

Następnie w tym samym formularzu odhaczymy checkboksy Clear: Constant, Clear: Setup i RootLevel. Dwa pierwsze z

nich (Constant i Setup) informują TYPO3, że mamy do czynienia z nowym szablo-nem, który nie dziedziczy z głównego sza-blonu (dziedziczenie po poprzednich sza-blonach w linii root kodu TypoScript zosta-je wykasowane). Trzecia opcja (Rootlevel) określa, że szablon jest korzeniem (ro-otem) witryny internetowej (główną stro-ną), a więc strona, na której zostanie uży-ty, będzie stroną domową całej witryny.

Możemy też dołączać (inkludować) elementy innych szablonów i ustawień składowanych w bazie danych (Include static) lub w plikach (Include static (from extensions)). Dodatkowo, możemy dołą-czać całe szablony używając pola Include basis template field. My skorzystamy z po-la Include static (from extensions), wybie-rając CSS Styled Content. Rezultatem tej operacji będzie dołączenie podstawowych ustawień dotyczących renderowania, co zaoszczędzi nam sporo pracy. Na koniec zapisujemy szablon korzystając ze znanej już nam doskonale ikony z dyskietką.

Zabawa z TypoScriptJeżeli zechcemy zobaczyć, jak wygląda nasza witryna (np. http://localhost/typo3/), zobaczymy komunikat o błędzie Error!. Dzieje się tak, gdyż nie ustaliliśmy jeszcze metody renderowania i TYPO3 nie wie ani jakiej zawartości powinien użyć, ani w jaki sposób ją wyświetlić – poprawmy to.

W naszym szablonie musimy wpisać odpowiednie informacje w polach Con-stant i Setup. Constant to zmienna w ję-zyku TypoScript oraz miejsce, w którym można wygodnie umieszczać wartości wy-korzystywane w polu Setup: później poka-żemy, jak to zrobić, a na razie zajmijmy się polem Setup.

Pamiętajmy, że TypoScript ma skład-nię obiektową, której używamy do konfigu-rowania silnika renderującego (ang. ren-dering engine) oraz umieszczania obiek-tów na stronach WWW. Wprowadzimy kod przedstawiony na Listingu 2 do po-la Setup. Zaczniemy od inicjalizacji stro-ny wpisując mypage = PAGE;, wskutek cze-go powstanie obiekt mypage typu (klasy) PAGE. Nadamy mu domyślny typ typeNum. Zauważmy, że pozwala nam to na zarzą-dzanie wieloma obiektami stron (np. ram-kami) w jednym skrypcie TypoScript, a dzięki typeNum możemy korzystać z róż-nych metod renderowania (np. druk, PDF czy WAP). Następnie zdefiniujemy ścież-kę CSS do obiektu strony mypage (mypa-ge.stylesheet). Jest to konieczne, gdyż

Listing 1. Znaczniki TYPO3 wstawione do szablonu sunflower

<body>

<!--###DOCUMENT_BODY### start-->

<div class="thebox">

<div class="logo"><a href="#">###SITENAME###</a></div>

<!--###MENU1### start-->

<ul class="header">

<li><a href="#">Diary</a></li>

<li><a href="#">The Idea</a></li>

<li><a href="#">Contact</a></li>

</ul>

<!--###MENU1### end-->

<div class="side">

<!--###MENU2### start-->

<h2>Archives</h2>

<a href="#">March 2006</a><br/>

<!--###MENU2### end-->

<!--###SIDECONTENT### start-->

<h2>Links</h2>

<a href="http://validator.w3.org/check/referer">XHTML</a><br/>

<form method="get" action="#">

<p><input size="12" style="border:1px solid #eee" type="text" id="q"

name="q" /></p>

</form>

<!--###SIDECONTENT### start-->

</div>

<div class="content">

<!--###CONTENT### start-->

<h2><a href='#'>Sunflower</a></h2><h4>22 March 2006</h4><p>Sunflower validates

as XHTML

1.1<br /><br />It has a liquid design or it will fit the available screen

width.<br />If

you like this theme, submit your comments in <a href="

http://www.kumi.co.nr/">www.kumi.co.nr</a><br /><br />Thank you^o^<br /></p>

<a href='#'>0&nbsp;Comments</a><br/><p>&nbsp;</p>

<!--###CONTENT### end-->

</div><div class="footer">

&copy; Copyright you | Design by <a href="http://www.kumi.co.nr">Kumiko</a>

</div>

</div><!--###DOCUMENT_BODY### end-->

</body>

PHP Solutions Nr 5/2006

Narzędzia

www.phpsolmag.org34

ścieżka CSS zdefiniowana w szablonie HTML jest błędna.

Kolejnym krokiem będzie załadowanie szablonu HTML i poinformowanie silnika TYPO3 o tym, aby w miejsce oznaczonych (otagowanych) stref w tym pliku wsta-wił odpowiednią treść. Pierwszym obiek-tem na tej stronie (page.10), który utwo-rzymy, będzie obiekt typu TEMPLATE (mypa-ge.10 = TEMPLATE). Określimy następnie plik do załadowania (template = FILE i template.file = fileadmin/templates/

sunflower/index.html).W pliku HTML-owym musimy ozna-

czyć strefy stosując pary znaczników <!--###MYZONE##-->, które określają pod-części (ang. subparts). Każda podczęść określona pomiędzy dwoma tagami może następnie zostać zastąpiona treścią wyge-nerowaną przez silnik TYPO3.

Możemy też wstawiać oznaczenia składające się tylko z jednego tagu: zo-staną one zastąpione w ten sam spo-sób. Następnym krokiem będzie zdefinio-wanie części używanej w szablonie jako workOnSubpart = DOCUMENT_BODY. Pozwala to TYPO3 na stosowanie wielu szablonów zgromadzonych w jednym pliku HTML.

Ostatnia część naszego kodu zastępu-je oznaczenia i podczęści treścią. Znacze-nie obiektu TEXT jest oczywiste, wyjaśnimy więc inny obiekt, styles.content.get. Jest on skrótem zdefiniowanym w szablonie sta-tycznym CSS styled content, który wcze-śniej załadowaliśmy. Jego zadaniem jest za-rządzenie, aby TYPO3 pobierał zawartość z kolumny NORMAL i wyświetlał ją. Linia subparts.CONTENT < styles.content.get wstawia więc kolumnę NORMAL do sza-blonu HTML w miejscu określonym przez znacznik <!--###CONTENT###-->. Tej samej techniki używamy w celu wstawienia menu1 i menu2 w odpowiednich dla nich strefach.

Tworzymy menuPrzyjrzyjmy się teraz bliżej menu. Obiekt menu o nazwie temp.menu1 jest typu HMENU (H jak hierarchiczny). Zawiera po jednym obiekcie podrzędnym dla każdego pozio-mu drzewa witryny, który chcemy wyświe-tlić. Przyjrzyjmy się sposobowi numera-cji menu: jest on ścisły, ponieważ indeks każdej pozycji w menu określa jednocze-śnie jej głębokość. Każda pozycja w me-nu (tzn. każdy poziom menu) może być in-nego typu. W naszym przykładzie mamy tylko menu tekstowe, można jednak two-rzyć również menu graficzne (przy użyciu Gdlib) lub graficzno-tekstowe, menu za-

Listing 2. Ustawienia naszego szablonu

# definiujemy pierwsze menu

temp.menu1 = HMENU

temp.menu1.special = list

temp.menu1.special.value = 8, 2, 1

temp.menu1.1= TMENU

temp.menu1.1.NO.linkWrap = <li>|</li>

temp.menu1.wrap = <ul class="header">|</ul>

# definiujemy drugie menu

temp.menu2 = HMENU

temp.menu2.wrap = <ul class="sidemenu">|</ul>

temp.menu2.1 = TMENU

temp.menu2.1 {

NO.linkWrap = <li> |</li>

ACT = 1

ACT.linkWrap = <li> |</li>

ACT.ATagParams = style="color:#e69b0a;"

}

temp.menu2.2 = TMENU

temp.menu2.2 < temp.menu2.1

temp.menu2.2.wrap = <ul>|</ul>

## inicjalizujemy stronę

mypage = PAGE

mypage.typeNum = 0

mypage.stylesheet = fileadmin/templates/sunflower/style.css

## ładujemy szablon HTML-owy

mypage.10 = TEMPLATE

mypage.10 {

template = FILE

template.file = fileadmin/templates/sunflower/index.html

## odczytujemy część zawartą pomiędzy znacznikami <body> i </body>

workOnSubpart = DOCUMENT_BODY

## zastępujemy poszczególne strefy

marks.SITENAME = TEXT

marks.SITENAME.value = Our Site

subparts.MENU1 < temp.menu1

subparts.MENU2 < temp.menu2

subparts.CONTENT < styles.content.get

subparts.SIDECONTENT = TEXT

subparts.SIDECONTENT.value = Here the side content

}

Rysunek 3. Zarządzanie zawartością strony zmodyfikowanej przez zastosowanie TemplaVoila

PHP Solutions Nr 5/2006

Narzędzia

www.phpsolmag.org 35

PHP Solutions Nr 5/2006

Narzędzia

www.phpsolmag.org36

wierające się w polu <select> lub menu rozwijalne. Ponieważ dla nas te możliwo-ści nie mają wielkiego znaczenia, aby się o nich dowiedzieć zajrzyj do instrukcji TS by example manual oraz TS Reference.

Aby utworzyć pozycję menu teksto-wego używamy konstrukcji temp.menu1.1= TMENU. Jedyną właściwością, którą musimy zdefiniować jest status normal tego menu, który ustawimy wpisując temp.menu1.1.NO.linkWrap. Konstrukcja ta nakazuje TYPO3umieścić link pomiędzy tagami <li>. Usta-wianie statusu NO jest obowiązkowe.

Istnieje wiele ustawień menu, które zmieniają sposób jego zachowania w ska-li globalnej (całej witryny) albo modyfiku-ją jedynie wybraną pozycję. Przykładem na to pierwsze jest konstrukcja special = list in temp.menu, która zmienia typ me-nu z dynamicznego na statyczne, którego pozycje mają postać listy.

Istnieje też parametr umożliwiający tworzenie zestawów przycisków Previo-us | UP | Next (Poprzedni | DO GÓRY | Następny), nawigacja typu breadcrumb (menu pozwalające przełączać się mię-dzy kilkoma poziomami stron, np. newsy->kraj>najnowsze czy języki programowa-nia>php>nowe projekty) lub katalog stron (wypróbuj temp.menu1.special=directory czy temp.menu1.special.value = 13).

Menu o nazwie menu2 jest przykła-dem konfigurowania poszczególnych pozy-cji menu. Oprócz statusu normal, możemy używać innych statusów, takich jak RO (rollo-ver – stan pozwalający na zdefiniowanie te-go, jak ma wyglądać pozycja menu, gdy na-jedziemy na nią kursorem myszy), ACT (acti-ve page – stan ten oznacza, że pozycja me-nu odnosi się do aktualnie oglądanej strony) lub IFSUB (pozycja menu posiadająca ten status jest aktywna, jeżeli ma podstrony). W przypadku menu2 włączymy status ACT (ACT=1), dodamy nowy styl wewnątrz tagu <a> używając ACT.ATagParams i zawrzemy link pomiędzy tagami <li>. Przeglądając później witrynę we FrontEndzie, zobaczy-my, że strony aktywne (włącznie z główną) są podświetlone w menu.

Ostatnie szlify naszego szablonuJedną z ostatnich czynności, jakie prze-prowadzimy wobec tego szablonu jest za-stąpienie podczęści SIDECONTENT praw-dziwą zawartością. Zawartość lewej kolumny umieścimy w odpowiedniej pod-części. W tym celu podmienimy po-prostu linie subparts.SIDECONTENT po-przez subparts.SIDECONTENT < styles.

content.getLeft, co spowoduje, że TYPO3 będzie pobierał zawartość z lewej kolumny i umieszczał ją w podczęści SIDECONTENT. Musimy tylko dodać treść w lewej kolum-nie używając sekwencji Page –> TEST –> LEFT -> Create content (w ostatnim kroku zwyczajnie klikamy ikonę Create content icon umieszczoną tuż pod nagłówkiem LEFT). Można tak dostosować BE, aby wy-świetlane były tylko kolumny Left i Normal, ale nie będziemy się tym zajmować.

Inne silniki szablonówIstnieją również inne niż opisane przez nas metody wprowadzenia i wykorzysta-nia szablonów w Typo3. Skupiliśmy się na Modern Template Building, ponieważ jest ono najlepszym sposobem, aby obserwo-wać efekty użycia TypoScriptu. Unowo-cześniona wersja tej metody działa dzię-ki wykorzystaniu rozszerzenia Automa-ke Template, a znaczniki umieszczane w szablonie są automatycznie generowane przez TYPO3. Używając wymienionego rozszerzenia pomijamy jeden krok w pro-cesie projektowania witryny, ponieważ nie musimy modyfikować plików HTML.

Ponad rok temu pojawiło się rozsze-rzenie TemplaVoila. Jest ono bardzo potęż-ną metodą zarządzania szablonami w TY-PO3. Została opisana w należącym do ma-nuala TYPO3 rozdziale Futuristic Templa-

te Building. Opiera się na wykorzystywaniu technik graficznych, w większości typu po-int-and-click. Tym, co czyni TemplaVoila, jest konwersja HTML-a do XML-a. TV zastępuje istotne tagi HTML-owe (div, td, dd, itd.) wę-złami XML, a następnie określa, czy dany znacznik ma zarządzać treścią i jaką. Ma-powanie zawartości do elementów HTML jest bardzo proste i dokonujemy go korzy-stając z formularzy, w których wprowadza-my potrzebne dane. Liczba mapowanych elementów (treści) odpowiada ilości pól lub kolumn w docelowym HTML-u. Przykłado-wo, jeżeli utworzymy 2 mapowania treści w TV, otrzymamy 2 kolumny zamiast 4, któ-re mieliśmy używając metody Modern Tem-plate Building (zob. Rysunek 3). Dobre jest to, że w szablonie również możemy używać pól, określających np. kolor strony. Wymie-nione pole będzie dodane do właściwości strony, a odpowiedni kolor zostanie zastą-piony przez silnik renderujący.

Metody TemplaVoila możemy używać zarówno wobec szablonu całej strony, jak i jego części. Możliwe jest tworzenie treści stanowiącej strukturę (np. opis filmu) bez tworzenia tabeli bazodanowej, konieczne w takim wypadku jest jednak zastosowa-nie końcowego renderowania HTML. Na Rysunku 4 widzimy elastyczny spis treści utworzony na podstawie struktury danych zapisanej w XML-u, a nie w bazie danych.

Rysunek 4. Directory Flexcontent – domyślny formularz oparty na (X/HT)ML

PHP Solutions Nr 5/2006

Narzędzia

www.phpsolmag.org 37

Metoda TemplaVoila daje bardzo duże możliwości, aby je lepiej poznać, powinie-neś przeczytać manual na temat Futuristic template building. Najmocniejszą stroną tej metody jest na pewno mapowanie przy użyciu narzędzi graficznych: wystarczy, że wybierzemy pożądany typ zawartości (Content Column, Typoscript, Text Field, itd), klikniemy na znaczniku, którego chce-my użyć na danej stronie et voilà! – nasz szablon jest gotowy.

Działanie TemplaVoila zostało przed-stawione na filmie zrobionym we Flashu, dostępnym pod adresem: http://typo3.org/documentation/articles/minute-website/. Dla Twojej wygody, umieściliśmy ten film na naszej witrynie demonstracyjnej (TYPO3 –> English -> Cool Features).

Dodawanie nowych aplikacjiJak już wiemy, jedną z najlepszych cech TYPO3 jest system rozszerzeń, o któ-rym już wspominaliśmy. Podobnie, jak in-ne systemy wtyczek (np. z Firefoksa), po-zwala on na wzbogacanie naszej witryny o nowe funkcje bez modyfikowania silnika leżącego u jej podstaw (rdzenia). Działa-nie każdego rozszerzenia może dotyczyć FrontEndu, BackEndu lub obu z nich.

Rozszerzenia TYPO3 możemy pobrać z repozytorium znajdującego się pod ad-resem http://typo3.org/extensions (ponad 1400 dostępnych rozszerzeń) lub zapisać na serwerze, na którym są tworzone i prze-słać je na serwer produkcyjny. Każde roz-szerzenie jest zarchiwizowane w postaci pojedynczego pliku .t3x, który zawiera mo-dyfikacje bazy danych, pliki konfiguracyjne i samą aplikację (jej logikę biznesową).

Dodajemy i uruchamiamy system newsówZainstalujmy system newsów z repozy-torium TYPO3 (TER). Będziemy do tego potrzebowali połączenia z Internetem. Po-dobnie, jak miało to miejsce przy instala-cji tt_board, musimy zacząć od kliknięcia opcji Ext Manager w menu po lewej stro-nie ekranu (sekcja Tools). W ramce, która pojawi się po prawej stronie ekranu, wybie-rzemy Menu: Import extensions. Po przeła-dowaniu strony, wpisujemy tt_news w polu Lookup i klikamy przycisk Lookup. Powin-na się pojawić pozycja News – kliknijmy na czerwoną strzałkę z lewej strony tej nazwy. Od tej chwili musimy postępować zgodnie z instrukcjami programu instalacyjnego, który zapyta o to, czy utworzyć tabelę ba-

zodanową i da możliwość zmiany opcji. W tym przypadku poprostu przewijamy stronę i zgadzamy się na instalację: moduł News jest gotowy do pracy.

UWAGA: ten sposób instalacji może okazać się niemożliwy przy powolnym po-łączeniu z Internetem: skrypt może prze-kroczyć maksymalny czas wykonywania (nastąpi timeout), który jest standardowo ustawiony na 30 sekund. Aby temu zapo-biec, musimy albo zmienić zawarte w pliku php.ini ustawienie max_execution_time na 0 (skrypt będzie wykonywany w nieskoń-czoność, aż do przerwania jego pracy), al-bo też ręcznie pobrać rozszerzenie, a na-stępnie zainstalować je z dysku lokalnego – identycznie, jak w przypadku opisanej przez nas instalacji tt_board.

Mając już moduł newsów, dodamy go do strony Test. W tym celu przechodzi-my na stronę Test i klikamy Create new re-cord -> Page Content -> Click for Wizard. Następnie musimy wybrać plugin o nazwie News, który jest umieszczony na końcu li-sty w sekcji Plugins. Klikamy go i umiesz-czamy w kolumnie Normal. W formularzu edycyjnym wpisujemy nagłówek (np. Moje newsy) i wybieramy LATEST w What to di-splay: moduł będzie wyświetlał tytuł i pierw-sze słowa każdego newsa. Zapiszmy nasze modyfikacje korzystając (jak zwykle) z ikony przedstawiającej dyskietkę.

Musimy jeszcze zaktualizować sza-blon, aby uwzględnić w nim newsy. W tym celu przechodzimy do menu List i edytuje-my szablon Test. Następnie do listy roz-szerzeń Include static from extensions do-dajemy CSS-based tmpl (tt_news) i zapi-sujemy przeprowadzone zmiany.

Możemy teraz umieścić wiadomości na naszej witrynie: klikamy więc Page –> Test –> Create new record i wybieramy typ rekordu newsów korzystając z formu-larza, który się pojawi po prawej. Następ-nie wpisujemy tytuł, odhaczamy checkbox hide i wpisujemy treść wiadomości w polu Text (jeżeli checkbox hide jest niewidocz-ny (nie ma go pod polem tytułu), to prze-wijamy stronę do samego końca i zazna-czamy box Show secondary options). Na-stępnie zapisujemy naszą wiadomość kli-kając ikonę dyskietki z lupą (Save docu-ment and view page) – gotowe. System newsów w środowisku produkcyjnym (np. na portalu korporacyjnym) powinien mieć osobną stronę do wyświetlania wszystkich wiadomości, archiwum, itd. Moduł new-sów w TYPO3 pozwala na te operacje, możemy też zmieniać sposób prezenta-

cji lub liczbę jednocześnie wyświetlanych wiadomości, itd. My tylko pokazaliśmy, jak go uruchomić i jak dodawać wiadomości.

Do swojej dyspozycji mamy również narzędzie ułatwiające tworzenie rozszerzeń (asystenta) o nazwie Extension Kickstarter. Jego zadaniem jest automatyczne tworze-nie wymaganych w każdym rozszerzeniu plików oraz tworzenie pustego kodu PHP (który później zastąpimy własnym). Może-my użyć tego narzędzia do wstawiania no-wych pól do bazy danych. Co ciekawe, po-la te dodajemy używając oznaczeń typo-wych dla formularzy (textarea, selector, file), a nie typów SQL-owych (np. varchar czy integer). Wydaje się to trochę dziwne, lecz jest wydajne.

PodsumowanieOpisaliśmy najbardziej użyteczne moż-liwości TYPO3, dając Ci solidne podsta-wy do podjęcia decyzji, czy chcesz ko-rzystać z tego CMS-a, czy nie. Aby się o tym upewnić, możesz zajrzeć pod adres http://typo3.com, gdzie znajduje się Eva-luation Center – miejsce, w którym zgro-madzone sa rozmaite informacje na te-mat TYPO3. Możliwości TYPO3 są dodat-kowo zilustrowane przy użyciu przykładów we Flashu (http://typo3.com/Feature_list.1243.0.html).

Niezwykłą funkcją TYPO3, która poja-wiła się w wersji 4.0, jest logika przestrzeni roboczych (ang. workspace logic). Pozwa-la ona na określenie przestrzeni, w której będzie zapisawana treść, np. brudnopis czekający na akceptację redaktora.

Zachęcamy Cię do poznawania TY-PO3 krok po kroku: zacznij od urucho-mienia małej witryny, naucz się Typo-Script, wypróbuj TemplaVoila i zainstaluj rozszerzenia, których potrzebujesz. Dzięki własnej praktyce szybko staniesz się mi-strzem obsługi TYPO3. n

Jean-Gael Rouchon jest dyrektorem badań i rozwoju (R&D director) we francuskiej firmie ONEXT, która spe-cjalizuje się w opensourcowych syste-mach CMS tworzonych w PHP. Pracu-je nad TYPO3 od 2002 i współadmini-struje portalem typo3.frKontakt: [email protected]

O autorze

www.phpsolmag.org38 PHP Solutions Nr 5/2006

Kasa dla Webmastera

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

Freelancing

Najogólniej mówiąc, freelancing to rynek zleceń. Choć istniał już wcześniej, do jego rozwoju w naj-

większym stopniu przyczyniły się wyspe-cjalizowane serwisy freelancingowe, w których jedni zgłaszają swoje pomysły, a inni je wykonują. Nad przebiegiem re-alizacji projektu i przekazania wynagro-dzenia czuwają administratorzy tych wi-tryn, którzy są tym samym pośrednikami w kontakcie zleceniodawców z wykonaw-cami (zleceniobiorcami).

Różnorodność ofertFreelancing to nie tylko programowanie w PHP lub innych językach. Przegląda-jąc witryny pośredników natkniemy się na multum dziedzin, w których możemy składać oferty oraz podejmować zada-nia. Znajdziemy więc zlecenia na pisanie programów, artykułów, tłumaczenie tek-stów, dostosowywanie skryptów do po-trzeb klienta, tworzenie grafiki i reklam, a nawet rozwiązywanie zadań domowych.

Jesteś programistą i chcesz wziąć udział w cie-kawym projekcie i zarobić trochę pieniędzy?A może potrzebujesz kogoś, kto wykona dla Ciebie witrynę internetową, aplikację dla księgo-wości czy grafikę? Dzięki serwisom freelancin-gowym każdy z Was znajdzie to, czego potrze-buje przy minimalnym lub żadnym ryzyku.

Wszystkie oferty są przydzielone do za-kresów cenowych, począwszy od drob-nych zadań za dziesięć dolarów, do du-żych zleceń wykonania całych portali za kwoty sięgające tysięcy dolarów. Mno-gość i różnorodność ofert powoduje, że nawet mgliste pojęcie o jakimś zagadnie-niu wystarczy, by podołać niektórym za-daniom. Przykładowo, jeśli nasza wiedza na temat PHP wystarcza do zrozumienia cudzego kodu, możemy się śmiało podjąć zaktualizowania już zainstalowanej apli-kacji (np. osCommerce czy XOOPS) lub

Freelancing – zostań wolnym strzelcemKrzysztof Trynkiewicz

W SIECI

• http://allfreelance.com -– kompendium linków freelancingowych

• http://guru.com• http://rentacoder.com• http://getafreelancer.com• http://zlecenia.przez.net

– polskojęzyczny serwis freelancingowy

Stopień trudności: lll

Co należy wiedzieć...Przydatna będzie podstawowa znajo-mość terminologii biznesowej.

Co obiecujemy...Pokażemy, jak działa freelancing na przykładzie najpopularniejszych ser-wisów, które go organizują.

www.phpsolmag.org38 PHP Solutions Nr 5/2006

Kasa dla Webmastera

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

Freelancing

przetłumaczyć na język obcy tekst zapi-sany w kodzie (ang. hard-coded text), np. komentarze czy łańcuchy wyświetlane na stronie WWW.

Jak to działaWiększość serwisów freelancingowych oprócz spisu ofert udostępnia system pro-wadzący krok po kroku przez cały pro-ces realizacji projektu, którego stadia są w większości serwisów podobne. Załóż-my, że chcemy zlecić komuś wykona-nie panelu administracyjnego naszej wi-tryny internetowej. Określamy więc na-sze wymagania (opcjonalnie także osta-teczny termin zakończenia pracy, tzw. de-adline) i umieszczamy zlecenie na wybra-nym serwisie freelancingowym. Na naszą ofertę odpowiadają potencjalni wykonaw-cy, często proponując konkretną cenę lub zakres cenowy, dodając własne pomysły i określając czas wykonania zadania. Jeśli któryś z nich spełni nasze wymagania, to akceptujemy jego propozycję i wpłacamy ustaloną sumę na konto pośrednika (ang. put to escrow). Mając takie zabezpiecze-nie, wykonawca może rozpocząć swoją pracę. Postępy są odnotowywane u po-średnika – programista musi zdawać co-tygodniowe relacje poparte odpowiedni-mi plikami. W razie niewywiązywania się z umowy, możemy się zwrócić o mediację do pośrednika (może o nią poprosić rów-nież wykonawca) – obie strony są wtedy zobowiązane do zaakceptowania decy-zji arbitra, który rozsądzi racje stron. Je-śli projekt zostanie zakończony pomyśl-nie, pośrednik przekaże wynagrodzenie wykonawcy (możemy mu też, jako zleca-jący, przekazywać części kwoty w trakcie wypełniania kolejnych postanowień umo-wy). Do naszych powinności należało bę-dzie również wystawienie opinii o wyko-nawcy, z którą będziemy się mogli zapo-znać my oraz przyszli klienci. Na Rysun-ku 1 przedstawiamy schemat kolejnych etapów współpracy.

Zadowolenie przede wszystkimWykonując swoją pracę sumiennie, bę-dziemy stopniowo zyskiwali renomę i zaufanie zleceniodawców. Zadowolo-ny klient chętniej skorzysta z naszych usług ponownie – może wystawić tzw. aukcję prywatną, do której zaprosi jedy-nie wybrane przez siebie osoby. Zaufa-

nie i współpraca są korzystne dla obu stron: zleceniodawca może przedsta-wić swój projekt tylko nam (aukcja typu One on One), dzięki czemu zaoszczę-dzi, gdyż w takiej sytuacji opłata pobie-rana przez pośrednika jest często niż-sza: w przypadku Rent a Coder (http://www.rentacoder.com) różnica wynosi aż 2,5% ceny całego zlecenia.

Jak się wybić z tłumuCzęsto zleceniodawcy trudno ocenić, czy osoba stawiająca korzystną propozycję cenową tylko sprawdza grunt, czy też jest to prawdziwa okazja. Jeśli zaakceptujemy taką ofertę, zaś później okaże się, że wy-konawca nie wywiązał się z umowy, mo-żemy stracić dużo więcej, niż tylko czas (pieniądze z depozytu zostaną nam zwró-cone), przykładowo gdy nie będziemy mogli się terminowo wywiązać z naszych zobowiązań wobec innych osób lub firm.

Profesjonalista może też mieć pro-blem, aby przekonać zleceniodawców do relatywnie wysokiej, choć adekwat-nej do jakości świadczonych usług ceny, jeśli musi konkurować z atrakcyjnymi ce-nowo ofertami początkujących freelance-rów. Rozwiązaniem tego problemu jest

wprowadzony np. w Rent a Coder depo-zyt bezpieczeństwa zleceniodawcy (ang. Seller Guarantee Deposit).

Będąc wykonawcą projektów, mo-żemy zdeponować pewien ustalony pro-cent wartości całego zlecenia na kon-to pośrednika. Jeżeli nie wywiążemy się z umowy, nasze pieniądze zostaną prze-znaczone na pokrycie kosztów manipula-cyjnych zleceniodawcy oraz na cele cha-rytatywne (lecz nie samemu zlecenio-dawcy, by uniemożliwić wyłudzanie pie-niędzy). Jeśli natomiast wszystko pójdzie zgodnie z planem, zostanie nam zwróco-ne około 98% zdeponowanej kwoty. De-pozyt stanowi więc zabezpieczenie dla zleceniodawcy i pozwala nam podnieść zaufanie do naszych ofert.

Chrońmy swój pomysłCzęsto miewamy pomysły, których sa-mi nie potrafimy zrealizować z przyczyn technicznych. Taką ideę (a czasem goto-wy do wdrożenia projekt) możemy sprze-dać komuś, kto zrobi z niej użytek. Pa-miętajmy jednak, że nie każdy nabywca jest uczciwy i może po zapoznaniu się z naszymi pomysłami uniknąć zapłaty lub odsprzedać je komuś innemu. Aby unik-

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

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

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

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

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

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

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

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

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

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

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

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

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

Rysunek 1. Kolejne stadia współpracy pomiędzy wykonawcą a zleceniobiorcą (proponowane przez pośredników)

www.phpsolmag.org40

Kasa dla Webmastera

PHP Solutions Nr 5/2006

nąć takich sytuacji, czyli uniemożliwić po-dejrzenie szczegółów projektu przez nie-uczciwych wykonawców, niektóre serwi-sy freelancingowe wprowadziły tzw. Umo-wę o nieujawnianiu informacji (ang. Non-disclosure Agreement). Jeśli dajemy zle-cenie w trybie zgodnym z tą umową, wy-konawcy będą widzieć jedynie podstawo-we informacje o naszym projekcie – jego typ, zakres cenowy i wprowadzone przez nas publicznie dostępne wytyczne. By uj-rzeć szczegóły, wykonawca musi wyrazić chęć ich poznania przez podpisanie po-wyższej umowy, my zaś możemy udo-stępnić mu tajne dane lub nie.

Właściwy wybór wykonawcyBędąc zleceniodawcą, powinniśmy się dobrze zastanowić przed wyborem wyko-nawcy naszego projektu. Naszej decyzji nie powinniśmy podejmować wyłącznie w oparciu o cenę, zwłaszcza w informatyce, gdzie przykładowo koszty stron WWW oscylują między 50zł a tysiącami dola-rów. Dokonując wyboru najlepiej zwró-cić uwagę na to, jak wiele zadań wykonał nasz potencjalny zleceniobiorca oraz ja-kie opinie wyrażali o nim poprzedni zlece-niodawcy i jakie oceny mu wystawili. Po-winniśmy też zajrzeć do portfolio i resume wykonawcy, gdzie dowiemy się o umiejęt-nościach i doświadczeniu nabytym przez niego poza serwisem freelancingowym. Warto też zwrócić uwagę na języki, któ-rymi posługuje się nasz potencjalny zle-ceniobiorca – jeśli wykonanie naszego zadania będzie wymagało częstych kon-sultacji, na pewno łatwiej będzie dojść do porozumienia w języku ojczystym. Jeżeli obowiązuje nas deadline, najważniejszą wytyczną będzie doświadczenie. Warto też zamieścić informację, że do jednego projektu możemy przydzielić kilku wyko-nawców.

Nie powinniśmy też zatrudniać wyko-nawcy, który robił dotychczas projekty za stawki wielokrotnie niższe niż oferowane przez nas, gdyż ich waga i złożoność mu-siała być znacznie mniejsza. Jeśli jednak mamy więcej czasu, niż pieniędzy, to mo-żemy rozważyć zatrudnienie początkują-cego freelancera – ostatecznie i tak po-siadamy zabezpieczenie naszych finan-sów w postaci escrow. Serwisy pośredni-czące oferują często możliwość komuni-kacji z wykonawcami jeszcze przed za-akceptowaniem ich ofert w celu określe-nia szczegółów i wyjaśnienia wszelkich

wątpliwości. Jeżeli jednak wciąż pojawia-ją się niejasności, możemy poprosić o ra-dę facilitatora – przedstawiciela specjal-nego oddziału serwisu freelancingowego, przedstawiając naszą sytuację i podając link do naszej aukcji.

Wybór serwisów pośredniczącychW sieci znajdziemy wiele ofert pośredni-ków. Ważne jest, by wybrać serwis z re-nomą, działający legalnie i obsługują-cy opisane w artykule zabezpieczenia przed nieuczciwością i niesolidnością. Rent a Coder (http://rentacoder.com) ofe-ruje wszystkie wspomniane udogodnienia i z pewnością ma wielką renomę. W chwi-li pisania artykułu posiadał 2 tys. otwar-tych projektów oczekujących na oferty, 150 tys. zarejestrowanych wykonawców i 60 tys. zleceniodawców. Prym we fre-elancingu wiedzie jednak inny serwis – Guru.com (http://www.guru.com/). Liczba zarejestrowanych tam wykonawców jest dziesięciokrotnie większa niż w przypad-ku Rent a Coder (568 tysięcy). Co cieka-we, zleceniodawców jest dwukrotnie mniej niż w poprzednim serwisie (30 tysięcy), natomiast ilość otwartych projektów jest kilkukrotnie większa. Serwis ten oferuje również kluczowe udogodnienia, jednak w chwili pisania artykułu nie funkcjono-wał omawiany w artykule depozyt bezpie-czeństwa zleceniodawcy (Seller Guaran-tee Deposit). Możemy się spodziewać, że zabezpieczenie to zostanie bardzo szybko wprowadzone. Z kolei w serwisie Elance (http://elance.com) znajdziemy ponad 100 tysięcy projektów i obszerne informacje i porady na temat zamawiania i świadcze-nia usług (w tym wzory umów, m.in. Non-disclosure agreement). Listy serwisów fre-elancingowych znajdziemy pod adresami http://www.phpkitchen.com/index.php?/archives/670-PHP-Freelance.html (uwaga – są tu również typowe serwisy do wyszu-kiwania ofert pracy, np. monster.com czy mojolin.com) oraz http://allfreelance.com (All Freelance). Pamiętajmy jednak, że oprócz dużych i często wykorzystywa-nych serwisów, znajdują się tam również takie, które niedawno zaczęły swoją dzia-łalność lub nie są zbyt popularne.

Jeśli szukamy zleceń do wykona-nia, możemy zajrzeć również na ta-kie serwisy, jak Get A Freelancer (http://getafreelancer.com/), który posiada bazę kilku tysięcy otwartych projektów i może być przydatny mimo braku wielu udogod-

nień. Niewątpliwą zaletą takiego rozwią-zania jest mniejsza konkurencja w przy-padku składania przez nas ofert. Jeżeli jednak to my szukamy wykonawcy, war-to wziąć pod uwagę różne, nie tylko naj-większe serwisy – liczy się bowiem także prowizja, jaką będziemy musieli zapłacić pośrednikowi. Z pewnością mniejsza po-pularność nie oznacza gorszych wyko-nawców, więc jeśli nie jesteśmy zbyt wy-bredni, możemy podjąć niewielkie ryzyko i spróbować zaoszczędzić.

Freelancing w PolsceNiestety, w chwili pisania artykułu freelan-cing w naszym kraju był słabo rozwinię-ty. W zasadzie tylko jeden polski serwis freelancingowy, http://zlecenia.przez.net, był wart uwagi i to wyłącznie ze wzglę-du na ilość aukcji, gdyż niestety nie ofe-ruje on wielu opisanych przez nas zabez-pieczeń i udogodnień. Miejmy nadzieję, że w najbliższym czasie sytuacja ta ule-gnie zmianie.

PodsumowanieJako programistom, freelancing daje nam ogromne możliwości zarobienia pieniędzy, nawiązania kontaktów biznesowych, wyro-bienia sobie renomy i doskonalenia swoich umiejętności. Jeśli natomiast potrzebujemy wykonawców naszego projektu, to dzię-ki rozbudowanym serwisom freelancingo-wym mamy dużą szansę znaleźć najbar-dziej odpowiednie osoby. W obu przypad-kach, warto się dobrze zapoznać z różny-mi ofertami oraz opiniami o zamawiających czy wykonawcach. Istotne jest również po-ważne podejście do spraw formalnych – zastosowanie się do opisanych przez nas wskazówek pozwoli zmniejszyć ryzyko wystąpienia niemiłych niespodzianek czy wręcz natknięcia się na oszusta. Życzymy udanych interesów! n

Krzysztof Trynkiewicz od wielu lat zaj-muje się tworzeniem witryn w PHP i Flash. Współpracuje z magazynem PHP Solutions, wykonuje także zlecenia fre-elancingowe. Obecnie rozwija kilka rów-noległych projektów autorskich dostęp-nych na witrynie http://eldoras.com.

Kontakt z autorem:[email protected]

O autorze

www.phpsolmag.org42 PHP Solutions Nr 5/2006

Dla zaawansowanych

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

Zend API Dla zaawansowanych

Zend API jest interfejsem progra-misty, pozwalającym pisać roz-szerzenia dla PHP w języku C.

Do pisania bardzo prostych rozszerzeń wystarczy podstawowa znajomość tego języka – jak zobaczycie dalej, dostęp-ne są narzędzia generujące cały szablon takiego rozszerzenia za nas – jednak do pisania bardziej zaawansowanych pro-gramów, potrzebna jest dobra znajomość C oraz zacięcie detektywistyczne, z po-wodu bardzo słabej dokumentacji niektó-rych elementów Zend API. Nie obejdzie-my się więc bez konieczności analizo-wania źródeł rozszerzeń dostarczanych standardowo z PHP.

Na potrzeby tego artykułu wspólnie stworzymy rozszerzenie wzbogacające PHP o funkcję pozwalającą obliczać pro-ste wyrażenia matematyczne – zawierają-ce podstawowe operacje takie jak =, -, *, czy /. Będzie można także do woli korzy-stać z nawiasów okrągłych ( ), a także kwadratowych [ ].

Twój skrypt działa zbyt wolno? Wydaje Ci się,że przyczyna tkwi w wydajności PHP?A może chcesz połączyć się z inna aplikacją lub wykorzystać swoją ulubiona bibliotekę z C? Rozwiązaniem Twoich problemów może okazać się Zend API.

CodeGen_PECL– przygotowujemy się do pracyStworzenie rozszerzenia całkowicie samo-dzielnie jest zadaniem bardzo skompliko-wanym i żmudnym. Szczególnie począt-kowy etap implementacji jest dość złożo-ny. Na szczęście dziś w większości przy-padków nie musimy się już o to martwić. Wystarczy na przykład skorzystać z Co-deGen_PECL. Skrypt ten na podstawie pliku XML, w którym opisujemy tworzone rozszerzenie, generuje dla nas wszystkie

Zend API – tworzymy własne rozszerzenie dla PHPMarcin Staniszczak

W SIECI

l http://www.php.net/manual/en/internals.php – informacje na temat Zend API znajdują-ce się w oficjalnej dokumen-tacji PHP – niestety dość skromne

l http://somabo.de/talks/ -- materiały z różnych kon-ferencji dotyczących PHP – znajdują się tu także dwie dobre prezentacje porusza-jące problem rozszerzeń dla PHP

l http://www.zend.com/php/internals/extension-writin-g1.php – pierwsza część tu-toriala na temat pisania roz-szerzeń z wykorzystaniem Zend API

l http://www.zend.com/php/internals/extension-writin-g2.php – druga część po-wyższego tutoriala

Stopień trudności: lll

Co należy wiedzieć...Potrzebna będzie podstawowa znajo-mość języka C i PHP.

Co obiecujemy...Z artykułu dowiesz się, jak z wykorzy-staniem Zend API zbudować przykła-dowe rozszerzenie dla PHP pozwala-jące na wykonywanie prostych opera-cji matematycznych.

www.phpsolmag.org42 PHP Solutions Nr 5/2006

Dla zaawansowanych

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

Zend API Dla zaawansowanych

niezbędne pliki oraz szablon rozszerze-nia (zalążek pliku źródłowego w C, któ-ry następnie rozbudowujemy o planowa-ną funkcjonalność). Pozwala to zaoszczę-dzić sporo czasu oraz nerwów, które stra-cilibyśmy starając się samodzielnie wyko-nać całą pracę.

Zanim rozpoczniemy opisywanie rozszerzenia za pomocą XML-a, musi-my najpierw zainstalować skrypt Code-Gen_PECL. Można to zrobić na kilka spo-sobów. Jeśli posiadacie dostęp do Sieci, najlepiej wydać z poziomu terminala po-lecenie:

pear install –o Codegen_PECL

Spowoduje to pobranie oraz zainstalowa-nie skryptu. Jeśli nie mamy aktualnie do-stępu do Internetu, na CD dołączonym do magazynu znajduje się archiwum ze skryptem w wersji 1.0.0. Możemy je zain-stalować przechodząc do katalogu z tym archiwum oraz wydać następujące pole-cenia:

pear install CodeGen-1.0.0.tgz

pear install CodeGen-PECL-1.0.0.tgz

Spowoduje to zainstalowanie odpowied-nio skryptu CodeGen wymaganego do poprawnego działania przez CodeGen_PECL oraz samego CodeGen_PECL.

Opisujemy rozszerzenie za po-mocą XML-aGdy mamy już zainstalowany skrypt CodeGen_PECL, możemy przystąpić do two-rzenia pliku XML z opisem naszego roz-szerzenia.

Cały opis rozszerzenia umieszcza się w znaczniku <extension>. Przyjmuje on jeden parametr będący nazwą tworzone-go rozszerzenia. Nasze rozszerzenie na-zwiemy RPN – od Reverse Polish Nota-tion – nazwy sposobu, w jaki będzie liczy-ło wyrażenie (będzie ono zamieniane do postaci Odwrotnej Notacji Polskiej, a na-stępnie obliczane – więcej na temat ONP w ramce Odwrotna Notacja Polska). Opis rozszerzenia umieszczamy zatem we-wnątrz następującego bloku:

<extension name=”RPN”>

<!—tutaj będzie umieszczony

opis rozszerzenia-->

</extension>

Skrypt CodeGen_PECL na podstawie in-formacji z pliku XML potrafi wygenero-wać tabelkę z informacjami, która ukaże się na stronie zwracanej przez phpinfo(). Nazwę rozszerzenia skrypt pobiera z pa-rametru name znacznika <extension>. Je-śli chcemy w phpinfo() umieścić krótką informację o rozszerzeniu (podsumowa-nie), należy ją umieścić wewnątrz znacz-nika <summary>:

<summary>

Przykładowe rozszerzenie PHP - RPN

</summary>

Główną informację o rozszerzeniu na-leży analogicznie umieścić w bloku

<description>. Autorzy skryptu Code-Gen_PECL zalecają umieszczanie obu tych znaczników zaraz pod otwierają-cym znacznikiem <extension>.

Za umieszczenie informacji o au-torach rozszerzenia odpowiada znacz-nik <maintainers>, zawierający tag <maintainer> opisujący każdego z auto-rów (Listing 1). Na taki opis składa się je-go nazwa (może to być np. nick) umiesz-czona w znaczniku <user>, imię i nazwi-sko umieszczone w znaczniku <name>, e-mail umieszczony w znaczniku <email> oraz rola jaką on odgrywa w zespole ludzi pracujących nad rozszerzeniem umiesz-czona w znaczniku <role>. Znacznik <maintainers> może oczywiście zawierać

Szybki startJuż teraz możecie zapoznać się z efektem działania prezentowanego tu rozszerzenia.Pamiętajmy jednak, że do jego kompilacji będziemy potrzebować systemu uniksowego (np. Linux czy FreeBSD). Teoretycznie w źródłach znajdują się pliki projektu dla Visual Studio, generowane automatycznie przez skrypt CodeGen_PECL, jednak autor nie spraw-dzał, czy rozszerzenie uda się skompilować pod systemem Windows.

Aby przeprowadzić kompilacje, musimy posiadać PHP z zainstalowanymi plika-mi nagłówkowymi (pliki z rozszerzeniem .h wykorzystywane w programach pisanychw języku C), a także kompilator C z narzędziami (gcc, make i itp.).

W celu skompilowania rozszerzenia na swoim systemie przegrywamy jego źródła do siebie na dysk, przechodzimy do katalogu z rozszerzeniem, a następnie wydajemy pole-cenie:

phpize; ./configure; make; sudo make install

Jeśli podczas wykonywania make install otrzymamy informacje o błędzie podczas pró-by wykonania polecenia (brak uprawnień, czyli odpowiedniego wpisu w pliku sudoers), za-logujcie się do systemu jako root i po przejściu do katalogu ze źródłami rozszerzenia po-nownie wydajcie polecenie make install.

Po skompilowaniu i zainstalowaniu rozszerzenia, musimy jeszcze odpowiednio skonfi-gurować PHP dodając w pliku php.ini następującą linijkę:

extension=RPN.so

Upewnijcie się także, że dyrektywa extension_dir w php.ini jest odpowiednio ustawionai wskazuje na katalog, do którego make install skopiowało rozszerzenie. U mnie na przykład wygląda ona tak:

extension_dir = "/usr/local/php5/lib/php/extensions/"

Teraz wystarczy restart serwera HTTP i wszystko powinno działać.

Odwrotna Notacja PolskaOdwrotna Notacja Polska jest specyficznym sposobem zapisu wyrażeń matematycznych. W ONP znak wykonywanej operacji umieszcza się po operandach, dzięki czemu eliminuje się całkowicie konieczność stosowania nawiasów. Na przykład wyrażenie:

(15 – 5) / 2

zapisuje się jako:

15 5 – 2 /

Więcej na temat ONP oraz algorytmów przekształcenia wyrażenia z zapisu in-fiksowego (tradycyjnego) do ONP oraz algorytmów pozwalających na obliczenie tak przekształconego wyrażenia znaleźć można na przykład w Wikipedii: http://pl.wikipedia.org/wiki/Odwrotna_notacja_polska

www.phpsolmag.org44 PHP Solutions Nr 5/2006

Zend APIDla zaawansowanych

wiele bloków <maintainer> z opisami po-szczególnych członków zespołu.

Dodajmy teraz informację o wer-sji naszego rozszerzenia. Służy do te-go blok <changelog> zawierający znacz-niki <release> opisujące poszczególne wersje. Każde wydanie może zostać opi-sane za pomocą numeru umieszczone-go w znaczniku <version>, daty wydania umieszczonej w znaczniku <date>, statu-su wydania (np. beta, stable itd.) w znacz-niku <state> oraz opisu wydania umiesz-czonego w znaczniku <notes>. W bloku <changelog> można umieścić opis wielu wersji. Na Listingu 2 znajduje się opis wy-dania naszego rozszerzenia.

W phpinfo() można jeszcze umie-ścić obrazek, jednak zdarzają się czasa-mi problemy z jego poprawnym wyświe-tlaniem. Jeśli chcemy spróbować, należy skorzystać ze znacznika <logo>. Znacznik ten przyjmuje dwa parametry – src będą-cy nazwą pliku z obrazkiem oraz mimetype określający typ MIME dla obrazka (para-metr ten można zignorować w przypad-ku obrazków w formacie gif, png lub jpeg – skrypt CodeGen_PECL wówczas sam potrafi rozpoznać typ obrazka):

<logo src="calc.gif"

mimetype="image/gif"/>

Ciekawym znacznikiem jest <license>, powodujący dodanie informacji licencyjnej do plików źródłowych rozszerzenia oraz wygenerowanie pliku LICENSE z treścią licencji. Znacznik w tej chwili może przyj-mować wartości PHP, BSD lub LGPL – in-ne traktuje jako wartość nieznaną, na pod-stawie której nie potrafi wygenerować od-powiednich danych.

Listing 1. Opis członków zespołu pracującego nad rozszerzeniem

<maintainers>

<maintainer>

<user>Staniszczak</user>

<name>Marcin Staniszczak</name>

<email>[email protected]</email>

<role>lead</role>

</maintainer>

<!—możesz tutaj umieścić więcej bloków maintainer -->

</maintainers>

Listing 2. Opis wydania rozszerzenia – informacje tu zawarte są wykorzystywane podczas wyświetlania numeru wersji w phpinfo()

<changelog>

<release>

<version>0.0.1</version>

<date>2006-03-20</date>

<state>beta</state>

<notes>Pierwsza beta wersja</notes>

</release>

<!-- możesz tutaj umieścić opis także innych wydań, każde opisując tak jak to

powyżej -->

</changelog>

Listing 3. Opis funkcji tworzonego rozszerzenia

<function name="rpn_calculation">

<proto>float rpn_calculation(string phrase)</proto>

</function>

<function name="rpn_errormsg">

<proto>string rpn_errormsg()</proto>

</function>

<function name="rpn_error">

<proto>bool rpn_errormsg()</proto>

</function>

Rysunek 1. Przykładowa informacja o rozszerzeniu, wygenerowana na podstawie danych zawartych w pliku XML

CodeGen_PECLSkrypt CodeGen_PECL można zna-leźć w repozytorium PEAR. Poprzed-nio nosił on nazwę PECL_Gen i był częścią repozytorium PECL. Skrypt ten w znacznym stopniu ułatwia two-rzenie rozszerzeń dla języka PHP, ge-nerując na podstawie XML-a komplet-ne szablony rozszerzeń. Dokumen-tacja CodeGen_PECL znajduje się pod adresem http://php-baustelle.de/CodeGen_PECL/manual.htm, same rozszerzenie zaś można pobrać albo za pomocą programu pear wchodzą-cego w skład PHP albo ze strony http://pear.php.net/package/CodeGen_PECL/download.

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

Zend API Dla zaawansowanych

www.phpsolmag.org46 PHP Solutions Nr 5/2006

Zend APIDla zaawansowanych

Na Rysunku 1 znajduję się informacja phpinfo() o naszym rozszerzeniu.

Skrypt CodeGen_PECL zawsze sam dodaje ramkę z informacjami o zmiennych wymaganych przez rozszerzenie, ustawia-nych z poziomu pliku php.ini.

Przyszedł czas na to, aby zastano-wić się, jakie funkcje będą potrzebne w naszym rozszerzeniu. Niech będą to rpn_calculation() obliczająca wartość wyrażenia, rpn_errormsg() wyświetlają-ca ewentualną informację o błędzie oraz rpn_error zwracająca true/false w zależ-ności od tego, czy błąd wystąpił nie. Tylko funkcja rpn_calculation będzie przyjmo-wała parametr, wszystkie natomiast będą zwracały wartości. Opiszmy więc w XML-u funkcje tak, aby skrypt CodeGen_PECL mógł dla nas wygenerować ich szablony do wypełnienia kodem. Posłużymy się w tym celu znacznikiem <function>, przyj-mującym parametr name, będący nazwą tworzonej funkcji. Sama nazwa to jed-nak za mało. Musimy jeszcze zdeklaro-wać prototyp funkcji, zawierający dodatko-wo informację o typie zwracanych danych oraz nazwie i typie przyjmowanych para-metrów. Na Listingu 3 znajduje się przy-kład opisujący nasze trzy funkcje.

Jak widzimy owe prototypy funk-cji – umieszczone w znaczniku <proto> – przypominają deklaracje funkcji w C. Różnią się jednak one nazwami typów. Dozwolone nazwy typów zebrane zosta-ły w Tabeli 1.

W opisach funkcji można bezpośred-nio umieszczać kod w języku C. Dla przy-kładu w pliku XML umieściłem kod dla specjalnych funkcji Zend API: RINIT oraz RSHUTDOWN. Funkcje te są wywoływane au-tomatycznie odpowiednio przed oraz po wykonaniu skryptu PHP lub w przypad-ku skryptów CGI / CLI zaraz po wystarto-waniu PHP (dokładniejszy opis wszystkich funkcji systemowych znajduje się w Tabe-li 2). Jeśli chcemy zdefiniować ciało funk-cji specjalnej, trzeba użyć parametru rolę znacznika <function>, ustawiając go na wartość internal, nazwę funkcji zaś usta-wić na jedną z wartości z Tabeli 2. Na Li-stingu 4 znajduje się przykładowa deklara-cja ciał funkcji RINIT oraz RSHUTDOWN.

Zauważmy, że kod funkcji został umieszczony w znaczniku <code> oraz za-mknięty w sekcji CDATA języka XML – na-

leży korzystać z sekcji CDATA, aby parser XML ignorował błędy XML, jakie mogły-by powodować znaki umieszczone w ko-dzie. Ze znacznika <code> możemy ko-rzystać także w funkcjach deklarowanych przez nas samych (np. w naszych funk-cjach rpn_calculation, rpn_errormsg i rpn_error). Jest to wygodne w przypad-ku kodu tak prostego jak na Listingu 4, jednak gdy kod staje się bardziej złożony (taki jak np. kod funkcji rpn_calculation) wygodniej jest kazać skryptowi CodeGen_PECL wygenerować jedynie szablon funk-cji, a cały kod napisać w ulubionym edyto-rze lub środowisku programistycznym.

Na koniec pozostało zdeklarowanie zmiennych ustawianych z poziomu pli-ku php.ini. W naszym przypadku zmien-ne te będą określały rozmiar stosu oraz kolejki wykorzystywanych podczas prze-kształcania wyrażenia matematyczne-go do Odwrotnej Notacji Polskiej oraz do późniejszego przeprowadzenia obliczeń. Zmienne te będą nosiły odpowiednio na-zwy rpn_stack_size oraz rpn_queue_size. Zmienne ustawiane z poziomu php.ini de-finiuje się w bloku <globals>, korzystając ze znacznika <phpini>. Znacznik <phpini> przyjmuje 5 parametrów:

• name – jest to nazwa zmiennej, • type – określa typ zmiennej (patrz Ta-

bela 1),• value – określa wartość, jaką ma

przyjąć zmienna, gdy nie została ona określona w pliku php.ini (lub .htacces czy konfiguracji HTTP) – powinno tu nadawać się jedynie wartości zmien-nym numerycznym, wartości pozosta-łych typów powinno się nadawać w funkcji RINIT,

• onupdate – nazwa metody, która ma zostać wywołana przy zmianie warto-ści – gdy nie podamy tego parametru,

Tabela 1. Typy danych dozwolone w opisach prototypów funkcji w pliku XML

Nazwa typu Opisbool Typ logiczny przyjmujący wartości true oraz false

int Typ numeryczny – całkowity

float Typ numeryczny – zmiennoprzecinkowy

string Ciąg znaków – napis

array Tablica

object Obiekt

mixed Nieokreślony jednoznacznie typ danych

callback Funkcja zwrotna

resource Zasoby

stream Strumień danych

Listing 4. Deklaracja ciał funkcji bezpośrednio w pliku XML

<function role="internal" name="RINIT">

<code>

<![CDATA[

memset(errorMessage, 0, 255);

isError = 0;

int s = RPN_G(rpn_stack_size);

stack_initialize(s);

s = RPN_G(rpn_queue_size);

queue_initialize(s);

]]>

</code>

</function>

<function role="internal" name="RSHUTDOWN">

<code>

<![CDATA[

stack_free();

queue_free();

]]>

</code>

</function>

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

Zend API Dla zaawansowanych

użyta zostanie standardowa metoda OnUpdateString. Możemy tu zastoso-wać metodę o nazwie OnUpdateTYP, gdzie TYP to nazwa typu z Tabeli 1 (pisana z dużej litery). Można także podać nazwę własnej funkcji, jednak trzeba ją wówczas samemu zdefinio-wać (więcej na ten temat w dalszej

części artykułu – niestety skrypt Co-deGen_PECL w tym już nie umie po-móc),

• access – informacja o tym, gdzie użyt-kownik może ustawić wartość danej zmiennej – parametr ten może przy-jąć jedną z wartości zebranych w Ta-beli 3.

Na Listingu 5 możemy zapoznać sięz przykładową deklaracją dwóch zmien-nych modyfikowanych z poziomu pliku php.ini.

Opis umieszczony pomiędzy znacz-nikiem otwierającym a zamykającym <phpini>, używany jest podczas genero-wania przez CodeGen_PECL dokumenta-cji w formacie DocBook (tak jak i wiele in-nych informacji z pliku XML).

Mamy więc już gotowy plik XML.Jeśli chciałbyś dowiedzieć się więcej o CodeGen_PECL, zajrzyj do Ramki Code-Gen_PECL.

Gdy plik XML jest już gotowy, przyszła pora na wygenerowanie na jego podsta-wie ram naszego rozszerzenia. Przejdź-my więc do okna terminala i w katalogu, w którym znajduje się plik XML, wydajmy polecenie:

pecl-gen nazwa_pliku.xml

Jeśli wszystko się udało, skrypt powinien utworzyć nowy katalog o nazwie takiej, jak nasze rozszerzenie, a w nim wszyst-kie niezbędne pliki. Czas więc zacząć pro-gramowanie!

Piszemy programPrzyjrzyjmy się teraz zawartości wygene-rowanego przez skrypt katalogu. Wśród wielu plików znajdują się tam dwa w tej chwili dla nas najistotniejsze – RPN.c oraz php_RPN.h. Otwórzmy teraz plik RPM.c, bo nim będziemy się zajmowali, a na-stępnie odszukajmy po nazwie funkcję rpn_calculation.

Funkcjarpn_calculation znajduje się w dwóch miejscach – najpierw w tablicy RPN_

functions, a następnie już jako defini-cja funkcji (definicja nie przypomina Wam zapewne języka C – o tym jednak w dal-szej części artykułu) – nie licząc komenta-rzy, które dodawał po drodze CodeGen_PECL. Jak można zauważyć, w tablicy zdefiniowane są wszystkie trzy funkcje naszego rozszerzenia – Listing 6. Ele-menty tablicy opisującej funkcje są ty-pu function_entry, który jest strukturą o trzech polach. Nas jednak nigdy nie bę-dą one interesowały. Do zdefiniowania funkcji będziemy używali skryptu Code-Gen_PECL, tak jak to zostało już zapre-zentowane albo makra – np. PHP_FE, tak jak to robi CodeGen_PECL. Jako że cza-sami prawdopodobnie będziemy musieli

Tabela 3. Wartości, jakie może przyjmować parametr access znacznika <phpini> – czyli gdzie można zmieniać wartość danej zmiennej

Nazwa OpisSystem Wartość zmiennej może być ustawiona w pliku php.ini lub konfiguracji

serwera HTTP

Perdir Wartość zmiennej może być ustawiona w pliku .htaccess

User Wartość zmiennej może być zmieniona w kodzie PHP

All Wartość zmiennej może być zmieniona w jakimkolwiek z wymienionych wyżej miejsc.

Listing 5. Deklaracja zmiennych o wartości modyfikowalnej z poziomu pliku php.ini

<globals>

<phpini name="rpn_stack_size" type="int" value="50"

onupdate="OnUpdateLong" access="perdir">

Stack size used for calculations.

</phpini>

<phpini name="rpn_queue_size" type="int" value="150"

onupdate="OnUpdateLong" access="perdir">

Queue size used for calculations.

</phpini>

</globals>

Listing 6. Definicja funkcji oferowanych przez rozszerzenie

function_entry RPN_functions[] = {

PHP_FE(rpn_calculation , NULL)

PHP_FE(rpn_errormsg , NULL)

PHP_FE(rpn_error , NULL)

{ NULL, NULL, NULL }

};

Tabela 2. Wewnętrzne funkcje Zend API

Nazwa funkcji OpisMINIT Funkcja inicjalizująca rozszerzenie. Jest ona wywoływana raz, za-

raz po wystartowaniu modułu PHP na serwerze.

MSHUTDOWN Funkcja sprzątająca po rozszerzeniu. Jest ona wywoływana raz, gdy moduł PHP kończy swoje działanie. Może ona nie być wywo-łana gdy program/moduł PHP/serwer HTTP zakończy swoje dzia-łanie niepoprawnie np. w wyniku błędu.

RINIT Funkcja ta jest wywoływana przed każdym uruchomieniem skryp-tu PHP lub zaraz po funkcji MINIT w przypadku skryptów CGI i CLI.

RSHUTDOWN Funkcja ta jest wywoływana po wykonaniu skryptu PHP. Zosta-nie ona wywołana nawet, gdy skrypt spowoduje wystąpienie błędu krytycznego, może jednak nie zostać wywołana, gdy wystąpi błąd na poziomie języka C.

MINFO Funkcja ta jest wywoływana przez phpinfo() podczas wyświetla-nia informacji o rozszerzeniu. To ona odpowiada za wyświetlanie ramki z informacjami.

www.phpsolmag.org48 PHP Solutions Nr 5/2006

Zend APIDla zaawansowanych

samodzielnie dodać jakąś funkcję do roz-szerzenia, powinniśmy zapoznać się z makrami ułatwiającymi to zadanie. W Ta-beli 4 zostały zebrane wszystkie makra wspomagające samodzielne definiowa-nie funkcji. Należy pamiętać, że ostatnim elementem tablicy z Listingu 6 musi być {NULL, NULL, NULL}.

Sama deklaracja funkcji jest dość nietypowa jak na język C. Do deklaracji wykorzystano makro PHP_FUNCTION ko-respondujące z makrem PHP_FE (gdy ko-rzystasz z makr z serii ZEND, do deklara-cji funkcji należy użyć analogicznego do PHP_FUNCTION makra ZEND_FUNCTION). Jak widać na Listingu 7, nie zostało tu w ty-powy sposób określone jakie, ani nawet ile parametrów przyjmuje funkcja. Na szczęście istnieje makro ZEND_NUM_ARGS zwracające liczbę parametrów przeka-zanych do funkcji. Można je wykorzy-stać do sprawdzenia, czy przekazana ze skryptu PHP liczba argumentów jest po-prawna. Jeśli nie, należy wywołać makro WRONG_PARAM_COUNT, które kończy działa-nie funkcji, informacją o błędzie zbliżo-ną do tej:

Warning: Wrong parameter count for

rpn_calculation in

/home/marcin/public_html/test.php

on line 3

Kod wygenerowany przez Code-Gen_PECL do parsowania parame-trów podanych podczas wywołania funk-cji z poziomu skryptu PHP używa funkcji zend_parse_parameters. Jest to funkcja o zmiennej liczbie parametrów. Jej nagłó-wek wygląda następująco:

int zend_parse_parameters

( int num_args TSRMLS_DC,

char* type_spec, ... )

Pierwszym argumentem jest liczba pa-rametrów przekazanych do funkcji pod-czas jej wywołania, drugi to łańcuch znaków opisujący typ parametrów ocze-kiwanych przez funkcję, dalej są to już zmienne, w których mają zostać zapisa-ne parametry. Ważny jest tu drugi para-metr, czyli łańcuch znaków opisujący parametry. Łańcuch ten może składać się z następujących znaków:

• l – określa, że zmienna na pozycji tego znaku ma być typu long,

• d – określa, że zmienna na pozycji tego znaku ma być typu double,

• s – określa, że zmienna na pozy-cji tego znaku ma być typu string – oprócz napisu, funkcja zend _

parse _ parameters zwraca także jego długość (do kolejnego parametru na liście parametrów wywołania funk-cji zend _ parse _ parameters),

• b –określa, że zmienna na pozycji te-go znaku ma być typu boolean,

• r – określa, że zmienna na pozycji te-go znaku ma wskazywać na zasób (ang. resource) – zwraca wartość ty-pu zval*,

• a – określa, że zmienna na pozycji tego znaku ma być tablicą – zwraca wartość typu zval*,

• o – określa, że zmienna na pozycji te-go znaku ma być obiektem dowolnej klasy – zwraca wartość typu zval*,

• O – określa, że zmienna na pozycji te-go znaku ma być obiektem takiej sa-mej klasy jak klasa kolejnego parame-tru na liście wywołania funkcji zend _

parse _ parameters (parametr ten nie będzie używany do przechowywania wartości kolejnego parametru przeka-zanego do skryptu PHP – w tym celuużyty zostanie kolejny parametr z li-sty parametrów) – zwraca wartość ty-pu zval*,

• z – parametr dowolnego typu – zwra-ca wartość typu zval*,

• | (pionowa kreska) – oznacza, że na-stępne parametry są opcjonalne,

• / – oznacza, że wartość poprzednie-go parametru ma zostać skopiowana, a nie przekazana przez referencję,

• ! – oznacza, że poprzedni parametr może przyjmować wartość NULL.

Najwięcej wątpliwości może nasuwać wartość O oraz s, gdyż korzystają one z dwóch parametrów. Skorzystanie z war-tości s, jak widać na Listingu 7, powodu-

Tabela 4. Makra służące do definiowania funkcji

Nazwa Makra Opis

ZEND_FE(name, arg_types) Definicja funkcji o nazwie name. Parametr arg_types powinien być zawsze ustawio-ny na NULL. Zdeklarowana w ten sposób funkcja będzie widoczna w PHP pod na-zwą name, natomiast odwoływała się bę-dzie do funkcji C zif_name

ZEND_NAMED_FE(php_name, name, arg_types)

Definicja funkcji widocznej w PHP pod nazwą php_name natomiast odwołująca się do funkcji C o nazwie name. Makro to powinno być używane wówczas, gdy nie chcemy, aby makro ZEND_FE auto-matycznie nadawało prefix naszym funk-cjom. Tak jak poprzednio parametr arg_ty-pes powinien być ustawiony na NULL

ZEND_FALIAS(name, alias, arg_types) Makro to definiuje alias o nazwie alias do funkcji PHP o nazwie name. arg_types powinno być ustawione na wartość NULL.

PHP_FE(name, arg_types) Stary odpowiednik makra ZEND_FE

PHP_NAMED_FE(runtime_name, name, arg_types)

Stary odpowiednik makra ZEND_NA-MED_FE

Listing 7. Deklaracja funkcji rpn_calculation

PHP_FUNCTION(rpn_calculation)

{

const char * phrase = NULL; int phrase_len = 0;

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s/", &phrase, &phrase_len) == FAILURE) {

return; }

php_error(E_WARNING, "rpn_calculation: not yet implemented"); RETURN_FALSE;

RETURN_DOUBLE(0.0);

}

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

Zend API Dla zaawansowanych

je zwrócenie poza napisem (do zmiennej phrase) także jego długości (do zmiennej phrase_len). Gdyby funkcja oczekiwała po napisie jeszcze jakiegoś parametru, zo-stałby on zwrócony do zmiennej umiesz-czonej na liście parametrów funkcji zend_parse_parameters zaraz za phrase_len. Podobnie działa znak O, z tą różnicą, że drugi parametr wykorzystuje nie do zapi-sania długości, a do odczytu wymagane-go typu klasy.

Funkcja zend_parse_parameters po zakończeniu swojego działania zwraca wartość SUCCESS w przypadku powodze-nia lub FAILURE, gdy wystąpi błąd (w przy-

padku błędu, poza zwróceniem odpowied-niej wartości, powoduje ona także wyświe-tlenie ostrzeżenia z informacją np. o nie-poprawnym typie parametrów użytkowni-kowi skryptu).

Tworząc w Zend API funkcje dostępne z poziomu PHP, należy pamiętać o tym, aby zwracać z niej wartości korzystając z odpowiednich makr. Makra te mają za za-danie odpowiednio skonwertować zwra-caną wartość tak, aby mogła ona być po-prawnie zinterpretowana przez Zend En-gine. Wszystkie makra, z których można korzystać do zwracania wartości z funkcji, zebrano w Tabeli 5.

Często przydatne i ułatwiające wie-le zadań może okazać się wywołanie z poziomu naszego rozszerzenia jednej z funkcji PHP. Nie da się tego zrobić w spo-sób naturalny, tak jak zrobilibyśmy to wy-wołując funkcje napisane w C. Na szczę-ście dostępna jest w Zend API funkcja call_user_function. Oto jej nagłówek:

int call_user_function (

HashTable* function_table,

zval** object_pp, zval* function_name,

zval* retval_ptr, zend_uint param_count,

zval* params[] TSRMLS_DC )

Parametry wyglądają dość zawile, na szczęście wszystko jest prostsze, niż może się teraz wydawać. Przyjrzyjmy się Listingowi 8. Jest to fragment funkcji rpn_calculation, odpowiedzialny za za-mianę wszystkich nawiasów kwadrato-wych występujących w wyrażeniu mate-matycznym, na nawiasy okrągłe. Jak wi-dać, w wywołaniu funkcji, za pierwszy parametr podaliśmy wywołanie makra CG(function_table), które na podstawie swojego parametru zwraca tabele funk-cji. Drugi parametr otrzymał wartość NULL, gdyż jest on istotny tylko wówczas, gdy wywoływana funkcja jest metodą jakiegoś obiektu. Jako że my potrzebujemy funkcji str_replace, parametr ten ustawiamy na wartość NULL. Kolejny parametr jest na-zwą zmiennej, do której ma zostać zapi-sany wynik działania wywoływanej funk-cji. W naszym przypadku jest to zmienna phrase, więc ta sama, która zostaje pod-dana działaniu funkcji. Ostatnie dwa argu-menty funkcji call_user_function to licz-ba parametrów, jakie mają zostać przeka-zane do wywoływanej funkcji oraz tablica zawierająca te parametry.

Prawdopodobnie zauważyliście już, że większość parametrów oczekiwa-nych przez funkcję call_user_function jest typu zval. Musimy więc je odpowied-nio utworzyć. Po zdeklarowaniu parame-tru, musimy go zainicjować jeszcze przed nadaniem mu jakiejkolwiek wartości. Słu-ży do tego makro MAKE_STD_ZVAL. Na-stępnie po zainicjowaniu zmiennej, może-my nadać jej wartość korzystając z makr z serii ZVAL_NAZWATYPU. Makra te zostały ze-brane i opisane dokładniej w Tabeli 6.

Tak więc pierwsze wywołanie funkcji str_replace, zamieniające nawias [ na ( w PHP miałoby postać:

$phrase = str_replace(‘[‘,‘(‘, $phrase);

Tabela 5. Makra zwracające wartość z funkcji – należy używać w tworzonych funkcjach, zamiast typowych instrukcji return wartosc

Makro Opis

RETURN_RESOURCE(resource) Zwraca wskaźnik do zasobów

RETURN_BOOL(bool) Zwraca typ logiczny

RETURN_NULL() Zwraca wartość NULL

RETURN_LONG(long) Zwraca cyfrę całkowitą

RETURN_DOUBLE(double) Zwraca liczbę zmiennoprzecinkową

RETURN_STRING(string, duplicate) Zwraca napis – drugi parametr określa, czy napis ma zstać zduplikowany

RETURN_STRINGL(string, length, dupli-cate)

Zwraca napis o długości określonej dru-gim parametrem. Trzeci parametr określa, czy napis ma być zduplikowany. Makro to jest szybsze od RETURN_STRING.

RETURN_EMPTY_STRING() Zwraca pusty napis

RETURN_FALSE Zwraca logiczny fałsz

RETURN_TRUE Zwraca logiczną prawdę

Listing 8. Deklaracja funkcji rpn_calculation

zval func;

zval *params[3];

MAKE_STD_ZVAL(params[0]);

MAKE_STD_ZVAL(params[1]);

MAKE_STD_ZVAL(params[2]);

ZVAL_STRING(&func, "str_replace", 0);

ZVAL_STRING(params[0], "[", 0);

ZVAL_STRING(params[1], "(", 0);

ZVAL_STRING(params[2], phrase, 0);

call_user_function(CG(function_table),

(zval**)NULL, &func, &phrase, 3, params TSRMLS_CC);

ZVAL_STRING(params[0], "]", 0);

ZVAL_STRING(params[1], ")", 0);

ZVAL_STRING(params[2], phrase, 0);

call_user_function(CG(function_table),

(zval**)NULL, &func, &phrase, 3, params TSRMLS_CC);

www.phpsolmag.org50 PHP Solutions Nr 5/2006

Zend APIDla zaawansowanych

Tutaj natomiast potrzebowaliśmy na to 10 linijek. Zauważmy także, że przy drugim wywołaniu funkcji str_replace (zamienia-jącym nawias ] na )), nie trzeba już inicjo-wać zmiennych.

Typ zval jest wykorzystywany bardzo intensywnie przez Zend API – tego wła-śnie typu są wszystkie zmienne, na któ-rych operuje PHP. Do wygodnej pracy, potrzebne więc są także funkcje konwer-tujące zawartość zval pomiędzy różny-mi typami obsługiwanymi przez PHP (np. convert_to_boolean_ex(z)). Warto też zapoznać się z Rysunkiem 2, na którym przedstawiona została budowa struktury zval (gdy ją poznasz, możesz samodziel-nie korzystać z jej pól).

Funkcje specjalne Zend APITo, z czym zapoznaliście się do tej po-ry, pozwoli Wam już na pisanie nawet dość złożonych rozszerzeń. Przyjrzyj-my się teraz funkcjom specjalnym Zend API (przypomnijmy sobie, jak konfiguro-waliśmy za pomocą XML-a funkcje we-wnętrzne Zend API – Tabela 2). Tak jak w przypadku funkcji oferowanych przez rozszerzenie, tak i funkcje wewnętrzne Zend API muszą zostać przed użyciem zdeklarowane w odpowiedniej struktu-rze. Przyjrzyjmy się więc Listingowi 9 oraz 10. Jak się prawdopodobnie już do-myślacie, struktura zend_module_entry, służy do pisania tworzonego rozsze-rzenia. Pierwsze jej cztery pola zawszezastępuje się makrem STANDARD_MODULE_HEADRE, kolejne pole jest nazwą rozsze-rzenia – w naszym przypadku jest to RPN. Następnie podajemy nazwę tablicy opisującej funkcje oferowane przez roz-szerzenie (u nas struktura ta nazywa się RPN_functions – Listing 6). Kolejne pięć pól struktury, to deklaracje funkcji we-wnętrznych rozszerzenia – odpowied-nio MINIT, MSHUTDOWN, RINIT, RSHUTDOWN, oraz MINFO. Jeśli któraś z tych funk-cji jest w naszym rozszerzeniu zbędna, makro z serii PHP_ dodane podczas ge-nerowania pliku źródłowego w C przez skrypt CodeGen_PECL, zastępujemy wartością NULL. Wszystkie pozostałe po-la struktury zostają wypełnione poprzez wywołanie makra STANDARD_MODULE_

PROPERTIES.Definicje funkcji wewnętrznych zo-

stały wygenerowane przez Code-Gen_PECL. Tak jak w przypadku kodu funkcji oferowanych przez nasze roz-szerzenie, tak i w przypadku funkcji we-

Tabela 6. Makra przypisujące odpowiednią wartość zmiennej typu zval – pierwszy parametr makra to zawsze nazwa zmiennej typu zval

Nazwa makra OpisZVAL_RESOURCE(z, l) Przypisuje zmiennej zval wartość zasobu. Parametr l jest

wartością typu long

ZVAL_BOOL(z, b) Przypisuje zmiennej zval wartość logiczną – boolean.Parametr b przyjmuje wartości 0/1.

ZVAL_FALSE(z) Jest odpowiednikiem wywołania makra ZVAL_BOOL(z, 0)

ZVAL_TRUE(z) Jest odpowiednikiem wywołania makra ZVAL_BOOL(z, 1)

ZVAL_NULL(z) Przypisuje zmiennej zval wartość NULL

ZVAL_LONG(z, l) Przypisuje zmiennej zval wartość typu long, przekazanąw drugim parametrze (l).

ZVAL_DOUBLE(z, d) Przypisuje zmiennej zval wartość typu double, przekazaną w drugim parametrze (d).

ZVAL_STRING(z, s, dup) Przypisuje zmiennej zval wartość typu string (napis).Parametry: s – ciąg znaków char*, dup – jeśli ma wartość 1 zmienna ma zostać skopiowana, jeśli 0 ma zostać przeka-zana przez referencję

ZVAL_STRINGL(z, s, l, dup) Przypisuje zmiennej zval wartość typu string (napis). Pa-rametry: s – ciąg znaków char*, l – długość ciągu znaków, dup – jeśli ma wartość 1 zmienna ma zostać skopiowana, jeśli 0 ma zostać przekazana przez referencję

ZVAL_EMPTY_STRING(z) Przypisuje zmiennej zval pusty ciąg znaków

ZVAL_ZVAL(z, zv, dup, dtor) Kopiuje jedną zmienną zval do drugiej. Parametr dup okre-śla, czy wartość ma zostać skopiowana, czy przekazana przez referencję. Parametr dtor określa, czy zmienna źró-dłowa (zv) ma zostać zniszczona (1) po wykonaniu kopio-wania, czy też nie (0).

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

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

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

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

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

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

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

� � ��������

� ������

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

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

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

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

� ������

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

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

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

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

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

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

��

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

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

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

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

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

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

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

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

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

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

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

Rysunek 2. Budowa struktury zval – w PHP wszystkie wartością zmiennych są typu zval

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

Zend API Dla zaawansowanych

wnętrznych Zend API, definicja funkcji realizowana jest z wykorzystaniem od-powiednich makr. Makra te noszą na-zwy PHP_nazwaFunkcji_FUNCTION, gdzie nazwa funkcji to odpowiednio MINIT,

MSHITDOWN, RINIT, RSHUTDOWN oraz MINFO. Funkcja MINFO została już praw-dopodobnie wystarczająco dobrze wy-generowana przez skrypt, można jed-nak się jej przyjrzeć i w razie konieczno-ści zmodyfikować wygenerowany kod. Nasze rozszerzenie wykorzystuje dodat-kowo funkcje RINIT oraz RSHUTDOWN do zainicjowania stosu oraz kolejki wyko-rzystywanej podczas obliczeń (RINIT), a następnie zwolnienia wykorzystywanych przez te struktury zasobów (RSHUTDOWN). Jeśli dobrze pamiętamy, ciało tych funk-cji zostało określone jeszcze na pozio-mie pliku XML.

Zmienne php.iniDo pełni szczęścia brakuje nam jesz-cze umiejętności odczytywania wartości zmiennych konfigurowalnych z poziomu pliku php.ini. Zmienne te są deklarowane z wykorzystaniem makr (Listing 11).

Makro STD_PHP_INI_ENTRY infor-muje PHP, która globalna zmienna ma być modyfikowana z poziomu pliku php.ini – zajrzyjmy do pliku nagłówko-wego php_RPN.h, tam są zdeklarowane zmienne globalne. Parametry makra, to odpowiednio: nazwa zmiennej, pod ja-ką ma ona być modyfikowana z pozio-mu pliku php.ini; wartość zmiennej, jaka ma zostać użyta, gdy nie zostanie ona nadana w pliku php.ini; sposób dostępu do zmiennej (możliwe wartości to PHP_INI_SYSTEM, PHP_INI_USER, PHP_INI_ALL oraz PHP_INI_PERDIR – Tabela 3). Ko-lejny parametr jest nazwą metody, któ-ra ma zostać wywołana w momencie zmiany wartości zmiennej. Dostępne są standardowe funkcje obsługi zdarze-nia aktualizacji. Posiadają one nazwy OnUpdateTyp, gdzie typ to odpowiednia nazwa typu zmiennej – Long, String, Bool, Double itd. Piąty parametr makra to zmienna globalna, która ma otrzymać wartość zmiennej z pliku php.ini. Osta-nie dwa parametry są strukturami opi-sującymi zmienne globalne naszej apli-kacji – nie musimy się nimi przejmować, gdyż nawet jeśli chcemy ręcznie dodać zmienną modyfikowaną z poziomu pliku php.ini, ostatnie dwa parametry pozo-staną takie, jak wygenerował je skrypt CodeGen_PECL.

Czasami zdarza się, że chcielibyśmy napisać własną funkcję wykonywaną auto-matycznie podczas zmiany wartości zmien-nej z php.ini. Jest to zadanie bardzo proste i sprowadza się do użycia odpowiedniego makra podczas tworzenia funkcji:

PHP_INI_MH(OnChangeQueueSize)

{

zend_printf("Wartość RPM.rpn_queue_size

to %s<br>", new_value);

return SUCCESS;

}

Jak widać, nowa wartość przekazywa-na jest w zmiennej new_value. Oczy-wiście, aby funkcja ta była automa-tycznie wywoływana po zmianie war-

tości queue_size, musimy jej nazwę umieścić jako czwarty parametr makra STD_PHP_INI_ENTRY. Jako że często po-trzebna może być nam także długość nowej wartości (np. gdy jest to napis), poniżej przedstawiamy definicję makra PHP_INI_MH:

#define PHP_INI_MH (name)

int name(php_ini_entry *entry,

char *new_value,

uint new_value_length,

void *mh_arg1,

void *mh_arg2, void *mh_arg3)

Poszczególne parametry funkcji stworzo-nej za pomocą tego makra, to odpowied-nio – struktura opisująca zmienną, nowa

Listing 9. Struktura opisująca nasze rozszerzenie oraz funkcje wewnętrzne Zend API

zend_module_entry RPN_module_entry = {

STANDARD_MODULE_HEADER,

"RPN",

RPN_functions,

PHP_MINIT(RPN),

PHP_MSHUTDOWN(RPN),

PHP_RINIT(RPN),

PHP_RSHUTDOWN(RPN),

PHP_MINFO(RPN),

"0.0.1",

STANDARD_MODULE_PROPERTIES

};

Listing 10. Struktura zend_module_entry

typedef struct _zend_module_entry zend_module_entry;

struct _zend_module_entry { unsigned short size; unsigned int zend_api; unsigned char zend_debug; unsigned char zts; char *name; zend_function_entry *functions;

int (*module_startup_func)(INIT_FUNC_ARGS); int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); int (*request_startup_func)(INIT_FUNC_ARGS); int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); char *version;[pozostałe elementy struktury]

};

Listing 11. Zmienne konfigurowalne deklarowane z wykorzystaniem makr będą odczytywane z poziomu php.ini

PHP_INI_BEGIN()

STD_PHP_INI_ENTRY("RPN.rpn_stack_size", "50", PHP_INI_PERDIR,

OnUpdateLong, rpn_stack_size, zend_RPN_globals, RPN_globals)

STD_PHP_INI_ENTRY("RPN.rpn_queue_size", "150", PHP_INI_PERDIR,

OnUpdateLong, rpn_queue_size, zend_RPN_globals, RPN_globals)

PHP_INI_END()

www.phpsolmag.org52 PHP Solutions Nr 5/2006

Zend APIDla zaawansowanych

wartość zmiennej, długość zmiennej oraz trzy opcjonalne argumenty, które w tej chwili nas nie interesują.

Zanim będziemy mogli odczy-tać zmienne zdefiniowane wyżej, musi-my jeszcze w funkcji MINIT wywołać ma-kro ZEND_INIT_MODULE_GLOBALS. Makro to przyjmuje trzy parametry – nazwę roz-szerzenia (u nas jest to RPN), nazwę funk-cji, która ma być wywołana podczas inicjo-wania zmiennych, nazwę funkcji, która ma być wywołana podczas niszczenia zmien-nych.

Należy także pamiętać o tym, aby za-inicjować w wywołaniu w funkcji MINIT ma-kra REGISTER_INI_ENTRIES(), w funkcji MSHUTDOWN natomiast makra UNREGISTER_INI_ENTRIES().

Teraz możemy się już odwoływać do zmiennych globalnych, za pomocą makra NAZWAROZSZERZENIA_G(nazwa_zmiennej), czyli w naszym przypadku RPN_G(nazwa_zmiennej) – spójrzmy na Listing 4, tam w kodzie funkcji RINIT oraz RSHUDOWN odwo-ływaliśmy się do zmiennych rpn_stack_size oraz rpn_queue_size.

Kompilacja i uruchomieniePrzetestujmy teraz nasze rozszerze-nie. Dzięki plikom wygenerowanym przez

skrypt CodeGen_PECL, kompilacja i in-stalacja rozszerzenie jest zaskakująco prosta. Sprowadza się do wydania w ka-talogu z rozszerzeniem następujących ko-mend:

phpize

./configure

make

sudo make install

Teraz wystarczy, że w pliku php.ini do-damy linijkę nakazującą załadowanie rozszerzenia (w przypadku rozszerze-nia przykładowego linijka ta ma postać: extension=RPN.so) i restartujemy ser-wer HTTP. Możemy także odpowiednio zmodyfikować rozmiar stosu oraz kolej-ki, dodając do pliku php.ini jeszcze dwie linijki:

RPN.rpn_stack_size = wielkość_stosu

RPN.rpn_queue_size = wielkość_kolejki

Jeśli wszystko przebiegło pomyślnie, po-winniśmy móc uruchomić przykładową aplikację PHP korzystającą z naszego rozszerzenia (patrz Rysunek 3).

Na zakończenieArtykuł ten nie wyczerpuje tematu tworze-nia rozszerzeń dla PHP. Brak dobrej doku-mentacji na pewno utrudnia życie. Z dru-giej strony duża liczba standardowych roz-szerzeń dostępnych ze źródłami PHP bar-dzo dużo pomaga przy pisaniu własnych rozszerzeń. Trzeba jednak być przygoto-wanym na konieczność analizowania cu-dzego kodu np. parametrów przyjmowa-nych przez interesujące nas makro.

Jeśli zainteresował Was temat roz-szerzeń i chciałbyście pogłębić swo-ją wiedzę w tej dziedzinie, polecam rozpocząć pracę od modyfikacji na-szego rozszerzenia. Zostało ono ce-lowo stworzone w sposób, który po-zwoli Wam na dokonanie w nim wielu usprawnień. Zacznijcie od próby wyeli-minowania konieczności inicjalizowa-nia stosu oraz kolejki – niech ich roz-miar dostosowuję się do potrzeb (nie jest to trudne zadanie w C). Następnie możecie zmodyfikować kod tak, aby stos oraz kolejka nie zapamiętywały wartości typu double lub char, a od ra-zu PHP-owe zval. n

Rysunek 3. Działanie przykładowej aplikacji korzystającej z utworzonego rozszerzenia

Listing 12. Kod przykładowej aplikacji korzystającej z utworzonego rozszerzenia, efekt jego działania wiadać na Rysunku 3

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

xhtml1/DTD/xhtml1-strict.dtd">

<html>

<head>

<meta name="Description" content=

"Skrypt demonstrujący korzystanie z funkcji rozszerzenia RPN"/>

<meta name="Keywords" content="ZendAPI PHP extension rozszerzenie C" />

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

<title>Strona demonstracyjna</title>

</head>

<body>

<?php

if (isset($_POST['phrase'])) { $fltRet = rpn_calculation($_POST['phrase']);

if ($fltRet !== false) { echo $_POST['phrase'].' = '.$fltRet.'<br/>'; } else {

echo '<em>'.rpn_errormsg().'</em>';

}

}

?>

<form method="post">

Podaj wyrażenie do obliczenia: <input type="text" name="phrase"/> <input type="submit" value="Oblicz"/>

</form>

</body>

</html>

Marcin Staniszczak jest studen-tem pierwszego roku informatyki stu-diów uzupełniających magisterskich na WSHE w Łodzi. W PHP progra-muje od wielu lat. Jest autorem kilku-nastu publikacji o tematyce PHP. Jest autorem frameworka MVC dla PHP5 (web.framework) oraz systemu szablo-nów dla PHP5 (web.template).

O autorze

www.phpsolmag.org54 PHP Solutions Nr 5/2006

Dla zaawansowanych

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

Jak zbudować własny kontener IoC? Dla zaawansowanych

Na szczęście mamy kontener IoC (ang. Inversion of Control), któ-ry już niejednokrotnie gościł na

łamach PHP Solutions. Czytelnicy, którzy znają poprzednie artykuły, wiedzą już, co mniej więcej potrafi IoC: kontener zapew-ni nam graf przygotowanych do użycia obiektów czy np. umożliwi łatwe konfigu-rowanie dekoratorów.

Nie dzwoń do nas, my zadzwonimy do CiebiePowyższe zdanie to dobrze znane progra-mistom tzw. prawo Hollywood (ang. Holly-wood Principle). W myśl tego paradygma-tu tworzy się oprogramowanie spójne, z luźnymi powiązaniami pomiędzy obiekta-mi, łatwe do testowania i utrzymania.

W obecnym artykule z cyklu Wzorce projektowe i dobre praktyki programistycz-ne wspólnie stworzymy prosty kontener IoC, którego działanie w dużym uprosz-czeniu opiera się na wspomnianym prawie Hollywood. Kod kontenera wraz z przykła-

Wyobraźmy sobie, że firma, dla której stworzyliśmy aplikację, po jakimś czasie powiększyła się znacznie i poprosiła nas o migrację baz danych do jednej centralnej pracującej w oparciu o LDAP. Niestety, jeśli architektura naszej aplikacji nie została zaprojektowana prawidłowo, czeka nas wyjątkowo żmudna i ciężka praca.

dową implementacją zostanie jak w przy-padku poprzednich artykułów umieszczo-ny na stronie http://flexi.sf.net/.

Czym jest kontener IoC?Kontener IoC to nic innego jak konfigu-rowalna fabryka obiektów, która potra-fi nie tylko powołać obiekty do życia, ale także je skonfigurować tak, aby zaraz po powołaniu były gotowe do użycia. O tym, jak właściwie składać obiekty i o różnych metodach składania obiektów, można pi-sać książki. Wbrew pozorom jest to pro-ces dość skomplikowany i jeżeli robiony

Budujemy własny kontener IoC, czyli jak to się robi w Hollywood?Piotr Szarwas

W SIECI

l http://en.wikipedia.org/wiki/Hollywood_Principle – Holly-wood Principle

l http://www.theserverside.com/tt/articles/article.tss?l=IOCBeginners – wprowadze-nie do Dependency Injection

l http://www.martinfowler.com/articles/injection.html – Inversion of Control Containers/Dependency In-jection pattern

l http://picocontainer.codehaus.org/ – implementacja IoC dla Javy

l http://flexi.sf.net/ – budowa-ny przez nas framework, peł-ne źródła omawiane w arty-kule

Stopień trudności: lll

Co należy wiedzieć...Potrzebna będzie znajomość obiektowe-go programowania w PHP.

Co obiecujemy...Z artykułu dowiesz się, jak stworzyć własny kontener IoC w myśl paradyg-matu Hollywood Principle.

www.phpsolmag.org54 PHP Solutions Nr 5/2006

Dla zaawansowanych

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

Jak zbudować własny kontener IoC? Dla zaawansowanych

jest niewłaściwie, może spowodować, że aplikacja, którą napiszecie będzie bardzo trudna w utrzymaniu. Istnieje wiele spraw-dzonych wzorców pokazujących, jak po-prawnie składać obiekty. Zachęcamy do przeczytania artykułu Obiektowa linia montażowa, czyli przejrzyste i elastyczne aplikacje w PHP5, który ukazał się w PHP Solutions nr 2/2006 i jest poświęcony te-mu zagadnieniu.

Zastosowanie kontenera IoC do skła-dania obiektów w funkcjonalne grupy może znacznie ułatwić budowanie i testowanie złożonych aplikacji. Budowanie dlatego, że obiekty komponujemy w funkcjonalnegrupy dopiero podczas uruchamiania apli-kacji. Oznacza to, że praktycznie w do-wolnym momencie życia aplikacji może-my wymieniać klasy, z których składa się dana aplikacja. Taka wymiana potrafi być oczywiście bardzo trudna, w szczególno-ści jeżeli nie stosujemy pisania do inter-fejsów i silnego typowania. Od pewnego czasu jednak obie te funkcjonalności do-stępne są w PHP, dlatego powinniśmy je stosować.

Przykład z życia wziętyWyobraźmy sobie następujący problem: jakiś czas temu stworzyliśmy aplikację, w której istniała klasa odpowiadająca za uwierzytelnienie i autoryzację użytkowni-ków. Klasa ta operowała na bazie danych. Jako że była kluczowa w działaniu aplika-cji, używaliśmy jej w wielu miejscach.

Jeżeli nie wiedzieliśmy jeszcze o ist-nieniu kontenera IoC, do przekazywa-nia tej klasy pomiędzy różnymi warstwa-mi aplikacji mógł posłużyć nam wzorzec singleton lub registry. Wyobraźmy sobie teraz, że klient, dla którego została napi-sana aplikacja, wymaga modyfikacji bazy użytkowników, jego firma powiększa się i została podjęta decyzja o migracji wszyst-kich baz użytkowników do centralnej bazy działającej w oparciu o LDAP. Wyobraźmy sobie przeszukiwanie całej aplikacji w sy-tuacji, gdy umieszczaliśmy wywołania kla-sy ręcznie lub poprzez wzorzec singleton czy registry. Dodatkową bolączką na pew-no będzie przetestowanie takiej aplikacji. Nie chcemy wpaść w takie tarapaty.

Gdybyśmy stosowali interfejsy i konte-ner IoC, sprawa byłaby bardzo prosta. Na-leżałoby stworzyć nową klasą implemen-tującą odpowiedni interfejs i podmienić w pliku konfiguracyjnym kontenera defini-

cję klasy odpowiadającej za zarządzanie użytkownikami. O tym, jak to zrobić, do-wiecie się już za chwilę.

Należy jeszcze powiedzieć o innej, bardzo ważnej zalecie IoC. Dzięki IoC obiekty są słabo ze sobą powiązane, a przez to łatwiejsze w testowaniu przy po-mocy Unit Testów (testów jednostkowych).

Pamiętajmy, że relacje pomiędzy obiekta-mi pojawiają się dopiero w momencie uru-chomienia aplikacji. Słabe wiązanie powo-duje też, że klasy są bardziej elastyczne i generyczne, co oznacza, że można je wy-korzystywać w większej ilość przypadków.

Wracając teraz do naszego przykładu, można byłoby stworzyć klasę do uwierzy-

�������

�������

�������

�������

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

Rysunek 1. Przykładowe drzewo obiektów, które kontener IoC powinien móc zbudować

Listing 1. Fragment index.php konfigurujący obiekty i uruchamiający front kontroler

<?php

$sessionFilter = new SessionFilter()); $actionResolver =

new FilePerActionResolvingStrategy($currentDir.'/controllers/'); $viewResolver = new PHPViewResolvingStrategy( $currentDir.'/views/'); $localeResolver = new NullLocaleResolvingStrategy(); $httpRequest = new HttpRequest(); $frontController =

new FrontControllerImpl( $actionResolver,$viewResolver,$localeResolver, $filterChain);

echo $frontController->doService( $httpRequest );?>

Listing 2. Fragment index.php konfigurujący obiekty i uruchamiający front kontroler, ale tym razem z wykorzystaniem IoC

<?php

$mappingBuilder = new MappingBuilderFromArray( $iocMap ); $iocContainer =

new DefaultIoCContainter( $mappingBuilder->getApplicationMap() ); $frontController = $iocContainer->create( "frontController" );

echo $frontController->doService( $iocContainer->create( "httpRequest" ) );

?>

www.phpsolmag.org56 PHP Solutions Nr 5/2006

Jak zbudować własny kontener IoC?Dla początkujących

telnienia i autoryzacji poprzez LDAP, prze-testować ją poza aplikacją, co jest z całą pewnością łatwiejsze, a następnie umie-ścić przy pomocy kontenera IoC w aplika-cji. Takie podejście zaoszczędzi nam spo-ro czasu! Dodatkowo klasa taka mogłaby z dużym prawdopodobieństwem zostać bez zmian wykorzystana w innych pro-jektach.

Wróćmy teraz do wymagań stawia-nych kontenerowi IoC, po pierwsze ta-ki kontener powinien móc powoływać do życia dowolnie złożone drzewa obiek-tów. Wyobraźmy sobie obiekt, który do swojego działania potrzebuje dwóch in-nych obiektów, które z kolei też potrze-bują zewnętrznych obiektów do popraw-nego działania. W taki sposób możemy utworzyć dowolnie skomplikowane drze-wo obiektów (Rysunek 1). Kontener IoC w takiej sytuacji powinien powołać do ży-cia obiekt będący korzeniem całej struk-tury, a następnie powołać automatycznie pozostałe obiekty i poskładać je w drze-wo. Tylko skąd kontener ma wiedzieć, ja-kie obiekty powołać i jak je poskładać? Obiekty składamy dokładnie tak, jak robi się to ręcznie: podając obiekt do konstruk-tora, settera lub przypisując go do zmien-nej publicznej. Najlepiej zilustrować to na przykładzie. Załóżmy, że obiekt A wymaga do swojego działania zewnętrznego obiek-tu B. Obiekt B możemy przekazać obiek-towi A na trzy sposoby:

• $a = new A(new B()) – poprzez kon-struktor,

• $a->setB(new B()) – poprzez setter,• $a->b = new B() – poprzez zmienną

publiczną.

Zapomnijcie zatem o bardziej archaicz-nych metodach takich jak singleton, re-jestr czy też ręczne powoływanie obiektu B wewnątrz obiektu A. Te metody tworzą silne wiązania pomiędzy sobą i w rezulta-cie zmniejszają elastyczność kodu.

Wiemy już, jak kontener powinien po-składać obiekty w drzewa. Jest kilka moż-liwości opisania takiego drzewa. Pierwsza to zewnętrzny plik konfiguracyjny, który opisuje wszystkie obiekty i drzewa obiek-tów. Plik ściśle definiuje, jakie obiekty, stałe liczbowe lub znakowe należy wsta-wić, w odpowiednie parametry konstruk-tora czy też settera. Format pliku nie ma znaczenia, powinien być jedynie czytel-ny, gdyż będzie trzeba napisać go ręcz-nie. Dlatego w naszej aplikacji parsowa-

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

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

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

��������

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

Rysunek 2. Diagram UML klas mapujących plik konfiguracyjny kontenera IoC na obiekty zrozumiałe dla samego kontenera

Listing 3. Fragment konfiguracji naszego kontenera IoC, dzięki której jesteśmy w stanie odtworzyć drzewo obiektów, które powołaliśmy do życia ręcznie na Listingu 1

<?php

$frameworkPath = "ścieżka do folderu z frameworkiem";

$iocMap = array( "sessionFilter" => array( "className" => "SessionFilter",

"file" => $frameworkPath.

"/web/mvc/controllers/filters/SessionFilter.class.php",

"singleton" => true,

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

"actionResolver" => array( "className" => "FilePerActionResolvingStrategy",

"file" => $frameworkPath.

"/web/mvc/actions/resolvers/FilePerActionResolvingStrategy.class.php",

"singleton" => true, "properties" => array(), "constructorParams" => array( $currentDir.'/controllers/' ) ),

"viewResolver" => array( "className" => "PHPViewResolvingStrategy",

"file" => $frameworkPath.

"/web/mvc/views/resolvers/PHPViewResolvingStrategy.class.php",

"singleton" => true, "properties" => array(), "constructorParams" => array( $currentDir.'/views/' ) ),

"locateResolver" => array( "className" => "NullLocaleResolvingStrategy",

"file" => $frameworkPath.

"/locale/resolvers/NullLocaleResolvingStrategy.class.php",

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

);

?>

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

Jak zbudować własny kontener IoC? Dla początkujących

nie pliku konfiguracyjnego zostanie wy-dzielone do osobnych klas tak, aby każ-dy mógł łatwo zmienić format i źródło da-nych konfiguracyjnych. W szczególno-ści można stworzyć plik konfiguracyjny w oparciu o XML i Xschema, a do budowy plików konfiguracyjnych wykorzystywać jakieś graficzne narzędzie, które dodatko-wo będzie za nas sprawdzać poprawność utworzonego pliku XML. My plik konfigura-cyjny zbudujemy w oparciu o tablice aso-cjacyjne.

Druga metoda definiowania drzewa jest rozszerzeniem pierwszej, wymaga jednak dodatkowo zastosowania w ko-dzie silnego typowania. Wyobraźmy so-bie, że obiekt A wymaga do swojego po-prawnego działania dowolnej klasy, któ-ra implementuje interfejs BI lub też po prostu klasy B. Od pewnego czasu w PHP możemy wymusić typy obiektów, dlatego konstruktor, czy też setter kla-sy A można zakodować w następują-cy sposób:

class A {

public function __construct(BI $b){}

public function setB(BI $b){}

}

a jeżeli nie chcemy interfejsu, korzystamy jedynie z klasy B:

class A {

public function __construct(B $b){}

public function setB(B $b){}

}

Druga metoda zwana jest autowiązaniem obiektów. Powołując do życia obiekt A sprawdza jego konstruktor i settery. Je-żeli znajdzie w nich deklarację obiek-tów, które ma zdefiniowane w swoim pli-ku konfiguracyjnym, automatycznie po-woła je do życia i umieści w obiekcie A. Ta metoda składania obiektów jest bar-dzo wygodna, gdyż nie wymaga tworze-nia skomplikowanych plików konfigura-cyjnych, niesie jednak ze sobą pewne niebezpieczeństwo.

Wyobraźmy sobie, że klasa A wymaga do działania dowolnej klasy implementu-jącej interfejs BI, w pliku konfiguracyjnym mamy zdefiniowane dwie klasy implemen-tujące interfejs BI, którą więc powinniśmy wybrać? Takie problemy nie będą się po-jawiać w projektach prostych, gdzie plik konfiguracyjny zawiera kilka obiektów, ale w projektach z kilkudziesięcioma obiek-

Listing 4. Implementacja klas mapujących plik konfiguracyjny do modelu obiektowego – klasa DefaultParamMap

<?php

abstract class DefaultParamMap {

private $name;

private $value;

private $type;

public function __construct($name, $value, $type) { $this->setName($name);

$this->setValue($value);

$this->setType($type); }

public function getName() { return $this->name; }

protected function setName( $name ) { $this->name = $name;

}

public function getValue() { return $this->value; }

protected function setValue( $value ) { $this->value = $value;

}

public function getType() { return $this->type; }

protected function setType( $type ) { $this->type = $type;

}

}

?>

Listing 5. Implementacja klas mapujących plik konfiguracyjny do modelu obiektowego – klasa ConstructorParamMap

<?php

require_once 'ioc/DefaultParamMap.class.php';

class ConstructorParamMap extends DefaultParamMap {

// dla parametru konstruktora nazwa parametru nie ma znaczenia

// więc zmieniamy konstruktor tej klasy

public function __construct($value, $type) { parent::__construct(null,$value,$type);

}

}

?>

tami możemy być prawie pewni, że bę-dziemy mieli z tym do czynienia. Konte-ner, który napiszemy, będzie jedynie po-siadał opcję samego składania obiektów

z definicji znajdujących się w pliku konfi-guracyjnym.

Istnieje jeszcze jedna metoda wiąza-nia obiektów bardzo podobna do autowią-

www.phpsolmag.org58 PHP Solutions Nr 5/2006

Jak zbudować własny kontener IoC?Dla początkujących

zania. Polega ona na sprawdzaniu nazwy settera i umieszczeniu przy jego pomocy odpowiedniej klasy w obiekcie. Ponownie najlepiej pokazać to na przykładzie, odwo-łajmy się więc do obiektu A i B. Załóżmy, że klasa A jest zdefiniowana w następują-cy sposób:

class A {

public setB($b){}

}

Przy takiej budowie klasy i zastosowaniu trzeciej metody składania obiektów kon-tener IoC sprawdzi, czy klasa ma jakiś setter badając, czy zawiera metody roz-poczynające się od słowa set a następ-nie sprawdzi, czy ma zarejestrowane w pliku konfiguracyjnym klasy, których na-zwa pokrywa sie z pozostałą częścią na-zwy settera, w naszym przypadku jest to klasa B. Ta metoda również niesie ze so-bą pewne niebezpieczeństwo. Musimy uważać na konwencję nazywania sette-rów, gdyż w przypadku pomyłki w nazwie możemy dostać obiekt, którego wcale nie chcieliśmy.

Mając zdefiniowane założenia do bu-dowy kontenera, zdefiniujmy teraz struktu-rę pliku konfiguracyjnego.

Konfiguracja konteneraDo konfigurowania kontenera IoC wyko-rzystamy prosty model obiektowy i ta-

Listing 6. Implementacja klas mapujących plik konfiguracyjny do modelu obiektowego – klasa ProperyParamMap

<?php

require_once

'ioc/DefaultParamMap.class.php';

class PropertyParamMap extends DefaultParamMap {

public function getSetterMethodName() {

return "set".ucfirst( $this->getName());

}

public function getGetterMethodName() {

return "get".ucfirst( $this->getName());

}

}

?>

Listing 7. Implementacja klas mapujących plik konfiguracyjny do modelu obiektowego – klasa ClassMap

<?php

require_once 'ioc/PropertyParamMap.class.php';

require_once 'ioc/ConstructorParamMap.class.php';

class ClassMap {

private $className;

private $classFile;

private $isSingleton;

private $propertiesParams = array(); private $constructorParams = array(); public function __construct( $className, $classFile, $isSingleton ) { $this->setName($className);

$this->setClassFile($classFile);

$this->isSingleton = $isSingleton;

}

public function getName() { return $this->className; }

protected function setName($className) { $this->className = $className;

}

public function getClassFile() { return $this->classFile; }

protected function setClassFile( $classFile ) { if ( !file_exists( $classFile ) ) { throw new Exception("Plik {$classFile} nie istnieje"); }

$this->classFile = $classFile;

}

public function getConstructorParams() { return $this->constructorParams; }

public function setConstructorParam( ConstructorParamMap $constructorParamMap ) {

$this->constructorParams[] = $constructorParamMap;

}

public function setProperty( PropertyParamMap $propertyMap ) { $this->propertiesParams[$propertyMap->getName()] = $propertyMap;

}

public function getProperty( $propertyName ) { if ( $this->hasProperty( $propertyName ) ) { return $this->propertiesParams[$propertyName]; } else {throw new Exception("Property {$propertyName} nie istnieje");} }

public function hasProperty( $propertyName ) { return isset( $this->propertiesParams[$propertyName] ); }

public function getProperties() { return $this->propertiesParams; }

public function isSingleton() { return (bool)$this->isSingleton; }

}

?>

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

Jak zbudować własny kontener IoC? Dla początkujących

blice asocjacyjne. Konfiguracja, którą będzie zarządzał programista, będzie znajdować się w specjalnej tablicy, która następnie będzie tłumaczona na zestaw obiektów przez specjalną klasę parsują-cą. Na tych obiektach będzie operował nasz kontener. Dzięki takiemu podej-ściu łatwo będziemy mogli zmieniać for-maty i źródła danych konfiguracyjnych, a w ekstremalnych sytuacjach będziemy mogli konfigurować kontener nie posia-dając w ogóle plików konfiguracyjnych.

Listing 1 przedstawia wycinek skryp-tu index.php. Możemy tu znaleźć dwie sekcje: jedna bardzo długa, konfiguru-jąca obiekty i druga znacznie krótsza, uruchamiająca front kontroler. Teraz przyjrzyjmy się Listingowi 2, gdzie znaj-duje się ten sam skrypt index.php, jed-nak tym razem konfigurację obiektów i ich powołanie do życia przejął na sie-bie kontener IoC. Już na pierwszy rzut oka widać, że skrypt jest dużo prost-szy. Warto dodać, że im większe będzie drzewo obiektów, które musimy utwo-rzyć, tym większy będzie zysk na czy-telności kodu.

Na Listingu 3 znajduje się konfigura-cja naszego kontenera IoC, dzięki któ-rej jesteśmy w stanie odtworzyć drzewo obiektów, które powołaliśmy do życia ręcznie na Listingu 1. I w tym momen-cie wielu z Was zapewne złapie się za głowę i powie – zaraz, ale przecież kon-figuracja zajmuje więcej miejsca niż Li-stingu 1, tak więc co właściwie zyskuje-my, skoro ilość kodu, którą musimy na-pisać dla kontenera jest w sumie więk-sza, niż jakby go nie było. Na szczęście w ogólnym rozrachunku nie jest to praw-da. Po pierwsze konfigurację piszemy raz, a wykorzystujemy wiele razy – w ramach jednej lub kolejnych aplikacji pi-sanych w oparciu o te same klasy. Może być więc tak, że wykorzystamy moduły z poprzedniej aplikacji w nienaruszonej wersji, a ich modyfikacja będzie polegać jedynie na zmianie pliku konfiguracyjne-go. Dodatkowo, jeżeli do pliku konfigu-racyjnego przeniesiemy wszystkie infor-macje o budowie obiektów, automatycz-nie stworzymy świetną dokumentację, w jednym miejscu będzie zawarta pełna informacja o strukturze aplikacji.

Wróćmy teraz do pliku konfiguracyj-nego. Tylko na pierwszy rzut oka mo-że wydawać się on skomplikowany. Po pierwsze wszystkie definicje obiektów umieszczone są w jednej tablicy aso-

cjacyjnej, każdy klucz tej tablicy jedno-znacznie identyfikuje obiekt. Na posta-wie tego identyfikatora z kontenera będą pobierane obiekty. Zwróćmy uwagę, że w jednym pliku można zdefiniować wie-le konfiguracji dla jednej klasy, wystar-czy, aby były one identyfikowane przez inne klucze w tablicy. Pod identyfikato-rem obiektu znajduje się kolejna tablica asocjacyjna, która ma jasno zdefiniowa-ny zestaw kluczy:

• className – nazwa klasy,• file – położenie pliku z definicją klasy,• singleton – flaga mówiąca, czy

obiekt ma zostać utworzony tyl-ko raz i potem umieszczony w ca-che'u, czy też za każdym razem ma być tworzona kolejna instancja obiektu,

• properties – tablica asocjacyjna, w której kluczem jest nazwa atrybutu obiektu, a wartością zmienna, któ-rą należy podstawić pod ten atry-but, w przypadku stałych znakowych lub liczbowych wpisujemy tu ich wartość, w przypadku referencji do obiektów wpisujemy znak & i identy-fikator obiektu zdefiniowany w konte-nerze IoC,

• constructorParams – tablica z pa-rametrami przeznaczonymi dla kon-struktora obiektu, tym razem nie jest to tablica asocjacyjna, znaczenie ma w niej kolejność umieszczanych war-tości, będą one w tej samej kolejności umieszczone w konstruktorze pod-czas powoływania obiektu do życia, zasady wpisywania parametrów są takie same jak powyżej.

Listing 8. Implementacja klas mapujących plik konfiguracyjny do modelu obiektowego – klasa ApplicationMap

<?php

require_once 'ioc/ClassMap.class.php';

class ApplicationMap {

private $classes = array();

public function __construct() { }

public function setClass($objKey, ClassMap $class) { $this->classes[$objKey] = $class;

}

public function getClass($objKey) { if ( $this->hasClass( $objKey ) ) { return $this->classes[$objKey]; } else { throw new Exception("Klasa {$objKey} nie istnieje"); }

}

public function hasClass($objKey) { return isset($this->classes[$objKey]); }

}

?>

Listing 9. Każda implementacja kontenera musi implementować interfejs IoCContainer

<?php

interface IoCContainer {

public function create($className);}

?>

www.phpsolmag.org60 PHP Solutions Nr 5/2006

Jak zbudować własny kontener IoC?Dla początkujących

Na Rysunku 2 widać diagram UML, a na Listingach 4–8 implementacje klas ma-pujących plik konfiguracyjny do modelu obiektowego.

Każda z klas przedstawionych na dia-gramie mapuje jeden do jednego któ-rąś z danych konfiguracyjnych. Klasa ProperyParamMap odpowiada za mapowa-nie danych wstawianych do obiektów przy pomocy setterów, ConstructorParamMap spełnia to samo zadanie w przypad-ku konstruktora obiektu. Natomiast kla-sy ClassMap i ApplicationMap są w dużej mierze agregatorami właściwych danych konfiguracyjnych. Pominiemy prezentację kodu transformującego tablicę na obiekty. Kod ten podobnie jak i pozostałe elementy kontenera będzie można pobrać ze strony http://flexi.sf.net.

Kontener IoCPrzejdźmy teraz do implementacji sa-mego kontenera IoC. Po pierwsze każ-da implementacja kontenera musi im-plementować interfejs IoCContainer. In-terfejs ten ma tylko jedną metodę pu-bliczną create() (Listing 9). Pełna imple-mentacja kontenera w podstawowej for-mie pokazana jest na Listingu 10. Kla-sa DefaultIoCContainer implementu-je interfejs IoCContainer. Do swojego poprawnego działania wymaga klasy ApplicationMap, która zawiera konfigu-rację kontenera. Klasa ta posiada tylko jedną metodę publiczną create(). Meto-da na podstawie podanego identyfikato-ra obiektu zwraca instancję tego obiektu wraz z drzewem podrzędnych mu obiek-tów, jeżeli takowe zostały zdefiniowane w pliku konfiguracyjnym.

Metoda create pobiera konfigura-cję dla danego identyfikatora, następ-nie sprawdza, czy obiekt jest typu sin-gleton (tzn. że podczas życia aplikacji może zostać utworzona tylko jedna in-stancja danego obiektu). Jeżeli tak, to sprawdzamy, czy nie został już wcze-śniej utworzony. Jeżeli tak to zwraca-my go, jeżeli nie to tworzymy go przy pomocy metody createObject. Metoda, po pierwsze, ładuje plik z definicją kla-sy, którą ma powołać do życia – ścież-ka do pliku zdefiniowana jest w konfi-guracji.

Po drugie, sprawdza, czy obiekt przy tworzeniu wymaga podania parametrów do konstruktora. Jeżeli tak, to tworzony jest obiekt przy pomocy funkcji eval, je-żeli nie, to tradycyjnie przy pomocy ope-

Listing 10a. Pełna implementacja kontenera w podstawowej formie

<?php

require_once 'ioc/IoCContainer.interface.php';

class DefaultIoCContainter implements IoCContainer {

private $applicationMap;

private $objectCache = array();

public function __construct( ApplicationMap $applicationMap ) { $this->setApplicationMap( $applicationMap );

}

protected function setApplicationMap(ApplicationMap $value) { $this->applicationMap = $value;

}

protected function getApplicationMap() { return $this->applicationMap; }

public function create($className) { $classMap = $this->getApplicationMap()->getClass($className);

if ( $classMap->isSingleton() ) { if ( $this->inCache( $className ) ) { return $this->getFromCache( $className ); } else { $classObj = $this->createObject($classMap);

$this->putInCache($className,$classObj);

return $classObj; }

} else { return $this->createObject($classMap); }

}

protected function createObject(ClassMap $classMap) { $className = $classMap->getName();

if ( !class_exists( $className ) ) { require_once($classMap->getClassFile());

}

$paramsArr = $classMap->getConstructorParams();

if( !empty( $paramsArr ) ) { $params = array(); $evalArr = array(); $i = 0;

foreach ($paramsArr as $constructorParam) { $params[] = $this->getConstructorParamsRecursively($constructorPar

am);

$evalArr[] = '$params['.$i++.']';

}

$evalStr = '$classObj = new '.$className.'('.implode(",",$evalArr).');';

eval($evalStr);

} else { $classObj = new $className(); }

$this->setProperties($classMap, $classObj);

return $classObj; }

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

Jak zbudować własny kontener IoC? Dla początkujących

ratora new. Jeżeli korzystacie z wersji PHP 5.1.3 lub nowszej możecie two-rzyć obiekty z parametrami do konstruk-tora przy pomocy Reflection API i meto-dy newInstanceArgs.

Po utworzeniu obiektu jego in-stancja przekazywana jest do metody setProperties, gdzie do obiektu przy pomocy setterów dodawane są zmien-ne, które w pliku konfiguracyjnym zo-stały zdefiniowane w sekcji properties.

Informacja o tym, jakiego settera użyć, nie jest zapisana w konfiguracji, konte-ner tworzy ją sam. Poprawna nazwa settera składa się z przedrostka set i nazwy propertisa ze zmienioną pierw-sza literą na wielką.

Ta konwencja nazywania metod dostępowych do atrybutów obiektów nie powinna być Wam obca, jest zgod-na z konwencją stosowaną w innych aplikacjach.

Warto jeszcze zwrócić uwagę na dwie metody getConstructorParamsR

ecursively i getPropertyValueRecursively. Obie są bardzo podobne. Na podstawie typu parametru, który mo-że przyjąć dwie wartości value lub reference, kontener pobiera z kon-figuracji stałą lub ponownie urucha-mia swoją metodę create, aby utwo-rzyć obiekt. Dzięki temu kawałkowi ko-du kontener może zwracać całe drze-wa obiektów.

O tym, czy parametr jest typu va-lue, czy reference, decyduje obecność & przy definicji wartości parametru w pliku konfiguracyjnym.

PodsumowanieKontener IoC, którego uczyliśmy się tutaj razem zbudować, jest od jakie-goś czasu wykorzystywany w codzien-nej pracy w mojej firmie. Z doświadcze-nia wynika, że jego wdrożenie ułatwiło nam pisanie aplikacji.

Najwięcej zyskaliśmy w kwestii te-stów i przejrzystości kodu. Dodatkowo spora część klasy po przystosowaniu do specyfiki kontenera IoC – dostarcza-my obiekty zewnętrzne przez konstruk-tor i settery – stała się na tyle elastycz-na, że z powodzeniem bez przepisywa-nia zaczęliśmy wykorzystywać je w kolej-nych projektach.

Udało się nam też usunąć z podsta-wowego kodu aspekty związanie z auto-ryzacją, logowaniem i walidacją. Jak to zrobiliśmy? O tym przeczytacie w kolej-nych artykułach w magazynie PHP So-lutions. n

Listing 10b. Pełna implementacja kontenera w podstawowej formie – ciąg dalszy

protected function getConstructorParamsRecursively(ConstructorParamMap $constructorParamMap) {

switch ( $constructorParamMap->getType() ) { case 'value' : $param = $constructorParamMap->getValue(); break; case 'reference' : $param = $this->create( $constructorParamMap->getValue() ); break; default : throw new Exception( "Niewłaściwy typ parametru {$constructorParamMap->getType()}" );

}

return $param; }

protected function setProperties(ClassMap $classMap, $object) { $properties = $classMap->getProperties();

foreach($properties as $propertyParamMap) { $propertySetterName = $propertyParamMap->getSetterMethodName();

$object->{$propertySetterName}

( $this->getPropertyValueRecursively($propertyParamMap) );

}

}

protected function getPropertyValueRecursively( PropertyParamMap $propertyParamMap ) {

switch ( $propertyParamMap->getType() ) { case 'value' : $param = $propertyParamMap->getValue(); break; case 'reference' : $param = $ this->create( $propertyParamMap->getValue() ); break; default : throw new Exception( "Niewłaściwy typ atrybutu {$propertyParamMap->getType()}" );

}

return $param; }

protected function putInCache($key,$value) { $this->objectCache[$key] = $value;

}

protected function getFromCache($key) { return $this->objectCache[$key]; }

protected function inCache($key) { return isset($this->objectCache[$key]); }

}

?>

Piotr Szarwas jest pracownikiemSUPER-MEDIA Interactive i dokto-rantem na wydziale Fizyki Politech-niki Warszawskiej. Od 2003 roku projektuje aplikacje WWW w opar-ciu o PHP4/5. Obecnie zajmuje się tworzeniem frameworka dla PHP opartego na rozwiązaniach Hiberna-te i Spring.Kontakt z autorem:[email protected]

O autorze

www.phpsolmag.org62 PHP Solutions Nr 5/2006

Dla zaawansowanych

www.phpsolmag.org 63

PEAR

PHP Solutions Nr 5/2006

Dla zaawansowanych

XML i PHP w praktyceGuillaume Ponçon

Standard XML, czyli eXtensible Markup Language został określo-ny przez World Wide Web Con-

sortium (W3C). Obecnie jest przeważnie wykorzystywany jako format gromadze-nia i wymiany danych (np. SXW, ODT, RDF, RSS) oraz tworzenia specjalistycz-nych języków opartych na znacznikach – jest więc metajęzykiem. Każdy doku-ment XML zawiera dane uporządkowa-ne w hierarchii drzewiastej. Spójrzmy na Listing 1: przedstawiamy na nim przykła-dowy kod XML. Jego struktura opiera się na znacznikach zawartych w nawiasach trójkątnych (podobnie jak w przypadku HTML-a). Para znaczników, np. <title> i </title> określa element dokumentu XML-owego, który może zawierać tekst lub kolejne elementy (zwane podrzędny-mi lub potomnymi). Składnia każdego do-kumentu XML musi być ściśle przestrze-gana (nie wolno np. pozostawiać niedo-mkniętych znaczników; można też okre-ślić inne zasady korzystając z plików

Bazy danych, dokumenty biurowe, RSS: coraz więcej formatów gromadzenia i przesyłania danych opiera się na XML-u. Jego główną zaletą jest łatwość tworzenia i przetwarzania dokumentów XML niezależnie od platformy sprzętowej i systemowej. Jako programiści PHP, mamy szerokie możliwości wykorzystania XML-a przy użyciu co najmniej kilku technik...

DTD czy XML Schema), gdyż w innym wypadku program odczytujący ten doku-ment zgłosi błąd.

Techniki przetwarzania XML-a w PHPPisząc skrypty w języku PHP możemy swobodnie korzystać z dokumentów XML-owych. Do ich przetwarzania służą roz-szerzenia języka PHP o nazwach: SAX,

W SIECI

• http://www.w3.org/XML/ – oficjalna specyfikacja stan-dardu XML

• http://www.saxproject.org/ – strona projektu SAX

• http://www.w3.org/DOM/ – oficjalna specyfikacja stan-dardu DOM

• http://books.evc-cit.info/odbook/book.html – oficjalna specyfikacja formatu Open-Document

Stopień trudności: lll

Co należy wiedzieć...Przydatna będzie znajomość podstaw programowania obiektowego w PHP5.

Co obiecujemy...Przedstawimy zasady działania roz-szerzeń języka PHP: DOM, SAX oraz SimpleXML oraz pokażemy, jak korzy-stając z SimpleXML tworzyć i odczyty-wać dokumenty XML-owe, w tym pliki OpenOffice.org i dane programu Gant-tproject.

www.phpsolmag.org62 PHP Solutions Nr 5/2006

Dla zaawansowanych

www.phpsolmag.org 63

PEAR

PHP Solutions Nr 5/2006

Dla zaawansowanych

DOM-XML (PHP4), DOM (PHP5) oraz SimpleXML (tylko PHP5). Omówimy po-krótce SAX, DOM-XML oraz SimpleXML, podając przykłady wykorzystania każdego z tych rozszerzeń.

SAXSAX (http://www.saxproject.org/, http://www.php.net/xml).oznacza Simple API for XML i umożliwia sekwencyjny odczyt do-kumentów XML. Pozwala na odczyt prak-tycznie każdego rodzaju dokumentów XML, nawet tych, które zostały utworzo-ne niepoprawnie. SAX nie umożliwia nato-miast zapisu ani modyfikacji danych.

Na Listingu 2 przedstawiamy skrypt służący do odczytu dokumentu XML-owe-go z użyciem techniki SAX. Zaczynamy od utworzenia parsera ($xml_parser), który jest zasobem (ang. resource) tworzonym przy użyciu funkcji xml_parser_create() i reprezentującym dokument XML, który bę-dziemy przetwarzali. Do tego ostatniego w technice SAX służą funkcje zwane handle-rami (uchwytami), które są wywoływane przez język PHP podczas odczytu nasze-go dokumentu. Musimy je sami utworzyć, zanim się do nich odwołamy. W naszym przykładzie zdefiniujemy najpierw dwa podstawowe uchwyty: początku (startE-lement()) i końca (endElement()) każdego elementu XML-owego, uruchamiane od-powiednio, gdy parser natrafi na znacznik rozpoczynający (np. <author>) i kończący (np. </author>) dany element. W przypad-ku napotkania elementu danych pomiędzy dwoma znacznikami XML wywoływany jest uchwyt dataHandler. Handlery począt-ku i końca elementu definiujemy przy po-mocy funkcji xml_set_element_handler(), a uchwyt elementu danych używając xml_set_character_data_handler(). Przetwa-rzanie dokumentu XML rozpoczniemy ko-rzystając z funkcji xml_parse(), jako jej parametry podając: instancję parsera (za-sób $xml_parser), źródło dokumentu w wersji tekstowej (np. odczytane z pliku; my użyjemy zmiennej $xml, w której umieści-my fragment kodu z Listingu 1. Na koniec, zwalniamy zasób $xml_parser korzystając z funkcji xml_parser_free().

DOM-XMLRozszerzenie DOM-XML umożliwia prze-twarzanie plików XML w PHP zgodnie ze standardem DOM (skrót od Document Object Model). DOM-XML pozwala za-

Rysunek 1. Okno programu Ganttproject: po lewej stronie wykres Gantta

InstalacjaAby można było uruchomić przykłady z tego artykułu, zalecane jest posiadanie PHP 5.1.4 lub nowszego. Pod systemem Windows możemy w tym celu skorzystać z ze-stawu typu AMP (Apache, MySQL, PHP) czy też 3 w 1 o nazwie WampServer (http://www.wampserver.com). Jego instalacja jest trywialna i odbywa się przy użyciu pro-stego wizarda.

Listing 1. Dokument XML Simple (opis książki)

<document id="12">

<author>Guillaume Ponçon</author>

<title>Best practices PHP 5</title>

<chapter>

<title>Strumienie XML</title>

<paragraph type="introduction">...</paragraph>

</chapter>

</document>

Listing 2. Przeglądamy dokument XML-owy przy użyciu SAX (wyświetlanie znaczników i zawartości)

function startElement($parser, $name, $attrs) { echo $name . "\n"; }function endElement($parser, $name) { echo $name . "\n"; }function dataHandler($parser, $data) { echo '-> ' . trim($data) . "\n"; }

$xml_parser = xml_parser_create();

$xml='<document id="12"><author>Guillaume Ponçon</author></document>';

xml_set_element_handler($xml_parser, "startElement", "endElement");

xml_set_character_data_handler($xml_parser, 'dataHandler');

xml_parse($xml_parser, $xml, true);

xml_parser_free($xml_parser);

Listing 3. Przykład odczytu dokumentu XML przy użyciu DOMXML (wyświetlanie informacji zawartych na Listingu 1)

$dom = new DOMDocument();$dom->LoadXML($xml);

$title = $dom->getElementsByTagName('title');

echo "Rozdziały książki " . $title->item(0)->nodeValue . " : \n";

foreach ($dom->getElementsByTagName('chapter') as $element) { $titles = $element->getElementsByTagName('title');

echo "\n- " . $titles->item(0)->nodeValue . "\n"; $paragraphs = $element->getElementsByTagName('paragraph');

foreach ($paragraphs as $paragraph) { echo ' * ' . $paragraph->nodeValue . "\n"; }

}

www.phpsolmag.org64 PHP Solutions Nr 5/2006

Dla zaawansowanych

równo na odczyt, jak i zapis (tworzenie i modyfikowanie) dokumentów XML. Stan-dard DOM znajduje zastosowanie nie tyl-ko w PHP, ale także w innych językach (m.in. Python, Java, JavaScript). Głów-ną ideą DOM jest to, że struktura XML jest traktowana jako hierarchiczne drze-wo węzłów (ang. nodes), z których każ-dy jest reprezentowany jako obiekt języka PHP (lub innego języka, dla którego istnie-je implementacja DOM). W PHP obsługę standardu DOM umożliwiają rozszerzenia DOM-XML (PHP4, http://www.php.net/domxml) i DOM (PHP5). Jak już powie-dzieliśmy, skorzystamy z tego pierwszego.

Na Listingu 3 przedstawiamy przy-kład odczytu, a na Listingu 4 przykład zapisu dokumentu XML za pomocą DOM. W obu przypadkach zaczynamy tworząc obiekt $dom klasy DOMDocument (musimy go utworzyć), do którego bę-dziemy się odwoływać przy wykonywa-niu operacji na dokumencie. Następnie ładujemy kod XML w wersji tekstowej (ze zmiennej łańcuchowej $xml) korzysta-jąc z metody LoadXML obiektu $dom. Aby odczytać lub zmodyfikować dany wę-

zeł, musimy go najpierw odnaleźć meto-dą getElementsByTagName(), tworząc no-wy obiekt (najlepiej o nazwie odpowiada-jącej nazwie węzła). Następnie używamy atrybutów i metod tego obiektu, umożli-wiających odczytywanie i zapisywanie danych w zaznaczonym węźle. My wy-korzystamy omówioną metodę do zna-lezienia węzła title (element pomię-dzy znacznikami <title> i </title>; pa-miętajmy, że posługujemy się cały czas przykładowym kodem XML z Listingu 1).

Należy wiedzieć, że omówiona meto-da getElementsByTagName() odnajdu-je wszystkie węzły określone daną parą znaczników (np. <title>...</title>) i spo-rządza ich listę, więc nawet, gdy istnieje tylko jeden węzeł o wybranej nazwie (jak u nas), musimy go wskazać (np. używa-jąc metody item(), choć istnieją też inne sposoby). Analogicznie wyświetlimy listę rozdziałów (element chapter) i akapitów (paragraph). Tekst zapisany w danym węźle odczytamy i zmodyfikujemy korzy-stając z atrybutu nodeValue.

Utworznie nowego węzła nastę-puje poprzez użycie aż dwóch metod: createElement(), która pozwala na utwo-rzenie obiektu węzła o określonej nazwie (u nas category) i zawartości (u nas PHP), który go reprezentuje oraz appendChild(), która z kolei pozwala dodać ten element w odpowiednim miejscu drzewa węzłów. W naszym przypadku, element ten będzie podrzędny wobec pierwszego odnalezio-nego elementu drzewa (title), niższego o jeden poziom od korzenia drzewa (u nas document). Wreszcie, do zapisu gotowe-go dokumentu XML w pliku służy metoda save() obiektu $dom.

Jak widać, rozwiązanie DOM jest sku-teczne i spójne logicznie, ale niestety bar-dzo rozwlekłe i wymagające dużo kodu. Dlatego też jest relatywnie rzadko sto-sowane, choć zawiera bardzo pożytecz-ne narzędzia, takie jak np. XSLT (połą-czenie dokumentu XML i arkusza stylów XSL) czy XPath (język zapytań umożli-wiający wyszukiwanie elementów i warto-ści w dokumentach XML, będący niejako XML-owym odpowiednikiem SQL-a).

SimpleXMLSimpleXML (http://www.php.net/simplexml)to rozszerzenie, które pojawiło się wraz z PHP5. Jest bardzo łatwe w obsłudze i

Rysunek 2. Skrypt odczytujący dokument utworzony przez program Ganttproject i wy-świetlający jego strukturę

RSS – podstawyRSS (Really Simple Syndication, znane dawniej jako Rich Site Summary czy RDF Site Summary) to popularny i prosty format strumienia przesyłania newsów (prze-ważnie krótkich) w Internecie. Zestawy newsów są dostępne w postaci plików okre-ślanych jako kanały (ang. channels), strumienie lub feeds. Każdy news zawiera na ogół tytuł, krótki opis, rozwinięcie i link do dłuższego artykułu. RSS opiera się na standardzie XML. Sajty udostępniające wiadomości w postaci RSS (głównie gazety, magazyny i portale internetowe, takiej jak Yahoo!, Slashdot (http://rss.slashdot.org/Slashdot/slashdot) czy freshmeat.net (http://rss.freshmeat.net/freshmeat/feeds/fm-re-leases-global)) są zwykle oznaczone ikonką zawierającą skrót RSS, RDF lub XML na stronie głównej.

Newsy RSS mogą być następnie pobierane i wyświetlane przez czytnik umiesz-czony na komputerze klienta lub na dowolnej witrynie internetowej, takiej jak np. pry-watna strona domowa czy blog, a także niektóre programy pocztowe (np. Thunder-bird). Wyświetlanie zawartości kanałów RSS jest możliwe również przy użyciu prze-glądarki internetowej: po wpisaniu adresu RSS powinna się w niej pojawić pełna struktura i zawartość pliku XML z newsami.

Listing 4. Modyfikujemy dokument XML-owy korzystając z DOM-XML (dodanie węzła category w dokumencie XML z Listingu 1)

$dom = new DOMDocument();$dom->LoadXML($xml);

$title = $dom->getElementsByTagName('title');

$title->item(0)->nodeValue = "Best practices in PHP5";

$element = $dom->createElement('category', 'PHP');

$rootElement = $dom->getElementsByTagName('document');

$rootElement->item(0)->appendchild($element);

$dom->save('/tmp/document.xml');

echo file_get_contents('/tmp/document.xml');

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

Dla zaawansowanych

umożliwia zarówno odczyt (w PHP 5.0), jak i zapis (w PHP 5.1.4) dokumentów XML. Jest domyślnie skompilowane w każdej z tych dystrybucji PHP.

Na Listingu 5 pokazujemy, jak od-czytywać zawartość dokumentu XML, a na Listingu 6, jak ją modyfikować. W obu przypadkach korzystanie z Simple-XML zaczynamy od utworzenia obiek-tu $simpleXml, do czego służy funkcja simplexml_load_string(), która tworzy dokument w oparciu o kod XML w po-staci tekstowej (zawarty w zmiennej łań-cuchowej). Podobnie, jak w przypadku DOM, w SimpleXML struktura dokumen-tu XML jest reprezentowana przez ze-staw obiektów języka PHP, tyle, że są one tworzone automatycznie i mają na-zwy odpowiadające nazwom węzłów (elementów) naszego dokumentu, a ko-rzystanie z nich jest znacznie prostsze, co możemy sami zobaczyć porównując długość kodu w obu przypadkach: pod-czas, gdy w DOM trzeba było tworzyć rozwlekły skrypt, w SimpleXML wystar-czy parę linijek! Co więcej, z elementów dokumentu XML korzystamy przeważnie (zarówno do odczytu, jak i zapisu) uży-wając atrybutów (pól) obiektów poszcze-gólnych węzłów. Pola te mogą zawierać zarówno treść tekstową, jak i listy ele-mentów podrzędnych, które możemy ite-rować korzystając z instrukcji foreach. Znacznie prostsze niż w DOM jest rów-nież dodawanie nowych węzłów: musimy

jedynie skorzystać z metody addChild() i gotowe.

Odczyt strumienia RSS za pomocą SimpleXMLRSS to standard przesyłania wiadomo-ści w Internecie, o którym więcej mówi-my w Ramce RSS – podstawy. Na Li-stingu 7 przedstawiamy przykładowy

plik RSS, a na Listingu 8 skrypt odczy-tujący strumień RSS spod adresu http://rss.freshmeat.net/freshmeat/feeds/fm-releases-global, czyli informacje o no-wych projektach programistycznych, któ-re zostały zamieszczone na witrynie fre-shmeat.net. Ładowanie zestawu wiado-mości następuje poprzez zwykłe załado-wanie pliku znajdującego się pod wspo-mnianym adresem, po czym przystępu-jemy do ich wyświetlania w oknie prze-glądarki: zaczynamy od wypisania tytułu kanału (element <title>, podrzędny wo-bec <channel>). Następnie iterujemy wia-domości, z których każda jest elemen-tem podrzędnym wobec <channel> ozna-czonym jako <item> i wyświetlamy ich elementy potomne: <description> oraz <date>. Korzeń (root) dokumentu XML jest oznaczony jako <rss> (czasem też jako <RDF>) i jest reprezentowany przez sam obiekt $rss.

Po uruchomieniu tego skryptu zoba-czymy zestaw wiadomości – zauważmy, że niektóre z nich zawierają również gra-fikę.

Manipulujemy wykresami Gantta za pomocą SimpleXML oraz GanttprojectWykres Gantta (ang. Gantt chart) to po-pularna metoda wizualizacji etapów i po-

Czym jest Ganttproject?Ganttproject (Rysunek 1) jest napisanym w Javie opensourcowym programem do sporządzania wykresów Gantta. Możemy go pobrać spod adresu http://ganttproject.sourceforge.net. Zgodnie ze specyfiką wykresów Gantta, Ganttproject pozwa-la na wizualizację organizacji zadań i zasobów przydzielonych do wybranego pro-jektu w czasie. Jedną z podstawowych funkcji programu Ganttproject jest tworze-nie zadań, które możemy układać w kategorie. Narzędzie to pozwala nam również na śledzenie postępu zadań, sporządzanie listy zasobów (osób pracujących nad projektem), wyświetlanie wykresu strat oraz wykonywanie wielu innych użytecz-nych operacji.

Rysunek 3. Przykład pliku tekstowego stworzonego w edytorze OpenOffice.org Writer

Listing 5. Odczyt dokumentu przy wykorzystaniu SimpleXML (wyświetlanie infor-macji zawartych na Listingu 1)

$simpleXml = simplexml_load_string($xml);

echo '+ '.$simpleXml->title ."\n";echo ' by '.$simpleXml->author."\n\n";

foreach($simpleXml->chapter as $chapter){ echo '- ' . $chapter->title . "\n";

foreach($chapter->paragraph as $paragraph){ echo ' -> ' . $paragraph . "\n"; }

echo "\n";}

Listing 6. Przykład modyfikacji dokumentu XML-oweog za pomocą SimpleXML (dodanie rozdziału do dokumentu XML z Listingu 1)

$simpleXml = simplexml_load_string($xml);

$simpleXml->title = "Dobre wprawki w PHP 5";

$newChapter = $simpleXml->addChild('chapter');

$newChapter->addChild('title', 'Programowanie obiektowe');

$newChapter->addChild('paragraph', 'Programowanie zorientowane obiektowo jest

wspaniałe ...');

echo $simpleXml->asXml();

www.phpsolmag.org66 PHP Solutions Nr 5/2006

Dla zaawansowanych

stępów wykonania projektu, często sto-sowana w firmach i innych organiza-cjach. Istnieje wiele narzędzi służących do sporządzania takich wykresów; jed-nym z nich jest opensourcowy program Ganttproject, o którym mówimy więcej w Ramce Czym jest Ganttproject?. Po-nieważ dane programu Ganttproject są gromadzone w postaci plików XML, więc korzystanie z nich w skryptach PHP jest całkiem proste.

Każdemu wykresowi przypisany jest jeden plik XML, w którym zbierane są dotyczące go dane, takie jak np. ko-lor przypisany do każdego zadania. Ko-rzeniem (rootem) tego pliku jest element o nazwie <project>. Jego atrybuty za-wierają różne informacje, takie jak da-ta ostatniego odczytu czy wersja pliku. Podrzędne wobec <project> elementy pierwszego poziomu stanowią kategorie informacyjne: kalendarze, zadania, za-soby, przypisanie zasobów, zwolnienia, role i różne kategorie ustawień. Elemen-ty drugiego i następnych poziomów za-wierają informacje związane z każdą ka-tegorią.

Odczytujemy wykres Gantta z programu GanttprojectAby odczytać dokument programu Gant-tproject, napiszemy niewielki skrypt działający w linii poleceń i korzystają-cy z SimpleXML (Listing 10). Odczyta on wszystkie zadania zawarte w pliku, którego nazwę podajemy jako parametr w linii poleceń i wyświetli informacje o

każdym z nich: datę początkową, czas trwania i tytuł. Potrzebujemy na to około dziesięciu linii kodu. Jak widzimy, nasz algorytm składa się z funkcji rekurencyj-nej, dzięki której możemy przetwarzać

kolejne zadania w hierarchii zadań. Na Rysunku 2 przedstawiamy efekt działa-nia naszego skryptu: wylistowaną w linii poleceń hierarchię zadań wraz ze wspo-mnianymi informacjami (datą początko-wą, czasem trwania i tytułem).

Modyfikujemy zadanie utworzone w GanttprojectModyfikacja istniejącego zadania pliku da-nych Ganttproject z użyciem SimpleXML jest równie prosta: jak widzimy na Listingu 11, potrzebujemy do tego zaledwie trzech linijek kodu! W naszym przykładzie zmie-nimy nazwę (parametr name) pierwszego zadania na wykresie Gantta.

Dodajemy zadanieAby dodać zadanie do wykresu Gantta, musimy umieścić nowy element <task> w pliku XML (Listing 12). W przypadku Sim-pleXML służy do tego metoda addChild(), o której już mówiliśmy. Zaczniemy od za-ładowania pliku XML (openoffice.xml). Aby dodać nowe zadanie, będziemy mu-sieli nadać mu identyfikator będący nu-merem większym o jeden od ID ostatnio

Czym jest przestrzeń nazw?Przestrzeń nazw umożliwia podzielenie dużych plików XML na kategorie, co uła-twia zorientowanie się w jego zawartości i manipulowanie nią. Z użyciem przestrzeni nazw mamy do czynienia, gdy znacznik w pliku XML-owym składa się z dwóch czę-ści oddzielonych dwukropkiem (:), np. <text:p>...</text:p>. Identyfikatorem prze-strzeni nazw jest słowo kluczowe znajdujące się przed dwukropkiem. Przykładowo, każdy znacznik zawarty w pliku Open Office content.xml o nazwie text:p (więc nale-żący do przestrzeni nazw text), odpowiada jednemu akapitowi tekstu.

Rysunek 4. Zawartość pliku tekstowego model.odt

Listing 7. Struktura strumienia RSS

<rss>

<channel>

<title>

Tytuł strumienia wiadomości

</title>

(...)

<item>

<title>

Tytuł pierwszej wiadomości

</title>

<description>

Opis pierwszej wiadomości

</description>

(...)

</item>

<item>

(...)

</item>

(...)

</channel>

</rss>

Listing 8. Odczyt strumienia wiadomości RSS przy użyciu SimpleXML

$rss = simplexml_load_file('http://rss.freshmeat.net/freshmeat/feeds/

fm-releases-global');

echo '<h1>'.$rss->channel->title.'</h1>';

foreach ($rss->channel->item as $item) {

echo '<h3>' . $item->date . ' : ' . $item->title . '</h3>';

echo '<p>' . $item->description . '</p>';

}

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

Dla zaawansowanych

zdefiniowanego zadania. Ponieważ kolej-ny identyfikator jest zawsze o 1 większy od poprzedniego, wystarczy znaleźć naj-większe ID. W tym celu sporządzimy naj-pierw listę wszystkich zadań korzystając z xpath, a następnie odnajdziemy najwięk-szy identyfikator używając funkcji max() w pętli foreach. Po wykonaniu tych czyn-ności musimy wydłużyć czas trwania za-dania macierzystego, którym dla naszego

nowego zadania (które umieścimy na tym samym poziomie, co tests) będzie Base. Teraz dodamy nowe zadanie przy uży-ciu metody addChild(), która jest dostęp-na dla każdego węzła drzewa dokumentu w SimpleXML oraz nadamy mu atrybuty: indentyfikator, nazwę, datę rozpoczęcia, okres trwania i parametry wyświetlania (m.in. kolor). Na koniec zapiszemy nasze modyfikacje w pliku korzystając z funk-

cji wbudowanej PHP file_put_contents() oraz metody asXml() obiektu $project, który reprezentuje nasz dokument XML-owy. Tak, jak w poprzednich przykładach, dzięki zastosowaniu SimpleXML ograni-czyliśmy zestaw czynności do niezbęd-nych, unikając pisania dodatkowego ko-du, które miałoby miejsce np. w przypad-ku DOM.

Korzystamy z dokumentów OpenOffice.org za pomocą SAXOpenOffice.org to opensourcowy pa-kiet biurowy o ciągle rosnącej popular-ności. Korzystają z niego m.in. firmy, in-stytucje publiczne, organizacje pozarzą-dowe i osoby prywatne z całego świata. Wszystkie formaty dokumentów OpenOf-fice.org (edytora, arkusza kalkulacyjne-go, programu do tworzenia prezentacji i innych są oparte na XML-u. W wersji 2 tego pakietu wprowadzono format Open-Document, którym się posłużymy w na-szym artykule.

Kompozycja dokumentu OpenOffice.orgKażdy dokument OpenOffice.org to w rze-czywistości skompresowane archiwum za-wierające pliki XML. Przykładowo, archi-wum pliku example.odt utworzonego w OO Writer (edytorze tekstu) będzie za-wierało tekst oraz style i obrazy osadzo-ne w tekście.

Głównym plikiem tego archiwum, le-żącym w jego katalogu głównym jest plik content.xml, którego przykładową zawar-tość (fragment) prezentujemy na Listingu 13. Zawiera on dane naszego dokumen-tu i kilka deklaracji stylów. Zdefiniowane w dokumencie style tekstowe są zapisane w pliku styles.xml.

My pokażemy sposób tworzenia tytu-łu i akapitu.

Odczyt i modyfikacja dokumentu OpenOffice.orgZanim będziemy mogli odczytać lub zmo-dyfikować dokument OpenOffice (test.odt), musimy wydobyć z archiwum plik con-tent.xml (Rysunek 3). W tym celu użyjemy biblioteki pclzip , którą pobierzemy spod adresu http://www.phpconcept.net/pclzip/.

Plik content.xml, którego fragment już przedstawiliśmy na Listingu 13 składa się ze znacznika korzenia office:document oraz pierwszego poziomu hierarchii XML

Rysunek 5. Dokument tekstowy OpenOffice.org Writer generowany przez skrypt napi-sany w PHP

Listing 9. Zawartość dokumentu XML programu Ganttproject

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

<project name="OpenOfficeManager"

company="Anaska" webLink="http://www.anaska.com" (...)>

<description/>

<view zooming-state="default:3"/>

<calendars>

<!-- właściwości ogólne kalendarza (dni wolne, etc.) -->

</calendars>

<tasks color="#8cb6ce">

<taskproperties>

<!-- deklaracja typów zadań -->

</taskproperties>

<task id="0" name="kernel" (...) >

<task id="1" name="modelisation" (...)>

<depend id="2" type="2" difference="0" hardness="Strong"/>

</task>

<task id="2" name="development" (...)>

<depend id="3" type="2" difference="0" hardness="Strong"/>

</task>

<task id="3" (...)/>

</task>

</tasks>

<resources/>

<allocations/>

<vacations/>

<taskdisplaycolumns>

(...)

</taskdisplaycolumns>

<previous/>

<roles roleset-name="Default"/>

</project>

www.phpsolmag.org68 PHP Solutions Nr 5/2006

Dla zaawansowanych

przedstawiającego kategorie zawartości. W kategorii office:body zawarte są dane naszego dokumentu.

Odczytamy plik content.xml przy uży-ciu SAX oraz zmodyfikujemy go korzysta-jąc z SimpleXML i dodając tytuł i akapit. Gotowy skrypt przedstawiamy na Listingu 14 – składa się on z 4 części:

• wydobycie pliku content.xml zawiera-jącego zawartość naszego dokumen-tu z archiwum,

• odczyt pliku content.xml za pomocą SAX,

• dodanie tytułu i akapitu przy użyciu SimpleXML,

• zapis pliku na dysk.

Zaczniemy od dołączenia biblioteki pcl-zip (pclzip.lib.php) i utworzenia obiek-tu $zip klasy PclZip, któremu jako pa-rametr konstruktora przekazujemy na-zwę archiwum, które chcemy rozpako-wać (test.odt). Następnie korzystając z metody listContent() tego obiektu prze-szukamy listę plików zawartych w archi-wum, aż natrafimy na content.xml. Je-go ekstrakcji dokonamy używając metody extractByIndex() obiektu $zip. Mając plik content.xml poza archiwum, skorzystamy z PHP-owej funkcji file_get_contents(), aby go załadować.

Czas na przetwarzanie odczytanego źródła XML przy pomocy SAX. Dużą za-letą tego rozwiązania jest jego szybkość i prostota, co zdążyliśmy już częściowo poznać. Tworzymy więc parser SAX przy użyciu xml_parser_create() i przy pomo-cy funkcji rozszerzenia SAX xml_parse_into_struct() przekształcimy odczyta-ną zawartość pliku XML-owego w tabli-cę. Następnie użyjemy na tej tablicy pę-tli foreach, aby wyekstrahować z niej ty-tuły i akapity.

Teraz utworzymy obiekt $xml, repre-zentujący ten dokument w SimpleXML. W tym celu skorzystamy z funkcji simplexml_load_file() i załadujemy ponownie ten sam plik (content.xml).

Następnie użyjemy metody xpath() obiektu $xml, aby wydobyć węzeł office:text, który obejmuje zawarty w pliku tekst. Węzeł ten zawiera ciąg deklaracji akapi-tów, a pobierzemy go z pierwszego ele-mentu tablicy zwróconej przez xpath() (o indeksie 0).

Dodamy teraz nowy tytuł i akapit. W tym celu musimy utworzyć dwa wę-zły text:p potomne wobec węzła office:

text korzystając ze znanej już nam meto-dy addChild(). Dla każdego węzła poda-jemy jego nazwę (text:p), zawartość (No-wy tytuł i Nowy akapit) i przestrzeń nazw (text). Po dodaniu obu węzłów dodamy akapitowi atrybut text:style-name, który określa styl (u nas Standard).

Przejdźmy teraz do zapisywania pli-ku. Tak jak poprzednio, użyjemy meto-dy asXml() aby uzyskać tekstową wer-

sję dokumentu XML, którą zapiszemy w pliku content.xml przy użyciu funkcji file_put_contents().

Pozostała nam już tylko czwarta część: usunięcie starego pliku content.xml z archiwum test.odt przy pomocy pclzip i zastąpienie go zmodyfikowanym przez nas oraz skasowanie pliku content.xml z bieżącego katalogu (unlink()). Oczywi-ście, tę ostatnią funkcję musimy wywołać

Listing 10. Odczyt listy zadań z dokumentu aplikacji Ganttproject

// Pobieranie pliku do przetwarzania$file = $_SERVER['argv'][1];

// Otwieranie pliku przy użyciu SimpleXML

$xml = simplexml_load_file($file);

// Funkcja rekurencyjna odczytująca zadania

function display_tasks(&$xml, $level = 0) { $prefix = str_repeat('|', $level).'+-> ';

foreach ($xml->task as $task) { echo $task['start'].' '; echo ($task['duration'] > 9 ? '' : '0').$task['duration']; echo ' day(s) '.$prefix." ".utf8_decode($task['name'])."\n"; display_tasks($task, $level + 1);

}

}

// Wywołanie funkcji rekurencyjnej display_tasks()

display_tasks($xml->tasks);

Listing 11. Zmiana nazwy pierwszego zadania w pliku programu Ganttproject (kernel staje się Base)

$project = simplexml_load_file('openoffice.xml');

$project->tasks->task[0]['name'] = 'Base';

file_put_contents('openoffice.xml', $project->asXml());

Listing 12. Dodanie zadania w pliku programu Ganttproject za pomocą SimpleXML

$project = simplexml_load_file('openoffice.xml');

// Poszukiwanie identyfikatora największego zadania

$xpath = $project->xpath("//task/@id");

$maxId = 0;

foreach ($xpath as $item) { $maxId = max ((int) $item['id'], $maxId);}

// Modyfikacja trwania zadania macierzystego

$project->tasks->task[0]['duration'] = 11;

// Dodanie nowego zadania

$newTask = $project->tasks->task[0]->addChild('task');

$newTask['id'] = $maxId + 1;

$newTask['name'] = "preprod_tests";

$newTask['color'] = '#0066ff';

$newTask['meeting'] = 'false';

$newTask['start'] = '2006-12-03';

$newTask['duration'] = '5';

$newTask['complete'] = '0';

$newTask['priority'] = '1';

$newTask['expand'] = 'true';

// Zapisywanie modyfikacji

file_put_contents('openoffice.xml', $project->asXml());

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

Dla zaawansowanych

na samym końcu, gdy już dodamy ten plik do archiwum.

Generujemy dokument OpenOffice.org Writer na podstawie szablonuUtworzymy teraz dokument OO Writera przy użyciu szablonu, czyli istniejącego dokumentu, który posłuży nam jako model (trochę podobnie jak w przypadku syste-mów szablonów HTML-owych, takich jak np. Smarty). Będzie to wymagało wydoby-cia pliku content.xml z archiwum szablo-nu (u nas będzie to model.odt) i wykorzy-stania go w celu utworzenia nowego pli-ku content.xml, zawierającego dane, któ-re dodamy. Na koniec umieścimy ten no-wy plik w archiwum będącym kopią na-szego szablonu.

Wbudowane do PHP rozszerzenie ZIP nie pozwala niestety na zapisanie pli-ku: będziemy więc wywoływać programy zip oraz unzip korzystając z funkcji shell_exec(). Nic nie stoi również na przeszko-dzie, abyśmy użyli biblioteki pclzip, którą poznaliśmy w poprzednim przykładzie.

Naszemu szablonowi (Rysunek 4) nadamy nazwę model.odt, a następnie użyjemy systemu szablonów Smarty (http://smarty.php.net) w celu wygenerowania do-kumentu ze strumienia RSS (Rysunek 5).

Nasz przykład (Listing 15) jest prze-znaczony dla PHP5; wymaga również dostępu do plików zip (używanego w ce-lu dodania pliku do archiwum) i unzip (słu-żącego do wydobycia pliku z archiwum) w systemie operacyjnym.

Główną częścią naszego skryptu ge-nerującego plik OpenOffice Writera na podstawie szablonu jest klasa OpenOffice, która dziedziczy ze Smarty, co pozwala nam używać metod i atrybutów tej dru-giej. W rzeczywistości, klasa OpenOffi-ce będzie więc stanowiła API posiadają-ce silnik szablonów do generowania do-kumentów OpenOffice. Przykładowo, metoda assign() należy do Smarty, zaś mergeAndRight() do OpenOffice.

W ramach waszych dalszych działań i rozwinięć, możecie umieścić klasę Ope-nOffice w osobnym pliku, aby móc ją do-dać do innych przetwarzań. Korzystanie z tej klasy okazuje się bardzo proste, jak tego dowodzą cztery linijki z Listingu 15. Musimy tylko po prostu instancjonować nowy obiekt podając w parametrze na-zwę pliku OpenOffice Writer, który zamie-rzamy stworzyć. Następnie, dokonujemy przypisań i zakańczamy przez wywołanie

Listing 13. Plik content.xml obejmujący zawartość dokumentu OO Writera

<office:document-content office:version="1.0">

<office:scripts/>

<!-- Specyficzne style dokumentu -->

<office:font-face-decls>(...)</office:font-face-decls>

<office:automatic-styles/>

<!-- Zawartość dokumentu-->

<office:body>

<office:text>

<text:sequence-decls>(...)</text:sequence-decls>

<text:p text:style-name="Heading">Tytuł</text:p>

<text:p text:style-name="Standard">Paragraf...</text:p>

</office:text>

</office:body>

</office:document-content>

Listing 14. Manipulacja danymi w dokumencie OpenOffice.org Writer

include "pclzip.lib.php";

$zip = new PclZip('test.odt');foreach ($zip->listContent() as $file) { if ($file['filename'] == 'content.xml') { $zip->extractByIndex($file['index']);

$xml_txt = file_get_contents('content.xml');

break; }

}

if (!isset($xml_txt)){ exit;}

// Parsing dokumentu przy użyciu SAX i wyświetlanie danych

$p = xml_parser_create();

xml_parse_into_struct($p, $xml_txt, $vals, $index);

xml_parser_free($p);

foreach ($vals as $value) { if (isset($value['value'])) { switch($value['tag']) { case 'TEXT:H' : echo '<h1>'.utf8_decode($value['value'])."</h1>\n"; break;

case 'TEXT:P' : echo '<p>'.utf8_decode($value['value'])."</p>\n"; break; }

}

}

// Dodanie tytułu i akapitu za pomocą SimpleXML

$xml = simplexml_load_file('content.xml');

$contentPart = $xml->xpath('/office:document-content/office:body/office:text/');

$contentPart = $contentPart[0];

$title = $contentPart->addChild('text:p', 'New title', 'text');

$title['text:style-name'] = 'Heading';

$paragraph = $contentPart->addChild('text:p', 'New paragraph...', 'text');

$paragraph['text:style-name'] = 'Standard';

file_put_contents('content.xml', $xml->asXml());

// Zapisywanie modyfikacji

$zip->deleteByIndex($file['index']);

$zip->add('content.xml');

unlink('content.xml');

www.phpsolmag.org70 PHP Solutions Nr 5/2006

Dla zaawansowanych

metody mergeAndWrite , która stworzy do-kument, łącząc w całość dane zapisane i model model.odt.

Na początku tej klasy zdeklarujemy stałe ZIP_EXECUTABLE i UNZIP_EXECUTABLE, którym przypiszemy nazwy programów zip i unzip, co pozwala je zmienić wedle po-trzeb (np. w ramach dostosowywania do innego niż Linux systemu operacyjnego). Następnie zainicjujemy zmienne prywatne $OoFile i $OoModel i przejdziemy do two-rzenia konstruktora, w którym zdefiniuje-my parametry (nasze dwie zmienne i usta-wienia Smarty'ego). Ostatnim krokiem bu-dowania naszej klasy będzie utworze-nie metody publicznej mergeAndWrite(),

której zadaniem będzie tworzenie doku-mentu, w którym połączymy dane otrzy-mane z pliku podanego w $this->OoFile oraz szablonu. Rozpoczniemy ją kopiu-jąc archiwum szablonu (model.odt) jako test.odt. Następnie wydobędziemy z te-go archiwum plik content.xml i potraktuje-my go jako szablon używając w tym ce-lu odziedziczonej po klasie Smarty() me-tody fetch(), która zwraca skompilowa-ny dokument powstający przy użyciu sza-blonu. Otrzymany dokument przypiszemy do zmiennej $content, a następnie zwol-nimy zasób zajmowany przez skompilo-waną wersję szablonu używając metody $this->clear_compiled_tpl(). Pozostało

jeszcze zapisać otrzymany dokument ja-ko content.xml oraz dodać ten plik do ar-chiwum test.odt, zastępując nim poprzed-nio istniejący.

Pobierzemy teraz dane ze strumie-nia RSS (z portalu Yahoo!), wykorzystu-jąc do tego, jak poprzednio, SimpleXML. Pozostało jeszcze utworzenie obiektu $oo klasy OpenOffice, któremu przez para-metr konstruktora przekażemy nazwę pli-ku test.odt. Następnie korzystając z me-tod klasy Smarty przypiszemy zmiennym news i name załadowanego szablonu odpo-wiednio treść odczytanych newsów (któ-re przetworzymy w pętli foreach) oraz nazwisko twórcy dokumentu (Guillaume Ponçon). Na koniec wywołamy metodę mergeAndWrite() obiektu $oo, aby zapisać zmieniony plik. Nasza praca z dokumenta-mi OpenOffice.org jest zakończona.

PodsumowaniePokazaliśmy, jak łatwo i bezproblemo-wo korzystać z XML-a w PHP przy użyciu technik SAX, DOM i SimpleXML. Ta ostat-nia metoda okazała się szczególnie war-ta uwagi, ze względu na połączenie sze-rokich możliwości (włącznie z wykorzysta-niem XPath) z prostotą tworzenia oparte-go na niej kodu. Warto pamiętać, że cza-sami najlepsze może się okazać połą-czenie kilku technik, jak to ukazaliśmy na przykładzie aplikacji przetwarzającej do-kumenty edytora OpenOffice.org Writer. Bądźmy również świadomi, że możliwo-ści pracy skryptów PHP z dokumentami XML-owymi nie kończą się na SAX, DOM i SimpleXML: istnieją i wciąż powstają no-we rozszerzenia i biblioteki, np. wysoko-poziomowe API do łatwiejszego przetwa-rzania wiadomości RSS czy danych pa-kietu biurowego, czy też pakiety ułatwia-jące tworzenie dokumentów XML według wzorca. W każdym przypadku, dobre opa-nowanie obsługi XML-a utoruje nam dro-gę do tworzenia nowoczesnych aplika-cji, które mogą współpracować z przyjęty-mi standardami, nie będziemy więc skaza-ni na izolację naszych rozwiązań od resz-ty świata. n

Guillaume Ponçon jest architektem PHP i autorem Best practices PHP 5,francuskiej książki wydanej nakładem wydawnictwa Eyrolles.

O autorze

Listing 15. Tworzenie pliku edytora OpenOffice.org Writer w oparciu o szablon

include 'smarty/Smarty.class.php';

class OpenOffice extends Smarty { const ZIP_EXECUTABLE = 'zip';

const UNZIP_EXECUTABLE = 'unzip';

private $vars = array(); private $OoFile = '';

private $OoModel = '';

public function __construct($oo_file, $oo_model = 'model.odt') { $this->OoFile = $oo_file;

$this->OoModel = $oo_model;

$this->left_delimiter = '{{';

$this->right_delimiter = '}}';

$this->Smarty();

$this->template_dir = dirname(__FILE__).'/'; $this->compile_dir = $this->template_dir;

$this->config_dir = $this->template_dir;

$this->cache_dir = $this->template_dir;

$this->caching = false;

}

// Tworzy dokument, łącząc w całość przesłane dane i szablon

public function mergeAndWrite() { copy($this->OoModel, $this->OoFile); shell_exec(self::UNZIP_EXECUTABLE.' '.$this->OoFile.' content.xml');

$content = $this->fetch('content.xml');

$this->clear_compiled_tpl('content.xml');

file_put_contents('content.xml', $content);

shell_exec(self::ZIP_EXECUTABLE.' '.$this->OoFile.' -mq content.xml');

}

}

// Pobieranie danych ze strumienia RSS

$news = array();$xml_news = simplexml_load_file('http://rss.news.yahoo.com/rss/world');

foreach ($xml_news->channel->item as $item) { $news[] = (string) $item->title;}

// utworzenie i użycie obiektu OpenOffice

// do stworzenia nowego dokumentu "test.odt".

$oo = new OpenOffice('test.odt');$oo->assign('news', $news);

$oo->assign('name', "Guillaume Ponçon");

$oo->mergeAndWrite();

www.phpsolmag.org72

Projekty

PHP Solutions Nr 5/2006

Video streaming

www.phpsolmag.org 73

Projekty

PHP Solutions Nr 5/2006

Publikacja filmów w Internecie w oparciu o technologię Flash spro-wadza się do wykonania trzech

czynności:

• konwersji filmu do formatu Flash Vi-deo (FLV) obsługiwanego przez Flash Playera,

• stworzenia odtwarzacza (Media Play-era), który pobierze z serwera film i zaprezentuje go klientowi w przeglą-darce,

• publikacji strony internetowej, w której osadzimy odtwarzacz.

Aby opisać powyższe zadania, artykuł po-dzieliliśmy na trzy części:

• część pierwsza – tzw. szybki start, bez zbędnej teorii pokażemy, jak opubliko-wać w internecie pojedynczy film. Bę-dzie to prosty przykład tylko dla plat-formy Windows,

Zastanawiałeś się, jak działa odtwarzanie filmów z poziomu WWW? Podoba Ci się Google Video? Poznaj video streaming od kuchni i stwórz własną, webową galerię filmów. Wystarczy podstawowa znajomość PHP. To wszystko!

• część druga – rozbudujemy przykład, który został opisany w pierwszej czę-ści i pokażemy, jak można zautoma-tyzować opisane czynności orazi jak uniezależnić się od systemu opera-cyjnego, na którym będzie działać galeria,

• część trzecia – wykorzystamy nabytą w części drugiej wiedzę i kod do stwo-rzenia ostatecznej wersji serwisu – w pełni funkcjonalnej i w 100% uniwer-salnej galerii filmów.

Własne Google Video, czyli video streaming w PHPRafał Malinowski

W SIECI

l http://ffmpeg.mplayerhq.hu/ – FFMPEG

l http://ffdshow.faireal.net/mirror/ffmpeg/ – FFMPEG (windows binary)

l http://lame.sourceforge.net/ – LAME

l http://klaus.geekserver.net/libflv/ – LIBFLV – program do konwersji plików FLV

l http://www.geovid.com/ –GEO-VID Video to Flash converter

l http://www.rivavx.com/?encoder – Riva FVL Enco-der

l http://ffmpeg-php.sourcefor-ge.net/ – PHP-FFMPEG

l http://www.php.net/ming – MING – moduł PHP do tworzenia SWF-ów

Stopień trudności: lll

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

Co obiecujemy...Z artykułu dowiesz się, jak stworzyć własne Google Video – kompletną gale-rię filmów z flashowym Media Playerem na WWW pozwalającą na upload filmów i ich strumieniowe odtwarzanie.

www.phpsolmag.org72

Projekty

PHP Solutions Nr 5/2006

Video streaming

www.phpsolmag.org 73

Projekty

PHP Solutions Nr 5/2006

Szybki startMamy do wykonania trzy kroki:

• krok 1 – konwersja pliku wideo,• krok 2 – wykonanie wideoodtwarzacza

we Flashu,• krok 3 – osadzenie playera w HTML-u.

W zależności od systemu operacyjne-go, jak i od tego, czy chcemy korzystać z darmowych, czy też komercyjnych rozwiązań, każdy z tych kroków może-my wykonać na wiele sposobów. Na po-czątku wykonamy prezentację nasta-wioną na szybkie osiągnięcie celu. Uży-jemy do tego programów komercyjnych (wersje trialowe) dostępnych niestety tylko dla platformy Windows. Wszyst-ko zostanie pokazane krok po kroku. W następnej części artykułu przedsta-wimy rozwiązania niezależne od syste-mu operacyjnego, wymagające jednak większej pracy.

Konwersja pliku wideo.Flash Player pracuje z plikami wideo zapi-sanymi w formacie Flash Video (FLV). Na początku więc będziemy musieli przekon-wertować nasz film do FLV. Użyjemy do tego celu programu Riva FLV Encoder (http://www.rivavx.com/?encoder). Jak wi-

dać na Rysunku 1, przy pomocy Riva FLV Encoder możemy:

• ustawić rozdzielczość filmu wyjściowe-go (1),

• ustawić parametry jakościowe/kompresji pliku (2),

• odpowiednio przyciąć obraz filmu (3),• ustawić parametry dźwięku (4).

Dodatkowo program ten pozwala nam wy-ciągnąć z filmu pojedynczą klatkę i zapi-sać ją w postaci pliku graficznego JPEG – zakładka Image (5).

Aby dokonać konwersji, ustawiamy ścieżkę do pliku wejściowego, następnie do pliku wyjściowego i klikamy na przycisk FLV Encode (6). Zapisujemy pierwszy plik FLV. Nazwijmy go videotest.flv.

Flashowy wideoodtwarzaczMacromedia Flash – program, którego użyjemy do stworzenia pierwszego klipu flashowego, dysponuje eleganckim Media Playerem. Macromedia Flash jest odpłat-ny, ale dla potrzeb prezentacji wykorzy-stamy go w wersji trial, do ściągnięcia ze strony: http://www.adobe.com/products/flash/flashpro/ .

Po zainstalowaniu mamy możliwość zrobienia efektownego flashowego wide-oodtwarzacza przy minimalnym nakładzie pracy. Uruchamiamy Flasha (Rysunek 2), tworzymy nowy dokument np. o nazwie testplayer.fla i przenosimy na scenę (2) (ang. Stage, obszar roboczy dokumentu) komponent MediaPlayback (1). Następnie uruchamiamy Component Inspektor (3), gdzie musimy wpisać adres URL do wcze-śniej przygotowanego pliku FLV (4). URL może mięć postać ścieżki względnej lub bezwzględnej (rozpoczynającej się od fi-le:// lub http://). My dla uproszczenia ustal-my, że zarówno Media Player jak i film bę-dą znajdować się w tym samym folderze, Rysunek 1. Riva FLV Encoder – konwerter plików wideo do formatu FLV

Rysunek 2. Macromedia Flash – obszar roboczy aplikacji

Video streaming

www.phpsolmag.org74

Projekty

PHP Solutions Nr 5/2006

dlatego wystarczy że wpiszemy nazwę pli-ku wideo – videotest.flv..

Publikacja Movie ClipaNa koniec musimy wykonać plik HTML, w którym osadzimy Movie Player tak, by mogli go oglądać inni. Całą resztą, czy-li ściąganiem pliku wideo i odtwarzaniem go w przeglądarce zajmie się sam Movie Player. Również w tym przypadku Ma-cromedia Flash bardzo ułatwia nam za-danie. Wybieramy z menu polecenie File -> Publish Settings (Rysunek 3). W no-wym oknie, w zakładce Flash ustawiamy parametry publikacji i wybieramy wer-sję 7 Flash Playera (1). Klikamy na przy-cisk Publish i w katalogu, w którym zapi-saliśmy plik testplayer.fla, pojawiają się dwa nowe pliki: testplayer.swf i testplay-er.html. Jeśli pliki te wraz z plikiem wi-deo umieścimy na serwerze, to po otwo-rzeniu strony testplayer.html powinniśmy zobaczyć w przeglądarce internetowej opublikowany film (Rysunek 4).

I gotowe!Tym sposobem szybko kończymy wstęp-ną prezentację. Wykonana strona widocz-na będzie we wszystkich przeglądarkach internetowych posiadających zainstalowa-ny plugin Flash Player 7 lub nowszy. Jak widać, wykonana przez nas publikacja pli-ków wideo, jest zadaniem prostym. Nie-stety zastosowane rozwiązania nie speł-niają naszych wymogów. Po pierwsze, są komercyjne (dodatkowo sporo kosztują) i są dostępne tylko dla platformy Windows, a po drugie, wszystkie czynności musieli-śmy wykonać ręcznie. Teraz postaramy się zautomatyzować proces konwersji fil-mów, postawimy większy nacisk na uni-wersalność, uniezależniając się od syste-mu operacyjnego i do tego skorzystamy jedynie z darmowego oprogramowania.

Czas na rozbudowęJeśli chcemy dać użytkownikom możliwość uploadu własnych plików wideo, musimy zadbać o automatyczną konwersję filmów. Standard Flash Video (FLV) jest formatem dla plików wideo wymyślonym przez firmę Macromedia pozwalającym na tzw. stru-mieniowe pobieranie (ang. streaming) fil-mów z serwera za pomocą Macromedia Flash Playera (wersje od 6 w górę). Stru-mieniowe, czyli takie, które pozwala na po-kazywanie filmu podczas jego ściągania. Flash Player dysponuje również innymi me-todami dostarczania filmów do przeglądarki,

Rysunek 3. Macromedia Flash – Publish Settings

Rysunek 4. Komponent MediaPlayback firmy Macromedia prezentujący film w prze-glądarce internetowej

Video streaming

www.phpsolmag.org 75

Projekty

PHP Solutions Nr 5/2006

np. pobieranie progresywne (ang. progresi-ve) lub zapisywanie pliku wideo wewnątrz animacji Flash. Mają one swoje zalety, ale w przypadku dłuższych filmów najlepiej za-stosować streaming.

W pierwszym przykładzie do konwersji plików wideo użyliśmy programu Riva FLV Encoder, który wykorzystuje do konwer-

sji dwa programy Open Source: FFMPEG (http://ffmpeg.sourceforge.net/index.php) oraz LAME (http://lame.sourceforge.net/). Pierwszy z nich to wszechstronny konwer-ter dla plików wideo i audio, a drugi tylko dla audio pozwalający na zapis w formacie MP3 (MP3 jest jak na razie jedynym odczytywa-nym przez Flash Player formatem audio).

FFMPEG – konwersja plików wideoJeśli zdecydujemy się na własnoręczną kompilację źródeł programu FFMPEG na serwerze, pamiętajmy o opcji -mp3lame. Umożliwi ona konwersję ścieżek audio pli-ków wideo do formatu MP3. Jeśli tego nie zrobimy, nasze wideo na etapie odtwarza-nia przez Flash Player zostanie pozbawio-ne dźwięku (w przypadku, gdy podczas kompilacji otrzymamy komunikat o braku wymaganych bibliotek, zalecamy pobra-nie źródeł LAME). Przykładowe polecenie configure to:

./configure

--enable-mp3lame

--enable-gpl

--enable-memalign-hack

Lista wspieranych przez FFMPEG forma-tów, a tym samym możliwości konwer-sji jest długa. Komenda ffmpeg -formats prezentują ich listę, czyli pokazuje jakich konwersji możemy dokonać przy pomocy zainstalowanego konwertera.

Aby dokonać najprostszej konwersji z pliku AVI do FLV, użyjmy komendy:

ffmpeg -i tmp\test.avi

tmp\test.flv

Program rozpozna rozszerzenia plików i dopasuje odpowiednie formaty konwer-sji. Załaduje też domyślne parametry dla tej operacji.

Przykładowe polecenie, które (okre-ślając wielkość filmu i jakość dźwięku) do-kona konwersji filmu w formacie AVI do FLV, to:

ffmpeg -y -i tmp\test.avi

-ar 22050 -acodec mp3

-ab 32 -f flv -s 160x120

tmp\test.flv

gdzie:

• -ar rate – ustawia samplingrate w Hz,

• -acodec codec – wymusza uży-cie wskazanego typu kodowania dla ścieżki audio,

• -ab bitrate – ustawia bitrate w kbit/s,• -s size – ustawia rozmiar klatki WxH, • -f fmt – wymusza format kodowania

pliku wyjściowego,• -y – nagranie pliku bez potwierdze-

nia.

Rysunek 5. phpinfo() – informacja o zainstalowanej wersji modułów MING i FFMPEG

Listing 1. Inicjacja Movie Clipa

$movie =new SWFMovie(7);$movie->setDimension(200,100);

$movie->setBackground(0xff,0xcc,0x0);

$movie->setRate(30);

$movie->nextFrame();

$movie->save("empty.swf");

Listing 2. Tworzymy przyciski dla Movie Clipa

$button = new SWFButton(); $flags = (SWFBUTTON_UP | SWFBUTTON_HIT | SWFBUTTON_OVER | SWFBUTTON_DOWN);

$button->addShape(getButtonShape(),$flags);

$action = new SWFAction($actionscript); $button->addAction($action, SWFBUTTON_MOUSEDOWN);

$button_ref = $this->movie->add($button);

$button_ref->moveTo($x, $y);

Listing 3. Programujemy kształt dla przycisków

function getButtonShape() {$img = new SWFBitmap(fopen("image.dbl", "rb"));$w = $img->getWidth();;

$h = $img->getHeight();

$shape = new SWFShape(); $shape->setLine(1,255,255,255);

$fill = $shape->addFill($img, SWFFILL_TILED_BITMAP);

$shape->setRightFill($fill);

$shape->drawLine($w,0);

$shape->drawLine(0,$h);

$shape->drawLine(-$w,0);

$shape->drawLine(0,-$h);

return $shape;}

Video streaming

www.phpsolmag.org76

Projekty

PHP Solutions Nr 5/2006

Polecenie, które wyciągnie pojedynczą klatkę z trzeciej sekundy filmu formatu AVI i zapisze ją w postaci obrazu w formacje JPG to:

ffmpeg -i tmp\test.avi

-s 320×240 -vframes 1

-ss 3 -f mjpeg test.jpg

gdzie:

• -ss time_offset – ustawia offset (przesunięcie względem początku fil-mu) w sekundach,

• -vframes number – ustawia ilość klatek wideo, jakie mają być pobrane z pliku wejściowego i przekazane do konwer-sji.

MING – tworzymy Media PlayerTeraz, gdy zapoznaliśmy się już z samą konwersją plików wideo, przejdziemy do zrobienia własnego flashowego Media Playera. Na liście darmowych programów, które potrafią wygenerować Flasha mamy dwie pozycje:

• MTASC – darmowy, kompilator Open Source dla Action Script (AS to język programowania używany we Flashu)

• MING – napisany w C generator pli-ków SWF.

Obydwa programy mają bardzo du-że możliwości. My wybierzemy MING-a, bo posiada swoje rozszerzenie dla PHP. Dzięki temu możemy z poziomu PHP wy-generować flashowego Movie Clipa. Wię-cej o MINGU można poczytać w numerze 3/2005 Search Engine Optimization, w ar-tykule Flash w PHP, czyli MING w akcji.

Po zainstalowaniu rozszerzenia MING (informacje na temat, jak to zrobić, znaj-dziemy na stronie www.php.net/ming) przechodzimy do tworzenia pliku SWF.

Zaczniemy od stworzenia pod-stawowego Movie Clipa, co pokazu-je Listing 1. Ustalamy w nim, dla jakiej wersji Flash Playera tworzymy clip SWFMovie($playerVersion), jakie ma mieć roz-miary setDimension($width, $height), dodatkowo ustalić możemy kolor tła setBackground($r,$g,$b)czy ilość klatek wyświetlanych w ciągu sekundy setRa-te($frameRate). Zapisanie pliku odbywa się po wywołaniu metody save($fileName) obiektu $movie, przy czym, zanim to zrobi-my, powinniśmy mieć na scenie minimum jedną wygenerowaną klatkę, co zapewnia

nam metoda nextFrame(). Wynikiem wy-konania kodu z Listingu 1 jest pusty Movie Clip o rozmiarach 200px na 100px i czer-wonym kolorze tła.

Teraz przechodzimy do ustawia-nia elementów na scenie Movie Cli-

pa. Warto dodać, że wszystko układa-my w obrębie jednego frama, czyli Mo-vie Clip tak naprawdę składać się bę-dzie z jednej klatki. Media Player, któ-rego tworzymy, składać się będzie z trzech przycisków (Stop, Play, Pause)

Listing 4. Tworzymy obiekt SWFVideoStream obsługujący video streaming

$stream = new SWFVideoStream();$stream->setDimension($width, $height);

$item = $this->movie->add($stream);

$item->moveTo($x, $y);

//nazwa obiektu VideoStream na scenie

$item->setname("my_player_vstrm");

$action_script = "

stop();

netConn = new NetConnection();

netConn.connect(null);

netStream = new NetStream(netConn);

my_player_vstrm.attachVideo(netStream);

netStream.setBufferTime(10);

netStream.play(_level0.video_path);

";

//netStream.play(_level0.video_path);

$this->movie->add(new SWFAction($action_script));

Listing 5. Źródło pliku testplayer.html

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

<head>

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-2" />

<title>testplayer</title>

</head>

<body bgcolor="#ffffff">

<object

classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"

codebase="http://fpdownload.macromedia.com/pub/

shockwave/cabs/flash/swflash.cab#version=8,0,0,0"

width="550"

height="400"

id="testplayer"

align="middle">

<param name="allowScriptAccess" value="sameDomain" />

<param name="movie" value="testplayer.swf" />

<param name="quality" value="high" />

<param name="bgcolor" value="#ffffff" />

<embed

src="testplayer.swf"

quality="high"

bgcolor="#ffffff"

width="550"

height="400"

name="testplayer"

align="middle"

allowScriptAccess="sameDomain"

type="application/x-shockwave-flash"

pluginspage="http://www.macromedia.com/go/getflashplayer" />

</object>

</body>

</html>

Video streaming

www.phpsolmag.org 77

Projekty

PHP Solutions Nr 5/2006

oraz obszaru, w którym pokazywany będzie film.

Budowę zaczniemy od stworzenia przycisków. Z punktu widzenia użytkowni-ka, przycisk we Flashu to pewien obszar powierzchni o dowolnym kształcie, który reaguje na zachowanie myszki. Z punk-tu widzenia programisty przycisk to obiekt, który reaguje na pewne zdarzenia wywo-łane przez kursor myszki nad zdefiniowa-nym obszarem sceny. MING pozwala nam zaprogramować dla przycisków następu-jące zdarzenia:

• SWFBUTTON_MOUSEUP – gdy puścimy przycisk myszy nad zdefiniowanym obszarem,

• SWFBUTTON_MOUSEOVER – gdy najedzie-my kursorem na zdefiniowany obszar,

• SWFBUTTON_MOUSEOUT – gdy wyjedziemy kursorem poza zdefiniowany obszar,

• SWFBUTTON_MOUSEDOWN – gdy naciśnie-my przycisk myszki nad zdefiniowa-nym obszarem,

• SWFBUTTON_DRAGOVER – gdy najedzie-my na obszar kursorem myszki z na-ciśniętym równocześnie przyciskiem myszki,

• SWFBUTTON_DRAGOUT – gdy wyjedziemy poza obszar kursorem myszki z naci-śniętym przyciskiem myszki.

Po stworzeniu instancji przycisku $button = new SWFButton(), dla każdego zdarze-nia lub kombinacji zdarzeń możemy zde-finiować pewną akcję. W tym przypadku wykorzystamy tylko reakcję na zdarzenie kliknięcia przycisku myszki, czyli tworzy-my button, dla którego definiujemy pewną akcję, gdy zaistnieje zdarzenie SWFBUTTON_MOUSEDOWN. Takie polecenie w PHP wyglą-da następująco:

$button->addAction($action,

SWFBUTTON_MOUSEDOWN).

Gdzie $action to akcja, jaka ma zostać wykonana po zdarzeniu. Obiekt $action tworzymy w następujący sposób:

$action = new SWFAction(

$actionscript)

przy czym $actionscript to kawałek ko-du, który musimy już sami napisać w Ac-tion Script, np.

$actionscript =

'myVideoPlayer.pause();'.

To, co udało nam się przed chwilą wy-konać, oznacza, że w przypadku, gdy zostanie naciśnięty button (ustawia-liśmy akcje dla zdarzenia SWFBUTTON_

MOUSEDOWN) należy wykonać polecenie: myVideoPlayer.pause();.

Polecenie to wykonuje metodę pause() na obiekcie myVideoPlayer, o którym napiszemy za chwilę. Gdy określimy już, jak przycisk ma reago-wać na dane zdarzenia, musimy okre-ślić kształt, rozmiar, wygląd i pozycję

jego obszaru aktywnego. Do tego słu-ży następująca metoda $button->add

Shape($shape,$flags), która ustala, że dla buttona $button aktywnym kształ-tem dla zdarzeń $flags będzie obiekt $shape. Dzięki temu, że możemy ustalić różne kształty obszarów dla wybranych zdarzeń, w przyszłości będziemy mogli np. zmieniać kolor przycisku, gdy naje-dziemy na niego myszką. Na razie jed-nak ustalimy jeden kształt dla wszyst-kich zdarzeń:

Rysunek 6. Formularz uploadu plików wideo

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

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

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

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

������

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

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

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

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

������

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

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

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

���������

Rysunek 7. Schemat bazy przechowującej informacje o zapisanych plikach wideo

Video streaming

www.phpsolmag.org78

Projekty

PHP Solutions Nr 5/2006

$flags = (SWFBUTTON_UP

| SWFBUTTON_HIT

| SWFBUTTON_OVER

| SWFBUTTON_DOWN);

W przypadku $shape mamy duże pole do popisu – możemy sobie bowiem naryso-wać np. prostokąt o kolorze czerwonym lub inny, ciekawszy obrazek przycisku symbolizującego klawisz pauzy. W tym drugim przypadku przyda się nam do te-go możliwość stworzenia kształtu, którego tłem będzie bitmapa – możemy tu przywo-łać analogię do HTML-a, gdzie tworzymy tabelkę z tłem jako plik. Wykonanie tego zadania wymaga stworzenia na początku obiektu SWFBitmap:

$img = new SWFBitmap(

fopen("image.dbl", "rb"));.

Przy czym SWFBitmap() pozwala na pobra-nie plików graficznych w formacie DBL lub JPG (na stronach projektu MING dostęp-ne jest narzędzie do konwersji plików GIF lub PNG do DBL). Po zainicjowaniu obiek-tu $img, tworzymy nowy kształt $shape = new SWFShape() i ustawiamy jako jego wy-pełnienie grafikę:

$fill = $shape->addFill(

$img, SWFFILL_TILED_BITMAP);

Następnie rysujemy prostokąt, którego rozmiar pokrywa się z obrazkiem. Do tego celu wykorzystujemy polecenie:

$shape->setLine(1,255,255,255);

określające parametry linii, którą będzie-my obrysowywać obszar (grubość i skła-dowe RGB koloru) oraz:

$shape->drawLine($x,y);

które rysuje linie do punktu $x,$y, z po-przedniego punktu, w jakim znajdował się kursor. Listing 3 pokazuje, jak wygląda funkcja tworząca obrys przycisku, którego tłem jest obraz image.dbl

Po wykonaniu tych operacji na sce-nie będziemy już widzieli przyciski. Te-raz skupimy się na odegraniu pliku vi-deo. Do tego celu wykorzystamy polece-nie $stream = new SWFVideoStream();, które inicjuje obiekt obsługujący video streaming. Następnie nadajemy obiek-towi odpowiednie wymiary $stream-

>setDimension($width, $height); i po-

Listing 6. Konwersja plików wideo z poziomu PHP

<?php

define('FFMPEG_FRAME_SIZE_PARAM','-s 160x120');

//-ar rate – ustawiamy częstotliwość próbkowania (w Hz)

define('FFMPEG_AUDIO_RATE_PARAM','-ar 22050');

define('MOVIE_SOUND_','160x120');

class VideoFile { private $dbh;

private $osh;

public function __construct(DatabaseHandler $dbh) { $this->osh = PlatformFactory::getPlatformInstance();

$this->dbh = $dbh;

}

public function uploadFile($files_name,$files_tmp_name,$size,$name,$desc) { $target_path = MY_MEDIA_DATA_DIR . basename($files_name); // jeśli plik zostal wgrany, kopiujiemy go do katalogu z mediami

if( move_uploaded_file($files_tmp_name, $target_path)) {

} else{ throw new Exception("Powstał błąd podczas wgrywania pliku.");

}

$this->dbh->addResource($name,$desc,$size);

$this->convertMovieToFlv(basename($files_name)); $this->convertMovieToImges(basename($files_name)); }

private function convertMovieToFlv($fname) { $flvName = $this->osh->getFileBaseName($fname).'.flv';

$command = 'ffmpeg -i "'.MY_MEDIA_DATA_DIR.$fname.'"'.

' -ar 22050 -acodec mp3 -ab 32 -f flv -s 160x120 '.

'"'.MY_MEDIA_DATA_DIR.$flvName.'"';

if ($this->osh->exec($command)== 0) { $this->dbh->addMovie($flvName );

return true; }

else return false; }

private function convertMovieToImges($fname) {

if ($this->osh->isPhpFfmpegExtensionLoaded()) { $mov = new ffmpeg_movie(MY_MEDIA_DATA_DIR.$fname); $duration = floatval( $mov->getDuration());

$step1 = $duration/3;

$step2 = $step1*2;

$temp = array(0,$step1,$step2);

foreach ($temp as $k=>$v) { $imgName = $this->osh->getFileBaseName($fname)."0$k.jpg";

$command ='ffmpeg -i "'.MY_MEDIA_DATA_DIR.$fname.'" -s 160x120

-vframes 1 -f mjpeg -ss '.$v.' "'.MY_MEDIA_DATA_DIR.$imgName.'"';

if ($this->osh->exec($command) == 0) { $this->dbh->addImage( $imgName );

}

}

}

else { $command ='ffmpeg -i '.MY_MEDIA_DATA_DIR.$fname.' -s 160x120 -vframes 1

-f mjpeg '.MY_MEDIA_DATA_DIR.$this->osh->getFileBaseName($fname).'00.jp

g';

$this->osh->exec($command); }

}

}

?>

Video streaming

www.phpsolmag.org 79

Projekty

PHP Solutions Nr 5/2006

zycję $item->moveTo($x, $y); oraz nazwę $item->setname("my_player_

vstrm");, którą posługiwać się będzie-my przy sterowaniu przebiegiem odtwa-rzania filmu. Na koniec musimy jeszcze zainicjować na scenie proces wgrywa-nia pliku wideo. Zrobimy to już samym

Action Scriptem, który wstawimy bezpo-średnio w pierwszą klatkę Movie Clipa.

Klasa SWFVideoStream, której in-stancję o nazwie $stream tworzymy na Listingu 4, zajmuje się obsługą strumieni we Flashu. Aby otworzyć taki strumień, wywołujemy odpowiednie metody języ-

ka Action Script. Najpierw ustanawiamy połączenie:

netConn = new NetConnection();

netConn.connect(null);

Następnie tworzymy strumień, który bę-dzie korzystał z tego połączenia:

netStream = new NetStream(netConn);

Kolejny krok to wskazanie instancji my_player_vstrm(SWFVideoStream) ob-sługującej video streaming tak, aby ko-rzystała z zainicjowanego właśnie stru-mienia:

my_player_vstrm.attachVideo(

netStream);

Na końcu jeszcze ustawiamy wielkość bu-fora dla strumenia video, czyli ile sekund filmu chcemy pobrać z serwera do buffo-ra, zanim zaczniemy odgrywać film, a na-stępnie możemy wskazać, jaki plik wideo ma zostać wgrany przez Media Player:

netStream.play(_level0.video_path);

Zauważmy, że do rozpoczęcia proce-su wgrywania pliku FLV wykorzystujemy zmienną o nazwie _level0.video_path. Zmienną tą będziemy wykorzystywać do przesyłania do wnętrza Movie Clipa na-zwy pliku video, który chcemy odegrać na scenie.

Parametryzowanie SWF-aPrzyjrzyjmy się przykładowi, który stwo-rzyliśmy na samym początku niniejsze-go artykułu. Warto obejrzeć sobie plik te-stplayer.html, który po odpowiednim sfor-matowaniu wygląda mniej więcej jak na Listingu 5.

Jest to standardowy sposób na wsta-wienie pliku SWF w ciało strony interne-towej. Widzimy tu tagi <object> i <embed> opisujące zmienne środowiskowe dla pu-blikowanego Movie Clipa, w tym przypad-ku pliku testplayer.swf.

Zauważmy, że parametry i ich warto-ści dublują się. Wynika to z braku kom-patybilności przeglądarek. Tag <embed> przeznaczony jest dla przeglądarek opar-tych o engine Gecko (Netscape, Mozilla) i dla Macintosh Internet Explorer, natomiast tag <object> przeznaczony jest dla prze-glądarek wspierających ActiveX. Aby prze-kazać jakąś wartość do Movie Clipa, mo-

Listing 7. Wywołanie komendy dla różnych systemów operacyjnych

<?PHP

class PlatformUnix extends PlatformAbstract{ public function exec($command) { exec($command,$return,$return_value); return $return_value; }

}

?>

<?PHP

class PlatformWindows extends PlatformAbstract{ public function exec($command) { $command = 'cmd /c ' . $command . '';

exec($command,$return,$return_value); return $return_value; }

}

?>

Rysunek 8. Galeria zdjęć z klatek wyciętych z uploadowanych filmów

Video streaming

www.phpsolmag.org80

Projekty

PHP Solutions Nr 5/2006

żemy rozwinąć nazwę pliku SWF stosując metodę, jaką wykorzystujemy przy przesy-łaniu danych formularza w URL-u (używa-jąc metody GET).

Jeśli zatem chcemy przekazać do Mo-vie Clipa pojedynczą zmienną o nazwie np. video_path i nadać jej wartość np. test.flv to napiszemy:

testplayer.swf?

video_path=test.flv

Czyli wnętrze tagów <embed> i <object> zmieniamy odpowiednio na:

src=

"testplayer.swf?video_path=test.flv"

<param name="movie"

value="testplayer.swf?

video_path=test.flv" />

Od tej pory w środowisku Movie Clipa do-stępna będzie zmienna video_path, (któ-rą wykorzystaliśmy już podczas tworze-nia pliku SWF), o wartości test.flv. W ten sposób będziemy mogli przekazać Me-dia Playerowi informację o filmie, który ma być ściągnięty i odegrany, bez potrzeby tworzenia nowego pliku SWF dla każde-go nowego filmu.

Tworzymy galerię filmówWyposażeni we własny Media Player i za-instalowany konwerter plików video FFM-PEG możemy przejść do tworzenia serwi-su internetowego. Nasz portal powinien:

• umożliwiać wgrywanie przez użytkow-ników nowych plików wideo,

• przedstawiać pliki video udostępnia-ne na serwerze w postaci galerii zdjęć, przedstawiających trzy wybrane klatki z filmu,

• prezentować nagranie, w momencie gdy użytkownik kliknie na wybraną se-rię klatek.

Budowę witryny rozpoczniemy od stwo-rzenia formularza do uploadu plików. Ry-sunek 6 przedstawia przykładowy for-mularz. Serwis dokona konwersji uplo-adowanego pliku wideo do formatu FLV, a następnie wybierze z filmu trzy klatki i zapisze je w postaci plików graficznych. Wszystkie informacje przechowywać bę-dziemy w bazie danych tak, by były póź-niej łatwo dostępne podczas generowa-nia galerii zdjęć.

Po kliknięciu na przycisk Upload File, przechodzimy do konwersji i generowania klatek, do czego użyjemy polecenia PHP: exec($cmd), które wywołuje zewnętrzny program (jednak polecenie to często nie jest zalecene do stosowania w środowi-skach produkcyjnych).

Wywołanie komendy w zależności od systemu operacyjnego wygląda jak na Li-stingu 7.

Efektem wykonania kodu przedstawio-nego na Listingach 6 i 7 są:

• trzy pliki graficzne w katalogu media-data,

• jeden plik FLV w katalogu mediadata,• informacje o plikach graficznych w ba-

zie (tabela images),• informacje o pliku FLV w bazie (tabela

movies),• informacje o nowo dodanym zasobie

(wpis w tabeli resources).

Pozostaje nam teraz wywołać zapytanie SQL-owe, które pobierze z bazy (Rysu-nek 7) informacje o zasobach galerii i wy-generowanie strony przedstawiającej li-stę wyciętych klatek z filmu (Rysunek 8).Po kliknięciu na jedną z nich nastąpi od-

tworzenie samego filmu, co widać na Ry-sunku 9.

PodsumowanieW artykule przedstawiliśmy sposób stwo-rzenia własnej galerii video w oparciu o rozwiązania komercyjne oraz darmowe. Pokazaliśmy ręczny sposób przeprowa-dzenia streamingu video oraz metody au-tomatyzacji całego procesu publikacji stru-mienia video na stronach WWW. Wklada-jąc trochę więcej wysiłku możemy dopra-cować i rozbudować naszą galerię, aby pod żadnym względem nie ustępowała ta-kim rozwiązaniom, jak Google Video oraz zintegrować ją z dowolnym serwisem in-ternetowym. n

Rysunek 9. Stworzony przy pomocy MING-a Media Player odgrywa film Robot.flv

Rafał Malinowski jest programistą i pro-jektantem aplikacji sieciowych w języ-kach PHP, JAVA i ActionScript. W wol-nych chwilach tworzy biblioteki dla Fla-sha i udziela się w kilku projektach Open Source. Obecnie pracuje w SUPERME-DIA jako projektant / programista Java.Kontakt [email protected]

O autorze

Ponadto planujemy:

■ TESTY – Test systemów CMS■ DLA ZAAWANSOWANYCH Aspekt Oriented Programming w PHP■ NARZĘDZIA – PHP-Qt

■ PEAR – XML Fast Create

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

BEZPIECZEŃSTWO

PROJEKTY

DLA ZAAWANSOWANYCH Więcej o IoC (Inversion of Control) Zajmiemy się aspektami związanymi z logowaniem,

bezpieczeństwem i walidacją obiektów bizneso-wych.

Wielojęzyczny portal w 5 minut z wykorzystaniem eZ publish

Nowe możliwości eZ publish 3.8.

Kryptografi a asymetryczna w PHP Implementacja RS

W następnym numerze

W sprzedaży od 15 października!

PHP Solutions 6/2006 (17)

TESTY Co nowego w IDE dla PHP? Nowe, rewolucyjne możlowości środowisk programi-

stycznych