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

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

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

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

Следующие названия методов считаются магическими: __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __serialize(), __unserialize(), __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() не должны объявлять возвращаемый тип; иначе возникает фатальная ошибка.

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

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

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

Замечание:

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

Замечание:

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

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

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

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

Пример #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
__sleep()
{
return array(
'dsn', 'username', 'password');
}

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

?>

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

public __serialize(): array
public __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.

Пример #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
__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();
}
}

?>

Метод __toString()

public __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()

__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 __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()

__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)
}
Добавить

Примечания пользователей 10 notes

up
49
jon at webignition dot net
16 years ago
The __toString() method is extremely useful for converting class attribute names and values into common string representations of data (of which there are many choices). I mention this as previous references to __toString() refer only to debugging uses.

I have previously used the __toString() method in the following ways:

- representing a data-holding object as:
- XML
- raw POST data
- a GET query string
- header name:value pairs

- representing a custom mail object as an actual email (headers then body, all correctly represented)

When creating a class, consider what possible standard string representations are available and, of those, which would be the most relevant with respect to the purpose of the class.

Being able to represent data-holding objects in standardised string forms makes it much easier for your internal representations of data to be shared in an interoperable way with other applications.
up
8
tyler at nighthound dot us
1 year ago
Please note that as of PHP 8.2 implementing __serialize() has no control over the output of json_encode(). you still have to implement JsonSerializable.
up
18
jsnell at e-normous dot com
16 years ago
Be very careful to define __set_state() in classes which inherit from a parent using it, as the static __set_state() call will be called for any children. If you are not careful, you will end up with an object of the wrong type. Here is an example:

<?php
class A
{
public
$var1;

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

class
B extends A {
}

$b = new B;
$b->var1 = 5;

eval(
'$new_b = ' . var_export($b, true) . ';');
var_dump($new_b);
/*
object(A)#2 (1) {
["var1"]=>
int(5)
}
*/
?>
up
11
kguest at php dot net
7 years ago
__debugInfo is also utilised when calling print_r on an object:

$ cat test.php
<?php
class FooQ {

private
$bar = '';

public function
__construct($val) {

$this->bar = $val;
}

public function
__debugInfo()
{
return [
'_bar' => $this->bar];
}
}
$fooq = new FooQ("q");
print_r ($fooq);

$
php test.php
FooQ Object
(
[
_bar] => q
)
$
up
8
martin dot goldinger at netserver dot ch
19 years ago
When you use sessions, its very important to keep the sessiondata small, due to low performance with unserialize. Every class shoud extend from this class. The result will be, that no null Values are written to the sessiondata. It will increase performance.

<?
class BaseObject
{
function __sleep()
{
$vars = (array)$this;
foreach ($vars as $key => $val)
{
if (is_null($val))
{
unset($vars[$key]);
}
}
return array_keys($vars);
}
};
?>
up
7
daniel dot peder at gmail dot com
6 years ago
http://sandbox.onlinephpfunctions.com/code/4d2cc3648aed58c0dad90c7868173a4775e5ba0c

IMHO a bug or need feature change

providing a object as a array index doesn't try to us __toString() method so some volatile object identifier is used to index the array, which is breaking any persistency. Type hinting solves that, but while other than "string" type hinting doesn't work on ob jects, the automatic conversion to string should be very intuitive.

PS: tried to submit bug, but withot patch the bugs are ignored, unfortunately, I don't C coding

<?php

class shop_product_id {

protected
$shop_name;
protected
$product_id;

function
__construct($shop_name,$product_id){
$this->shop_name = $shop_name;
$this->product_id = $product_id;
}

function
__toString(){
return
$this->shop_name . ':' . $this->product_id;
}
}

$shop_name = 'Shop_A';
$product_id = 123;
$demo_id = $shop_name . ':' . $product_id;
$demo_name = 'Some product in shop A';

$all_products = [ $demo_id => $demo_name ];
$pid = new shop_product_id( $shop_name, $product_id );

echo
"with type hinting: ";
echo (
$demo_name === $all_products[(string)$pid]) ? "ok" : "fail";
echo
"\n";

echo
"without type hinting: ";
echo (
$demo_name === $all_products[$pid]) ? "ok" : "fail";
echo
"\n";
up
5
ctamayo at sitecrafting dot com
4 years ago
Due to a bug in PHP <= 7.3, overriding the __debugInfo() method from SPL classes is silently ignored.

<?php

class Debuggable extends ArrayObject {
public function
__debugInfo() {
return [
'special' => 'This should show up'];
}
}

var_dump(new Debuggable());

// Expected output:
// object(Debuggable)#1 (1) {
// ["special"]=>
// string(19) "This should show up"
// }

// Actual output:
// object(Debuggable)#1 (1) {
// ["storage":"ArrayObject":private]=>
// array(0) {
// }
// }

?>

Bug report: https://bugs.php.net/bug.php?id=69264
up
5
jeffxlevy at gmail dot com
19 years ago
Intriguing what happens when __sleep() and __wakeup() and sessions() are mixed. I had a hunch that, as session data is serialized, __sleep would be called when an object, or whatever, is stored in _SESSION. true. The same hunch applied when session_start() was called. Would __wakeup() be called? True. Very helpful, specifically as I'm building massive objects (well, lots of simple objects stored in sessions), and need lots of automated tasks (potentially) reloaded at "wakeup" time. (for instance, restarting a database session/connection).
up
5
ddavenport at newagedigital dot com
20 years ago
One of the principles of OOP is encapsulation--the idea that an object should handle its own data and no others'. Asking base classes to take care of subclasses' data, esp considering that a class can't possibly know how many dozens of ways it will be extended, is irresponsible and dangerous.

Consider the following...

<?php
class SomeStupidStorageClass
{
public function
getContents($pos, $len) { ...stuff... }
}

class
CryptedStorageClass extends SomeStupidStorageClass
{
private
$decrypted_block;
public function
getContents($pos, $len) { ...decrypt... }
}
?>

If SomeStupidStorageClass decided to serialize its subclasses' data as well as its own, a portion of what was once an encrypted thingie could be stored, in the clear, wherever the thingie was stored. Obviously, CryptedStorageClass would never have chosen this...but it had to either know how to serialize its parent class's data without calling parent::_sleep(), or let the base class do what it wanted to.

Considering encapsulation again, no class should have to know how the parent handles its own private data. And it certainly shouldn't have to worry that users will find a way to break access controls in the name of convenience.

If a class wants both to have private/protected data and to survive serialization, it should have its own __sleep() method which asks the parent to report its own fields and then adds to the list if applicable. Like so....

<?php

class BetterClass
{
private
$content;

public function
__sleep()
{
return array(
'basedata1', 'basedata2');
}

public function
getContents() { ...stuff... }
}

class
BetterDerivedClass extends BetterClass
{
private
$decrypted_block;

public function
__sleep()
{
return
parent::__sleep();
}

public function
getContents() { ...decrypt... }
}

?>

The derived class has better control over its data, and we don't have to worry about something being stored that shouldn't be.
up
4
rayRO
19 years ago
If you use the Magical Method '__set()', be shure that the call of
<?php
$myobject
->test['myarray'] = 'data';
?>
will not appear!

For that u have to do it the fine way if you want to use __set Method ;)
<?php
$myobject
->test = array('myarray' => 'data');
?>

