background image

Techniki

PHP Solutions Nr 5/2005

www.phpsolmag.org

60

J

ęzyk PHP5 przyniósł wiele zmian, 

które odróżniają go od wcześniej-

szych wersji. W większości wypad-

ków nowa funkcjonalność czy zwiększo-

na wydajność nie wpłynęły na kompatybil-

ność wstecz. Najistotniejsze wyjątki psują-

ce zgodność z poprzednimi wydaniami do-

tyczą funkcji związanych z programowa-

niem zorientowanym obiektowo (OO, ang. 

object-oriented) – tutaj twórcy PHP wywo-

łali prawdziwą rewolucję w języku. W tym 

artykule pokażemy, że nowe, obiektowe 

możliwości PHP5 są na tyle istotną zmia-

ną w języku, iż każdy powinien je przyjąć 

oraz że nie warto rozpoczynać projektów 

w PHP4.

Powyższa teza ma dwa argumenty 

na swoje poparcie: pierwszy jest taki, że 

nowe możliwości pozwolą programistom 

PHP na zgodne ze standardami techniki 

programowania i projektowania aplikacji. 

Podejście obiektowe ma znaczną przewa-

gę nad proceduralnym – pokażemy te za-

lety na przykładzie.

Masz dość żmudnego i podatnego na błędy 
programowania proceduralnego w PHP? PHP5 
posiada świetny model OOP oraz silnik, o jakim 
programiści PHP4 mogą tylko marzyć i który 
sprawia, że użytkownicy  Javy płoną z zazdrości.

Drugim argumentem jest to, że do PHP 

dodano potężną (i, biorąc pod uwagę np. 

metody magiczne,  nieortodoksyjną) funkcjo-

nalność OO, dzięki której język ten wyraź-

nie różni się od innych. W kolejnym artykule 

przyjrzymy się tym specjalnym możliwościom 

i pokażemy, na jak wiele one pozwalają.

Dlaczego obiekty?

Powszechnym zarzutem przeciwko 

obiektom, wysuwanym przez programi-

stów, którzy ich nie stosują jest to, że 

gdy patrzą na kod OO, mówią: Mógłbym 

zrobić to samo bez obiektów. Z technicz-

Po co nam PHP5?

Erik Zoltán

W SIECI

1. http://php.net – ofi cjalna 

strona PHP

2. http://www.eventhelix.com/

RealtimeMantra/Object_
Oriented/ – Object Oriented 
Design and Principles, witry-
na poświęcona OOP

3. http://www.objectfaq.com/

oofaq2/ – Object Orien-
ted FAQ

4. http://www.agilemodeling

.com/artifacts/
sequenceDiagram.htm
 – wy-
jaśnienie diagramów se-
kwencyjnych

Co powinieneś 
wiedzieć...

Pownieneś znać programowanie proce-
duralne (PP) w PHP.

Co obiecujemy...

Po przeczytaniu artykułu będziesz wie-
dział jak modelować zorientowane obiek-
towo aplikacje w PHP5.

60_61_62_63_64_65_66_67____php5_zoltan.indd   60

2005-09-12, 10:52:03

background image

Techniki

PHP5

PHP Solutions Nr 5/2005

www.phpsolmag.org

61

nego punktu widzenia mają rację – osta-

tecznie programy OO można zreduko-

wać do zestawu kroków wykonywanych 

przez komputer, więc takie same rezul-

taty da się uzyskać metodą procedural-

ną. Oczywiście w praktyce nie jest to aż 

takie proste.

Programy składają się z dwóch ele-

mentów: kodu i danych. Tradycyjne pro-

gramowanie proceduralne koncentru-

je się przede wszystkim na kodzie, pod-

czas gdy struktura danych w naturalny 

sposób wynika z funkcjonalności. Pro-

gramowanie obiektowe skupia się nato-

miast głównie na strukturze danych, zaś 

określone oddziaływania funkcjonalne w 

naturalny sposób wynikają ze struktury 

obiektów.

Spójrzmy na przykład ilustrujący różni-

cę między podejścierm obiektowym a tra-

dycyjnym. Wyobraźmy sobie dobrze zna-

ną aplikację, na przykład generator parse-

rów czytający instrukcje w jednym forma-

cie i generujący dane wyjściowe w innym. 

