Магические методы

Магические методы — методы, которые переопределяют действие PHP по умолчанию, когда над объектом выполняются отдельные действия.

Предостережение

Названия методов, которые начинаются с двух символов подчёркивания __, зарезервировали в PHP, поэтому лучше не указывать методам такие названия, если только не требуется переопределить поведение PHP.

Следующие названия методов относятся к магическим: __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __serialize(), __unserialize(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone() и __debugInfo()

Внимание

Магические методы, за исключением __construct(), __destruct() и __clone(), ТРЕБУЕТСЯ объявлять с модификатором public, иначе PHP выдаст ошибку уровня E_WARNING. До PHP 8.0.0 для магических методов __sleep(), __wakeup(), __serialize(), __unserialize() и __set_state() не выполнялась проверка.

Внимание

При объявлении типов в определении магического метода требуется повторять сигнатуру, которую описывает этот документ, иначе возникнет фатальная ошибка. До PHP 8.0.0 диагностические сообщения не отправлялись. При этом в методах __construct() и __destruct() тип возврата не объявляют, иначе возникнет фатальная ошибка.

Методы __serialize() и __unserialize()

public function __serialize(): array
public function __unserialize(array $data): void

Функция serialize() проверяет, определили ли в классе магический метод с названием __serialize(). Метод, если его определили в классе, выполняется перед сериализацией. Цель метода — вернуть ассоциативный массив пар «ключ — значение» для представления объекта в сериализованной форме. При невозврате массива выбрасывается ошибка TypeError.

Замечание:

При определении в классе обоих методов — и __serialize(), и __sleep(), PHP вызовет только метод __serialize(). Метод __sleep() проигнорируется. PHP проигнорирует интерфейсный метод serialize(), и вместо него вызовет метод __serialize(), если класс реализует интерфейс Serializable.

Назначение метода __serialize() заключается в определении удобного для сериализации произвольного представления объекта. Элементам массива разрешается соответствовать свойствам объекта, но это не обязательно.

И наоборот, функция unserialize() проверяет доступность магического метода __unserialize(). PHP передаст методу массив, который восстановил и вернул метод __serialize(), если метод определили в классе. А затем, если потребуется, метод восстановит свойства объекта из этого массива.

Замечание:

При определении в классе обоих методов — и __unserialize(), и __wakeup(), PHP вызовет только метод __unserialize(), а метод __wakeup() проигнорируется.

Замечание:

Метод доступен с PHP 7.4.0.

Пример #1 Пример сериализации и десериализации

<?php

class Connection
{
    protected $link;
    private $dsn, $username, $password;

    public function __construct($dsn, $username, $password)
    {
        $this->dsn = $dsn;
        $this->username = $username;
        $this->password = $password;
        $this->connect();
    }

    private function connect()
    {
        $this->link = new PDO($this->dsn, $this->username, $this->password);
    }

    public function __serialize(): array
    {
        return [
          'dsn' => $this->dsn,
          'user' => $this->username,
          'pass' => $this->password,
        ];
    }

    public function __unserialize(array $data): void
    {
        $this->dsn = $data['dsn'];
        $this->username = $data['user'];
        $this->password = $data['pass'];

        $this->connect();
    }
}

?>

Методы __sleep() и __wakeup()

public function __sleep(): array
public function __wakeup(): void

Функция serialize() проверяет, определили ли в классе магический метод с названием __sleep(). Магический метод, если его определили, выполняется перед сериализацией. В методе очищают сериализуемый объект, если требуется, и возвращают из метода массив с названиями переменных объекта, которые требуется сериализовать. При невозврате из магического метода значения сериализуется константа null и выдаётся предупреждение E_NOTICE.

Замечание:

Методу __sleep() нельзя возвращать названия закрытых свойств родительских классов. Это сгенерирует ошибку уровня E_NOTICE. Для сериализации закрытых родительских свойств вместо магического метода __sleep вызывают магический метод __serialize().

Замечание:

Начиная с PHP 8.0.0 при возврате из метода __sleep() значения кроме массива генерируется предупреждение. Раньше выдавалось уведомление.

Назначение метода __sleep() — зафиксировать отложенные данные или выполнить аналогичные задачи очистки. Метод также будет полезным, когда требуется сохранить только часть объекта.

И наоборот, функция unserialize() проверяет в классе определение магического метода с названием __wakeup(). Методу пробуждения, если его определили в классе, доступно восстановление любых ресурсов, которые разрешается содержать объекту.

Назначение метода __wakeup() — восстановить соединения с базой данных, которые потерялись при сериализации, и выполнить другие задачи повторной инициализации.

Пример #2 Пример засыпания и пробуждения

<?php

class Connection
{
    protected $link;
    private $dsn, $username, $password;

    public function __construct($dsn, $username, $password)
    {
        $this->dsn = $dsn;
        $this->username = $username;
        $this->password = $password;
        $this->connect();
    }

    private function connect()
    {
        $this->link = new PDO($this->dsn, $this->username, $this->password);
    }

    public function __sleep()
    {
        return array('dsn', 'username', 'password');
    }

    public function __wakeup()
    {
        $this->connect();
    }
}

?>

Метод __toString()

public function __toString(): string

Метод __toString() разрешает классу выбирать, как класс будет реагировать, когда с ним обращаются как со строкой. Например, класс решает, что выведет выражение echo $obj;.

Внимание

С PHP 8.0.0 возвращаемое значение соответствует стандартной семантике PHP-типов, поэтому значение приводится к строке (string), если возможно и если отключили строгую типизацию.

Объект, который реализует интерфейс Stringable, не будет приниматься объявлением типа string, если включили строгую типизацию. Объявление типа должно принимать интерфейс Stringable и строку (string) через объединение типов, если требуется такое поведение.

С PHP 8.0.0 каждый класс, в котором описали магический метод __toString(), неявно реализует интерфейс Stringable, и поэтому проходит проверку типа для этого интерфейса. В определении класса рекомендуют явно указывать, что класс реализует интерфейс.

В PHP 7.4 значение возврата ДОЛЖНО принадлежать типу string, иначе выбрасывается ошибка Error.

До PHP 7.4.0 значение возврата должно было принадлежать типу string, иначе возникала фатальная ошибка уровня E_RECOVERABLE_ERROR.

Внимание

До PHP 7.4.0 возникала фатальная ошибка, если из метода __toString() выбрасывали исключение.

Пример #3 Простой пример

<?php

// Объявление простого класса
class TestClass
{
    public $foo;

    public function __construct($foo)
    {
        $this->foo = $foo;
    }

    public function __toString()
    {
        return $this->foo;
    }
}

$class = new TestClass('Привет');
echo $class;

?>

Результат выполнения приведённого примера:

Привет

Метод __invoke()

function __invoke( ...$values): mixed

Метод __invoke() вызывается, когда скрипт пытается выполнить объект как функцию.

Пример #4 Пример вызова объекта класса с методом __invoke()

<?php

class CallableClass
{
    public function __invoke($x)
    {
        var_dump($x);
    }
}

$obj = new CallableClass();

$obj(5);
var_dump(is_callable($obj));

?>

Результат выполнения приведённого примера:

int(5)
bool(true)

Пример #5 Пример вызова объекта класса с методом __invoke()

<?php

class Sort
{
    private $key;

    public function __construct(string $key)
    {
        $this->key = $key;
    }

    public function __invoke(array $a, array $b): int
    {
        return $a[$this->key] <=> $b[$this->key];
    }
}

$customers = [
    ['id' => 1, 'first_name' => 'John', 'last_name' => 'Do'],
    ['id' => 3, 'first_name' => 'Alice', 'last_name' => 'Gustav'],
    ['id' => 2, 'first_name' => 'Bob', 'last_name' => 'Filipe']
];

// Сортировка клиентов по имени
usort($customers, new Sort('first_name'));
print_r($customers);

// Сортировка клиентов по фамилии
usort($customers, new Sort('last_name'));
print_r($customers);

?>

Результат выполнения приведённого примера:

Array
(
    [0] => Array
        (
            [id] => 3
            [first_name] => Alice
            [last_name] => Gustav
        )

    [1] => Array
        (
            [id] => 2
            [first_name] => Bob
            [last_name] => Filipe
        )

    [2] => Array
        (
            [id] => 1
            [first_name] => John
            [last_name] => Do
        )

)
Array
(
    [0] => Array
        (
            [id] => 1
            [first_name] => John
            [last_name] => Do
        )

    [1] => Array
        (
            [id] => 2
            [first_name] => Bob
            [last_name] => Filipe
        )

    [2] => Array
        (
            [id] => 3
            [first_name] => Alice
            [last_name] => Gustav
        )

)

Метод __set_state()

static function __set_state(array $properties): object

Этот статический метод вызывается для тех классов, которые экспортируются функцией var_export().

Единственный параметр метода — массив, который содержит экспортируемые свойства в виде ['property' => value, ...].

Пример #6 Пример использования метода __set_state()

<?php

class A
{
    public $var1;
    public $var2;

    public static function __set_state($an_array)
    {
        $obj = new A;
        $obj->var1 = $an_array['var1'];
        $obj->var2 = $an_array['var2'];
        return $obj;
    }
}

$a = new A();
$a->var1 = 5;
$a->var2 = 'foo';

$b = var_export($a, true);
var_dump($b);
eval('$c = ' . $b . ';');
var_dump($c);

?>

Результат выполнения приведённого примера:

string(60) "A::__set_state(array(
   'var1' => 5,
   'var2' => 'foo',
))"
object(A)#2 (2) {
  ["var1"]=>
  int(5)
  ["var2"]=>
  string(3) "foo"
}

Замечание: При экспорте объекта функция var_export() не проверяет, реализует ли класс объекта метод __set_state(), поэтому повторный импорт объектов выбросит исключение Error, если метод __set_state() не реализовали. В частности, это относится к ряду внутренних классов. Программист несёт ответственность повторный импорт только тех объектов, класс которых реализует метод __set_state().

Метод __debugInfo()

function __debugInfo(): array

Этот метод вызывается функцией var_dump(), когда требуется вывести список свойств объекта. Функция выведет каждое объектное свойство c модификаторами public, protected и private, если метод не определили.

Пример #7 Пример вывода отладочной информации методом __debugInfo()

<?php

class C
{
    private $prop;

    public function __construct($val)
    {
        $this->prop = $val;
    }

    public function __debugInfo()
    {
        return [
            'propSquared' => $this->prop ** 2,
        ];
    }
}

var_dump(new C(42));

?>

Результат выполнения приведённого примера:

object(C)#1 (1) {
  ["propSquared"]=>
  int(1764)
}