If a Variable is already set, the __set Magic Method already wont appear!

My first solution was to use a Caller Class.
With that, i ever knew which Module i currently use!
But who needs it... :]
There are quiet better solutions for this...
Here's the Code:

<?php
class Caller {
public
$caller;
public
$module;

function
__call($funcname, $args = array()) {
$this->setModuleInformation();

if (
is_object($this->caller) && function_exists('call_user_func_array'))
$return = call_user_func_array(array(&$this->caller, $funcname), $args);
else
trigger_error("Call to Function with call_user_func_array failed", E_USER_ERROR);

$this->unsetModuleInformation();
return
$return;
}

function
__construct($callerClassName = false, $callerModuleName = 'Webboard') {
if (
$callerClassName == false)
trigger_error('No Classname', E_USER_ERROR);

$this->module = $callerModuleName;

if (
class_exists($callerClassName))
$this->caller = new $callerClassName();
else
trigger_error('Class not exists: \''.$callerClassName.'\'', E_USER_ERROR);

if (
is_object($this->caller))
{
$this->setModuleInformation();
if (
method_exists($this->caller, '__init'))
$this->caller->__init();
$this->unsetModuleInformation();
}
else
trigger_error('Caller is no object!', E_USER_ERROR);
}

function
__destruct() {
$this->setModuleInformation();
if (
method_exists($this->caller, '__deinit'))
$this->caller->__deinit();
$this->unsetModuleInformation();
}

function
__isset($isset) {
$this->setModuleInformation();
if (
is_object($this->caller))
$return = isset($this->caller->{$isset});
else
trigger_error('Caller is no object!', E_USER_ERROR);
$this->unsetModuleInformation();
return
$return;
}

function
__unset($unset) {
$this->setModuleInformation();
if (
is_object($this->caller)) {
if (isset(
$this->caller->{$unset}))
unset(
$this->caller->{$unset});
}
else
trigger_error('Caller is no object!', E_USER_ERROR);
$this->unsetModuleInformation();
}

function
__set($set, $val) {
$this->setModuleInformation();
if (
is_object($this->caller))
$this->caller->{$set} = $val;
else
trigger_error('Caller is no object!', E_USER_ERROR);
$this->unsetModuleInformation();
}

function
__get($get) {
$this->setModuleInformation();
if (
is_object($this->caller)) {
if (isset(
$this->caller->{$get}))
$return = $this->caller->{$get};
else
$return = false;
}
else
trigger_error('Caller is no object!', E_USER_ERROR);
$this->unsetModuleInformation();
return
$return;
}

function
setModuleInformation() {
$this->caller->module = $this->module;
}

function
unsetModuleInformation() {
$this->caller->module = NULL;
}
}

// Well this can be a Config Class?
class Config {
public
$module;

public
$test;

function
__construct()
{
print(
'Constructor will have no Module Information... Use __init() instead!<br />');
print(
'--> '.print_r($this->module, 1).' <--');
print(
'<br />');
print(
'<br />');
$this->test = '123';
}

function
__init()
{
print(
'Using of __init()!<br />');
print(
'--> '.print_r($this->module, 1).' <--');
print(
'<br />');
print(
'<br />');
}

function
testFunction($test = false)
{
if (
$test != false)
$this->test = $test;
}
}

echo(
'<pre>');
$wow = new Caller('Config', 'Guestbook');
print_r($wow->test);
print(
'<br />');
print(
'<br />');
$wow->test = '456';
print_r($wow->test);
print(
'<br />');
print(
'<br />');
$wow->testFunction('789');
print_r($wow->test);
print(
'<br />');
print(
'<br />');
print_r($wow->module);
echo(
'</pre>');
?>

Outputs something Like:

Constructor will have no Module Information... Use __init() instead!
--> <--

Using of __init()!
--> Guestbook <--

123

456

789

Guestbook
To Top