Charakterystyczna dla PHP dobra obsłu-

ga łańcuchów i tablic sprawia, że język ten 

jest doskonałym wyborem przy tworzeniu 

tego typu aplikacji.

Projekt proceduralny

Przy korzystaniu z pionowego (ang 

top-down) projektowania proceduralne-

go można zacząć od procedury głów-

nej, a następnie rozbić ją na podproce-

dury. Może to wyglądać tak, jak na Ry-

sunku 1.

Strzałki na tym schemacie przedsta-

wiają przepływ kontroli. Zauważmy, że 

myślimy tu w kategoriach czasowników 

(lub raczej zdań). Program główny wywo-

łuje każdą z podfunkcji, na którą wskazu-

je, a z kolei każda z tych podfunkcji może 

wywołać funkcję 

reportErrors

. Pamiętaj-

my, że aplikacja tłumaczy dane wejściowe 

w języku źródłowym na dane wyjściowe 

w docelowym. W przypadku tego sche-

matu można sobie wyobrazić, że funk-

cja 

getInputText

 wczyta zawartość pliku, 

zaś 

tokenizeInput

 podzieli ją na kawał-

ki (tokeny), aby parsowanie było łatwiej-

sze. Z kolei funkcja 

buildParseTree

 zor-

ganizuje program wejściowy w drzewia-

stą strukturę zgodnie z regułami języka 

źródłowego, natomiast 

verifySemantics

 

sprawdzi drzewo, by upewnić się, czy nie 

pojawią się określone błędy oraz dokona 

odpowiednich transformacji. Wreszcie, 

Rysunek 1. 

Pionowy projekt proceduralny parsera

Rysunek 2. 

Najwyższego poziomu obiektowy projekt parsera

Rysunek 3. 

Dodawanie właściwości do obiektów

generateCode

 użyje informacji z drzewa 

parsowania, by wygenerować program 

wyjściowy w języku docelowym. Takie po-

dejście wygląda klarownie i z pewnością 

jest łatwe w zaprojektowaniu oraz imple-

mentacji. Może natomiast być niezbyt wy-

godne przy jakichkolwiek zmianach, czym 

zajmiemy się później.

Projekt OO 

najwyższego poziomu

Przyjrzyjmy się projektowi obiektowemu 

najwyższego poziomu (ang top-level) dla 

aplikacji tego samego typu co poprzednio 

(Rysunek 2).

W projektowaniu OO najwyższe-

go poziomu nie zajmujemy się tym, jak 

60_61_62_63_64_65_66_67____php5_zoltan.indd   61

2005-09-12, 10:52:11

background image

PHP5

Techniki

PHP Solutions Nr 5/2005

www.phpsolmag.org

62

Rysunek 4. 

Diagram parsowania

Rysunek 5. 

Diagram sekwencyjny procesu generowania kodu wyjściowego

60_61_62_63_64_65_66_67____php5_zoltan.indd   62

2005-09-12, 10:52:18

background image

Techniki

PHP5

PHP Solutions Nr 5/2005

www.phpsolmag.org

63

działa aplikacja. Zamiast o czasowni-

kach, myślimy o rzeczownikach. Każ-

dy z prostokątów na tym schemacie re-

prezentuje pojedynczą klasę (typ obiek-

tu). 

Document

 przedstawia informacje za-

warte w pliku wejściowym lub wyjścio-

wym. Klasa 

Grammar

 reprezentuje skład-

nię i semantykę docelowego języka, 

zaś  

Production

 pojedynczy element ję-

zyka. Zwróćmy uwagę na romb łączą-

cy klasy  

Grammar

 i 

Production

 – świad-

czy on o tym, że 

Grammar

 może zawie-

rać wiele klas  

Production

. Z kolei kla-

sa 

ParseTree

 zawiera te same informa-

cje, co 

Document

, ale jest zorganizowa-

na w hierarchiczną strukturę zdefi niowa-

ną przez 

Grammar

. Klasa 

TreeNode

 jest tą 

częścią drzewa parsowania, która repre-

zentuje pojedynczy element języka (czyli 

pojedynczą klasę  

Production

 zawartą w 

Grammar

). Natomiast 

Parser

 odpowiada 

za dane wejściowe, co sprowadza się 

