Comment créer une copie d'un objet en PHP?

168

Il semble qu'en PHP les objets sont passés par référence. Même les opérateurs d'affectation ne semblent pas créer une copie de l'objet.

Voici une preuve simple et artificielle:

<?php

class A {
    public $b;
}


function set_b($obj) { $obj->b = "after"; }

$a = new A();
$a->b = "before";
$c = $a; //i would especially expect this to create a copy.

set_b($a);

print $a->b; //i would expect this to show 'before'
print $c->b; //i would ESPECIALLY expect this to show 'before'

?>

Dans les deux cas d'impression, je reçois «après»

Alors, comment passer $ a à set_b () par valeur, pas par référence?

Nick Stinemates
la source
3
Il existe très peu de cas où vous souhaiteriez réellement ce comportement. Donc, si vous vous trouvez souvent en train de l'utiliser, il y a peut-être quelque chose de plus fondamental qui ne va pas dans la façon dont vous écrivez votre code?
troelskn
1
Non, je n'ai pas encore eu besoin de l'utiliser.
Nick Stinemates
(object) ((array) $objectA)peut entraîner les mêmes résultats souhaités avec de meilleures performances que l'utilisation de clone $objectAou new stdClass.
Binyamin
Re "Même les opérateurs d'affectation ne semblent pas créer une copie de l'objet." - J'espère que non! S'ils le faisaient, le résultat ne serait plus un langage OO (à toutes fins pratiques).
ToolmakerSteve il y a

Réponses:

284

En PHP 5+, les objets sont passés par référence. En PHP 4, ils sont passés par valeur (c'est pourquoi il avait un passage par référence à l'exécution, qui est devenu obsolète).

Vous pouvez utiliser l'opérateur 'clone' en PHP5 pour copier des objets:

$objectB = clone $objectA;

De plus, ce ne sont que des objets qui sont passés par référence, pas tout comme vous l'avez dit dans votre question ...

Eran Galperin
la source
Je veux juste ajouter à quiconque lit ceci, que le clonage gardera la référence à l'objet original. L'exécution de requêtes MySQL à l'aide de l'objet cloné peut avoir des résultats imprévisibles à cause de cela, car l'exécution peut ne pas se dérouler de manière linéaire.
Ælex
20
Pour corriger une idée fausse courante (je pense que même la documentation PHP se trompe!) Les objets de PHP 5 ne sont pas "passés par référence". Comme en Java, ils ont un niveau supplémentaire d'indirection - la variable pointe vers un "pointeur d'objet", et qui pointe vers un objet. Ainsi deux variables peuvent pointer vers le même objet sans être des références à la même valeur. Ceci peut être vu dans cet exemple: $a = new stdClass; $b =& $a; $a = 42; var_export($b);voici $bune référence à la variable $a ; si vous remplacez =&par une normale =, ce n'est pas une référence et pointe toujours vers l'objet d'origine.
IMSoP
Le passage par référence à l'exécution est une mauvaise idée, car il fait dépendre l'effet d'un appel de fonction de l'implémentation de la fonction plutôt que de la spécification. Cela n'a rien à voir avec le passage par valeur par défaut.
Oswald
1
@Alex Pouvez-vous élaborer sur votre commentaire? (Soit ici ou ailleurs.) Votre point est un peu flou IMO.
Chris Middleton
@ChrisMiddleton Pensez aux termes de C ou C ++: si vous avez cloné une référence à un objet qui est libre, hors de portée ou libéré, alors votre référence clonée est invalidée. Ainsi, vous pouvez obtenir un comportement non défini en fonction de ce qui s'est passé avec l'objet d'origine, auquel vous détenez une référence via le clonage.
Ælex
104

Les réponses se trouvent généralement dans les livres Java.

  1. clonage: si vous ne remplacez pas la méthode de clonage, le comportement par défaut est une copie superficielle. Si vos objets n'ont que des variables membres primitives, c'est tout à fait correct. Mais dans un langage sans type avec un autre objet comme variables membres, c'est un casse-tête.

  2. sérialisation / désérialisation

$new_object = unserialize(serialize($your_object))

Cela permet d'obtenir une copie profonde avec un coût élevé en fonction de la complexité de l'objet.

