Files
2023-11-04 14:52:53 +00:00

631 lines
19 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!-- EN-Revision: 5e15a6c3e4d5819102361ae78e73c90a06238c8a Maintainer: yannick Status: ready -->
<!-- Reviewed: no -->
<sect1 xml:id="language.oop5.magic" xmlns="http://docbook.org/ns/docbook">
<title>Méthodes magiques</title>
<para>
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.
</para>
<caution>
<simpara>
Toutes les méthodes commençant par <literal>__</literal> 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.
</simpara>
</caution>
<para>
Les méthodes suivantes sont considérées magiques :
<!-- Should be an itemized list ? -->
<link linkend="object.construct">__construct()</link>,
<link linkend="object.destruct">__destruct()</link>,
<link linkend="object.call">__call()</link>,
<link linkend="object.callstatic">__callStatic()</link>,
<link linkend="object.get">__get()</link>,
<link linkend="object.set">__set()</link>,
<link linkend="object.isset">__isset()</link>,
<link linkend="object.unset">__unset()</link>,
<link linkend="object.sleep">__sleep()</link>,
<link linkend="object.wakeup">__wakeup()</link>,
<link linkend="object.serialize">__serialize()</link>,
<link linkend="object.unserialize">__unserialize()</link>,
<link linkend="object.tostring">__toString()</link>,
<link linkend="object.invoke">__invoke()</link>,
<link linkend="object.set-state">__set_state()</link>
<link linkend="object.clone">__clone()</link>, et
<link linkend="object.debuginfo">__debugInfo()</link>.
</para>
<warning>
<simpara>
Toutes les méthodes magiques, à l'exception de
<link linkend="object.construct">__construct()</link>,
<link linkend="object.destruct">__destruct()</link>, et
<link linkend="object.clone">__clone()</link>,
<emphasis>doivent</emphasis> être déclarées en tant que <literal>public</literal>,
sinon une <constant>E_WARNING</constant> est émise.
Antérieur à PHP 8.0.0, aucun diagnostic n'était émis pour les méthodes magiques
<link linkend="object.sleep">__sleep()</link>,
<link linkend="object.wakeup">__wakeup()</link>,
<link linkend="object.serialize">__serialize()</link>,
<link linkend="object.unserialize">__unserialize()</link>, et
<link linkend="object.set-state">__set_state()</link>.
</simpara>
</warning>
<warning>
<para>
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, <link linkend="object.construct">__construct()</link> et
<link linkend="object.destruct">__destruct()</link> ne doivent pas déclarer
un type de retour ; sinon une erreur fatale est émise.
</para>
</warning>
<sect2 xml:id="language.oop5.magic.sleep">
<title>
<link linkend="object.sleep">__sleep()</link> et
<link linkend="object.wakeup">__wakeup()</link>
</title>
<methodsynopsis xml:id="object.sleep">
<modifier>public</modifier> <type>array</type><methodname>__sleep</methodname>
<void/>
</methodsynopsis>
<methodsynopsis xml:id="object.wakeup">
<modifier>public</modifier> <type>void</type><methodname>__wakeup</methodname>
<void/>
</methodsynopsis>
<para>
<function>serialize</function> vérifie si la classe a une méthode avec le
nom magique <link linkend="object.sleep">__sleep()</link>.
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
<constant>E_NOTICE</constant> sera émise.
</para>
<note>
<para>
Il n'est pas possible pour <link linkend="object.sleep">__sleep()</link> de retourner
des noms de propriétés privées des classes parentes. Le faire
résultera en une erreur de niveau <constant>E_NOTICE</constant>.
Utilisez <link linkend="object.serialize">__serialize()</link> à la place.
</para>
</note>
<note>
<para>
À partir de PHP 8.0.0, retourner une valeur qui n'est pas un tableau depuis
<link linkend="object.sleep">__sleep()</link> émet un avertissement.
Auparavant une notice était émise.
</para>
</note>
<para>
Le but avoué de <link linkend="object.sleep">__sleep()</link> 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é.
</para>
<para>
Réciproquement, la fonction <function>unserialize</function> vérifie
la présence d'une méthode dont le nom est le nom magique
<link linkend="object.wakeup">__wakeup()</link>. Si elle est présente, cette fonction
peut reconstruire toute ressource que l'objet pourrait posséder.
</para>
<para>
Le but avoué de <link linkend="object.wakeup">__wakeup()</link> 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.
</para>
<example>
<title>Utilisation de sleep() et wakeup()</title>
<programlisting role="php">
<![CDATA[
<?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();
}
}
?>
]]>
</programlisting>
</example>
</sect2>
<sect2 xml:id="language.oop5.magic.serialize">
<title>
<link linkend="object.serialize">__serialize()</link> et
<link linkend="object.unserialize">__unserialize()</link>
</title>
<methodsynopsis xml:id="object.serialize">
<modifier>public</modifier> <type>array</type><methodname>__serialize</methodname>
<void/>
</methodsynopsis>
<methodsynopsis xml:id="object.unserialize">
<modifier>public</modifier> <type>void</type><methodname>__unserialize</methodname>
<methodparam><type>array</type><parameter>data</parameter></methodparam>
</methodsynopsis>
<para>
<function>serialize</function> vérifie si la classe a une méthode avec le
nom magique <link linkend="object.serialize">__serialize()</link>.
Si c'est le cas, cette méthode sera exécutée avant toute sérialisation.
Elle doit construire et retourner un &array; associatif de paire clé/valeur
qui représente la forme sérialisée de l'objet. Si aucun tableau n'est
retournée une <classname>TypeError</classname> sera lancée.
</para>
<note>
<para>
Si <link linkend="object.serialize">__serialize()</link> et
<link linkend="object.sleep">__sleep()</link> sont toutes les deux définies
dans le même objet, alors seulement <link linkend="object.serialize">__serialize()</link>
sera appelée.
<link linkend="object.sleep">__sleep()</link> sera ignorée. Si l'objet
implémente l'interface <link linkend="class.serializable">Serializable</link>,
la méthode <literal>serialize()</literal> de l'interface sera ignorée et
<link linkend="object.serialize">__serialize()</link> sera utilisée à la place.
</para>
</note>
<para>
L'utilisation prévue de <link linkend="object.serialize">__serialize()</link>
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.
</para>
<para>
inversement, <function>unserialize</function> vérifie la présence d'une
fonction avec le nom magique
<link linkend="object.unserialize">__unserialize()</link>.
Si elle est présente, cette fonction recevra le tableau restauré renvoyé
par <link linkend="object.serialize">__serialize()</link>. Il peut alors
restaurer les propriétés de l'objet depuis ce tableau comme approprié.
</para>
<note>
<para>
Si <link linkend="object.unserialize">__unserialize()</link> et
<link linkend="object.wakeup">__wakeup()</link> sont toutes les deux définies
dans le même objet, alors seulement <link linkend="object.unserialize">__unserialize()</link>
sera appelée.
<link linkend="object.wakeup">__wakeup()</link> sera ignorée.
</para>
</note>
<note>
<para>
Cette fonctionnalité est disponible à partir de PHP 7.4.0.
</para>
</note>
<example>
<title>Serialize et unserialize</title>
<programlisting role="php">
<![CDATA[
<?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();
}
}?>
]]>
</programlisting>
</example>
</sect2>
<sect2 xml:id="language.oop5.magic.tostring">
<title><link linkend="object.tostring">__toString()</link></title>
<methodsynopsis xml:id="object.tostring">
<modifier>public</modifier> <type>string</type><methodname>__toString</methodname>
<void/>
</methodsynopsis>
<para>
La méthode <link linkend="object.tostring">__toString()</link> détermine comment l'objet
doit réagir lorsqu'il est traité comme une chaîne de caractères.
Par exemple, ce que <literal>echo $obj;</literal> affichera.
</para>
<warning>
<para>
Un objet <interfacename>Stringable</interfacename>
<emphasis>ne</emphasis> sera pas accepté par une déclaration de type <type>string</type> si la
<link linkend="language.types.declarations.strict">déclaration de type strict</link> est activée.
Si un tel comportement est souhaité, la déclaration de type doit accepter
à la fois <interfacename>Stringable</interfacename> et <type>string</type> via un type union.
</para>
<para>
À 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 <type>string</type>
si possible et si le
<link linkend="language.types.declarations.strict">typage stricte</link>
est désactivé.
</para>
<para>
À partir de PHP 8.0.0, toute classe qui contient une méthode
<link linkend="object.tostring">__toString()</link> implémente aussi
implicitement l'interface <interfacename>Stringable</interfacename>,
et passera donc les vérifications de types pour cette interface.
Implémenter quand même explicitement l'interface est recommandé.
</para>
<para>
En PHP 7.4, la valeur de retour <emphasis>doit</emphasis> être une
<type>string</type>, sinon une <classname>Error</classname> est lancée.
</para>
<para>
Antérieur à PHP 7.4.0, la valeur de retour <emphasis>doit</emphasis>
être une <type>string</type>, sinon une <constant>E_RECOVERABLE_ERROR</constant>
fatale est émise.
</para>
</warning>
<warning>
<simpara>
Il était impossible de lancer une exception depuis la méthode
<link linkend="object.tostring">__toString()</link> antérieur à PHP 7.4.0.
Cela entraînera une erreur fatale.
</simpara>
</warning>
<example>
<title>Exemple simple</title>
<programlisting role="php">
<![CDATA[
<?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;
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Bonjour
]]>
</screen>
</example>
</sect2>
<sect2 xml:id="language.oop5.magic.invoke">
<title><link linkend="object.invoke">__invoke()</link></title>
<methodsynopsis xml:id="object.invoke">
<type>mixed</type><methodname>__invoke</methodname>
<methodparam rep="repeat"><parameter>values</parameter></methodparam>
</methodsynopsis>
<para>
La méthode <link linkend="object.invoke">__invoke()</link> est appelée lorsqu'un script tente
d'appeler un objet comme une fonction.
</para>
<example>
<title>Exemple avec <link linkend="object.invoke">__invoke()</link></title>
<programlisting role="php">
<![CDATA[
<?php
class CallableClass
{
public function __invoke($x)
{
var_dump($x);
}
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
int(5)
bool(true)
]]>
</screen>
</example>
<example>
<title>Exemple avec <link linkend="object.invoke">__invoke()</link></title>
<programlisting role="php">
<![CDATA[
<?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);
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
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
)
)
]]>
</screen>
</example>
</sect2>
<sect2 xml:id="language.oop5.magic.set-state">
<title><link linkend="object.set-state">__set_state()</link></title>
<methodsynopsis xml:id="object.set-state">
<modifier>static</modifier> <type>object</type><methodname>__set_state</methodname>
<methodparam><type>array</type><parameter>properties</parameter></methodparam>
</methodsynopsis>
<para>
Cette méthode <link linkend="language.oop5.static">statique</link> est appelée
pour les classes exportées par la fonction <function>var_export</function>.
</para>
<para>
Le seul paramètre de cette méthode est un tableau contenant les propriétés
exportées sous la forme <literal>['property' => value, ...]</literal>.
</para>
<example>
<title>Utilisation de <link linkend="object.set-state">__set_state()</link></title>
<programlisting role="php">
<![CDATA[
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);
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
string(60) "A::__set_state(array(
'var1' => 5,
'var2' => 'foo',
))"
object(A)#2 (2) {
["var1"]=>
int(5)
["var2"]=>
string(3) "foo"
}
]]>
</screen>
</example>
<note>
<simpara>
Lors de l'exportation d'un objet, <function>var_export</function> ne
vérifie pas si <link linkend="object.set-state">__set_state()</link> est
implémentée par la classe de l'objet, ainsi la réimportation d'objets
résultera en une exception <classname>Error</classname>,
si __set_state() n'est pas implémentée.
En particulier, cela affecte certaines classes internes.
</simpara>
<simpara>
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.
</simpara>
</note>
</sect2>
<sect2 xml:id="language.oop5.magic.debuginfo">
<title><link linkend="object.debuginfo">__debugInfo()</link></title>
<methodsynopsis xml:id="object.debuginfo">
<type>array</type><methodname>__debugInfo</methodname>
<void/>
</methodsynopsis>
<para>
Cette méthode est appelée par <function>var_dump</function> 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.
</para>
<example>
<title>Utilisation de <link linkend="object.debuginfo">__debugInfo()</link></title>
<programlisting role="php">
<![CDATA[
<?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));
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
object(C)#1 (1) {
["propSquared"]=>
int(1764)
}
]]>
</screen>
</example>
</sect2>
</sect1>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->