do przekształcania dokumentu wejścio-

wego w drzewo parsowania. Z kolei kla-

sa 

Generator

 ma za zadanie przekształ-

cić drzewo parsowania w dokument wyj-

ściowy.

Dodawanie właściwości

Gdy już mamy projekt najwyższego po-

ziomu, przyjrzyjmy się dokładniej da-

nym. Określimy wewnętrzne zmienne, 

których będzie potrzebował każdy obiekt 

– zarówno informacje zawarte w obiek-

tach, jak i te, które łączą ze sobą obiekty 

(można je porównać do kleju). Rysunek 

Rysunek 6. 

Klasy zawierające pola i metody

3 przedstawia zmodyfi kowany schemat, 

pokazujący zmienne wewnętrzne (pola) 

każdego z obiektów.

Omówmy pokrótce zmienne, które do-

daliśmy do każdej klasy.

•   Dokument musi zawierać jakiś tekst 

– zdecydowaliśmy się stworzyć tabli-

cę reprezentującą fragmenty doku-

mentu (tokeny). Możemy podzielić do-

kument  na  tokeny  w  dowolny,  dosto-

sowany do parsowanego języka spo-

sób.

•   Drzewo parsowania musi znać głów-

ny węzeł (ang. root node) drzewa i 

skojarzoną z nim klasę 

Gram mar

.

•   Każdy węzeł drzewa musi być po-

łączony z klasą 

Production

 w doce-

lowym języku i może zawierać tekst 

zawarty w pojedynczym tokenie. 

Może też mieć potomków będących 

dodatkowymi węzłami drzewa.

•  

Grammar

 może posiadać macierz 

wszystkich swoich elementów ty-

pu 

Production

, ale musi znać głów-

ny element 

Production

 lub element ję-

zyka dla docelowego pliku. Aby klasa 

Grammar

 reprezentowała PHP, możemy 

jej nadać nazwę, na przykład program.

•   Każda  klasa 

Production

 ma indy-

widualną nazwę. Reprezentowa-

ny przez nią element języka mo-

że być opcjonalny lub wymaga-

ny. Może istnieć kilka alternatyw-

nych defi nicji, z których każda bę-

dzie klasą 

Production

. ( przykład: w 

PHP istnieje kilka różnych zasadni-

czych funkcji, więc klasa 

Production

 

o nazwie function może wymie-

niać je wszystkie jako alternatyw-

ne). Wreszcie, element języka może 

zwierać pewną liczbę komponentów 

potomnych, które muszą być obec-

ne w stałym porządku (przykład: 

przypisanie zmiennej wymaga jej 

nazwy, znaku równości i wyrażenia 

reprezentującego wartość przecho-

wywaną w zmiennej).

Przedstawiliśmy tylko ogólny szkic: do-

dawanie do projektów nowych atrybu-

tów w czasie projektowania i późniejsze-

go kodowania jest powszechne. Typowe 

jest także wstawianie do projektu nowych 

klas, w miarę jak określamy atrybuty każ-

dej z nich.

Diagramy sekwencji

Następna faza projektu obiektowego jest 

taka sama, jak pierwsza faza przy pro-

jektowaniu proceduralnym. Aby stwo-

rzyć aplikację, musimy stworzyć meto-

dy (lub funkcje) dla każdej klasy i spraw-

dzić, jak łączą się one i współpracują ze 

sobą. Diagram sekwencji lub diagram in-

terakcji to dobry sposób, gdyż pokazu-

je, jak obiekty wywołują się wzajemnie 

w czasie typowej interakcji. W przypad-

ku generatora parserów typową interak-

cją może być parsowanie i generowa-

nie kodu. Spójrzmy na schematy tych 

dwóch sytuacji. 

60_61_62_63_64_65_66_67____php5_zoltan.indd   63

2005-09-12, 10:52:23

background image

PHP5

Techniki

PHP Solutions Nr 5/2005

www.phpsolmag.org

64

Weźmy najpierw schemat parsowa-

nia (Rysunek 4). Na tym diagramie apli-

kacja przeprowadza proces parsowa-

nia dokumentu wejściowego. Jego ce-

lem jest stworzenie drzewa parsowa-

nia, które będzie reprezentować instruk-

cje zawarte w dokumencie wejściowym. 

