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

Usunięta treść Dodana treść
Zyx (dyskusja | edycje)
Dokończenie opisu klonowania.
Zyx (dyskusja | edycje)
dokończenie rozdziału
Linia 24:
{
return $this->get($name);
} // end count__get();
// pozostała część klasy
Linia 283:
 
=== Serializacja obiektów ===
 
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:
 
<source lang="php" line><?php
$array = array(
'foo' => 'foo',
'bar' => 'bar',
'joe' => 'joe'
);
 
// Sprawdźmy, jak wygląda tablica normalnie
var_dump($array);
 
// Zserializujmy ją
$serialized = serialize($array);
echo $serialized.'<br/>';
 
// A teraz przywracamy oryginał
var_dump(unserialize($serialized));
</source>
 
Wynikiem działania skryptu powinno być:
 
<nowiki>array
'foo' => string 'foo' (length=3)
'bar' => string 'bar' (length=3)
'joe' => string 'joe' (length=3)
a:3:{s:3:"foo";s:3:"foo";s:3:"bar";s:3:"bar";s:3:"joe";s:3:"joe";}
array
'foo' => string 'foo' (length=3)
'bar' => string 'bar' (length=3)
'joe' => string 'joe' (length=3)
</nowiki>
 
Funkcja <code>serialize()</code> przekonwertowała tablicę na specjalnie sformatowany ciąg tekstowy, w którym zawarte są informacje o wszystkich jej elementach. Gdy wprowadzimy go do funkcji <code>unserialize()</code>, PHP z powrotem odtworzy naszą tablicę. Serializować można także obiekty, a konwersji podlegają wszystkie pola. Nie zawsze jest to zjawisko pożądane. Jeśli nasz obiekt zawiera uchwyt do pliku, zostanie on zgubiony podczas przesyłania, dlatego dobrze by było, gdyby zamiast niego w zserializowanym tekście znalazła się po prostu ścieżka do niego. Podczas deserializacji musielibyśmy otworzyć go ponownie.
 
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:
 
<source lang="php" line><?php
 
class File
{
private $_handle;
private $_name;
 
public function __construct($filename)
{
$this->_name = $filename;
$this->_handle = fopen($filename, 'r');
} // end __construct();
 
public function __destruct()
{
fclose($this->_handle);
} // end __destruct();
 
public function getFilename()
{
return $this->_name;
} // end getFilename();
 
public function read($bytes)
{
return fread($this->_handle, (int)$bytes);
} // end read();
 
public function __sleep()
{
return array('_name');
} // end __sleep();
 
public function __wakeup()
{
echo 'Odtwarzam połączenie z plikiem...<br/>';
$this->_handle = fopen($this->_name, 'r');
} // end __wakeup();
} // end File;
</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ść:
 
<source lang="php" line><?php
require('./File.php');
 
$file = new File('plik.txt');
echo $file->read(100).'<br/>';
 
$file2 = unserialize(serialize($file));
echo $file2->read(100).'<br/>';
</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 "Otwieram 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.
 
=== Rozszerzony dostęp do pól obiektów ===
 
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>:
 
<source lang="php">if(isset($config->property))
{
...
}
</source>
 
Metoda <code>__unset()</code> wywoływana jest podczas wykonywania komendy <code>unset()</code>, czyli próby usunięcia pola z obiektu:
 
<source lang="php">unset($config->property);</source>
 
Obie pobierają jeden argument będący nazwą pola. Dodajmy zatem do naszej klasy <code>Config</code> metodę <code>__isset()</code>.
 
<source lang="php" line>public function __isset($name)
{
return isset($this->_config[$name]);
} // end __isset();
</source>
 
Jak widać, jest to bardzo proste. Jedyne, co robimy, to każemy sprawdzić czy opcja istnieje w naszej tablicy z opcjami.
 
=== Konwersja do ciągu tekstowego ===
 
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:
 
<source lang="php" line>public function __toString()
{
return $this->_name;
} // end __toString();
</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:
 
<source lang="php" line><?php
require('./File.php');
 
$file = new File('plik.txt');
echo 'Otwarłem plik '.$file.'<br/>';
</source>
 
== Zakończenie ==