PHP: Programowanie obiektowe dla początkujących - konstruktory, destruktory i magia

Dariusz Półtorak 23-02-2011, 10:30

W pierwszej części tego artykułu udało nam się utworzyć obiekt Osoba. Nauczyliśmy się tworzyć metody obiektu oraz jego atrybuty. Nie mieliśmy też najmniejszego problemu z ich użyciem. Ten tekst dotyczy „magii” obiektowości w PHP... w dosłownym tego słowa znaczeniu. Wytłumaczę, czym jest owa magia, jak się jej używa oraz jakie mogą być jej skutki.

1. Panie, a gdzie kultura?

W pierwszej części tego tekstu poznaliście Zenka Obiektowego. Przypomnijmy, oto on:

Ilustracja 1: Zenek Obiektowy

Ci z Was, którzy jeszcze nie znają Zenka, powinni się zapoznać z pierwszą częścią tego tekstu. Utworzyliśmy również klasę Zenka, która pozwala na wykonanie kilku czynności, takich jak jedzenie, bieganie czy spanie. Klasa ta przedstawia się następująco:

class Osoba {
	public $imie = 'Zenek';
	public $tytul = 'Inżynier';
	private $cel = 'Podbicie świata';
	private $hobby = 'Wzruszające telenowele';
	public $potrawa = 'Spaghetti';
	private $biceps = '0cm';
	public $numer_buta = 44;
 
	public function biegnij() { echo 'Biegnę! '; }
	public function skacz() { echo 'Skaczę! '; }
	public function pracuj() { echo 'Zdobywam świ... eee... pracuję! '; }
	public function jedz() { echo 'Przerwa na spaghetti! '; }
	public function spij() { echo 'Idę spać. Do jutra! '; }
}

Mówi się, że jedyne, co odróżnia nas od małp, to kultura osobista. Przejawem kultury jest przywitanie się, gdy gdzieś przychodzimy, oraz żegnanie się, kiedy wychodzimy. Ktoś z Was powiedziałby, że nie ma nic prostszego. Wyposażmy Zenka w metodę witaj(); oraz zegnaj(); i to załatwi sprawę.

Problem z użyciem takich metod jest jeden. Można o nich zapomnieć. W programowaniu bywa, że obiekt powinien mieć na starcie jakieś wartości. I ich pojawienie się powinno być wymuszone na programiście. Dla tych i innych celów obiekty wyposażono w konstruktor. Konstruktor jest niczym innym, jak metodą, która jest wywoływana za każdym razem, gdy tworzymy nowy obiekt. PHP, jako specyficzny język, wymaga, by metoda będąca konstruktorem nazywała się __construct(). Ci znający inne języki programowania zapewne się zdziwią, gdyż na ogół metoda w takim wypadku nazywa się tak samo, jak klasa. Przykład konstruktora (pamiętajcie, że powinien być on umieszczony wewnątrz klasy):

public function __construct() { 
	echo 'Witam wszystkich'; 
}

Teraz mały przykład użycia:

$Osoba = new Osoba();

Jak widzicie, utworzenie instancji obiektu Osoba zaowocowało wyświetleniem na ekranie „Witam wszystkich”. Jak by ktoś pytał, czyja to wina, możecie zwalić wszystko na konstruktor.

2. Kim jestem?

Jak widać, powyższy konstruktor naszej klasy nie jest zbyt przydatny. Żeby sprawić, by się bardziej przydał, uzupełnimy go o imię. Konstruktory, jak każda inna metoda czy funkcja, mogą przyjmować parametry.

public function __construct($imie) { 
	echo 'Witam wszystkich. Jestem '.$imie; 
}

Jeżeli utworzymy nasz obiekt, podając imię Zenek w ten sposób:

$Osoba = new Osoba('Zenek');

Otrzymamy:

Witam wszystkich. Jestem Zenek

Kolejnym krokiem do szczęścia jest to, by osoba zapamiętała, jakie ma imię. Po co podawać drugi raz imię osoby, skoro zrobiliśmy to w konstruktorze? Pamiętajcie jednak, że zmienna $imie, która jest w konstruktorze, nie jest atrybutem obiektu. To, że obiekt ma atrybut (public $imie), nie oznacza że zmienna wewnątrz obiektu to właśnie ten atrybut. Pokaże to poniższy przykład:

public function __construct($imie) { 
	echo 'Witam wszystkich. Jestem '.$imie.'
';
	$this->imie = $imie; 
	$imie = 'Franek'; 
	echo $imie.'
';
}

A teraz użycie:

$Osoba = new Osoba('Zenek'); 
echo 'Spodziewales sie Franka? - '.$Osoba->imie;

Wywołując nasz skrypt, otrzymaliśmy:

Witam wszystkich. Jestem Zenek
Franek
Spodziewales sie Franka? -Zenek

Wewnątrz obiektu mamy zarezerwowaną zmienną $this, która jest referencją obiektu, w którym ją używamy. Poprzez $this możemy korzystać z obiektu tak samo, jak byśmy korzystali ze zmiennej $Osoba poza nim. W ten sposób możemy pobierać albo zmieniać atrybuty, wywoływać metody czy też przekazywać wartości pomiędzy wywołaniami metod.

Oprócz $this w obiekcie mamy dostępną zmienną $self. Zmienna ta pozwala na uzyskanie dostępu do stałych obiektu oraz zmiennych statycznych. Obie te wartości deklarujemy podobnie, jak atrybuty. Poprzedzamy je jednak słowem kluczowym. Deklaracja stałej wygląda tak:

const niezmienny = 'Test';

Z kolei deklaracja zmiennej statycznej wygląda tak:

public static $statyczna = 'Test 2';

A oto przykład wywołania zmiennej statycznej:

echo Osoba::$statyczna; 
echo $Osoba::$statyczna;

Oraz stałej:

echo $Osoba::niezmienny;

Wewnątrz obiektu zrobilibyśmy to tak:

echo self::$statyczna;
echo self::niezmienny;

Po co są nam potrzebne stałe i zmienne statyczne, zapytacie? Stała, jak sama nazwa wskazuje, jest to obszar pamięci, który się nie zmienia. Dlatego w chwili jego deklaracji, musieliśmy mu nadać wartość „Test”. O ile możemy odczytać wartość, jak w przykładzie powyżej, o tyle nie możemy już tej wartości zmienić.

Zmienna statyczna z kolei to obszar pamięci, który jest współdzielony przez wszystkie obiekty. W skrócie, jeżeli utworzymy 10 różnych obiektów Osoba i jednemu z nich ustawimy $statyczna na 5, wszystkie obiekty będą zwracały wartość 5, jeżeli spróbujemy ją odczytać. Oto przykład:

$Osoba_Zenek = new Osoba('Zenek'); 
$Osoba_Franek = new Osoba('Franek'); 

$Osoba_Franek::$statyczna = 0; 
$Osoba_Zenek::$statyczna = 5; 
echo $Osoba_Franek::$statyczna;

Jak widać, otrzymaliśmy na ekranie wartość 5. Dodatkowo warto zauważyć, że nie potrzebujemy instancji obiektu w zmiennej, jeżeli chcemy uzyskać wartość stałej bądź statycznej. Mało tego, zmienne statyczne możemy zmieniać, nawet kiedy nie mamy obiektu. I każdy utworzony obiekt będzie posiadał tę wartość. Oto przykład:

echo Osoba::$statyczna;
echo Osoba::$statyczna;
echo Osoba::niezmienny;

Teraz mała ciekawostka dla wszystkich, którzy będą się starali poderwać partnera w barze na dziwne słowa. Operator ::, którego używamy do uzyskania wartości zmiennej statycznej bądź stałej, nazywa się PAAMAYIM NEKUDOTAYIM. Czyli podwójny dwukropek po hebrajsku.

3. Magia dostępu

Czym jest magia? Magia jest czymś niezrozumiałym. Czymś, czego nie da się racjonalnie wyjaśnić. Jest marzeniem każdego dziecka i rozrywką każdego dorosłego. W programowaniu magia jest różnie interpretowana.

Kiedy programista rozwiązał jakiś problem w zupełnie niezrozumiały sposób, dochodząc do momentu, kiedy w ogóle ani on, ani nikt inny nie ma pojęcia, jak dany fragment programu działa (a działa, i to jak!), mówimy że to magia. Kiedy politycy obiecują obniżenie podatków, a następnie je podnoszą, a ich poparcie nie maleje, również nazywamy to magią. Kiedy jakiś człowiek przebrany za błazna rozcina na pół kobietę na stole piłą motorową przy aplauzie publiczności, nazywamy to magią. Magia to coś niepojętego, niezrozumiałego. Coś, co się dzieje, mimo że dziać się nie powinno.

W PHP powstały w ten sposób metody magiczne. Jak by ktoś pytał, od razu powiem, że jest to ich oficjalna nazwa. W dokumentacji języka znajdziemy rozdział nazwany „Magic Methods”.