Każda strzałka reprezentuje wywoła-

nie funkcji lub pewnego rodzaju opera-

cję wewnętrzną. Najpierw  

Application

 

nakazuje klasie 

Document

, by załado-

wała (

Load

) plik wejściowy i jako argu-

ment podaje jego nazwę. Następnie 

Application

 nakazuje parserowi (

Par-

ser

), by przetworzył (

Parse

) dokument 

wejściowy (argumentem jest obiekt kla-

sy 

Document

).

Parser

 zapytuje 

Grammar

 w celu okre-

ślenia głównego obiektu 

Production

 (w 

przypadku języka PHP zapewne nazywał-

by się on program). Następnie otwiera w 

pętlę, w której powtarza następujące ope-

racje aż do końca pliku:

1.  

GetToken

: pobiera natępny token z do-

kumentu.

2.  

TokenDivider

: klasa 

Document

 py-

ta 

Gram mar

, czy każdy znak jest se-

paratorem tokenów. W PHP sepa-

ratorami byłyby znaki przestankowe 

(takie jak nawiasy, przecinki czy cu-

dzysłowy), a litery, cyfry i spacje już 

nie.

3.  

IsValid

: sprawdza poprawność toke-

na względem aktualnej 

Production

.

4.  

AddNode

: dodaje nowy węzeł do drze-

wa parsowania. Drzewo to jest two-

rzone wewnętrznie przez 

AddNode

.

Po zakończeniu tych działań możemy się 

spodziewać, że drzewo parsowania zosta-

ło poprawnie stworzone. Spójrzmy więc 

na diagram sekwencyjny procesu tworze-

nia kodu wyjściowego w języku docelo-

wym (Rysunek 5).

Po zakończeniu parsowania doku-

mentu wejściowego, aplikacja powinna 

wygenerować dokument wyjściowy w ję-

zyku docelowym. W tym celu 

Application

 

wywołuje klasę 

Generator

. Ten obiekt po 

prostu pobiera główny węzeł z 

ParseTree

 

i nakazuje mu, aby wygenerował (

Genera-

te

) się sam.

Podczas generowania samego siebie, 

węzeł drzewa parsowania prosi powią-

zaną z nim klasę 

Production

 o wykona-

nie wszystkich przekształceń wyjściowych 

na nim i jego potomkach (przykładowo, 

Production

 mogłaby przetłumaczyć słowo 

kluczowe lub operator na język docelowy 

lub posortować węzły potomne w nowym 

porządku; jest to rodzaj operacji seman-

tycznej, którą w projekcie proceduralnym 

wykonałaby funkcja 

verifySemantics

). Je-

śli drzewo parsowania ma potomków, to 

zaczyna pętlę, w której wywołuje ich re-

kursywnie z żądaniem, aby sami się wy-

generowali. Jeśli węzeł drzewa parsowa-

nia zawiera token tekstu wejściowego, do-

daje go do dokumentu wyjściowego. Nie-

które węzły drzewa będą miały potomków 

i nie będą zawierać tekstu, inne odwrot-

nie – będą zawierać tekst bez potomków. 

Jeszcze inne będą miały jedno i drugie.

Dodawanie metod 

do diagramów klas

Opierając się na tych diagramach sekwen-

cyjnych, możemy powrócić do stworzone-

go już projektu bazującego na klasach i 

dodać metody potrzebne w tych sekwen-

cjach (zwykle używa się łatwo dostępnych 

na rynku narzędzi, które robią to automa-

tycznie).

Patrząc na diagramy klas i diagramy 

sekwencyjne, zauważymy pewne luki w 

projekcie – możemy potrzebować bardziej 

szczegółowych schematów, aby dokład-

niej prześledzić część procesu. Dokład-

ność w fazie projektu zwykle się opłaca, 

bo to najlepszy sposób na jak najwcze-

śniejsze zidentyfi 

kowanie i poprawienie 

błędów.

W tym miejscu możemy przystąpić 

do właściwego kodowania. Jeśli mamy 

zespół programistów, każdy z nich mo-

że zająć się daną klasą lub grupą klas. 

Czasem część klas współpracuje ze so-

bą tak ściśle, że programiści i tak muszą 

Rysunek 7. 

Diagram sekwencyjny planu testowania klasy Document za pomocą naszej 

