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

Usunięta treść Dodana treść
Yrazec (dyskusja | edycje)
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ą:
 
<sourcesyntaxhighlight lang="php" line highlight="17"><?php
class Config implements Countable
{
Linia 32:
$config->addLoader(new FileConfigLoader('./config.ini.php'));
echo $config->websiteTitle;
</syntaxhighlight>
</source>
 
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''':
 
<sourcesyntaxhighlight lang="php" line><?php
interface EventDispatcher
{
Linia 68:
} // end addUser();
} // end User;
</syntaxhighlight>
</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.
 
<sourcesyntaxhighlight lang="php" line highlight="17"><?php
 
class EventHandler
Linia 93:
} // end __call();
} // end EventHandler;
</syntaxhighlight>
</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.
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ę.
 
<sourcesyntaxhighlight lang="php" line><?php
class LoggingEvent extends EventHandler
{
Linia 144:
$user->addEventHandler(new CompetitionEvent);
$user->addUser('jacek', 'joe');
</syntaxhighlight>
</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?
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>:
 
<sourcesyntaxhighlight lang="php" line>$kopia = clone $obiekt;
</syntaxhighlight>
</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.
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.
 
<sourcesyntaxhighlight lang="php" line><?php
 
class QueueElement
Linia 190:
} // end getNext();
} // end QueueElement;
</syntaxhighlight>
</source>
 
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.
 
<sourcesyntaxhighlight lang="php" line><?php
 
class Queue
Linia 249:
} // end __clone();
} // end Queue;
</syntaxhighlight>
</source>
 
Przykładowe działanie:
 
<sourcesyntaxhighlight lang="php" line><?php
 
$queue = new Queue;
Linia 274:
// pusto
}
</syntaxhighlight>
</source>
 
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:
 
<sourcesyntaxhighlight lang="php" line><?php
$array = array(
'foo' => 'foo',
Linia 302:
// A teraz przywracamy oryginał
var_dump(unserialize($serialized));
</syntaxhighlight>
</source>
 
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:
 
<sourcesyntaxhighlight lang="php" line><?php
 
class File
Linia 360:
} // end __wakeup();
} // end File;
</syntaxhighlight>
</source>
 
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ść:
 
<sourcesyntaxhighlight lang="php" line><?php
require('./File.php');
 
Linia 372:
$file2 = unserialize(serialize($file));
echo $file2->read(100).'<br/>';
</syntaxhighlight>
</source>
 
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>:
 
<sourcesyntaxhighlight lang="php">if(isset($config->property))
{
...
}
</syntaxhighlight>
</source>
 
Metoda <code>__unset()</code> wywoływana jest podczas wykonywania komendy <code>unset()</code>, czyli próby usunięcia pola z obiektu:
 
<sourcesyntaxhighlight lang="php">unset($config->property);</sourcesyntaxhighlight>
 
Obie pobierają jeden argument będący nazwą pola. Dodajmy zatem do naszej klasy <code>Config</code> metodę <code>__isset()</code>.
 
<sourcesyntaxhighlight lang="php" line>public function __isset($name)
{
return isset($this->_config[$name]);
} // end __isset();
</syntaxhighlight>
</source>
 
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:
 
<sourcesyntaxhighlight lang="php" line>public function __toString()
{
return $this->_name;
} // end __toString();
</syntaxhighlight>
</source>
 
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:
 
<sourcesyntaxhighlight lang="php" line><?php
require('./File.php');
 
$file = new File('plik.txt');
echo 'Otworzyłem plik '.$file.'<br/>';
</syntaxhighlight>
</source>
 
== Zakończenie ==