reseau social

Le clonage d'objets avec PHP 5

Un article de ToutProgrammer.com.

PHP évoluant énormément, son utilisation devrait petit à petit s'étendre à des domaines pour lesquels il n'avait pas été étudié à l'origine. C'est le cas par exemple avec PHP-GTK. Dans ce contexte de nouveaux besoins apparaissent comme par exemple la copie d'objets qui était inexistante jusqu'alors avec les autres versions de PHP.

Sommaire

[modifier] A quoi sert le clonage d'objets

On entend par clonage d'objets l'action qui consiste à dupliquer intelligemment un objet. S'il y a copie, cela signifie que chacune des références des attributs de l'objet original sera elle aussi recopiée ce qui fait que le clone et l'original partageront certains espaces de la mémoire. Le clonage doit permettre de modifier cela afin que chacun des attributs du clone utilise sa propre instance.

[modifier] Pas de clonage avec PHP 4

Le titre de cette section résume tout puisqu'en effet, il n'est pas possible avec PHP version 4 (et les versions antérieures) d'effectuer un clonage d'objet. Une chose possible, simple et rapide, est de faire une référence à un autre objet ce qui est très loin du clonage. Il est également possible de copier un objet mais le processus ne fera que copier les membres de l'objet original sans introduire de personnalisation automatisée.

[modifier] Le clonage dans PHP 5

PHP 5 introduit le nouveau mot clé clone. Celui-ci fonctionne comme le mot clé new mais l'instance retournée est une référence sur une copie. Voici un exemple simple d'utilisation de cloneavec PHP 5:

  1. <?php
  2. // La ligne ci-dessous permet d'afficher tout texte
  3. // comme texte simple et non comme du HTML.
  4. // C'est utile car nous utiliserons la fonction print_r
  5. // (voir la documentation PHP pour plus d'info)
  6. header("Content-type: text/plain");
  7.  
  8. class Personne {
  9. private $nom;
  10. private $prenom;
  11.  
  12. function Personne($nom, $prenom) {
  13. $this->setNom($nom);
  14. $this->setPrenom($prenom);
  15. }
  16.  
  17. function setNom($nom) {
  18. $this->nom = $nom;
  19. }
  20.  
  21. function setPrenom($prenom) {
  22. $this->prenom = $prenom;
  23. }
  24.  
  25. function getNomPrenom() {
  26. return($this->nom." ".$this->prenom);
  27. }
  28. }
  29.  
  30. // Instanciation de l'objet original
  31. $personne = new Personne("MARTIN", "Paul");
  32.  
  33. // Clonage de l'objet
  34. $clonePersonne = clone $personne;
  35.  
  36. // Ces lignes nous démontrent que nous avons bien 2 objets qui contenaient
  37. // les mêmes données mais n'avaient pas les mêmes
  38. // références sur celles-ci
  39. $clonePersonne->setPrenom("Jean");
  40. print_r($personne);
  41. print_r($clonePersonne);
  42. ?>

produit:

Personne Object
(
[nom:private] => MARTIN
[prenom:private] => Paul
)
Personne Object
(
[nom:private] => MARTIN
[prenom:private] => Jean
)

Comme nous le voyons avec ces 2 personnes, Jeanest le clone de Paul: elles ont toutes les deux les mêmes caractéristiques (un nom et un prénom) mais ce sont 2 êtres indépendants. Ceci est démontré en modifiant le prénom du clone ce qui ne modifie pas le prénom de l'original.

Comme indiqué plus haut, le clonage est un élément qui peut avoir son importance avec PHP-GTK. Comme indiqué dans le document de référence Zend Engine 2.0, vous pouvez vouloir créer une copie conforme d'une fenêtre avec les mêmes propriétés: le clonage le permet.

On retrouve déjà un fonctionnement similaire dans certaines applications Java qui proposent un menu "Cloner" ou comme dans l'EDI Eclipse un sous menu "Nouvelle fenêtre".

Eclipse clone

[modifier] Personnalisation du clonage

Avec PHP 5, une nouvelle méthode réservée et nommée __clone est ajoutée à chaque objet. Par défaut celle-ci ne fait que dupliquer les différents attributs de l'objet original dans l'instance de l'objet cloné. Mais un problème se pose dès lors qu'un attribut est une instance d'un objet.

