Les méthodes magiques sont des méthodes spéciales qui écrasent l'action
par défaut de PHP quand certaines actions sont réalisées sur un objet.
Attention
Toutes les méthodes commençant par __ sont réservées par PHP.
Ainsi, il n'est pas recommandé d'utiliser un tel nom de méthode sauf lors
de l'écrasage du comportement de PHP.
Si des déclarations de types sont utilisées dans la définition d'une méthode
magique, elles doivent être identiques à la signature décrite dans ce document.
Sinon, une erreur fatale est émise.
Antérieur à PHP 8.0.0, aucun diagnostic n'était émis.
Cependant, __construct() et
__destruct() ne doivent pas déclarer
un type de retour ; sinon une erreur fatale est émise.
serialize() vérifie si la classe a une méthode avec le
nom magique __sleep().
Si c'est le cas, cette méthode sera exécutée avant toute sérialisation. Elle peut
nettoyer l'objet, et elle est supposée retourner un tableau avec les noms de toutes
les variables de l'objet qui doivent être sérialisées.
Si la méthode ne retourne rien, alors null sera sérialisé, et une alerte de type
E_NOTICE sera émise.
Note:
Il n'est pas possible pour __sleep() de retourner
des noms de propriétés privées des classes parentes. Le faire
résultera en une erreur de niveau E_NOTICE.
Utilisez __serialize() à la place.
Note:
À partir de PHP 8.0.0, retourner une valeur qui n'est pas un tableau depuis
__sleep() émet un avertissement.
Auparavant une notice était émise.
Le but avoué de __sleep() est de valider des données en attente
ou d'effectuer des opérations de nettoyage.
De plus, cette fonction est utile si un objet très large n'a pas besoin
d'être sauvegardés dans sa totalité.
Réciproquement, la fonction unserialize() vérifie
la présence d'une méthode dont le nom est le nom magique
__wakeup(). Si elle est présente, cette fonction
peut reconstruire toute ressource que l'objet pourrait posséder.
Le but avoué de __wakeup() est de rétablir
toute connexion de base de données qui aurait été perdue
durant la sérialisation et d'effectuer des tâches de réinitialisation.
Exemple #1 Utilisation de sleep() et wakeup()
<?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() vérifie si la classe a une méthode avec le
nom magique __serialize().
Si c'est le cas, cette méthode sera exécutée avant toute sérialisation.
Elle doit construire et retourner un tableau associatif de paire clé/valeur
qui représente la forme sérialisée de l'objet. Si aucun tableau n'est
retournée une TypeError sera lancée.
Note:
Si __serialize() et
__sleep() sont toutes les deux définies
dans le même objet, alors seulement __serialize()
sera appelée.
__sleep() sera ignorée. Si l'objet
implémente l'interface Serializable,
la méthode serialize() de l'interface sera ignorée et
__serialize() sera utilisée à la place.
L'utilisation prévue de __serialize()
est de définir une représentation arbitraire de l'objet pour le sérialiser
facilement. Les éléments du tableau peuvent correspondre aux propriétés de
l'objet mais ceci n'est pas requis.
inversement, unserialize() vérifie la présence d'une
fonction avec le nom magique
__unserialize().
Si elle est présente, cette fonction recevra le tableau restauré renvoyé
par __serialize(). Il peut alors
restaurer les propriétés de l'objet depuis ce tableau comme approprié.
La méthode __toString() détermine comment l'objet
doit réagir lorsqu'il est traité comme une chaîne de caractères.
Par exemple, ce que echo $obj; affichera.
Avertissement
Un objet Stringablene sera pas accepté par une déclaration de type string si la
déclaration de type strict est activée.
Si un tel comportement est souhaité, la déclaration de type doit accepter
à la fois Stringable et string via un type union.
À partir de PHP 8.0.0, la valeur de retour suit les sémantiques standard
de PHP, signifiant que la valeur sera convertie en une string
si possible et si le
typage stricte
est désactivé.
À partir de PHP 8.0.0, toute classe qui contient une méthode
__toString() implémente aussi
implicitement l'interface Stringable,
et passera donc les vérifications de types pour cette interface.
Implémenter quand même explicitement l'interface est recommandé.
En PHP 7.4, la valeur de retour doit être une
string, sinon une Error est lancée.
Antérieur à PHP 7.4.0, la valeur de retour doit
être une string, sinon une E_RECOVERABLE_ERROR
fatale est émise.
Avertissement
Il était impossible de lancer une exception depuis la méthode
__toString() antérieur à PHP 7.4.0.
Cela entraînera une erreur fatale.
Exemple #3 Exemple simple
<?php // Déclaration d'une classe simple class ClasseTest { public $foo;
public function __construct($foo) { $this->foo = $foo; }
public function __toString() { return $this->foo; } }
$class = new ClasseTest('Bonjour'); echo $class; ?>
Note:
Lors de l'exportation d'un objet, var_export() ne
vérifie pas si __set_state() est
implémentée par la classe de l'objet, ainsi la réimportation d'objets
résultera en une exception Error,
si __set_state() n'est pas implémentée.
En particulier, cela affecte certaines classes internes.
Il est de la responsabilité du programmeur de vérifier que seuls les objets dont la classe implémente __set_state() seront ré-importés.
Cette méthode est appelée par var_dump() lors
du traitement d'un objet pour récupérer les propriétés qui
doivent être affichées. Si la méthode n'est pas définie dans un objet,
alors toutes les propriétés publiques, protégées et privées seront
affichées.
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; } }