PHPCon Poland 2024

Méthodes magiques

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.

Les méthodes suivantes sont considérées magiques : __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __serialize(), __unserialize(), __toString(), __invoke(), __set_state() __clone(), et __debugInfo().

Avertissement

Toutes les méthodes magiques, à l'exception de __construct(), __destruct(), et __clone(), doivent être déclarées en tant que public, sinon une E_WARNING est émise. Antérieur à PHP 8.0.0, aucun diagnostic n'était émis pour les méthodes magiques __sleep(), __wakeup(), __serialize(), __unserialize(), et __set_state().

Avertissement

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.

__sleep() et __wakeup()

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

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() et __unserialize()

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

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é.

Note:

Si __unserialize() et __wakeup() sont toutes les deux définies dans le même objet, alors seulement __unserialize() sera appelée. __wakeup() sera ignorée.

Note:

Cette fonctionnalité est disponible à partir de PHP 7.4.0.

Exemple #2 Serialize et unserialize

<?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

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 Stringable ne 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;
?>

L'exemple ci-dessus va afficher :

Bonjour

__invoke()

__invoke( ...$values): mixed

La méthode __invoke() est appelée lorsqu'un script tente d'appeler un objet comme une fonction.

Exemple #4 Exemple avec __invoke()

<?php
class CallableClass
{
public function
__invoke($x)
{
var_dump($x);
}
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
?>

L'exemple ci-dessus va afficher :

int(5)
bool(true)

Exemple #5 Exemple avec __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']
];

// trier les clients par prénom
usort($customers, new Sort('first_name'));
print_r($customers);

// trier les clients par nom de famille
usort($customers, new Sort('last_name'));
print_r($customers);
?>

L'exemple ci-dessus va afficher :

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

Cette méthode statique est appelée pour les classes exportées par la fonction var_export().

Le seul paramètre de cette méthode est un tableau contenant les propriétés exportées sous la forme ['property' => value, ...].

Exemple #6 Utilisation de __set_state()

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);
?>

L'exemple ci-dessus va afficher :

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

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.

__debugInfo()

__debugInfo(): array

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.

Exemple #7 Utilisation de __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));
?>

L'exemple ci-dessus va afficher :

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

User Contributed Notes 10 notes

up
49
jon at webignition dot net
15 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
3
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
16
jsnell at e-normous dot com
15 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
10
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
5
ctamayo at sitecrafting dot com
3 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
6
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
7
rayRO
18 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
up
6
martin dot goldinger at netserver dot ch
18 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
4
jeffxlevy at gmail dot com
18 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
19 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.
To Top