procedury testującej

60_61_62_63_64_65_66_67____php5_zoltan.indd   64

2005-09-12, 10:52:28

background image

Techniki

PHP5

PHP Solutions Nr 5/2005

www.phpsolmag.org

65

działać wspólnie. W tym projekcie sku-

pimy się głównie na klasach 

TreeNode

 i 

Production

, które są ze sobą bardzo bli-

sko związane. W innych przypadkach 

klasy są bardziej odizolowane (na przy-

kład 

Document

) i można je tworzyć nieza-

leżnie.

Programowanie 

sterowane testami

Ważną zaletą OO jest to, że każdy 

obiekt jest jednostką, którą można od-

dzielnie testować. Podejście oparte 

na testach, zwane również TDP (ang. 

test-driven programming) lub TDD (ang. 

test-driven development) zakłada rygo-

rystyczne testowanie każdego kompo-

nentu zanim zostanie on włączony do 

gotowej aplikacji. W ten sposób pro-

ces testów właściwie steruje tworzeniem 

programu, skąd pochodzi nazwa tej me-

tody. Opisujemy ją szczegółowo w arty-

kule Testowanie modułów z użyciem fra-

meworka SimpleTest.

Opierając się na naszym przykładzie 

wyobraźmy sobie, że stworzyliśmy pełną 

implementację klasy 

Document

 (Listing 

1). Zwróćmy uwagę na tekstową wesję 

diagramu klasy umieszczoną w kodzie 

przed  deklaracją klasy. Jest to pomoc-

ne, bo pozwala zobaczyć interfejs ze-

wnętrzny bez konieczności przewijania 

całego listingu. Warto też modyfi kować 

ten diagram zgodnie ze zmianami w kla-

sie – ułatwia to porównanie kodu źródło-

wego do projektu w celu sprawdzenia, 

czy zgadzają się ze sobą.

Dla zilustrowania procedury testowa-

nia, w powyższym kodzie umieszczono 

celowo dwa błędy – w celu ułatwienia ich 

odnalezienia zostały one oznaczone. Od-

kryjemy i naprawimy je później.

Istotną rzeczą w tej implementacji jest 

fakt, że zawartość klasy jest oparta na 

założeniach zupełnie innych, niż te uży-

te w intefejsie zewnętrznym. Użytkownik 

tej klasy poczyniłby zapewne następują-

ce założenia:

•   Kiedy wywoływana jest metoda 

Load

obiekt 

Document

 odczyta całą zawar-

tosć pliku.

•   Za  każdym  wywołaniem  metody 

GetToken

 obiekt 

Document

 przeskanu-

je jeden token w przód i zwróci war-

tość następnego.

Takie zachowanie obiektu 

Document

 nie by-

łoby jednak zbyt wydajnie, gdyż musiałby

Listing 1. 

Klasa Document

<?

php

/*

+--------------------------+

| Document                 |

+--------------------------+

|-fi leName                 |

|-text: array              |

+--------------------------+

|+Load($fi leName)          |

|+GetToken($grammar, $pos) |

|+AddToken($token)         |

|+Write($fi leName)         |

+--------------------------+

*/

class

 Document

{

   

private

 

$text

 = 

array

()

;

   

private

 

$loaded

 = false;

   

function

 Load

(

$fi leName

){

      

$this

-

>

fi leName = 

$fi leName

;

   

}

   

function

 GetToken

(

$grammar

$pos

){

      

if

 

(

!

$this

-

>

loaded

){

         

$handle

 = 

fopen

(

$this

-

>

fi leName, 

"r"

)

;

         

$accum

 = 

""

;

         

while

 

((

$c

 = 

fgetc

(

$handle

))

 !== false

){

            

if

 

(

$grammar

-

>

TokenDivider

(

$c

)){

               

if

 

(

$accum

 != 

""

){

                  

$this

-

>

text

[]

 = 

$accum

;

                  

$accum

 = 

""

;

               

}

               

$this

-

>

text

[]

 = 

$c

;

            

}

            

else

               

$accum

 .= 

$c

;

         

}

         

fclose

(

$handle

)

;

        

 //if ($accum != "")

        

 //$this->text[] = $accum;

         

$this

-

>

loaded = true;

      

}

      

if

 

(

count

(

$this

-

>

text

)

 

>

 

$pos

)

         

return

 

$this

-

>

text

[

$pos

]

;

      

return

 null;

   

}

   

function

 AddToken

(

$token

){

      

$this

-

>

text

[]

 = 

$token

;

   

}

   

function

 Write

(

$fi leName

){

      

$this

-

>

fi leName = 

$fi leName

;

      

$handle

 = 

fopen

(

$fi leName

"w"

)

;

      

$tokens

 = 

count

(

$this

-

>

text

)

;

      

for

 

(

$i

=1; 

$i

<

$tokens; $i++

)

 

        

fwrite

(

$handle, $this-

>

text

[

$i

])

;

      

fclose

(

$handle

)

;

   

}

}

