Магические методы — методы, которые переопределяют действие PHP по умолчанию,
когда над объектом выполняются отдельные действия.
Предостережение
Имена методов, которые начинаются с двух символов подчёркивания __, зарезервированы PHP.
Не рекомендуется использовать имена методов с символами __ в PHP, если не требуется
магическая функциональность.
Если в определении магического метода указали объявления типа, они должны повторять сигнатуру,
которую описывает этот документ.
Иначе возникает фатальная ошибка.
До PHP 8.0.0 диагностические сообщения не отправлялись.
Однако методы __construct()
и __destruct() не должны объявлять возвращаемый тип;
иначе возникает фатальная ошибка.
Функция serialize() проверяет, определили ли
в классе метод с магическим именем __sleep().
Если метод определили, он выполняется перед сериализацией. Метод может
очистить объект и должен вернуть массив с именами
всех переменных этого объекта, которые должны быть сериализованы.
Если метод ничего не возвращает, то сериализуется константа null
и выдаётся предупреждение E_NOTICE.
Замечание:
Методу __sleep() нельзя
возвращать имена закрытых свойств в родительских классах. Это
приведёт к ошибке уровня E_NOTICE.
Вместо этого используйте метод
__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() проверяет, определили ли в классе
метод с магическим именем __serialize(). Если метод определили,
функция выполняется перед сериализацией. Метод должен создать и вернуть ассоциативный массив пар ключ и значение,
которые представляют сериализованную форму объекта.
Если метод не вернул массив, выбрасывается исключение TypeError.
Замечание:
Если в одном и том же объекте определили оба метода —
и __serialize(), и __sleep(),
PHP вызовет только метод __serialize().
Метод __sleep() проигнорируется.
Если объект реализует интерфейс Serializable,
PHP проигнорирует интерфейсный метод serialize(), и вместо него
будет использовать метод __serialize().
Назначение метода __serialize() заключается в определении удобного для сериализации
произвольного представления объекта. Элементам массива разрешается соответствовать свойствам объекта, но это не обязательно.
И наоборот, функция unserialize() проверяет
присутствие магического метода __unserialize().
Если метод определили, PHP передаст методу массив, который восстановил и вернул метод
__serialize(). Затем, если нужно,
метод восстанавливает свойства объекта из этого массива.
Метод __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; } }
Замечание:
При экспорте объекта функция var_export() не проверяет,
реализует ли класс объекта метод __set_state(),
поэтому повторный импорт объектов выбросит исключение Error,
если метод __set_state() не реализовали. В частности, это относится к ряду внутренних классов.
Программист несет ответственность за то, чтобы повторно
импортировались только те объекты, класс которых реализует метод __set_state().
Этот метод вызывается функцией var_dump(), когда
необходимо вывести список свойств объекта. Если этот метод не определили,
выводится каждое свойство объекта c модификаторами public, protected и private.
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.
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.
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;
}
}
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); } }; ?>
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; } }
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).
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.
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);
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; } }