PHP/Metody magiczne: Różnice pomiędzy wersjami
Usunięta treść Dodana treść
poprawki błędów w skryptach i tekście |
Nie podano opisu zmian |
||
Linia 15:
Przyjrzyjmy się naszemu stale ulepszanemu systemowi konfiguracji. Odwoływanie się do opcji konfiguracyjnych przez <code>$config->get('nazwa')</code> jest odrobinę niewygodne. Dlatego zasymulujemy, że poszczególne opcje są dostępne jako pola klasy. Poniżej pokazany jest fragment klasy '''Config''' z nową metodą:
<
class Config implements Countable
{
Linia 32:
$config->addLoader(new FileConfigLoader('./config.ini.php'));
echo $config->websiteTitle;
</syntaxhighlight>
Metoda <code>__get</code> wywoływana jest w podświetlonej linijce, gdy próbujemy dostać się do nieistniejącego pola. PHP pobiera jej wynik i zwraca skryptowi jako jego wartość. Jednocześnie ponieważ nie chcemy, aby ktoś modyfikował konfigurację, nie udostępniamy metody <code>__set()</code> wywoływanej przy próbie przypisania nieistniejącemu polu jakiejś wartości.
Linia 44:
Najpierw zapoznajmy się z interfejsem '''EventDispatcher'''. Musi go implementować każda klasa, która będzie chciała obsługiwać zdarzenia. Przy okazji napiszemy też korzystającą z niego klasę '''User''':
<
interface EventDispatcher
{
Linia 68:
} // end addUser();
} // end User;
</syntaxhighlight>
Teraz pora na klasę '''EventHandler''' tworzącą podstawę dla wszystkich klas obsługi zdarzeń, jakie będziemy chcieli stworzyć. Zauważmy, że nie zawsze będziemy chcieli implementować wszystkie możliwe zdarzenia. Klasa '''User''' na razie obsługuje jedynie <code>onAdd()</code>, ale przecież w rzeczywistej aplikacji może tez posiadać zdarzenia <code>onEdit()</code>, <code>onDelete()</code> i inne. Po co tworzyć puste implementacje, kiedy można wszystko elegancko przechwycić poprzez <code>__call()</code> i odpowiednio przetworzyć przed użyciem? Ponadto, przed wywołaniem zdarzenia warto sprawdzić, czy spełnione są wszystkie warunki. '''EventHandler''' będzie zawierać dodatkową metodę <code>_checkConditions()</code>, która musi zwrócić '''true''', aby wyrazić zgodę na wykonanie zdarzenia.
<
class EventHandler
Linia 93:
} // end __call();
} // end EventHandler;
</syntaxhighlight>
Zwróćmy uwagę na linijkę 17 - oto odpowiedź, dlaczego nazwa pola klasy nie jest poprzedzona znakiem dolara. W tym miejscu PHP odczytuje nazwę metody do wywołania wprost ze zmiennej, dzięki czemu możemy dynamicznie decydować, co wykonać! Oczywiście warto wcześniej upewnić się, że w ogóle posiadamy odpowiednią metodę do obsługi zdarzenia i temu służy funkcja <code>method_exists()</code>. Zauważmy, że jeśli nie chcemy obsługiwać zdarzenia, nie grozi to nam teraz żadnymi konsekwencjami w postaci '''Fatal error'''. Wszystko przechwyci <code>__call()</code> i jeśli stwierdzi, że nie potrafi zająć się zdarzeniem, po prostu je zignoruje.
Linia 99:
Poniżej pokazujemy przykładowe zastosowanie. Stworzymy sobie dwie klasy do obsługi zdarzeń. Pierwsza będzie rejestrować fakt dodania nowego użytkownika, a druga pomoże nam w organizacji konkursu, gdzie pięciu losowo wybranych nowo zarejestrowanych użytkowników wygrywa nagrodę.
<
class LoggingEvent extends EventHandler
{
Linia 144:
$user->addEventHandler(new CompetitionEvent);
$user->addUser('jacek', 'joe');
</syntaxhighlight>
Oto potęga programowania obiektowego - przy odrobinie pomysłowości możemy rozszerzać istniejące klasy bez zmiany ani jednej linijki ich kodu. Wszystko odbywa się w pełni dynamicznie, a pomogła nam w tym magiczna metoda <code>__call()</code>. W ramach ćwiczenia przyjrzyj się dokładnie kodowi do losowania zwycięzców. W pewnej sytuacji nie zadziała on prawidłowo. Czy potrafisz powiedzieć kiedy i dlaczego?
Linia 154:
Jak pamiętamy, operator przypisania kopiuje jedynie referencję do obiektu. Są jednak sytuacje, gdy chcielibyśmy fizycznie skopiować cały obiekt. Najprostsze wyjście to wykorzystanie operatora <tt>clone</tt>:
<
</syntaxhighlight>
Jednak nasz obiekt może mieć otwarte różne zasoby (np. pliki) lub przechowywać referencje do innych obiektów. Standardowe klonowanie po prostu skopiuje same referencje, dlatego musimy mieć możliwość "poprawienia" sklonowanej kopii tak, by była ona w pełni samodzielna wtedy, gdy tego potrzebujemy. Tu do akcji wkracza magiczna metoda <code>__clone()</code>. Gdy znajduje się ona w klasie, PHP wywołuje ją tuż po sklonowaniu obiektu, dzięki czemu ma ona możliwość wprowadzenia poprawek.
Linia 163:
Zaczynamy od napisania klasy pojedynczego elementu kolejki. Będzie on przechowywać informację oraz referencję do następnego elementu w kolejce. Następny element może mieć namiary na kolejny i tak dalej, aż do końca - tworzy się w ten sposób łańcuch elementów prowadzących od pierwszego do ostatniego czekającego elementu.
<
class QueueElement
Linia 190:
} // end getNext();
} // end QueueElement;
</syntaxhighlight>
Nasza klasa składa się praktycznie wyłącznie z getterów i setterów. Nie ma tutaj jeszcze operacji klonowania, ponieważ na tym poziomie nie mamy jeszcze nic do zrobienia. Dopiero główna klasa kolejki zawierać będzie całe sterowanie. Znajdować się tu będą namiary na pierwszy i ostatni element kolejki tak, aby można było się do nich szybko dostać podczas odczytu lub zapisu.
<
class Queue
Linia 249:
} // end __clone();
} // end Queue;
</syntaxhighlight>
Przykładowe działanie:
<
$queue = new Queue;
Linia 274:
// pusto
}
</syntaxhighlight>
W przykładzie dodajemy do kolejki trzy elementy, a następnie ją klonujemy i dorzucamy do kopii jeszcze jeden element. Na końcu wyświetlamy elementy oryginału. Gdyby podczas klonowania pozostawić niezmienione elementy, wyświetliłyby nam się cztery liczby. Elementy kolejki są obiektami, zatem w skrypcie dostępne są jedynie referencje do nich. Operacja <code>enqueue()</code> sprawiłaby, że czwarta liczba zostałaby doczepiona do wspólnego elementu, a co gorsza - oryginał wcale by o tym nie wiedział! Mogłoby to doprowadzić do poważnych błędów w działaniu obu list. Zakomentuj operację <code>__clone()</code> i prześledź wszystko samodzielnie.
Linia 286:
Pisząc w PHP nie musimy przejmować się, jak interpreter reprezentuje tablice i obiekty w pamięci komputera. Jednak zdarza się, że chcielibyśmy zapisać je w całości do pliku lub przesłać komuś przez sieć. Za przetłumaczenie złożonego typu do postaci tekstowej i złożenie z niej z powrotem oryginału po drugiej stronie odpowiada tzw. ''serializacja''. Spróbujmy zobaczyć, jak ten proces przebiega:
<
$array = array(
'foo' => 'foo',
Linia 302:
// A teraz przywracamy oryginał
var_dump(unserialize($serialized));
</syntaxhighlight>
Wynikiem działania skryptu powinno być:
Linia 321:
Niestandardowe reguły serializacji takie, jak powyżej opisana, mogą zostać zaprogramowane dzięki magicznym metodom <code>__sleep()</code> oraz <code>__wakeup()</code>, które umieszczamy w naszej klasie. Nie pobierają one żadnych argumentów. Pierwsza z nich powinna zwrócić tablicę z nazwami pól, które mają podlegać serializacji, zaś w drugiej możemy umieścić cokolwiek. Zobaczmy zatem, jak przesłać tekstem obiekt reprezentujący połączenie z plikiem:
<
class File
Linia 360:
} // end __wakeup();
} // end File;
</syntaxhighlight>
W metodzie magicznej <code>__sleep()</code> mówimy: serializacja pola <code>$_handle</code> nie ma sensu, wystarczy nam nazwa pliku na odtworzenie obiektu. Odtworzenie w <code>__wakeup()</code> polega po prostu na ponownym otwarciu pliku. Możemy teraz przetestować całość:
<
require('./File.php');
Linia 372:
$file2 = unserialize(serialize($file));
echo $file2->read(100).'<br/>';
</syntaxhighlight>
Pierwszy, oryginalny obiekt tworzymy w tradycyjny sposób i odczytujemy z pliku 100 bajtów. Następnie całość serializujemy i odserializowujemy, by spróbować coś odczytać poprzez "nowy" obiekt. Po uruchomieniu zauważymy, że oba odczyty się udały. PHP wywołał <code>__wakeup()</code> podczas deserializacji i ponownie otworzył plik, a świadczy o tym komunikat "Odtwarzam połączenie z plikiem...".
Linia 382:
Rozdział ten zaczęliśmy od omówienia metod <code>__get()</code> i <code>__set()</code>, pisząc prosty system konfiguracyjny. Możemy odczytywać i zapisywać nowe wartości do różnych opcji, ale nie mamy jak sprawdzić czy konkretna opcja istnieje. Uzupełnimy teraz to niedopatrzenie dzięki dwóm kolejnym metodom magicznym: <code>__isset()</code> oraz <code>__unset()</code>. Pierwsza z nich wywoływana jest, gdy na danym polu zadziałamy komendą <code>isset()</code>:
<
{
...
}
</syntaxhighlight>
Metoda <code>__unset()</code> wywoływana jest podczas wykonywania komendy <code>unset()</code>, czyli próby usunięcia pola z obiektu:
<
Obie pobierają jeden argument będący nazwą pola. Dodajmy zatem do naszej klasy <code>Config</code> metodę <code>__isset()</code>.
<
{
return isset($this->_config[$name]);
} // end __isset();
</syntaxhighlight>
Jak widać, jest to bardzo proste. Jedyne, co robimy, to każemy sprawdzić czy opcja istnieje w naszej tablicy z opcjami.
Linia 406:
W PHP każdy obiekt można przekonwertować na ciąg tekstowy, a dzięki magicznym metodom możemy oprogramować tę operację. Na tapetę weźmiemy naszą klasę <code>File</code>. Nie chcemy ciągle pisać skomplikowanych odwołań w stylu <code>$file->getFilename()</code> za każdym razem, gdy próbujemy wyświetlić nazwę pliku. Dlatego dodamy metodę <code>__toString()</code>, która pozwoli nam to nieco uprościć. Dodaj poniższy kod do ciała wspomnianej klasy:
<
{
return $this->_name;
} // end __toString();
</syntaxhighlight>
Od metody tej oczekujemy, że wygeneruje tekstową reprezentację obiektu. W naszym przypadku ma to być po prostu nazwa pliku. Dzięki temu możemy teraz wyświetlić ją dużo prościej:
<
require('./File.php');
$file = new File('plik.txt');
echo 'Otworzyłem plik '.$file.'<br/>';
</syntaxhighlight>
== Zakończenie ==
|