?>

60_61_62_63_64_65_66_67____php5_zoltan.indd   65

2005-09-12, 10:52:34

background image

PHP5

Techniki

PHP Solutions Nr 5/2005

www.phpsolmag.org

66

on przechowywać całą zawartość pliku w 

pamięci. Pociągałoby ono za sobą rów-

nież wielokrotne wewnętrzne kopiowanie 

długich łańcuchów. Zamiast tego użyliśmy 

więc następującej implementacji:

•   Kiedy wywoływana jest metoda 

Load

obiekt 

Document

 zapamiętuje nazwę 

określonego pliku i nie robi nic więcej.

•   Za  pierwszym  wywołaniem 

GetToken

 

obiekt 

Document

 czyta całą zawartość 

pliku i dzieli go na fragmenty, umiesz-

czając je w wewnętrznej tablicy, zgod-

nie z tokenami zdefi niowanymi  przez 

Grammar

.

•   W  kolejnych  wywołaniach 

GetToken

 

obiekt 

Document

  po  prostu  wyszukuje 

odpowiedni numer tokena i zwraca go 

wywołującemu obiektowi.

Stanowi to odejście od oryginalnego dia-

gramu sekwencyjnego. Nie jest to pro-

blemem, ponieważ taka implementacja 

jest bardziej wydajna i wciąż zgodna z 

duchem projektu opartego na klasach. 

W rzeczywistości nasz projektant mógł-

by zechcieć cofnąć się i zmodyfi kować 

diagram sekwencyjny, podczas gdy pro-

gramiści kontynuowaliby pracę.

Aby tak się stało, musimy mieć 

przynajmniej częściowo sprawną klasę 

Grammar

. Na raie zadowolimy się protezą, 

która po prostu dostarczy listę tokenów 

klasie wywołującej (Listing 2).

Obiekt wywołujący, który tworzy no-

wą instancję klasy 

Grammar

, przekazuje po 

prostu tablicę zawierającą tokeny  

Grammar

Używa jej wewnętrznie aby określić, czy da-

ny znak jest tokenem w języku. Możemy się 

spodziewać dodania do tej klasy pozostałej 

funkcjonalności później.

Zautomatyzowana 

procedura testująca

Aby to przetestować, musimy stworzyć 

procedurę, która każe wykonać klasie 

Document

 jej wszystkie kroki i porówna 

spodziewane rezultaty do rzeczywistych. 

Taka procedura musi być powtarzal-

na, tzn. ma się zachowywać poprawnie 

podczas setek wywołań w trakcie proce-

su debugowania. W dodatku procedura 

testująca musi być w przyszłości utrzy-

mywana równolegle z klasą 

Document

 

– jeśli zmodyfi kujemy lub rozubuduje-

my 

Document

, będziemy musieli uaktu-

alnić procedurę testującą i upewnić się, 

że wykonuje ona stare testy. Nie chce-

my przecież dodawać nowej funkcjonal-

ności, która zepsuje stary kod. Naszym 

zamiarem jest również wprowadzanie 

nowych testów dla nowo dodawanych 

możliwości klasy.

Na Rysunku 7 przedstawiamy prosty 

diagram sekwencyjny pokazujący, w jaki 

sposób planujemy testować obiekt 

Document

 

korzystając z naszej procedury.

Musimy stworzyć dwa oddzielne obiek-

ty 

Document

 – jeden do testowania wejścia, 

a drugi do sprawdzania wyjścia. Procedu-

ra testująca po prostu stworzy dokument 

