Files
doc-fr/language/oop.xml
Yannick Torres 134266cd2e sync with EN
git-svn-id: https://svn.php.net/repository/phpdoc/fr/trunk@278265 c90b9560-bf6c-de11-be94-00142212c4b1
2009-04-05 20:20:34 +00:00

1149 lines
32 KiB
XML

<?xml version="1.0" encoding="iso-8859-1"?>
<!-- $Revision: 1.74 $ -->
<!-- EN-Revision: 1.72 Maintainer: yannick Status: ready -->
<!-- Reviewed: yes -->
<chapter xml:id="language.oop" xmlns="http://docbook.org/ns/docbook">
<title>Les classes et les objets (PHP 4)</title>
<sect1 xml:id="keyword.class">
<title>Les classes : <literal>class</literal></title>
<para>
Une classe est une collection de variables et de fonctions qui
fonctionnent avec ces variables. Les variables sont définies par
l'élément <literal>var</literal> et les fonctions par
<literal>function</literal>. Une classe est définie
en utilisant la syntaxe suivante :
</para>
<para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class Panier {
// Eléments de notre panier
var $items;
// Ajout de $num articles de type $artnr au panier
function add_item($artnr, $num) {
$this->items[$artnr] += $num;
}
// Suppression de $num articles du type $artnr du panier
function remove_item($artnr, $num) {
if ($this->items[$artnr] > $num) {
$this->items[$artnr] -= $num;
return true;
} elseif ($this->items[$artnr] == $num) {
unset($this->items[$artnr]);
return true;
} else {
return false;
}
}
}
?>
]]>
</programlisting>
</informalexample>
</para>
<para>
L'exemple ci-dessus définit la classe Panier qui est composée
d'un tableau associatif contenant les articles du panier et de deux
fonctions, une pour ajouter et une pour enlever des éléments
au panier.
</para>
<warning>
<simpara>
Vous <emphasis>NE POUVEZ PAS</emphasis> couper la définition d'une
classe en plusieurs fichiers. De la même façon, vous <emphasis>NE POUVEZ
PAS</emphasis> couper la définition d'une classe en de multiples blocs, à
moins que la coupure ne soit à l'intérieur de la déclaration d'une
méthode. Ce qui suit ne fonctionnera pas :
</simpara>
<para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class test {
?>
<?php
function test() {
print 'OK';
}
}
?>
]]>
</programlisting>
</informalexample>
</para>
<simpara>
Néanmoins, ce qui suit est autorisé :
</simpara>
<para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class test {
function test() {
?>
<?php
print 'OK';
}
}
?>
]]>
</programlisting>
</informalexample>
</para>
</warning>
<simpara>
Les notes suivantes ne sont valables que pour PHP 4.
</simpara>
<caution>
<simpara>
Le nom <literal>stdClass</literal> est utilisé en interne par
Zend et ne doit pas être utilisé. Vous ne pouvez pas nommer
une classe <literal>stdClass</literal> en PHP.
</simpara>
</caution>
<caution>
<simpara>
Les noms de fonctions <literal>__sleep</literal> et
<literal>__wakeup</literal> sont magiques en PHP. Vous ne pouvez
pas utiliser ces noms de fonctions dans vos classes, à moins
que vous ne souhaitiez utiliser la magie qui y est associée.
</simpara>
</caution>
<caution>
<simpara>
PHP se réserve l'usage de tous les noms de fonctions
commençant par <literal>__</literal>, pour sa propre magie.
Il est vivement recommandé de ne pas utiliser des noms de fonctions
commençant par <literal>__</literal>, à moins que vous ne
souhaitiez utiliser la magie qui y est associée.
</simpara>
</caution>
<simpara>
En PHP 4, seuls les initialiseurs constants pour les variables
<literal>var</literal> sont autorisés. Utilisez les
constructeurs pour les initialisations variables, ou utilisant
des expressions.
</simpara>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
/* Aucune de ces syntaxes ne fonctionnera en PHP 4 */
class Panier {
var $date_du_jour = date("d/m/Y");
var $name = $firstname;
var $owner = 'Fred ' . 'Jones';
var $items = array("DVD", "Télé","Magnétoscope");
}
/* Voici comment cela doit se faire désormais. */
class Panier {
var $date_du_jour;
var $name;
var $owner;
var $items;
function Panier() {
$this->date_du_jour = date("d/m/Y");
$this->name = $GLOBALS['firstname'];
/* etc. */
}
}
?>
]]>
</programlisting>
</informalexample>
<para>
Les classes forment un type de variable. Pour créer une variable
du type désiré, vous devez utiliser l'opérateur <literal>new</literal>.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
$cart = new Panier;
$cart->add_item("10", 1);
$another_cart = new Panier;
$another_cart->add_item("0815", 3);
?>
]]>
</programlisting>
</informalexample>
<para>
L'instruction ci-dessus crée l'objet <varname>$cart</varname> de la classe
<classname>Panier</classname>.
La fonction <literal>add_idem()</literal> est appelée afin d'ajouter
l'article numéro 10 dans le panier. Trois articles numéro 0815 sont
ajoutés au panier <varname>$another_cart</varname>.
</para>
<para>
<varname>$cart</varname> et <varname>$another_cart</varname> disposent des
fonctions <literal>add_item()</literal>, <literal>remove_item()</literal>
et de la variable <varname>items</varname>. Ce sont des fonctions
et variables distinctes. Vous pouvez vous représenter les
objets comme des dossiers sur votre disque dur. Vous pouvez
avoir deux fichiers <filename>lisez-moi.txt</filename> sur votre disque dur,
tant qu'ils ne sont pas dans le même répertoire. De même que vous
devez alors taper le chemin complet jusqu'au fichier, vous devez
spécifier le nom complet de la méthode avant de l'employer :
en termes PHP, le dossier racine est l'espace de nom global, et le
séparateur de dossier est <literal>-&gt;</literal>. Par exemple,
les noms <varname>$cart-&gt;items</varname> et
<varname>$another_cart-&gt;items</varname> représentent deux
variables distinctes. Notez que le nom de la variable est alors
<varname>$cart-&gt;items,</varname> et non pas
<varname>$cart-&gt;$items</varname> : il n'y a qu'un seul
signe <literal>$</literal> dans un nom de variable.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
// correct, le signe $ est unique
$cart->items = array("10" => 1);
// incorrect, car $cart->$items devient $cart->""
$cart->$items = array("10" => 1);
// correct, mais risque de ne pas se comporter comme prévu
// $cart->$myvar devient $cart->items
$myvar = 'items';
$cart->$myvar = array("10" => 1);
?>
]]>
</programlisting>
</informalexample>
<para>
À l'intérieur d'une définition de classe, vous ne savez pas le
nom de la variable à partir duquel l'objet sera accessible dans
le script. On ne peut prévoir que l'objet créé sera
affecté à la variable <varname>$cart</varname>,
<varname>$another_cart</varname> ou quelque chose d'autres. Donc, vous ne
pouvez pas utiliser la syntaxe <varname>$cart-&gt;items.</varname> Mais pour
pouvoir accéder aux méthodes et membres d'un objet, vous pouvez utiliser
la variable spéciale <varname>$this,</varname> qui peut s'interpréter comme
"moi-même", ou bien "l'objet courant". Par exemple,
<literal>'$this->items[$artnr] += $num;'</literal>
peut se lire comme 'ajouter <varname>$num</varname> au compteur
<varname>$artnr</varname> de mon propre tableau de compteur' ou bien
'ajouter <varname>$num</varname> au compteur <varname>$artnr</varname> du
tableau de compteurs de l'objet courant'.
</para>
<note>
<para>
La pseudo-variable <varname>$this</varname> n'est pas toujours définie si
la méthode dans laquelle elle est présente est appelée statiquement.
Cependant, ceci n'est pas une règle stricte : <varname>$this</varname>
est définie si une méthode est appelée statiquement depuis un autre objet.
Dans ce cas, sa valeur vaut l'objet appelé. Ce comportement est illustré
dans l'exemple ci-dessous :
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class A
{
function foo()
{
if (isset($this)) {
echo '$this est défini (';
echo get_class($this);
echo ")\n";
} else {
echo "\$this n'est pas défini.\n";
}
}
}
class B
{
function bar()
{
A::foo();
}
}
$a = new A();
$a->foo();
A::foo();
$b = new B();
$b->bar();
B::bar();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
$this est défini (a)
$this n'est pas défini.
$this est défini (b)
$this n'est pas défini.
]]>
</screen>
</informalexample>
</para>
</note>
<note>
<para>
Il y a des fonctions très pratiques pour gérer les classes et objets.
Vous pouvez étudier le chapitre sur les
<link linkend="ref.classobj">fonctions de classes et objets</link>.
</para>
</note>
</sect1>
<sect1 xml:id="keyword.extends">
<title><literal>extends</literal> : héritage</title>
<para>
Souvent, vous aurez besoin d'une classe avec des méthodes et
fonctions similaires à une autre classe. En fait, il est bon
de définir des classes génériques, qui pourront
être réutilisées et adaptées à tous
vos projets. Pour faciliter cela, une classe peut être une extension
d'une autre classe. La classe dérivée hérite alors de
toutes les méthodes et variables de la classe de base (cet
héritage a de bien que personne ne meurt pour en profiter), mais peut
définir ses propres fonctions et variables, qui s'ajouteront. Une
classe ne peut hériter que d'une seule autre classe, et l'héritage
multiple n'est pas supporté. Les héritages se font avec le mot
clé '<literal>extends</literal>'.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class Panier_nomme extends Panier {
var $owner;
function set_owner ($name) {
$this->owner = $name;
}
}
?>
]]>
</programlisting>
</informalexample>
<para>
L'exemple ci-dessus définit la classe <classname>Panier_nomme</classname>
qui possède les mêmes variables que la classe Panier et
la variable <varname>$owner</varname> en plus, ainsi que la fonction
<literal>set_owner()</literal>.
Vous créez un panier nominatif de la même
manière que précédemment, et vous pouvez alors
affecter un nom au panier ou en connaître le nom. Vous pouvez de
toutes les façons utiliser les mêmes fonctions que sur un
panier classique.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
$ncart = new Panier_nomme; // Création d'un panier nominatif
$ncart->set_owner ("kris"); // Affectation du nom du panier
print $ncart->owner; // Affichage du nom du panier
$ncart->add_item ("10", 1); // (héritage des fonctions de la classe père)
?>
]]>
</programlisting>
</informalexample>
<para>
Ceci est également appelé une relation "parent-enfant". Vous créez une
classe parent, et utilisez <literal>extends</literal> pour créer une
nouvelle classe <emphasis>basée</emphasis> sur la classe parent : la
classe enfant. Vous pouvez toujours utiliser cette nouvelle classe enfant
et en créer une nouvelle basée sur cette classe enfant.
</para>
<note>
<para>
Les classes doivent être définies avant d'être utilisées ! Si vous voulez
que la classe <literal>Named_Cart</literal> étende la classe
<literal>Cart</literal>, vous devez d'abord définir la classe
<literal>Cart</literal>. Si vous voulez créer une autre classe appelée
<literal>Yellow_named_cart</literal>, basée sur la classe
<literal>Named_Cart</literal>, vous devez d'abord définir la classe
<literal>Named_Cart</literal>. Pour faire court : l'ordre dans lequel
les classes sont définies est important.
</para>
</note>
</sect1>
<sect1 xml:id="language.oop.constructor">
<title>Constructeur</title>
<para>
Le constructeur est la fonction qui est appelée
automatiquement par la classe lorsque vous créez
une nouvelle instance d'une classe à l'aide de l'opérateur
<literal>new</literal>. La fonction constructeur a
le même nom que la classe.
Une fonction devient le constructeur si elle porte le
même nom que la classe. Si une classe n'a pas de constructeur,
le constructeur de la classe de base sera appelé, s'il existe.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class Auto_Panier extends Panier {
function Auto_Panier () {
$this->add_item ("10", 1);
}
}
?>
]]>
</programlisting>
</informalexample>
<para>
L'exemple ci-dessus définit la classe <classname>Auto_Panier</classname>
qui hérite de la classe Panier et définit le constructeur
de la classe. Ce dernier initialise le panier avec 1 article de
type numéro 10 dès que l'instruction <literal>new</literal> est
appelée. La fonction constructeur peut prendre ou non
des paramètres optionnels, ce qui la rend beaucoup plus
pratique. Pour pouvoir utiliser cette classe sans paramètre,
tous les paramètres du constructeurs devraient être optionnels,
en fournissant une valeur par défaut, comme ci-dessous.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class Constructor_Cart extends Cart {
function Constructor_Cart($item = "10", $num = 1) {
$this->add_item ($item, $num);
}
}
// Création du Panier
$default_cart = new Constructor_Cart;
// Création d'un vrai Panier
$different_cart = new Constructor_Cart("20", 17);
?>
]]>
</programlisting>
</informalexample>
<para>
Vous pouvez également utiliser l'opérateur <literal>@</literal> pour
<emphasis>empêcher</emphasis> les erreurs survenant dans le constructeur
de s'afficher, e.g. <literal>@new</literal>.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class A
{
function A()
{
echo "Je suis le constructeur de A.<br />\n";
}
function B()
{
echo "Je suis une fonction standard appelée B dans la classe A.<br />\n";
echo "Je ne suis pas le constructeur de A.<br />\n";
}
}
class B extends A
{
}
// Cette syntaxe va appeler B() comme constructeur.
$b = new B;
?>
]]>
</programlisting>
</informalexample>
<para>
La fonction B() de la classe A va soudainement devenir le
constructeur de la classe B, bien qu'il n'ait pas été
prévu pour. PHP 4 ne se soucie guère si la fonction est
définie dans la classe B ou si elle a été héritée.
</para>
<caution>
<simpara>
PHP n'appelle pas automatiquement le constructeur de
la classe supérieure depuis le constructeur de la classe
dérivée. Il est de votre responsabilité de propager
l'appel des constructeurs.
</simpara>
</caution>
<para>
Les destructeurs sont des fonctions qui sont appelées lorsqu'un
objet est détruit, soit avec la fonction <function>unset</function>
soit par simple sortie d'une fonction (cas des variables locales).
Il n'y a pas de destructeurs en PHP. Vous devez utiliser la
fonction <function>register_shutdown_function</function>
à la place pour simuler la plupart des effets des destructeurs.
</para>
</sect1>
<sect1 xml:id="keyword.paamayim-nekudotayim"><!-- :-) -->
<title>Opérateur de contexte de classe (<literal>::</literal>)</title>
<caution>
<simpara>
La documentation suivante n'est valable que pour PHP 4 et plus récent.
</simpara>
</caution>
<para>
Parfois, il est pratique de faire référence aux fonctions
et variables d'une classe de base, ou bien d'utiliser
des méthodes de classes qui n'ont pas encore d'objets
créés. L'opérateur <literal>::</literal> est là pour ces
situations.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class A {
function example() {
echo "Je suis la fonction originale A::example().<br />\n";
}
}
class B extends A {
function example() {
echo "Je suis la fonction redéfinie B::example().<br />\n";
A::example();
}
}
// Il n'y a pas d'objets de classe A.
// L'affichage est :
// Je suis la fonction originale A::example().<br />
A::example();
// Création d'un objet de la classe B.
$b = new B;
// L'affichage est :
// Je suis la fonction redéfinie B::example().<br />
// Je suis la fonction originale A::example().<br />
$b->example();
?>
]]>
</programlisting>
</informalexample>
<para>
Les exemples ci-dessus appellent la fonction <literal>example()</literal> dans la
classe A, mais il n'y a pas encore d'objet de classe A, alors il
n'est pas possible d'écrire <literal>$a->example()</literal>. À la place, on appelle
la fonction <literal>example()</literal> comme une fonction de classe, c'est-à-dire
avec le nom de la classe elle-même, et sans objet.
</para>
<para>
Il y a des fonctions de classe, mais pas de variables de classe.
En fait, il n'y a aucun objet au moment de l'appel de la fonction.
Donc, une fonction de classe ne peut accéder à aucune variable
(mais elle peut accéder aux variables locales et globales).
Il faut proscrire l'utilisation de <varname>$this</varname>.
</para>
<para>
Dans l'exemple ci-dessus, la classe B redéfinit la fonction <literal>example()</literal>.
La définition originale dans la classe A est remplacée par celle de
B, et n'est plus accessible depuis B, à moins que vous n'appeliez
spécifiquement la fonction <literal>example()</literal> de la classe A avec
l'opérateur ::. Écrivez <literal>A::example()</literal> pour cela (en fait,
il faudrait écrire <literal>parent::example()</literal>, comme expliqué dans
la section suivante).
</para>
<para>
Dans ce contexte, il y a un objet courant, qui peut avoir d'autres
variables objets. De ce fait, lorsqu'il est utilisé depuis une méthode
d'un objet, vous pouvez utiliser <varname>$this.</varname>
</para>
</sect1>
<sect1 xml:id="keyword.parent">
<title><literal>parent</literal></title>
<para>
Il arrive que vous ayez à écrire du code qui faire référence
aux variables et fonctions des classes de base. C'est particulièrement
vrai si votre classe dérivée est une spécialisation de votre classe
de base.
</para>
<para>
Au lieu d'utiliser le nom littéral de votre classe de base dans
votre code, vous pouvez utiliser le mot réservé
<literal>parent</literal>, qui représente votre classe de
base (celle indiqué par <literal>extends</literal>, dans la déclaration
de votre classe). En faisant cela, vous évitez d'appeler le
nom de votre classe de base directement dans votre code.
Si votre héritage change, vous n'aurez plus qu'à modifier
le nom de la classe dans la déclaration <literal>extends</literal>
de votre classe.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class A {
function example() {
echo "Je suis A::example() et je fournis une fonctionnalité de base.<br />\n";
}
}
class B extends A {
function example() {
echo "Je suis B::example() et je fournis une fonctionnalité supplémentaire.<br />\n";
parent::example();
}
}
$b = new B;
// Cette syntaxe va appeler B::example(), qui, à sont tour, va appeler A::example().
$b->example();
?>
]]>
</programlisting>
</informalexample>
</sect1>
<sect1 xml:id="language.oop.serialization">
<title>Sauvegarde d'objets - cas des sessions</title>
<para>
<function>serialize</function> retourne une chaîne représentant
une valeur qui peut être stockée dans les sessions de PHP, ou une
base de données. <function>unserialize</function> peut relire cette
chaîne pour recréer la valeur originale. <function>serialize</function>
va sauver toutes les variables d'un objet. Le nom de la classe sera
sauvé mais par les méthodes de cet objet.
</para>
<para>
Pour permettre à <function>unserialize</function> de lire un objet,
la classe de cet objet doit être définie. C'est-à-dire, si vous avez
un objet <varname>$a</varname> de la classe A dans une page
<filename>php1.php</filename>, et que vous le
linéarisez avec <function>serialize</function>, vous obtiendrez une
chaîne qui fait référence à la classe A, et contient toutes les valeurs
de <varname>$a.</varname> Pour pouvoir le relire avec la fonction
<function>unserialize</function>
dans une page <filename>page2.php</filename>, il faut que la définition de la
classe A soit présente dans cette deuxième page. Cela peut se faire
de manière pratique en sauvant la définition de la classe A dans un
fichier séparé, et en l'incluant dans les deux pages <filename>page1.php</filename> et
<filename>page2.php</filename>.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
// classa.inc:
class A {
var $one = 1;
function show_one() {
echo $this->one;
}
}
// page1.php:
include("classa.inc");
$a = new A;
$s = serialize($a);
// enregistrez $s où la page2.php pourra la trouver.
$fp = fopen("store", "w");
fwrite($fp, $s);
fclose($fp);
// page2.php:
// Ceci est nécessaire pour que unserialize() fonctionne correctement
include("classa.inc");
$s = implode("", @file("store"));
$a = unserialize($s);
// maintenant, utilisez la méthode show_one de l'objet $a.
$a->show_one();
?>
]]>
</programlisting>
</informalexample>
<para>
Si vous utilisez les sessions et la fonction <function>session_register</function>
pour sauver des objets, ces objets seront linéarisés automatiquement
avec la fonction <function>serialize</function> à la fin de chaque
script, et relus avec <function>unserialize</function> au début du prochain
script. Cela signifie que ces objets peuvent apparaître dans n'importe quelle
page qui utilise vos sessions.
</para>
<para>
Il est vivement recommandé d'inclure la définition de classe dans
toutes vos pages, même si vous n'utilisez pas ces classes dans
toutes vos pages. Si vous l'oubliez et qu'un tel objet est
présent, il perdra sa classe, et deviendra un objet de classe
<classname>__PHP_Incomplete_Class_Name</classname> sans aucune fonction et, donc, plutôt
inutile.
</para>
<para>
Si, dans l'exemple ci-dessus, <varname>$a</varname> devient un objet de
session avec l'utilisation de <literal>session_register("a")</literal>, vous
devez penser à inclure le fichier <literal>classa.inc</literal> dans toutes
vos pages, et pas seulement <filename>page1.php</filename> et <filename>page2.php</filename>.
</para>
</sect1>
<sect1 xml:id="language.oop.magic-functions">
<title>Les fonctions magiques <literal>__sleep</literal> et <literal>__wakeup</literal></title>
<para>
<function>serialize</function> s'assure que votre classe a une méthode
avec le nom magique <literal>__sleep</literal>. Si c'est le cas, cette
fonction est appelée avant toute linéarisation. Elle peut alors nettoyer
l'objet et on s'attend à ce qu'elle retourne un tableau avec la liste des
noms de variables qui doivent être sauvées.
Si la méthode ne retourne rien, alors &null; est linéarisé et une alerte de type
E_NOTICE sera émise.
</para>
<para>
Le but avoué de <literal>__sleep</literal> est de valider les données en attente
ou d'effectuer des opérations de nettoyage. Cette fonction
est aussi pratique si vous avez de très grands objets qui n'ont pas
besoin d'être sauvés entièrement.
</para>
<para>
À l'inverse, <function>unserialize</function> s'assure de la présence
de la fonction magique <literal>__wakeup</literal>. Si elle existe, cette
fonction reconstruit toutes les ressources d'un objet.
</para>
<para>
Le but de cette fonction <literal>__wakeup</literal> est de rétablir
toutes les connexions aux bases de données, et de recréer les
variables qui n'ont pas été sauvées.
</para>
</sect1>
<sect1 xml:id="language.oop.newref">
<title>Références dans un constructeur</title>
<para>
Créer des références dans un constructeur peut conduire à des résultats
étranges. Ce tutoriel vous guide pour éviter ces problèmes.
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class Foo {
function Foo($name) {
// crée une référence dans le tableau global $globalref
global $globalref;
$globalref[] = &$this;
// donne le nom de la variable
$this->setName($name);
// et l'affiche
$this->echoName();
}
function echoName() {
echo "<br />", $this->name;
}
function setName($name) {
$this->name = $name;
}
}
?>
]]>
</programlisting>
</informalexample>
</para>
<para>
Vérifions maintenant qu'il y a une différence entre
<varname>$bar1</varname> qui a été créé avec
<literal>=</literal> et <varname>$bar2</varname> qui a été
créé avec l'opérateur de référence
<literal>=&amp;</literal> :
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
$bar1 = new Foo('crée dans le constructeur');
$bar1->echoName();
$globalref[0]->echoName();
/* affiche :
crée dans le constructeur
crée dans le constructeur
crée dans le constructeur */
$bar2 =&new foo('crée dans le constructeur');
$bar2->echoName();
$globalref[1]->echoName();
/* affiche :
crée dans le constructeur
crée dans le constructeur
crée dans le constructeur */
?>
]]>
</programlisting>
</informalexample>
</para>
<para>
Apparemment, il n'y a pas de différence, mais en fait, il y en a une
significative : <varname>$bar1</varname> et <varname>$globalref[0]</varname>
ne sont pas référencées, ces deux variables sont différentes.
Cela est dû au fait que l'opérateur <literal>new</literal>
ne retourne par de référence, mais retourne une copie.
<note>
<simpara>
Il n'y a aucune perte de performances (puisque PHP 4 utilise un compteur
de références) à retourner des copies au lieu de
références. Au contraire, il est souvent mieux de travailler
sur les copies plutôt que sur les références,
car créer une référence prend un peu plus de temps
que de créer une copie qui ne prend virtuellement pas de temps
(à moins de créer un tableau géant ou un objet monstrueux,
auquel cas il est préférable de passer par des
références).
</simpara>
</note>
Pour prouver ceci, regardez le code suivant :
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
// maintenant, nous allons changer de nom. Qu'attendez-vous ?
// Vous pouvez vous attendre à ce que les deux variables $bar
// et $globalref[0] changent de nom...
$bar1->setName('modifié');
// comme prédit, ce n'est pas le cas
$bar1->echoName();
$globalref[0]->echoName();
/* affiche :
modifié
crée dans le constructeur
*/
// quelle est la différence entre $bar2 et $globalref[1]
$bar2->setName('modifié');
// Heureusement, elles sont non seulement égales, mais
// elles représentent la même variable.
// donc $bar2->Name et $globalref[1]->Name sont les mêmes
$bar2->echoName();
$globalref[1]->echoName();
/* affiche :
modifié
modifié */
?>
]]>
</programlisting>
</informalexample>
</para>
<para>
Un dernier exemple pour bien comprendre.
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class A {
function A($i) {
$this->value = $i;
// Essayez de comprendre on n'a pas besoin de
// référence ici
$this->b = new B($this);
}
function createRef() {
$this->c = new B($this);
}
function echoValue() {
echo "<br />","class ",get_class($this),': ',$this->value;
}
}
class B {
function B(&$a) {
$this->a = &$a;
}
function echoValue() {
echo "<br />","class ",get_class($this),': ',$this->a->value;
}
}
// Essayez de comprendre pourquoi une copie simple va
// conduire à un résultat indésirable à
// la ligne marquée d'une étoile
$a =&new a(10);
$a->createRef();
$a->echoValue();
$a->b->echoValue();
$a->c->echoValue();
$a->value = 11;
$a->echoValue();
$a->b->echoValue(); // *
$a->c->echoValue();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
class A: 10
class B: 10
class B: 10
class A: 11
class B: 11
class B: 11
]]>
</screen>
</informalexample>
</para>
</sect1>
<sect1 xml:id="language.oop.object-comparison">
<title>Comparer des objets</title>
<para>
En PHP 4, les objets sont comparés de manière très simple,
à savoir : deux instances sont égales si elles ont les mêmes
attributs et valeurs, et qu'elles sont de la même classe. Des règles
similaires s'appliquent lors de la comparaison avec l'opérateur
<literal>===</literal>.
</para>
<para>
Si vous exécutez le code suivant :
<example>
<title>Exemple de comparaison d'objets en PHP 4</title>
<programlisting role='php'>
<![CDATA[
<?php
function bool2str($bool) {
if ($bool === false) {
return 'FALSE';
} else {
return 'TRUE';
}
}
function compareObjects(&$o1, &$o2) {
echo 'o1 == o2 : '.bool2str($o1 == $o2)."\n";
echo 'o1 != o2 : '.bool2str($o1 != $o2)."\n";
echo 'o1 === o2 : '.bool2str($o1 === $o2)."\n";
echo 'o1 !== o2 : '.bool2str($o1 !== $o2)."\n";
}
class Flag {
var $flag;
function Flag($flag=true) {
$this->flag = $flag;
}
}
class SwitchableFlag extends Flag {
function turnOn() {
$this->flag = true;
}
function turnOff() {
$this->flag = false;
}
}
$o = new Flag();
$p = new Flag(false);
$q = new Flag();
$r = new SwitchableFlag();
echo "Compare des instances créées avec les mêmes paramètres\n";
compareObjects($o, $q);
echo "\nCompare des instances créées avec différents paramètres\n";
compareObjects($o, $p);
echo "\nCompare une instance d'un parent avec celle d'une sous-classe\n";
compareObjects($o, $r);
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Compare des instances créées avec les mêmes paramètres
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : TRUE
o1 !== o2 : FALSE
Compare des instances créées avec différents paramètres
o1 == o2 : FALSE
o1 != o2 : TRUE
o1 === o2 : FALSE
o1 !== o2 : TRUE
Compare une instance d'un parent avec celle d'une sous-classe
o1 == o2 : FALSE
o1 != o2 : TRUE
o1 === o2 : FALSE
o1 !== o2 : TRUE
]]>
</screen>
</example>
Ce qui est le résultat que nous attendions, au vu des règles édictées.
Seules deux instances avec les mêmes valeurs d'attributs, et issues de la même
classe, sont considérées comme égales.
</para>
<para>
Même lorsque nous avons une composition d'objet, la même règle de comparaison
s'applique. Dans l'exemple ci-dessous, nous allons créer une classe
conteneur, qui stocke un tableau associatif <classname>Flag</classname>.
<example>
<title>Comparaison d'objets composés en PHP 4</title>
<programlisting role='php'>
<![CDATA[
<?php
class FlagSet {
var $set;
function FlagSet($flagArr = array()) {
$this->set = $flagArr;
}
function addFlag($name, $flag) {
$this->set[$name] = $flag;
}
function removeFlag($name) {
if (array_key_exists($name, $this->set)) {
unset($this->set[$name]);
}
}
}
$u = new FlagSet();
$u->addFlag('flag1', $o);
$u->addFlag('flag2', $p);
$v = new FlagSet(array('flag1'=>$q, 'flag2'=>$p));
$w = new FlagSet(array('flag1'=>$q));
echo "\nObjects composés u(o,p) et v(q,p)\n";
compareObjects($u, $v);
echo "\nu(o,p) et w(q)\n";
compareObjects($u, $w);
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Objects composés u(o,p) et v(q,p)
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : TRUE
o1 !== o2 : FALSE
u(o,p) et w(q)
o1 == o2 : FALSE
o1 != o2 : TRUE
o1 === o2 : FALSE
o1 !== o2 : TRUE
]]>
</screen>
</example>
</para>
</sect1>
</chapter>
<!-- 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:"../../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
-->