Clonage d'objets : analyse approfondie
Fait partie de la section Programmation Orientée Objet du Journey PHP de Coddy — leçon 61 sur 91.
Vous avez découvert la méthode magique __clone plus tôt dans ce cours. Explorons maintenant la différence cruciale entre le clonage superficiel (shallow) et profond (deep) lorsque les objets contiennent d'autres objets en tant que propriétés.
Par défaut, PHP effectue une copie superficielle (shallow copy) lors du clonage. Les valeurs scalaires sont dupliquées, mais les propriétés d'objet restent des références aux mêmes objets :
<?php
class Address {
public function __construct(public string $city) {}
}
class Person {
public function __construct(
public string $name,
public Address $address
) {}
}
$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;
$clone->name = "Bob";
$clone->address->city = "London";
echo $original->name . " - " . $original->address->city;
Résultat :
Alice - LondonRemarquez que la modification de $clone->name n'a pas affecté l'original, mais que la modification de la ville de l'adresse l'a fait ! Les deux objets partagent la même instance Address.
Pour créer une copie profonde où les objets imbriqués sont également clonés, implémentez __clone :
<?php
class Person {
public function __construct(
public string $name,
public Address $address
) {}
public function __clone(): void {
// Lors du clonage de l'objet Person, nous clonons également l'objet Address
$this->address = clone $this->address;
}
}
$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;
$clone->address->city = "London";
echo $original->address->city;
Résultat :
ParisDésormais, chaque Person possède son propre Address indépendant. Lorsque vos objets contiennent plusieurs objets imbriqués, vous devez cloner chacun d'eux dans __clone pour obtenir une véritable indépendance entre l'original et la copie.
Défi
FacileConstruisons un système de gestion de documents qui démontre la différence entre le clonage superficiel (shallow) et le clonage profond (deep). Vous allez créer des documents contenant des informations sur l'auteur et vous assurer que le clonage d'un document crée une copie véritablement indépendante.
Vous organiserez votre code sur trois fichiers :
Author.php— Créez une classeAuthorqui représente l'auteur d'un document. Utilisez la promotion de constructeur pour définir un$name(string) public et un$email(string) public.Document.php— Créez une classeDocumentqui contient un auteur. Incluez le fichier Author. La classe doit :- Utiliser la promotion de constructeur pour un
$title(string) public et un$author(Author) public - Implémenter la méthode magique
__clone()pour effectuer un clonage profond — lorsqu'un document est cloné, son auteur doit également être cloné afin que les modifications apportées à l'auteur du clone n'affectent pas l'original - Avoir une méthode
getInfo()qui retourne"{$this->title} by {$this->author->name} ({$this->author->email})"
- Utiliser la promotion de constructeur pour un
main.php— Incluez le fichier Document. Vous recevrez trois entrées : un titre de document, un nom d'auteur et un e-mail d'auteur.Créez un
Documentavec unAuthoren utilisant les entrées fournies. Clonez le document, puis modifiez le nom de l'auteur du clone en y ajoutant" (Copy)"et changez le titre du clone en y ajoutant le préfixe"Copy of ".Affichez deux lignes :
- Les informations du document original
- Les informations du document cloné
Si votre clonage profond est implémenté correctement, l'auteur du document original doit rester inchangé même après avoir modifié l'auteur du clone.
Ce défi démontre pourquoi le clonage profond est important — sans une implémentation correcte de __clone(), les deux documents partageraient le même objet Author, et en modifier un affecterait l'autre.
Aide-mémoire
Par défaut, PHP effectue une copie superficielle (shallow copy) lors du clonage d'objets. Les valeurs scalaires sont dupliquées, mais les propriétés d'objet restent des références aux mêmes objets.
Exemple de clonage superficiel :
<?php
class Address {
public function __construct(public string $city) {}
}
class Person {
public function __construct(
public string $name,
public Address $address
) {}
}
$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;
$clone->name = "Bob";
$clone->address->city = "London";
echo $original->name . " - " . $original->address->city;
// Résultat : Alice - London
Modifier $clone->name n'affecte pas l'original, mais modifier l'objet imbriqué address le fait car les deux objets partagent la même instance de Address.
Pour créer une copie profonde (deep copy) où les objets imbriqués sont également clonés, implémentez la méthode magique __clone :
<?php
class Person {
public function __construct(
public string $name,
public Address $address
) {}
public function __clone(): void {
$this->address = clone $this->address;
}
}
$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;
$clone->address->city = "London";
echo $original->address->city;
// Résultat : Paris
Lorsque les objets contiennent plusieurs objets imbriqués, vous devez cloner chacun d'eux dans __clone pour obtenir une véritable indépendance entre l'original et la copie.
Essayez vous-même
<?php
require_once 'Document.php';
// Lire les entrées
$title = trim(fgets(STDIN));
$authorName = trim(fgets(STDIN));
$authorEmail = trim(fgets(STDIN));
// TODO: Créer un auteur avec le nom et l'e-mail fournis
// TODO: Créer un document avec le titre fourni et l'auteur
// TODO: Cloner le document
// TODO: Modifier le nom de l'auteur du clone en y ajoutant " (Copy)"
// TODO: Modifier le titre du clone en y ajoutant le préfixe "Copy of "
// TODO: Afficher les informations du document original
// TODO: Afficher les informations du document cloné
?>Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Les bases de la POO
Fichiers externesIntroduction à la POOClasses vs ObjetsLe mot-clé $thisMéthodesPropriétésConstructeur (__construct)Destructeur (__destruct)Récapitulatif - Calculatrice simple4Héritage
Héritage de baseLe mot-clé parent::Redéfinition de méthodeLe mot-clé finalClasses abstraitesRécapitulatif - Hiérarchie des employés7Encapsulation
Public, Protégé, PrivéModificateurs d'accès en détailGetters et SettersMasquage d'informationPromotion de constructeur (8.0)Récapitulatif - Système de gestion d'étudiants10Concepts avancés de la POO
Composition vs HéritageInjection de dépendancesClasses anonymesÉnumérations (PHP 8.1)Fibers (PHP 8.1)Clonage d'objets : analyse approfondieGénérateurs et Itérateurs13Patrons de conception Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Méthode ModèlePatron ÉtatPatron CompositePatron Repository2Espaces de noms et Autoloading
Introduction aux espaces de nomsLe mot-clé useStandard d'Autoloading PSR-4L'Autoloader ComposerRécapitulatif - Projet organisé5Interfaces et Contrats
Introduction aux InterfacesImplémentation d'InterfacesImplémentation d'Interfaces MultiplesInterface vs Classe AbstraiteType Hinting avec les InterfacesRécapitulatif - Calculateur de Formes8Méthodes magiques
Introduction aux méthodes magiques__toString & __debugInfo__get, __set, __isset, __unset__call & __callStatic__clone & Clonage d'objets__serialize & __unserializeRécapitulatif - Collection personnalisée3Propriétés de classe
Propriétés d'instance vs Propriétés statiquesConstantes dans les classesMéthodes et propriétés statiquesPropriétés privées et protégéesPropriétés readonly (PHP 8.1)Récapitulatif - Gestionnaire de compte bancaire6Polymorphisme
Retour sur la redéfinition de méthodePolymorphisme via les interfacesType Hinting et Types d'unionLiaison statique tardiveRécapitulatif - Processeur de paiement9Traits
Introduction aux TraitsUtilisation de plusieurs TraitsRésolution des conflits de TraitsMéthodes abstraites dans les TraitsTraits vs Héritage12Patrons de conception Partie 1
Introduction aux patrons de conceptionPatron SingletonPatron FabriquePatron ObservateurPatron Stratégie