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
Linia 42:
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 go rozszerzyćimplementować każda klasa, która będzie chciała obsługiwać zdarzenia. Przy okazji napiszemy też korzystającą z niejniego klasę '''User''':
 
<source lang="php" line><?php
Linia 70:
</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_checkConditions()</code>, która musi zwrócić '''true''', aby wyrazić zgodę na wykonanie zdarzenia.
 
<source lang="php" line highlight="17"><?php
Linia 76:
class EventHandler
{
protected function checkConditions_checkConditions()
{
return true;
} // end checkConditions_checkConditions();
 
public function __call($name, $arguments)
{
if($this->checkConditions_checkConditions())
{
$name = '_'.$name.'Event';
Linia 97:
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 nowozarejestrowanychnowo zarejestrowanych użytkowników wygrywa nagrodę.
 
<source lang="php" line><?php
Linia 114:
protected $_winners = 0;
 
protected function checkConditions_checkConditions()
{
// sprawdzmy, czy w ogole mozna jeszcze wygrywac.
$this->_winners = trim(file_get_contents('./winner_count.txt'));
return ($this->_winners < 5);
} // end checkConditions_checkConditions();
 
protected function _onAddEvent($arguments)
Linia 240:
$this->_first = $this->_last = null;
 
// PrzejedzmyPrzejedźmy siesię po dotychczasowej kolejce i sklonujmyskopiujmy
// wszystkie jej elementy
while($top != null)
{
$this->enqueue(clone $top->getData());
$top = $top->getNext();
}
Linia 374:
</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 "OtwieramOdtwarzam połączenie z plikiem...".
 
Jest kilka zastosowań dla serializacji. Może posłużyć jako szybki cache złożonych struktur. Zamiast mozolnie budować dużą kolekcję obiektów przy każdym wejściu na stronę, możemy zbudować ją raz, zserializować i zapisać do pliku, a przy kolejnych żądaniach HTTP ładować z powrotem. Jednak musimy tutaj uważać. Prostota serializacji wielu programistom zupełnie przesłania kwestię bezpieczeństwa. Przede wszystkim ''nie wolno'' zserializowanych danych pokazywać użytkownikom naszej witryny w sposób jawny (nawet w ciastkach!). Spróbuj wyświetlić sobie zserializowaną postać obiektu klasy <code>File</code>. Zauważysz, że zapisana jest tam informacja o tym, do jakiej klasy obiekt należy. Jeśli będziesz bezmyślnie odserializowywać wszystko, co przyjdzie do Ciebie z przeglądarki, sprytny hacker może tak spreparować ciąg, by powstał jakiś kluczowy dla aplikacji obiekt i wykorzystać go do swoich niecnych celów. Oto przykład: mamy klasę <code>Mailer</code> służącą do wysyłania e-maili i wpadliśmy na pomysł, by dane o zalogowanych użytkownikach trzymać w ciastkach jako zserializowane tablice. Agresor jednak modyfikuje takie ciastko tak, aby po odczytaniu przez skrypt tworzyło obiekt klasy <code>Mailer</code> i rozesłało spam w Twoim imieniu. Musisz przyznać, że nie jest to ciekawa perspektywa. Dlatego trzy razy pomyśl, zanim zserializowane dane udostępnisz komukolwiek poza serwerem.