Jak utworzyć kopię obiektu w PHP?

168

Wygląda na to, że w PHP obiekty są przekazywane przez referencje. Wydaje się, że nawet operatory przypisania nie tworzą kopii obiektu.

Oto prosty, wymyślony dowód:

<?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'

?>

W obu przypadkach drukowania otrzymuję „po”

Jak więc przekazać $ a do set_b () przez wartość, a nie przez odniesienie?

Nick Stinemate
źródło
2
Jest bardzo niewiele przypadków, w których rzeczywiście chciałbyś takiego zachowania. Więc jeśli zauważysz, że często go używasz, być może jest coś bardziej fundamentalnego nie tak w sposobie pisania kodu?
troelskn
1
Nie, jeszcze nie musiałem go używać.
Nick Stinemate
(object) ((array) $objectA)może dać takie same pożądane rezultaty z lepszą wydajnością niż użycie clone $objectAlub new stdClass.
Binyamin

Odpowiedzi:

284

W PHP 5+ obiekty są przekazywane przez referencje. W PHP 4 są one przekazywane przez wartość (z tego powodu w czasie wykonywania zostało przekazane przez odwołanie, które stało się przestarzałe).

Możesz użyć operatora „clone” w PHP5 do kopiowania obiektów:

$objectB = clone $objectA;

Poza tym to tylko obiekty przekazywane przez odniesienie, a nie wszystko, co powiedziałeś w swoim pytaniu ...

Eran Galperin
źródło
Chcę tylko dodać do każdego, kto to czyta, że ​​klonowanie zachowa odniesienie do oryginalnego obiektu. Uruchamianie zapytań MySQL przy użyciu sklonowanego obiektu może z tego powodu mieć nieprzewidywalne wyniki, ponieważ wykonanie może nie odbywać się w sposób liniowy.
Ælex
20
Aby poprawić powszechne nieporozumienie (myślę, że nawet dokumentacja PHP się myli!) Obiekty PHP 5 nie są „przekazywane przez odniesienie”. Podobnie jak w Javie, mają dodatkowy poziom pośredni - zmienna wskazuje na „wskaźnik obiektu”, a to wskazuje na obiekt. W ten sposób dwie zmienne mogą wskazywać na ten sam obiekt bez odniesienia do tej samej wartości. Można to zobaczyć na tym przykładzie: $a = new stdClass; $b =& $a; $a = 42; var_export($b);tutaj $bjest odniesienie do zmiennej $a ; jeśli zastąpi =&się normalne =, to nie odwołanie, i nadal zwraca się do oryginalnego obiektu.
IMSoP
Przekazywanie w czasie wykonywania przez odwołanie jest złym pomysłem, ponieważ powoduje, że efekt wywołania funkcji zależy od implementacji funkcji, a nie od specyfikacji. Nie ma to nic wspólnego z domyślną wartością przekazywania przez.
Oswald
1
@Alex Czy możesz rozwinąć swój komentarz? (Albo tutaj, albo gdzie indziej.) Twój punkt widzenia jest nieco niejasny IMO.
Chris Middleton
@ChrisMiddleton Pomyśl o warunkach C lub C ++: jeśli sklonowałeś odniesienie do obiektu, który jest zwolniony, poza zakresem lub zwolniony, to sklonowane odniesienie jest unieważnione. W ten sposób można uzyskać niezdefiniowane zachowanie w zależności od tego, co się stało z oryginalnego obiektu, do którego masz odniesienie poprzez klonowanie.
Ælex
103

Odpowiedzi można często znaleźć w książkach Java.

  1. cloning: Jeśli nie zastąpisz metody clone, domyślnym zachowaniem jest płytka kopia. Jeśli twoje obiekty mają tylko prymitywne zmienne składowe, wszystko jest w porządku. Ale w języku bez typu, w którym inny obiekt jest zmiennymi składowymi, jest to ból głowy.

  2. serializacja / deserializacja

$new_object = unserialize(serialize($your_object))

Zapewnia to głębokie kopiowanie przy dużym koszcie w zależności od złożoności obiektu.

yogman
źródło
4
+1 świetny, świetny, świetny sposób na wykonanie DEEP kopii w PHP, również bardzo łatwy. Zamiast tego pozwól mi zapytać o standardową płytką kopię oferowaną przez słowo kluczowe klon PHP. Powiedziałeś, że tylko prymitywne zmienne składowe są kopiowane: czy tablice / ciągi znaków PHP są uważane za prymitywne zmienne składowe, więc są kopiowane, mam rację?
Marco Demaio,
3
Dla każdego, kto to zauważy: "płytka" kopia ( $a = clone $bbez użycia magicznych __clone()metod) jest równoważna spojrzeniu na każdą z właściwości obiektu $bw terminach i przypisaniu do tej samej właściwości w nowym elemencie tej samej klasy przy użyciu =. Właściwości, które są obiektami, nie otrzymają cloned, podobnie jak obiekty wewnątrz tablicy; to samo dotyczy zmiennych związanych przez odniesienie; wszystko inne jest tylko wartością i jest kopiowane tak jak w przypadku każdego zadania.
IMSoP
3
Idealny! json_decode (json_encode ($ obj)); not clone private / protected properties and any method ... unserialize (serialize not clone methods too ...
zloctb
Niesamowite! W końcu pozbyłem się błędu PhpStorma; Call to method __clone from invalid context:)
numediaweb
Przyjaciel otrzymuje błąd parsowania PHP podczas wykonywania tego: $new_date = (clone $date_start)->subDays(1);Nie udaje się to (), jeśli je usunę, pojawia się inny błąd. Chodzi o to, że używamy dokładnie tego samego php 7.2.3, a mój działa dobrze. Jakieś pomysły? Wszędzie wyszukiwane ..
emocje
21

Zgodnie z poprzednim komentarzem, jeśli masz inny obiekt jako zmienną składową, wykonaj następujące czynności:

class MyClass {
  private $someObject;

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

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

}

Teraz możesz klonować:

$bar = new MyClass();
$foo = clone $bar;
Stanislav
źródło
4

Aby wyjaśnić, że PHP używa kopiowania przy zapisie, więc w zasadzie wszystko jest odniesieniem, dopóki go nie zmodyfikujesz, ale w przypadku obiektów musisz użyć clone i magicznej metody __clone (), jak w zaakceptowanej odpowiedzi.

Patricio Rossi
źródło
1

Ten kod pomaga w klonowaniu metod

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
źródło
ten kod jest trochę bezużyteczny, działałby nawet jeśli usuniesz metodę __clone :)
amik
1

Robiłem testy i otrzymałem to:

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
źródło
1

W tym przykładzie utworzymy klasę iPhone'a i zrobimy z niej dokładną kopię przez klonowanie

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
źródło
-1

Jeśli chcesz w pełni skopiować właściwości obiektu w innej instancji, możesz użyć tej techniki:

Serializuj go do JSON, a następnie deserializuj z powrotem do Object.

diy_nunez
źródło
7
Hmm, uniknąłbym tego jak diabli.
Jimmy Kane