Pour mieux comprendre le problème posé, nous allons étudier un exemple avec cette fois 2 classes:

  1. <?php
  2. // La ligne ci-dessous permet d'afficher tout
  3. // texte comme texte simple et non comme du HTML
  4. // C'est utile car nous utiliserons la fonction
  5. // print_r (voir la documentation PHP pour plus d'info)
  6. header("Content-type: text/plain");
  7.  
  8. // Un object de gestion des chaines de caractères
  9. class Chaine {
  10. private $valeur;
  11.  
  12. function Chaine($valeur) {
  13. $this->setValeur($valeur);
  14. }
  15.  
  16. function setValeur($valeur) {
  17. $this->valeur = $valeur;
  18. }
  19.  
  20. function getValeur() {
  21. return($this->valeur);
  22. }
  23. }
  24.  
  25. class Personne {
  26. private $nom;
  27. private $prenom;
  28.  
  29. function Personne($nom, $prenom) {
  30. $this->nom = new Chaine($nom);
  31. $this->prenom = new Chaine($prenom);
  32. }
  33.  
  34. function setNom($nom) {
  35. $this->nom->setValeur($nom);
  36. }
  37.  
  38. function setPrenom($prenom) {
  39. $this->prenom->setValeur($prenom);
  40. }
  41.  
  42. function getNomPrenom() {
  43. return($this->nom." ".$this->prenom);
  44. }
  45. }
  46.  
  47. // Instanciation de l'objet original
  48. $personne = new Personne("MARTIN", "Paul");
  49.  
  50. // Clonage de l'objet
  51. $clonePersonne = clone $personne;
  52.  
  53. // Ces lignes nous démontrent que nous avons
  54. // bien 2 objets qui avec la méthode de clonage
  55. // par défaut contiennent des références sur les
  56. // mêmes objects
  57. $clonePersonne->setPrenom("Jean");
  58. print_r($personne);
  59. print_r($clonePersonne);
  60. ?>

affiche:

Personne Object
(
[nom:private] => Chaine Object
(
[valeur:private] => MARTIN
)

[prenom:private] => Chaine Object
(
[valeur:private] => Jean
)

)
Personne Object
(
[nom:private] => Chaine Object
(
[valeur:private] => MARTIN
)

[prenom:private] => Chaine Object
(
[valeur:private] => Jean
)

)