yogman
la source
4
+1 excellent, excellent, excellent moyen de faire une copie DEEP en PHP, très facile aussi. Permettez-moi plutôt de vous poser une question sur la copie standard peu profonde offerte par le mot clé PHP clone, vous avez dit que seules les variables membres primitives sont copiées: les tableaux / chaînes PHP sont-ils considérés comme des variables membres primitives, donc ils sont copiés, ai-je raison?
Marco Demaio du
3
Pour quiconque prend ceci: une copie "superficielle" ( $a = clone $b, sans __clone()méthodes magiques en jeu) équivaut à regarder chacune des propriétés de l'objet $ben terme, et à affecter la même propriété dans un nouveau membre de la même classe, en utilisant =. Les propriétés qui sont des objets n'obtiendront pas cloned, pas plus que les objets à l'intérieur d'un tableau; il en va de même pour les variables liées par référence; tout le reste n'est qu'une valeur et est copié comme avec n'importe quelle affectation.
IMSoP
3
Parfait! json_decode (json_encode ($ obj)); ne pas cloner les propriétés privées / protégées et toute méthode ... désérialiser (sérialiser pas les méthodes de clonage aussi ...
zloctb
Impressionnant! Je me débarrasse enfin de l'erreur de PhpStorm; Call to method __clone from invalid context:)
numediaweb
1
Cela ajoute beaucoup de temps d'exécution. Vous sérialisez l'objet en une chaîne, puis vous analysez cette chaîne en une nouvelle variable. Bien que cela fasse ce que vous avez l'intention de faire, il le fait d'une manière terriblement lente. Non seulement vous convertissez tout votre objet en une chaîne et inversement, en utilisant le temps et la mémoire, mais vous cassez également le mécanisme CopyOnWrite possible de PHP. Un bien meilleur moyen consiste à implémenter __clonecorrectement votre méthode, comme suggéré par stackoverflow.com/a/186191/1614903 ci-dessous. Voir phpinternalsbook.com/php5/zvals/memory_management.html pour une explication approfondie
Holger Böhnke
22

Selon le commentaire précédent, si vous avez un autre objet comme variable membre, procédez comme suit:

class MyClass {
  private $someObject;

  public function __construct() {
    $this->someObject = new SomeClass();
  }

  public function __clone() {
    $this->someObject = clone $this->someObject;
  }

}

Vous pouvez maintenant faire du clonage:

$bar = new MyClass();
$foo = clone $bar;
Stanislav
la source
4

Juste pour clarifier, PHP utilise la copie à l'écriture, donc fondamentalement, tout est une référence jusqu'à ce que vous le modifiiez, mais pour les objets, vous devez utiliser clone et la méthode magique __clone () comme dans la réponse acceptée.

Patricio Rossi
la source
1

Ce code aide les méthodes de clonage

class Foo{

    private $run=10;
    public $foo=array(2,array(2,8));
    public function hoo(){return 5;}


    public function __clone(){

        $this->boo=function(){$this->hoo();};

    }
}
$obj=new Foo;

$news=  clone $obj;
var_dump($news->hoo());
zloctb
la source
ce code est un peu inutile, il fonctionnerait même si vous supprimiez la méthode __clone :)
amik
1

Je faisais des tests et j'ai obtenu ceci:

class A {
  public $property;
}

function set_property($obj) {
  $obj->property = "after";
  var_dump($obj);
}

$a = new A();
$a->property = "before";

// Creates a new Object from $a. Like "new A();"
$b = new $a;
// Makes a Copy of var $a, not referenced.
$c = clone $a;

set_property($a);
// object(A)#1 (1) { ["property"]=> string(5) "after" }

var_dump($a); // Because function set_property get by reference
// object(A)#1 (1) { ["property"]=> string(5) "after" }
var_dump($b);
// object(A)#2 (1) { ["property"]=> NULL }
var_dump($c);
// object(A)#3 (1) { ["property"]=> string(6) "before" }

// Now creates a new obj A and passes to the function by clone (will copied)
$d = new A();
$d->property = "before";

set_property(clone $d); // A new variable was created from $d, and not made a reference
// object(A)#5 (1) { ["property"]=> string(5) "after" }

var_dump($d);
// object(A)#4 (1) { ["property"]=> string(6) "before" }

?>
Pyetro
la source
1

Dans cet exemple, nous allons créer une classe iPhone et en faire une copie exacte par clonage

class iPhone {

public $name;
public $email;

    public function __construct($n, $e) {

       $this->name = $n;
       $this->email = $e;

    }
}


$main = new iPhone('Dark', '[email protected]');
$copy = clone $main;


// if you want to print both objects, just write this    

echo "<pre>"; print_r($main);  echo "</pre>";
echo "<pre>"; print_r($copy);  echo "</pre>";
Muhammad Ebrahim
la source
-1

Si vous souhaitez copier entièrement les propriétés d'un objet dans une autre instance, vous pouvez utiliser cette technique:

Sérialisez-le en JSON, puis dé-sérialisez-le en Object.

diy_nunez
la source
7
Hmm, j'éviterais ça comme un diable.
Jimmy Kane