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

Имена методов __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state() and __clone() зарезервированы для "магических" методов в PHP. Не стоит называть свои методы этими именами, если вы не хотите использовать их "магическую" функциональность.

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

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

__sleep() и __wakeup()

public array __sleep ( void )
void __wakeup ( void )

Функция serialize() проверяет, присутствует ли в вашем классе метод с "магическим" именем __sleep(). Если это так, то этот метод выполняется прежде любой операции сериализации. Он может очистить объект и предполагается, что будет возвращен массив с именами всех переменных объекта, который должен быть сериализован. Если метод ничего не возвращает кроме NULL, то это значит, что объект сериализован и выдается предупреждение E_NOTICE.

Замечание:

Недопустимо возвращать в __sleep() имена приватных свойств объекта в родительский класс. Это приведет к предупреждению E_NOTICE. Вместо этого вы можете использовать интерфейс Serializable.

Рекомендованное использование __sleep() состоит в завершении работы над данными, ждущими обработки или других подобных задач очистки. Кроме того, этот метод можно выполнять в тех случаях, когда нет необходимости сохранять полностью очень большие объекты.

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

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

Пример #1 Sleep и wakeup

<?php
class Connection
{
    protected 
$link;
    private 
$server$username$password$db;
    
    public function 
__construct($server$username$password$db)
    {
        
$this->server $server;
        
$this->username $username;
        
$this->password $password;
        
$this->db $db;
        
$this->connect();
    }
    
    private function 
connect()
    {
        
$this->link mysql_connect($this->server$this->username$this->password);
        
mysql_select_db($this->db$this->link);
    }
    
    public function 
__sleep()
    {
        return array(
'server''username''password''db');
    }
    
    public function 
__wakeup()
    {
        
$this->connect();
    }
}
?>

__toString()

public string __toString ( void )

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

Внимание

Нельзя бросить исключение из метода __toString(). Попытка это сделать закончится фатальной ошибкой.

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

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

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

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

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

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

Привет

Ранее, до PHP 5.2.0, метод __toString() вызывался только непосредственно в сочетании с функциями echo или print. Начиная с PHP 5.2.0, он вызывается в любом строчном контексте (например, в printf() с модификатором %s), но не в контекстах других типов (например, с %d модификатором). Начиная с PHP 5.2.0, преобразование объекта в строку при отсутствии метода __toString() вызывает ошибку E_RECOVERABLE_ERROR.

__invoke()

mixed __invoke ([ $... ] )

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

Замечание:

Данный метод доступен начиная с PHP 5.3.0.

Пример #3 Использование __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)

__set_state()

static object __set_state ( array $properties )

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

Параметр этого метода должен содержать массив, состоящий из экспортируемых свойств в виде array('property' => value, ...).

Пример #4 Использование __set_state() (начиная с PHP 5.1.0)

<?php

class A
{
    public 
$var1;
    public 
$var2;

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

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

eval(
'$b = ' var_export($atrue) . ';'); // $b = A::__set_state(array(
                                            //    'var1' => 5,
                                            //    'var2' => 'foo',
                                            // ));
var_dump($b);

?>

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

object(A)#2 (2) {
  ["var1"]=>
  int(5)
  ["var2"]=>
  string(3) "foo"
}

Коментарии

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.
2005-01-27 01:09:17
http://php5.kiev.ua/manual/ru/language.oop5.magic.html
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).
2005-08-13 21:26:21
http://php5.kiev.ua/manual/ru/language.oop5.magic.html
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);
    }
};
?>
2005-08-15 07:47:24
http://php5.kiev.ua/manual/ru/language.oop5.magic.html
Автор:
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->module1).' <--');
        print(
'<br />');
        print(
'<br />');
       
$this->test '123';
    }
   
    function 
__init()
    {
        print(
'Using of __init()!<br />');
        print(
'--> '.print_r($this->module1).' <--');
        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
2006-04-02 12:55:21
http://php5.kiev.ua/manual/ru/language.oop5.magic.html
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.
2008-10-03 10:26:53
http://php5.kiev.ua/manual/ru/language.oop5.magic.html
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 
extends {
}

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

eval(
'$new_b = ' var_export($btrue) . ';'); 
var_dump($new_b);
/*
object(A)#2 (1) {
  ["var1"]=>
  int(5)
}
*/
?>
2008-12-03 16:01:49
http://php5.kiev.ua/manual/ru/language.oop5.magic.html
__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
)
$
2017-05-30 16:27:10
http://php5.kiev.ua/manual/ru/language.oop5.magic.html
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";
2018-04-30 12:29:39
http://php5.kiev.ua/manual/ru/language.oop5.magic.html
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
2020-08-18 21:10:05
http://php5.kiev.ua/manual/ru/language.oop5.magic.html
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.
2023-06-18 16:26:21
http://php5.kiev.ua/manual/ru/language.oop5.magic.html

    Поддержать сайт на родительском проекте КГБ