Comme nous pouvons le voir avec cet exemple, la méthode __clone de base de chacun des objets (méthode gérée en interne par PHP 5 lorsque celle-ci n'est pas implémentée par le développeur) n'a fait que dupliquer les références que sont les attributs $nom et $prenom de la classe Personne. Les objets (clone et original) ne sont plus indépendants puisqu'ils utilisent des références communes alors que nous souhaiterions que ces références soient différentes.

Afin de régler ce problème, il est possible de ré-écrire la méthode __clone afin d'obtenir un fonctionnement plus évolué et répondant mieux aux attentes que l'on peut en avoir.

  1. <?php
  2. // La ligne ci-dessous permet d'afficher tout
  3. // texte comme texte simple et non comme du HTML
  4. // C'est utile car nous utiliserons la fonction
  5. // print_r (voir la documentation PHP pour plus d'info)
  6. header("Content-type: text/plain");
  7.  
  8. // Un object de gestion des chaines de caractères
  9. class Chaine {
  10. private $valeur;
  11.  
  12. function Chaine($valeur) {
  13. $this->setValeur($valeur);
  14. }
  15.  
  16. function setValeur($valeur) {
  17. $this->valeur = $valeur;
  18. }
  19.  
  20. function getValeur() {
  21. return($this->valeur);
  22. }
  23. }
  24.  
  25. class Personne {
  26. private $nom;
  27. private $prenom;
  28.  
  29. function Personne($nom, $prenom) {
  30. $this->nom = new Chaine($nom);
  31. $this->prenom = new Chaine($prenom);
  32. }
  33.  
  34. function setNom($nom) {
  35. $this->nom->setValeur($nom);
  36. }
  37.  
  38. function setPrenom($prenom) {
  39. $this->prenom->setValeur($prenom);
  40. }
  41.  
  42. function getNomPrenom() {
  43. return($this->nom." ".$this->prenom);
  44. }
  45.  
  46. function __clone() {
  47. $this->nom = new Chaine($this->nom->getValeur());
  48. $this->prenom = new Chaine($this->prenom->getValeur());
  49. }
  50. }
  51.  
  52. // Instanciation de l'objet original
  53. $personne = new Personne("MARTIN", "Paul");
  54.  
  55. // Clonage de l'objet
  56. $clonePersonne = clone $personne;
  57.  
  58. // Ces lignes nous démontrent que nous avons bien 2 objets qui avec
  59. // la méthode de clonage par défaut contient des références sur les
  60. // mêmes objects
  61. $clonePersonne->setPrenom("Jean");
  62. print_r($personne);
  63. print_r($clonePersonne);
  64. ?>

produit:

Personne Object
(
[nom:private] => Chaine Object
(
[valeur:private] => MARTIN
)

[prenom:private] => Chaine Object
(
[valeur:private] => Paul
)

)
Personne Object
(
[nom:private] => Chaine Object
(
[valeur:private] => MARTIN
)

[prenom:private] => Chaine Object
(
[valeur:private] => Jean
)

)

Comme nous le voyons, nous avons ajouté une méthode __clone dans l'objet Personne. Nous voyons dans celle-ci que nous réinstancions les 2 attributs $nom et $prenom. Nous faisons ceci afin de dissocier les attributs de l'instance originale des attributs du clone.

Lorsque le mot clé cloneest rencontré lors de l'exécution, le moteur interne PHP 5 duplique l'objet indiqué et appelle la méthode __clone de la copie. Donc, à la première ligne de la méthode __clone, l'ensemble des attributs de notre clone sont initialisés avec les mêmes valeurs ou les mêmes références que l'original. C'est pour cette raison que nous recréons des instances indépendantes pour ces attributs et que nous les initialisons avec leur valeur courante.

Avec ce mécanisme, comme nous créons de nouvelles instances, il n'y a plus de problème: les attributs des 2 instances de Personne sont bel et bien indépendants. Nous pouvons modifier un objet sans impacter l'autre.

[modifier] Un second exemple de personnalisation de __clone

Voici un autre exemple de ré-écriture de la méthode de clonage. En partant d'un exemple vu un peu plus haut, nous ajoutons un attribut: l'heure d'instanciation de l'objet. Comme je l'ai indiqué plus haut, appeler la méthode __clone revient à instancier un objet comme le fait le mot réservé newmais à la différence que le constructeur de l'objet n'est pas appelé. Si la méthode n'est pas personnalisée, la date d'instanciation du clone sera donc la même que l'original, ce que nous ne voulons pas.

Voici donc le code modifié:

  1. <?php
  2. // La ligne ci-dessous permet d'afficher tout texte
  3. // comme texte simple et non comme du HTML.
  4. // C'est utile car nous utiliserons la fonction print_r
  5. // (voir la documentation PHP pour plus d'info)
  6. header("Content-type: text/plain");
  7.  
  8. class Personne {
  9. private $nom;
  10. private $prenom;
  11. private $dateCreation;
  12.  
  13. function Personne($nom, $prenom) {
  14. $this->setNom($nom);
  15. $this->setPrenom($prenom);
  16. $this->setDateCreation(date("j/m/Y H:i:s"));
  17. }
  18.  
  19. function setDateCreation($dateCreation) {
  20. $this->dateCreation = $dateCreation;
  21. }
  22.  
  23. function getDateCreation() {
  24. return($this->dateCreation);
  25. }
  26.  
  27. function setNom($nom) {
  28. $this->nom = $nom;
  29. }
  30.  
  31. function setPrenom($prenom) {
  32. $this->prenom = $prenom;
  33. }
  34.  
  35. function getNomPrenom() {
  36. return($this->nom." ".$this->prenom);
  37. }
  38.  
  39. function __clone() {
  40. $this->dateCreation = date("j/m/Y H:i:s");
  41. }
  42. }
  43.  
  44. // Instanciation de l'objet original
  45. $personne = new Personne("MARTIN", "Paul");
  46. print_r($personne);
  47.  
  48. // Une boucle d'attente de 5 secondes
  49. sleep(5);
  50. print("\n\n");
  51.  
  52. // Clonage de l'objet
  53. $clonePersonne = clone $personne;
  54.  
  55. // Ces lignes nous démontrent que nous avons bien 2
  56. // objets qui contenaient les mêmes données mais
  57. // n'avaient pas les mêmes références sur celles-ci
  58. $clonePersonne->setPrenom("Jean");
  59. print_r($personne);
  60. print_r($clonePersonne);
  61. ?>

affichera par exemple:

Personne Object
(
[nom:private] => MARTIN
[prenom:private] => Paul
[dateCreation:private] => 1/08/2004 01:52:43
)


Personne Object
(
[nom:private] => MARTIN
[prenom:private] => Paul
[dateCreation:private] => 1/08/2004 01:52:43
)
Personne Object
(
[nom:private] => MARTIN
[prenom:private] => Jean
[dateCreation:private] => 1/08/2004 01:52:48
)

L'appel à sleep() n'est là que pour démontrer que nous avons des dates/heures de création différentes.

Nous voyons donc que le clonage permet d'automatiser la copie "intelligente" d'un objet.

[modifier] Historique de l'article

Cet article, réalisé par Stéphane VANPOPERYNGHE, a été publié pour la première fois le 1er août 2004 sur le site ToutProgrammer.com (1ème version).