Do takich metod zaliczają się:

  1. __construct (tak, nasz konstruktor to magiczna metoda)
  2. __destruct (o niej na końcu tekstu)
  3. __call
  4. __callStatic
  5. __get
  6. __set
  7. __isset
  8. __unset
  9. __sleep
  10. __wakeup
  11. __toString
  12. __invoke
  13. __set_state
  14. __clone

Jak widzisz, wszystkie magiczne metody w PHP zaczynają się od __. Dlatego by uniknąć pomyłek czy konfliktów nazw, staraj się nie stosować podwójnego podkreślenia, nazywając swoje metody. Konstruktor już poznaliśmy. Destruktor (__destruct), został omówiony na końcu tego tekstu. Przejdźmy w takim razie do pozostałych metod.

__call jest metodą magiczną, która jest wywoływana wtedy, gdy próbujemy wywołać metodę obiektu, która nie istnieje. Przykład:

public function __call($nazwa, $argumenty) { 
	echo "Wybacz, ale nie potrafie wykonac rozkazu: '".$nazwa."'"; 
	var_dump($argumenty); 
}

Jeżeli spróbujemy wywołać $Osoba->idz_po_pizze('salami');, otrzymamy:

Wybacz ale nie potrafie wykonac rozkazu: 'idz_po_pizze'
array
0 => string 'salami' (length=6)

Jak widzisz, pierwszy parametr __call zwrócił nazwę metody, którą chcemy wykonać, drugi zaś tablicę parametrów, jakie do niej przekazaliśmy.

__get oraz __set z koleji mają podobne działanie, lecz operują na atrybutach, które nie istnieją. __get($nazwa) podaje nazwę nieistniejącego atrybutu, który chcemy uzyskać. Z kolei __set($nazwa, $wartosc) pozwala nam na uzyskanie nazwy nieistniejącego atrybutu oraz wartość, jaką chcieliśmy mu nadać.

Najprostszy przykład, do jakiego mogą posłużyć te metody, to wewnętrzna tablica obiektu. Jeżeli chcemy modyfikować elementy tej tablicy z użyciem zapisu obiektowego, możemy to robić za pomocą __get oraz __set.

__isset() oraz __empty() są niejako pomocą dla __get oraz __set. W momencie gdy używamy na atrybucie funkcji isset(), metoda __isset() obiektu jest wywoływana. Analogicznie w wypadku użycia funkcji empty(). Przykład:

if( isset( $Osoba->tester  ) ) { echo 'Jest'; } else { echo 'Nie ma'; }

W tym wypadku funkcja isset() zwróci false. Jeżeli jednak korzystamy z __set oraz __get, nie chcielibyśmy by zawsze tak było. Np. jeżeli w naszej wewnętrznej tablicy znajduje się klucz 'tester' ustawiony przez __set, metoda __isset() sprawdzi, czy taki element tablicy istnieje i zwróci poprawną wartość. A oto przykład implementacji metody magicznej __set:

public function __set($nazwa, $wartosc) { $this->tablica[$nazwa] = $wartosc; }

4. Czym Ty jesteś?

Kolejne metody, jakie mam zamiar przedstawić, potrafią czasami wprowadzić sporo zamieszania. Bo nic tak nie wprowadza w zdziwienie, jak widok widelca w ręku kogoś, kto je zupę. Jeszcze dziwniejsze jest to, że ten widelec nie utrudnia mu jedzenia! Tak samo zdziwienie może ogarnąć kogoś, kto widzi, jak programista próbuje wypisać za pomocą echo obiekt, bądź... użyć jego instancji jako funkcji. Przykład na obiekcie $Osoba:

$Osoba = new Osoba('Zenek'); 
echo $Osoba;

Próba wywołania tego kodu zakończy się oczywiście niepowodzeniem, w postaci komunikatu błędu: „Catchable fatal error: Object of class Osoba could not be converted to string in...”.

Jednak możemy ten problem ominąć, deklarując w naszym obiekcie metodę magiczną __toString(). Zostanie ona wywołana zawsze, gdy spróbujemy taką sztuczkę, jak powyżej, wykonać:

public function __toString() { 
	return "Czesc, jestem {$this->imie}. Jestes zdziwiony, ze to widzisz?"; 
}

Jeżeli dodamy tę metodę do naszej klasy, wywołanie powyższego kodu zakończy się komunikatem: Czesc, jestem Zenek. Jestes zdziwiony, ze to widzisz? Podobna sytuacja jest z metodą __invoke(). Kod:

$Osoba = new Osoba('Zenek'); 
$Osoba();

zakończy działanie błędem „Fatal error: Function name must be a string in”. Jednak gdy dodamy metodę magiczną __invoke(), uzyskamy oczekiwany efekt. Oto deklaracja metody __invoke:

public function __invoke() { 
	echo "Czesc, jestem {$this->imie}. Jestes zdziwiony, ze to widzisz?"; 
}

Są to podstawowe funkcje magiczne w PHP 5. Poniżej krótkie omówienie pozostałych metod magicznych. Zostaną one omówione w jednej z kolejnych części tego kursu z uwagi na to, że ich działanie wymaga dogłębnego wyjaśnienia. A są to:

  1. __callStatic – działa podobnie jak __call(), jednak wywoływana jest w momencie, gdy próbujemy uzyskać dostęp do statycznej metody.
  2. __sleep – działa, gdy obiekt poddany jest serializacji.
  3. __wakeup – działa, gdy obiekt poddany jest deserializacji.
  4. __set_state – jest wywoływana w momencie, gdy eksportujemy nasz obiekt za pomocą var_export()
  5. __clone – jest wywoływana w momencie klonowania obiektu za pomocą clone().

5. Jesteś dobrem czy złem?

Magia jest tym, czym jest, głównie dlatego, że wiele osób jej nie rozumie. Gdy Europejczycy najechali Amerykę, tamtejsi tubylcy widzieli w białych ludziach bogów, zaś broń palną, której ci używali, uznawali za coś magicznego. Gdy magik wyciąga królika z kapelusza, wielu z nas myśli, że to magia, gdyż nie widzą triku, który za tym stoi. Wielu nie chce znać tego triku, by owej magii nie stracić.

Jak widać w powyższych przykładach, jesteśmy w stanie obsłużyć nieistniejące atrybuty, metody, użyć obiektu jako funkcji czy też wywołać na nim instrukcję echo lub inną, niejako „drukując” go. Robimy wiele ciekawych rzeczy i zapewne wielu z Was przyszło do głowy w tej chwili setki pomysłów, jak ową magię wykorzystać.

Wśród programistów PHP panuje pierwsza i naczelna zasada programowania obiektowego. Nie stosować magii. I jest to zasada, którą powtarzają również ci, którzy ją stosują. Podstawową wadą magii jest to, że wielu jej nie rozumie. Tak samo jest w PHP. W momencie gdy wyświetla się zmienną za pomocą echo, a za chwilę używa się jej jak funkcji, by następnie wyciągnąć z niej atrybut, to człowiek zaczyna się gubić, co gdzie jest. Zaczyna się zastanawiać, kiedy obiekt został zastąpiony ciągiem znaków. W momencie gdy wywołuje nieistniejącą metodę i nie dostaje błędu, a obiekt coś wykonuje, może się cieszyć lub godzinami dochodzić, gdzie jest błąd, bo wywołując jedną z istniejących metod, zrobił drobny błąd w nazwie.

Magia w PHP może prowadzić do wielu trudnych do wychwycenia błędów. Nieumiejętnie użyta prowadzi do tzw. „zaciemniania kodu”. Przez to inny programista będzie miał trudności ze zrozumieniem jego działania, jeżeli zajdzie taka potrzeba. Służy ona przede wszystkim w sytuacjach, gdzie konwencjonalne rozwiązanie byłoby o wiele mniej eleganckie i czytelne niż „magiczne”. A takich sytuacji nie ma dużo. Dlatego z głową podejdźcie do jej stosowania.

6. Koniec na dzisiaj (destruktor)

To by było tyle. Zostało nam tylko do końca nauczyć Zenka żegnać się ze wszystkimi. Robimy to za pomocą destruktora. Destruktor nie przyjmuje żadnych parametrów. Jest wywoływany w momencie, gdy dany obiekt przestaje istnieć. A oto jego deklaracja dla Zenka:

public function __destruct() {
	echo 'PAPA WSZYSTKIM! DZIĘKUJĘ ZA UWAGĘ!';
}

Przy destruktorach warto pamiętać o paru drobiazgach. Na ogół dłużą do „sprzątania” po obiekcie i ogólnie wykonywania czynności przy niszczeniu obiektu. Wywoływane są w odwrotnej kolejności niż wywołane zostały konstruktory. Czyli jeżeli stworzymy obiekty Zenek, Franek i Dexter, to jeżeli nie usuniemy ręcznie tych obiektów, tylko poczekamy na zakończenie skryptu, najpierw wywołany zostanie destruktor Dextera, później Franka, a na końcu naszego Zenka.

Dariusz Półtorak


Źródło: DI24.pl
  
znajdź w serwisie


RSS - Wywiad
Wywiad  
RSS - Interwencje
RSS - Porady
Porady  
RSS - Listy
Listy