Programowanie
obiektowe w PHP 5
Autor: Hasin Hayder
ISBN: 978-83-246-1821-7
Tytu³ orygina³u:
Object-Oriented
Programming with PHP5
Format: 170x230, stron: 264
• Naucz siê definiowaæ w³aœciwoœci obiektów
• Stwórz kod, który bêdzie ³atwy w zarz¹dzaniu
• Zbuduj wydajn¹ i bezpieczn¹ aplikacjê
Programowanie obiektowe (OOP) wci¹¿ zyskuje rzesze nowych zwolenników.
Poniewa¿ opiera siê ono na klasach i obiektach, jest znacznie bardziej intuicyjne
ni¿ programowanie strukturalne. Do jego podstawowych zalet zaliczyæ nale¿y tak¿e
³atwoœæ modyfikowania oraz mo¿liwoœæ wielokrotnego wykorzystania klas. PHP 5
udostêpnia wiele ró¿norodnych mechanizmów (na przyk³ad obs³ugê wyj¹tków czy zbiór
interfejsów znacznie rozszerzaj¹cych mo¿liwoœci klas u¿ytkownika) oraz pe³ny modu³
obs³uguj¹cy styl programowania OOP, dziêki czemu jest doskona³ym narzêdziem,
pozwalaj¹cym tworzyæ wydajne, bezpieczne i dynamiczne aplikacje z wykorzystaniem
programowania obiektowego.
Ksi¹¿ka „Programowanie obiektowe w PHP 5” jest doskona³ym Ÿród³em informacji,
które pomo¿e Ci zrozumieæ najistotniejsze koncepcje programowania zorientowanego
obiektowo w PHP 5. Podrêcznik zawiera omówienie zagadnieñ podstawowych
oraz bardziej zaawansowanych, takich jak architektura Model-View-Controller (MVC)
oraz testy jednostkowe. Znajdziesz tu tak¿e praktyczne wskazówki i przyk³ady
dotycz¹ce m.in. u¿ycia biblioteki Standard PHP Library. Dowiesz siê, jak u¿ywaæ
odpowiedniego wzorca, aby zwiêkszyæ wydajnoœæ kodu, czym jest testowanie
jednostkowe i dlaczego stanowi ono zasadnicz¹ czêœæ tworzenia dobrego oraz
stabilnego projektu programu. Nauczysz siê tworzyæ wydajne, bezpieczne i ³atwe
w zarz¹dzaniu aplikacje.
• Praca z OOP — tworzenie obiektów
• Funkcje dostarczaj¹ce informacje o klasie
• Iteratory
• Automatyczne wczytywanie klas
• Serializacja
• Wzorce projektowe
• Refleksja i testy jednostkowe
• Biblioteka Standard PHP Library
• Obs³uga baz danych z u¿yciem stylu OOP
• U¿ywanie architektury MVC
Spis tre
Ăci
O autorze
9
O recenzentach
11
Wprowadzenie
13
Co zawiera ta ksi
Èĝka?
13
Dla kogo jest przeznaczona ksi
Èĝka?
15
Konwencje zastosowane w ksi
Èĝce
15
U
ĝycie przykïadowych kodów
16
Rozdzia
ï 1. Styl OOP kontra programowanie proceduralne
17
Wprowadzenie do PHP
18
Zaczynamy
18
Krótka historia stylu programowania OOP w PHP
19
Proceduralny styl kodowania kontra OOP
19
Zalety u
ĝywania stylu OOP
20
Wnikliwa analiza obiektu
22
Ró
ĝnice miÚdzy stylem OOP w PHP 4 i PHP 5
23
Niektóre podstawowe poj
Úcia z zakresu OOP
25
Ogólne konwencje programowania
26
Podsumowanie
27
Rozdzia
ï 2. RozpoczÚcie pracy z OOP
29
Tworzenie obiektów
29
Dost
Úp do wïaĂciwoĂci i metod z wewnÈtrz klasy
31
U
ĝywanie obiektu
31
Modyfikatory dost
Úpu
32
Konstruktory i destruktory
34
Sta
ïe klasy
36
Spis tre
Ğci
4
Rozszerzanie klasy (dziedziczenie)
38
Nadpisywanie metod
40
Uniemo
ĝliwianie nadpisywania
40
Uniemo
ĝliwianie rozszerzania
40
Polimorfizm
41
Interfejs
42
Klasa abstrakcyjna
44
Metody i w
ïaĂciwoĂci statyczne
45
Metody akcesorów
48
U
ĝywanie metod magicznych do pobierania i ustalania wartoĂci wïaĂciwoĂci klasy
49
Metody magiczne s
ïuĝÈce do przeciÈĝania metod klasy
51
Wizualne przedstawienie klasy
52
Podsumowanie
52
Rozdzia
ï 3. Jeszcze wiÚcej OOP
55
Funkcje dostarczaj
Èce informacje o klasie
55
Sprawdzanie, czy dana klasa istnieje
55
Okre
Ălanie aktualnie wczytanej klasy
56
Sprawdzanie, czy istniej
È podane metody i wïaĂciwoĂci
56
Okre
Ălanie rodzaju klasy
57
Okre
Ălanie nazwy klasy
57
Obs
ïuga wyjÈtków
58
Zebranie wszystkich b
ïÚdów PHP jako wyjÈtku
62
Iteratory
63
Obiekt ArrayObject
65
Konwersja tablicy na obiekt
66
Dost
Úp do obiektów z zastosowaniem stylu tablicy
67
Serializacja
68
Metody magiczne w serializacji
70
Klonowanie obiektu
72
Automatyczne wczytywanie klas, czyli klasy na
ĝÈdanie
73
añcuchowe wiÈzanie metod
74
Cykl
ĝycia obiektu w PHP oraz buforowanie obiektu
75
Podsumowanie
77
Rozdzia
ï 4. Wzorce projektowe
79
Jak to zosta
ïo zrobione wczeĂniej?
79
Wzorzec Strategia
80
Wzorzec Fabryka
82
Wzorzec Fabryka abstrakcyjna
85
Wzorzec Adapter
87
Wzorzec Singleton
91
Wzorzec Iterator
93
Wzorzec Obserwator
96
Wzorzec Proxy, czyli mechanizm Lazy Loading
98
Wzorzec Dekorator
100
Spis tre
Ğci
5
Wzorzec Active Record
103
Wzorzec Fasada
103
Podsumowanie
106
Rozdzia
ï 5. Refleksja i testy jednostkowe
109
Refleksja
109
ReflectionClass
110
Klasa ReflectionMethod
115
Klasa ReflectionParameter
117
Klasa ReflectionProperty
119
Testy jednostkowe
121
Korzy
Ăci pïynÈce z testów jednostkowych
121
Krótkie wprowadzenie do niebezpiecznych b
ïÚdów
122
Przygotowanie do przeprowadzania testów jednostkowych
123
Rozpocz
Úcie przeprowadzania testów jednostkowych
124
Testowanie obiektu EmailValidator
127
Testy jednostkowe dla zwyk
ïych skryptów
130
Podej
Ăcie Test Driven Development (TDD)
134
PHPUnit API
139
Podsumowanie
147
Rozdzia
ï 6. Biblioteka Standard PHP Library
149
Obiekty dost
Úpne w SPL
149
Klasa ArrayObject
150
Klasa ArrayIterator
155
Klasa DirectoryIterator
157
Klasa RecursiveDirectoryIterator
161
Klasa RecursiveIteratorIterator
162
Klasa AppendIterator
162
Klasa FilterIterator
164
Klasa LimitIterator
165
Klasa NoRewindIterator
166
Interfejs SeekableIterator
167
Interfejs RecursiveIterator
168
Obiekt SPLFileObject
169
Obiekt SPLFileInfo
170
Obiekt SPLObjectStorage
172
Podsumowanie
174
Rozdzia
ï 7. Obsïuga baz danych z uĝyciem stylu OOP
175
Wprowadzenie do MySQLi
175
Nawi
Èzywanie poïÈczenia z MySQL w stylu zgodnym z OOP
176
Pobieranie danych w stylu zgodnym z OOP
177
Uaktualnianie danych w stylu zgodnym z OOP
177
Zapytania preinterpretowane
178
U
ĝywanie obiektu BLOB w zapytaniach preinterpretowanych
180
Wykonanie procedury sk
ïadowanej za pomocÈ MySQLi i PHP
182
Spis tre
Ğci
6
PDO
183
Konfiguracja DSN dla ró
ĝnych silników baz danych
185
U
ĝywanie zapytañ preinterpretowanych za pomocÈ PDO
185
Wywo
ïywanie procedur skïadowanych
187
Inne ciekawe funkcje
187
Wprowadzenie do Data Abstraction Layers
188
ADOdb
189
MDB2
197
Wprowadzenie do ActiveRecord
200
Tworzenie nowego rekordu za pomoc
È ActiveRecord
200
Wybór lub uaktualnienie danych
201
Podsumowanie
201
Rozdzia
ï 8. Uĝywanie jÚzyka XML w stylu zgodnym z OOP
203
Format dokumentu XML
203
Wprowadzenie do SimpleXML
204
Przetwarzanie dokumentów
205
Uzyskiwanie dost
Úpu do atrybutów
206
Przetwarzanie
ěródeï Flickr za pomocÈ SimpleXML
206
Zarz
Èdzanie sekcjami CDATA za pomocÈ SimpleXML
209
XPath
210
DOM API
212
Modyfikacja istniej
Ècych dokumentów
213
Inne u
ĝyteczne funkcje
214
Podsumowanie
214
Rozdzia
ï 9. Uĝywanie architektury MVC
215
Co to jest MVC?
215
Rozplanowanie projektu
216
Projekt pliku rozruchowego
216
Dodanie obs
ïugi bazy danych
232
Sterowniki
235
Tworzenie aplikacji na podstawie gotowej struktury
245
Kontroler uwierzytelniania
246
Podsumowanie
252
Skorowidz
253
2
Rozpocz
Úcie pracy z OOP
W tym rozdziale czytelnik dowie si
Ú, w jaki sposób tworzyÊ obiekty, definiowaÊ ich atrybuty
(czyli w
ïaĂciwoĂci) oraz metody. W jÚzyku PHP obiekty zawsze sÈ tworzone za pomocÈ sïowa
kluczowego
class
. Po lekturze rozdzia
ïu czytelnik znacznie rozszerzy wiedzÚ z zakresu klas,
w
ïaĂciwoĂci i metod. Ponadto w rozdziale tym zostanÈ podjÚte tematy zwiÈzane z zasiÚgiem
metod, modyfikatorami metod oraz zostan
È przedstawione zalety pïynÈce z uĝywania interfej-
sów. Niniejszy rozdzia
ï jest takĝe wprowadzeniem do innych podstawowych funkcji programo-
wania zorientowanego obiektowo w PHP. Bior
Èc to wszystko pod uwagÚ, moĝna zaryzykowaÊ
stwierdzenie,
ĝe ten rozdziaï jest jednym z lepszych zasobów pozwalajÈcych na rozpoczÚcie
pracy z OOP w j
Úzyku PHP.
Tworzenie obiektów
Jak wcze
Ăniej wspomniano, obiekt w jÚzyku PHP jest tworzony za pomocÈ sïowa kluczowe-
go
class
. Wymieniona klasa sk
ïada siÚ z wïaĂciwoĂci i metod (publicznych bÈdě prywatnych).
Przyjrzyjmy si
Ú klasie
Emailer
, która zosta
ïa juĝ przedstawiona w rozdziale 1. Teraz przeanali-
zujemy sposób dzia
ïania klasy
Emailer
:
<?
// class.emailer.php
class Emailer
{
private $sender;
private $recipients;
private $subject;
private $body;
function __construct($sender)
{
$this->sender = $sender;
Programowanie obiektowe w PHP 5
30
$this->recipients = array();
}
public function addRecipients($recipient)
{
array_push($this->recipients, $recipient);
}
public function setSubject($subject)
{
$this->subject = $subject;
}
public function setBody($body)
{
$this->body = $body;
}
public function sendEmail()
{
foreach ($this->recipients as $recipient)
{
$result = mail($recipient, $this->subject, $this->body,
"From: {$this->sender}\r\n");
if ($result) echo "WiadomoĂÊ zostaïa wysïa do
{$recipient}<br/>";
}
}
}
?>
Powy
ĝszy kod rozpoczyna siÚ poleceniem
class Emailer
, które oznacza,
ĝe nazwa tworzonej kla-
sy to
Emailer
. Podczas nadawania nazw klasom nale
ĝy stosowaÊ tÚ samÈ konwencjÚ nazw, któ-
ra jest u
ĝywana w stosunku do zmiennych, na przykïad nazwy nie rozpoczynamy od cyfry, itd.
Nast
Úpnie wiersze kodu odpowiadajÈ za deklaracjÚ wïaĂciwoĂci klasy. Moĝemy wiÚc wyodrÚb-
ni
Ê cztery —
$sender
,
$recipient
,
$subject
oraz
$body
. Warto zwróci
Ê uwagÚ, ĝe kaĝda z wy-
mienionych w
ïaĂciwoĂci zostaïa zadeklarowana z uĝyciem sïowa kluczowego
private
(prywat-
na). W
ïaĂciwoĂÊ prywatna to taka, która jest dostÚpna jedynie w danej klasie. Trzeba jeszcze
doda
Ê, ĝe wïaĂciwoĂci to po prostu zmienne wewnÈtrz klasy.
Jak czytelnik zapewne pami
Úta, metoda jest po prostu funkcjÈ zdefiniowanÈ wewnÈtrz klasy.
W klasie przedstawionej na powy
ĝszym kodzie znajduje siÚ piÚÊ funkcji —
__construct()
,
addRecipient()
,
setSubject()
,
setBody()
oraz
sendEmail()
. Warto zwróci
Ê uwagÚ, ĝe ostatnie
cztery metody zosta
ïy zadeklarowane z uĝyciem sïowa kluczowego
public
(publiczne). Ozna-
cza to,
ĝe kaĝdy, kto utworzy egzemplarz tego obiektu, posiada równieĝ dostÚp do jego metod
publicznych.
Metoda
__construct()
jest metod
È specjalnego znaczenia w klasie i jest nazywana konstrukto-
rem
. W trakcie tworzenia nowego obiektu na podstawie klasy metoda konstruktora jest auto-
matycznie wywo
ïywana. Dlatego teĝ, jeĂli podczas tworzenia obiektu trzeba na nim wykonaÊ
Rozdzia
á 2. • RozpoczĊcie pracy z OOP
31
okre
Ălone zadania, to najlepszym rozwiÈzaniem jest zdefiniowanie ich w konstruktorze. Przykïa-
dowo, metoda konstruktora klasy
Emailer
powoduje zdefiniowanie pustej tablicy
$recipients
oraz danych nadawcy.
Dost
Úp do wïaĂciwoĂci i metod z wewnÈtrz klasy
Czytelnik zapewne zastanawia si
Ú, w jaki sposób funkcje mogÈ uzyskaÊ dostÚp do wïaĂciwoĂci
klasy z poziomu danej klasy? Do tego celu s
ïuĝy nastÚpujÈca konstrukcja kodu:
public function setBody($body)
{
$this->body = $body;
}
W klasie znajduje si
Ú wïaĂciwoĂÊ prywatna o nazwie
$body
. Je
ĝeli zachodzi potrzeba uzyska-
nia dost
Úpu do niej z wewnÈtrz funkcji, wtedy naleĝy uĝyÊ sïowa kluczowego
$this
. Wymie-
nione s
ïowo kluczowe
$this
oznacza odniesienie do bie
ĝÈcego egzemplarza obiektu. Dlatego
te
ĝ, aby uzyskaÊ dostÚp do wïaĂciwoĂci
body
, trzeba zastosowa
Ê polecenie
$this->body
. Warto
zwróci
Ê uwagÚ, ĝe w celu uzyskania dostÚpu do wïaĂciwoĂci (na przykïad zmiennych) klasy
trzeba u
ĝyÊ operatora „
->
”, a nast
Úpnie nazwy egzemplarza.
Podobnie jak w przypadku w
ïaĂciwoĂci, takĝe dostÚp do metod klasy z poziomu innej metody
klasy odbywa si
Ú z pomocÈ przedstawionej powyĝej konstrukcji. Przykïadowo, wywoïanie me-
tody
setSubject
nast
Úpuje w przedstawiony sposób:
$this->setSubject()
.
Warto zwróci
Ê uwagÚ, ĝe sïowo kluczowe $this jest poprawne tylko w zasiÚgu metody, która nie zo-
sta
ïa zadeklarowana jako static (statyczna). Sïowa kluczowego $this nie moĝna uĝyÊ z zewnÈtrz kla-
sy. Wi
Úcej informacji na temat modyfikatorów static, private i public zostanie przedstawionych
w podrozdziale Modyfikatory znajduj
Ècym siÚ w dalszej czÚĂci rozdziaïu.
U
ĝywanie obiektu
Nadesz
ïa pora na uĝycie nowo utworzonego obiektu
Emailer
w kodzie PHP. W tym miejscu
trzeba doda
Ê, ĝe przed uĝyciem obiektu naleĝy wykonaÊ pewne przygotowania. Przede wszyst-
kim, zanim obiekt b
Údzie mógï zostaÊ uĝyty, wczeĂniej musi byÊ utworzony jego egzemplarz.
Po utworzeniu egzemplarza obiektu programista zyskuje dost
Úp do jego wszystkich publicz-
nych w
ïaĂciwoĂci i metod poprzez uĝycie operatora „
->
” po nazwie obiektu. W poni
ĝszym
fragmencie kodu przedstawiono przyk
ïadowe uĝycie obiektu:
<?
$emailerobject = new Emailer("hasin@pageflakes.com");
$emailerobject->addRecipients("hasin@somewherein.net");
Programowanie obiektowe w PHP 5
32
$emailerobject->setSubject("To tylko test");
$emailerobject->setBody("CzeĂÊ Hasin,Jak siÚ miewasz?");
$emailerobject->sendEmail();
?>
Na powy
ĝszym fragmencie kodu pierwszym krokiem jest utworzenie egzemplarza klasy
Emailer
i przypisania go zmiennej o nazwie
$emailerobject
. Warto w tym miejscu zapami
ÚtaÊ bardzo
wa
ĝnÈ reguïÚ: podczas tworzenia nowego obiektu
Emailer
nale
ĝy podaÊ adres nadawcy. Caïy
wiersz jest wi
Úc nastÚpujÈcy:
$emailerobject = new Emailer("hasin@pageflakes.com");
Wynika to z faktu,
ĝe metoda konstruktora jest zdefiniowana w postaci
__construct($sender)
.
Jak wspomniano wcze
Ăniej, podczas tworzenia egzemplarza obiektu nastÚpuje automatyczne
wywo
ïanie konstruktora. Dlatego teĝ w trakcie ustanawiania klasy
Emailer
trzeba poda
Ê pra-
wid
ïowe argumenty, zgodnie z definicjÈ zawartÈ w metodzie konstruktora. Przykïadowo, wy-
konanie poni
ĝszego kodu spowoduje wygenerowanie komunikatu ostrzeĝenia:
<?
$emailer = new emailer();
?>
Po wykonaniu powy
ĝszego kodu na ekranie zostanie wyĂwietlony komunikat ostrzeĝenia:
Warning: Missing argument 1 for emailer::__construct(),
called in C:\OOP_PHP5\Kody\rozdzial1\class.emailer.php on line 42
and defined in <b>C:\OOP_PHP5\Kody\rozdzial1\class.emailer.php</b>
on line <b>9</b><br />
Teraz ró
ĝnica powinna byÊ doskonale widoczna. Jeĝeli klasa nie posiada metody konstruktora
b
Èdě konstruktor nie zawiera argumentów, wtedy egzemplarz obiektu moĝna utworzyÊ za po-
moc
È powyĝszego kodu.
Modyfikatory dost
Úpu
W omówionej wcze
Ăniej klasie zastosowano kilka sïów kluczowych, miÚdzy innymi
private
i
public
. Powstaje wi
Úc pytanie, co oznaczajÈ te sïowa kluczowe i dlaczego ich zastosowanie
w klasie jest konieczne? Ogólnie rzecz bior
Èc, wymienione sïowa kluczowe sÈ nazywane mody-
fikatorami dost
Úpu i zostaïy wprowadzone w PHP 5. Modyfikatory dostÚpu nie wystÚpowaïy
w PHP 4. Te s
ïowa kluczowe pomagajÈ programiĂcie w definiowaniu ograniczeñ w dostÚpno-
Ăci do zmiennych i wïaĂciwoĂci dla uĝytkowników danej klasy. Przekonajmy siÚ, w jaki sposób
mo
ĝna wykorzystaÊ dostÚpne modyfikatory dostÚpu.
Private
. W
ïaĂciwoĂci lub metody zadeklarowane z uĝyciem sïowa kluczowego
private
(pry-
watne) nie mog
È byÊ wywoïane z zewnÈtrz klasy. JednoczeĂnie dowolne metody wewnÈtrz tej
Rozdzia
á 2. • RozpoczĊcie pracy z OOP
33
samej klasy mog
È bez problemu uzyskaÊ dostÚp do elementów prywatnych. W omawianej kla-
sie
Emailer
wszystkie w
ïaĂciwoĂci zostaïy zdefiniowane jako prywatne, dlatego teĝ wykonanie
poni
ĝszego kodu spowoduje wygenerowanie komunikatu bïÚdu:
<?
include_once("class.emailer.php");
$emobject = new Emailer("hasin@somewherein.net");
$emobject->subject = "Witaj Ăwiecie";
?>
Po wykonaniu powy
ĝszego kodu zostanie wygenerowany bïÈd krytyczny:
<b>Fatal error</b>: Cannot access private property emailer::$subject
in <b>C:\OOP_PHP5\Kody\rozdzial1\class.emailer.php</b> on line
<b>43</><br />
Oznacza to,
ĝe z zewnÈtrz klasy nie moĝna uzyskaÊ dostÚpu do jakiejkolwiek prywatnej wïaĂci-
wo
Ăci bÈdě metody.
Public
. Ka
ĝda wïaĂciwoĂÊ lub metoda, która nie zostaïa wyraěnie zdefiniowana z uĝyciem sïów
kluczowych
private
(prywatna) b
Èdě
protected
(chroniona), jest metod
È publicznÈ (
public
).
Dost
Úp do metod publicznych jest moĝliwy równieĝ z zewnÈtrz klasy.
Protected
. To jest kolejny modyfikator dost
Úpu, który ma znaczenie specjalne w programowa-
niu zorientowanym obiektowo. Je
ĝeli jakakolwiek wïaĂciwoĂÊ lub metoda zostanie zdefiniowa-
na z u
ĝyciem sïowa kluczowego
protected
, to dost
Úp do niej moĝna uzyskaÊ tylko z poziomu
podklasy. Wi
Úcej informacji dotyczÈcych podklas zostanie przedstawionych w dalszej czÚĂci
rozdzia
ïu. Aby zademonstrowaÊ, w jaki sposób dziaïa chroniona metoda lub wïaĂciwoĂÊ, po-
s
ïuĝymy siÚ kolejnym przykïadem.
Rozpoczynamy od otwarcia pliku class.emailer.php (czyli klasy
Emailer
) i zmieniamy deklara-
cj
Ú zmiennej
$sender
. Po zmianie definicja zmiennej powinna by
Ê nastÚpujÈca:
protected $sender
Nast
Úpnie tworzymy kolejny plik o nazwie class.extendedemailer.php, w którym powinien znaj-
dowa
Ê siÚ poniĝszy fragment kodu:
<?
class ExtendedEmailer extends emailer
{
function __construct(){}
public function setSender($sender)
{
$this->sender = $sender;
}
}
?>
Programowanie obiektowe w PHP 5
34
Kolejny krok to u
ĝycie w nastÚpujÈcy sposób nowo utworzonego obiektu:
<?
include_once("class.emailer.php");
include_once("class.extendedemailer.php");
$xemailer = new ExtendedEmailer();
$xemailer->setSender("hasin@pageflakes.com");
$xemailer->addRecipients("hasin@somewherein.net");
$xemailer->setSubject("To tylko test ");
$xemailer->setBody("CzeĂÊ Hasin,Jak siÚ miewasz?");
$xemailer->sendEmail();
?>
Po dok
ïadnym przyjrzeniu siÚ kodowi klasy
ExtendedEmailer
czytelnik zauwa
ĝy, ĝe nastÚpuje
próba uzyskania dost
Úpu do wïaĂciwoĂci
$sender
jej klasy nadrz
Údnej (którÈ w rzeczywistoĂci
jest klasa
Emailer
). Dost
Úp do wymienionej wïaĂciwoĂci jest moĝliwy, poniewaĝ zostaïa zade-
klarowana jako chroniona. Dodatkow
È zaletÈ jest fakt, ĝe wïaĂciwoĂÊ
$sender
nadal pozostaje
bezpo
Ărednio niedostÚpna poza zasiÚgiem obu wymienionych klas. Oznacza to, ĝe próba wy-
konania poni
ĝszego fragmentu kodu spowoduje wygenerowanie bïÚdu krytycznego:
<?
include_once("class.emailer.php");
include_once("class.extendedemailer.php");
$xemailer = new ExtendedEmailer();
$xemailer->sender = "hasin@pageflakes.com";
?>
Po wykonaniu powy
ĝszego kodu zostanie wygenerowany bïÈd krytyczny:
<b>Fatal error</b>: Cannot access protected property
extendedEmailer::$sender in <b>C:\OOP_PHP5\Kody\rozdzial1\test.php
</b> on line <b>5</b><br />
Konstruktory i destruktory
We wcze
Ăniejszej czÚĂci rozdziaïu wspomniano o metodzie konstruktora. Wymieniony kon-
struktor to metoda specjalna, która jest automatycznie wykonywana podczas tworzenia egzem-
plarza klasy. W j
Úzyku PHP 5 istniejÈ dwa sposoby napisana metody konstruktora wewnÈtrz
klasy. Pierwszy z nich to po prostu zdefiniowanie w klasie metody o nazwie
__construct()
.
Natomiast drugim sposobem jest utworzenie metody o nazwie identycznej jak nazwa klasy.
Przyk
ïadowo, jeĂli klasa nosi nazwÚ
Emailer
, to nazw
È metody konstruktora bÚdzie
Emailer()
.
Przyjrzyjmy si
Ú poniĝszej klasie, której zadaniem jest obliczanie silni dowolnej liczby:
<?
// class.factorial.php
class factorial
Rozdzia
á 2. • RozpoczĊcie pracy z OOP
35
{
private $result = 1;// Inicjalizacj
Ċ moĪna przeprowadziü bezpoĞrednio z zewnątrz.
private $number;
function __construct($number)
{
$this->number = $number;
for($i=2; $i<=$number; $i++)
{
$this->result *= $i;
}
}
public function showResult()
{
echo "Silnia liczby {$this->number} wynosi {$this->result}. ";
}
}
?>
Na powy
ĝszym fragmencie kodu do zdefiniowania konstruktora wykorzystano metodÚ
__con-
struct()
. Dzia
ïanie kodu pozostanie bez zmian, jeĂli nazwa metody
__construct()
zostanie
zmieniona na
factorial()
.
W tym miejscu mo
ĝe zrodziÊ siÚ pytanie, czy w klasie dopuszczalne jest uĝycie konstruktorów
zdefiniowanych za pomoc
È obu omówionych stylów? Oznacza to istnienie w klasie funkcji
o nazwie
__construct()
oraz funkcji o nazwie identycznej z nazw
È klasy. Który z konstruktorów
zostanie u
ĝyty w takim przypadku? A moĝe zostanÈ wykonane obie te funkcje? To sÈ bardzo
trafne i ciekawe pytania. Warto zapami
ÚtaÊ, ĝe w rzeczywistoĂci jednak nie ma moĝliwoĂci
wykonania obu funkcji. Je
ĝeli w klasie bÚdÈ zdefiniowane dwie metody konstruktora, to PHP
5 daje pierwsze
ñstwo funkcji
__construct()
, natomiast druga metoda konstruktora b
Údzie zi-
gnorowana. Spójrzmy na poni
ĝszy fragment kodu:
<?
// class.factorial.php
class Factorial
{
private $result = 1;
private $number;
function __construct($number)
{
$this->number = $number;
for($i=2; $i<=$number; $i++)
{
$this->result*=$i;
}
echo "Wykonano metodÚ __construct(). ";
}
function factorial($number)
{
$this->number = $number;
Programowanie obiektowe w PHP 5
36
for($i=2; $i<=$number; $i++)
{
$this->result*=$i;
}
echo " Wykonano metodÚ factorial(). ";
}
public function showResult()
{
echo " Silnia liczby {$this->number} wynosi {$this->result}. ";
}
}
?>
Je
ĝeli powyĝsza klasa zostanie uĝyta w nastÚpujÈcy sposób:
<?
include_once("class.factorial.php");
$fact = new Factorial(5);
$fact->showResult();
?>
to na ekranie zostanie wy
Ăwietlony poniĝszy komunikat:
Wykonano metodÚ __construct().Silnia liczby 5 wynosi 120.
Podobnie do metody konstruktora w klasie wyst
Úpuje równieĝ metoda destruktora, która jest
wykonywana w trakcie niszczenia obiektu. Programista mo
ĝe wyraěnie utworzyÊ destruktora
poprzez zdefiniowanie metody o nazwie
__destruct()
. Wymieniona metoda zostanie automa-
tycznie wywo
ïana przez PHP na samym koñcu wykonywania danego skryptu. Aby sprawdziÊ,
w jaki sposób dzia
ïa destruktor, moĝna w omówionej powyĝej klasie dodaÊ metodÚ destruktora:
function __destruct()
{
echo "Obiekt zostaï zniszczony.";
}
Nast
Úpnie, po ponownym wykonaniu skryptu obliczajÈcego silniÚ, na ekranie zostanie wyĂwie-
tlony nast
ÚpujÈcy komunikat:
Wykonano metodÚ __construct(). Silnia liczby 5 wynosi 120. Obiekt zostaï
zniszczony.
Sta
ïe klasy
Czytelnik prawdopodobnie wie,
ĝe w skryptach PHP definiowanie staïej odbywa siÚ za po-
moc
È sïowa kluczowego
define
(definiowanie nazwy sta
ïej oraz jej wartoĂci). Jednak w celu
zdefiniowania sta
ïej w klasie uĝywa siÚ sïowa kluczowego
const
. W rzeczywisto
Ăci te staïe
Rozdzia
á 2. • RozpoczĊcie pracy z OOP
37
funkcjonuj
È na zasadzie zmiennych statycznych, a jedyna róĝnica miÚdzy nimi polega na tym,
ĝe sÈ tylko do odczytu. Przykïad tworzenia i uĝywania staïych w klasie zostaï przedstawiony
na poni
ĝszym fragmencie kodu:
<?
class WordCounter
{
const ASC=1; // Przed sta
áą nie trzeba stosowaü znaku dolara ($).
const DESC=2;
private $words;
function __construct($filename)
{
$file_content = file_get_contents($filename);
$this->words =
(array_count_values(str_word_count(strtolower
($file_content),1)));
}
public function count($order)
{
if ($order==self::ASC)
asort($this->words);
else if($order==self::DESC)
arsort($this->words);
foreach ($this->words as $key=>$val)
echo $key ." = ". $val."<br/>";
}
}
?>
Powy
ĝsza klasa
WordCounter
powoduje zliczanie cz
ÚstotliwoĂci wystÚpowania sïów w podanym
pliku. W kodzie zdefiniowano dwie sta
ïe
ASC
i
DESC
o warto
Ăciach odpowiednio
1
i
2
. Aby we-
wn
Ètrz klasy uzyskaÊ dostÚp do staïej, naleĝy odnieĂÊ siÚ do niej za pomocÈ sïowa kluczowego
self
. Warto zwróci
Ê uwagÚ, ĝe dostÚp do staïej nastÚpuje za pomocÈ operatora
::
, a nie ope-
ratora
->
. Wynika to z faktu,
ĝe staïe dziaïajÈ na zasadzie podobnej do elementów statycznych.
W celu u
ĝycia powyĝszej klasy trzeba wykorzystaÊ przedstawiony poniĝej fragment kodu. Za-
prezentowano w nim równie
ĝ sposób dostÚpu do staïej:
<?
include_once("class.wordcounter.php");
$wc = new WordCounter("words.txt");
$wc->count(WordCounter::DESC);
?>
Warto zwróci
Ê uwagÚ, ĝe dostÚp do staïej klasy nastÚpuje z zewnÈtrz klasy za pomocÈ operato-
ra
::
umieszczonego tu
ĝ za nazwÈ klasy, a nie za nazwÈ egzemplarza klasy. Kolejnym krokiem,
który trzeba wykona
Ê w celu przetestowania omówionego skryptu, jest utworzenie pliku tek-
stowego words.txt. Wymieniony plik musi znajdowa
Ê siÚ w tym samym katalogu, w którym
umieszczono skrypt:
Programowanie obiektowe w PHP 5
38
Plik: words.txt
Wordpress jest silnikiem bloga dostepnym na licencji open source. Czytelnikom
nieznajacym blogów wyjasniamy, ze blog pozwala uzytkownikowi na prowadzenie
dziennika w Internecie. Wordpress jest zupelnie bezplatny i zostal wydany
na licencji GPL.
Po wykonaniu skryptu z podanym powy
ĝej plikiem zostanÈ wyĂwietlone nastÚpujÈce dane
wyj
Ăciowe:
na = 3
licencji = 2
wordpress = 2
blog = 2
w = 2
jest = 2
internecie = 1
dziennika = 1
prowadzenie = 1
zupelnie = 1
bezplatny = 1
gpl = 1
wydany = 1
zostal = 1
i = 1
uzytkownikowi = 1
pozwala = 1
source = 1
open = 1
bloga = 1
czytelnikom = 1
nieznajacym = 1
ze = 1
wyjasniamy = 1
silnikiem = 1
dostepnym = 1
U
ĝyteczny skrypt, nieprawdaĝ?
Rozszerzanie klasy (dziedziczenie)
Jedn
È z najistotniejszych funkcji programowania zorientowanego obiektowo jest moĝliwoĂÊ
rozszerzenia klasy oraz utworzenie zupe
ïnie nowego obiektu. Ten nowo utworzony obiekt
b
Údzie posiadaï wszystkie funkcje obiektu nadrzÚdnego, które bÚdÈ mogïy zostaÊ rozbudowa-
ne b
Èdě nadpisane. Nowy obiekt moĝe takĝe zawieraÊ zupeïnie nowe funkcje. Na poniĝszym
fragmencie kodu rozszerzono przedstawion
È wczeĂniej klasÚ
Emailer
oraz nadpisano funkcj
Ú
sendEmail
, która obecnie ma mo
ĝliwoĂÊ wysyïania wiadomoĂci e-mail w formacie HTML.
Rozdzia
á 2. • RozpoczĊcie pracy z OOP
39
<?
class HtmlEmailer extends emailer
{
public function sendHTMLEmail()
{
foreach ($this->recipients as $recipient)
{
$headers = 'MIME-Version: 1.0' . "\r\n";
$headers .= 'Content-type: text/html; charset=iso-8859-2' .
"\r\n";
$headers .= 'From: {$this->sender}' . "\r\n";
$result = mail($recipient, $this->subject, $this->body,
$headers);
if ($result) echo "WiadomoĂÊ w formacie HTML zostaïa wysïana do
{$recipient}<br/>";
}
}
}
?>
Poniewa
ĝ nowa klasa rozszerza klasÚ
Emailer
oraz wprowadza now
È funkcjÚ o nazwie
send-
HTMLEmail()
, to programista nadal posiada dost
Úp do wszystkich metod obecnych w klasie nad-
rz
Údnej. Oznacza to, ĝe przedstawiony poniĝej fragment kodu jest jak najbardziej prawidïowy:
<?
include_once("class.htmlemailer.php");
$hm = new HtmlEmailer();
// ...miejsce na inne zadania...
$hm->sendEmail();
$hm->sendHTMLEmail();
?>
Je
ĝeli zachodzi potrzeba uzyskania dostÚpu do dowolnej metody klasy nadrzÚdnej (inaczej
nazywanej superklas
È), to moĝna uĝyÊ sïowa kluczowego
parent
. Przyk
ïadowo, jeĝeli progra-
mista chce uzyska
Ê dostÚp do metody o nazwie
sayHello
, to nale
ĝy wydaÊ polecenie
parent::
sayHello();
.
Warto zwróci
Ê uwagÚ, ĝe w klasie
HtmlEmailer
nie zosta
ïa zdefiniowana funkcja o nazwie
send-
Email()
. Natomiast wymieniona metoda dzia
ïa z klasy nadrzÚdnej, czyli
Emailer
.
W omówionym powy
ĝej przykïadzie klasa HtmlEmailer jest podklasÈ klasy Emailer, natomiast klasa
Emailer to superklasa dla klasy HtmlEmailer. Trzeba zapamiÚtaÊ, ĝe jeĂli podklasa nie posiada kon-
struktora, to zostanie u
ĝyta metoda konstruktora klasy nadrzÚdnej. W trakcie pisania niniejszej ksiÈĝki
wielokrotne dziedziczenie na poziomie klasy nie by
ïo obsïugiwane. Oznacza to, ĝe nie moĝna jednocze-
Ănie dziedziczyÊ z wiÚcej niĝ tylko jednej klasy. Jednak wielokrotne dziedziczenie jest obsïugiwane w in-
terfejsach. Dlatego te
ĝ interfejs moĝe rozszerzaÊ dowolnÈ liczbÚ interfejsów.
Programowanie obiektowe w PHP 5
40
Nadpisywanie metod
W rozszerzanym obiekcie mo
ĝna nadpisaÊ dowolnÈ metodÚ (zdefiniowanÈ jako chronionÈ lub
publiczn
È) i dowolnie zmieniaÊ sposób jej dziaïania. W jaki wiÚc sposób moĝna nadpisaÊ do-
woln
È metodÚ? Wystarczy po prostu utworzyÊ funkcjÚ o takiej samej nazwie jak ta, która ma
zosta
Ê nadpisana. Przykïadowo, po utworzeniu w klasie
HtmlEmailer
funkcji o nazwie
sendEmail
spowoduje ona nadpisanie metody
sendEmail()
zdefiniowanej w klasie nadrz
Údnej
Emailer
.
Je
ĝeli w podklasie zostanie zdefiniowana zmienna, która istnieje takĝe w superklasie, to pod-
czas dost
Úpu do zmiennej zostanie uĝyta ta zdefiniowana w podklasie.
Uniemo
ĝliwianie nadpisywania
Je
ĝeli metoda zostanie zdefiniowana z uĝyciem sïowa kluczowego
final
, to nie b
Údzie mogïa
zosta
Ê nadpisana w ĝadnej podklasie. Dlatego teĝ, jeĝeli programista nie chce, aby dana meto-
da by
ïa nadpisywana, to wystarczy zdefiniowaÊ jÈ jako
final
. Poni
ĝej pokazano definicjÚ me-
tody z u
ĝyciem sïowa kluczowego
final
:
<?
class SuperClass
{
public final function someMethod()
{
// ...miejsce na dowolny kod...
}
}
class SubClass extends SuperClass
{
public function someMethod()
{
// ...miejsce na dowolny kod, ale i tak nie zostanie on wykonany...
}
}
?>
Je
ĝeli powyĝszy kod zostanie wykonany, to spowoduje wygenerowanie bïÚdu krytycznego, po-
niewa
ĝ klasa
SubClass
próbuje nadpisa
Ê metodÚ z klasy nadrzÚdnej
SuperClass
, która zosta
ïa
zdefiniowana z u
ĝyciem sïowa kluczowego
final
.
Uniemo
ĝliwianie rozszerzania
Podobnie jak w przypadku metody zdefiniowanej jako
final
, tak
ĝe klasÚ moĝna zdefiniowaÊ
z u
ĝyciem sïowa kluczowego
final
, które uniemo
ĝliwi jej rozszerzanie. Dlatego teĝ po zde-
finiowaniu klasy w sposób przedstawiony na poni
ĝszym listingu nie bÚdzie moĝna jej dalej
rozszerza
Ê:
Rozdzia
á 2. • RozpoczĊcie pracy z OOP
41
<?
final class aclass
{
}
class bclass extends aclass
{
}
?>
Po wykonaniu powy
ĝszego kodu zostanie wygenerowany nastÚpujÈcy bïÈd krytyczny:
<b>Fatal error</b>: Class bclass may not inherit from final class
(aclass) in <b>C:\OOP_PHP5\Kody\rozdzial1\class.aclass.php</b> on
line <b>8</b><br />
Polimorfizm
Jak ju
ĝ wspomniano we wczeĂniejszej czÚĂci ksiÈĝki, polimorfizm jest procesem tworzenia
kilku obiektów z okre
Ălonych klas bazowych. Przykïadowo, warto spojrzeÊ na poniĝszy przy-
k
ïad, w którym wykorzystano wszystkie trzy klasy omówione dotychczas w rozdziale
Emailer
,
ExtendedEmailer
oraz
HtmlEmailer
:
<?
include("class.emailer.php");
include("class.extendedemailer.php");
include("class.htmlemailer.php");
$emailer = new Emailer("hasin@somewherein.net");
$extendedemailer = new ExtendedEmailer();
$htmlemailer = new HtmlEmailer("hasin@somewherein.net");
if ($extendedemailer instanceof emailer)
echo "Klasa Extended Emailer wywodzi siÚ z klasy Emailer.<br/>";
if ($htmlemailer instanceof emailer)
echo "Klasa HTML Emailer równieĝ wywodzi siÚ z klasy Emailer.<br/>";
if ($emailer instanceof htmlEmailer)
echo "Klasa Emailer wywodzi siÚ z klasy HTMLEmailer.<br/>";
if ($htmlemailer instanceof extendedEmailer)
echo "Klasa HTML Emailer wywodzi siÚ z klasy Emailer.<br/>";
?>
Po wykonaniu powy
ĝszego fragmentu kodu zostanÈ wyĂwietlone nastÚpujÈce dane wyjĂciowe:
Klasa Extended Emailer wywodzi siÚ z klasy Emailer.
Klasa HTML Emailer równieĝ wywodzi siÚ z klasy Emailer.
To jest przyk
ïad polimorfizmu.
Programowanie obiektowe w PHP 5
42
Dzi
Úki zastosowaniu operatora instanceof zawsze istnieje moĝliwoĂÊ sprawdzenia, czy klasa wywodzi
si
Ú z innej klasy.
Interfejs
Interfejs jest pust
È klasÈ, która zawiera jedynie deklaracje metod. Dlatego teĝ kaĝda klasa im-
plementuj
Èca dany interfejs musi zawieraÊ deklaracje zawartych w nim funkcji. Interfejs jest
wi
Úc jedynie zbiorem ĂciĂle okreĂlonych reguï, które pomagajÈ w rozszerzaniu dowolnej klasy
oraz
Ăcisïej implementacji wszystkich metod zadeklarowanych w interfejsie. Klasa moĝe sto-
sowa
Ê dowolny interfejs, uĝywajÈc sïowa kluczowego
implements
. Warto zwróci
Ê uwagÚ, ĝe
w interfejsie mo
ĝna jedynie zadeklarowaÊ metody, ale nie moĝna umieĂciÊ w nim definicji
tych
ĝe metod. Oznacza to, ĝe w interfejsie czÚĂci gïówne wszystkich metod pozostajÈ puste.
Powstaje wi
Úc pytanie, do czego moĝe sïuĝyÊ interfejs? Jednym z powodów jego stosowania
jest mo
ĝliwoĂÊ implementacji ĂciĂle okreĂlonych reguï podczas definicji klasy. Przykïadowo,
programista wie,
ĝe musi utworzyÊ klasy pewnego sterownika dla programu, które bÚdÈ za-
wiera
ïy operacje zwiÈzane z bazÈ danych. Dla bazy danych MySQL bÚdzie to jedna klasa, dla
PostgreSQL b
Údzie to kolejna klasa, dla SQLite kolejna, itd. W takim przypadku zespóï pro-
gramistów mo
ĝe liczyÊ trzy osoby, z których kaĝda bÚdzie oddzielnie tworzyïa wskazanÈ klasÚ.
Mo
ĝna teraz zadaÊ sobie pytanie, jaki byïby wynik pracy tych programistów, gdyby kaĝdy
z nich implementowa
ï w klasie wïasny styl? Inni programiĂci, którzy chcieliby wykorzystaÊ te
klasy sterowników, musieliby pozna
Ê definicje uĝytych metod, a nastÚpnie stosowaÊ taki sam
styl, aby móc je wykorzysta
Ê we wïasnym kodzie. Takie rozwiÈzanie staje siÚ wyjÈtkowo trud-
ne w obs
ïudze. Dlatego teĝ moĝna po prostu ustaliÊ, ĝe kaĝda klasa sterownika musi posiadaÊ
dwie metody o nazwach
connect()
i
execute()
. W takim przypadku programi
Ăci nie muszÈ
przejmowa
Ê siÚ wewnÚtrznÈ strukturÈ sterownika, poniewaĝ doskonale wiedzÈ, ĝe wszystkie
klasy posiadaj
È takie same definicje metod. Interfejs stanowi wiÚc duĝe uïatwienie podczas
pracy nad tego rodzaju projektem. Poni
ĝej przedstawiono kod przykïadowego interfejsu:
<?
// interface.dbdriver.php
interface DBDriver
{
public function connect();
public function execute($sql);
}
?>
Czy czytelnik zwróci
ï uwagÚ na fakt, ĝe w interfejsie definicje funkcji sÈ puste? Kolejny krok to
utworzenie klasy
MySQLDriver
, która b
Údzie implementowaïa przedstawiony powyĝej interfejs:
Rozdzia
á 2. • RozpoczĊcie pracy z OOP
43
<?
// class.mysqldriver.php
include("interface.dbdriver.php");
class MySQLDriver implements DBDriver
{
}
?>
Je
ĝeli powyĝszy kod zostanie uruchomiony, to spowoduje wygenerowanie poniĝszego ko-
munikatu b
ïÚdu. Wynika to z faktu, ĝe w klasie
MySQLDriver
nie zosta
ïy zdefiniowane funkcje
connect()
i
execute()
, które s
È zadeklarowane w interfejsie. Warto wiÚc uruchomiÊ kod i od-
czyta
Ê komunikat bïÚdu:
<b>Fatal error</b>: Class MySQLDriver contains 2 abstract methods
and must therefore be declared abstract or implement the remaining
methods (DBDriver::connect, DBDriver::execute) in
<b>C:\OOP_PHP5\Kody\rozdzial1\class.mysqldriver.php</b> on line <b>5</b><br />
Kolejny krok to dodanie do klasy
MySQLDriver
dwóch metod. Po wprowadzeniu zmian kod
przedstawia si
Ú nastÚpujÈco:
<?
include("interface.dbdriver.php");
class MySQLDriver implements DBDriver
{
public function connect()
{
// Nawi
ązanie poáączenia z bazą danych.
}
public function execute()
{
// Wykonanie zapytania i wy
Ğwietlenie jego wyników.
}
}
?>
Po uruchomieniu powy
ĝszego kodu na ekranie ponownie zostanie wyĂwietlony komunikat
b
ïÚdu:
<b>Fatal error</b>: Declaration of MySQLDriver::execute() must be
compatible with that of DBDriver::execute() in
<b>C:\OOP_PHP5\Kody\rozdzial1\class.mysqldriver.php</b> on line <b>3</b><br />
Ten komunikat informuje u
ĝytkownika, ĝe metoda
execute()
nie jest zgodna ze struktur
È me-
tody
execute()
, która zosta
ïa zadeklarowana w interfejsie. Po dokïadnym przyjrzeniu siÚ inter-
fejsowi czytelnik zauwa
ĝy, ĝe metoda
execute()
powinna posiada
Ê jeden argument. Oznacza
to,
ĝe w trakcie implementacji interfejsu w tworzonych klasach kaĝda struktura metody musi
by
Ê dokïadnie taka sama jak zadeklarowana w interfejsie. Po przepisaniu klasy
MySQLDriver
jej
kod przedstawia si
Ú nastÚpujÈco:
Programowanie obiektowe w PHP 5
44
<?
include("interface.dbdriver.php");
class MySQLDriver implements DBDriver
{
public function connect()
{
// Nawi
ązanie poáączenia z bazą danych.
}
public function execute($query)
{
// Wykonanie zapytania i wy
Ğwietlenie jego wyników.
}
}
?>
Klasa abstrakcyjna
Klasa abstrakcyjna jest niemal tak
È samÈ konstrukcjÈ jak interfejs, za wyjÈtkiem faktu, ĝe de-
klarowane w niej metody mog
È posiadaÊ definicje. Ponadto klasa abstrakcyjna musi byÊ „roz-
szerzana”, a nie „implementowana”. Dlatego te
ĝ, jeĝeli rozszerzane klasy posiadajÈ metody
o takich samych funkcjach, to te funkcje mo
ĝna zdefiniowaÊ w klasie abstrakcyjnej. Poniĝej
przedstawiono przyk
ïad klasy abstrakcyjnej:
<?
// abstract.reportgenerator.php
abstract class ReportGenerator
{
public function generateReport($resultArray)
{
// Miejsce na kod przetwarzaj
ący wielowymiarową tablicĊ wynikową oraz
// generuj
ący raport w postaci kodu HTML.
}
}
?>
W powy
ĝszej klasie abstrakcyjnej znajduje siÚ metoda o nazwie
generateRaport
, która jako ar-
gument pobiera wielowymiarow
È tablicÚ, a nastÚpnie na jej podstawie generuje raport w po-
staci kodu HTML. Powstaje zatem pytanie, dlaczego ta metoda zosta
ïa umieszczona w klasie
abstrakcyjnej? Odpowied
ě jest prosta — poniewaĝ generowanie raportu bÚdzie wspólnÈ funk-
cj
È wszystkich sterowników baz danych. Sama funkcja nie wpïywa równieĝ na kod sterowni-
ka, poniewa
ĝ jako argument pobiera tablicÚ i nie ma nic wspólnego z bazÈ danych. Dlatego
te
ĝ w przedstawionym poniĝej kodzie klasy
MySQLDriver
zastosowano klas
Ú abstrakcyjnÈ. Warto
zwróci
Ê uwagÚ, ĝe caïy kod odpowiedzialny za generowanie raportu zostaï juĝ wczeĂniej na-
pisany. Nie trzeba wi
Úc umieszczaÊ go ponownie w klasie sterownika, jak miaïoby to miejsce
w przypadku interfejsu.
Rozdzia
á 2. • RozpoczĊcie pracy z OOP
45
<?
include("interface.dbdriver.php");
include("abstract.reportgenerator.php");
class MySQLDriver extends ReportGenerator implements DBDriver
{
public function connect()
{
// Nawi
ązanie poáączenia z bazą danych.
}
public function execute($query)
{
// Wykonanie zapytania i wy
Ğwietlenie jego wyników.
}
// Nie trzeba w tym miejscu ponownie definiowa
ü lub umieszczaü metody generateReport
// poniewa
Ī ta klasa bezpoĞrednio rozszerza klasĊ abstrakcyjną.
}
?>
Warto zwróci
Ê uwagÚ, ĝe jednoczeĂnie moĝna zarówno uĝywaÊ klasy abstrakcyjnej, jak i im-
plementowa
Ê interfejs. Zostaïo to przedstawione na powyĝszym fragmencie kodu.
Klas
Ú abstrakcyjnÈ (abstract) nie moĝna zdefiniowaÊ za pomocÈ sïowa kluczowego final, poniewaĝ
klasa abstrakcyjna musi by
Ê rozszerzana. Natomiast sïowo kluczowe final uniemoĝliwia rozszerzenie
tak zdefiniowanej klasy. Dlatego te
ĝ jednoczesne uĝycie tych dwóch wymienionych sïów kluczowych jest
bezsensowne i j
Úzyk PHP na to nie pozwala.
Oprócz zdefiniowania klasy jako abstrakcyjnej tak
ĝe i metodÚ moĝna zdefiniowaÊ z uĝyciem
s
ïowa kluczowego
abstract
. Zdefiniowanie metody abstrakcyjnej oznacza,
ĝe podklasy muszÈ
nadpisywa
Ê tÚ metodÚ. W deklaracji metody abstrakcyjnej nie powinna znajdowaÊ siÚ jej de-
finicja. Przyk
ïad deklaracji metody abstrakcyjnej zostaï przedstawiony poniĝej:
abstract public function connectDB();
Metody i w
ïaĂciwoĂci statyczne
S
ïowo kluczowe
static
jest istotne w programowaniu zorientowanym obiektowo. Metody i w
ïa-
ĂciwoĂci statyczne peïniÈ bardzo waĝnÈ rolÚ zarówno w projekcie programu, jak i wzorcach
projektowych. Czym wi
Úc sÈ metody i wïaĂciwoĂci statyczne?
Jak wcze
Ăniej przedstawiono, w celu uzyskania dostÚpu do dowolnej metody bÈdě atrybutu
klasy wcze
Ăniej trzeba utworzyÊ jej egzemplarz (na przykïad za pomocÈ sïowa kluczowego
new
, czyli
$object = new emailer()
). W przeciwnym razie nie b
Údzie moĝna uzyskaÊ dostÚpu
do metod i w
ïaĂciwoĂci danej klasy. Istnieje jednak odstÚpstwo od tej reguïy i dotyczy metod
i w
ïaĂciwoĂci statycznych. Do metody lub wïaĂciwoĂci statycznej programista moĝe uzyskaÊ
Programowanie obiektowe w PHP 5
46
dost
Úp bezpoĂrednio bez potrzeby tworzenia egzemplarza danej klasy. Element statyczny jest
wi
Úc podobny do elementu globalnego danej klasy i wszystkich jej egzemplarzy. Ponadto wïa-
ĂciwoĂci statyczne zachowujÈ stan z ostatniego przypisania, co w niektórych sytuacjach jest
bardzo u
ĝyteczne.
Czytelnik mo
ĝe zadaÊ pytanie, dlaczego ktokolwiek chciaïby uĝywaÊ metod statycznych? Cóĝ,
s
È one bardzo podobne do metod pomocniczych. WykonujÈ wiÚc ĂciĂle okreĂlone zadanie lub
zwracaj
È ĂciĂle okreĂlony obiekt. (WïaĂciwoĂci i metody statyczne sÈ intensywnie uĝywane we
wzorcach projektowych, co zostanie przedstawione w dalszej cz
ÚĂci rozdziaïu). Z tego powodu
deklarowanie nowego obiektu za ka
ĝdym razem do wykonania takiego zadania moĝe byÊ uzna-
ne za niepotrzebne zu
ĝywanie zasobów. Spójrzmy wiÚc na przykïad uĝycia metod statycznych.
Wró
Êmy do omawianego wczeĂniej programu, który zajmuje siÚ obsïugÈ trzech baz danych
— MySQL, PostgreSQL i SQLite. Zak
ïadamy, ĝe w danej chwili zachodzi potrzeba uĝywania
tylko jednego sterownika. W tym celu tworzymy klas
Ú
DBManager
, której zadaniem jest utwo-
rzenie egzemplarza dowolnego sterownika oraz jego zwrócenie programi
Ăcie.
<?
// class.dbmanager.php
class DBManager
{
public static function getMySQLDriver()
{
// Utworzenie nowego egzemplarza obiektu sterownika bazy danych MySQL i jego
// zwrócenie.
}
public static function getPostgreSQLDriver()
{
// Utworzenie nowego egzemplarza obiektu sterownika bazy danych PostgreSQL i jego
// zwrócenie.
}
public static function getSQLiteDriver()
{
// Utworzenie nowego egzemplarza obiektu sterownika bazy danych SQLite i jego
// zwrócenie.
}
}
?>
W jaki sposób mo
ĝna uĝyÊ powyĝszÈ klasÚ? DostÚp do dowolnej wïaĂciwoĂci statycznej odby-
wa si
Ú poprzez operator
::
zamiast operatora
->
. Przyk
ïad uĝycia klasy
DBManager
zosta
ï przed-
stawiony poni
ĝej:
<?
// test.dbmanager.php
include_once("class.dbmanager.php");
$dbdriver = DBManager::getMySQLDriver();
// Miejsce na kod przetwarzaj
ący operacje bazy danych za pomocą obiektu $dbdriver.
?>
Rozdzia
á 2. • RozpoczĊcie pracy z OOP
47
Warto zwróci
Ê uwagÚ, ĝe w kodzie nie nastÚpuje tworzenie nowego egzemplarza obiektu
DBManager
, na przyk
ïad za pomocÈ polecenia
$dbmanager = new DBManager()
. Zamiast tego,
u
ĝywajÈc operatora
::
programista uzyskuje bezpo
Ăredni dostÚp do jednej z metod wymienio-
nego obiektu.
Co zyskuje programista, stosuj
Èc tego typu rozwiÈzanie? Ogólnie rzecz biorÈc, skoro po pro-
stu potrzebny jest obiekt sterownika, to nie ma potrzeby tworzenia nowego obiektu
DBManager
i zu
ĝywania przez niego pamiÚci aĝ do chwili zakoñczenia dziaïania skryptu. Metoda statycz-
na zwykle wykonuje swoje zadanie, a nast
Úpnie koñczy dziaïanie.
Trzeba zapami
ÚtaÊ jednÈ bardzo waĝnÈ kwestiÚ. WewnÈtrz metody statycznej nie moĝna uĝy-
wa
Ê pseudoobiektu
$this
. Poniewa
ĝ nie jest tworzony egzemplarz klasy, to sïowo kluczowe
$this
nie istnieje wewn
Ètrz metody statycznej. Zamiast niego naleĝy stosowaÊ sïowo kluczo-
we
self
.
Spójrzmy na poni
ĝszy fragment kodu, w którym zademonstrowano rzeczywisty sposób dziaïa-
nia w
ïaĂciwoĂci statycznej:
<?
// class.statictester.php
class StaticTester
{
private static $id=0;
function __construct()
{
self::$id +=1;
}
public static function checkIdFromStaticMehod()
{
echo "BieĝÈce Id z metody statycznej wynosi ".self::$id."\n";
}
public function checkIdFromNonStaticMethod()
{
echo " BieĝÈce Id z metody niestatycznej wynosi ".self::$id."\n";
}
}
$st1 = new StaticTester();
StaticTester::checkIdFromStaticMehod();
$st2 = new StaticTester();
$st1->checkIdFromNonStaticMethod(); // Zwrot warto
Ğci $id jako 2.
$st1->checkIdFromStaticMehod();
$st2->checkIdFromNonStaticMethod();
$st3 = new StaticTester();
StaticTester::checkIdFromStaticMehod();
?>
Po uruchomieniu powy
ĝszego kodu zostanÈ wyĂwietlone nastÚpujÈce dane wyjĂciowe:
Programowanie obiektowe w PHP 5
48
BieĝÈce Id z metody statycznej wynosi 1
BieĝÈce Id z metody niestatycznej wynosi 2
BieĝÈce Id z metody statycznej wynosi 2
BieĝÈce Id z metody niestatycznej wynosi 2
BieĝÈce Id z metody statycznej wynosi 3
Kiedy tylko zostanie utworzony nowy egzemplarz obiektu, b
Údzie on wpïywaï na pozostaïe
egzemplarze, poniewa
ĝ zmienna zostaïa zdefiniowana jako statyczna. Uĝywanie tej moĝliwoĂci,
czyli specjalnego wzorca projektowego o nazwie „Singleton”, doskonale sprawdza si
Ú w PHP.
Ostrze
ĝenie dotyczÈce uĝywania elementów statycznych
Elementy statyczne powoduj
È, ĝe obiekty zachowujÈ siÚ w sposób podobny do proceduralnego stylu
dzia
ïania. Bez tworzenia egzemplarzy programista moĝe bezpoĂrednio wywoïaÊ dowolnÈ funkcjÚ, po-
dobnie jak w programowaniu proceduralnym. Z tego powodu metody statyczne powinny by
Ê uĝywane
z zachowaniem ostro
ĝnoĂci. Nadmierne korzystanie z metod statycznych jest nieuĝyteczne. O ile nie za-
chodzi taka konieczno
ĂÊ, to naleĝy unikaÊ uĝywania elementów statycznych.
Metody akcesorów
Metody akcesorów to po prostu metody, których zadaniem jest pobieranie i ustalanie warto
Ăci
dowolnej w
ïaĂciwoĂci klasy. Dobrym nawykiem jest uzyskiwanie dostÚpu do wïaĂciwoĂci kla-
sy za pomoc
È metod akcesorów zamiast bezpoĂredniego ustalania lub pobierania ich wartoĂci.
Chocia
ĝ metody akcesorów sÈ takie same jak inne metody, to jednak istniejÈ pewne konwen-
cje ich tworzenia.
Dost
Úpne sÈ dwa rodzaje metod akcesorów. Pierwszy z nich nosi nazwÚ
getter
, a celem tej
metody jest pobranie warto
Ăci dowolnej wïaĂciwoĂci klasy. Drugi rodzaj metody nosi nazwÚ
setter
i s
ïuĝy do ustalania wartoĂci dowolnej wïaĂciwoĂci klasy. Poniĝej zaprezentowano przy-
k
ïadowe metody
getter
i
setter
u
ĝywane do operacji na wïaĂciwoĂciach klasy:
<?
class Student
{
private $name;
private $roll;
public function setName($name)
{
$this->name= $name;
}
public function setRoll($roll)
{
$this->roll =$roll;
}
Rozdzia
á 2. • RozpoczĊcie pracy z OOP
49
public function getName()
{
return $this->name;
}
public function getRoll()
{
return $this->roll;
}
}
?>
W powy
ĝszym fragmencie kodu zastosowano po dwie metody typu
getter
oraz
setter
. To jest
konwencja pisania metod akcesorów. Metoda typu
setter
powinna rozpoczyna
Ê siÚ sïowem
kluczowym
set
, a nast
Úpnie zawieraÊ nazwÚ wïaĂciwoĂci, której pierwsza litera jest duĝa. Po-
dobnie, metoda typu
getter
powinna rozpoczyna
Ê siÚ sïowem kluczowym
get
, a nast
Úpnie
zawiera
Ê nazwÚ zmiennej, w której pierwsza litera jest duĝa. Oznacza to, ĝe jeĂli nazwÈ wïa-
ĂciwoĂci jest
, to metoda typu
getter
powinna by
Ê nazwana
getEmail
, natomiast metoda
typu
setter
powinna mie
Ê nazwÚ
setEmail
. I to tyle!
Czytelnik mo
ĝe w tym miejscu zapytaÊ, dlaczego ktokolwiek mógïby chcieÊ wykonywaÊ do-
datkow
È pracÚ, definiujÈc te metody, skoro zmienne moĝna zdefiniowaÊ jako publiczne, a resztÚ
pozostawi
Ê bez zmian? Czy to nie bÚdzie miaïo takiego samego efektu? Ogólnie rzecz ujmujÈc,
nie. U
ĝywajÈc metod akcesorów, programista otrzymuje dodatkowe korzyĂci. Przede wszyst-
kim zachowuje pe
ïnÈ kontrolÚ nad ustalaniem i pobieraniem wartoĂci dowolnej wïaĂciwoĂci.
„I co z tego?” — móg
ïby zapytaÊ czytelnik. Zaïóĝmy, ĝe zachodzi potrzeba zastosowania filtrów
danych wej
Ăciowych uĝytkownika przed ustawieniem wartoĂci wïaĂciwoĂci. W takim przypad-
ku metoda typu
setter
pozwala na filtrowanie danych wej
Ăciowych przed ich ustawieniem
i u
ĝyciem w programie.
Czy je
Ăli w klasie znajduje siÚ 100 wïaĂciwoĂci, to programista musi napisaÊ po sto metod typu
getter
i
setter
? To bardzo dobre pytanie. J
Úzyk PHP jest na tyle elegancki, ĝe wyrÚcza pro-
gramist
Ú z takiego ĝmudnego zadania. W jaki sposób? Odpowiedě na to pytanie znajduje siÚ
w kolejnym podrozdziale, w którym zostan
È omówione metody magiczne sïuĝÈce do dynamicz-
nego pobierania i ustalania warto
Ăci wïaĂciwoĂci. Uĝywanie tego rodzaju metod powoduje re-
dukcj
Ú o okoïo 90% pracy zwiÈzanej z koniecznoĂciÈ ĝmudnego pisania kodu metod akcesorów.
A
ĝ trudno w to uwierzyÊ, nieprawdaĝ? JeĂli tak, to warto siÚ o tym przekonaÊ samodzielnie.
U
ĝywanie metod magicznych do pobierania
i ustalania warto
Ăci wïaĂciwoĂci klasy
Jak wspomniano w poprzednim podrozdziale, pisanie du
ĝej liczby metod akcesorów dla wïa-
ĂciwoĂci klasy moĝe byÊ prawdziwym koszmarem. Aby uniknÈÊ tego nudnego zadania, moĝna
wykorzysta
Ê metody magiczne. Taki proces nosi nazwÚ przeciÈĝania metody.
Programowanie obiektowe w PHP 5
50
W PHP 5 wprowadzono w klasach kilka metod magicznych, które znacznie u
ïatwiajÈ pracÚ
w niektórych zadaniach wykonywanych w OOP. Dwie z tych metod s
ïuĝÈ do dynamicznego
pobierania i ustalania warto
Ăci w klasie. Wspomniane metody noszÈ nazwy
__get()
oraz
__set()
.
Przyk
ïad ich uĝycia zostaï przedstawiony w poniĝszym fragmencie kodu:
<?
// class.student.php
class Student
{
private $properties = array();
function __get($property)
{
return $this->properties[$property];
}
function __set($property, $value)
{
$this->properties[$property]="AutoSet {$property} jako: ".$value;
}
}
?>
Kolejny krok to u
ĝycie tego kodu w programie. Powyĝsza klasa zostaje wiÚc zastosowana w po-
ni
ĝszym skrypcie:
<?
$st = new Student();
$st->name = "Afif";
$st->roll=16;
echo $st->name."\n";
echo $st->roll;
?>
Po wykonaniu kodu PHP natychmiast rozpozna,
ĝe w klasie nie istniejÈ wïaĂciwoĂci
name
i
roll
.
Poniewa
ĝ nazwy wïaĂciwoĂci istniejÈ, to nastÈpi wywoïanie metody
__set()
, która nast
Úpnie
przypisze warto
ĂÊ nowo utworzonej wïaĂciwoĂci klasy. Na ekranie zostanÈ wiÚc wyĂwietlone
nast
ÚpujÈce dane wyjĂciowe:
AutoSet name jako: Afif
AutoSet roll jako: 16
Wygl
Èda to caïkiem interesujÈco, nieprawdaĝ? UĝywajÈc metod magicznych, programista wciÈĝ
zachowuje pe
ïnÈ kontrolÚ nad ustawianiem i pobieraniem wartoĂci wïaĂciwoĂci klasy. Stosowa-
nie metod magicznych wi
Èĝe siÚ jednak z jednym ograniczeniem. Podczas uĝywania Reflection
API nie ma mo
ĝliwoĂci badania wïaĂciwoĂci klasy (wymienione Reflection API zostanie przed-
stawione w jednym z kolejnych rozdzia
ïów). Ponadto, sama klasa traci nieco ze swojej „czytel-
no
Ăci” oraz „ïatwoĂci obsïugi”. Dlaczego? Warto spojrzeÊ na poprzedniÈ i nowÈ klasÚ
Student
,
aby samodzielnie odpowiedzie
Ê sobie na to pytanie.
Rozdzia
á 2. • RozpoczĊcie pracy z OOP
51
Metody magiczne
s
ïuĝÈce do przeciÈĝania metod klasy
Podobnie jak w przypadku przeci
Èĝania i uĝywania metod akcesorów dostÚpne sÈ równieĝ
metody magiczne s
ïuĝÈce do przeciÈĝania wywoïania dowolnej metody klasy. Jeĝeli czytelnik
nadal nie rozumie poj
Úcia przeciÈĝania metody, to warto przypomnieÊ, ĝe jest to proces uzy-
skiwania dost
Úpu do dowolnej metody, która nawet nie istnieje w klasie. Brzmi niewiarygod-
nie, nieprawda
ĝ? Przyjrzyjmy siÚ bliĝej temu zagadnieniu.
Istnieje metoda magiczna, która pomaga w przeci
Èĝaniu dowolnego wywoïania metody w kon-
tek
Ăcie klasy jÚzyka PHP 5. Nazwa tej metody magicznej to
__call()
. Pozwala ona na zdefi-
niowanie dzia
ïañ lub wartoĂci zwrotnej w sytuacji, gdy w obiekcie nastÚpuje wywoïanie nie-
zdefiniowanej metody. Mo
ĝe to byÊ uĝywane do symulowania przeciÈĝania metody lub nawet
zapewnienia eleganckiej obs
ïugi bïÚdów, gdy niezdefiniowana metoda jest wywoïywana w obiek-
cie. Metoda
__call()
pobiera dwa argumenty — nazw
Ú metody oraz tablicÚ argumentów prze-
kazywanych niezdefiniowanej metodzie.
Poni
ĝej przedstawiono przykïad uĝycia metody
__call()
:
<?
class Overloader
{
function __call($method, $arguments)
{
echo "Wywoïano metodÚ o nazwie {method} z nastÚpujÈcymi
argumentami <br/>";
print_r($arguments);
echo "<br/>";
}
}
$ol = new Overloader();
$ol->access(2,3,4);
$ol->notAnyMethod("boo");
?>
Jak wida
Ê w powyĝszym kodzie, w klasie nie ma definicji metod
access
oraz
notAnyMethod
.
Dlatego te
ĝ próba ich wywoïania powinna zakoñczyÊ siÚ wygenerowaniem komunikatu bïÚ-
du, nieprawda
ĝ? Jednak technika przeciÈĝania metody pomaga w sytuacji, gdy nastÚpuje wy-
wo
ïanie nieistniejÈcej metody. Po wykonaniu powyĝszego kodu czytelnik otrzyma nastÚpujÈce
dane wyj
Ăciowe:
Wywoïano metodÚ o nazwie access z nastÚpujÈcymi argumentami
Array
(
[0] => 2