Bagaimana cara membuat salinan objek di PHP?

168

Tampaknya objek PHP dilewatkan oleh referensi. Bahkan operator penugasan tampaknya tidak membuat salinan Obyek.

Berikut ini adalah bukti sederhana yang dibuat-buat:

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

?>

Dalam kedua kasus cetak saya mendapatkan 'setelah'

Jadi, bagaimana saya meneruskan $ a ke set_b () berdasarkan nilai, bukan dengan referensi?

Nick Stinemates
sumber
2
Ada beberapa kasus, di mana Anda sebenarnya menginginkan perilaku ini. Jadi jika Anda menemukan diri Anda sering menggunakannya, maka mungkin ada sesuatu yang lebih mendasar dengan cara Anda menulis kode Anda?
troelskn
1
Tidak, belum perlu menggunakannya.
Nick Stinemates
(object) ((array) $objectA)mungkin menghasilkan hasil yang Anda inginkan sama dengan kinerja yang lebih baik daripada menggunakan clone $objectAatau new stdClass.
Binyamin

Jawaban:

284

Dalam PHP 5+ objek dilewatkan dengan referensi. Dalam PHP 4 mereka dilewatkan oleh nilai (itu sebabnya runtime lulus dengan referensi, yang menjadi usang).

Anda dapat menggunakan operator 'klon' di PHP5 untuk menyalin objek:

$objectB = clone $objectA;

Juga, itu hanya objek yang dilewatkan dengan referensi, bukan segalanya seperti yang Anda katakan dalam pertanyaan Anda ...

Eran Galperin
sumber
Hanya ingin menambahkan kepada siapa saja yang membaca ini, kloning itu akan tetap mengacu pada objek asli. Menjalankan kueri MySQL menggunakan objek yang dikloning mungkin memiliki hasil yang tidak dapat diprediksi karena hal ini, karena eksekusi mungkin tidak dilakukan secara linear.
Ælex
20
Untuk memperbaiki kesalahpahaman umum (saya pikir bahkan PHP docs salah!) Objek PHP 5 tidak "disahkan oleh referensi". Seperti di Jawa, mereka memiliki tambahan tingkat tipuan - poin variabel ke "objek pointer", dan bahwa poin ke objek. Dengan demikian dua variabel dapat menunjuk ke objek yang sama tanpa referensi ke nilai yang sama. Hal ini dapat dilihat dari contoh ini: $a = new stdClass; $b =& $a; $a = 42; var_export($b);di sini $badalah referensi ke variabel $a ; jika Anda mengganti =&dengan yang normal =, itu bukan referensi, dan masih menunjuk ke objek asli.
IMSoP
Runtime pass by reference adalah ide yang buruk, karena itu membuat efek pemanggilan fungsi bergantung pada implementasi fungsi, bukan pada spesifikasi. Itu tidak ada hubungannya dengan nilai by pass sebagai default.
Oswald
1
@Alex Bisakah Anda menguraikan komentar Anda? (Baik di sini atau di tempat lain.) Poin Anda muncul dari IMO agak tidak jelas.
Chris Middleton
@ChrisMiddleton Pikirkan istilah C atau C ++: jika Anda telah mengkloning referensi ke objek yang bebas, di luar cakupan atau dirilis, maka referensi Anda yang dikloning tidak valid. Dengan demikian Anda bisa mendapatkan perilaku yang tidak terdefinisi tergantung pada apa yang terjadi dengan objek asli, yang Anda pegang referensi melalui kloning.
Ælex
103

Jawabannya biasanya ditemukan di buku-buku Jawa.

  1. kloning: Jika Anda tidak menimpa metode klon, perilaku default adalah salinan dangkal. Jika objek Anda hanya memiliki variabel anggota primitif, itu benar-benar ok. Tetapi dalam bahasa tanpa jenis dengan objek lain sebagai variabel anggota, itu sakit kepala.

  2. serialisasi / deserialisasi

$new_object = unserialize(serialize($your_object))

Ini mencapai salinan dalam dengan biaya yang besar tergantung pada kompleksitas objek.

yogman
sumber
4
+1 bagus, hebat, cara hebat untuk melakukan salinan DEEP dalam PHP, juga sangat mudah. Sebagai gantinya, saya bertanya kepada Anda sesuatu tentang salinan dangkal standar yang ditawarkan oleh kata kunci klon PHP, Anda mengatakan bahwa hanya variabel anggota primitif yang akan disalin: apakah array / string PHP dianggap sebagai variabel anggota primitif, sehingga mereka dapat disalin, apakah saya benar?
Marco Demaio
3
Bagi siapa pun yang mengambil ini: salinan "dangkal" ( $a = clone $b, tanpa ada __clone()metode ajaib dalam permainan) setara dengan melihat masing-masing properti objek $bdalam istilah, dan menugaskan ke properti yang sama di anggota baru dari kelas yang sama, menggunakan =. Properti yang merupakan objek tidak akan mendapatkan cloned, juga objek di dalam array; hal yang sama berlaku untuk variabel terikat oleh referensi; yang lainnya hanyalah nilai, dan akan disalin seperti halnya dengan tugas apa pun.
IMSoP
3
Sempurna! json_decode (json_encode ($ obj)); tidak mengkloning properti pribadi / terproteksi dan metode apa pun ... unserialize (serialisasi bukan metode mengkloning juga ...
zloctb
Luar biasa! Saya akhirnya menyingkirkan kesalahan PhpStorm; Call to method __clone from invalid context:)
numediaweb
Teman mendapatkan kesalahan parse PHP ketika melakukannya seperti: $new_date = (clone $date_start)->subDays(1);Gagal dengan (), jika saya menghapusnya, saya mendapatkan kesalahan yang berbeda. Masalahnya, kita menggunakan php 7.2.3 yang sama persis dan milikku berfungsi dengan baik. Ada ide? Dicari di mana-mana ..
emotalitas
21

Menurut komentar sebelumnya, jika Anda memiliki objek lain sebagai variabel anggota, lakukan hal berikut:

class MyClass {
  private $someObject;

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

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

}

Sekarang Anda bisa melakukan kloning:

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

Hanya untuk memperjelas PHP menggunakan copy on write, jadi pada dasarnya semuanya adalah referensi sampai Anda memodifikasinya, tetapi untuk objek Anda perlu menggunakan klon dan metode ajaib __clone () seperti pada jawaban yang diterima.

Patricio Rossi
sumber
1

Kode ini membantu mengkloning metode

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
sumber
kode ini agak tidak berguna, itu akan bekerja bahkan jika Anda menghapus metode __clone :)
amik
1

Saya sedang melakukan beberapa pengujian dan mendapatkan ini:

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
sumber
1

Dalam contoh ini kita akan membuat kelas iPhone dan membuat salinan persisnya dengan kloning

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
sumber
-1

Jika Anda ingin sepenuhnya menyalin properti objek dalam contoh berbeda, Anda mungkin ingin menggunakan teknik ini:

Serialize ke JSON dan kemudian de-serialisasi kembali ke Object.

diy_nunez
sumber
7
Hmm aku akan menghindarinya seperti neraka.
Jimmy Kane