PHP Solutions 05 2006 PL

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

Comments

Content

Spis treści

AKTUALNOŚCI

6

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

W Egipcie nas nie lubią!

Opis CD

8

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 przypadku 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 popularnego 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 fajnego 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 wykorzystanie PHP w różnych regionach świata. Kolor zielony oznacza największe (np. Ukraina – 69%), a czerwony najmniejsze zainteresowanie (np. Egipt 3,93 %) technologią PHP. Kolor żółty oznacza średnią. Z rysunku wynika, że najbardziej lubią nas na Ukrainie i w Rosji. Kto wie, może zaczniemy wydawać w tych językach). Przyjemnej lektury, Redakcja PHP Solutions

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

WYWIAD
Wywiad z Tobiasem Schlittem, jednym z głównych deweloperów 10 platformy eZ components
Dariusz Pawłowski 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.

DLA POCZĄTKUJĄCYCH
Łączymy DB2 i PHP
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 bazodanowych. Przedstawiamy Ci DB2: solidną i rozbudowaną bazę, która umożliwia przechowywanie i operacje na danych w postaci XML-owej.

12

BEZPIECZEŃSTWO
Zaawansowane ataki SQL Injection
Mike Shema 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.

20

NARZĘDZIA
TYPO3 od kuchni, czyli wymarzony 28 portal w zasięgu ręki
Jean-Gael Rouchon

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

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

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.

4

www.phpsolmag.org

PHP Solutions Nr 5/2006

KASA DLA WEBMASTERA
Freelancing – zostań wolnym strzelcem
Krzysztof Trynkiewicz Jesteś programistą i chcesz wziąć udział w ciekawym projekcie i zarobić trochę pieniędzy? A może potrzebujesz kogoś, kto wykona dla Ciebie witrynę internetową, aplikację dla księgowości czy grafikę? Dzięki serwisom freelancingowym każdy z Was znajdzie to, czego potrzebuje przy minimalnym lub żadnym ryzyku.

Spis treści

38

Pytania dotyczące prenumeraty

Strona WWW/Forum

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

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

DLA ZAAWANSOWANYCH
Zend API – tworzymy własne rozszerzenie dla PHP
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 wykorzystać swoją ulubiona bibliotekę z C? Rozwiązaniem Twoich problemów może okazać się Zend API.

CD

42

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

Cena

Zamówienia /Numery archiwalne

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

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

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

Kontakt z redakcją
54

e-mail: [email protected] Software Wydawnictwo Sp. z o.o. Redakcja PHP Solutions ul. Piaskowa 3 01-067 Warszawa
Listingi wszystkich opisywanych programów zostały zamieszczone na naszej stronie internetowej www.phpsolmag.org/pl.

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.

PHP Solutions jest wydawany przez Software-Wydawnictwo Sp. z o.o. Dyrektor Wydawniczy: Jarosław Szumski Product Manager: Adam Urbanowski [email protected] Redaktorzy współpracujący: Dariusz Pawłowski [email protected], Krzysztof Sobolewski Stali współpracownicy: Paweł Kozłowski [email protected], Krzysztof Trynkiewicz Kierownik produkcji: Marta Kurpiewska [email protected] Projekt okładki: Agnieszka Marchocka Skład i łamanie: Sławomir Zadrożny [email protected] Dział reklamy: [email protected] Prenumerata: Marzena Dmowska [email protected] Nakład: 6 000 egz. Adres korespondencyjny: Software-Wydawnictwo Sp. z o.o., ul. Piaskowa 3, 01-067 Warszawa, Polska tel. +48 22 887 10 10, fax +48 22 887 10 11 www.phpsolmag.org [email protected] Dołączoną do magazynu płytę CD przetestowano programem AntiVirenKit firmy G DATA Software Sp. z o.o.

XML i PHP w praktyce
Guillaume Ponçon

62

Bazy danych, dokumenty biurowe, RSS: coraz wicej formatów gromadzenia i przesyania danych opiera si na XML-u. Jego gówn zalet jest atwo tworzenia i przetwarzania dokumentó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 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!

Redakcja dokłada wszelkich starań, by publikowane w piśmie i na towarzyszących mu nośnikach informacje i programy były poprawne, jednakże nie bierze odpowiedzialności za efekty wykorzystania ich; nie gwarantuje także poprawnego działania programów shareware, freeware i public domain. Uszkodzone podczas wysyłki płyty wymienia redakcja. Wszystkie znaki firmowe zawarte w piśmie są własnością odpowiednich firm i zostały użyte wyłącznie w celach informacyjnych. Redakcja używa systemu automatycznego składu Do tworzenia wykresów i diagramów wykorzystano program firmy

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

Zapowiedzi

82

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

.

PHP Solutions Nr 5/2006

www.phpsolmag.org

5

Aktualności
IBM dla PHP
IBM w coraz większym stopniu zauważa potencjał PHP i możliwości jego wykorzystania w oprogramowaniu przeznaczonym dla biznesu. Projekt PHP Integration Kit for WebSphere Application Server (WAS) Community Edition (CE) ma za zadanie zintegrowanie PHP z aplikacjami J2EE, w szczególności z architekturą SOA (service-oriented architecture). WAS Community Edition to lekki serwer aplikacji Java oparty o opensourcowy serwer Apache Geronimo. Darmowy PHP Integration Kit pozwoli programistom 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

phpMyVisites

p

phundament – 100% czystego PRADO

To framework oferujący komponenty do budowy aplikacji WWW napisane w PRADO 3. Projekt narodził się z idei stworzenia lekkiego, stabilnego i zorientowanego obiektowo kodu, który może być łatwo rozszerzany i wielokrotnie używany. Poza PRADO phundament wykorzystuje 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 stronie projektu znajdziemy dokumentację, wersję demo, a nawet tutoriale video.

ApacheCon Europe 2006

W dniach 29-30 czerwca 2006 w Dublinie, w Irlandii, 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 budowaniem aplikacji w oparciu o PHP (np. PHP 6 & Unicode: The Tower of Babel - Next Generation, Building a Fast and Rich Web App with PHP 5, Agile PHP Testing). http://www.eu.apachecon.com/

hpMyVisites to darmowe (GNU/GPL) narzędzie do sporządzania i wyświetlania statystyk. Trudno systemowi cokolwiek zarzucić: oferuje wszystkie najważniejsze i przydatne informacje dotyczące oglądalności witryny. Interfejs jest praktyczny i intuicyjny phpMyVisites, a instalacja nie stanowi najmniejszego problemu. Działają tak, jak większość tego typu rozwiązań: na wybranej witrynie umieszczamy prosty kod JavaScript zliczający wizyty. Informacje prezentowane użytkownikowi są przedstawione w prosty i praktyczny sposób. Do programu możemy dodawać dowolną liczbę stron, pod które podepniemy statystyki. W phpMyVisites możemy zarządzać użytkownikami, którzy otrzymują prawa do odczytu/administracji dla konkretnych stron. Statystki przedstawiają wiele interesujących informacji o odwiedzających nasze witryny, m.in: lokalizacja geograficzna, dane techniczne (system operacyjny, rozdzielczość komputera),

długość/częstość przebywania na stronie, strony odsyłające do naszej witryny czy nawet słowa kluczowe jakie były podawane podczas korzystania z wyszukiwarki na stronie. Dodatkowo, raport może zostać wysłany mailem lub rozprowadzany za pomocą RSS. Program dostępny jest w 29 wersjach językowych. Licencja: GNU/GPL http://www.phpmyvisites.us/

Zend Studio 5

DC PHP Conference 2006

W 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 zostaną niemal wszystkie najważniejsze zagadnienia związane z tworzeniem aplikacji w PHP (np. bezpieczeństwo, wydajność i skalowalność, narzędzia, biblioteki, techniki oraz zastosowania biznesowe PHP). http://dcphpconference.com/

N

Forum PHP 2006

W dniach 9-10 listopada 2006 w Paryżu, odbędzie się już piąta edycja spotkania deweloperów PHP. Wśród speakerów wystąpią między innymi Rasmus 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 newslettera

To bardzo rozbudowany, kompletny system do email marketingu stworzony przez firmę eZ systems. Program kontroluje wszystko, co związane 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. Dodatkowo system współpracuje z telefonami komórkowymi, faksem i PDA. http://ez.no/products/solutions/newsletter

ieprzerwany sukces języka PHP pociąga za sobą ciągły rozwój coraz bardziej zaawansowanych środowisk programistycznych (rozbudowanych edytorów) zwanych IDE (Integrated Development Environment). Wymagania wobec tych narzędzi stale rosną, dlatego PHPowe edytory rozwijają się w dość szybkim tempie. Wśród IDE dla PHP prym wiedzie nieprzerwanie Zend Studio. Nowe wcielenie tego IDE przynosi nam kilka bardzo ważnych funkcji i udogodnień. Już podczas 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 CVSa, ale również z Subversion, czyli z systemów kontroli wersji (Subversion jest nowszym i bardziej rozbudowanym narzędziem). Dodano również możliwość połączenia sFTP (secure FTP z wykorzystaniem SSL) i polepszono funkcję podpowiadania składni języka PHP. Zend podpowiada praktycznie wszystko to, co znaleźć można w manualu PHP, łącznie z nazwami własnych funkcji (wystarczy raz

stworzyć funkcję, aby potem była podpowiadana przez system), czy dokumentacją (która dostępna będzie właśnie podczas podpowiadania nazw funkcji). Drzewiasta struktura plików w oknie IDE pozwala na przeciąganie ich myszką np. z FTP do dowolnie wybranej lokalizacji. Na uwagę zasługuje też integracja Zend Studio z Web Serwisami (generowanie plikó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: komercyjna http://www.zend.com/ products/zend_studio/ professional_edition

6

www.phpsolmag.org

PHP Solutions Nr 5/2006

Aktualności
Nowości w Google Lab
eZ publish Online Editor – teraz za darmo!

P

rojekty Google ciągle zaskakują nas innowacyjnością. Przykładowo Google Notebook pozwala na wygodne zapisywanie treści (teksty, obrazki, linki) podczas przeglądania stron WWW w specjalnym miejscu, na stronie Google. W każdym momencie możemy po nie sięgać, wystarczy posiadać konto Google, np. Gmail. Dodatkowo mamy możliwość publikacji zbieranych przez nas informacji na swojej stronie WWW. Google Trends z kolei pokazuje, jak bardzo popularne są szukane informacje, np. możemy podać przyjkładową frazę XOOPS, Drupal, aby zobaczyć jakie było zainteresowanie tymi produktami na przestrzeni ostatnich miesięcy, a nawet lat (co przedstawia Rysunek). Kolejnym bardzo ciekawym pomysłem jest Google Spreadsheets, rozwiązanie które pozwala na tworzenie arkuszy kalku-

lacyjnych na WWW. Arkusze mogą być aktualizowane przez wiele osób jednocześnie. Bez problemu można też importować i eksportować dokumenty do formatów CSV i XLS. Jedna kopia dokumentu 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

Edytor WYSIWYG, za który jeszcze nie dawno trzeba było zapłacić $99, teraz dostaniemy zupełnie za darmo. System początkowo sprzedawany był jako dodatkowo płatny komponent do CMS-a eZ publish. Produkt oferuje wszystkie funkcje dobrego WYSIWYG-a. Obsługuje kilka języków w tym polski, francuski, hiszpański i włoski. Pracuje z Internet 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_online_editor

DutchPIPE

To projekt, który oferuje nowe podejście tworzenia stron dla większej grupy użytkowników. Strona staję się abstrakcyjnym światem zbudowanym na obiektach, które mogą być swobodnie przemieszczane między poszczególnymi 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 budowania różnych internetowych społeczności, dla chatroomów, gier RPG czy sklepów online. Ogranicza nas tylko wyobraźnia. Licencja: MIT http://dutchpipe.org/

poMMo – dobry email marketing za darmo

Zend Framework 0.1.4

p

oMMo to prosty i praktyczny system newslettera dla niewymagających. Instalacja jest zupełnie bezbolesna, a sam program nie przytłacza ilością opcji (jak na przykład PHPList), przez co administracja jest intuicyjna i łatwa. Narzędzie pomimo że nie skomplikowane, znakomicie sprawdza się w email marketingu. Można stworzyć formularz z kilkoma newsletterami i pytać użytkowników, które z nich chcą otrzymywać. Bez problemu zintegrujemy też poMMo ze sklepem online, żeby powiadamiać użytkowników o naszych produktach. Użytkownicy mogą sami zmieniać swój email, wypisywać się lub aktualizować preferencje. Maile wysyłane są bardzo wydajnie. System może wykorzystywać 4 użytkowników serwera SMTP i kontrolować ruch podczas wysyłki (np. ilość wysłanych ma-

Pojawiła się kolejna wersja rozwojowa Zend Frameworka. Dodano nowe komponenty oraz dokumentację w 10 językach. Na stronie projektu stworzono między innymi Community 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 wspracie dla bazy DB2. Licencja: BSD http://devzone.zend.com/node/view/id/606

PHP-QT

ili na minutę). Mamy możliwość zatrzymania, a następnie wznowienia procesu wysyłki, a po utracie połączenia i jego wznowieniu, wysyłka jest kontynuowana. Import 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ć jako 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ępnej na stronie projektu. Licencja: GPL http://www.iceburg.net/pommo/

Dostępne jest już trzecie wydanie PHP-Qt, rozszerzenia dla PHP5, które pozwala na wykorzystanie narzędzia Qt (Qt4 Framework) do tworzenia 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 GPL http://php-qt.berlios.de/

Portale rankingowe

Software-Wydawnictwo, wydawca magazynu PHP Solutions, uruchomiło pierwsze portale 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 magazynem, uruchomione zostaną kolejne projekty (m.in. poświecone systemom CMS) i to w kilku wersjach językowych. http://distrorankings.com http://PDFdev.com

PHP Solutions Nr 5/2006

www.phpsolmag.org

7

Opis CD
PHP Solutions Live

T

ę wersję PHP Solutions Live zbudowaliśmy, opierając się o dystrybucję Aurox 12 i skrypty automatycznej generacji (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ą programu yum. Teraz, oprócz kompletnego środowiska LAMP (Linux, Apache, MySQL, PHP) w systemie gotowe do użycia są środowiska programistyczne, takie jak: Eclip-

Rysunek 2. Nowy atrakcyjny wygląd
se, Nvu, Quanta, BlueFish oraz wiele innych charakterystycznych dla KDE i Linuksa narzędzi.Na dołączonej do pisma płycie znajduje się PHP Solutions Live – bootowalna dystrybucja Auroxa, zawierająca przydatne narzędzia, dokumentację, tutoriale i materiały dodatkowe do artykułów. Aby zacząć pracę z PHP Solutions Live, wystarczy uruchomić komputer z CD. Po uruchomieniu systemu zostaniemy automatycznie zalogowani w systemie, a po krótkiej chwili powinna uruchomić się przeglądarka Mozilla Firefox wraz z Menu przedstawiającym zawartość płyty związaną z bieżącym numerem.
Rysunek 1. Jeszcze więcej przydatnych narzędzi

Materiały dodatkowe

M


ateriały dodatkowe zostały umieszczone w następujących katalogach:

• •

install – hity numeru, w bieżącym numerze: PhpED - zintegrowane środowisko 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 potrzeby programowania ebook – książki i inne dokumenty w formacie PDF materialy – materiały do artykułu Typo3

PhpED jest zintegrowanym środowiskiem programowania dla języków: PHP, HTML, CSS, XML, SMARTY, XHTML i pozostałych. PhpED jest uniwersalnym edytorem zaspakajającym najbardziej wyszukane potrzeby programistów dzięki zastosowanym rozwiązaniom: zaawansowanemu edytorowi kodów, nie-

zawodnemu debagerowi dbg, produktywnemu klientowi połączeń do baz danych oraz szybkiemu i bezpiecznemu systemowi wdrożeniowemu. Oferujemy Wam wersje 120-dniową NuSphere PhpED 5.5.1. Maguma Workbench wychodzi poza typowe IDE tworząc IDP dla PHP i Python. Szybkość, solidność i modułowanie powoduję że dostępne IDE jest bardziej dostosowane do potrzeb. Maguma Workbench włącza modułową architekturę plug-in, dzięki której flagowy produkt jest szybki, elastyczny i funkcjonalny. Dzięki współpracy z użytkownikami MC, wydobywamy z naszych produktów to, co najlepsze, przyczyniając się tym samym do owocnej współpracy z klientami. Dzięki swojej elastyczności, MW wychodzi naprzeciw oczekiwaniom użytkowników, czytając niejako w ich myślach. Żeby pobrać wersję 90-dniową 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 ustawienie nowych skrótów klawiaturowych oraz reguł automatycznego poprawiania skryptów. Limbas jest aplikacją typu klient/ serwer pozwalającą nam, na szybkie tworzenie aplikacji bez potrzeby programowania. Używa tylko tabel, form i i takich modułów jak zarządzanie użytkownikami, manager plików czy interfejs SOAP. Praca z Limbas jest wybitnie bezbolesna – do tworzenia aplikacji po stronie klienta potrzebna jest tylko przeglądarka 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.

8

www.phpsolmag.org

PHP Solutions Nr 5/2006

Na CD
Przetestuj dowolne skrypty bez instalacji! 2 nowe książki elektroniczne
Cross-Platform GUI Programming with wxWidgets Subversion Version Control

HIT

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

Narzędzia w dystrybucji

Rozwiązanie z artykułu w PHP Solutions Live
Typo3

Kompletne środowisko do programowania w PHP Studio Developer

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

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

Wywiad

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

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.
Dariusz Pawłowski: Jak rozpocząłeś swoją przygodę z eZ systems? Czy mógłbyś dać kilka wskazówek dla młodych programistów PHP marzących o pracy dla takiej firmy jak Twoja? Tobias Schlitt: Hehe, to był w pewnym sensie przypadek. Kiedy przeprowadził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 projektami klientów eZ systems, ale potem Derick Rethans skierował mnie do eZ components. 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 najbardziej opiera się na tej idei, c) powinniście umieć pracować w zespole, zgranie się z całą firmą jest dla nas bardzo ważne. Cał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ługoterminowe zadania? Jak się komunikujecie na co dzień? TS: W chwili obecnej prawie wyłącznie pracuję dla eZ systems. Na początku kontynuowałem jeszcze prowadzenie mojej małej firmy konsultingowej, ale po jakimś 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. Oczywiście, mam długoterminowe zadania. Jestem członkiem teamu odpowiedzialnego za eZ components i zapowiada się na to, że tak przez najbliższy czas zostanie. Ponieważ 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 przywykł do tego rodzaju komunikacji, chociażby dzięki pracy nad różnymi projektami Open Source w przeszłości. Jakkolwiek, dwa razy do roku (czasami częściej) przyjeżdżam do Norwegii na kilka tygodni, aby porozmawiać i podyskutować na żywo o nowych zadaniach i projektach. DP: Jak rozpoczął się projekt eZ components? 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ększość jego wewnętrznych bibliotek ma dziwne API, dodatkowo bez dobrej dokumentacji. Tak więc postanowiliśmy przenieść kod wszystkich bibliotek do jednego niezależnego projektu – eZ components. W trakcie tworzenia komponentów dokonywana jest refaktoryzacja kodu i tworzona nowa dokumentacja. Projekt eZ components ma teraz na celu: a) stanowić fundament dla eZ publish,

10

www.phpsolmag.org

PHP Solutions Nr 5/2006

Wywiad

b) stanowić solidną i profesjonalną platformę dla innych. Tworzenie kodu oparte jest o testy i dokumentację. Oznacza to, że najpierw projektujemy wszystkie API, potem je dokumentujemy, następnie piszemy testy jednostkowe, a na koniec przechodzimy do realnej implementacji. W chwili obecnej mamy wersję 1.1, będącą już ulepszeniem poprzedniej. Po ukazaniu się kolejnej wersji cały zespół spotyka się. Wtedy decydujemy, które z ulepszeń zostaną zaimplementowane w następnych wersjach (właśnie takie spotkanie miało miejsce niedawno podczas konferencji eZ, gdzie dyskutowaliś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 mailingowej. Zawsze jesteśmy otwarci na nowe pomysły. DP: Czy według Ciebie Zend Framework stanowi konkurencję dla eZ components? Czy mógłbyś opowiedzieć o kilku najważniejszych funkcjach eZ components i odnieść się do Zend Framework? TS: Nie wydaje mi się, że Zend Framework stanowi konkurencję dla eZ components. Oczywiście, mają trochę podobieństw, ale tak naprawdę każde z nich działa inaczej. eZ components to nie framework, podczas gdy nazwa Zend Framework wskazuje już na framework. eZ components to kolekcja luźno powiązanych, przeważnie niezależnych komponentów do budowy aplikacji. Korzystając z typowego frameworka, w mniejszym lub większym stopniu, jesteśmy zmuszeni do budowania aplikacji z wykorzystaniem jego specyficznego podejścia itd. My dajemy Ci możliwość wyboru i sposobu użycia odpowiednich komponentów z naszej kolekcji. Możesz łączyć je z innymi rozwiązaniami takimi jak PEAR. Dla przykładu: jeśli chciałbyś użyć nasz system szablonów, ale nie podoba ci się komponent do internacjonalizacji, nie musisz stosować ich razem. Tak więc, najważniejsze cechy obu platform różnią się, gdyż w eZ components nie wymuszamy praktycznie żad-

nej integracji. Poza standardowymi komponentami, które znajdziemy też w Zend Framework i w wielu innych frameworkach, posiadamy wiele fajnych rozwiązań, które są jeszcze unikalne w świecie PHP. Jako przykład możemy podać ImageConversion component (w przypadku transformacji Thumbnail dokonywane jest skalowanie obrazków, kompresowanie, konwersja 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ą PHPowej funkcji mail() lub serwera SMTP), ale także odczytuje maile z serwerów POP / IMAP. Mamy naprawdę wiele świetnych komponentów, tak więc zachęcamy do zapoznania się z nimi. DP: Lubisz Open Source, czy preferujesz 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łówny plus oprogramowania Open Source polega na tym, że jest tworzone przez ludzi, którzy faktycznie go używają i wiedzą, jakich funkcjonalności potrzebują. Dzięki temu powstaje dużo lepsze oprogramowanie w porównaniu do wytwarzanego przez zamkniętą grupę kilku deweloperów, którzy nie są zainteresowani późniejszym wykorzystaniem 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 korzystają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ązaniach Javy czy ASP w swoich projektach? TS: Nie, właściwie to nie lubię obu tych technologii. Kodowałem dużo w Javie 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łównie do tworzenia GUI, nie dla aplikacji sieciowych. DP: Jaki będzie według Ciebie kolejny krok w rozwoju języka PHP? TS: To jest dość trudne pytanie. Obecna wersja PHP jest już prawie komplet-

na jeśli chodzi o sam język programowania. Tak naprawdę to nie widzę specjalnie nowych funkcjonalności, jakie miałyby 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 krokiem, podobnie jak usunięcie takich reliktó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ęzykiem będzie przebiegała w większym stopniu na tworzeniu fajnych rozszerzeń niż na rozwijaniu samego języka. Ale poczekajmy, jak zdecydują ludzie odpowiedzialni za rozwój PHP. DP: Jaki jest roadmap dla eZ components 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 tworzenia 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 aplikacji. Na komponentach zbudowany zostanie 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 dodając określone funkcjonalności (jak wspieranie 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ęść naszych bibliotek, eZ application server jako cała platforma lub eZ publish jako kompletny CMS. Moje osobiste plany na najbliższe 2 lata to: a) skończenie studiów, b) pomaganie eZ systems w robieniu świetnych produktów. Myślę, że oba punkty są do wykonania. DP: Dzięki za rozmowę! TS: To była przyjemność!

PHP Solutions Nr 5/2006

www.phpsolmag.org

11

Dla początkujących

