Ostatnio przygotowując artykuł dla kolegi, na temat obiektowości, dowiedziałem się, że już rozumie większość rzeczy i z chęcią dowiedziałby się czegoś o magicznych metodach i interfejsach. Co prawda słyszałem już o nich, ale nigdy nie zagłębiałem się w tym temacie. Postanowiłem poświęcić parę godzin w tygodniu na edukację, zrobiłem kilka przykładów i przełożyłem to na bardziej zrozumiały język, żeby korzystając z jednego artykułu, można było się wszystkiego dowiedzieć. Podczas czytania tego artykułu, zachęcam Cię drogi czytelniku do korzystania z php sandbox-a, który nie tylko przydaję się w pracy podczas szybkiego sprawdzania różnych rzeczy, ale również w nauce.
Czym są magiczne metody?
Charakteryzują się tym, że nazwy tych metod zaczynają się od podwójnego podkreślenia (__nazwa)
Każdy z nas zapewne kojarzy już 2 takie metody, są to:
__construct() oraz __destruct()
które są wywoływane automatycznie przy tworzeniu i niszczeniu obiektu.
Istnieją jeszcze kolejne:
__get() i __set()
__get() – funkcja jest uruchamiana gdy próbujemy dostać się do nieistniejącego pola. W normalnym przypadku, gdy pole nie istnieje otrzymamy Notice-a. Dzięki skorzystaniu z tej magicznej metody, możemy taki przypadek obsłużyć customowo, wywałając np. Exception. Funkcja ta przyjmuje tylko jeden parametr.
class Person
{
public $age;
public function __get($name) {
throw new Exception(sprintf('Variable %s has not been declared', $name));
}
}
$person = new Person();
echo $person->siblings;
__set() – funkcja jest uruchamiana w przypadku kiedy próbujemy ustawić nieistniejącą wartość. Przyjmuje ona 2 parametry ($name oraz $value). Pod $name, będziemy mieli wartość którą próbowaliśmy ustawić, a pod $value – zawartość. Podczas próby ustawienia wartości bez magicznej metody, nic się nie stanie (nie będzie Notice-a oraz Errora).
class Person
{
public $age;
public function __set($name, $value) {
echo 'Variable '.$name.' has not been declared and You can\'t set '.$value.' to it!';
}
}
$person = new Person();
$person->lastname = 'Andrew';
_call()
Działanie jest analogiczne do funkcji __get(). Jeżeli wywołamy getAgeFromBirthday() w naszym obiekcie Person, a taka metoda nie będzie istnieć lub dostęp będzie niemożliwy (modyfikator private lub protected) interpreter PHP sprawdzi, czy istnieje magiczna metoda __call. Jeżeli nie ma takiej metody, otrzymamy: „Uncaught Error: Call to undefined method Person::getAgeFromBirthday() in …”, możemy się przed tym zabezpieczyć, używając metody _call, która przyjmuje 2 parametry: nazwę którą probowaliśmy wywołać oraz przekazywane argumenty w postaci tablicy.
class Person
{
public $age;
public function __call($method, $params) {
echo 'unknown method "'.$method.'" Passed arguments:';
print_r($params);
}
}
$person = new Person();
$person->getAgeFromBirthday('12.12.1990');
__callStatic()
Takie samo działanie jak _call, tylko że dla wywołania statycznego
class Person
{
public $age;
public static function __callStatic($method, $params) {
echo 'unknown method "'.$method.'" Passed arguments:';
print_r($params);
}
}
$Person = new Person();
$Person::getAgeFromBirthday('12.12.1990');
__toStrnig()
Pozwala nam wywołać obiekt jako string. W normalnym przypadku, podczas wywołania
echo $broker = new Person(), orzymalibyśmy:
Recoverable fatal error</b>: Object of class Person could not be converted to string in… , możemy jednak sprawić, że będziemy mogli wywołać obiekt jako string. Magiczna metoda __toString nie przyjmuje żadnych parametrów. W momencie wywołania obiektu jako string, wykonywane jest ciało funkcji.
class Person {
public function __construct($name, $lastName, $age) {
$this->name = $name;
$this->age = $age;
$this->lastName = $lastName;
}
public function __toString() {
return $this->name.' '.$this->lastName.' '.$this->age;
}
}
$broker = new Person('Andres', 'Smith', 50);
echo $broker;
__invoke()
Funkcja ta jest bardzo podobna do __toString – wywoływana, kiedy obiekt używany jest w kontekście funkcji
class Person {
public function __construct($name, $lastName, $age) {
$this->name = $name;
$this->age = $age;
$this->lastName = $lastName;
}
public function __invoke($value) {
echo $value;
}
}
$broker = new Person('Andres', 'Smith', 50);
echo $broker(55);
__clone()
Kopiowanie w PHP odbywa się poprzez referencję. Zmiana jednego z obiektów spowoduje więc jednoczesną zmianę drugiego.
$SeniorBroker = new stdClass();
$SeniorBroker->age = 50;
$JuniorBroker = $SeniorBroker;
$JuniorBroker->age = 19;
echo $SeniorBroker->age;
Jak się domyślacie, bądź nie, w tym przypadku nadpiszemy wiek seniora i pojawi się nam wiek 19. Możemy natomiast skopiować obiekt, tak jak planowaliśmy, aby zmiany w kolejnym obiekcie, nie miały wpływu na oryginalny:
$SeniorBroker = new stdClass();
$SeniorBroker->age = 50;
$JuniorBroker = clone $SeniorBroker;
$JuniorBroker->age = 19;
echo $SeniorBroker->age;
Magiczna funkcja __clone(), pozwala nam na zdefiniowanie tego, co ma się dziać w klasie, jeżeli zostanie wywołana na niej metoda clone().
W poniższym przykładzie, nie chcemy aby ktokolwiek dowiedział się ile Senior miał lat oraz chcemy zabezpieczyć się przed Notice-m, gdy ktokolwiek spróbuje wywołac niezadeklarowane pole:
class Person {
public $age;
public $title;
public function __clone() {
unset($this->age);
}
public function __get($name) {
echo 'Variable "'.$name.'" has not been declared.';
}
}
$SeniorBroker = new Person();
$SeniorBroker->age = 50;
$Broker = clone $SeniorBroker;
echo $Broker->age;
__sleep() oraz __wakeup()
Serializacja obiektów polega na przetłumaczniu złożonego typu do postaci tekstowej i złożenie z niej z powrotem oryginału. Służy do tego funkcja serialize() a dzięki magicznym metodą, możemy nad serializacją zapanować(serializacja nie będzie w stanie przechować nam uchwytów np. do bazy danych).
_sleep()
Wywoływane podczas używania na obiekcie serialize(). Domyślnie funkcja serialize przekonwertuje nam cały obiekt, dzięki magicznej metodzie, możemy wybrać tylko te pola, które nas interesują.
Przykład – interesuje nas tylko title i age:
class Person {
public $age;
public $title = 'Senior Sale Specialist';
public $teamworkId = '34562';
private $bankAccountAmount = 150000;
private $db;
public function __sleep() {
return ['age', 'title'];
}
}
$SeniorBroker = new Person();
var_dump($brokerSerialized = serialize($SeniorBroker)); // mamy tutaj wylacznie age i title, bez teamworkId
__wakeup()
Wywoływane podczas używania na obiekcie unserialize(). Przykład – chcemy przywrócić połaczenie z bazą, które podczas stosowania serialize() zostało utracone.
class Person {
public $age;
public $title = 'Senior Sale Specialist';
private $bankAccountAmount = 150000;
private $db;
public function connect() {
$this->db = 'connected';
}
public function __wakeup() {
$this->connect();
}
}
$SeniorBroker = new Person();
$brokerSerialized = serialize($SeniorBroker);
var_dump((unserialize($brokerSerialized)));
_isset() i __unset()
_isset()
Wywoływana gdy na właściwości obiektu który nie istnieje, bądź do którego nie ma dostępu,
zostanie użyta fukcja isset() albo empty(). Przykład – sprawdzenie czy prywatne pole zostało ustawione(bez magicznej funkcji otrzymalibyśmy Fatal Error)
class Person {
public $age;
public $title = 'Senior Sale Specialist';
private $bankAccountAmount = 150000;
public function __isset($name) {
echo isset($this->{$name});
}
}
$SeniorBroker = new Person();
isset($SeniorBroker->bankAccountAmount);
__unset()
Wywoływana w identyczny sposób jak __isset(), z tą różnicą, że tylko wtedy, gdy użyta zostanie funkcja unset(). Przykład – zapisywanie do loga każdej usuwanej zmiennej, jeśli miała jakąś wartość.
class Person {
public $age;
public $title = 'Senior Sale Specialist';
private $bankAccountAmount = 150000;
public function __unset($name) {
if (isset($this->{$name})) {
echo 'Save to log: '.$name.'='.$this->{$name};
}
}
}
$SeniorBroker = new Person();
unset($SeniorBroker->bankAccountAmount);
__set_state()
Wywoływane podczas używania var_export(var_export różni się od var_dump oraz print_r tym, że zwraca poprawny do wywołania kod).
Przykład działania var_export:
$alphabet = ['a', 'b', 'c'];
var_export($alphabet);
$arrayFromVarExport = array ( // tablica zwrocona z var_export
0 => 'a',
1 => 'b',
2 => 'c',
);
var_dump($alphabet);
var_dump($arrayFromVarExport); // jak widzimy, sa identyczne
var_export nie do końca dobrze radzi sobie z wyświetlaniem obiektów jako prawidłowego kodu.
Poniższy kod zwróci nam Fatal Error:
class Person {
private $age = 50;
private $title = 'Senior Sale Specialist';
}
$Person = new Person;
var_export($Person);
eval(var_export($Person, true).';');
Rozwiązanie:
class Person {
private $age = 50;
private $title = 'Senior Sale Specialist';
public static function __set_state(array $parameters){
$Person = new Person;
$Person->name = $parameters['age'];
$Person->password = $parameters['title'];
return $Person;
}
}
$Person = new Person;
var_export($Person);
eval(var_export($Person, true).';');
__debugInfo()
Wywoływane podczas używania var_dump() (jeżeli w obiekcie nie ma magicznej metody, wszystkie pola: public, protected i private zostaną wyświetlone)
Przykład użycia: przesyłamy obiekt na inny serwer i nie chcemy, żeby zawartość była widoczna, po użyciu var_dump na obiekcie. W normalnym przypadku użycia var_dump-a zobaczymy całą zawartość obiektu:
class Person {
public $age;
public $title = 'Senior Sale Specialist';
private $bankAccountAmount = 150000;
private $secretPassword = 'porlakm5ikkdiu4';
}
$toSend = new Person;
var_dump($toSend);
Rozwiązanie:
class Person {
public $age = 50;
public $title = 'Senior Sale Specialist';
private $bankAccountAmount = 150000;
private $secretPassword = 'porlakm5ikkdiu4';
public function __debugInfo() {
return [];
}
}
$toSend = new Person;
var_dump($toSend);
echo $toSend->age;
UWAGA !!!
Wywołanie var_export na obiekcie pozwoli nam na zobaczenie całej zawartości
Przykłady wykorzystania były wymyślane na szybko, jeżeli w codziennej pracy korzystacie z tych funkcji, znacie ciekawe przypadki wykorzystania, podzielcie się w komentarzu :).