PHP/Metody magiczne: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Zyx (dyskusja | edycje)
rozpoczęcie prac nad rozdziałem.
Zyx (dyskusja | edycje)
dopisanie dalszej części
Linia 35:
 
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.
 
{{stub}}
 
=== Wywoływanie metod ===
 
Magiczne metody potrafią także przechwytywać wywołania metod. Zasada działania jest analogiczna - jeśli PHP stwierdzi, że nie istnieje metoda o podanej nazwie, ale w klasie zdefiniowana jest operacja <code>__call()</code>, zrzuca całą robotę na nią. <code>__call()</code> dostaje dwa argumenty: nazwę metody oraz tablicę argumentów, z którymi programista próbował ją wywołać. Wykorzystajmy to do stworzenia mechanizmu zdarzeń. Zasada działania jest bardzo prosta. Jak pamiętamy, klasy posiadają pewne zachowania reprezentowane poprzez metody. Chcielibyśmy mieć możliwość rejestrowania dodatkowych czynności do wykonania, gdy zajdzie określone zdarzenie. Możemy dodatkowe czynności zapisać w postaci specjalnej klasy, której obiekt "wstrzykniemy" do właściwego obiektu. Znajdujący się tam kod sam zadba o to, aby czynności zostały wykonane.
 
Opis ten brzmi nieco abstrakcyjnie, ale spójrzmy na przykładowe zastosowanie. Mamy klasę '''User''', która m.in. potrafi dodawać nowych użytkowników do naszej aplikacji. Chcemy, aby dodanie użytkownika z poziomu panelu administracyjnego zostało odnotowane w logach. Nic prostszego - piszemy klasę, w której implementujemy metodę <code>onAdd()</code>, która będzie zapisywać informację do logów, a utworzony od niej obiekt dodamy do obiektu '''User''' już w trakcie działania skryptu, gdy zorientujemy się, że wyświetlamy panel administracyjny. Nie musimy modyfikować kodu oryginalnej klasy. Co więcej, gdybyśmy chcieli wykonać przy tej okazji jeszcze inne czynności, wystarczy że napiszemy jeszcze więcej klas zdarzeń i podepniemy je pod '''User'''.
 
Najpierw zapoznajmy się z interfejsem '''EventDispatcher'''. Musi ją rozszerzyć każda klasa, która będzie chciała obsługiwać zdarzenia. Przy okazji napiszemy też korzystającą z niej klasę '''User''':
 
<source lang="php" line><?php
interface EventDispatcher
{
public function addEventHandler(EventHandler $handler);
}
 
class User implements EventDispatcher
{
private $_handlers = array();
 
public function addEventHandler(EventHandler $handler)
{
$this->_handlers[] = $handler;
} // end addEventHandler();
 
public function addUser($login, $password)
{
foreach($this->_handlers as $handler)
{
$handler->onAdd($login, $password);
}
echo 'Użytkownik '.$login.' został dodany<br/>';
} // end addUser();
} // end User;
</source>
 
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.
 
<source lang="php" line highlight="17"><?php
 
class EventHandler
{
protected function checkConditions()
{
return true;
} // end checkConditions();
 
public function __call($name, $arguments)
{
if($this->checkConditions())
{
$name = '_'.$name.'Event';
if(method_exists($this, $name))
{
$this->$name($arguments);
}
}
} // end __call();
} // end EventHandler;
</source>
 
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.
 
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 nowozarejestrowanych użytkowników wygrywa nagrodę.
 
<source lang="php" line><?php
class LoggingEvent extends EventHandler
{
protected function _onAddEvent($arguments)
{
$f = fopen('./logs/user_log.log', 'a');
fwrite($f, date('r').': '.$arguments[0].' zostal zarejestrowany.');
fclose($f);
} // end _onAddEvent();
} // end LoggingEvent;
 
class CompetitionEvent extends EventHandler
{
protected $_winners = 0;
 
protected function checkConditions()
{
// sprawdzmy, czy w ogole mozna jeszcze wygrywac.
$this->_winners = trim(file_get_contents('./winner_count.txt'));
return ($_winners < 5);
} // end checkConditions();
 
protected function _onAddEvent($arguments)
{
// Losuj zwyciezce z prawdopodobienstwem 0,2%
if(rand(0, 1000) < 2)
{
echo $arguments[0].' wygrał!<br/>';
file_put_contents('./winner_count.txt', ++$this->_winners);
}
} // end _onAddEvent();
} // end CompetitionEvent;
 
$user = new User;
 
// Tworzymy normalnie, bez zadnych bajerow.
$user->addUser('wacek', 'foo');
 
// Teraz bedziemy rejestrowac fakt utworzenia w logach.
$user->addEventHandler(new LoggingEvent);
$user->addUser('franek', 'bar');
 
// A ten uzytkownik wezmie udzial w konkursie.
$user->addEventHandler(new CompetitionEvent);
$user->addUser('jacek', 'joe');
</source>
 
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?
 
{{Infobox|Od PHP 5.3.0 dostępna jest także analogiczna metoda <code>__callStatic()</code> do obsługi metod statycznych.}}
 
=== Klonowanie obiektów ===
 
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>:
 
<source lang="php" line>$kopia = clone $obiekt;
</source>
 
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.
 
{{RDoZrobienia}}
 
=== Serializacja obiektów ===