wejściowy i pobierze wszystkie jego toke-

ny, porównując je z oczekiwaniami i rapor-

tując każde odstępstwo. Następnie stworzy 

dokument wyjściowy, doda do niego toke-

ny i zapisze jego zwartość do pliku. Rezulta-

tem tej operacji również będzie porównanie 

do oczekiwań i zgłoszenie wszystkich różnic 

jako błędów.

Trzeba pamiętać, że podczas automa-

tycznego testowania jedna połowa otrzy-

manych błędów będzie dotyczyć testowa-

nego kodu, a druga samej procedury testu-

jącej. Czy warto włożyć dodatkowy wysi-

łek w pisanie, utrzymywanie i debugowanie 

nadprogramowego kodu? Tak, gdyż efek-

tem będzie zwiększona niezawodność klas, 

które próbujemy zintegrować do stworzenia 

większej aplikacji. Każdy, kto miał kiedykol-

wiek problemy z nieprawidłowym zachowa-

niem dużego programu i nie mógł określić 

przyczyn błędów, z pewnością od razu do-

ceni takie podejście. Listing 3 przedstawia 

kod testujący, który spełnia powyższe wy-

magania.

Pętla testująco-debugująca

Chociaż zaimplementowaliśmy w pełni tylko 

jedną klasę (i częściowo drugą), jesteśmy 

gotowi do rozpoczęcia testowania i debugo-

wania. W rezultacie będziemy mogli zidenty-

fi kować problemy, które mogłyby prowadzić 

do poprawek w kodzie czy zmian projek-

tu. Jak najwcześniejsze rozpoznanie takich 

zmian jest bardzo pomocne. Uruchomienie 

naszego kodu testowego daje w efekcie ra-

port o błędach przedstawiony na Rysunku 8.

Listing 2. 

Protezowa implementacja klasy Grammar

<?

php

class

 Grammar

   public 

$Tokens

;

   

function

 __construct

(

$tokenArr

){

      

$this

-

>

Tokens = 

$tokenArr

;

   

}

   

function

 TokenDivider

(

$token

){

      

return

 array_key_exists

(

$token

$this

-

>

Tokens

)

;

   

}

}

?>

Rysunek 8. 

Raport o błędach

60_61_62_63_64_65_66_67____php5_zoltan.indd   66

2005-09-12, 12:37:09

background image

Techniki

PHP5

PHP Solutions Nr 5/2005

www.phpsolmag.org

67

W naszym kodzie są dwa błędy. Po 

pierwsze, gubimy część tekstu przetwa-

rzanego przez naszą pętlę tokenizują-

cą.  W celu wyeliminowania tego proble-

mu musimy wstawić dodatkowe wyraże-

nie uniemożliwiające pominięcie tekstu, 

przed którym nie ma żadnego tokena 

(tak jak tekst 

ostroznie!

 w naszym pli-

ku testującym). Po drugie, zmienna in-

deksu pętli zaczyna się od 1 zamiast od 

0. To sprawia, że gubimy pierwsze 

