PHP 8.2 - sprawdź co nowego!

Programowanie obiektowe > Lekcja 5

Metody magiczne

Przed nami kolejne dość ciekawe zagadnienie, co prawda z magią ma niewiele wspólnego. Są to w zasadzie normalne metody, ale nazywane magicznymi, ponieważ wywoływane są automatycznie w momencie wykonania danych akcji. Mamy w PHP kilka takich metod i zaraz sobie je omówimy. Występują tylko w kontekście klas, a ich nazwy zaczynają się od __ (dwa znaki podkreślenia).

__construct i __destruct

Najczęściej używana i spotykana to __construct - konstruktor. Metoda ta wywoływana jest w momencie tworzenia obiektu. Możemy też przekazywać przy jej pomocy dowolną liczbę argumentów. Do czego się przydaje w praktyce? Na przykład do przesłania jakichś wartości do nowo tworzonego obiektu.

Ciekawostka

Konstruktor używany jest przy wzorcu projektowym Dependency Injection (inaczej też: DI, wstrzykiwanie zależności). Jest to bardzo popularne podejście, które polega na wstrzykiwaniu obiektów do danej instancji, zamiast za każdym razem tworzeniu nowych gdzieś wewnątrz danej klasy. Przekazujemy po prostu potrzebne zależności poprzez argumenty konstruktora, przypisujemy do właściwości klasy i gotowe, będziemy ich mogli łatwo użyć w całej klasie. Umożliwia to lepszą kontrolę tego, gdzie dana instancja jest używana, pozwala operować na danym jednym obiekcie czy też będzie pomocne przy tworzeniu testów. Więcej o wzorcach, jak i samym wstrzykiwaniu, opowiem gdzieś dalej.

Destruktor działa odwrotnie, czyli wykonuje się, gdy obiekt jest niszczony. Samemu przeważnie się tego nie robi (chociaż teoretycznie można), więc z chwilą zakończenia wykonywania kodu wszystko jest niszczone. W praktyce destruktor nie jest zbytnio spotykany i używany.

<?php

class Person
{
    private string $name;
    private string $city;

    public function __construct(string $name, string $city)
    {
        $this->name = $name;
        $this->city = $city;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getCity(): string
    {
        return $this->city;
    }

    public function __destruct()
    {
        echo 'Person removed!';
    }
}

$person = new Person('Jan Kowalski', 'Warszawa');
echo 'Name: ' . $person->getName() . ', city: ' . $person->getCity(); // result: Name: Jan Kowalski, city: Warszawa

unset($person); // remove object, result: Person removed!

Uwaga

Dawniej konstruktor definiowany był poprzez utworzenie metody o nazwie identycznej, jak nazwa klasy. Jest to zasada bodajże z czasów php 4. Obecnie, od php >=7, taki zapis nie jest już w ogóle wspierany. Gdybyś jednak kiedyś musiał pracować przy bardzo starym kodzie, to warto o tym wiedzieć.

Zarówno konstruktor, jak i destruktor nie mogą niczego zwracać (void). Możemy w nich wykonać jakieś operacje, użyć $this, przypisać otrzymane argumenty, ale nie możemy wykorzystać return do zwrócenia danych, nie byłoby nawet za bardzo gdzie ich odczytać.

Warto jeszcze wspomnieć, że php >=8.0 oferuje funkcjonalność, która umożliwia automatyczne deklarowanie właściwości na bazie przekazanych argumentów. Wystarczy tylko dodać im modyfikator widoczności. Jest to po prostu skrócenie zapisu, ale jakże wygodne. Początek powyższego przykładu, czyli:

<?php

class Person
{
    private string $name;
    private string $city;

    public function __construct(string $name, string $city)
    {
        $this->name = $name;
        $this->city = $city;
    }
}

…możemy zamienić na:

<?php

class Person
{
    public function __construct(private string $name, private string $city) {}
}

…co spowoduje automatyczne zadeklarowanie w obiekcie właściwości o nazwach name i city z wybranym modyfikatorem oraz przypisanie im podanych wartości. Z pewnością dalej będzie jeszcze okazja pokazać to wszystko w praktyce. Należy pamiętać, że ten zapis zadziała wyłącznie dla konstruktora.

__get i __set

Metody te wykonują się, gdy odwołujemy się do nieistniejącej właściwości obiektu. __get przy próbie odczytania wartości (przyjmuje jeden argument, którym jest nazwa tejże właściwości), a __set w momencie zmiany nieistniejącej właściwości (przyjmuje dwa argumenty - nazwę i wartość).

<?php

class Person
{
    public function __get($name)
    {
        return false;
    }