Łączymy DB2 i PHP
Artur Wroński, Piotr Pietrzak

Stopień trudności: lll

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 bazodanowych. Przedstawiamy Ci DB2: solidną i rozbudowaną 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.

B
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

aza danych IBM DB2 była zawsze 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 dopracowanie 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 danych – w pełni funkcjonalnego, nadającego się do zastosowania na komputerach mających maksymalnie po dwa procesory i działających pod systemami Linux i Windows. Dla wszystkich deweloperów aplikacji PHP szczególnie interesujący jest darmowy odpowiednik DB2 Express: DB2 Express for Community, zwany również DB2 Express-C. Darmowa edycja różni się od komercyjnej głównie brakiem całodobowego wsparcia technicznego przez 7 dni w ty-

godniu. Co ważne, nie nakłada ograniczeń na rozmiar bazy danych czy liczbę obsługiwanych połączeń. Możemy również wykorzystywać DB2 ExpressC do celów komercyjnych, a także rozpowszechniać go razem z naszymi aplikacjami. Razem z bazą dostarczane są narzędzia graficzne do administracji. Najnowsza wersja DB2 zawiera także DB2 Developer Workbench – opar-

Co powinieneś wiedzieć...

Potrzebna będzie znajomość składni SQL-a i podstaw programowania w PHP (4 i 5) z wykorzystaniem baz danych. Przydatna będzie również wiedza na temat PDO i prepared statements.

Co obiecujemy...

Pokażemy, jak się łączyć z bazą danych DB2 Express oraz korzystać z nowego typu danych pozwalającego na przechowywanie dokumentów XML.

12

www.phpsolmag.org

PHP Solutions Nr 5/2006

Łączymy DB2 i PHP

Dla początkujących

tą o platformę Eclipse darmową aplikację do tworzenia procedur składowanych. Ułatwia ona tworzenie procedur składowanych i funkcji, a także pozwala na graficzne budowanie zapytań SQL oraz XQuery, przygotowywanie schematów XML, tworzenie obiektów bazodanowych, czy podgląd przykładowej zawartości bazy danych. W artykule omówimy metody łączenia się z bazami DB2 8.2 w aplikacjach PHP z oraz przybliżymy możliwości hierarchicznego silnika do obsługi danych XML, zaimplementowanego w nowej wersji DB2 9.

Szybki start

Do rozpoczęcia pracy z DB2 i PHP potrzebne 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.

Rysunek 1. Konsola konfiguracyjna Zend Core for IBM – strona powitalna
przy użyciu konsoli dostępnej pod adresem http://server:port/ZendCore/ (Rysunek 1). Interfejs konsoli administracyjnej Zend Core for IBM jest czytelny i intuicyjny (Rysunek 2). Pozwala na zmianę 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 informacji 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. kół DRDA (Distributed Relational Database Architecture). Komunikacja interfejsu bazodanowego po stronie PHP z DB2 następuje poprzez wykorzystanie innego, natywnego interfejsu CLI (Call Level Interface), który jest rozszerzeniem języka PHP napisanym w C, skompilowanym z bibliotekami obsługi DB2 i należącym do repozytorium PECL. Dla PHP 5.x istnieje kilka rozszerzeń, które pozwalają na komunikację z silnikiem DB2 oraz korzystanie z danych i obiektów. Są to: • • • Unified ODBC, PDO_ODBC, IBM_DB2.

Użycie Zend Core for IBM (http:// www.zend.com/core/ibm/) pozwala na automatyzację całego procesu instalacji i konfiguracji. Zend Core for IBM jest zintegrowanym środowiskiem do budowy aplikacji z użyciem PHP i baz DB2 lub Cloudscape (wersja opensourcowa jest udostępniana pod nazwą Apache Derby). Jest to certyfikowana przez Zend i IBM wersja PHP dla serwerów baz danych IBM, zawierająca wszelkie niezbędne dodatki i sterowniki potrzebne do komunikacji z wymienionymi bazami danych. Zend Core for IBM jest środowiskiem darmowym. Opcjonalnie można zamówić płatne wsparcie techniczne firmy Zend lub dokupić dodatkowe narzędzia do tworzenia oprogramowania 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 serwerze umieszczona zostanie również przykładowa aplikacja o nazwie DB2 Sample Application for PHP, która będzie dostępna pod adresem http://server: port/ZendCore/DB2Sample/. Natomiast zarządzanie i konfiguracja środowiska Zend Core for IBM będzie możliwe

Unified ODBC

Dostęp do danych DB2 z poziomu aplikacji PHP

Dostę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 silnikiem bazy danych wykorzystując proto-

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

PHP Solutions Nr 5/2006

www.phpsolmag.org

13

Dla początkujących

Łączymy DB2 i PHP

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

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

PDO_ODBC

tomiast zastosowanie połączenia skatalogowanego 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 bazodanowego. Używamy w tym celu dwóch funkcji: db2 _ connect() i db2 _ pconnect(). W przypadku db2 _ connect()

PDO_ODBC jest sterownikiem (ang. driver) baz zgodnych z ODBC, wykorzystywanym 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żliwia zunifikowany dostęp do różnych źródeł danych z wykorzystaniem tych samych metod. W połączeniu z oprogramowaniem 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żliwy dzięki CLI. Na Listingu 1 przedstawiamy przykład użycia PDO_ODBC do połączenia się z bazą DB2.

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

IBM_DB2

Interfejs IBM_DB2 umożliwia współpracę z bazami DB2, Cloudscape i Apache Derby. Obsługuje dwa sposoby łączenia się z bazą danych: skatalogowany (cataloged) i nieskatalogowany (uncataloged). W przypadku dostępu skatalogowanego wymagana jest obecność klienta DB2 na maszynie, na której działa serwer obsługujący PHP (jeżeli serwer ten jest zainstalowany na innym komputerze niż serwer DB2). Musimy też zdefiniować odpowiednie aliasy do serwera DB2, z którego będziemy korzystać. Możemy je utworzyć wpisując w linii poleceń klienta DB2 CLP (od Command Line Processor) komendy: catalog tcpip node oraz catalog database. Wygodniejsze jest jednak zastosowanie połączenia nieskatalogowanego, w którym wszelkie niezbędne informacje przekazujemy przy użyciu skryptu otwierającego połączenie z bazą danych. Na-

14

www.phpsolmag.org

PHP Solutions Nr 5/2006

Łączymy DB2 i PHP

Dla początkujących

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

połączenie (link) z bazą danych jest automatycznie zamykane po wykonaniu się skryptu, jeżeli natomiast stosujemy 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 korzystając z $conn _ string. Obsługę błędów zapewni nam sprawdzenie, czy istnieje zmienna $conn – jeśli tak, to połączenie się powiodło. IBM_DB2 umożliwia nie tylko wysyłanie kwerend SQL-owych, ale również pracę z dokumentami XML, proce-

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

durami składowanymi, widokami, itp. Co więcej, jest dostępny również dla wersji PHP wcześniejszych niż PHP5. Jak można się domyślić, interfejs IBM_DB2 opiera się więc na tradycyjnym programowaniu 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 systemowego DB2 (system catalog tables). Niewątpliwą nowością w IBM_DB2 jest możliwość natywnej obsługi dokumentów i danych składowanych w formacie XML.

IBM DB2 9: obsługa XML przez serwer bazodanowy

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

Pod koniec lipca tego roku (2006) pojawi się nowa wersja DB2 nosząca numer 9 i nazwę kodową Viper. Zastosowano w niej hierarchiczny silnik do obsługi danych XML (Rysunek 4), co jest zupełnie nowym podejściem w stosunku do dotychczasowych rozwiązań używanych w relacyjnych bazach danych. Warto nadmienić, iż już obecnie można pobrać darmową wersję DB2 9, która również 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, dokumenty XML są przechowywane w postaci dużych obiektów binarnych LOB (od ang. large objects) lub dekomponowane do tabel relacyjnych (ang. shred-

PHP Solutions Nr 5/2006

www.phpsolmag.org

15

Dla początkujących

Łączymy DB2 i PHP
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 )

ding). Zastosowanie obiektów LOB pozwala na szybkie wstawienie i pobranie całego dokumentu XML, ale ma poważną wadę, którą jest konieczność dynamicznego parsowania dokumentu, która bardzo negatywnie odbija się na wydajności zapytań. Dokumenty XML są w całości umieszczane w polu binarnym. Struktura dokumentu jest przetwarzana dopiero w momencie realizacji zapytania, w wyniku którego zwracane są elementy tego XML-a. Wydajniejszą metodą przeszukiwania dokumentów XML jest uprzednia dekompozycja każdego z nich do postaci relacyjnej. Jest ona przeprowadzana przez odpowiedni kod bazy danych podczas przesyłania dokumentu XML do bazy. Polega na wyodrębnianiu elementów dokumentu XML oraz ich wstawianiu do odpowiednio przygotowanych tabel relacyjnych (dokument XML zostaje pocięty na fragmenty, które stają się rekordami w bazie; dobrze oddaje to angielskie określenie shredding). Zasadniczą wadą shreddingu jest natomiast brak elastyczności, wynikający z konieczności uprzedniej znajomości struktury przesyłanego dokumentu, utworzenia tabel bazodanowych zawierających odpowiednie atrybuty oraz przypisania tych ostatnich do poszczególnych elementów XML-a. Łatwo sobie wyobrazić, jak skomplikowana staje się wtedy prosta modyfikacja struktury formularza na stronie WWW: razem z nim musimy zmieniać strukturę dokumentu XML i bazy danych – to ostatnie jest bardzo uciążliwym i czasochłonnym procesem. Ograniczeń obu metod (obiektów binarnych oraz shreddingu) możemy uniknąć korzystając z dedykowanych baz XML-owych. W DB2 9 zaimplementowano silnik pureXML®, który jest oparty o hierarchiczną bazę danych i przechowuje dane XML w wewnętrznym formacie odpowiadającym strukturze dokumentu XML. Architekci DB2 położyli bardzo duży nacisk nie tylko na wydajną pracę bazy XMLowej, ale także na jej integrację z silnikiem relacyjnym. Język zapytań dokumentów XML XQuery/XPath jest obsługiwany na równi z językiem SQL, a kompilator kwerend bazodanowych DB2 pozwala na łączenie ze sobą zapytań do danych relacyjnych z tymi kierowanymi do danych XML.

Atrybut pid byłby identyfikatorem produktu, podczas gdy info zawierałby szczegółowy opis produktu, będący zestawem danych specjalnego typu XML . Opisy produktów dla omawianego systemu byłyby dostarczane w postaci dokumentów XML. Wstawienie opisu nowego produktu z aplikacji PHP mogłoby przebiegać 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 relacyjnych. Ponieważ identyfikator produktu jest przechowywany w oddzielnym polu tej tabeli (pid), musimy go wyodrębnić z dokumentu XML. Możemy to zrobić wykorzystując interfejs PHP SimpleXml lub wykorzystując mechanizmy DB2, takie jak funkcja XMLTABLE , polecenie DECOMPOSE XML DOCUMENT czy procedura xdbDecompXML . Zwróćmy uwagę na użycie techniki prepared statements (db2 _ prepare() przy wstawianiu danych do ba) zy: występujące we wzorcu znaki zapytania zostaną zastąpione przez id produktu ($prodid) oraz zserializowaną zawartość odczytanego pliku XML ($filecontents). W bazie danych dokument ten będzie miał postać hierarchicznego drzewa odzwierciedlającego strukturę XML.

Listing 6. Przetwarzanie listy pobranej z dokumentu XML na format HTML (XQuery)
$xquery =for $i in $t/product let $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;

16

www.phpsolmag.org

PHP Solutions Nr 5/2006

Łączymy DB2 i PHP

Dla początkujących

PHP Solutions Nr 5/2006

www.phpsolmag.org

17

Dla początkujących

Łączymy DB2 i PHP
re można wybrać odpowiednim wyrażeniem XPath, np.:
CREATE UNIQUE INDEX prod_pid ON product(description) GENERATE KEY USING XMLPATTERN /product/@pid' AS SQL VARCHAR(10)

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

Podsumowanie

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

Korzystamy z XQuery

Do pobrania i prezentacji kategorii każdego produktu, przechowywanej jako element dokumentu XML, możemy użyć prostego 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 XQuery – 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 dokumentu XML, ale także na przetwarzanie jej w klauzuli return do formatu HTML na etapie zwracania wyników (zob. Listing 6).

Dla wygody możemy umieścić zapytanie XQuery w procedurze składowanej i przekazać jego wynik w postaci zbioru wynikowego. Procedura powinna być zadeklarowana z klauzulą DYNAMIC RESULT SETS, która oznacza liczbę zwracanych zbiorów wynikowych. By zwrócić zbiór wynikowy, kursor c _ cur musi zostać zadeklarowany z klauzulą WITH RETURN (Listing 7). Jedynym, co nam pozostało do zrobienia w aplikacji PHP jest uruchomienie procedury składowanej przy użyciu instrukcji CALL oraz odczytanie wyników naszej kwerendy (Listing 8). Po raz kolejny, zwróćmy uwagę na użycie techniki prepared statements.

System bazodanowy DB2 daje ogromne możliwości, z których najważniejsze to te wprowadzone w wersji 9, które dotyczą przechowywania danych w postaci dokumentów XML. Korzystając z nich możemy znacznie uprościć i przyspieszyć tworzenie struktur danych oraz uczynić ich przetwarzanie w naszych aplikacjach elastycznym. Będziemy o nich jeszcze pisali w następnych artykułach. Funkcjonalność i solidność poprzedniej, całkowicie darmowej wersji DB2 jest również warta uwagi. Biorąc pod uwagę te cechy oraz łatwość łączenia DB2 z PHP, warto się poważnie zastanowić nad jej zastosowaniem w swoich projektach. n

O autorach
Artur Wroński: Jest pracownikiem działu oprogramowania IBM i jest odpowiedzialny za rozwiązania z zakresu baz danych. Od 12 lat specjalizuje się w silnikach baz danych, migracjach oraz narzędziach dla hurtowni danych. Kontakt: [email protected] Piotr Pietrzak: Jest architektem systemów w dziale Systems & Technology Group w polskim oddziale firmy IBM. Od ponad 11 lat związany jest z branżą IT. Jest autorem wielu technicznych publikacji dotyczących architektury sprzętu oraz programowania. Kontakt: [email protected]

Indeksy w XML-u

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

18

www.phpsolmag.org

PHP Solutions Nr 5/2006

Wydania specjalne

SOFTWARE 2.0 EXTRA, SDJ EXTRA i LINUX+ EXTRA!

Dla prenumeratorów Linux+, Linux+ Extra! oraz SDJ trzy dowolne archiwalne numery SDJ Extra bądź Linux+ Extra tylko za
Oferta ważna do 31 maja 2006 r. lub do wyczerpania zapasów

25 PLN

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

ASP.NET STARTER KIT

SDJ EXTRA

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.

29,80 zł

Pismo przygotowane na premierę dwóch grup najważniejszych produktów Microsoft. Omawia nowości a zatem TSQl, SQL service Broker, XML oraz aspekty bezpieczeństwa w SQL Server 2005. Dodatkowo zawiera krótki kurs Visual Studio 2005. Pismo zawiera 2 płyty DVD.

PHP STARTER KIT
PHP Starter Kit krok po kroku wprowadzi Cię w świat PHP. Dowiesz się, jak skonfigurować pełne środowisko do tworzenia aplikacji w PHP, napisać pierwszy program, stworzyć portal i sklep internetowy. Na DVD: pełny zestaw narzędzi do tworzenia aplikacji WWW oraz bootowalna dystrybucja Linuksa z kompletnym środowiskiem do testowania i tworzenia programów w PHP.

PROGRAMOWANIE W JAVIE

29,80 zł

29,80 zł

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

ORACLE

DEBIAN 3.1

29,80 zł

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

37,00 zł

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

AUROX 11.1

FREEBSD 6.0

35,00 zł

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

35,00 zł

FreeBSD to system, który dzięki swojemu bezpieczeństwu cieszy się ogromnym powodzeniem wśród administratorów sieci, a dzięki prostej instalacji oprogramowania, zjednał sobie także sympatię użytkowników domowych. Sześć płyt CD + liczne dodatki.

GENTOO LINUX 2005.1

MANDRIVA LINUX

35,00 zł

Gentoo Linux to najszybsza ze wszystkich dostę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.

35,00 zł

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

Bezpieczeństwo

Zaawansowane ataki SQL Injection
Mike Shema

Stopień trudności: lll

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.

K
W SIECI
• http://www.spidynamics.com/ papers/SQLInjectionWhiteP aper.pdf – bardzo dobry artykuł o atakach SQL Injection, • http://msdn.microsoft.com/ msdnmag/issues/04/09/ SQLInjection/ – Stop SQL Injection Attacks Before They Stop You (strona MSDN), • http://www.sqlsecurity.com/ – wszystko o słabych punktach SQL, • http://www.imperva.com/ application_defense_center/ white_papers/sql_injection_ signatures_evasion.html – automatyczne, samoprogramujące ataki na SQL.

ażdy administrator serwera WWW powinien znać techniki, które mogą być wykorzystane do identyfikacji podatności na SQL Injection (patrz Artykuł Tobiasa Glemsera Ataki SQL Injection na PHP i MySQL, hakin9 2/2005) i być świadomy ryzyka, jakie niosą. Podstawowa metodologia SQL Injection polega na określeniu wektora ataku, a następnie jego wykorzystaniu za pomocą zmodyfikowanych 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 Injection może nie wykraczać poza możliwość wygenerowania błędów składni, takich jak próby przekształcenia łańcuchów (strings) w wartości numeryczne. W innych sytuacjach wektor umożliwia atakującemu pełne przejęcie kontroli nad informacjami z bazy danych. Chociaż nasze przykłady odnoszą się

do baz MySQL, techniki da się zastosować na każdej platformie bazodanowej – w większości przypadków bez modyfikacji. Techniki te w swojej istocie są wymierzone w język SQL. Określone rozszerzenia baz danych zwyczajnie ułatwiają zastosowanie omawianych technik.

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ę zapytań SQL, dowiesz się jak przeprowadzane są ataki na składnię języka SQL, poznasz również metody agresji na logikę SQL, nauczysz się kilku dodatkowych sztuczek SQL Injection oraz poznasz ogólne zasady obrony przed atakami SQL Injection.

20

www.phpsolmag.org

PHP Solutions Nr 5/2006

Zaawansowane SQL Injection

Bezpieczeństwo

Dla odświeżenia pamięci

wodu źle sformatowanej składni (niezamknięty pojedynczy cudzysłów): • •
SELECT ''';, *;, ;, foo FROM bar WHERE a =

Testy 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 – wstawianie typowych znaków SQL mające na celu wygenerowanie błędów ułatwiających identyfikację potencjalnego wektora 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 poprzez manipulację konstruktami języka 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.

SELECT foo FROM bar WHERE a = '/ SELECT foo FROM bar WHERE a = ';-SELECT foo FROM bar WHERE a = '#;.

• •

ASCII. Atakujący może wstrzyknąć cudzysł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ązku z tym monitorowanie danych wejściowych pod kątem cudzysłowów nie zarejestruje ani nie zablokuje ataku.



Chociaż apostrof (ASCII 0x27) jest najpopularniejszym 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 --.

Znaczenie zmiennych



Błę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ójkowe, ósemkowe, szesnastkowe lub notacja naukowa.

Techniki te mogą być łączone w celu ocenienia aplikacji webowej i określenia jej podatności na ataki SQL Injection. W następnych sekcjach przykładowe ładunki (ang. payload) SQL Injection będą prezentowane bez całego adresu (URL). Ułatwi to zrozumienie technik bez niewygodnych parametrów i tekstu. Innym powodem jest to, że wstrzyknię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ść podatnego parametru swoim ładunkiem: http: //witryna/page.cgi?a=<ładunek SQL Injection>&b=bar. Dla dalszego przypomnienia: trzeba pamiętać o kodowaniu spacji i innych znaków w ładunku, aby nie zakłócały one składni adresu.

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 wykorzystaniem podatności, ale zwykle są niewystarczające. Mogą po prostu zaciemniać bardziej podstawowe problemy z architekturą połączeń aplikacji z bazą danych.

Cudzysłowy kontra ukośniki

Podczas tworzenia silnych filtrów poprawności programiści PHP stają przed kilkoma wyzwaniami i potencjalnie mylącymi zaleceniami. Funkcja magic_quotes() automatycznie kasuje (ang. escape) wszystkie apostrofy znakiem odwróconego ukośnika (\). Jeśli jednak ta możliwość jest połączona z wywołaniem funkcji strip_slashes(), znaki kasowania zostaną usunięte: • •
SELECT foo FROM bar WHERE a = '\ '';



Ataki na składnię zapytań

– apostrof wykasowany,

SELECT foo FROM bar WHERE a = ''';