[

 to-

kena na wyjściu.

Aby naprawić te błędy, wprowadzi-

my w kodzie klasy 

Document

 następujące 

zmiany w zaznaczonych wcześniej miej-

scach.

Po pierwsze, te linie pownny być od-

komentowane w następujący sposób:

   if ($accum != "")

   $this->text[] = $accum;

W tych liniach 

1

 wewnątrz polecenia 

for

 

zostało zastąpione przez 

0

:

   for ($i=0; $i<$tokens; $i++)

      fwrite($handle, $this->text[$i]);

Te poprawki zlikwidują błędy w kodzie.

Podsumowanie

Nie musimy pokazywać całej implemen-

tacji przykładowej aplikacji, jest nam ona 

bowiem potrzebna wyłącznie jako ilustra-

cja wdrożenia spełniającego standar-

dy przemysłowe projektowania, tworze-

nia i testowania oprogramowania zorien-

towanego obiektowo w PHP5. Proces 

ten różni się od podejścia stosowane-

go w PHP w przeszłości. Mamy nadzie-

ję, że posłużyło to za przekonujący argu-

ment za szybkim zaadoptowaniem PHP5 

przez fi rmy i organizacje korzystające z 

PHP lub rozważające jego stosowanie. 

Listing 3. 

Kod testujący

<

html

>

   

<

body

>

      

<

h1

>

TestDocument.php

<

/h1

>

      

<

h2

>

Wyniki testów

<

/h1

>

<?

php

// Funkcja autoload dynamiczie zaimportuje wszystkie potrzebne do testowania

// klasy

require_once

 

"autoload.phpf"

;

$errors

 = 0;

// Tworzymy plik do testowania wejścia

$testFileName

 = 

"testDocument.txt"

;

$testText

 = 

"[To jest_jakis_tekst[rozbity na tokeny]]ostroznie!"

;

$handle

 = 

fopen

(

$testFileName

,

'w'

)

;

fwrite

(

$handle

$testText

)

;

fclose

(

$handle

)

;

// Stwórz obiekt Grammar i zdefi niuj tokeny, które należy użyć.

$tokens

 = 

array

()

;

$tokenChars

 = 

array

(

" "

=

>

true, 

"["

=

>

true, 

"]"

=

>

true

)

;

$grammar

 = 

new

 Grammar

(

$tokenChars

)

;

// To są tokeny spodziwane w oparciu o Grammar

$expectedTokens

 = 

array

(

"["

"To"

" "

"jest_jakis_tekst"

"["

"rozbity "

"

"na"

" "

"tokeny"

"]"

"]"

"ostroznie!"

)

;

// Stwórz nowy obiekt wejściowy Document

$doc

 = 

new

 Document

()

;

$doc

-

>

Load

(

$testFileName

)

;

// Get the tokens from the input document (based on our grammar)

for

 

(

$position

=0; 

$position

<

100; $position++){

 

  

$thisToken = $doc-

>

GetToken

(

$grammar

$position

)

;

   

if

 

(

$thisToken

 == null

)

      

break

;

   

$tokens

[]

 = 

$thisToken

;

}

// Porównaj prawdziwe tokeny ze spodziewanymi

for

 

(

$i

=0; 

$i

<

count($expectedTokens); $i++){

 

  

if ($i

>

=

count

(

$tokens

)){

      

$errors

++;

      

echo

 

"<br>Błąd: niepasująca liczba tokenów $i: '$expectedTokens[$i]'"

;

      

break

;

   

}

   

if

 

(

$tokens

[

$i

]

 != 

$expectedTokens

[

$i

]){

      

$errors

++;

      

echo

 

"<br>Błąd: niepasująca liczba tokenów $i: spodziewana

         '$expectedTokens[$i]'ale otrzymano '$tokens[$i]'."

;

   

}

}

// Teraz stwórz dokument wyjściowy i wyeksportuj jego zwartość do zewnętrznego

// pliku

$outDoc

 = 

new

 Document

()

;

for

 

(

$i

=0; 

$i

<

count

(

$expectedTokens); $i++

)

 

  

$outDoc-

>

AddToken

(

$expectedTokens

[

$i

])

;

$outDoc

-

>

Write

(

$testFileName

)

;

$outputResult

 = fi le_get_contents

(

$testFileName

)

;

// Porównaj tekst pliku wyjściowego do spodziewanego tekstu wyjściowego

if

 

(

$testText

 != 

$outputResult

){

   

$errors

++;

   

echo

 

"<p>Błąd: tekst pliku wyjściowego nie pasuje do tekstu wejściowego

      pliku:<br>$testText<br>$outputResult</p>"

;

}

// Na koniec zaraportuj liczbę błędów użytkownikowi

echo

 

"<h2>W sumie błędów: $errors</h2>"

;

?>

<

/body

>

<

/html

>

Erik Zoltán wykorzystuje PHP od 2000 r.
i używa go do tworzenia stron WWW 
oraz do backendowych (szkieletowych) 
systemów tłumaczenia i authoringu. 
Posiada 15 lad doświadczenia 
w programowaniu obiektowym, 
tworzył narzędzia projektowe i języki 
OO. Prowadził wykłady dla setek 
profesjonalnych programistów. Mieszka 
w Massachusetts, USA, gdzie obecnie 
pracuje jako wolny strzelec.
Kontakt z autorem: erik@zoltan.org

O autorze

60_61_62_63_64_65_66_67____php5_zoltan.indd   67

2005-09-12, 10:52:47