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?


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 ...


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.


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;


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.


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());

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" }

?>

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', 'm@m.com');
$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>";

-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.


7
Hmm, j'éviterais ça comme un diable.
Jimmy Kane
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.