Le passage de variables par valeur ou par « référence » (alias)
On a vu plus tôt dans ce cours qu’il existait deux façons de passer une variable (à une fonction par exemple) en PHP : on pouvait soit la passer par valeur (ce qui est le comportement par défaut), soit par référence en utilisant le symbole &
devant le nom de la variable.
Lorsqu’on parle de « passage par référence » en PHP, on devrait en fait plutôt parler d’alias au sens strict du terme et pour être cohérent par rapport à la plupart des autres langages de programmation.
Une « référence » en PHP ou plus précisément un alias est un moyen d’accéder au contenu d’une même variable en utilisant un autre nom. Pour le dire simplement, créer un alias signifie déclarer un autre nom de variable qui va partager la même valeur que la variable de départ.
Notez qu’en PHP le nom d’une variable et son contenu ou sa valeur sont identifiés comme deux choses distinctes par le langage. Cela permet donc de donner plusieurs noms à un même contenu (c’est-à-dire d’utiliser plusieurs noms pour accéder à un même contenu).
Ainsi, lorsqu’on modifie la valeur de l’alias, on modifie également la valeur de la variable de base puisque ces deux éléments partagent la même valeur.
Au contraire, lorsqu’on passe une variable par valeur (ce qui est le comportement par défaut en PHP), on travaille avec une « copie » de la variable de départ. Les deux copies sont alors indépendantes et lorsqu’on modifie le contenu de la copie, le contenu de la variable d’origine n’est pas modifié.
Regardez plutôt l’exemple ci-dessous pour bien vous en assurer :
<!DOCTYPE html> <html> <head> <title>Cours PHP & MySQL</title> <meta charset="utf-8"> <link rel="stylesheet" href="cours.css"> </head> <body> <h1>Titre principal</h1> <?php $x = 1; $y = $x; $z = &$y; $y = 2; echo 'Valeur de $x : ' .$x. '<br> Valeur de $y : ' .$y. '<br> Valeur de $z : ' .$z. '<br>'; $a = 1; $b = 2; function parValeur($valeur){ $valeur = 5; echo 'Valeur dans la fonction : ' .$valeur. '<br>'; } parValeur($a); echo 'Valeur de $a : ' .$a. '<br>'; function parReference(&$reference){ $reference = 10; echo 'Valeur dans la fonction : ' .$reference. '<br>'; } parReference($b); echo 'Valeur de $b : ' .$b. '<br>'; ?> <p>Un paragraphe</p> </body> </html>
Ici, on commence par déclarer une variable $x
à laquelle on assigne la valeur 1
.
Ensuite on définit une variable $y
en lui assignant le contenu de $x
. Par défaut, le passage se fait par valeur ce qui signifie qu’une copie de $x
est créée. Si on manipule ensuite la copie (c’est-à-dire $y
), le contenu de la variable de base $x
n’est pas modifié puisqu’on a bien deux éléments indépendants ici stockant chacun une valeur bien différenciée.
Finalement, on définit une troisième variable $z
en lui assignant le contenu de $y
mais cette fois-ci on passe le contenu par référence avec le signe &
. Notre variable $z
est donc ici un alias de $y
, ce qui signifie que $z
et $y
sont deux noms utilisés pour faire référence (= pour accéder ou pour manipuler) à la même valeur.
Nos deux variables $y
et $z
partagent donc ici la même valeur et lorsqu’on change la valeur assignée à l’une cela modifie forcément le contenu assigné à la seconde puisqu’encore une fois ces deux variables partagent la même valeur.
Ici, il faut bien comprendre une nouvelle fois qu’en PHP le nom d’une variable et son contenu sont deux éléments clairement identifiés. En fait, lorsqu’on assigne une valeur à un nom, on indique simplement au PHP qu’à partir de maintenant on va utiliser ce nom pour accéder à la valeur assignée.
Il se passe exactement la même chose lors du passage des arguments d’une fonction. Comme vous pouvez le voir, on passe l’argument dans notre fonction parValeur()
par valeur. Lorsqu’on modifie la valeur de l’argument à l’intérieur de la fonction, la valeur de la variable externe n’est pas impactée puisque ces deux éléments sont bien différents.
En revanche, on passe l’argument de la fonction parReference()
par référence. Ainsi, on crée un alias qui va servir de référence vers la même valeur que la variable qu’on va passer en argument à la fonction. Lorsqu’on modifie la valeur à l’intérieur de la fonction, on modifie donc également ce que stocke la variable à l’extérieur de la fonction.
Le passage des objets en PHP
Lorsqu’on crée une nouvelle instance de classe en PHP et qu’on assigne le résultat dans une variable, vous devez savoir qu’on n’assigne pas véritablement l’objet en soi à notre variable objet mais simplement un identifiant d’objet qu’on appelle également parfois un « pointeur ».
Cet identifiant va être utilisé pour accéder à l’objet en soi. Pour l’expliquer en d’autres termes, vous pouvez considérer que cet identifiant d’objet est à l’objet ce que le nom d’une variable est à la valeur qui lui est assignée.
Notre variable objet créée stocke donc un identifiant d’objet qui permet lui-même d’accéder aux propriétés de l’objet. Pour accéder à l’objet via son identifiant, on va utiliser l’opérateur ->
qu’on connait bien.
Ainsi, lorsqu’on passe une variable objet en argument d’une fonction, ou lorsqu’on demande à une fonction de retourner une variable objet, ou encore lorsqu’on assigne une variable objet à une autre variable objet, ce sont des copies de l’identifiant pointant vers le même objet qui sont passées.
Comme les copies de l’identifiant pointent toujours vers le même objet, on a tendance à dire que « les objets sont passés par référence ». Ce n’est cependant pas strictement vrai : encore une fois, ce sont des identifiants d’objets pointant vers le même objet qui vont être passés par valeur.
Regardez plutôt l’exemple suivant :
<!DOCTYPE html> <html> <head> <title>Cours PHP & MySQL</title> <meta charset="utf-8"> <link rel="stylesheet" href="cours.css"> </head> <body> <h1>Titre principal</h1> <?php class Utilisateur{ protected $user_name; public function __construct($n){ $this->user_name = $n; } public function getNom(){ echo $this->user_name; } public function setNom($nom){ return $this->user_name = $nom; } }; $pierre = new Utilisateur('Pierre'); $victor = $pierre; $victor->setNom('Victor'); $pierre->getNom(); ?> <p>Un paragraphe</p> </body> </html>
Ici, on crée une classe Utilisateur
qu’on instancie une première fois. On assigne un identifiant d’objet à la variable objet $pierre
.
On définit ensuite une deuxième variable $victor
en lui assignant le contenu de $pierre
. Notre variable va donc devenir de fait une variable objet et va stocker une copie de l’identifiant pointant vers le même objet que $pierre
.
C’est la raison pour laquelle lorsqu’on accède à l’objet via $victor->
pour modifier la valeur de la propriété $user_name
de l’objet, la valeur de $user_name
de $pierre
est également modifiée.
En effet, $pierre
et $victor
contiennent deux copies d’identifiant permettant d’accéder au même objet. C’est la raison pour laquelle le résultat ici peut faire pense que nos objets ont été passés par référence.
Ce n’est toutefois pas le cas, ce sont des copies d’identifiant pointant vers le même objet qui sont passées par valeur. Pour passer un identifiant d’objet par référence, nous allons une nouvelle fois devoir utiliser le signe &
.
Regardez le nouvel exemple ci-dessous pour bien comprendre la différence entre un passage par référence et un passage par valeur via un identifiant.
<!DOCTYPE html> <html> <head> <title>Cours PHP & MySQL</title> <meta charset="utf-8"> <link rel="stylesheet" href="cours.css"> </head> <body> <h1>Titre principal</h1> <?php class Utilisateur{ public $x = 1; public function modif(){ $this->x = 2; } } function tesZero($obj){ $obj = 0; } function tesVraimentZero(&$obj){ $obj = 0; } $pierre = new Utilisateur(); $pierre->modif(); echo 'Après modif() : ' ; var_dump($pierre); tesZero($pierre); echo '<br>Après tesZero() : ' ; var_dump($pierre); tesVraimentZero($pierre); echo '<br>Après tesVraimentZero() : ' ; var_dump($pierre); ?> <p>Un paragraphe</p> </body> </html>
Ici, on définit une classe qui contient une propriété et une méthode publiques et on instancie notre classe puis on assigne l’identifiant d’objet à notre variable objet $pierre
.
On définit également deux fonctions en dehors de notre classe.
On appelle ensuite notre méthode modif()
donc le rôle est de modifier la valeur de la propriété $x
de l’objet courant puis on affiche les informations relatives à notre objet grâce à var_dump()
. On constate que notre propriété $x
stocke bien la valeur 2.
Ensuite, on utilise notre fonction tesZero()
en lui passant $pierre
en argument. Le rôle de cette fonction est d’assigner la valeur 0 à la variable passée en argument. Pourtant, lorsqu’on var_dump()
à nouveau $pierre
, on s’aperçoit que le même objet que précédemment est renvoyé.
Cela est dû au fait qu’ici notre fonction tesZero()
n’a modifié que l’identifiant d’objet et non pas l’objet en soi.
Notre fonction tesVraimentZero()
utilise elle le passage par référence. Dans ce cas-là, c’est bien une référence à l’objet qui va être passée et on va donc bien pouvoir écraser l’objet cette fois-ci.
Je tiens ici à préciser que ces notions sont des notions abstraites et complexes et qu’il faut généralement beaucoup de pratique et une très bonne connaissance au préalable du langage pour bien les comprendre et surtout comprendre leurs implications. J’essaie ici de vous les présenter de la manière la plus simple possible, mais ne vous inquiétez pas si certaines choses vous échappent pour le moment : c’est tout à fait normal, car il faut du temps et du recul pour maitriser parfaitement un langage.