Dlaczego PHP5?
Transkrypt
Dlaczego PHP5?
CuteFlow
PHP Starter Kit
Dlaczego
Dlaczego PHP5?
Paweł Kozłowski
Mimo że od oficjalnej premiery PHP5 minął
już ponad rok, a na horyzoncie widać nawet
znacznie udoskonaloną wersję PHP5.1, wielu
programistów ciągle zwleka z przejściem na
nową platformę. Jakkolwiek to seria czwarta
przyczyniła się do lawinowej popularyzacji
PHP, to ilość udogodnień i nowych możliwości
wprowadzonych w PHP5 powoduje, że
praktycznie nie ma sensu rozpoczynać
projektów w PHP4.
C
W SIECI
1. www.php.net
2. http://www.zend.com/php5/
– poro ciekawych informacji
o PHP5
3. http://pecl.php.net/package/
PDO – PDO
4. http://www.php.net/SPL
– SPL
NA DVD
Na DVD umieściliśmy wszystkie
omawiane w artykule skrypty
48
o więcej, bardzo dobrze zachowana jest wsteczna kompatybilność,
w związku z czym uruchamianie istniejących projektów w nowym środowisku nie
jest trudne.
Przedstawione poniżej, praktyczne korzyści płynące z zastosowania PHP5, z
pewnością skłonią Was do prób z nową
wersją języka.
Bądźmy wyjątkowi
W PHP5 pojawiło się wiele nowości związanych z programowaniem obiektowym.
Są to ważne zmiany, ale nawet jeśli w dalszym ciągu chcemy budować programy
strukturalnie, warto zastosować wygodniejszy sposób obsługi błędów wprowadzony w PHP5: wyjątki.
Wyobraźmy sobie prostą funkcję, która odczytuje dane konfiguracyjne z pliku i zwraca je w postaci tablicy asocjacyjnej. Podczas większości wywołań otrzymamy prawidłowy wynik i będziemy mogli bez problemu skonfigurować aplika-
www.sdjournal.org
cję. Może się jednak zdarzyć, iż zaistnieją wyjątkowe okoliczności: pliku z konfiguracją nie będzie na dysku, nie będziemy mieli wystarczających praw do odczytu lub jego zawartość zostanie zniszczona. Pisząc funkcję, musimy zabezpieczyć
się na wspomniane ewentualności. Pierwsza próba implementacji może wyglądać
tak, jak na Listingu 1.
Przedstawiony fragment kodu zawiera bardzo poważny problem. Przy obecnej implementacji nie mamy możliwości
Z artykułu dowiesz się...
•
•
•
•
jakie są korzyści wykorzystania
nowego
modelu
obiektowego
w PHP5,
czy warto rozpoczynać projekty
w PHP4,
jak wygląda udoskonalona obsługa
baz danych (PDO), XML-a oraz
Web Services w PHP5,
o tym co powstaje wokół samego języka, m.in.: SPL oraz Reflection API
Dlaczego PHP5?
pokazania użytkownikowi specjalnej strony z komunikatem o błędzie, czy zapisania informacji o problemie w pliku log.
Nasz skrypt po prostu zakończy działanie z lakonicznym komunikatem, a co gorsze – wyświetli informacje o fizycznym położeniu programu w systemie plików, co
może stanowić wyłom w bezpieczeństwie
serwera. Wszystkie opisane nieszczęścia
wynikają z faktu, iż zdiagnozowanie błędu
(w tym przypadku przez file_exists) i jego
obsługa (die(...)) są ściśle ze sobą powiązane. W takim przypadku lepszym wyjściem jest implementacja przedstawiona
na Listingu 2.
Poprawiliśmy trochę sytuację, ponieważ skrypt korzystający z funkcji
readConf() sam może zdecydować, jak
obsłużyć powstałą sytuację wyjątkową:
zapisać błąd do pliku log i przekierować
użytkownika na stronę ze stosownym komunikatem, czy też przyjąć domyślne parametry konfiguracyjne.
Wykonany przed chwilą ruch rozwiązał jeden problem, ale natychmiast wygenerował inny. W tej chwili klient funkcji
readConf() musi sprawdzać przy każdym
wywołaniu, czy wynikiem przypadkiem nie
jest wartość null. Wszyscy z doświadczenia wiemy, jak łatwo jest zapomnieć o takim sprawdzeniu.
Wprowadzony w PHP5 model obsługi
wyjątków ma wszystkie zalety rozwiązania
z Listingu 2 i nie powiela jego wad. Spójrzmy na udoskonaloną wersję omawianej
funkcji na Listingu 3.
Konstrukcja throw new Exception()
może wyglądać dość egzotycznie, ale
tak naprawdę to sposób na zwrócenie
specjalnego, alternatywnego wyniku z
funkcji. Ten wyjątkowy wynik możemy
przechwycić tylko i wyłącznie korzystając z bloku try .. catch. Jeśli wyjątek
zostanie rzucony (wyjątki, w przeciwieństwie do standardowych wyników, nie
są zwracane, ale właśnie rzucane) poza blokiem try .. catch, to cały skrypt
zakończy działanie, dając efekt podobny do die().
Już to krótkie wprowadzenie powinno pokazać, o ile łatwiejsza może być
obsługa błędów przy wykorzystaniu wyjątków. Tak naprawdę w mechanizmie
tym drzemie o wiele większy potencjał.
Aby go w pełni wykorzystać, trzeba odwołać się do nowych cech obiektowych
PHP5.
Nowy model obiektowy
Dla wszystkich zwolenników obiektowego podejścia do konstruowania
oprogramowania mamy bardzo dobrą
wiadomość: PHP5 to w pełni dojrzały,
zorientowany obiektowo język programowania. Wiele konstrukcji znanych
z Java i C++ jest nareszcie dostępnych
w naszym ulubionym narzędziu. Co więcej, nowe elementy składni nie wymuszają całkowitej rezygnacji z podejścia
strukturalnego, ale raczej uzupełniają
język o potrzebne i od dawna wyczekiwane możliwości.
Listing 1. Problematyczny kod bez użycia wyjątków
function readConf(){
$cfgFile = '/path/config.cfg';
$resultArr = array();
if (file_exists($cfgFile)){
return parse_ini_file($cfgFile);
}
} else {
die ('Brak pliku z konfiguracją: '.$cfgFile);
}
Listing 2. Alternatywna implementacja bez użycia wyjątków
function readConf(){
$cfgFile = '/path/config.cfg';
$resultArr = array();
if (file_exists($cfgFile)){
return parse_ini_file($cfgFile);
} else {
return null;
}
}
Listing 3. Obsługa błędów przy użyciu wyjątków
<?php
function readConf(){
$cfgFile = '/path/config.cfg';
$resultArr = array();
}
if (file_exists($cfgFile)){
return parse_ini_file($cfgFile);
} else {
throw new Exception('Brak pliku z konfiguracją: '.$cfgFile);
}
try {
$conf = readConf();
} catch
//tu
echo
echo
}
?>
(Exception $e){
mozemy zdecydować, jak obsłużyć błąd, np.:
$e->getMessage().'<br>';
$e->getTraceAsString();
www.sdjournal.org
49
PHP Starter Kit
Na Listingu 4 znajdują się klasy, którymi będziemy posługiwać się w kolejnych
przykładach, stopniowo rozbudowując definicje pokazanych klas o nowe możliwości
zawarte w PHP5. Są to proste klasy modelujące dowolne zwierzę i drapieżnika.
Nasze zwierzątka mogą być małe lub trochę większe, natomiast drapieżniki – jak
to leży w ich naturze – potrafią kąsać. Dla
mniejszych zwierzaczków takie ukąszenie
może być niestety śmiertelne.
Okazuje się jednak, że takie brutalne
zachowanie może być poprawnie modelowane tylko w PHP5. Powodem jest sposób przekazywania parametrów do metod, a dokładniej: parametrów będących
obiektami. W PHP4 podczas wołania metody wartość argumentu jest kopiowana, a
następnie ta powielona wartość jest przekazywana do ciała metody. Jeśli rozważymy składową bite($animal) i przykładowy
fragment kodu z Listingu 5, to wynik jego
wykonania będzie zupełnie inny w PHP4
i PHP5. W przypadku poprzedniej wersji języka zwierzę $a1 w przedziwny sposób zmartwychwstanie po wykonaniu meListing 4. Przykładowe klasy, które
posłużą nam do omówienia nowych
cech obiektowych PHP5
class Animal {
private $_isSmall;
private $_isAlive;
public function __construct(
$isSmall, $isAlive = true){
$this->_isSmall = $isSmall;
$this->_isAlive = $isAlive;
}
public function isSmall(){
return $this->_isSmall;
}
public function isAlive(){
return $this->_isAlive;
}
}
public function kill(){
$this->_isAlive = false;
}
class Predator extends Animal {
}
50
public function bite($animal){
if ($animal->isSmall()){
$animal->kill();
}
}
CuteFlow
Dlaczego
tody bite($animal)! O ile funkcjonalność
taka może ucieszyć miłośników zwierząt,
to nie jest to zachowanie intuicyjne, znane
z realnego świata, który próbujemy modelować. Dlatego w PHP5 do metody nie są
przekazywane kopie obiektów, ale referencje do nich: przekazujemy tylko wskazanie, logiczne połączenie do jednej kopii obiektu. Takie zachowanie języka jest
bardziej intuicyjne i znane z innych środowisk, np. Java.
Przekazywanie obiektów przez referencje nie oznacza, że nie możemy wykonać kopii obiektu – zmieniła się tylko
składnia. Teraz kopie obiektu uzyskamy
dzięki użycia słowa kluczowego clone, np.
$a2 = clone $a1;.
Na Listingu 4 wykorzystaliśmy również inną nowość z repertuaru konstrukcji obiektowych PHP5: sterowanie zakresem widoczności pól. Zauważmy, że pola w klasie Animal są poprzedzone słowem kluczowym private. Jak w innych językach, oznacza to, że dostęp do tej składowej ma tylko kod umieszczony bezpośrednio w danej klasie. Jest to doskonały
sposób na zakomunikowanie intencji projektanta klasy, który w ten sposób może
powiedzieć: “To są wewnętrzne szczegóły obiektu, proszę ich nie zmieniać”. Z drugiej strony, dzięki zmiennym prywatnym
możemy chronić naszą klasę przed przypadkowym, nieprawidłowym użyciem. Nie
chcielibyśmy przecież, by ktoś przypadkowo wskrzeszał obiekty klasy Animal, wykonując kod $a1->_isAlive = true;. W
PHP4 jedynym sposobem ochrony zmiennych prywatnych było rozpoczynanie ich
nazw od znaku podkreślenia i... liczenie
na dobrą wolę użytkowników klasy.
Zmienne prywatne to nie jedyne modyfikatory widoczności pola. Do dyspozycji pozostają jeszcze: protected i
public. Domyślnie, jeśli nie nadamy żadnego modyfikatora, przyjmowany jest dostęp na poziomie public, czyli bez ograniczeń, dla wszystkich klas. Dobrą praktyką
jest jednak dopisywanie słowa kluczowe-
go public, aby w ten sposób lepiej przekazać intencje co do przeznaczenia danej
składowej.
Listing 4 jest też dobrą okazją do zademonstrowania innej cechy PHP5: nazewnictwa konstruktorów. W PHP4 jako konstruktor uruchamiana była metoda o nazwie identycznej z nazwą klasy.
Listing 6. Użycie modyfikatora
abstract
abstract class Animal {
private $_isSmall;
private $_isAlive;
public function __construct(
$isSmall, $isAlive = true){
$this->_isSmall = $isSmall;
$this->_isAlive = $isAlive;
}
public abstract function move();
public function isSmall(){
return $this->_isSmall;
}
public function isAlive(){
return $this->_isAlive;
}
}
class Rabbit extends Animal {
}
$a1->_isAlive = true;
$p1->bite($a1);
echo $a2->isAlive();
www.sdjournal.org
public function move(){
//skacz
}
abstract class Predator
extends Animal {
}
public function bite($animal){
if ($animal->isSmall()){
$animal->kill();
}
}
class Tiger extends Predator {
Listing 5. Przekazywanie obiektów
przez referencje
$a1 = new Animal(true, true);
$p1 = new Predator(true, false);
public final function kill(){
$this->_isAlive = false;
}
}
public function move(){
//szybko biegaj
}
$r1 = new Rabbit(true, true);
$t1 = new Tiger(false, true);
$t1->bite($r1);
echo $r1->isAlive();
Dlaczego PHP5?
Było to o tyle problematyczne, że zmieniając nazwę klasy musieliśmy dodatkowo modyfikować nazwę konstruktora.
W PHP5 problem ten został rozwiązany:
konstruktorem jest zawsze metoda o nazwie __construct(). Dodatkowo, pojawiła
się specjalna metoda, wywoływana automatycznie przy usuwaniu obiektu z pamięci, czyli destruktor. Jak się łatwo domyślić,
ma on nazwę __destruct().
Opisane powyżej zmiany w składni języka rozwiązują najbardziej dokuczliwe
problemy związane z programowaniem
obiektowym w PHP.
Na szczęście zmiany w PHP5 idą
jeszcze dalej i dzięki temu dostajemy bardzo silne narzędzia w postaci interfejsów,
klas abstrakcyjnych, finalnych i wiele, wiele innych. Przyjrzyjmy się dokładniej, co
oznaczają te nowe możliwości, ulepszając
klasy z Listingu 4. Przede wszystkim zauważmy, że metoda kill() jest kluczowa
dla obiektów klasy Animal. Nie chcemy,
by istniała możliwość zmiany jej definicji,
a tym samym – podstawowego zachowania obiektu. W PHP5 istnieje sposób na
wyrażenie takiej intencji. Słowo kluczowe
final powoduje, że poprzedzona nim metoda nie może być przedefiniowana w klasie rozszerzającej. Poprawmy więc naszą
definicję, zapisując ją jako: public final
function kill(). Oprócz metod, również
klasy mogą być oznaczone jako finalne,
co uniemożliwia ich rozszerzenie przez jakąkolwiek inną klasę.
Bliższe przyjrzenie się naszemu modelowi obiektowemu może nasunąć pewną myśl: nasze klasy są bardzo ogólne. W
rzeczywistym świecie mamy do czynienia
z realnymi zwierzętami, żyjące obiekty są
kotami czy królikami, nie zaś abstrakcyjnymi zwierzętami. W rozważanym przypadku klasa Animal jest pojęciem ogólnym,
abstrakcyjnym, dla której nie powinny istnieć instancje obiektu. Również taki model
możemy bardzo dobrze wyrazić w PHP5,
stosując modyfikator abstract. Spójrzmy
na Listing 6, gdzie znajdują się przykładowe klasy po zmianach. Widać teraz, jak łatwo jest zakomunikować istnienie abstrakcyjnej metody move(): w przypadku ogólnego pojęcia “zwierzę” konkretna implementacja nie ma sensu, ponieważ poszczególne stworzenia są inne np. poruszają się inaczej. Dlatego poprzedzamy tę
nową metodę słówkiem abstract.
Obserwując dalej poprawiony kod
możemy zauważyć, że w klasie Predator
i metodzie bite nie ma mechanizmów
sprawdzających, co tak naprawdę jest
nadgryzane. Raczej mało prawdopodobne jest, by hipotetyczny tygrys z równą ochotą konsumował króliki i siano. W
PHP, jako języku bez ścisłego określenia
typów, możliwe jest przekazanie dowolnego argumentu do metody. Nawet takiego, na którym wołanie metod isSmall
czy kill nie ma sensu. W PHP5 dodano dwa sposoby na zapobieganie takim
niespodziewanym sytuacjom. Po pierwsze, możemy użyć nowego operatora instanceof. Dużo wygodniejsze jest
jednak ustalenie typu argumentów, jakie
może przyjmować dana metoda. Przykłady obu konstrukcji znajdują się na Listingu 7. Widać, że drugie z zaproponowanych rozwiązań przesuwa ciężar odpowiedzialności za sprawdzanie poprawności argumentów, z programisty na interpreter języka.
Przy podawaniu typu dla argumentów funkcji możemy używać zarówno
klas zwykłych i abstrakcyjnych. PHP5
wprowadza jeszcze jedno pojęcie ściśle związane z typami danych: interfejsy. Najłatwiej myśleć o tych nowych konstrukcjach jako o w pełni abstrakcyjnych
klasach (tzn. takich, w których wszystkie metody są abstrakcyjne), w których
nie zdefiniowano pól z danymi. Na pierwszy rzut oka może wydawać się, że takie abstrakcyjne twory nie są zbyt użyteczne: nie zawierają danych i funkcjonalności. Okazuje się, że samo zdefiniowanie możliwych zachowań w interfejsie
jest szalenie pożyteczne, bowiem interfejsów możemy używać jako określenia
Listing 7. Operator instanceof i
określanie typu w argumentach
funkcji
typów w parametrach funkcji, dokładnie
w taki sam sposób, jak używaliśmy nazw
klas. Listing 8 pokazuje definicję przykładowych interfejsów oraz jeszcze inną miłą ich cechę: klasy mogą implementować
wiele interfejsów.
Ostatnią nowością PHP5 pozostałą
do omówienia są składowe statyczne. W
PHP4 możliwe było poprzedzenie zmiennej słowem static, ale tylko wewnątrz
funkcji. PHP5 nie narzuca już tych ograniczeń i możemy cieszyć się pełną obsługą
składowych statycznych. Co więcej, istnieje inna, bardzo podobna konstrukcja – stałych klasowych. Można myśleć o niej jak
o polu statycznym, z tą jednak różnicą, iż
nie można zmienić wartości raz przypisanej do stałej.
Przy okazji omawiania cech obiektowych języka, warto wspomnieć o nowym
zestawie klas, standardowo dostępnym
w PHP5. Klasy te zgromadzono w module
ReflectionAPI, a służą one do pobierania informacji (dostępne metody i ich
argumenty, konstruktor itd.) o zdefiniowanych klasach i w tym sensie zastępują
Listing 8. Przykład użycia interfejsów
interface Moveable {
}
interface Killable {
}
private $_isSmall;
private $_isAlive;
public function __construct(
$isSmall, $isAlive = true){
$this->_isSmall = $isSmall;
$this->_isAlive = $isAlive;
}
if ($animal instanceof Animal){
if ($animal->isSmall()){
$animal->kill();
}
} else {
hrow new
InvalidArgumentException();
}
www.sdjournal.org
public function kill();
abstract class Animal
implements Moveable, Killable {
public function bite($animal){
}
public function bite(
Animal $animal){
if ($animal->isSmall()){
$animal->kill();
}
}
public function move();
public function isSmall(){
return $this->_isSmall;
}
public function isAlive(){
return $this->_isAlive;
}
}
public final function kill(){
$this->_isAlive = false;
}
51
PHP Starter Kit
takie funkcje jak get_declared_classes
czy get_class_methods.
Hokus, pokus
– funkcje magiczne
Podczas omawiania nowego modelu
obiektowego PHP5, natknęliśmy się na
dwie metody, których nazwy rozpoczynają się od podwójnego znaku podkreślenia: __construct() i __destruct(). Jeśli dokładniej przeszukamy standardową
dokumentację PHP, znajdziemy kilka innych funkcji o podobnym schemacie nazewnictwa, np. __call, __autoload() czy
__wakeup(). Mimo karkołomnego nazewnictwa, funkcje te mogą spełniać niezwykle pożyteczne zadania.
CuteFlow
woduje to, z jednej strony, konieczność
umieszczania wielu wierszy z instrukcjami include czy require na początku każdego skryptu korzystającego z bibliotek.
Co więcej, bardzo łatwo o konflikt, gdy
chcemy zastosować dwie biblioteki, w których znajdują się klasy o identycznych nazwach.
Próbą rozwiązania wspomnianych
problemów jest przyjmowanie konwencji
__autoload
Prawdziwą zmorą PHP, niestety – obecną również w PHP5, jest brak przestrzeni nazw i konieczność ręcznego włączania potrzebnych definicji klas i funkcji. Po-
52
nazewnictwa, tak jak ma to miejsce w pakietach PEAR. Wypracowane konwencje
mogą być bardzo różne, ale najczęściej
sprowadzają się do takiego pisania klas,
by w ich nazwach znalazła się nazwa producenta, modułu itd., np.: DB_DataObject_
FormBuilder_QuickForm_ElementTable.
Uzgodnione standardy nazewnictwa
pomagają w uniknięciu konfliktów w nazewnictwie klas, ale nie rozwiązują pro-
Listing 9. Dekorator zaimplementowany przy użyciu __call
<?php
class PDODecorator {
public $_decoratedPDO;
public function __construct(PDO $decoratedPDO){
$this->_decoratedPDO = $decoratedPDO;
}
AOP
W PHP5 pojawiła się nowa, bardzo ciekawa metoda: __call. Jest ona wywoływana automatycznie, gdy w obiekcie danej klasy nie zdefiniowano wywoływanej
metody.
Nie zatrzymując się dłużej nad suchą
definicją, przyjrzyjmy się przykładowi, który pokaże zastosowanie metody __call
do bardzo łatwej implementacji wzorca
projektowego o nazwie dekorator. Ilość
zastosowań dekoratora jest naprawdę olbrzymia, a jego istota sprowadza się do
wzbogacenia funkcjonalności („udekorowania”) klasy, bez jej modyfikowania. Jak
to możliwe? Aby to wyjaśnić, posłużmy się
przykładem: załóżmy, że chcielibyśmy zapisać do pliku log wszystkie wykonywane
instrukcje SQL i czas ich trwania. Podejście takie wymaga jednak modyfikacji klasy bibliotecznej i jest zupełnie niewykonalne przy korzystaniu z PDO (nowy interfejs
bazodanowy, ang. PHP5 Data Objects
Abstraction Layer), o którym opowiem w
dalszej części artykułu). Na szczęście możemy udekorować pierwotną klasę, dodając potrzebną nam funkcjonalność (Listing 9).
O ile metoda __call jest uruchamiana
w momencie braku definicji docelowej metody, to dzięki __get i __set możemy osiągnąć identyczny efekt dla pól klasy. Może to być bardzo pomocne przy śledzeniu
niewłaściwych odwołań do pól klasy, np.
tak jak na Listingu 10.
Dlaczego
public function __call($name, $arguments){
echo "Przed wykonaniem metody '$name' w PDO";
$result = call_user_func_array(array(&$this->_decoratedPDO,
$name),$arguments);
echo "Po wywołaniu metody '$name' w PDO";
}
return $result;
}
try {
$dbh = new PDODecorator(new PDO($dsn, $user, $password));
...
} catch (PDOException $e) {
echo 'DB error: '. $e->getMessage();
}
?>
Listing 10. Wychwytywanie niewłaściwego dostępu do pól przez __set i __get
<?php
class CheckAccessViolation {
}
public $field1;
public function __set($fieldName, $value ){
$this->throwAccessViolation($fieldName);
}
public function __get ($fieldName ){
$this->throwAccessViolation($fieldName);
}
private function throwAccessViolation($fieldName){
throw new Exception("Pole '$fieldName' nie istnieje w klasie '".__CLASS__
."'");
}
$obj = new CheckAccessViolation();
$obj->field2 = 1;
?>
www.sdjournal.org
Dlaczego PHP5?
blemu włączania ich definicji. Tu z pomocą przychodzi nowa funkcja PHP5:
__autoload(). Jej działanie można opisać bardzo prostym algorytmem: jeśli w
programie pojawi się użycie klasy, która nie jest jeszcze zadeklarowana, wywoływana jest wspomniana funkcja
__autoload(). Wewnątrz niej możemy
próbować odnaleźć i załadować potrzebną definicję, np. tak jak na Listingu 11.
O ile na pierwszy rzut oka funkcja __
autoload() wydaje się szalenie użyteczna, to przed podjęciem decyzji o jej stosowaniu warto zdawać sobie sprawę z
dwóch poważnych ograniczeń. Po pierwsze, bardzo ściśle łączymy nazewnictwo klas z nazwami i położeniem skryptów w systemie plików. Powoduje to, że
trudniej nam będzie zmienić nazwę klasy, co ma często miejsce przy poprawia-
niu wewnętrznej struktury kodu (ang. refactoring). Po drugie, możemy popaść w
spore kłopoty, jeśli znajdziemy bibliotekę,
która używa zupełnie innej konwencji nazewniczej (np. przechowuje wszystkie
pliki w jednym folderze, bez względu na
obecność znaków podkreślenia): funkcję
__autoload definiujemy przecież raz, dla
całego skryptu!
__sleep i __wakeup
Ostatnie z omawianych funkcji magicznych są najczęściej używane razem i pomagają kontrolować zachowanie obiektu
podczas jego serializacji i odtwarzania.
Jak łatwo wywnioskować z nazw tych
metod, __sleep jest wywoływane automatycznie przekształcamy obiekt z jego
naturalnej postaci na ciąg znaków (serializacja), __wakeup natomiast odwrotnie
Listing 11. Przykładowa implementacja __autoload
function __autoload($class_name) {
$PATH_SEPARATOR = '_';
$base_path = dirname(__FILE__);
$full_path = join(split($PATH_SEPARATOR, $class_name), '/');
}
require_once $base_path.'/'.$full_path.'.php';
$obj = new Pakiet_Klasa();
Listing 12. Przykład użycia __sleep oraz __wakeup
<?php
class ClassWithDBConnection {
private $_dbhandle = null;
private $_field1 = 5;
private $_field2 = 3;
function __sleep(){
$this->_dbhandle = null;
$objectVars = get_object_vars($this);
$serializeVars = array();
foreach ($objectVars as $key => $val) {
$serializeVars[] = $key;
}
return $serializeVars;
}
function __wakeup(){
$this->connectToDB();
}
private function connectToDB(){
//metoda do pobierania połączenia, np.:
$this->_dbhandle = DBManager::getConnection();
}
}
$obj = new ClassWithDBConnection();
$serialstr = serialize(new ClassWithDBConnection());
$unserializedObj = unserialize($serialstr);
?>
www.sdjournal.org
– przy konwersji reprezentacji znakowej
do instancji obiektu. Podjęcie specjalnych środków w czasie serializacji może być konieczne, jeśli w obiekcie są pola, które odwołują się do zewnętrznych
zasobów, np. uchwyty (ang. handler) do
pliku czy bazy danych. Aby prawidłowo
przywrócić taki obiekt do życia, trzeba
własnoręcznie zadbać o ponowne połączenie pól obiektu z zewnętrznymi zasobami. Może się to odbywać podobnie jak
na Listingu 12.
SPL
Poprzednie akapity pozwoliły nam dokładniej zobaczyć, jak dużą rewolucją
jest wersja piąta PHP, szczególnie w
kontekście programowania obiektowego.
Łatwy w opanowaniu język skryptowy zyskał zupełnie nową siłę wyrazu, co znacząco rozszerza zakres jego stosowania.
Możliwe staje się używanie konstrukcji i
wzorców projektowych, które do tej pory
były trudne do oprogramowania. Otwiera to drogę do budowania bardziej złożonych bibliotek. Tym tropem idzie SPL
(ang. Standard PHP Library), czyli standardowa biblioteka PHP udostępniająca gotowe implementacje często spotykanych wzorców projektowych (np. Iterator, Observer) i definiująca hierarchię
wyjątków.
Podstawowym wzorcem projektowym
dostępnym w SPL jest Iterator. Jest on
bardzo prosty w swej koncepcji i od dawna obecny w PHP w postaci konstrukcji
foreach. Wiemy doskonale, jak łatwo wykonać operacje na wszystkich elementach
tablicy, iterując kolejno po jej zawartości.
Dzięki SPL ta prosta koncepcja została rozszerzona na inny typy danych, jak choćby
zawartość katalogu w systemie plików. Listing 13 pokazuje dobrze znaną konstrukcję zastosowaną do zbiorów dyskowych.
Istotą iteratora jest całkowite oddzielenie typu zbioru danych i metody jego przeglądania. Dowolna kolekcja danych udostępniająca iterator może być przeszukiwana w ten sam sposób, niezależnie od
tego, czy jest to tablica, zbiory na dysku
czy rekordy z DB. Takie podejście do zestawu informacji pozwala na ogólne budowanie operacji na danych, np. sortowanie
i filtrowanie.
Innym ważnym składnikiem SPL,
rozbudowanym w PHP5.1, jest cała
hierarchia wyjątków. Zgłaszanie sytuacji wyjątkowych tylko i wyłącznie
przy użyciu klasy Exception nie jest
53
PHP Starter Kit
najdoskonalszym rozwiązaniem – w ten
sposób ograniczamy sobie możliwość
selektywnego przechwytywania różnego typu wyjątków i odpowiedniego
reagowania w zależności od typu błędu.
Jeśli pomyślimy o błędzie łączenia się
z bazą danych i błędzie wynikającym
z podania nieprawidłowego argumentu
do funkcji, z łatwością dojdziemy do
wniosku, że są to sytuacje wyjątkowe, ale o zupełnie różnym ciężarze
gatunkowym. O ile użytkownik może
jeszcze raz podać dane wejściowe
i prawidłowo uruchomić metodę, to na
brak komunikacji z DB najczęściej nie
jest w stanie nic poradzić. Opisane sytuacje są wyjątkowe, ale powinny być
zgłaszane poprzez różne typy wyjątków.
W SPL znajdziemy właśnie takie gotowe, predefiniowane klasy rozszerzające
podstawowy Exception. Wracając do
przedstawionego przykładu, najprawdopodobniej użylibyśmy RuntimeException
dla oznaczenia problemów z DB
i InvalidArgumentException w przypadku niewłaściwego argumentu wywołania.
CuteFlow
SPL w naturalny sposób wykorzystuje nowe cechy obiektowe PHP5. Należy spodziewać się, że biblioteka ta będzie się rozrastać i w przyszłości stanie
się nieodzownym elementem większości
skryptów PHP. Już teraz jest to silne narzędzie, któremu z pewnością warto poświęcić uwagę.
PDO
Pisanie aplikacji, która musi współpracować z różnymi typami baz danych,
może być frustrującym zadaniem,
jeśli korzystamy tylko ze standardowych bibliotek PHP. Nazwy funkcji
wykonujących operacje na różnych DB
nazywają się inaczej (np. pg_connect
i mysql_connect), a zestaw dostępnych
funkcji może być różny w zależności
od używanego systemu DB. Powoduje
to, że migracja aplikacji napisanej tylko
pod jeden typ DB, z wykorzystaniem
standardowych funkcji PHP, może być
prawdziwym koszmarem. Do pewnego
stopnia problem ten rozwiązują biblioteki
stanowiące dodatkową warstwę abstrak-
Listing 13. Użycie iteratora do przeglądania zawartości dysku
<?php
$path = 'c:\\';
$diterator = new DirectoryIterator($path);
foreach ($diterator as $dirobj){
}
?>
if ($dirobj->isDir ()){
echo '<b>'.$dirobj->getFilename().'</b>';
} else {
echo $dirobj->getFilename();
}
echo '<br>';
Listing 14. Dostęp do bazy danych z użyciem PDO
$dsn = 'pgsql:host=localhost dbname=txtest';
$user = 'postgres';
$password = 'postgres';
try {
$dbh = new PDO($dsn, $user, $password);
$stmt = $dbh->prepare("SELECT * FROM users WHERE
userstatus = 1");
if ($stmt->execute()){
while ($row = $stmt->fetch()) {
echo $row["userid"];
echo $row["fullname"];
echo $row["userstatus"];
}
}
} catch (PDOException $e) {
echo 'DB error: '. $e->getMessage();
}
54
Dlaczego
www.sdjournal.org
cji pomiędzy aplikacją a standardowymi
funkcjami PHP i maskujące różnice nazewnictwa. Do najpopularniejszych rozwiązań tego typu należą AdoDB, pakiety
dostępne w PEAR i Creole. Jakkolwiek
biblioteki te dość dobrze spełniają swoje
zadnie, to nie są pozbawione wad. Podstawowym problemem jest różnorodność dostępnych rozwiązań, przez co
rozpoczynając nowy projekt musimy zastanawiać się nad wyborem konkretnej
implementacji. Kolejna wada to fakt, iż
wszystkie wymienione rozwiązania pisane są jako skrypty PHP, co nie najlepiej
odbija się na ich wydajności.
PDO (ang. PHP5 Data Objects) to
odpowiedź na wymienione przed chwilą uciążliwości. Zadaniem PDO jest zastąpienie istniejącego, niespójnego wewnętrznie zestawu funkcji, jedną, standardową biblioteką. W odróżnieniu od
prekursorów, implementacja wykonana jest w języku C i dzięki temu o wiele szybsza. Architektura PDO to jeden
centralny podsystem, zawierający podstawowe funkcje, oraz zestaw sterowników (również jako moduły tworzone w C)
do poszczególnych DB. Obsługa błędów
związanych z dostępem do bazy danych
może odbywać się przy pomocy wyjątków, co znakomicie upraszcza kod naszych skryptów.
Dla osób korzystających z AdoDB czy
Crole praca z PDO nie sprawi najmniejszego problemu. Nowicjusze również nie
powinni poczuć się zagubieni. Wystarczy
spojrzeć na Listing 14, by przekonać się,
że nowy model współpracy z DB nie jest
trudny do zrozumienia.
Jedyna informacja o typie bazy danych, do której się łączymy, zawarta jest w
parametrach konfiguracyjnych. Przykładowy kod korzysta z PostgreSQL, ale przełączenie się na MySQL to tylko kwestia
zmiany trzech pierwszych linijek.
Siła PDO leży nie tylko w uporządkowaniu dotychczasowego chaosu związanego z funkcjami dostępu do DB i zapewnieniu abstrakcji przy operacjach z wykorzystaniem bazy danych. Bardzo dobrze
rozwiązany jest problem uciążliwego przygotowania danych do przesłania do DB a
pochodzących od użytkownika (ang. Quoting). Wystarczy przygotować instrukcje INSERT i UPDATE tak, jak na Listingu
15, a PDO automatycznie zastąpi kłopotliwe znaki.
Podobnie jak SPL, PDO jest nowością w PHP. Oficjalna premiera zbie-
Dlaczego PHP5?
gnie się z wydaniem wersji PHP5.1. Mimo młodego wieku, to prawdopodobnie
te dwa rozwiązania będą w jakimś stopniu wyznaczać kierunek rozwoju nowoczesnego PHP.
XML / WebServices
Chociaż podstawowa obsługa XML
była wbudowana w PHP4, to jednak
poprzednia implementacja obsługiwała
tylko niewielki podzbiór możliwości drzemiących w technologiach XML-owych.
Co więcej, część pakietów nigdy nie
wyszła poza fazę eksperymentalną (np.
DOM) a i w tych stabilnych zdarzały się
błędy. Inne, kluczowe dla infrastruktury
XML-owej specyfikacje (np. SOAP),
były pisane jako osobne biblioteki lub
pakiety PEAR. Biorąc pod uwagę słabość istniejących w PHP implementacji
oraz rosnącą popularność technologii
wykorzystujących specyfikacje z rodziny
XML, twórcy PHP5 postanowili przepisać podsystemy wspomagające pracę
z XML całkowicie od nowa. W efekcie
PHP5 został wyposażony w bardzo
dobrą i zaawansowaną obsługę XML-a
oraz wiele rozszerzeń znacznie ułatwiających pracę ze zbiorami opisanymi
w XML.
Listing 15. Quot-owanie z PDO
$dbh = new PDO($dsn, $user, $password);
$stmt = $dbh->prepare("INSERT INTO users (fullname, userstatus)
VALUES (:fullname, :userstatus)");
$stmt->execute(array(
':fullname' => 'Paweł Kozłowski',
':userstatus' => 1
));
Listing 16. Plik XML używany w przykładach
<?xml version="1.0" encoding="utf-8" ?>
<phpstarterkit>
<title>PHP Starter Kit – PHP dla początkujących</title>
<publish-date>2005-09-01</publish-date>
<articles>
<article>
<title>Praktyczne nowości w PHP5</title>
<author>Paweł Kozłowski</author>
<page-from>1</page-from>
<page-to>3</page-to>
</article>
<article>
<title>Tytul 1</title>
<author>Autor 1</author>
<page-from>3</page-from>
<page-to>7</page-to>
</article>
<article>
<title>Tytul 2</title>
<author>Autor 2</author>
<page-from>7</page-from>
<page-to>12</page-to>
</article>
</articles>
</phpstarterkit>
Listing 17. Czytanie wartości z pliku XML
$dom = new DomDocument();
$dom->load("articles.xml");
$titles = $dom->getElementsByTagName("title");
foreach($titles as $node) {
print $node->textContent . "<br>";
}
Obsługa standardów
Na Listingu 16 zaprezentowaliśmy prosty
plik XML, którym będziemy manipulować
w kolejnych przykładach.
O ile parsowanie w modelu SAX działało poprawnie już w PHP4, to praca w
standardzie DOM sprawiała sporo kłopotów. Wszystkie te problemy zostały usunięte w PHP5, czego najlepszym dowodem jest kod z Listingu 17. Pokazuje on
sposób na odwołanie się do przykładowego pliku XML.
Zauważmy, że wyszukiwane są
wszystkie sekcje oznaczone jako
<title>, niezależnie od ich położenia
w strukturze dokumentu XML-owego.
Zastanówmy się, co zrobić, żeby wypisać
tylko tytuły artykułów, bez głównego tytułu magazynu. Na szczęście w PHP5 jest
to proste zadanie, a to dzięki implementacji specyfikacji XPath, która wspomaga
wyszukiwanie elementów w dokumencie
XML-owym, podobnie jak możemy to
robić w systemie plików na dysku. Uruchomienie programu z Listingu 18 spowoduje wypisanie tylko interesujących
nas artykułów wewnątrz danego numeru
pisma.
Oczywiście przedstawione przykłady
to tylko wierzchołek góry lodowej. PHP5
zawarto znacznie szersze wsparcie dla
technologii XML, z czego z najważniejszych należy wymienić XSLT i sprawdzanie poprawności dokumentów XML (ang.
validation) w oparciu o DTD, XML Schema i RelaxNG.
Trzeba przyznać, że ta mnogość
skrótów i standardów składających się
na świat XML może być trudna do opanowania dla początkującego użytkownika. Na szczęście w PHP5 znajdziemy
też prostszą metodę manipulowania dokumentami: SimpleXML. Jest to prostszy
interfejs nadbudowany na DOM: jego wykorzystanie powoduje, że podstawowe
przetwarzanie dokumentu XML sprowadza się do zaledwie kilku linijek kodu (Listing 19). Ponieważ SimpleXML jest nakładką na standardowe API, dwie funkcje PHP5 (simplexml_import_dom($dom)
i dom_import_simplexml($sxe)) umożliwiają konwersję pomiędzy dokumentami
SimpleXML i DOM.
Web Services
Dzięki nowym modułom wbudowanym
w PHP5 obsługa dokumentów XML stała
się bardzo prosta. Od kilku lat budowane
się jednak rozwiązania, które używają XML
www.sdjournal.org
55
PHP Starter Kit
jako niskopoziomowej, wspomagającej
technologii. Prawdopodobnie najbardziej
popularnym ruchem w tym kierunku są
WebServices i SOAP. Nie wdając się
w szczegóły dotyczące sposobu funkcjonowania Web Services, możemy spokojnie stwierdzić, że ich obsługa z poziomu
PHP5 jest dziecinnie prosta. Listing 20,
w zaledwie 3 linijkach kodu, tworzy klienta
SOAP i wywołuje zdalną metodę. W przedstawionym przykładzie posługujemy się
testowym Web Service, dostępnym na
stronach www.xmethods.net. Jak widać na
wspomnianym Listingu 20, do stworzenia
klienta potrzebny jest tylko standardowy
plik opisujący Web Service – WSDL (ang.
Web Service Definition Language). To
naprawdę wszystko. W klasie SoapClient,
po jej utworzeniu, automatycznie dostępne są wszystkie metody zdefiniowane
w WSDL.
Prawdopodobnie PHP5 znacznie
częściej będzie używany do tworzenia
części klienckiej Web Service, ale gdyby zaszła konieczność zbudowania serwera, nasze zadanie w dalszym ciągu
jest bardzo łatwe. Listing 21 pokazuje,
jak szybko możemy zaimplementować
Web Service zdefiniowany przez wykorzystywany już opis WSDL. Jedyna praca, jaką pozostawili nam twórcy rozszerzenia SOAP dla PHP5, to napisanie ciała metody realizującej konkretne zadanie – praktycznie bez dodatkowego kodu wspomagającego całą infrastrukturę
Web Services.
CuteFlow
w którym można naprawdę szybko tworzyć złożone, ale solidnie skonstruowane projekty.
Bez wielkich fanfar, ale za to z realną
korzyścią, rozwija się projekt pecl.php.net,
gdzie gromadzone są rozszerzenia do
języka pisane bezpośrednio w C. Część
z tych dodatkowych modułów w naturalny
sposób znajduje miejsce w głównej dystrybucji, czego najlepszymi przykładami są
SPL i PDO.
To, że PHP5 dokonał tak ogromnego
postępu, nie uszło uwadze największych
56
firm informatycznych na świecie. Wystarczy powiedzieć, że wsparcie dla PHP deklarują tacy potentaci jak IBM, Oracle,
SAP czy SUN.
Przyszłość należy do PHP5 i kolejnych wersji języka. Jeśli tylko środowisko, w którym uruchamiamy nasze
skrypty nie narzuca drakońskich ograniczeń, warto i trzeba rozpoczynać projekty w oparciu o PHP5. Nawet programując
strukturalnie zyskamy na nowościach
zawartych w PHP5 i pozostawimy sobie
otwarta droga do rozwoju. n
Listing 18. Użycie XPath do pobrania części danych z XML
$dom = new DomDocument();
$dom->load("articles.xml");
$xp = new domxpath($dom);
$titles = $xp->query("/phpstarterkit/articles/article/title");
foreach($titles as $node) {
print $node->textContent . "<br>";
}
Listing 19. Prostsze operacje na XML dzięki SimpleXML
$sxe = simplexml_load_file("articles.xml");
$articles = $sxe->xpath("/phpstarterkit/articles/article");
foreach($articles as $node) {
echo $node->author.' : '.$node->title.'<br>';
}
Listing 20. Implementacja prostego klienta SOAP
<?php
$client = new SoapClient
("http://www.xmethods.net/sd/2001/CurrencyExchangeService.wsdl");
$return_val = $client->getRate('Poland', 'France');
echo $return_val;
Podsumowanie
W poprzednich paragrafach przyjrzeliśmy się nowym konstrukcjom i bibliotekom dostępnym w PHP5. Wiele z tych
usprawnień można z czystym sumieniem
określić jako rewolucyjne. Warto jednak
na chwilę spojrzeć ponad zawiłościami nowych słów kluczowych i przebogatej funkcjonalności bibliotek, by dostrzec
jak zmienia się PHP jako całe środowisko programistyczne.
Dodanie do PHP5 silnych cech
obiektowych sprawiło, że nagle stanęła
otworem droga do implementacji ogromnej większości wzorców projektowych. To
daje społeczności PHP dostęp do wiedzy gromadzonej dosłownie przez dziesięciolecia w projektach tworzonych w
SmalTalk, C++ czy Java. Dobrze sprawdzone praktyki są gotowe do wykorzystania, a mimo to PHP5 pozostał językiem łatwym do opanowania. Językiem,
Dlaczego
?>
Listing 21. Implementacja prostego serwera SOAP
<?php
class SoapExRateHandler {
}
public function getRate($country1, $country2){
return 1.0;
}
ini_set("soap.wsdl_cache_enabled", "0");
$server = new SoapServer("CurrencyExchangeService.wsdl");
$server->setClass(SoapExRateHandler);
$server->handle();
?>
www.sdjournal.org