    public function __set($name, $value)
    {
        $this->$name = $value;
    }
}

$person = new Person();
$person->city = 'Poznań';
echo 'Name: ' . $person->name . ', city: ' . $person->city; // result: Name: , city: Poznań

Dzięki tym metodom możemy na przykład dynamicznie tworzyć nowe właściwości dla naszego obiektu, jak w przykładzie powyżej. Brzmi jak fajne ułatwienie, ale w praktyce nie do końca tak jest. Przez takie automatyczne tworzenie właściwości możemy utracić kontrolę nad tym, co właściwie będzie działo się z obiektem, bo każdy może do niego dopisać, co tylko chce. Z tego też powodu należy tych metod używać z rozwagą. Powiedziałbym nawet, że powinny być używane tylko tam, gdzie to naprawdę konieczne, a i są teorie, że w ogóle nie powinny być używane. Wiedzieć, że istnieją z pewnością warto.

__call i __callStatic

Powyżej było coś do nieistniejących właściwości, to może i coś do metod by się przydało? Oto jest. __call uruchamia się w momencie odwołania do nieistniejącej metody. Dostajemy do dyspozycji dwa argumenty: pierwszy z jej nazwą, drugi z tablicą zawierającą przesłane do wywoływanej metody argumenty. __callStatic działa identycznie, z tym że obsługuje nieistniejące metody statyczne.

W przypadku tych magicznych metod również należy mieć na uwadze słowa, które padły kilka linii wyżej: automatyczne wywoływanie pewnych akcji, gdy metoda nie istnieje, może być przydatne, ale rodzi też niebezpieczeństwo przypadkowego wykonania pewnych rzeczy w niezamierzonym miejscu.

<?php

class Person
{
    public function __call($name, $arguments): string
    {
        return 'Method: ' . $name;
    }

    public static function __callStatic($name, $arguments): string
    {
        return 'Static method: ' . $name;
    }
}

$person = new Person();
echo $person->getName(); // result: Method: getMethod
echo $person::getName(); // result: Static method: getMethod

__toString

Tej metody możemy użyć, aby w momencie próby wyświetlania całego obiektu został zwrócony jakiś string. Standardowo oczywiście taka próba zakończyłaby się błędem, a dzięki temu sposobowi możemy tego w razie potrzeby dokonać. Również nie jest to zbyt często spotykane i nie powinno być używane jako opcja na typowe wyświetlanie czy konwertowanie danych.

<?php

class Person
{
    public function __toString(): string
    {
        return 'person';
    }
}

echo new Person(); // result: person

Ważna uwaga

Metody magiczne muszą być publiczne (modyfikator public). W innym przypadku kod po prostu nie zadziała i dostaniemy odpowiedni błąd.

Oprócz wymienionych powyżej istnieją jeszcze następujące magiczne metody:

  • __isset - wywoływana, gdy przy użyciu isset() lub empty() próbujemy sprawdzić nieistniejącą właściwość obiektu
  • __unset - wywoływana przy próbie usunięcia nieistniejącej właściwości
  • __sleep i __wakeup oraz __serialize php >=7.4 i __unserialize php >=7.4 - używane do serializacji/deserializacji obiektu
  • __clone - metoda uruchamiana po sklonowaniu obiektu
  • __invoke - daje możliwość wykonania obiektu jako funkcji
  • __set_state - metoda statyczna, używana z var_export()
  • __debugInfo - określa, co będzie zwrócone w momencie użycia var_dump()

Nie będę na razie więcej wyjaśniał. Poznałeś podstawowe metody magiczne, w razie czego więcej w manualu. No i pamiętaj o ich rozważnym używaniu ;) Z doświadczenia mogę powiedzieć tyle, że najbardziej powszechny i regularnie używany jest konstruktor, o pozostałych myślałbym bardziej okazyjnie i gdy są naprawdę potrzebne.

Dokumentacja: www.php.net/manual/en/language.oop5.magic.php, www.php.net/manual/en/language.oop5.decon.php

Poprzednia lekcja Następna lekcja

Udostępnij

  • Facebook
  • Twitter

Komentarze