Pojedynczy cudzysłów, zwany apostrofem (') – choć jest zdecydowanie najpopularniejszym znakiem stosowanym do identyfikacji wektorów SQL Injection – nie jest w żadnym wypadku jedynym znakiem potrzebnym do wygenerowania błędu bazy danych. Ta technika obejmuje najbardziej podstawowe testy potencjalnych luk poprzez użycie metaznaków lub znaków formatujących języka MySQL do zakłócenia składni oryginalnego zapytania. Na przykład następujące wyrażenia nie mogą być przetworzone do poprawnej postaci z po-

– odwrócony ukośnik wycięty, zapytanie ź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 atakowaniu zapytania SQL. Intruz może łączyć funkcje SQL, by generować błędy składni zapytania. Do wywołania błędów można również użyć wbudowanych funkcji SQL. Funkcja CHAR() drukuje odpowiednik argumentu w

Te ataki liczbowe często się udają (generują błędy), ponieważ zmienne używane do śledzenia tych wartości nie mają ściśle określonego typu. W języku PHP typem parametru dla wszystkich zmiennych $_REQUEST jest łańcuch (string). Oznacza to, że – mimo możliwości wykonywania operacji na zmiennych ($a = 1; $a++) – typ zmiennej powinien być traktowany jako łańcuch liczbowy. Zmienna może być nawet po cichu zmieniona z liczby na łańcuch liczbowy, przy którym wartość 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 wartość prawdziwą (bo jest to liczba), ale is_numeric("1e309") zwróci fałsz – nie jest to ani liczba, ani łańcuch liczbowy, ponieważ znajduje się poza zakresem typu PHP double float. Zmienna musi być przestawiona w tryb numeryczny bezpośred-

PHP Solutions Nr 5/2006

www.phpsolmag.org

21

Bezpieczeństwo

Zaawansowane SQL Injection
zmieniają. Oznacza to także, że filtrowanie oparte na składni – takie jak w firewallach warstwy aplikacji – musi być bardzo dokładne, aby zabezpieczyć przed tego typu atakami. W rzeczywistości kombinacja 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ęzyka

W języku SQL zdanie Szekspira o różach wyglądałoby zupełnie niepoetycko:
SELECT name FROM roses WHERE scent='sweet';

Rysunek 1. Oryginalny przykładowy URL

Rysunek 2. Zmodyfikowany łańcuch z adresem

nio za pomocą funkcji settype(), ale uwaga – 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 synonimy

Solidne filtry poprawności danych wejściowych mogą być skuteczną obroną przed tymi technikami, ale niestety są niewystarczające. Błędy baz danych i inne wyjątki powinno się wyłapywać i zapobiegać ich wysyłaniu do przeglądarki. Szczegółowe informacje o błędach zwykle dostarczają cennych danych złośliwym użytkownikom atakującym bazę. Jak zobaczymy

za chwilę, filtry poprawności danych mogą być nieodpowiednie. Na przykład przekonaliśmy się już, że wartość 1e309 nie jest liczbą (dla większości języków i baz danych SQL) i wywoła błąd w słabo zabezpieczonych 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ć jako x'27. Koncentrujemy się na łańcuchu CHAR(0x27), by uniknąć gołych cudzysłowów, ale szczegóły każdego testu się

Czy róża zostanie nazwana butem, trzmielem, czy zegarkiem, jej atrybut pięknego zapachu pozostanie bez zmian. SQL posiada bogaty zestaw funkcji, które mogą być użyte do tworzenia semantycznych ekwiwalentów zapytań, tekstowo prezentujących się zupełnie inaczej. Ta zdolność pozwala atakującemu zidentyfikować i wykorzystać podatność na wstrzyknięcia, nawet gdy serwer nie pokazuje informacji o błędach czy innych podobnych danych. Podczas gdy psucie zapytań jest użyteczne przy znajdowaniu potencjalnych podatności, warto także atakować zapytania za pomocą semantyki wbudowanych funkcji SQL. Z tego powodu, zamiast atakować interpreter języka aplikacji (PHP, JSP czy innych), agresja koncentruje się na samym języku SQL. Ma to dodatkowe zalety – nie tylko identyfikuje wektory ataku, lecz także dostarcza więcej informacji o filtrach poprawności wykorzystywanych przez aplikację. Innym skutkiem ubocznym tej techniki jest możliwość przeprowadzania ślepych ataków SQL Injection lub ataków, które nie wymagają generowania błędów do identyfikacji lub wykorzystania.

Numeryczne typy danych

Numeryczne typy danych to pierwsi kandydaci do przetestowania tej techniki. Rysunek 1 przedstawia oryginalny przykładowy URL, zaś Rysunki 2 i 3 zawierają zmodyfikowane adresy. Korzystamy ze starej, niezabezpieczonej wersji sklepu internetowego FreznoShop – wersje nowsze niż 1.4 są odporne na te błędy. Rozważmy następującą listę par nazwa/wartość:

22

www.phpsolmag.org

PHP Solutions Nr 5/2006

Zaawansowane SQL Injection
• • • •
rowid = 111,

Bezpieczeństwo

rowid = 0x6f, rowid = 0157

(reprezentacja ósem-

kowa),

• • • •

(w praktyce używajmy ponieważ znak + w adresie oznacza spację), rowid = 112–1, rowid = MOD(111,112), rowid = REPEAT(1,3), rowid = COALESCE(NULL,NULL,111).
rowid = 110+1 110%2b1,

Z punktu widzenia bazy danych, każde z tych żądań daje w rezultacie taką samą wartość: 111. Zauważmy też, że żadne z nich nie korzysta z apostrofu. Pierwsze trzy wyglądają na łańcuchy numeryczne lub alfanumeryczne, następne dwa mają pozornie nieszkodliwe znaki dla symboli dodawania i odejmowania, zaś ostatnie trzy wartości zawierają nawiasy i przecinki. Jeśli filtry poprawności koncentrowałyby się na wycinaniu apostrofów, takie zabezpieczenie aplikacji nie miałoby żadnego efektu.

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

danych, śmiało możemy wziąć na muszkę składnię funkcji SQL. Na przykład: • • •
BIN(-1), LIMIT

Surowe parametry

Ta technika, używająca semantycznych sobowtórów (ang. doppelganger) pozwala użytkownikowi na identyfikację wektoró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 zapytaniu SQL. Rozważmy na przykład takie zapytanie o numer wiersza:
SELECT foo FROM table WHERE rowid = 110+1;

a (przydatne, bo nie wymaga nawiasów), MOD(0,a).

Znów świadomie postanowiliśmy unikać cudzysłowów, ponieważ zwykle wszczynają 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 nawiasów i przecinków. Poniższe przykłady są identyczne znaczeniowo: • • • •
page.cgi?category=music,

Oczywiście wartości liczbowe również powinny być przetestowane pod kątem warunków granicznych, jak wspomniano w poprzedniej sekcji.

page.cgi?category=REVERSE(cisum), page.cgi?category=GREATEST(0x61,0x 6d75736963), page.cgi?category=LEAST(0x6d757369 63,0x6e75736963).

Znaki przedwczesnego zakończenia

Baza danych oblicza 110+1 = 111 przed przetworzeniem reszty zapytania, zgodnie ze swoim porządkiem operacji. Daje to taki sam rezultat, jak oryginalne zapytanie:
SELECT foo FROM table WHERE rowid = 111;

Ta technika służy do tworzenia indywidualnych zapytań SQL. Takie zapytania często nie potrzebują znaków cudzysłowu, ale zwykle wymagają znaków przedwczesnego 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()/*;

Obrona

Zanim wyjaśnimy jak rozbudować ten atak w celu otrzymania dowolnych danych, zbadajmy najpierw kilka innych przypadków, które można wykorzystać do wygenerowania 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

jest dobrym przykładem. Łańcuchy stanowią większe wyzwanie, ponieważ tutaj istnieje mniej funkcji języka SQL dostarczających pomocnych sobowtórów semantycznych. W takich przypadkach 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).

Najlepszą obroną przed tymi atakami jest korzystanie z filtrów poprawności danych wejściowych i używanie ścisłych typów danych przy przypisywaniu parametrom zapytań danych dostarczanych przez użytkownika. Mimo że 0x27 jest poprawną wartością szesnastkową, aplikacja powinna zabraniać jej stosowania (lub cicho przemienić w dziesiętne 27) – surowa wartość zawiera znak nienumeryczny. Na tej samej zasadzie wartość dziesiętna 0157 powinna być albo odrzucona z powodu poprzedzającego liczbę zera, albo obcięta tak, by stała się wartością dziesiętną 157, co da zupełnie inny numer wiersza. Wreszcie, programiści powinni znać alternatywne systemy liczbowe i wiedzieć, w jaki sposób są interpretowane zarówno w języku aplikacji, jak i w bazie danych. Traktowanie wszystkich danych użytkownika jako łańcuchy jest bardzo łatwe,

PHP Solutions Nr 5/2006

www.phpsolmag.org

23

Bezpieczeństwo

Zaawansowane SQL Injection
• wyrażenie UNION powinno kończyć zapytanie, aby składnia była poprawna – dodatkowa logika musi być usunięta, wyrażenia UNION wymagają odpowiedniej liczby kolumn w każdym wyrażeniu SELECT.



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

Liczenie kolumn

Rysunek 4. Udany atak UNION SELECT

ale jeśli dane mają być umieszczone w zapytaniu, powinny być bezpośrednio przypisane (rzutowane) do odpowiedniego typu danych. W przypadku języków interpretowanych 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ć traktowane ostrożnie i sprawdzane pod kątem wyjątków – uczulamy na ataki z wykorzystaniem łańcuchów formatujących (patrz Artykuł Nadużycia z wykorzystaniem ciągów formatujących, hakin9 5/2004).

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 SELECT

Ataki na logikę zapytania

Manipulacje składnią zapytań są użyteczne przy identyfikacji podatności na SQL Injection, ale tylko wskazują na istnienie problemu. Prawdziwym ryzykiem związanym z atakami SQL Injection jest dostęp do dowolnych danych. MySQL obsługuje charakterystyczne makro komentarzowe, które wyświetla wersję bazy danych: /*!<wersja> */, gdzie <wersja> jest pięciocyfrową wartością reprezentującą wersję MySQL. Na przykład wersja 3.23.02 wygląda jak 32302, wersja 4.1.10 to 40110, zaś 5.0.3 wygląda jak 50003. Najszybszym sposobem sprawdzenia możliwości wbudowanych ataków na MySQL jest połączenie komentarza z wyrażeniem skutkującym błędem zapytania: • •
/*!32302+AND+0+*/,

Po identyfikacji parametru jako wektora dla ataków SQL Injection, następnym krokiem jest określenie tego, na co podatna jest baza danych. Osiągamy to przez manipulowanie logiką oryginalnego zapytania. 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 uwagę wcześniejsze SELECT. Najszybszą techniką jest użycie słowa kluczowego UNION. Wyrażenie UNION łączy wiele wyrażeń SELECT i jest obsługiwane przez większość systemów bazodanowych. Jego podstawowa forma ma postać SELECT
foo FROM bar WHERE a=b UNION SELECT foo2 FROM bar2 WHERE c=d;.

Drugi problem także nie jest wielkim wyzwaniem, ale wymaga kilku powtarzalnych kroków. Wstrzyknięte wyrażenie UNION będzie miało albo za mało, albo zbyt dużo kolumn, nam zaś potrzeba poprawnej liczby. Jeśli mamy możliwość obserwacji komunikatów o błędach generowanych przez bazę, zobaczymy coś w rodzaju The used SELECT statements have a different number of columns (Użyte wyrażenia SELECT mają różną liczbę kolumn). Niedoszacowanie kolumn można poprawić 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 mysql.user; user,user,user,user FROM

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 pomocą funkcji SELECT USER(). Żądanie mogłoby wyglądać następująco:
SELECT text FROM articles WHERE id=0 UNION SELECT USER();

/*!32302+AND+0+*//*

(może być konieczne zakończenie zapytania).

Przy stosowaniu wyrażeń UNION w atakach SQL Injection pojawia się kilka problemów:

Każde z tych zapytań pobiera nazwę (lub nazwy) użytkownika z domyślnej tablicy mysql.user. W każdym z powyższych przykładów liczba kolumn wzrasta z 1 do 4. W praktyce lepiej powtórzyć nazwę kolumny aby upewnić się, że wartość jest wyświetlana przez aplikację. Pierwsze miejsce na kolumnę działa, ale trudno powiedzieć, którą kolumnę wyświetli aplikacja. Przeszacowania kolumn mogą być poprawione za pomocą wyrażenia CONCAT. Przeszacowania pojawiają się, gdy pierwsze wyrażenie SELECT spodziewa się mniejszej liczby kolumn niż zawarto w zapytaniu. Wyrażenie CONCAT rozwiązuje ten problem poprzez połączenie każdej kolumny w pojedynczy łańcuch – większa

24

www.phpsolmag.org

PHP Solutions Nr 5/2006

Zaawansowane SQL Injection

Bezpieczeństwo

Dodatkowe sztuczki SQL

Naszym 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 wykorzystanie 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 wykorzystać 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().

dur – oddzielają one logikę zapytań od danych. W konsekwencji ataki SQL Injection będą mogły uszkodzić oryginalne zapytanie SQL. Potencjalną wadą takich zabezpieczeń jest to, że wymagają one dodatkowych zmian w aplikacji. To może prowadzić do spadku wydajności, jednak z drugiej strony negatywny wpływ może być minimalny, a wzrost bezpieczeństwa tak czy inaczej będzie ogromny.

Separowanie

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;

Istnieje o wiele łatwiejsza metoda, polegająca na wykorzystaniu offsetów w wyrażeniu LIMIT. Można oczywiście ograniczyć wyniki do jednego wiersza za pomocą LIMIT 1, jednak wyrażenie umożliwia też sterowanie wyświetlanymi wierszami przez dodanie opcjonalnego offsetu zaczynającego się od 0. Na przykład: •
SELECT foo FROM table WHERE a=b UNION (SELECT CONCAT(*) FROM mysql. user LIMIT 0,1);,

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;



SELECT foo FROM table WHERE a=b UNION (SELECT CONCAT(*) FROM mysql. user LIMIT 1,1);,



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

SELECT foo FROM table WHERE a=b UNION (SELECT CONCAT(*) FROM mysql. user LIMIT 2,1);.

Filtry 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 separacji między logiką zapytań a danymi. Logika jest definiowana przez programistę i powinna pozostać statyczna, zaś dane podaje użytkownik. Kiedy dane i logika się mieszają, tak jak w przypadku łączenia łańcuchów przy tworzeniu zapytań, dane dostarczane przez użytkownika mogą wpływać na logikę zapytania. Wiąże się z tym większe ryzyko niż to związane z poprawnością danych wejściowych, ponieważ zmodyfikowane zapytanie umożliwia dostęp do danych zawartych w bazie. Złośliwie umieszczony w zapisanej procedurze znak formatowania może skutkować błędem bazy, a nie wyświetleniem właściwych danych. Nie chodzi o to, że sprawdzanie poprawności danych nie jest istotne – chodzi o to, że każdy system obrony 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 testy polegają wyłącznie na wstrzyknięciach opartych na apostrofach, będą bezużyteczne – do ataków SQL Injection wykorzystuje się wiele różnych znaków. n

Wiersze na muszce

Jeśli liczba kolumn w wyrażeniu już się zgadza, następnym krokiem jest zwykle określenie wiersza tablicy, który chcemy pobrać. Kiedy zapytanie zwraca wiele 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 wiersza, ale tylko wtedy, gdy znamy wcześniej ogólną strukturę (nazwy kolumn) tablicy.

Możemy tak postępować do momentu, 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 osadzone w zapytaniu.

O autorze
Mike Shema pracuje jako dyrektor ds. bezpieczeństwa w zajmującej się bezpieczeństwem aplikacji webowych firmie NT Objectives. Kontakt z autorem: [email protected]

Obrona przez wyrażenia

Efektywne sposoby obrony przed omawianymi technikami to wykorzystanie spreparowanych wyrażeń lub zapisanych proce-

PHP Solutions Nr 5/2006

www.phpsolmag.org

25

www.buyitpress.com
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!

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

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

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

Ilość numerów

Ilość zamawianych prenumerat

Od numeru pisma lub miesiąca

Opłata w zł z VAT

12 6 12 12 8 6 6 6 4

250/1801 150/1352 199/1791 199/1791 232/1982 135 135 140 1193

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

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

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

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

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

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

Magazyn z najpopularniejszym polskim Linuksem

Suma

1 2 3

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

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

www.buyitpress.com

Narzędzia

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

Stopień trudności: lll

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.

T
W SIECI
• http://www.typo3.org – główna strona TYPO3 • http://wiki.typo3.org – oficjalna encyklopedia TYPO3 • http://web.inselhof-triemli.ch/ index.php?id=98&type=3 – wprowadzenie do Typo3 Testsite • http://opensourcecms.com – witryna poświęcona CMSom

YPO3 jest potężnym i elastycznym 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 CMSem wielojęzycznym (48 wersji językowych w maju 2006) i wielowitrynowym (potrafiącym obsłużyć wiele witryn z jednej instalacji). TYPO3 daje użytkownikowi ogromną swobodę tworzenia strony, a jego architektura jest zorientowana na strony (ang. pagecentric paradigm). Oferuje rozwinięte możliwości zarządzania grupami oraz dostosowywania funkcjonalności pod kątem autorów i gości. Posiada również wbudowany system wersjonowania oraz funkcje przekierowywania URL-i (ang. URL rewriting). Jedną z największych zalet TYPO3 jest system rozszerzeń, który pozwala dodawać nowe funkcje bez modyfikacji rdzenia systemu. Obecnie dostępnych jest ponad 1400 rozszerzeń (maj 2006). Dodatkowo, twórcy TYPO3 udostępniają framework

pozwalający na łatwe dodawanie kolejnych możliwości. Historia TYPO3 zaczęła się od wspomagania rozwoju portali korporacyjnych w Internecie (głównie B2C, ang. Business-to-Customer, czyli przeznaczonych do kontaktu między firmą a jej klientami). Dzięki systemowi rozszerzeń i wspomnianemu frameworkowi, system może również obsługiwać witryny B2B, intranety, extranety, a także wspomagać tworzenie pojedynczych aplikacji.

Co powinieneś wiedzieć...

Przydatna będzie podstawowa znajomość 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 dyskusyjne i moduł newsów.

28

www.phpsolmag.org

PHP Solutions Nr 5/2006

Narzędzia

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

Łączymy się z BackEndem

Podobnie jak większość systemów CMS, TYPO3 został podzielony na FrontOffice (witryna widoczna dla użytkownika) i BackOffice (interfejs administracyjny). W TYPO3 witryna jest nosi nazwę FrontEnd (FE), a interfejs administracyjny, w którym modyfikujemy zawartość – BackEnd (BE). Między tymi warstwami istnieje wyraźny podział, gdyż dane użytkowników FE i BE znajdują się w osobnych tabelach 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 konta redactor obowiązuje jedynie dla witryny demonstracyjnej i nie działa w przypadku instalacji tradycyjnej; w tej drugiej sytuacji korzystaj z konta administratora (login: admin, hasło: password; w zastosowaniach produkcyjnych należy zmienić hasło). Interfejs BE jest podzielony na 3 części umieszczone w 2 ramkach (zob. Rysunek 1). Po lewej mamy główne menu, z którego wybieramy czynność do przeprowadzenia (edycja treści, zmiana ustawień użytkownika, zarządzanie plikami, itd.). W środkowej ramce (jeżeli jest ona wyświetlana), pokaże się rozwijalne drzewo przedstawiające hierarchię zawartości naszej witryny. Wreszcie, w prawej części ekranu ujrzymy efekty działań wybranych w części środkowej. Wrócimy do tego później – zacznijmy od zmiany języka używanego w BackEnd.

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

ich ustawień. Na górze strony z ustawieniami 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 Language Pack (możemy to zrobić korzystając z opcji Ext Manager w sekcji User tego samego menu). Na stronie konfiguracyjnej możemy też zmienić ustawienia związane z rekurencyjnym kopiowaniem i kasowaniem stron (określa ono, do którego poziomu mają być kopiowane lub kasowane 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 Configuration, po czym się wylogować i zalogować ponownie: to wszystko.

Zarządzanie treścią

Następnie, nadal zalogowani jako redactor lub admin, dodamy nową stronę do witryny, po czym umieścimy w niej zawartość. Później utworzymy 3 kolejne strony i zainstalujemy aplikację (forum dyskusyjne) na jednej z nich.

Modyfikacja treści

Zmiana języka interfejsu BackEnd

W menu po lewej stronie ekranu wybieramy opcję User–>Setup, aby dojść do swo-

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

rą chcemy wyedytować (w naszym przypadku Test). Po prawej stronie ekranu pokażą się przyciski i menu ułatwaiające nam wykonywanie rozmaitych operacji na wybranej stronie: oglądanie historii jej zmian, edycja właściwości, przenoszenie strony, dodawanie nowej strony lub jej zawartości oraz przeszukiwanie strony. Wyedytujmy teraz treść strony: w tym celu musimy po prostu kliknąć przykładowy 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ą dostę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 window: 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 edytora 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 numerowanymi i wypunktowanymi, cofanie i przywracanie przeprowadzonych działań, wyszukiwanie i zastępowanie tekstu, itd. Kli-

PHP Solutions Nr 5/2006

www.phpsolmag.org

29

Narzędzia
kając wyglądający jak <> przycisk Toggle HTML Source możemy również zobaczyć wersję HTML (źródło) edytowanego tekstu, a wciskając przycisk Remove formatting (wyglądający jak połowa litery A) usuniemy formatowanie tekstu (np. wklejonego z aplikacji biurowej do okienka naszego edytora). Pobawmy się trochę tym edytorem i wprowadźmy jakiś tekst. Jeśli potrzebujemy więcej miejsca do edycji, możemy kliknąć 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 znajdujących się na szczycie strony naszego edytora ikon symbolizujących dyskietki.

Wymagania

TYPO3 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 wyedytuj plik php.ini (/etc/php4/apache/php.ini) i ustaw zmienną memory limit na 32M (memory_limit = 32M) zamiast na 8M.

Instalacja tradycyjna

Dodawanie obrazków do tekstu

Aby 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 dummyx.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 wybrać istniejącą, jeśli chcesz ją nadpisać) i utworzyć tę bazę. To wszystko, co jest wymagane: możesz przejść do BackEndu lub FrontEndu witryny, choć warto najpierw przeczytać uwagi instalatora dotyczące bezpieczeństwa. Aby przejść do BackEndu, wejdź do katalogu 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.

Możemy również wstawiać obrazy: tuż pod obszarem edycyjnym mamy narzędzie pozwalające nam wybierać i uploadować pliki graficzne. Jest tam również lista umożliwiają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 przestrzeni plikowej konta redactor lub admin. To ostatnie wykonamy klikając ikonę symbolizują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 widzimy General Options – ustawienia dotyczące całej zawartości i pozwalające na ustawianie dat Start i Stop, określających okres, w którym zdefiniowana zawartość ma być widoczna. Możemy też zdecydować, czy dana strona będzie mogła zawierać podstrony i ustawiać jej widoczność dla zalogowanych i niezalogowanych użytkowników. Opcje te obowiązują dla wszystkich typów zawartości.

Korzystamy z witryny demonstracyjnej

Zamiast instalować TYPO3 w sposób tradycyjny, możesz użyć specjalnej witryny demonstracyjnej, 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 uruchomione. Tworzymy więc folder dla TYPO3 w przestrzeni serwera WWW (w Linuksie jako root wpisujemy polecenie mkdir /var/www/t3test i przechodzimy do tego katalogu komendą 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ądarce WWW przechodzimy pod adres http://localhost/3test/install/index.php. Uruchomi się narzę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 mysql database table, decydujemy się na import bazy ze skryptu database_demo.sql. Po zakończeniu instalacji można zacząć korzystać z witryny: jej FrontEnd znajduje się pod adresem http://localhost/t3test/index.php, a BackEnd pod http://localhost/t3test/typo3/.

Uwaga na temat specyfiki dema

W wersji demonstracyjnej wykorzystaliśmy szablon strony WWW o nazwie sunflower pobrany z witryny oswd.org. Znajduje się on w domenie publicznej (ang. public domain). 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 index.html umieściliśmy znaczniki (tagi) wymagane przez TYPO3. Poprawilismy też rozszerzenie 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.

Wstawianie hiperlinków

Aby wstawić hiperlinka w tekście, musimy zaznaczyć w edytorze słowo lub obraz i kliknąć przycisk Insert Web Link (ikona 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ć korzystając z drzewa, do pliku w przestrzeni plikowej TYPO3 lub do zewnętrznego URL-a czy adresu emailowego: wszystko, co musimy zrobić, to kliknięcie w odpowiednią zakładkę i wprowadzenie wymaganych danych. Domyślnie linki zewnętrzne ukazują się w osobnym okienku, a wewnętrzne w tym samym oknie. Ważne jest to, że możemy ukryć adresy emailowe przed robotami spamerskimi.

Po kliknięciu lewym przyciskiem myszy na ikonie zawartości (pierwsza ikona w lewym górnym rogu ramki edycyjnej) ukaże sie menu kontekstowe pozwalające na dokonanie wybranych operacji na zawartości, takich jak skopiowanie lub wycięcie bloku. Jest to prosty sposób na przenoszenie 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ć

30

www.phpsolmag.org

PHP Solutions Nr 5/2006

Narzędzia
strony w hierarchii drzewa metodą przeciągnij i upuść.

Dokumentacja TYPO3

Używanie i czyszczenie cache'a

TYPO3 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 pracujemy, wybieramy [Menu] –> [Clear Cache] –> This page.

TYPO3 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 podstawy TYPO3. Jest ona dostępna w niemieckiej, angielskiej i francuskiej wersji językowej i pozwala na bardzo dobre poznanie TYPO3.

Dodawanie strony

TYPO3 jest CMS-em zorientowanym na strony (ang. page-centric CMS). Oznacza to, że pojedyncza strona internetowa jest w nim podstawą całej witryny, umożliwiając nawigację i przechowując zawartość. Jest to zupełnie inne rozwiązanie niż stosowane w takich CMS-ach jak Mambo, gdzie zawartością zarządzamy przy użyciu osobnego modułu, a potem linkujemy ją do wybranych stron. Koncepcja i codzienne zarządzanie witryną są więc zupełnie inne niż w tamtych CMS-ach. Zależnie od swoich uprawnień, redaktor może dodać stronę w wybranej części witryny. Drzewo stron jest całkowicie niezależne od innych czynników, takich jak zainstalowane moduły (w przeciwieństwie np. do phpNuke i pochodnych). Każda nowa strona jest automatycznie dodawana do menu (jeżeli istnieje menu odpowiedniego poziomu). Dodajmy teraz parę stron do naszej witryny: będzie to trochę podobne do modyfikacji zawartości strony. W tym celu klikamy opcję Page w menu po lewej stronie ekranu i wybieramy 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 dodawania stron. Wybieramy w nim opcję Page(Inside) – Click here for Wizard, a następnie wybieramy docelowe położenie strony. Pojawi się formularz dodawania strony, w którym wpiszemy jej tytuł (np. Moja Strona) – będzie on używany w tagu <title> strony WWW oraz jako nazwa strony w drzewie. Jak widzimy, strona jest domyślnie ukryta (poprzez zaznaczenie 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 rzeczy. Po pierwsze, istnieje wiele sposobów na dodawanie stron, m.in. użycie ikony Create new page w Page view czy też opcji new w menu uruchamianym po kliknięciu dowolnej ikony w ramach drzewa pośrodku ekranu. Po drugie, istnieją inne typy stron (wybieramy je z listy rozwijalnej Type w opisywanym Wizardzie), takie jak skróty do innych części witryny, linki 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, dane użytkowników FE, itd), których będziemy mogli użyć w dowolnej części witryny. Po trzecie, możemy tworzyć wiele stron jednocześnie korzystając z menu Functions po lewej stronie ekranu (jest to bardzo przydatne, gdy budujemy witrynę od podstaw, gdyż skraca czas).

Dodawanie bloku tekstowego

Dobrze, 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 listę typów zawartość dostępnych dla użytkownika redactor (lub admin). Gdybyśmy wybrali Regular Text, moglibyśmy ustalić, w której kolumnie chcemy umieścić zawartość (wybierzemy kolumnę Normal). Na koniec widzimy ten sam formularz, którego używaliśmy podczas modyfikacji istniejącej treści. Przejdźmy teraz do listy rozwijalnej Type i wybierzmy header: pozwala nam to na wstawienie samego nagłó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 obrazka. Aby zobaczyć więcej opcji, przewijamy formularz i na jego samym dole zaznaczamy checkbox Show secondary options: w formularzu pojawią się takie opcje, jak jakość obrazu, efekty (np. obroty czy regulacja 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 dekorowanie obrazka zachodzi automatycznie wobec wszystkich obrazów z zestawu przy użyciu kombinacji bibliotek GD oraz ImageMagick. Kolejnym typem zawartości jest Filelinks, którego używamy w celu wyświetlenia listy plików, z których każdy zawiera 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 zdefiniować wygląd takiej listy korzystając z kilku gotowych wzorów. Wracając do klasycznej zawartości strony, mamy Menu Site Map, czyli mapę witryny. Używamy jej, aby umieścić na stronie mapę całego sajtu lub jego części (korzystając z pola Starting Point: mamy do dyspozycji drzewo, które pozwala nam wybrać miejsce witryny, od którego 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. podsumowanie (resume) podstron. Inną pożyteczną sztuczką, którą możemy zastosować na mapie witryny jest sporządzenie spisu treści stron na stronie aktywnej. Przydatne jest również zaznaczenie boksu To top w formularzu edycyjnym każdego elementu strony (opcja secondary options, umieszczona tuż poniżej listy roz-

PHP Solutions Nr 5/2006

www.phpsolmag.org

31

Narzędzia
nych innych pól, również tego oznaczonego jako CODE, które w obecnej wersji TYPO3 jest poprostu przestarzałe. Zapisanie forum poprzez kliknięcie przycisku Save document and view (ikona dyskietki) kończy naszą pracę nad nim. Mamy gotowe, małe forum dyskusyjne na naszej stronie, które nadaje się świetnie jako narzędzie do komentowania zawartości strony przez użytkowników. Jeżeli wybierzemy opcję List z menu po lewej stronie ekranu, a następnie stronę My Page w drzewie pośrodku ekranu, zobaczymy listę wypowiedzi (postów) z forum zamieszczonych na stronie. Plugin tt_board pobiera wypowiedzi z danej strony, a następnie je segreguje i wyświetla 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. tt_board nie jest załadowane, istnieje szansa, że mamy je gotowe do instalacji w systemie TYPO3: aby to sprawdzić, wybierzmy 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 rozszerzeń TYPO3 (TYPO3 Extensions Repository), które znajduje się pod adresem http: //www.typo3.org/extensions/, klikamy na zakładkę Full list, odnajdujemy tt_board i pobieramy je na dysk lokalny. Następnie wracamy do Extension Managera i wybieramy opcję Import extensions. Zwróćmy uwagę na nagłówek UPLOAD EXTENSION FILE DIRECTLY (.T3X). Tuż pod nim znajduje 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 jego instrukcjami. W ten sposób zainstalujemy tt_board. Aby umieścić zainstalowane w TYPO3 tt_board na naszej stronie, przechodzimy do Page -> My Page -> Create new content, przewijamy listę typów treści i w sekcji Plugins wybieramy Discussion forum (na samym końcu listy). Ta ostatnia operacja jest identyczna z wybraniem General plugin w tej samej sekcji, następującej po nim selekcji Insert plugin jako typu zawartoś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-

Rysunek 2. Szablon, który utworzymy

wijalnej umożliwiającej wybór typu treści, musi być włączona). W ten sposób umożliwimy nawigację pomiędzy blokami tekstowymi a spisem treści. Ostatnim klasycznym typem zawartości, który omówimy, jest Insert Record. Pozwala on na wyświetlanie treści zgromadzonej w innej części witryny. Znaczy to, że zawartość ta będzie się cały czas znajdowała w oryginalnym położeniu, a każda jej modyfikacja będzie natychmiastowo i automatycznie widoczna na naszej stronie. Insert Record jest więc nie tyle typem treści, co referencją do innego bloku.

Potęga szablonów

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ć nowe typy zawartości lub funkcje BackEndu albo zawierać gotowe aplikacje (np. forum dyskusyjne). Te ostatnie będą widoczne we FrontEndzie i noszą nazwę wtyczek (pluginów, ang. plugins). Dla TYPO3, forum dyskusyjne jest złożonym typem zawartości korzystającym z wielu rekordów (którymi są wypowiedzi) oraz narzędzia wyświetlającego (samo forum). Zainstalujemy rozszerzenie tt_board, które jest bardzo prostym forum dyskusyjnym. Zacznijmy od sprawdzenia, czy tt_board jest zainstalowane w TYPO3. W tym celu przechodzimy do Extension Managera (Ext manager w menu po lewej stronie ekranu) i w liście rozwijalnej Menu: wybieramy Loaded extensions. Nawet, jeśli

Możemy już tworzyć i zarządzać zawartością strony – świetnie. Jeszcze lepiej byłoby jednak, gdybyśmy mogli tworzyć własną witrynę w oparciu o szablon. Chcemy, aby zawierał on 2 menu, z których pierwsze jest poziome, a drugie jest pionowe i istnieje na drugim i trzecim poziomie witryny. Chcemy, aby zawartość była wyświetlana w dwóch strefach: pierwsza z nich będzie duża i ulokowana w centrum, a druga mała i umieszczona po lewej, przeznaczona do zamieszczania niewielkich notatek. Użyjemy szablonu sunflower (zob. Rysunek 2) – szablon znajdujący się w domenie 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 lewej stronie ekranu i zalogujmy się na konto 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 wykorzystania szablonów w TYPO3: Modern Template Building (nazwa pochodzi z manuala) oraz Templavoila. Pierwsza z nich, której

32

www.phpsolmag.org

PHP Solutions Nr 5/2006

Narzędzia
użyjemy na naszej witrynie, jest klasyczna i opiera się na umieszczaniu specjalnych znaczników w plikach HTML. Metoda ta jest prosta do zrozumienia i znajduje zastosowanie przy tworzeniu rozszerzeń. Użycie MTB pozwoli nam się przyzwyczaić do języka TypoScript. Natomiast druga metoda, Templavoila, pozwala na stosowanie szablonów przy użyciu interfejsu typu point-and-click. Dla szablonów w TYPO3 przeznaczono osobny typ zawartości o nazwie Template 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 witryny, 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 konfiguracji tego rozszerzenia). nich (Constant i Setup) informują TYPO3, że mamy do czynienia z nowym szablonem, który nie dziedziczy z głównego szablonu (dziedziczenie po poprzednich szablonach w linii root kodu TypoScript zostaje wykasowane). Trzecia opcja (Rootlevel) określa, że szablon jest korzeniem (rootem) witryny internetowej (główną stroną), a więc strona, na której zostanie użyty, 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 pola Include static (from extensions), wybierają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ą.

Tworzymy własny szablon

Aby utworzyć swój własny szablon skorzystamy z menu List po lewej stronie, wybierzemy stronę Test w drzewie pośrodku ekranu, klikniemy Create new record i jako typ treści wybierzemy Template. Ukaże nam się formularz dotyczący szablonu, w którym wypełnimy jedynie tytuł (będący nazwą szablonu). Możemy też ustawić nazwę witryny: zostanie ona użyta w tagu <title> przy nazwie strony (np. MojaWitryna : Strona1). Następnie w tym samym formularzu odhaczymy checkboksy Clear: Constant, Clear: Setup i RootLevel. Dwa pierwsze z

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>

Zabawa z TypoScript

Jeż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 Constant i Setup. Constant to zmienna w języku TypoScript oraz miejsce, w którym można wygodnie umieszczać wartości wykorzystywane w polu Setup: później pokażemy, jak to zrobić, a na razie zajmijmy się polem Setup. Pamiętajmy, że TypoScript ma składnię obiektową, której używamy do konfigurowania silnika renderującego (ang. rendering engine) oraz umieszczania obiektów na stronach WWW. Wprowadzimy kod przedstawiony na Listingu 2 do pola Setup. Zaczniemy od inicjalizacji strony wpisując mypage = PAGE;, wskutek czego 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. ramkami) 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 (mypage.stylesheet). Jest to konieczne, gdyż

PHP Solutions Nr 5/2006

www.phpsolmag.org

33

Narzędzia
ś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 wstawił odpowiednią treść. Pierwszym obiektem na tej stronie (page.10), który utworzymy, będzie obiekt typu TEMPLATE (mypage.10 = TEMPLATE). Określimy następnie plik do załadowania (template = FILE i
template.file sunflower/index.html). = fileadmin/templates/

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

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

W pliku HTML-owym musimy oznaczyć strefy stosując pary znaczników <!--###MYZONE##-->, które określają podczęści (ang. subparts). Każda podczęść określona pomiędzy dwoma tagami może następnie zostać zastąpiona treścią wygenerowaną przez silnik TYPO3. Możemy też wstawiać oznaczenia składające się tylko z jednego tagu: zostaną one zastąpione w ten sam sposób. Następnym krokiem będzie zdefiniowanie 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ępuje oznaczenia i podczęści treścią. Znaczenie obiektu TEXT jest oczywiste, wyjaśnimy więc inny obiekt, styles.content.get. Jest on skrótem zdefiniowanym w szablonie statycznym CSS styled content, który wcześniej załadowaliśmy. Jego zadaniem jest zarządzenie, aby TYPO3 pobierał zawartość z kolumny NORMAL i wyświetlał ją. Linia
subparts.CONTENT

wstawia więc kolumnę NORMAL do szablonu HTML w miejscu określonym przez znacznik <!--###CONTENT###-->. Tej samej techniki używamy w celu wstawienia menu1 i menu2 w odpowiednich dla nich strefach.

<

styles.content.get

Tworzymy menu

}

Przyjrzyjmy 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 poziomu drzewa witryny, który chcemy wyświetlić. Przyjrzyjmy się sposobowi numeracji menu: jest on ścisły, ponieważ indeks każdej pozycji w menu określa jednocześnie jej głębokość. Każda pozycja w menu (tzn. każdy poziom menu) może być innego typu. W naszym przykładzie mamy tylko menu tekstowe, można jednak tworzyć również menu graficzne (przy użyciu Gdlib) lub graficzno-tekstowe, menu za-

34

www.phpsolmag.org

PHP Solutions Nr 5/2006

Narzędzia

PHP Solutions Nr 5/2006

www.phpsolmag.org

35

Narzędzia
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 tekstowego 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 TYPO3 umieścić link pomiędzy tagami <li>. Ustawianie statusu NO jest obowiązkowe. Istnieje wiele ustawień menu, które zmieniają sposób jego zachowania w skali globalnej (całej witryny) albo modyfikują jedynie wybraną pozycję. Przykładem na to pierwsze jest konstrukcja special = list in temp.menu, która zmienia typ menu z dynamicznego na statyczne, którego pozycje mają postać listy. Istnieje też parametr umożliwiający tworzenie zestawów przycisków Previous | 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 programowania>php>nowe projekty) lub katalog stron (wypróbuj temp.menu1.special=directory czy temp.menu1.special.value = 13). Menu o nazwie menu2 jest przykładem konfigurowania poszczególnych pozycji menu. Oprócz statusu normal, możemy używać innych statusów, takich jak RO (rollover – stan pozwalający na zdefiniowanie tego, jak ma wyglądać pozycja menu, gdy najedziemy na nią kursorem myszy), ACT (active page – stan ten oznacza, że pozycja menu 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, zobaczymy, że strony aktywne (włącznie z główną) są podświetlone w menu.

Rysunek 4. Directory Flexcontent – domyślny formularz oparty na (X/HT)ML
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 kolumnie 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ów

Ostatnie szlify naszego szablonu

Jedną z ostatnich czynności, jakie przeprowadzimy wobec tego szablonu jest zastąpienie podczęści SIDECONTENT prawdziwą zawartością. Zawartość lewej kolumny umieścimy w odpowiedniej podczęści. W tym celu podmienimy poprostu linie subparts.SIDECONTENT poprzez subparts.SIDECONTENT < styles.

Istnieją również inne niż opisane przez nas metody wprowadzenia i wykorzystania szablonów w Typo3. Skupiliśmy się na Modern Template Building, ponieważ jest ono najlepszym sposobem, aby obserwować efekty użycia TypoScriptu. Unowocześniona wersja tej metody działa dzięki wykorzystaniu rozszerzenia Automake Template, a znaczniki umieszczane w szablonie są automatycznie generowane przez TYPO3. Używając wymienionego rozszerzenia pomijamy jeden krok w procesie projektowania witryny, ponieważ nie musimy modyfikować plików HTML. Ponad rok temu pojawiło się rozszerzenie TemplaVoila. Jest ono bardzo potężną metodą zarządzania szablonami w TYPO3. Została opisana w należącym do manuala TYPO3 rozdziale Futuristic Templa-

te Building. Opiera się na wykorzystywaniu technik graficznych, w większości typu point-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ą. Mapowanie zawartości do elementów HTML jest bardzo proste i dokonujemy go korzystając z formularzy, w których wprowadzamy potrzebne dane. Liczba mapowanych elementów (treści) odpowiada ilości pól lub kolumn w docelowym HTML-u. Przykładowo, jeżeli utworzymy 2 mapowania treści w TV, otrzymamy 2 kolumny zamiast 4, które mieliśmy używając metody Modern Template Building (zob. Rysunek 3). Dobre jest to, że w szablonie również możemy używać pól, określających np. kolor strony. Wymienione 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 zastosowanie 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.

36

www.phpsolmag.org

PHP Solutions Nr 5/2006

Narzędzia
Metoda TemplaVoila daje bardzo duże możliwości, aby je lepiej poznać, powinieneś 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 chcemy użyć na danej stronie et voilà! – nasz szablon jest gotowy. Działanie TemplaVoila zostało przedstawione 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). 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 przekroczyć maksymalny czas wykonywania (nastąpi timeout), który jest standardowo ustawiony na 30 sekund. Aby temu zapobiec, 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), albo też ręcznie pobrać rozszerzenie, a nastę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 przechodzimy na stronę Test i klikamy Create new record -> Page Content -> Click for Wizard. Następnie musimy wybrać plugin o nazwie News, który jest umieszczony na końcu listy w sekcji Plugins. Klikamy go i umieszczamy w kolumnie Normal. W formularzu edycyjnym wpisujemy nagłówek (np. Moje newsy) i wybieramy LATEST w What to display: moduł będzie wyświetlał tytuł i pierwsze słowa każdego newsa. Zapiszmy nasze modyfikacje korzystając (jak zwykle) z ikony przedstawiającej dyskietkę. Musimy jeszcze zaktualizować szablon, aby uwzględnić w nim newsy. W tym celu przechodzimy do menu List i edytujemy szablon Test. Następnie do listy rozszerzeń Include static from extensions dodajemy CSS-based tmpl (tt_news) i zapisujemy 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 formularza, który się pojawi po prawej. Następnie wpisujemy tytuł, odhaczamy checkbox hide i wpisujemy treść wiadomości w polu Text (jeżeli checkbox hide jest niewidoczny (nie ma go pod polem tytułu), to przewijamy stronę do samego końca i zaznaczamy box Show secondary options). Następnie zapisujemy naszą wiadomość klikając ikonę dyskietki z lupą (Save document 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ł newsów w TYPO3 pozwala na te operacje, możemy też zmieniać sposób prezentacji 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 tworzenie wymaganych w każdym rozszerzeniu plików oraz tworzenie pustego kodu PHP (który później zastąpimy własnym). Możemy użyć tego narzędzia do wstawiania nowych pól do bazy danych. Co ciekawe, pola te dodajemy używając oznaczeń typowych dla formularzy (textarea, selector, file), a nie typów SQL-owych (np. varchar czy integer). Wydaje się to trochę dziwne, lecz jest wydajne.

Dodawanie nowych aplikacji

Podsumowanie

Jak już wiemy, jedną z najlepszych cech TYPO3 jest system rozszerzeń, o którym już wspominaliśmy. Podobnie, jak inne systemy wtyczek (np. z Firefoksa), pozwala on na wzbogacanie naszej witryny o nowe funkcje bez modyfikowania silnika leżącego u jej podstaw (rdzenia). Działanie każdego rozszerzenia może dotyczyć FrontEndu, BackEndu lub obu z nich. Rozszerzenia TYPO3 możemy pobrać z repozytorium znajdującego się pod adresem http://typo3.org/extensions (ponad 1400 dostępnych rozszerzeń) lub zapisać na serwerze, na którym są tworzone i przesłać je na serwer produkcyjny. Każde rozszerzenie jest zarchiwizowane w postaci pojedynczego pliku .t3x, który zawiera modyfikacje bazy danych, pliki konfiguracyjne i samą aplikację (jej logikę biznesową).

Dodajemy i uruchamiamy system newsów

Zainstalujmy system newsów z repozytorium TYPO3 (TER). Będziemy do tego potrzebowali połączenia z Internetem. Podobnie, jak miało to miejsce przy instalacji tt_board, musimy zacząć od kliknięcia opcji Ext Manager w menu po lewej stronie ekranu (sekcja Tools). W ramce, która pojawi się po prawej stronie ekranu, wybierzemy Menu: Import extensions. Po przeładowaniu strony, wpisujemy tt_news w polu Lookup i klikamy przycisk Lookup. Powinna 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-

Opisaliśmy najbardziej użyteczne możliwości TYPO3, dając Ci solidne podstawy do podjęcia decyzji, czy chcesz korzystać z tego CMS-a, czy nie. Aby się o tym upewnić, możesz zajrzeć pod adres http://typo3.com, gdzie znajduje się Evaluation Center – miejsce, w którym zgromadzone sa rozmaite informacje na temat TYPO3. Możliwości TYPO3 są dodatkowo zilustrowane przy użyciu przykładów we Flashu (http://typo3.com/Feature_ list.1243.0.html). Niezwykłą funkcją TYPO3, która pojawiła się w wersji 4.0, jest logika przestrzeni roboczych (ang. workspace logic). Pozwala ona na określenie przestrzeni, w której będzie zapisawana treść, np. brudnopis czekający na akceptację redaktora. Zachęcamy Cię do poznawania TYPO3 krok po kroku: zacznij od uruchomienia małej witryny, naucz się TypoScript, wypróbuj TemplaVoila i zainstaluj rozszerzenia, których potrzebujesz. Dzięki własnej praktyce szybko staniesz się mistrzem obsługi TYPO3. n

O autorze
Jean-Gael Rouchon jest dyrektorem badań i rozwoju (R&D director) we francuskiej firmie ONEXT, która specjalizuje się w opensourcowych systemach CMS tworzonych w PHP. Pracuje nad TYPO3 od 2002 i współadministruje portalem typo3.fr Kontakt: [email protected]

PHP Solutions Nr 5/2006

www.phpsolmag.org

37

Kasa dla Webmastera

Freelancing – zostań wolnym strzelcem
Krzysztof Trynkiewicz

Stopień trudności: lll

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

N

ajogólniej mówiąc, freelancing to rynek zleceń. Choć istniał już wcześniej, do jego rozwoju w największym stopniu przyczyniły się wyspecjalizowane serwisy freelancingowe, w których jedni zgłaszają swoje pomysły, a inni je wykonują. Nad przebiegiem realizacji projektu i przekazania wynagrodzenia czuwają administratorzy tych witryn, którzy są tym samym pośrednikami w kontakcie zleceniodawców z wykonawcami (zleceniobiorcami).

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

Różnorodność ofert

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

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

Co należy wiedzieć...

Przydatna będzie podstawowa znajomość terminologii biznesowej.

Co obiecujemy...

Pokażemy, jak działa freelancing na przykładzie najpopularniejszych serwisów, które go organizują.

38

www.phpsolmag.org

PHP Solutions Nr 5/2006

Freelancing

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

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

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

Jak to działa

Większość serwisów freelancingowych oprócz spisu ofert udostępnia system prowadzący krok po kroku przez cały proces realizacji projektu, którego stadia są w większości serwisów podobne. Załóżmy, że chcemy zlecić komuś wykonanie panelu administracyjnego naszej witryny internetowej. Określamy więc nasze wymagania (opcjonalnie także ostateczny termin zakończenia pracy, tzw. deadline) i umieszczamy zlecenie na wybranym serwisie freelancingowym. Na naszą ofertę odpowiadają potencjalni wykonawcy, 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 zabezpieczenie, wykonawca może rozpocząć swoją pracę. Postępy są odnotowywane u pośrednika – programista musi zdawać cotygodniowe relacje poparte odpowiednimi plikami. W razie niewywiązywania się z umowy, możemy się zwrócić o mediację do pośrednika (może o nią poprosić również wykonawca) – obie strony są wtedy zobowiązane do zaakceptowania decyzji arbitra, który rozsądzi racje stron. Jeśli projekt zostanie zakończony pomyślnie, pośrednik przekaże wynagrodzenie wykonawcy (możemy mu też, jako zlecający, przekazywać części kwoty w trakcie wypełniania kolejnych postanowień umowy). Do naszych powinności należało będzie również wystawienie opinii o wykonawcy, z którą będziemy się mogli zapoznać my oraz przyszli klienci. Na Rysunku 1 przedstawiamy schemat kolejnych etapów współpracy.

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

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

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

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

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

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

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

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

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

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

Rysunek 1. Kolejne stadia współpracy pomiędzy wykonawcą a zleceniobiorcą (proponowane przez pośredników) nie i współpraca są korzystne dla obu stron: zleceniodawca może przedstawić swój projekt tylko nam (aukcja typu One on One), dzięki czemu zaoszczędzi, gdyż w takiej sytuacji opłata pobierana 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. wprowadzony np. w Rent a Coder depozyt bezpieczeństwa zleceniodawcy (ang. Seller Guarantee Deposit). Będąc wykonawcą projektów, możemy zdeponować pewien ustalony procent wartości całego zlecenia na konto pośrednika. Jeżeli nie wywiążemy się z umowy, nasze pieniądze zostaną przeznaczone na pokrycie kosztów manipulacyjnych zleceniodawcy oraz na cele charytatywne (lecz nie samemu zleceniodawcy, by uniemożliwić wyłudzanie pieniędzy). Jeśli natomiast wszystko pójdzie zgodnie z planem, zostanie nam zwrócone około 98% zdeponowanej kwoty. Depozyt stanowi więc zabezpieczenie dla zleceniodawcy i pozwala nam podnieść zaufanie do naszych ofert.

Jak się wybić z tłumu

Zadowolenie przede wszystkim

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

Czę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 wykonawca 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ć problem, aby przekonać zleceniodawców do relatywnie wysokiej, choć adekwatnej do jakości świadczonych usług ceny, jeśli musi konkurować z atrakcyjnymi cenowo ofertami początkujących freelancerów. Rozwiązaniem tego problemu jest

Chrońmy swój pomysł

Często miewamy pomysły, których sami nie potrafimy zrealizować z przyczyn technicznych. Taką ideę (a czasem gotowy do wdrożenia projekt) możemy sprzedać komuś, kto zrobi z niej użytek. Pamię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-

PHP Solutions Nr 5/2006

www.phpsolmag.org

39

Kasa dla Webmastera
nąć takich sytuacji, czyli uniemożliwić podejrzenie szczegółów projektu przez nieuczciwych wykonawców, niektóre serwisy freelancingowe wprowadziły tzw. Umowę o nieujawnianiu informacji (ang. Nondisclosure Agreement). Jeśli dajemy zlecenie w trybie zgodnym z tą umową, wykonawcy będą widzieć jedynie podstawowe informacje o naszym projekcie – jego typ, zakres cenowy i wprowadzone przez nas publicznie dostępne wytyczne. By ujrzeć szczegóły, wykonawca musi wyrazić chęć ich poznania przez podpisanie powyższej umowy, my zaś możemy udostępnić mu tajne dane lub nie. wątpliwości. Jeżeli jednak wciąż pojawiają się niejasności, możemy poprosić o radę facilitatora – przedstawiciela specjalnego oddziału serwisu freelancingowego, przedstawiając naszą sytuację i podając link do naszej aukcji. nień. Niewątpliwą zaletą takiego rozwiązania jest mniejsza konkurencja w przypadku składania przez nas ofert. Jeżeli jednak to my szukamy wykonawcy, warto wziąć pod uwagę różne, nie tylko największe serwisy – liczy się bowiem także prowizja, jaką będziemy musieli zapłacić pośrednikowi. Z pewnością mniejsza popularność nie oznacza gorszych wykonawców, więc jeśli nie jesteśmy zbyt wybredni, możemy podjąć niewielkie ryzyko i spróbować zaoszczędzić.

Wybór serwisów pośredniczących

Właściwy wybór wykonawcy

Będąc zleceniodawcą, powinniśmy się dobrze zastanowić przed wyborem wykonawcy 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 dolarów. Dokonując wyboru najlepiej zwrócić uwagę na to, jak wiele zadań wykonał nasz potencjalny zleceniobiorca oraz jakie opinie wyrażali o nim poprzedni zleceniodawcy i jakie oceny mu wystawili. Powinniśmy też zajrzeć do portfolio i resume wykonawcy, gdzie dowiemy się o umiejętnoś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 zleceniobiorca – jeśli wykonanie naszego zadania będzie wymagało częstych konsultacji, 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 wykonawców. Nie powinniśmy też zatrudniać wykonawcy, który robił dotychczas projekty za stawki wielokrotnie niższe niż oferowane przez nas, gdyż ich waga i złożoność musiał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 posiadamy zabezpieczenie naszych finansów w postaci escrow. Serwisy pośredniczące oferują często możliwość komunikacji z wykonawcami jeszcze przed zaakceptowaniem ich ofert w celu określenia szczegółów i wyjaśnienia wszelkich

W sieci znajdziemy wiele ofert pośredników. Ważne jest, by wybrać serwis z renomą, działający legalnie i obsługujący opisane w artykule zabezpieczenia przed nieuczciwością i niesolidnością. Rent a Coder (http://rentacoder.com) oferuje wszystkie wspomniane udogodnienia i z pewnością ma wielką renomę. W chwili pisania artykułu posiadał 2 tys. otwartych projektów oczekujących na oferty, 150 tys. zarejestrowanych wykonawców i 60 tys. zleceniodawców. Prym we freelancingu wiedzie jednak inny serwis – Guru.com (http://www.guru.com/). Liczba zarejestrowanych tam wykonawców jest dziesięciokrotnie większa niż w przypadku Rent a Coder (568 tysięcy). Co ciekawe, 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 funkcjonował omawiany w artykule depozyt bezpieczeństwa zleceniodawcy (Seller Guarantee 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 świadczenia usług (w tym wzory umów, m.in. Nondisclosure agreement). Listy serwisów freelancingowych znajdziemy pod adresami http://www.phpkitchen.com/index.php?/ archives/670-PHP-Freelance.html (uwaga – są tu również typowe serwisy do wyszukiwania 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 wykorzystywanych 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 wykonania, możemy zajrzeć również na takie 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-

Freelancing w Polsce

Niestety, w chwili pisania artykułu freelancing 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 oferuje on wielu opisanych przez nas zabezpieczeń i udogodnień. Miejmy nadzieję, że w najbliższym czasie sytuacja ta ulegnie zmianie.

Podsumowanie

Jako programistom, freelancing daje nam ogromne możliwości zarobienia pieniędzy, nawiązania kontaktów biznesowych, wyrobienia sobie renomy i doskonalenia swoich umiejętności. Jeśli natomiast potrzebujemy wykonawców naszego projektu, to dzięki rozbudowanym serwisom freelancingowym mamy dużą szansę znaleźć najbardziej odpowiednie osoby. W obu przypadkach, warto się dobrze zapoznać z różnymi ofertami oraz opiniami o zamawiających czy wykonawcach. Istotne jest również poważ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

O autorze
Krzysztof Trynkiewicz od wielu lat zajmuje się tworzeniem witryn w PHP i Flash. Współpracuje z magazynem PHP Solutions, wykonuje także zlecenia freelancingowe. Obecnie rozwija kilka równoległych projektów autorskich dostępnych na witrynie http://eldoras.com. Kontakt z autorem: [email protected]

40

www.phpsolmag.org

PHP Solutions Nr 5/2006

Dla zaawansowanych

Zend API – tworzymy własne rozszerzenie dla PHP
Marcin Staniszczak

Stopień trudności: lll

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.

W SIECI
l

Z

l

l

l

http://www.php.net/manual/ en/internals.php – informacje na temat Zend API znajdujące się w oficjalnej dokumentacji PHP – niestety dość skromne http://somabo.de/talks/ -- materiały z różnych konferencji dotyczących PHP – znajdują się tu także dwie dobre prezentacje poruszające problem rozszerzeń dla PHP http://www.zend.com/php/ internals/extension-writing1.php – pierwsza część tutoriala na temat pisania rozszerzeń z wykorzystaniem Zend API http://www.zend.com/php/ internals/extension-writing2.php – druga część powyższego tutoriala

end API jest interfejsem programisty, pozwalającym pisać rozszerzenia dla PHP w języku C. Do pisania bardzo prostych rozszerzeń wystarczy podstawowa znajomość tego języka – jak zobaczycie dalej, dostępne są narzędzia generujące cały szablon takiego rozszerzenia za nas – jednak do pisania bardziej zaawansowanych programów, potrzebna jest dobra znajomość C oraz zacięcie detektywistyczne, z powodu bardzo słabej dokumentacji niektórych elementów Zend API. Nie obejdziemy się więc bez konieczności analizowania źródeł rozszerzeń dostarczanych standardowo z PHP. Na potrzeby tego artykułu wspólnie stworzymy rozszerzenie wzbogacające PHP o funkcję pozwalającą obliczać proste wyrażenia matematyczne – zawierające podstawowe operacje takie jak =, -, *, czy /. Będzie można także do woli korzystać z nawiasów okrągłych ( ), a także kwadratowych [ ].

CodeGen_PECL – przygotowujemy się do pracy

Stworzenie rozszerzenia całkowicie samodzielnie jest zadaniem bardzo skomplikowanym i żmudnym. Szczególnie początkowy etap implementacji jest dość złożony. Na szczęście dziś w większości przypadków nie musimy się już o to martwić. Wystarczy na przykład skorzystać z CodeGen_PECL. Skrypt ten na podstawie pliku XML, w którym opisujemy tworzone rozszerzenie, generuje dla nas wszystkie

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

Potrzebna będzie podstawowa znajomość języka C i PHP.

Z artykułu dowiesz się, jak z wykorzystaniem Zend API zbudować przykładowe rozszerzenie dla PHP pozwalające na wykonywanie prostych operacji matematycznych.

42

www.phpsolmag.org

PHP Solutions Nr 5/2006

Zend API

Dla zaawansowanych

niezbędne pliki oraz szablon rozszerzenia (zalążek pliku źródłowego w C, który następnie rozbudowujemy o planowaną funkcjonalność). Pozwala to zaoszczędzić sporo czasu oraz nerwów, które stracilibyśmy starając się samodzielnie wykonać całą pracę. Zanim rozpoczniemy opisywanie rozszerzenia za pomocą XML-a, musimy najpierw zainstalować skrypt CodeGen_PECL. Można to zrobić na kilka sposobów. Jeśli posiadacie dostęp do Sieci, najlepiej wydać z poziomu terminala polecenie:
pear install –o Codegen_PECL

Szybki start

Już 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 sprawdzał, czy rozszerzenie uda się skompilować pod systemem Windows. Aby przeprowadzić kompilacje, musimy posiadać PHP z zainstalowanymi plikami nagłówkowymi (pliki z rozszerzeniem .h wykorzystywane w programach pisanych w 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 polecenie:
phpize; ./configure; make; sudo make install

Spowoduje to pobranie oraz zainstalowanie skryptu. Jeśli nie mamy aktualnie dostępu do Internetu, na CD dołączonym do magazynu znajduje się archiwum ze skryptem w wersji 1.0.0. Możemy je zainstalować przechodząc do katalogu z tym archiwum oraz wydać następujące polecenia:
pear install CodeGen-1.0.0.tgz pear install CodeGen-PECL-1.0.0.tgz

Jeśli podczas wykonywania make install otrzymamy informacje o błędzie podczas próby wykonania polecenia (brak uprawnień, czyli odpowiedniego wpisu w pliku sudoers), zalogujcie się do systemu jako root i po przejściu do katalogu ze źródłami rozszerzenia ponownie wydajcie polecenie make install. Po skompilowaniu i zainstalowaniu rozszerzenia, musimy jeszcze odpowiednio skonfigurować 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 ustawiona i 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 Polska

Spowoduje to zainstalowanie odpowiednio skryptu CodeGen wymaganego do poprawnego działania przez CodeGen_ PECL oraz samego CodeGen_PECL.

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

Opisujemy rozszerzenie za pomocą XML-a

Gdy mamy już zainstalowany skrypt CodeGen_PECL, możemy przystąpić do tworzenia pliku XML z opisem naszego rozszerzenia. Cały opis rozszerzenia umieszcza się w znaczniku <extension>. Przyjmuje on jeden parametr będący nazwą tworzonego rozszerzenia. Nasze rozszerzenie nazwiemy RPN – od Reverse Polish Notation – nazwy sposobu, w jaki będzie liczyło wyrażenie (będzie ono zamieniane do postaci Odwrotnej Notacji Polskiej, a następnie obliczane – więcej na temat ONP w ramce Odwrotna Notacja Polska). Opis rozszerzenia umieszczamy zatem wewnątrz następującego bloku:
<extension name=”RPN”> <!—tutaj będzie umieszczony opis rozszerzenia--> </extension>

Więcej na temat ONP oraz algorytmów przekształcenia wyrażenia z zapisu infiksowego (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

Skrypt CodeGen_PECL na podstawie informacji z pliku XML potrafi wygenerować tabelkę z informacjami, która ukaże się na stronie zwracanej przez phpinfo(). Nazwę rozszerzenia skrypt pobiera z parametru name znacznika <extension>. Jeśli chcemy w phpinfo() umieścić krótką informację o rozszerzeniu (podsumowanie), należy ją umieścić wewnątrz znacznika <summary>:
<summary> Przykładowe rozszerzenie PHP - RPN </summary>

<description>.

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

Autorzy skryptu CodeGen_PECL zalecają umieszczanie obu tych znaczników zaraz pod otwierającym znacznikiem <extension>. Za umieszczenie informacji o autorach rozszerzenia odpowiada znacznik <maintainers>, zawierający tag <maintainer> opisujący każdego z autorów (Listing 1). Na taki opis składa się jego nazwa (może to być np. nick) umieszczona w znaczniku <user>, imię i nazwisko umieszczone w znaczniku <name>, e-mail umieszczony w znaczniku <email> oraz rola jaką on odgrywa w zespole ludzi pracujących nad rozszerzeniem umieszczona w znaczniku <role>. Znacznik <maintainers> może oczywiście zawierać

PHP Solutions Nr 5/2006

www.phpsolmag.org

43

Dla zaawansowanych

Zend API

CodeGen_PECL
Skrypt CodeGen_PECL można znaleźć w repozytorium PEAR. Poprzednio nosił on nazwę PECL_Gen i był częścią repozytorium PECL. Skrypt ten w znacznym stopniu ułatwia tworzenie rozszerzeń dla języka PHP, generując na podstawie XML-a kompletne szablony rozszerzeń. Dokumentacja 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.

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

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>

wiele bloków <maintainer> z opisami poszczególnych członków zespołu. Dodajmy teraz informację o wersji naszego rozszerzenia. Służy do tego blok <changelog> zawierający znaczniki <release> opisujące poszczególne wersje. Każde wydanie może zostać opisane za pomocą numeru umieszczonego w znaczniku <version>, daty wydania umieszczonej w znaczniku <date>, statusu wydania (np. beta, stable itd.) w znaczniku <state> oraz opisu wydania umieszczonego w znaczniku <notes>. W bloku <changelog> można umieścić opis wielu wersji. Na Listingu 2 znajduje się opis wydania naszego rozszerzenia. W phpinfo() można jeszcze umieścić obrazek, jednak zdarzają się czasami problemy z jego poprawnym wyświetlaniem. 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 (parametr ten można zignorować w przypadku 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"/>

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>

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 przyjmować wartości PHP, BSD lub LGPL – inne traktuje jako wartość nieznaną, na podstawie której nie potrafi wygenerować odpowiednich danych.

44

www.phpsolmag.org

PHP Solutions Nr 5/2006

Zend API

Dla zaawansowanych

PHP Solutions Nr 5/2006

www.phpsolmag.org

45

Dla zaawansowanych

Zend API
Tabela 1. Typy danych dozwolone w opisach prototypów funkcji w pliku XML Nazwa typu bool int float string array object mixed callback resource stream Opis Typ logiczny przyjmujący wartości true oraz false Typ numeryczny – całkowity Typ numeryczny – zmiennoprzecinkowy Ciąg znaków – napis Tablica Obiekt Nieokreślony jednoznacznie typ danych Funkcja zwrotna Zasoby Strumień danych leży korzystać z sekcji CDATA, aby parser XML ignorował błędy XML, jakie mogłyby powodować znaki umieszczone w kodzie. Ze znacznika <code> możemy korzystać także w funkcjach deklarowanych przez nas samych (np. w naszych funkcjach rpn_calculation, rpn_errormsg i rpn_error). Jest to wygodne w przypadku 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 funkcji, a cały kod napisać w ulubionym edytorze lub środowisku programistycznym. Na koniec pozostało zdeklarowanie zmiennych ustawianych z poziomu pliku php.ini. W naszym przypadku zmienne te będą określały rozmiar stosu oraz kolejki wykorzystywanych podczas przekształcania wyrażenia matematycznego do Odwrotnej Notacji Polskiej oraz do późniejszego przeprowadzenia obliczeń. Zmienne te będą nosiły odpowiednio nazwy rpn_stack_size oraz rpn_queue_size. Zmienne ustawiane z poziomu php.ini definiuje się w bloku <globals>, korzystając ze znacznika <phpini>. Znacznik <phpini> przyjmuje 5 parametrów: • • • – jest to nazwa zmiennej, – określa typ zmiennej (patrz Tabela 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 zmiennym 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,
name type

Na Rysunku 1 znajduję się informacja o naszym rozszerzeniu. Skrypt CodeGen_PECL zawsze sam dodaje ramkę z informacjami o zmiennych wymaganych przez rozszerzenie, ustawianych z poziomu pliku php.ini. Przyszedł czas na to, aby zastanowić 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 przyjmował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>, przyjmującym parametr name, będący nazwą tworzonej funkcji. Sama nazwa to jednak za mało. Musimy jeszcze zdeklarować prototyp funkcji, zawierający dodatkowo informację o typie zwracanych danych oraz nazwie i typie przyjmowanych parametrów. Na Listingu 3 znajduje się przykład opisujący nasze trzy funkcje. Jak widzimy owe prototypy funkcji – 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.
phpinfo()

W opisach funkcji można bezpośrednio umieszczać kod w języku C. Dla przykładu w pliku XML umieściłem kod dla specjalnych funkcji Zend API: RINIT oraz RSHUTDOWN. Funkcje te są wywoływane automatycznie odpowiednio przed oraz po wykonaniu skryptu PHP lub w przypadku skryptów CGI / CLI zaraz po wystartowaniu PHP (dokładniejszy opis wszystkich funkcji systemowych znajduje się w Tabeli 2). Jeśli chcemy zdefiniować ciało funkcji specjalnej, trzeba użyć parametru rolę znacznika <function>, ustawiając go na wartość internal, nazwę funkcji zaś ustawić na jedną z wartości z Tabeli 2. Na Listingu 4 znajduje się przykładowa deklaracja ciał funkcji RINIT oraz RSHUTDOWN. Zauważmy, że kod funkcji został umieszczony w znaczniku <code> oraz zamknięty w sekcji CDATA języka XML – na-

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>



46

www.phpsolmag.org

PHP Solutions Nr 5/2006

Zend API
Tabela 2. Wewnętrzne funkcje Zend API Nazwa funkcji MINIT MSHUTDOWN Opis Funkcja inicjalizująca rozszerzenie. Jest ona wywoływana raz, zaraz po wystartowaniu modułu PHP na serwerze. 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. Funkcja ta jest wywoływana przed każdym uruchomieniem skryptu PHP lub zaraz po funkcji MINIT w przypadku skryptów CGI i CLI. Funkcja ta jest wywoływana po wykonaniu skryptu PHP. Zostanie 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. Funkcja ta jest wywoływana przez phpinfo() podczas wyświetlania informacji o rozszerzeniu. To ona odpowiada za wyświetlanie ramki z informacjami.

Dla zaawansowanych

RINIT

RSHUTDOWN

MINFO

Tabela 3. Wartości, jakie może przyjmować parametr access znacznika <phpini> – czyli gdzie można zmieniać wartość danej zmiennej Nazwa System Perdir User All Opis Wartość zmiennej może być ustawiona w pliku php.ini lub konfiguracji serwera HTTP Wartość zmiennej może być ustawiona w pliku .htaccess Wartość zmiennej może być zmieniona w kodzie PHP Wartość zmiennej może być zmieniona w jakimkolwiek z wymienionych wyżej miejsc. użyta zostanie standardowa metoda OnUpdateString. Możemy tu zastosować 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 zdefiniować (więcej na ten temat w dalszej części artykułu – niestety skrypt CodeGen_PECL w tym już nie umie pomóc), access – informacja o tym, gdzie użytkownik może ustawić wartość danej zmiennej – parametr ten może przyjąć jedną z wartości zebranych w Tabeli 3.

Na Listingu 5 możemy zapoznać się z przykładową deklaracją dwóch zmiennych modyfikowanych z poziomu pliku php.ini. Opis umieszczony pomiędzy znacznikiem otwierającym a zamykającym <phpini>, używany jest podczas generowania przez CodeGen_PECL dokumentacji w formacie DocBook (tak jak i wiele innych 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 CodeGen_PECL. Gdy plik XML jest już gotowy, przyszła pora na wygenerowanie na jego podstawie 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 wszystkie niezbędne pliki. Czas więc zacząć programowanie!

Piszemy program



Przyjrzyjmy się teraz zawartości wygenerowanego 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 następnie odszukajmy po nazwie funkcję
rpn_calculation.

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>

Funkcja

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

znajduje się w dwóch miejscach – najpierw w tablicy RPN_ functions, a następnie już jako definicja funkcji (definicja nie przypomina Wam zapewne języka C – o tym jednak w dalszej części artykułu) – nie licząc komentarzy, które dodawał po drodze CodeGen_ PECL. Jak można zauważyć, w tablicy zdefiniowane są wszystkie trzy funkcje naszego rozszerzenia – Listing 6. Elementy tablicy opisującej funkcje są typu 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 CodeGen_PECL, tak jak to zostało już zaprezentowane albo makra – np. PHP_FE, tak jak to robi CodeGen_PECL. Jako że czasami prawdopodobnie będziemy musieli
rpn_calculation

PHP Solutions Nr 5/2006

www.phpsolmag.org

47

Dla zaawansowanych

Zend API
Tabela 4. Makra służące do definiowania funkcji Nazwa Makra ZEND_FE(name, arg_types) Opis Definicja funkcji o nazwie name. Parametr arg_types powinien być zawsze ustawiony na NULL. Zdeklarowana w ten sposób funkcja będzie widoczna w PHP pod nazwą name, natomiast odwoływała się będzie do funkcji C zif_name name, 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 automatycznie nadawało prefix naszym funkcjom. Tak jak poprzednio parametr arg_types powinien być ustawiony na NULL Makro to definiuje alias o nazwie alias do funkcji PHP o nazwie name. arg_types powinno być ustawione na wartość NULL. Stary odpowiednik makra ZEND_FE

samodzielnie dodać jakąś funkcję do rozszerzenia, powinniśmy zapoznać się z makrami ułatwiającymi to zadanie. W Tabeli 4 zostały zebrane wszystkie makra wspomagające samodzielne definiowanie 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 korespondujące z makrem PHP_FE (gdy korzystasz z makr z serii ZEND, do deklaracji funkcji należy użyć analogicznego do PHP_FUNCTION makra ZEND_FUNCTION). Jak widać na Listingu 7, nie zostało tu w typowy 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 przekazanych do funkcji. Można je wykorzystać do sprawdzenia, czy przekazana ze skryptu PHP liczba argumentów jest poprawna. Jeśli nie, należy wywołać makro WRONG_PARAM_COUNT, które kończy działanie funkcji, informacją o błędzie zbliżoną do tej:
Warning: Wrong parameter count for rpn_calculation in /home/marcin/public_html/test.php on line 3

ZEND_NAMED_FE(php_name, arg_types)

ZEND_FALIAS(name, alias, arg_types)

PHP_FE(name, arg_types)

PHP_NAMED_FE(runtime_name, name, Stary odpowiednik makra ZEND_NAarg_types) MED_FE Pierwszym argumentem jest liczba parametrów przekazanych do funkcji podczas jej wywołania, drugi to łańcuch znaków opisujący typ parametrów oczekiwanych przez funkcję, dalej są to już zmienne, w których mają zostać zapisane parametry. Ważny jest tu drugi parametr, czyli łańcuch znaków opisujący parametry. Łańcuch ten może składać się z następujących znaków: • • • – 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 pozycji 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 funkcji zend _ parse _ parameters),
l

• •





Kod wygenerowany przez CodeGen_PECL do parsowania parametrów podanych podczas wywołania funkcji 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, ... )



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

• • •



–określa, że zmienna na pozycji tego znaku ma być typu boolean, r – określa, że zmienna na pozycji tego znaku ma wskazywać na zasób (ang. resource) – zwraca wartość typu zval* , a – określa, że zmienna na pozycji tego znaku ma być tablicą – zwraca wartość typu zval* , o – określa, że zmienna na pozycji tego znaku ma być obiektem dowolnej klasy – zwraca wartość typu zval* , O – określa, że zmienna na pozycji tego znaku ma być obiektem takiej samej klasy jak klasa kolejnego parametru na liście wywołania funkcji zend _ parse _ parameters (parametr ten nie będzie używany do przechowywania wartości kolejnego parametru przekazanego do skryptu PHP – w tym celu użyty zostanie kolejny parametr z listy parametrów) – zwraca wartość typu zval* , z – parametr dowolnego typu – zwraca wartość typu zval* , | (pionowa kreska) – oznacza, że następne parametry są opcjonalne, / – oznacza, że wartość poprzedniego parametru ma zostać skopiowana, a nie przekazana przez referencję, ! – oznacza, że poprzedni parametr może przyjmować wartość NULL .
b

}

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

48

www.phpsolmag.org

PHP Solutions Nr 5/2006

Zend API
Tabela 5. Makra zwracające wartość z funkcji – należy używać w tworzonych funkcjach, zamiast typowych instrukcji return wartosc Makro RETURN_RESOURCE(resource) RETURN_BOOL(bool) RETURN_NULL() RETURN_LONG(long) RETURN_DOUBLE(double) RETURN_STRING(string, duplicate) Opis Zwraca wskaźnik do zasobów Zwraca typ logiczny Zwraca wartość NULL Zwraca cyfrę całkowitą Zwraca liczbę zmiennoprzecinkową Zwraca napis – drugi parametr określa, czy napis ma zstać zduplikowany

Dla zaawansowanych

Często przydatne i ułatwiające wiele zadań może okazać się wywołanie z poziomu naszego rozszerzenia jednej z funkcji PHP. Nie da się tego zrobić w sposób naturalny, tak jak zrobilibyśmy to wywoł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 )

RETURN_STRINGL(string, length, dupli- Zwraca napis o długości określonej drucate) gim parametrem. Trzeci parametr określa, czy napis ma być zduplikowany. Makro to jest szybsze od RETURN_STRING. RETURN_EMPTY_STRING() RETURN_FALSE RETURN_TRUE 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, zostałby on zwrócony do zmiennej umieszczonej 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 zapisania długości, a do odczytu wymaganego typu klasy. Funkcja zend_parse_parameters po zakończeniu swojego działania zwraca wartość SUCCESS w przypadku powodzenia lub FAILURE, gdy wystąpi błąd (w przyZwraca pusty napis Zwraca logiczny fałsz Zwraca logiczną prawdę padku błędu, poza zwróceniem odpowiedniej wartości, powoduje ona także wyświetlenie ostrzeżenia z informacją np. o niepoprawnym typie parametrów użytkownikowi 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 zadanie odpowiednio skonwertować zwracaną wartość tak, aby mogła ona być poprawnie zinterpretowana przez Zend Engine. Wszystkie makra, z których można korzystać do zwracania wartości z funkcji, zebrano w Tabeli 5.

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

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 zamianę wszystkich nawiasów kwadratowych występujących w wyrażeniu matematycznym, na nawiasy okrągłe. Jak widać, w wywołaniu funkcji, za pierwszy parametr podaliśmy wywołanie makra CG(function_table), które na podstawie swojego parametru zwraca tabele funkcji. 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 nazwą zmiennej, do której ma zostać zapisany wynik działania wywoływanej funkcji. W naszym przypadku jest to zmienna phrase, więc ta sama, która zostaje poddana działaniu funkcji. Ostatnie dwa argumenty funkcji call_user_function to liczba parametrów, jakie mają zostać przekazane do wywoływanej funkcji oraz tablica zawierająca te parametry. Prawdopodobnie zauważyliście już, że większość parametrów oczekiwanych przez funkcję call_user_function jest typu zval. Musimy więc je odpowiednio utworzyć. Po zdeklarowaniu parametru, musimy go zainicjować jeszcze przed nadaniem mu jakiejkolwiek wartości. Służy do tego makro MAKE_STD_ZVAL. Następnie po zainicjowaniu zmiennej, możemy nadać jej wartość korzystając z makr z serii ZVAL_NAZWATYPU. Makra te zostały zebrane 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);

PHP Solutions Nr 5/2006

www.phpsolmag.org

49

Dla zaawansowanych

Zend API

Tutaj natomiast potrzebowaliśmy na to 10 linijek. Zauważmy także, że przy drugim wywołaniu funkcji str_replace (zamieniającym nawias ] na )), nie trzeba już inicjować 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 konwertujące zawartość zval pomiędzy różnymi 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 samodzielnie korzystać z jej pól).

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

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

Funkcje specjalne Zend API

To, z czym zapoznaliście się do tej pory, pozwoli Wam już na pisanie nawet dość złożonych rozszerzeń. Przyjrzyjmy się teraz funkcjom specjalnym Zend API (przypomnijmy sobie, jak konfigurowaliśmy za pomocą XML-a funkcje wewnę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 strukturze. Przyjrzyjmy się więc Listingowi 9 oraz 10. Jak się prawdopodobnie już domyślacie, struktura zend_module_entry, służy do pisania tworzonego rozszerzenia. Pierwsze jej cztery pola zawsze zastępuje się makrem STANDARD_MODULE_ HEADRE, kolejne pole jest nazwą rozszerzenia – w naszym przypadku jest to RPN. Następnie podajemy nazwę tablicy opisującej funkcje oferowane przez rozszerzenie (u nas struktura ta nazywa się RPN_functions – Listing 6). Kolejne pięć pól struktury, to deklaracje funkcji wewnętrznych rozszerzenia – odpowiednio MINIT, MSHUTDOWN, RINIT, RSHUTDOWN, oraz MINFO. Jeśli któraś z tych funkcji jest w naszym rozszerzeniu zbędna, makro z serii PHP_ dodane podczas generowania pliku źródłowego w C przez skrypt CodeGen_PECL, zastępujemy wartością NULL. Wszystkie pozostałe pola struktury zostają wypełnione poprzez wywołanie makra STANDARD_MODULE_ PROPERTIES. Definicje funkcji wewnętrznych zostały wygenerowane przez CodeGen_PECL. Tak jak w przypadku kodu funkcji oferowanych przez nasze rozszerzenie, tak i w przypadku funkcji we-

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

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

Rysunek 2. Budowa struktury zval – w PHP wszystkie wartością zmiennych są typu zval Tabela 6. Makra przypisujące odpowiednią wartość zmiennej typu zval – pierwszy parametr makra to zawsze nazwa zmiennej typu zval Nazwa makra
ZVAL_RESOURCE(z, l) ZVAL_BOOL(z, b) ZVAL_FALSE(z) ZVAL_TRUE(z) ZVAL_NULL(z) ZVAL_LONG(z, l) ZVAL_DOUBLE(z, d) ZVAL_STRING(z, s, dup)

Opis
Przypisuje zmiennej zval wartość zasobu. Parametr l jest wartością typu long Przypisuje zmiennej zval wartość logiczną – boolean. Parametr b przyjmuje wartości 0/1. Jest odpowiednikiem wywołania makra ZVAL_BOOL(z, 0) Jest odpowiednikiem wywołania makra ZVAL_BOOL(z, 1) Przypisuje zmiennej zval wartość NULL Przypisuje zmiennej zval wartość typu long, przekazaną w drugim parametrze (l). Przypisuje zmiennej zval wartość typu double, przekazaną w drugim parametrze (d). 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ć przekazana przez referencję Przypisuje zmiennej zval wartość typu string (napis). Parametry: 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ę Przypisuje zmiennej zval pusty ciąg znaków 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 kopiowania, czy też nie (0).

ZVAL_STRINGL(z, s, l, dup)

ZVAL_EMPTY_STRING(z) ZVAL_ZVAL(z, zv, dup, dtor)

50

www.phpsolmag.org

PHP Solutions Nr 5/2006

Zend API
wnętrznych Zend API, definicja funkcji realizowana jest z wykorzystaniem odpowiednich makr. Makra te noszą nazwy PHP_nazwaFunkcji_FUNCTION, gdzie nazwa funkcji to odpowiednio MINIT, MSHITDOWN, RINIT, RSHUTDOWN oraz MINFO. Funkcja MINFO została już prawdopodobnie wystarczająco dobrze wygenerowana przez skrypt, można jednak się jej przyjrzeć i w razie konieczności zmodyfikować wygenerowany kod. Nasze rozszerzenie wykorzystuje dodatkowo funkcje RINIT oraz RSHUTDOWN do zainicjowania stosu oraz kolejki wykorzystywanej podczas obliczeń (RINIT), a następnie zwolnienia wykorzystywanych przez te struktury zasobów (RSHUTDOWN). Jeśli dobrze pamiętamy, ciało tych funkcji zostało określone jeszcze na poziomie pliku XML. Czasami zdarza się, że chcielibyśmy napisać własną funkcję wykonywaną automatycznie podczas zmiany wartości zmiennej 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; }

Dla zaawansowanych

tości queue_size, musimy jej nazwę umieścić jako czwarty parametr makra STD_PHP_INI_ENTRY. Jako że często potrzebna 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)

Jak widać, nowa wartość przekazywana jest w zmiennej new_value. Oczywiście, aby funkcja ta była automatycznie wywoływana po zmianie war-

Poszczególne parametry funkcji stworzonej za pomocą tego makra, to odpowiednio – struktura opisująca zmienną, nowa

Zmienne php.ini

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

Do pełni szczęścia brakuje nam jeszcze 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 informuje PHP, która globalna zmienna ma być modyfikowana z poziomu pliku php.ini – zajrzyjmy do pliku nagłówkowego php_RPN.h, tam są zdeklarowane zmienne globalne. Parametry makra, to odpowiednio: nazwa zmiennej, pod jaką ma ona być modyfikowana z poziomu 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). Kolejny parametr jest nazwą metody, która ma zostać wywołana w momencie zmiany wartości zmiennej. Dostępne są standardowe funkcje obsługi zdarzenia 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. Ostanie dwa parametry są strukturami opisującymi zmienne globalne naszej aplikacji – nie musimy się nimi przejmować, gdyż nawet jeśli chcemy ręcznie dodać zmienną modyfikowaną z poziomu pliku php.ini, ostatnie dwa parametry pozostaną takie, jak wygenerował je skrypt CodeGen_PECL.

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

PHP Solutions Nr 5/2006

www.phpsolmag.org

51

Dla zaawansowanych

Zend API

wartość zmiennej, długość zmiennej oraz trzy opcjonalne argumenty, które w tej chwili nas nie interesują. Zanim będziemy mogli odczytać zmienne zdefiniowane wyżej, musimy jeszcze w funkcji MINIT wywołać makro ZEND_INIT_MODULE_GLOBALS. Makro to przyjmuje trzy parametry – nazwę rozszerzenia (u nas jest to RPN), nazwę funkcji, która ma być wywołana podczas inicjowania zmiennych, nazwę funkcji, która ma być wywołana podczas niszczenia zmiennych. Należy także pamiętać o tym, aby zainicjować w wywołaniu w funkcji MINIT makra 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.

Rysunek 3. Działanie przykładowej aplikacji korzystającej z utworzonego rozszerzenia

skrypt CodeGen_PECL, kompilacja i instalacja rozszerzenie jest zaskakująco prosta. Sprowadza się do wydania w katalogu z rozszerzeniem następujących komend:
phpize ./configure make sudo make install

RPN.rpn_stack_size = wielkość_stosu RPN.rpn_queue_size = wielkość_kolejki

Jeśli wszystko przebiegło pomyślnie, powinniśmy móc uruchomić przykładową aplikację PHP korzystającą z naszego rozszerzenia (patrz Rysunek 3).

Na zakończenie

Kompilacja i uruchomienie

Przetestujmy teraz nasze rozszerzenie. Dzięki plikom wygenerowanym przez

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

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>

Artykuł ten nie wyczerpuje tematu tworzenia rozszerzeń dla PHP. Brak dobrej dokumentacji na pewno utrudnia życie. Z drugiej strony duża liczba standardowych rozszerzeń dostępnych ze źródłami PHP bardzo dużo pomaga przy pisaniu własnych rozszerzeń. Trzeba jednak być przygotowanym na konieczność analizowania cudzego kodu np. parametrów przyjmowanych przez interesujące nas makro. Jeśli zainteresował Was temat rozszerzeń i chciałbyście pogłębić swoją wiedzę w tej dziedzinie, polecam rozpocząć pracę od modyfikacji naszego rozszerzenia. Zostało ono celowo stworzone w sposób, który pozwoli Wam na dokonanie w nim wielu usprawnień. Zacznijcie od próby wyeliminowania konieczności inicjalizowania stosu oraz kolejki – niech ich rozmiar 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 razu PHP-owe zval. n

O autorze
Marcin Staniszczak jest studentem pierwszego roku informatyki studiów uzupełniających magisterskich na WSHE w Łodzi. W PHP programuje od wielu lat. Jest autorem kilkunastu publikacji o tematyce PHP. Jest autorem frameworka MVC dla PHP5 (web.framework) oraz systemu szablonów dla PHP5 (web.template).

52

www.phpsolmag.org

PHP Solutions Nr 5/2006

Dla zaawansowanych

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

Stopień trudności: lll

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.

N
W SIECI
l l

a 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 zapewni nam graf przygotowanych do użycia obiektów czy np. umożliwi łatwe konfigurowanie dekoratorów.

dową implementacją zostanie jak w przypadku poprzednich artykułów umieszczony na stronie http://flexi.sf.net/.

Czym jest kontener IoC?

l

l

l

http://en.wikipedia.org/wiki/ Hollywood_Principle – Hollywood Principle http://www.theserverside. com/tt/articles/article.tss?l=I OCBeginners – wprowadzenie do Dependency Injection http://www.martinfowler.com/ articles/injection.html – Inversion of Control Containers/Dependency Injection pattern http://picocontainer.codeha us.org/ – implementacja IoC dla Javy http://flexi.sf.net/ – budowany przez nas framework, pełne źródła omawiane w artykule

Nie dzwoń do nas, my zadzwonimy do Ciebie

Powyższe zdanie to dobrze znane programistom tzw. prawo Hollywood (ang. Hollywood Principle). W myśl tego paradygmatu tworzy się oprogramowanie spójne, z luźnymi powiązaniami pomiędzy obiektami, łatwe do testowania i utrzymania. W obecnym artykule z cyklu Wzorce projektowe i dobre praktyki programistyczne wspólnie stworzymy prosty kontener IoC, którego działanie w dużym uproszczeniu opiera się na wspomnianym prawie Hollywood. Kod kontenera wraz z przykła-

Kontener IoC to nic innego jak konfigurowalna fabryka obiektów, która potrafi 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 pisać książki. Wbrew pozorom jest to proces dość skomplikowany i jeżeli robiony

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

Potrzebna będzie znajomość obiektowego programowania w PHP.

Z artykułu dowiesz się, jak stworzyć własny kontener IoC w myśl paradygmatu Hollywood Principle.

54

www.phpsolmag.org

PHP 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 sprawdzonych wzorców pokazujących, jak poprawnie 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 temu zagadnieniu. Zastosowanie kontenera IoC do składania obiektów w funkcjonalne grupy może znacznie ułatwić budowanie i testowanie złożonych aplikacji. Budowanie dlatego, że obiekty komponujemy w funkcjonalne grupy dopiero podczas uruchamiania aplikacji. Oznacza to, że praktycznie w dowolnym momencie życia aplikacji możemy 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 interfejsów i silnego typowania. Od pewnego czasu jednak obie te funkcjonalności dostępne są w PHP, dlatego powinniśmy je stosować.

cję klasy odpowiadającej za zarządzanie użytkownikami. O tym, jak to zrobić, dowiecie 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 pomocy Unit Testów (testów jednostkowych).

Pamiętajmy, że relacje pomiędzy obiektami pojawiają się dopiero w momencie uruchomienia aplikacji. Słabe wiązanie powoduje też, że klasy są bardziej elastyczne i generyczne, co oznacza, że można je wykorzystywać w większej ilość przypadków. Wracając teraz do naszego przykładu, można byłoby stworzyć klasę do uwierzy-

�������

�������

�������

�������

�������

�������

Przykład z życia wzięty

Wyobraź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żytkowników. Klasa ta operowała na bazie danych. Jako że była kluczowa w działaniu aplikacji, używaliśmy jej w wielu miejscach. Jeżeli nie wiedzieliśmy jeszcze o istnieniu kontenera IoC, do przekazywania tej klasy pomiędzy różnymi warstwami aplikacji mógł posłużyć nam wzorzec singleton lub registry. Wyobraźmy sobie teraz, że klient, dla którego została napisana aplikacja, wymaga modyfikacji bazy użytkowników, jego firma powiększa się i została podjęta decyzja o migracji wszystkich baz użytkowników do centralnej bazy działającej w oparciu o LDAP. Wyobraźmy sobie przeszukiwanie całej aplikacji w sytuacji, gdy umieszczaliśmy wywołania klasy ręcznie lub poprzez wzorzec singleton czy registry. Dodatkową bolączką na pewno będzie przetestowanie takiej aplikacji. Nie chcemy wpaść w takie tarapaty. Gdybyśmy stosowali interfejsy i kontener IoC, sprawa byłaby bardzo prosta. Należałoby stworzyć nową klasą implementującą odpowiedni interfejs i podmienić w pliku konfiguracyjnym kontenera defini-

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

PHP Solutions Nr 5/2006

www.phpsolmag.org

55

Dla początkujących

Jak zbudować własny kontener IoC?

telnienia i autoryzacji poprzez LDAP, przetestować ją poza aplikacją, co jest z całą pewnością łatwiejsze, a następnie umieścić przy pomocy kontenera IoC w aplikacji. Takie podejście zaoszczędzi nam sporo czasu! Dodatkowo klasa taka mogłaby z dużym prawdopodobieństwem zostać bez zmian wykorzystana w innych projektach. Wróćmy teraz do wymagań stawianych kontenerowi IoC, po pierwsze taki kontener powinien móc powoływać do życia dowolnie złożone drzewa obiektów. Wyobraźmy sobie obiekt, który do swojego działania potrzebuje dwóch innych obiektów, które z kolei też potrzebują zewnętrznych obiektów do poprawnego działania. W taki sposób możemy utworzyć dowolnie skomplikowane drzewo obiektów (Rysunek 1). Kontener IoC w takiej sytuacji powinien powołać do życia obiekt będący korzeniem całej struktury, a następnie powołać automatycznie pozostałe obiekty i poskładać je w drzewo. Tylko skąd kontener ma wiedzieć, jakie obiekty powołać i jak je poskładać? Obiekty składamy dokładnie tak, jak robi się to ręcznie: podając obiekt do konstruktora, settera lub przypisując go do zmiennej publicznej. Najlepiej zilustrować to na przykładzie. Załóżmy, że obiekt A wymaga do swojego działania zewnętrznego obiektu B. Obiekt B możemy przekazać obiektowi A na trzy sposoby: • • •
$a = new A(new B())

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

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

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

��������

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

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

– poprzez kon-

struktor,

$a->setB(new B()) $a->b = new B()

– poprzez setter, – poprzez zmienną

publiczną.

Zapomnijcie zatem o bardziej archaicznych metodach takich jak singleton, rejestr czy też ręczne powoływanie obiektu B wewnątrz obiektu A. Te metody tworzą silne wiązania pomiędzy sobą i w rezultacie zmniejszają elastyczność kodu. Wiemy już, jak kontener powinien poskł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 obiektów. Plik ściśle definiuje, jakie obiekty, stałe liczbowe lub znakowe należy wstawić, w odpowiednie parametry konstruktora czy też settera. Format pliku nie ma znaczenia, powinien być jedynie czytelny, gdyż będzie trzeba napisać go ręcznie. Dlatego w naszej aplikacji parsowa-

?>

);

56

www.phpsolmag.org

PHP Solutions Nr 5/2006

Jak zbudować własny kontener IoC?
nie pliku konfiguracyjnego zostanie wydzielone do osobnych klas tak, aby każdy mógł łatwo zmienić format i źródło danych 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 dodatkowo będzie za nas sprawdzać poprawność utworzonego pliku XML. My plik konfiguracyjny zbudujemy w oparciu o tablice asocjacyjne. Druga metoda definiowania drzewa jest rozszerzeniem pierwszej, wymaga jednak dodatkowo zastosowania w kodzie silnego typowania. Wyobraźmy sobie, że obiekt A wymaga do swojego poprawnego 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 klasy A można zakodować w następujący sposób:
class A { public function __construct(BI $b){} public function setB(BI $b){} }

Dla początkujących

tami możemy być prawie pewni, że będziemy mieli z tym do czynienia. Kontener, który napiszemy, będzie jedynie posiadał opcję samego składania obiektów

z definicji znajdujących się w pliku konfiguracyjnym. Istnieje jeszcze jedna metoda wiązania obiektów bardzo podobna do autowią-

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

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ę obiektów, które ma zdefiniowane w swoim pliku konfiguracyjnym, automatycznie powoła je do życia i umieści w obiekcie A. Ta metoda składania obiektów jest bardzo wygodna, gdyż nie wymaga tworzenia skomplikowanych plików konfiguracyjnych, niesie jednak ze sobą pewne niebezpieczeństwo. Wyobraźmy sobie, że klasa A wymaga do działania dowolnej klasy implementującej interfejs BI, w pliku konfiguracyjnym mamy zdefiniowane dwie klasy implementujące interfejs BI, którą więc powinniśmy wybrać? Takie problemy nie będą się pojawiać w projektach prostych, gdzie plik konfiguracyjny zawiera kilka obiektów, ale w projektach z kilkudziesięcioma obiek-

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

PHP Solutions Nr 5/2006

www.phpsolmag.org

57

Dla początkujących

Jak zbudować własny kontener IoC?

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

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

Przy takiej budowie klasy i zastosowaniu trzeciej metody składania obiektów kontener IoC sprawdzi, czy klasa ma jakiś setter badając, czy zawiera metody rozpoczynające się od słowa set a następnie sprawdzi, czy ma zarejestrowane w pliku konfiguracyjnym klasy, których nazwa pokrywa sie z pozostałą częścią nazwy settera, w naszym przypadku jest to klasa B. Ta metoda również niesie ze sobą pewne niebezpieczeństwo. Musimy uważać na konwencję nazywania setteró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 budowy kontenera, zdefiniujmy teraz strukturę pliku konfiguracyjnego.

{

Konfiguracja kontenera

Do konfigurowania kontenera IoC wykorzystamy prosty model obiektowy i taListing 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()); } } ?> } ?>

58

www.phpsolmag.org

PHP Solutions Nr 5/2006

Jak zbudować własny kontener IoC?
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ć formaty i źródła danych konfiguracyjnych, a w ekstremalnych sytuacjach będziemy mogli konfigurować kontener nie posiadając w ogóle plików konfiguracyjnych. Listing 1 przedstawia wycinek skryptu index.php. Możemy tu znaleźć dwie sekcje: jedna bardzo długa, konfigurująca obiekty i druga znacznie krótsza, uruchamiająca front kontroler. Teraz przyjrzyjmy się Listingowi 2, gdzie znajduje się ten sam skrypt index.php, jednak tym razem konfigurację obiektów i ich powołanie do życia przejął na siebie kontener IoC. Już na pierwszy rzut oka widać, że skrypt jest dużo prostszy. Warto dodać, że im większe będzie drzewo obiektów, które musimy utworzyć, tym większy będzie zysk na czytelności kodu. Na Listingu 3 znajduje się konfiguracja 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 momencie wielu z Was zapewne złapie się za głowę i powie – zaraz, ale przecież konfiguracja zajmuje więcej miejsca niż Listingu 1, tak więc co właściwie zyskujemy, skoro ilość kodu, którą musimy napisać dla kontenera jest w sumie większa, niż jakby go nie było. Na szczęście w ogólnym rozrachunku nie jest to prawda. Po pierwsze konfigurację piszemy raz, a wykorzystujemy wiele razy – w ramach jednej lub kolejnych aplikacji pisanych 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 konfiguracyjnego. Dodatkowo, jeżeli do pliku konfiguracyjnego przeniesiemy wszystkie informacje o budowie obiektów, automatycznie stworzymy świetną dokumentację, w jednym miejscu będzie zawarta pełna informacja o strukturze aplikacji. Wróćmy teraz do pliku konfiguracyjnego. Tylko na pierwszy rzut oka może wydawać się on skomplikowany. Po pierwsze wszystkie definicje obiektów umieszczone są w jednej tablicy asocjacyjnej, każdy klucz tej tablicy jednoznacznie identyfikuje obiekt. Na postawie tego identyfikatora z kontenera będą pobierane obiekty. Zwróćmy uwagę, że w jednym pliku można zdefiniować wiele konfiguracji dla jednej klasy, wystarczy, aby były one identyfikowane przez inne klucze w tablicy. Pod identyfikatorem obiektu znajduje się kolejna tablica asocjacyjna, która ma jasno zdefiniowany zestaw kluczy: • • • – nazwa klasy, – położenie pliku z definicją klasy, singleton – flaga mówiąca, czy obiekt ma zostać utworzony tylko raz i potem umieszczony w cache'u, czy też za każdym razem ma być tworzona kolejna instancja obiektu,
className file

Dla początkujących





– tablica asocjacyjna, w której kluczem jest nazwa atrybutu obiektu, a wartością zmienna, którą należy podstawić pod ten atrybut, w przypadku stałych znakowych lub liczbowych wpisujemy tu ich wartość, w przypadku referencji do obiektów wpisujemy znak & i identyfikator obiektu zdefiniowany w kontenerze IoC, constructorParams – tablica z parametrami przeznaczonymi dla konstruktora obiektu, tym razem nie jest to tablica asocjacyjna, znaczenie ma w niej kolejność umieszczanych wartości, będą one w tej samej kolejności umieszczone w konstruktorze podczas powoływania obiektu do życia, zasady wpisywania parametrów są takie same jak powyżej.
properties

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

PHP Solutions Nr 5/2006

www.phpsolmag.org

59

Dla początkujących

Jak zbudować własny kontener IoC?

Na Rysunku 2 widać diagram UML, a na Listingach 4–8 implementacje klas mapujących plik konfiguracyjny do modelu obiektowego. Każda z klas przedstawionych na diagramie mapuje jeden do jednego którąś z danych konfiguracyjnych. Klasa ProperyParamMap odpowiada za mapowanie danych wstawianych do obiektów przy pomocy setterów, ConstructorParamMap spełnia to samo zadanie w przypadku konstruktora obiektu. Natomiast klasy 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.

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;

Kontener IoC

Przejdźmy teraz do implementacji samego kontenera IoC. Po pierwsze każda implementacja kontenera musi implementować interfejs IoCContainer. Interfejs ten ma tylko jedną metodę publiczną create() (Listing 9). Pełna implementacja kontenera w podstawowej formie pokazana jest na Listingu 10. Klasa DefaultIoCContainer implementuje interfejs IoCContainer. Do swojego poprawnego działania wymaga klasy ApplicationMap, która zawiera konfigurację kontenera. Klasa ta posiada tylko jedną metodę publiczną create(). Metoda na podstawie podanego identyfikatora obiektu zwraca instancję tego obiektu wraz z drzewem podrzędnych mu obiektów, jeżeli takowe zostały zdefiniowane w pliku konfiguracyjnym. Metoda create pobiera konfigurację dla danego identyfikatora, następnie sprawdza, czy obiekt jest typu singleton (tzn. że podczas życia aplikacji może zostać utworzona tylko jedna instancja danego obiektu). Jeżeli tak, to sprawdzamy, czy nie został już wcześniej utworzony. Jeżeli tak to zwracamy go, jeżeli nie to tworzymy go przy pomocy metody createObject. Metoda, po pierwsze, ładuje plik z definicją klasy, którą ma powołać do życia – ścieżka do pliku zdefiniowana jest w konfiguracji. 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-

}

60

www.phpsolmag.org

PHP Solutions Nr 5/2006

Jak zbudować własny kontener IoC?
ratora new. Jeżeli korzystacie z wersji PHP 5.1.3 lub nowszej możecie tworzyć obiekty z parametrami do konstruktora przy pomocy Reflection API i metody newInstanceArgs. Po utworzeniu obiektu jego instancja przekazywana jest do metody setProperties, gdzie do obiektu przy pomocy setterów dodawane są zmienne, które w pliku konfiguracyjnym zostały zdefiniowane w sekcji properties. Informacja o tym, jakiego settera użyć, nie jest zapisana w konfiguracji, kontener tworzy ją sam. Poprawna nazwa settera składa się z przedrostka set i nazwy propertisa ze zmienioną pierwsza literą na wielką. Ta konwencja nazywania metod dostępowych do atrybutów obiektów nie powinna być Wam obca, jest zgodna z konwencją stosowaną w innych aplikacjach.

Dla początkujących

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;

Warto jeszcze zwrócić uwagę na dwie metody getConstructorParamsR ecursively i getPropertyValueRecur sively. Obie są bardzo podobne. Na podstawie typu parametru, który może przyjąć dwie wartości value lub reference, kontener pobiera z konfiguracji stałą lub ponownie uruchamia swoją metodę create, aby utworzyć obiekt. Dzięki temu kawałkowi kodu kontener może zwracać całe drzewa obiektów. O tym, czy parametr jest typu value, czy reference, decyduje obecność & przy definicji wartości parametru w pliku konfiguracyjnym.

Podsumowanie

}

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;

Kontener IoC, którego uczyliśmy się tutaj razem zbudować, jest od jakiegoś czasu wykorzystywany w codziennej pracy w mojej firmie. Z doświadczenia wynika, że jego wdrożenie ułatwiło nam pisanie aplikacji. Najwięcej zyskaliśmy w kwestii testów i przejrzystości kodu. Dodatkowo spora część klasy po przystosowaniu do specyfiki kontenera IoC – dostarczamy obiekty zewnętrzne przez konstruktor i settery – stała się na tyle elastyczna, że z powodzeniem bez przepisywania zaczęliśmy wykorzystywać je w kolejnych projektach. Udało się nam też usunąć z podstawowego kodu aspekty związanie z autoryzacją, logowaniem i walidacją. Jak to zrobiliśmy? O tym przeczytacie w kolejnych artykułach w magazynie PHP Solutions. n

}

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

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

PHP Solutions Nr 5/2006

www.phpsolmag.org

61

Dla zaawansowanych

XML i PHP w praktyce
Guillaume Ponçon

Stopień trudności: lll

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 XMLa przy użyciu co najmniej kilku technik...

S
W SIECI
• http://www.w3.org/XML/ – oficjalna specyfikacja standardu XML • http://www.saxproject.org/ – strona projektu SAX • http://www.w3.org/DOM/ – oficjalna specyfikacja standardu DOM • http://books.evc-cit.info/ odbook/book.html – oficjalna specyfikacja formatu OpenDocument

tandard XML, czyli eXtensible Markup Language został określony przez World Wide Web Consortium (W3C). Obecnie jest przeważnie wykorzystywany jako format gromadzenia i wymiany danych (np. SXW, ODT, RDF, RSS) oraz tworzenia specjalistycznych języków opartych na znacznikach – jest więc metajęzykiem. Każdy dokument XML zawiera dane uporządkowane w hierarchii drzewiastej. Spójrzmy na Listing 1: przedstawiamy na nim przykładowy 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ędnymi lub potomnymi). Składnia każdego dokumentu XML musi być ściśle przestrzegana (nie wolno np. pozostawiać niedomkniętych znaczników; można też określić inne zasady korzystając z plików

DTD czy XML Schema), gdyż w innym wypadku program odczytujący ten dokument zgłosi błąd.

Techniki przetwarzania XML-a w PHP

Pisząc skrypty w języku PHP możemy swobodnie korzystać z dokumentów XMLowych. Do ich przetwarzania służą rozszerzenia języka PHP o nazwach: SAX,

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

Przydatna będzie znajomość podstaw programowania obiektowego w PHP5.

Przedstawimy zasady działania rozszerzeń języka PHP: DOM, SAX oraz SimpleXML oraz pokażemy, jak korzystając z SimpleXML tworzyć i odczytywać dokumenty XML-owe, w tym pliki OpenOffice.org i dane programu Ganttproject.

62

www.phpsolmag.org

PHP Solutions Nr 5/2006

Dla zaawansowanych PEAR

DOM-XML (PHP4), DOM (PHP5) oraz SimpleXML (tylko PHP5). Omówimy pokrótce SAX, DOM-XML oraz SimpleXML, podając przykłady wykorzystania każdego z tych rozszerzeń.

SAX

SAX (http://www.saxproject.org/, http:// www.php.net/xml).oznacza Simple API for XML i umożliwia sekwencyjny odczyt dokumentów XML. Pozwala na odczyt praktycznie każdego rodzaju dokumentów XML, nawet tych, które zostały utworzone niepoprawnie. SAX nie umożliwia natomiast zapisu ani modyfikacji danych. Na Listingu 2 przedstawiamy skrypt służący do odczytu dokumentu XML-owego 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 handlerami (uchwytami), które są wywoływane przez język PHP podczas odczytu naszego dokumentu. Musimy je sami utworzyć, zanim się do nich odwołamy. W naszym przykładzie zdefiniujemy najpierw dwa podstawowe uchwyty: początku (startElement()) i końca (endElement()) każdego elementu XML-owego, uruchamiane odpowiednio, gdy parser natrafi na znacznik rozpoczynający (np. <author>) i kończący (np. </author>) dany element. W przypadku napotkania elementu danych pomiędzy dwoma znacznikami XML wywoływany jest uchwyt dataHandler. Handlery początku i końca elementu definiujemy przy pomocy funkcji xml_set_element_handler(), a uchwyt elementu danych używając xml_ set_character_data_handler(). Przetwarzanie dokumentu XML rozpoczniemy korzystając z funkcji xml_parse(), jako jej parametry podając: instancję parsera (zasób $xml_parser), źródło dokumentu w wersji tekstowej (np. odczytane z pliku; my użyjemy zmiennej $xml, w której umieścimy fragment kodu z Listingu 1. Na koniec, zwalniamy zasób $xml_parser korzystając z funkcji xml_parser_free().

Rysunek 1. Okno programu Ganttproject: po lewej stronie wykres Gantta

Instalacja

Aby 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 zestawu 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 prostego 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"; } }

DOM-XML

Rozszerzenie DOM-XML umożliwia przetwarzanie plików XML w PHP zgodnie ze standardem DOM (skrót od Document Object Model). DOM-XML pozwala za-

PHP Solutions Nr 5/2006

www.phpsolmag.org

63

Dla zaawansowanych
Należy wiedzieć, że omówiona metoda getElementsByTagName() odnajduje wszystkie węzły określone daną parą znaczników (np. <title>...</title>) i sporządza ich listę, więc nawet, gdy istnieje tylko jeden węzeł o wybranej nazwie (jak u nas), musimy go wskazać (np. używają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 korzystając z atrybutu nodeValue. Utworznie nowego węzła następuje poprzez użycie aż dwóch metod: createElement(), która pozwala na utworzenie 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 odnalezionego elementu drzewa (title), niższego o jeden poziom od korzenia drzewa (u nas document). Wreszcie, do zapisu gotowego dokumentu XML w pliku służy metoda save() obiektu $dom. Jak widać, rozwiązanie DOM jest skuteczne i spójne logicznie, ale niestety bardzo rozwlekłe i wymagające dużo kodu. Dlatego też jest relatywnie rzadko stosowane, choć zawiera bardzo pożyteczne narzędzia, takie jak np. XSLT (połączenie dokumentu XML i arkusza stylów XSL) czy XPath (język zapytań umożliwiający wyszukiwanie elementów i wartości w dokumentach XML, będący niejako XML-owym odpowiednikiem SQL-a).

Rysunek 2. Skrypt odczytujący dokument utworzony przez program Ganttproject i wyświetlający jego strukturę

RSS – podstawy

RSS (Really Simple Syndication, znane dawniej jako Rich Site Summary czy RDF Site Summary) to popularny i prosty format strumienia przesyłania newsów (przeważ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-releases-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 umieszczony na komputerze klienta lub na dowolnej witrynie internetowej, takiej jak np. prywatna strona domowa czy blog, a także niektóre programy pocztowe (np. Thunderbird). Wyświetlanie zawartości kanałów RSS jest możliwe również przy użyciu przeglądarki internetowej: po wpisaniu adresu RSS powinna się w niej pojawić pełna struktura i zawartość pliku XML z newsami.

równo na odczyt, jak i zapis (tworzenie i modyfikowanie) dokumentów XML. Standard DOM znajduje zastosowanie nie tylko w PHP, ale także w innych językach (m.in. Python, Java, JavaScript). Główną ideą DOM jest to, że struktura XML jest traktowana jako hierarchiczne drzewo węzłów (ang. nodes), z których każdy jest reprezentowany jako obiekt języka PHP (lub innego języka, dla którego istnieje implementacja DOM). W PHP obsługę standardu DOM umożliwiają rozszerzenia DOM-XML (PHP4, http://www.php.net/ domxml) i DOM (PHP5). Jak już powiedzieliśmy, skorzystamy z tego pierwszego. Na Listingu 3 przedstawiamy przykł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 wykonywaniu operacji na dokumencie. Następnie ładujemy kod XML w wersji tekstowej (ze zmiennej łańcuchowej $xml) korzystając z metody LoadXML obiektu $dom. Aby odczytać lub zmodyfikować dany wę-

zeł, musimy go najpierw odnaleźć metodą getElementsByTagName(), tworząc nowy obiekt (najlepiej o nazwie odpowiadającej nazwie węzła). Następnie używamy atrybutów i metod tego obiektu, umożliwiających odczytywanie i zapisywanie danych w zaznaczonym węźle. My wykorzystamy omówioną metodę do znalezienia węzła title (element pomiędzy znacznikami <title> i </title>; pamiętajmy, że posługujemy się cały czas przykładowym kodem XML z Listingu 1).

SimpleXML

SimpleXML (http://www.php.net/simplexml) to rozszerzenie, które pojawiło się wraz z PHP5. Jest bardzo łatwe w obsłudze i

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

64

www.phpsolmag.org

PHP Solutions Nr 5/2006

Dla zaawansowanych
plik RSS, a na Listingu 8 skrypt odczytujący strumień RSS spod adresu http: //rss.freshmeat.net/freshmeat/feeds/fmreleases-global, czyli informacje o nowych projektach programistycznych, które zostały zamieszczone na witrynie freshmeat.net. Ładowanie zestawu wiadomości następuje poprzez zwykłe załadowanie pliku znajdującego się pod wspomnianym adresem, po czym przystępujemy do ich wyświetlania w oknie przeglądarki: zaczynamy od wypisania tytułu kanału (element <title>, podrzędny wobec <channel>). Następnie iterujemy wiadomości, z których każda jest elementem podrzędnym wobec <channel> oznaczonym 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 zobaczymy zestaw wiadomości – zauważmy, że niektóre z nich zawierają również grafikę.

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://ganttproje ct.sourceforge.net. Zgodnie ze specyfiką wykresów Gantta, Ganttproject pozwala na wizualizację organizacji zadań i zasobów przydzielonych do wybranego projektu w czasie. Jedną z podstawowych funkcji programu Ganttproject jest tworzenie 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żytecznych operacji.

Rysunek 3. Przykład pliku tekstowego stworzonego w edytorze OpenOffice.org Writer

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 odczytywać zawartość dokumentu XML, a na Listingu 6, jak ją modyfikować. W obu przypadkach korzystanie z SimpleXML zaczynamy od utworzenia obiektu $simpleXml, do czego służy funkcja simplexml_load_string(), która tworzy dokument w oparciu o kod XML w postaci tekstowej (zawarty w zmiennej łańcuchowej). Podobnie, jak w przypadku DOM, w SimpleXML struktura dokumentu XML jest reprezentowana przez zestaw obiektów języka PHP, tyle, że są one tworzone automatycznie i mają nazwy odpowiadające nazwom węzłów (elementów) naszego dokumentu, a korzystanie z nich jest znacznie prostsze, co możemy sami zobaczyć porównując długość kodu w obu przypadkach: podczas, gdy w DOM trzeba było tworzyć rozwlekły skrypt, w SimpleXML wystarczy parę linijek! Co więcej, z elementów dokumentu XML korzystamy przeważnie (zarówno do odczytu, jak i zapisu) używając atrybutów (pól) obiektów poszczególnych węzłów. Pola te mogą zawierać zarówno treść tekstową, jak i listy elementów podrzędnych, które możemy iterować korzystając z instrukcji foreach. Znacznie prostsze niż w DOM jest również dodawanie nowych węzłów: musimy

jedynie skorzystać z metody addChild() i gotowe.

Odczyt strumienia RSS za pomocą SimpleXML

RSS to standard przesyłania wiadomości w Internecie, o którym więcej mówimy w Ramce RSS – podstawy. Na Listingu 7 przedstawiamy przykładowy

Manipulujemy wykresami Gantta za pomocą SimpleXML oraz Ganttproject

Wykres Gantta (ang. Gantt chart) to popularna metoda wizualizacji etapów i po-

Listing 5. Odczyt dokumentu przy wykorzystaniu SimpleXML (wyświetlanie informacji 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();

PHP Solutions Nr 5/2006

www.phpsolmag.org

65

Dla zaawansowanych
kolejne zadania w hierarchii zadań. Na Rysunku 2 przedstawiamy efekt działania naszego skryptu: wylistowaną w linii poleceń hierarchię zadań wraz ze wspomnianymi informacjami (datą początkową, czasem trwania i tytułem).

Czym jest przestrzeń nazw?

Przestrzeń nazw umożliwia podzielenie dużych plików XML na kategorie, co ułatwia 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 przestrzeni 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.

Modyfikujemy zadanie utworzone w Ganttproject

Modyfikacja istniejącego zadania pliku danych 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 zmienimy nazwę (parametr name) pierwszego zadania na wykresie Gantta.

Dodajemy zadanie

Rysunek 4. Zawartość pliku tekstowego model.odt

stępów wykonania projektu, często stosowana w firmach i innych organizacjach. Istnieje wiele narzędzi służących do sporządzania takich wykresów; jednym z nich jest opensourcowy program Ganttproject, o którym mówimy więcej w Ramce Czym jest Ganttproject?. Ponieważ 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. kolor przypisany do każdego zadania. Korzeniem (rootem) tego pliku jest element o nazwie <project>. Jego atrybuty zawierają różne informacje, takie jak data ostatniego odczytu czy wersja pliku. Podrzędne wobec <project> elementy pierwszego poziomu stanowią kategorie informacyjne: kalendarze, zadania, zasoby, przypisanie zasobów, zwolnienia, role i różne kategorie ustawień. Elementy drugiego i następnych poziomów zawierają informacje związane z każdą kategorią.

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 rekurencyjnej, dzięki której możemy przetwarzać
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>

Aby dodać zadanie do wykresu Gantta, musimy umieścić nowy element <task> w pliku XML (Listing 12). W przypadku SimpleXML 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 musieli nadać mu identyfikator będący numerem większym o jeden od ID ostatnio

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

Odczytujemy wykres Gantta z programu Ganttproject

Aby odczytać dokument programu Ganttproject, 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

66

www.phpsolmag.org

PHP Solutions Nr 5/2006

Dla zaawansowanych
cji wbudowanej PHP file_put_contents() oraz metody asXml() obiektu $project, który reprezentuje nasz dokument XMLowy. Tak, jak w poprzednich przykładach, dzięki zastosowaniu SimpleXML ograniczyliśmy zestaw czynności do niezbędnych, unikając pisania dodatkowego kodu, które miałoby miejsce np. w przypadku DOM.

Rysunek 5. Dokument tekstowy OpenOffice.org Writer generowany przez skrypt napisany w PHP

zdefiniowanego zadania. Ponieważ kolejny identyfikator jest zawsze o 1 większy od poprzedniego, wystarczy znaleźć największe ID. W tym celu sporządzimy najpierw listę wszystkich zadań korzystając z xpath, a następnie odnajdziemy największy identyfikator używając funkcji max() w pętli foreach. Po wykonaniu tych czynności musimy wydłużyć czas trwania zadania 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życiu metody addChild(), która jest dostępna 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-

Korzystamy z dokumentów OpenOffice.org za pomocą SAX

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>

OpenOffice.org to opensourcowy pakiet biurowy o ciągle rosnącej popularności. Korzystają z niego m.in. firmy, instytucje publiczne, organizacje pozarządowe i osoby prywatne z całego świata. Wszystkie formaty dokumentów OpenOffice.org (edytora, arkusza kalkulacyjnego, programu do tworzenia prezentacji i innych są oparte na XML-u. W wersji 2 tego pakietu wprowadzono format OpenDocument, którym się posłużymy w naszym artykule.

Kompozycja dokumentu OpenOffice.org

Każdy dokument OpenOffice.org to w rzeczywistości skompresowane archiwum zawierające pliki XML. Przykładowo, archiwum pliku example.odt utworzonego w OO Writer (edytorze tekstu) będzie zawierało tekst oraz style i obrazy osadzone w tekście. Głównym plikiem tego archiwum, leżącym w jego katalogu głównym jest plik content.xml, którego przykładową zawartość (fragment) prezentujemy na Listingu 13. Zawiera on dane naszego dokumentu 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.org

Zanim będziemy mogli odczytać lub zmodyfikować dokument OpenOffice (test.odt), musimy wydobyć z archiwum plik content.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

PHP Solutions Nr 5/2006

www.phpsolmag.org

67

Dla zaawansowanych
przedstawiającego kategorie zawartości. W kategorii office:body zawarte są dane naszego dokumentu. Odczytamy plik content.xml przy użyciu SAX oraz zmodyfikujemy go korzystają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 zawierającego zawartość naszego dokumentu z archiwum, odczyt pliku content.xml za pomocą SAX, dodanie tytułu i akapitu przy użyciu SimpleXML, zapis pliku na dysk. korzystając ze znanej już nam metody addChild(). Dla każdego węzła podajemy jego nazwę (text:p), zawartość (Nowy 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 pliku. Tak jak poprzednio, użyjemy metody asXml() aby uzyskać tekstową wertext

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

Zaczniemy od dołączenia biblioteki pclzip (pclzip.lib.php) i utworzenia obiektu $zip klasy PclZip, któremu jako parametr konstruktora przekazujemy nazwę archiwum, które chcemy rozpakować (test.odt). Następnie korzystając z metody listContent() tego obiektu przeszukamy listę plików zawartych w archiwum, aż natrafimy na content.xml. Jego 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żą zaletą 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 pomocy funkcji rozszerzenia SAX xml_parse_ into_struct() przekształcimy odczytaną zawartość pliku XML-owego w tablicę. Następnie użyjemy na tej tablicy pętli foreach, aby wyekstrahować z niej tytuły i akapity. Teraz utworzymy obiekt $xml, reprezentują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 akapitów, a pobierzemy go z pierwszego elementu 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:

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

68

www.phpsolmag.org

PHP Solutions Nr 5/2006

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

na samym końcu, gdy już dodamy ten plik do archiwum.

Generujemy dokument OpenOffice.org Writer na podstawie szablonu

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

Utworzymy 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 systemów szablonów HTML-owych, takich jak np. Smarty). Będzie to wymagało wydobycia pliku content.xml z archiwum szablonu (u nas będzie to model.odt) i wykorzystania go w celu utworzenia nowego pliku content.xml, zawierającego dane, które dodamy. Na koniec umieścimy ten nowy plik w archiwum będącym kopią naszego szablonu. Wbudowane do PHP rozszerzenie ZIP nie pozwala niestety na zapisanie pliku: będziemy więc wywoływać programy zip oraz unzip korzystając z funkcji shell_ exec(). Nic nie stoi również na przeszkodzie, 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 dokumentu ze strumienia RSS (Rysunek 5). Nasz przykład (Listing 15) jest przeznaczony dla PHP5; wymaga również dostępu do plików zip (używanego w celu dodania pliku do archiwum) i unzip (służącego do wydobycia pliku z archiwum) w systemie operacyjnym. Główną częścią naszego skryptu generują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 drugiej. W rzeczywistości, klasa OpenOffice będzie więc stanowiła API posiadające silnik szablonów do generowania dokumentó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ę OpenOffice w osobnym pliku, aby móc ją dodać 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 nazwę pliku OpenOffice Writer, który zamierzamy stworzyć. Następnie, dokonujemy przypisań i zakańczamy przez wywołanie

PHP Solutions Nr 5/2006

www.phpsolmag.org

69

Dla zaawansowanych
jeszcze zapisać otrzymany dokument jako content.xml oraz dodać ten plik do archiwum test.odt, zastępując nim poprzednio istniejący. Pobierzemy teraz dane ze strumienia RSS (z portalu Yahoo!), wykorzystując do tego, jak poprzednio, SimpleXML. Pozostało jeszcze utworzenie obiektu $oo klasy OpenOffice, któremu przez parametr konstruktora przekażemy nazwę pliku test.odt. Następnie korzystając z metod klasy Smarty przypiszemy zmiennym news i name załadowanego szablonu odpowiednio 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 dokumentami OpenOffice.org jest zakończona.

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

Podsumowanie

}

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

metody mergeAndWrite , która stworzy dokument, łą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 potrzeb (np. w ramach dostosowywania do innego niż Linux systemu operacyjnego). Następnie zainicjujemy zmienne prywatne $OoFile i $OoModel i przejdziemy do tworzenia konstruktora, w którym zdefiniujemy parametry (nasze dwie zmienne i ustawienia Smarty'ego). Ostatnim krokiem budowania naszej klasy będzie utworzenie metody publicznej mergeAndWrite(),

której zadaniem będzie tworzenie dokumentu, w którym połączymy dane otrzymane z pliku podanego w $this->OoFile oraz szablonu. Rozpoczniemy ją kopiując archiwum szablonu (model.odt) jako test.odt. Następnie wydobędziemy z tego archiwum plik content.xml i potraktujemy go jako szablon używając w tym celu odziedziczonej po klasie Smarty() metody fetch(), która zwraca skompilowany dokument powstający przy użyciu szablonu. Otrzymany dokument przypiszemy do zmiennej $content, a następnie zwolnimy zasób zajmowany przez skompilowaną wersję szablonu używając metody $this->clear_compiled_tpl(). Pozostało

Pokazaliśmy, jak łatwo i bezproblemowo korzystać z XML-a w PHP przy użyciu technik SAX, DOM i SimpleXML. Ta ostatnia metoda okazała się szczególnie warta uwagi, ze względu na połączenie szerokich możliwości (włącznie z wykorzystaniem XPath) z prostotą tworzenia opartego na niej kodu. Warto pamiętać, że czasami najlepsze może się okazać połączenie kilku technik, jak to ukazaliśmy na przykładzie aplikacji przetwarzającej dokumenty 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ą nowe rozszerzenia i biblioteki, np. wysokopoziomowe API do łatwiejszego przetwarzania wiadomości RSS czy danych pakietu biurowego, czy też pakiety ułatwiające tworzenie dokumentów XML według wzorca. W każdym przypadku, dobre opanowanie obsługi XML-a utoruje nam drogę do tworzenia nowoczesnych aplikacji, które mogą współpracować z przyjętymi standardami, nie będziemy więc skazani na izolację naszych rozwiązań od reszty świata. n

O autorze
Guillaume Ponçon jest architektem PHP i autorem Best practices PHP 5, francuskiej książki wydanej nakładem wydawnictwa Eyrolles.

70

www.phpsolmag.org

PHP Solutions Nr 5/2006

Projekty

Własne Google Video, czyli video streaming w PHP
Rafał Malinowski

Stopień trudności: lll

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!

P
W SIECI
l

ublikacja filmów w Internecie w oparciu o technologię Flash sprowadza się do wykonania trzech czynności: • konwersji filmu do formatu Flash Video (FLV) obsługiwanego przez Flash Playera, stworzenia odtwarzacza (Media Playera), który pobierze z serwera film i zaprezentuje go klientowi w przeglądarce, publikacji strony internetowej, w której osadzimy odtwarzacz.



l

l

l

l

l

l

l

http://ffmpeg.mplayerhq.hu/ – FFMPEG http://ffdshow.faireal.net/ mirror/ffmpeg/ – FFMPEG (windows binary) http://lame.sourceforge.net/ – LAME http://klaus.geekserver.net/ libflv/ – LIBFLV – program do konwersji plików FLV http://www.geovid.com/ –GEO-VID Video to Flash converter http://www.rivavx.com/ ?encoder – Riva FVL Encoder http://ffmpeg-php.sourceforge.net/ – PHP-FFMPEG http://www.php.net/ming – MING – moduł PHP do tworzenia SWF-ów





część druga – rozbudujemy przykład, który został opisany w pierwszej części i pokażemy, jak można zautomatyzować opisane czynności orazi jak uniezależnić się od systemu operacyjnego, na którym będzie działać galeria, część trzecia – wykorzystamy nabytą w części drugiej wiedzę i kod do stworzenia ostatecznej wersji serwisu – w pełni funkcjonalnej i w 100% uniwersalnej galerii filmów.



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

Aby opisać powyższe zadania, artykuł podzieliliśmy na trzy części: • część pierwsza – tzw. szybki start, bez zbędnej teorii pokażemy, jak opublikować w internecie pojedynczy film. Będzie to prosty przykład tylko dla platformy Windows,

Przydatna będzie podstawowa znajomość PHP.

Z artykułu dowiesz się, jak stworzyć własne Google Video – kompletną galerię filmów z flashowym Media Playerem na WWW pozwalającą na upload filmów i ich strumieniowe odtwarzanie.

72

www.phpsolmag.org

PHP Solutions Nr 5/2006

Video streaming

Projekty

Szybki start
• • •

Mamy 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 operacyjnego, jak i od tego, czy chcemy korzystać z darmowych, czy też komercyjnych rozwiązań, każdy z tych kroków możemy wykonać na wiele sposobów. Na początku wykonamy prezentację nastawioną na szybkie osiągnięcie celu. Użyjemy do tego programów komercyjnych (wersje trialowe) dostępnych niestety tylko dla platformy Windows. Wszystko zostanie pokazane krok po kroku. W następnej części artykułu przedstawimy rozwiązania niezależne od systemu operacyjnego, wymagające jednak większej pracy.

Rysunek 2. Macromedia Flash – obszar roboczy aplikacji

Konwersja pliku wideo.

dać na Rysunku 1, przy pomocy Riva FLV Encoder możemy: • • • • ustawić rozdzielczość filmu wyjściowego (1), ustawić parametry jakościowe/ kompresji pliku (2), odpowiednio przyciąć obraz filmu (3), ustawić parametry dźwięku (4).

Flash Player pracuje z plikami wideo zapisanymi w formacie Flash Video (FLV). Na początku więc będziemy musieli przekonwertować nasz film do FLV. Użyjemy do tego celu programu Riva FLV Encoder (http://www.rivavx.com/?encoder). Jak wi-

Dodatkowo program ten pozwala nam wyciągnąć z filmu pojedynczą klatkę i zapisać 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 wideoodtwarzacz

Rysunek 1. Riva FLV Encoder – konwerter plików wideo do formatu FLV

Macromedia Flash – program, którego użyjemy do stworzenia pierwszego klipu flashowego, dysponuje eleganckim Media Playerem. Macromedia Flash jest odpłatny, ale dla potrzeb prezentacji wykorzystamy 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 wideoodtwarzacza 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 file:// lub http://). My dla uproszczenia ustalmy, że zarówno Media Player jak i film będą znajdować się w tym samym folderze,

PHP Solutions Nr 5/2006

www.phpsolmag.org

73

Projekty

Video streaming
dlatego wystarczy że wpiszemy nazwę pliku wideo – videotest.flv..

Publikacja Movie Clipa

Na koniec musimy wykonać plik HTML, w którym osadzimy Movie Player tak, by mogli go oglądać inni. Całą resztą, czyli ściąganiem pliku wideo i odtwarzaniem go w przeglądarce zajmie się sam Movie Player. Również w tym przypadku Macromedia Flash bardzo ułatwia nam zadanie. Wybieramy z menu polecenie File -> Publish Settings (Rysunek 3). W nowym oknie, w zakładce Flash ustawiamy parametry publikacji i wybieramy wersję 7 Flash Playera (1). Klikamy na przycisk Publish i w katalogu, w którym zapisaliśmy plik testplayer.fla, pojawiają się dwa nowe pliki: testplayer.swf i testplayer.html. Jeśli pliki te wraz z plikiem wideo umieścimy na serwerze, to po otworzeniu strony testplayer.html powinniśmy zobaczyć w przeglądarce internetowej opublikowany film (Rysunek 4).

I gotowe!

Rysunek 3. Macromedia Flash – Publish Settings

Tym sposobem szybko kończymy wstępną prezentację. Wykonana strona widoczna będzie we wszystkich przeglądarkach internetowych posiadających zainstalowany plugin Flash Player 7 lub nowszy. Jak widać, wykonana przez nas publikacja plików wideo, jest zadaniem prostym. Niestety 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 filmów, postawimy większy nacisk na uniwersalność, uniezależniając się od systemu operacyjnego i do tego skorzystamy jedynie z darmowego oprogramowania.

Czas na rozbudowę

Rysunek 4. Komponent MediaPlayback firmy Macromedia prezentujący film w przeglądarce internetowej

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. strumieniowe pobieranie (ang. streaming) filmów z serwera za pomocą Macromedia Flash Playera (wersje od 6 w górę). Strumieniowe, czyli takie, które pozwala na pokazywanie filmu podczas jego ściągania. Flash Player dysponuje również innymi metodami dostarczania filmów do przeglądarki,

74

www.phpsolmag.org

PHP Solutions Nr 5/2006

Video streaming

Projekty

FFMPEG – konwersja plików wideo

Jeś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 plików wideo do formatu MP3. Jeśli tego nie zrobimy, nasze wideo na etapie odtwarzania przez Flash Player zostanie pozbawione dźwięku (w przypadku, gdy podczas kompilacji otrzymamy komunikat o braku wymaganych bibliotek, zalecamy pobranie źródeł LAME). Przykładowe polecenie configure to:
Rysunek 5. phpinfo() – informacja o zainstalowanej wersji modułów MING i FFMPEG
./configure --enable-mp3lame --enable-gpl --enable-memalign-hack

np. pobieranie progresywne (ang. progresive) lub zapisywanie pliku wideo wewnątrz animacji Flash. Mają one swoje zalety, ale w przypadku dłuższych filmów najlepiej zastosować streaming. W pierwszym przykładzie do konwersji plików wideo użyliśmy programu Riva FLV Encoder, który wykorzystuje do konwerListing 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");

sji dwa programy Open Source: FFMPEG (http://ffmpeg.sourceforge.net/index.php) oraz LAME (http://lame.sourceforge.net/). Pierwszy z nich to wszechstronny konwerter dla plików wideo i audio, a drugi tylko dla audio pozwalający na zapis w formacie MP3 (MP3 jest jak na razie jedynym odczytywanym przez Flash Player formatem audio).

Lista wspieranych przez FFMPEG formatów, a tym samym możliwości konwersji 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

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

Program rozpozna rozszerzenia plików i dopasuje odpowiednie formaty konwersji. Załaduje też domyślne parametry dla tej operacji. Przykładowe polecenie, które (określając wielkość filmu i jakość dźwięku) dokona 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

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

gdzie: • •
-ar rate

– ustawia samplingrate w

Hz,

• • • •

codec – wymusza użycie 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 potwierdzenia. -acodec

PHP Solutions Nr 5/2006

www.phpsolmag.org

75

Projekty

Video streaming
nam metoda nextFrame(). Wynikiem wykonania kodu z Listingu 1 jest pusty Movie Clip o rozmiarach 200px na 100px i czerwonym kolorze tła. Teraz przechodzimy do ustawiania elementów na scenie Movie Clipa. Warto dodać, że wszystko układamy w obrębie jednego frama, czyli Movie 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)

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

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

gdzie: •
time_offset – ustawia offset (przesunięcie względem początku filmu) w sekundach, -vframes number – ustawia ilość klatek wideo, jakie mają być pobrane z pliku wejściowego i przekazane do konwersji. -ss



MING – tworzymy Media Player

Teraz, 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 plików SWF.

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>



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 wygenerować flashowego Movie Clipa. Więcej o MINGU można poczytać w numerze 3/2005 Search Engine Optimization, w artykule Flash w PHP, czyli MING w akcji. Po zainstalowaniu rozszerzenia MING (informacje na temat, jak to zrobić, znajdziemy na stronie www.php.net/ming) przechodzimy do tworzenia pliku SWF. Zaczniemy od stworzenia podstawowego Movie Clipa, co pokazuje Listing 1. Ustalamy w nim, dla jakiej wersji Flash Playera tworzymy clip SWFMo vie($playerVersion), jakie ma mieć rozmiary setDimension($width, $height), dodatkowo ustalić możemy kolor tła setBackground($r,$g,$b)czy ilość klatek wyświetlanych w ciągu sekundy setRate($frameRate). Zapisanie pliku odbywa się po wywołaniu metody save($fileName) obiektu $movie, przy czym, zanim to zrobimy, powinniśmy mieć na scenie minimum jedną wygenerowaną klatkę, co zapewnia

76

www.phpsolmag.org

PHP Solutions Nr 5/2006

Video streaming
oraz obszaru, w którym pokazywany będzie film. Budowę zaczniemy od stworzenia przycisków. Z punktu widzenia użytkownika, przycisk we Flashu to pewien obszar powierzchni o dowolnym kształcie, który reaguje na zachowanie myszki. Z punktu widzenia programisty przycisk to obiekt, który reaguje na pewne zdarzenia wywołane przez kursor myszki nad zdefiniowanym obszarem sceny. MING pozwala nam zaprogramować dla przycisków następujące zdarzenia: • – gdy puścimy przycisk myszy nad zdefiniowanym obszarem, SWFBUTTON_MOUSEOVER – gdy najedziemy kursorem na zdefiniowany obszar, SWFBUTTON_MOUSEOUT – gdy wyjedziemy kursorem poza zdefiniowany obszar, SWFBUTTON_MOUSEDOWN – gdy naciśniemy przycisk myszki nad zdefiniowanym obszarem, SWFBUTTON_DRAGOVER – gdy najedziemy na obszar kursorem myszki z naciśniętym równocześnie przyciskiem myszki, SWFBUTTON_DRAGOUT – gdy wyjedziemy poza obszar kursorem myszki z naciśniętym przyciskiem myszki.
SWFBUTTON_MOUSEUP

Projekty

To, co udało nam się przed chwilą wykonać, oznacza, że w przypadku, gdy zostanie naciśnięty button (ustawialiś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 reagować 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 najedziemy na niego myszką. Na razie jednak ustalimy jeden kształt dla wszystkich zdarzeń:

• • •





Po stworzeniu instancji przycisku $button = new SWFButton(), dla każdego zdarzenia lub kombinacji zdarzeń możemy zdefiniować pewną akcję. W tym przypadku wykorzystamy tylko reakcję na zdarzenie kliknięcia przycisku myszki, czyli tworzymy 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).

Rysunek 6. Formularz uploadu plików wideo

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

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 kodu, który musimy już sami napisać w Action Script, np.
$actionscript = 'myVideoPlayer.pause();'.

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

Rysunek 7. Schemat bazy przechowującej informacje o zapisanych plikach wideo

PHP Solutions Nr 5/2006

www.phpsolmag.org

77

Projekty

Video streaming

$flags = (SWFBUTTON_UP | SWFBUTTON_HIT | SWFBUTTON_OVER | SWFBUTTON_DOWN);

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

W przypadku $shape mamy duże pole do popisu – możemy sobie bowiem narysować np. prostokąt o kolorze czerwonym lub inny, ciekawszy obrazek przycisku symbolizującego klawisz pauzy. W tym drugim przypadku przyda się nam do tego 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 pobranie plików graficznych w formacie DBL lub JPG (na stronach projektu MING dostępne jest narzędzie do konwersji plików GIF lub PNG do DBL). Po zainicjowaniu obiektu $img, tworzymy nowy kształt $shape = new SWFShape() i ustawiamy jako jego wypeł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);

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

określające parametry linii, którą będziemy obrysowywać obszar (grubość i składowe RGB koloru) oraz:
$shape->drawLine($x,y);

które rysuje linie do punktu $x,$y, z poprzedniego 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 scenie będziemy już widzieli przyciski. Teraz skupimy się na odegraniu pliku video. Do tego celu wykorzystamy polecenie $stream = new SWFVideoStream();, które inicjuje obiekt obsługujący video streaming. Następnie nadajemy obiektowi odpowiednie wymiary $stream>setDimension($width, $height); i po-

} } ?>

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

}

}

78

www.phpsolmag.org

PHP Solutions Nr 5/2006

Video streaming

Projekty

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

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) obsługującej video streaming tak, aby korzystała z zainicjowanego właśnie strumienia:
my_player_vstrm.attachVideo( netStream);

zycję $item->moveTo($x, $y); oraz nazwę $item->setname("my_player_ vstrm");, którą posługiwać się będziemy przy sterowaniu przebiegiem odtwarzania filmu. Na koniec musimy jeszcze zainicjować na scenie proces wgrywania pliku wideo. Zrobimy to już samym

Action Scriptem, który wstawimy bezpośrednio w pierwszą klatkę Movie Clipa. Klasa SWFVideoStream, której instancję o nazwie $stream tworzymy na Listingu 4, zajmuje się obsługą strumieni we Flashu. Aby otworzyć taki strumień, wywołujemy odpowiednie metody języ-

Na końcu jeszcze ustawiamy wielkość bufora dla strumenia video, czyli ile sekund filmu chcemy pobrać z serwera do buffora, zanim zaczniemy odgrywać film, a następnie możemy wskazać, jaki plik wideo ma zostać wgrany przez Media Player:
netStream.play(_level0.video_path);

Zauważmy, że do rozpoczęcia procesu wgrywania pliku FLV wykorzystujemy zmienną o nazwie _level0.video_path. Zmienną tą będziemy wykorzystywać do przesyłania do wnętrza Movie Clipa nazwy pliku video, który chcemy odegrać na scenie.

Parametryzowanie SWF-a

Rysunek 8. Galeria zdjęć z klatek wyciętych z uploadowanych filmów

Przyjrzyjmy się przykładowi, który stworzyliśmy na samym początku niniejszego artykułu. Warto obejrzeć sobie plik testplayer.html, który po odpowiednim sformatowaniu wygląda mniej więcej jak na Listingu 5. Jest to standardowy sposób na wstawienie pliku SWF w ciało strony internetowej. Widzimy tu tagi <object> i <embed> opisujące zmienne środowiskowe dla publikowanego Movie Clipa, w tym przypadku pliku testplayer.swf. Zauważmy, że parametry i ich wartości dublują się. Wynika to z braku kompatybilności przeglądarek. Tag <embed> przeznaczony jest dla przeglądarek opartych o engine Gecko (Netscape, Mozilla) i dla Macintosh Internet Explorer, natomiast tag <object> przeznaczony jest dla przeglądarek wspierających ActiveX. Aby przekazać jakąś wartość do Movie Clipa, mo-

PHP Solutions Nr 5/2006

www.phpsolmag.org

79

Projekty

Video streaming

żemy rozwinąć nazwę pliku SWF stosując metodę, jaką wykorzystujemy przy przesyłaniu danych formularza w URL-u (używając metody GET). Jeśli zatem chcemy przekazać do Movie 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 dostępna będzie zmienna video_path, (którą wykorzystaliśmy już podczas tworzenia pliku SWF), o wartości test.flv. W ten sposób będziemy mogli przekazać Media Playerowi informację o filmie, który ma być ściągnięty i odegrany, bez potrzeby tworzenia nowego pliku SWF dla każdego nowego filmu.

Rysunek 9. Stworzony przy pomocy MING-a Media Player odgrywa film Robot.flv

Tworzymy galerię filmów

Wyposażeni we własny Media Player i zainstalowany konwerter plików video FFMPEG możemy przejść do tworzenia serwisu internetowego. Nasz portal powinien: • • umożliwiać wgrywanie przez użytkowników nowych plików wideo, przedstawiać pliki video udostępniane na serwerze w postaci galerii zdjęć, przedstawiających trzy wybrane klatki z filmu, prezentować nagranie, w momencie gdy użytkownik kliknie na wybraną serię klatek.

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 środowiskach produkcyjnych). Wywołanie komendy w zależności od systemu operacyjnego wygląda jak na Listingu 7. Efektem wykonania kodu przedstawionego na Listingach 6 i 7 są: • • • • • trzy pliki graficzne w katalogu mediadata, jeden plik FLV w katalogu mediadata, informacje o plikach graficznych w bazie (tabela images), informacje o pliku FLV w bazie (tabela movies), informacje o nowo dodanym zasobie (wpis w tabeli resources).

tworzenie samego filmu, co widać na Rysunku 9.

Podsumowanie



W artykule przedstawiliśmy sposób stworzenia własnej galerii video w oparciu o rozwiązania komercyjne oraz darmowe. Pokazaliśmy ręczny sposób przeprowadzenia streamingu video oraz metody automatyzacji całego procesu publikacji strumienia video na stronach WWW. Wkladając trochę więcej wysiłku możemy dopracować i rozbudować naszą galerię, aby pod żadnym względem nie ustępowała takim rozwiązaniom, jak Google Video oraz zintegrować ją z dowolnym serwisem internetowym. n

Budowę witryny rozpoczniemy od stworzenia formularza do uploadu plików. Rysunek 6 przedstawia przykładowy formularz. Serwis dokona konwersji uploadowanego 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 generowania galerii zdjęć.

O autorze
Rafał Malinowski jest programistą i projektantem aplikacji sieciowych w językach PHP, JAVA i ActionScript. W wolnych chwilach tworzy biblioteki dla Flasha i udziela się w kilku projektach Open Source. Obecnie pracuje w SUPERMEDIA jako projektant / programista Java. Kontakt [email protected]

Pozostaje nam teraz wywołać zapytanie SQL-owe, które pobierze z bazy (Rysunek 7) informacje o zasobach galerii i wygenerowanie strony przedstawiającej listę wyciętych klatek z filmu (Rysunek 8). Po kliknięciu na jedną z nich nastąpi od-

80

www.phpsolmag.org

PHP Solutions Nr 5/2006

PHP Solutions

W następnym numerze

6/2006(17)

BEZPIECZEŃSTWO
Kryptografia asymetryczna w PHP Implementacja RS

PROJEKTY

Wielojęzyczny portal w 5 minut z wykorzystaniem eZ publish Nowe możliwości eZ publish 3.8.

DLA ZAAWANSOWANYCH

Więcej o IoC (Inversion of Control) Zajmiemy się aspektami związanymi z logowaniem, bezpieczeństwem i walidacją obiektów biznesowych.

TESTY

Co nowego w IDE dla PHP? Nowe, rewolucyjne możlowości środowisk programistycznych

Ponadto planujemy: ■ ■ ■ ■
TESTY – Test systemów CMS DLA ZAAWANSOWANYCH Aspekt Oriented Programming w PHP NARZĘDZIA – PHP-Qt PEAR – XML Fast Create

od 15 paź dziernika !

W sprzed aży

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

Sponsor Documents

Or use your account on DocShare.tips

Hide

Forgot your password?

Or register your new account on DocShare.tips

Hide

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

Back to log-in

Close