ฉันจะสร้างสำเนาของวัตถุใน PHP ได้อย่างไร


168

ปรากฏว่าในวัตถุ PHP จะถูกส่งผ่านโดยอ้างอิง แม้ผู้ประกอบการที่ได้รับมอบหมายไม่ปรากฏว่ากำลังสร้างสำเนาของวัตถุ

นี่เป็นข้อพิสูจน์ที่เรียบง่าย

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

?>

ในทั้งสองกรณีพิมพ์ฉันได้รับ 'after'

ดังนั้นฉันจะส่ง$ aถึงset_b ()ด้วยค่าไม่ใช่การอ้างอิงได้อย่างไร


2
มีบางกรณีที่คุณต้องการพฤติกรรมนี้จริง ๆ ดังนั้นหากคุณพบว่าตัวเองใช้มันบ่อยครั้งอาจมีบางอย่างผิดปกติกับการเขียนโค้ดของคุณ?
troelskn

1
ไม่ไม่จำเป็นต้องใช้
Nick Stinemates

(object) ((array) $objectA)อาจส่งผลให้ผลลัพธ์เดียวกันต้องการด้วยประสิทธิภาพที่ดีขึ้นแล้วใช้หรือclone $objectA new stdClass
Binyamin

คำตอบ:


284

ใน PHP 5+ วัตถุจะถูกส่งผ่านโดยการอ้างอิง ใน PHP 4 พวกเขาถูกส่งผ่านโดยค่า (นั่นคือเหตุผลว่าทำไมมันมี runtime pass โดยการอ้างอิงซึ่งกลายเป็นเลิก

คุณสามารถใช้ตัวดำเนินการ 'clone' ใน PHP5 เพื่อคัดลอกวัตถุ:

$objectB = clone $objectA;

นอกจากนี้มันเป็นเพียงวัตถุที่ถูกส่งผ่านโดยการอ้างอิงไม่ใช่ทุกสิ่งที่คุณพูดในคำถามของคุณ ...


เพียงแค่ต้องการเพิ่มให้กับทุกคนที่อ่านสิ่งนี้การโคลนนั้นจะอ้างอิงไปยังวัตถุดั้งเดิม การรันคำสั่ง MySQL โดยใช้วัตถุที่ถูกโคลนอาจมีผลลัพธ์ที่ไม่สามารถคาดการณ์ได้เนื่องจากสิ่งนี้เนื่องจากการดำเนินการอาจไม่เกิดขึ้นในลักษณะเชิงเส้น
Ælex

20
เพื่อแก้ไขความเข้าใจผิดที่พบบ่อย (ฉันคิดว่าแม้แต่เอกสาร PHP ก็ผิด!) วัตถุของ PHP 5 ไม่ได้ "ผ่านการอ้างอิง" ในฐานะที่เป็นใน Java พวกเขามีเพิ่มเติมระดับของความร้าย - จุดตัวแปรไปยังวัตถุ "ชี้" และจุดที่วัตถุ ดังนั้นตัวแปรสองตัวสามารถชี้ไปที่วัตถุเดียวกันโดยไม่ต้องอ้างอิงกับค่าเดียวกัน สามารถเห็นได้จากตัวอย่างนี้: $a = new stdClass; $b =& $a; $a = 42; var_export($b);นี่$bคือการอ้างอิงถึงตัวแปร $a ; หากคุณแทนที่=&ด้วยปกติ=มันไม่ใช่การอ้างอิงและยังคงชี้ไปที่วัตถุต้นฉบับ
IMSoP

รันไทม์โดยการอ้างอิงเป็นความคิดที่ไม่ดีเพราะมันทำให้ผลกระทบของการเรียกใช้ฟังก์ชั่นขึ้นอยู่กับการใช้งานของฟังก์ชั่นมากกว่าในสเปค มันไม่มีอะไรเกี่ยวข้องกับการส่งผ่านค่าเป็นค่าเริ่มต้น
Oswald

1
@Alex คุณสามารถอธิบายความคิดเห็นของคุณได้หรือไม่? (ไม่ว่าจะที่นี่หรือที่อื่น ๆ ) ประเด็นของคุณเกิดจาก IMO ที่ไม่ชัดเจน
Chris Middleton

@ChrisMiddleton คิดถึงเงื่อนไขของ C หรือ C ++: หากคุณได้ทำการอ้างอิงไปยังวัตถุที่ไม่ได้อยู่ในขอบเขตหรือเผยแพร่แล้วการอ้างอิงการโคลนของคุณจะไม่ถูกต้อง ดังนั้นคุณอาจได้รับพฤติกรรมที่ไม่ได้กำหนดขึ้นอยู่กับสิ่งที่เกิดขึ้นกับวัตถุต้นฉบับที่คุณถือการอ้างอิงผ่านการโคลน
Ælex

103

คำตอบที่พบได้ทั่วไปในหนังสือ Java

  1. การโคลน: หากคุณไม่ได้แทนที่วิธีการโคลนการทำงานเริ่มต้นคือการคัดลอกที่ตื้น ถ้าวัตถุของคุณมีตัวแปรสมาชิกดั้งเดิมเท่านั้นมันก็โอเคเลย แต่ในภาษาที่ไม่มีการพิมพ์โดยมีวัตถุอื่นเป็นตัวแปรสมาชิก

  2. อนุกรม / deserialization

$new_object = unserialize(serialize($your_object))

การทำสำเนาแบบเจาะลึกนี้มีค่าใช้จ่ายมากขึ้นอยู่กับความซับซ้อนของวัตถุ


4
+1 ที่ยอดเยี่ยมวิธีที่ยอดเยี่ยมในการทำสำเนา DEEP ใน PHP ก็ทำได้ง่ายเช่นกัน ให้ฉันถามคุณบางอย่างเกี่ยวกับการคัดลอกมาตรฐานตื้นที่นำเสนอโดยคำหลัก PHP โคลนคุณบอกว่าตัวแปรสมาชิกดั้งเดิมเท่านั้นที่ได้รับการคัดลอก: เป็น PHP อาร์เรย์ / สตริงถือเป็นตัวแปรสมาชิกดั้งเดิมดังนั้นพวกเขาได้รับการคัดลอกใช่ไหม?
Marco Demaio

3
สำหรับใครก็ตามที่หยิบมันขึ้นมา: สำเนา "ตื้น" ( $a = clone $bไม่มี__clone()วิธีเล่นมายากล) เทียบเท่ากับการดูแต่ละคุณสมบัติของวัตถุ$bในระยะและกำหนดให้คุณสมบัติเดียวกันในสมาชิกใหม่ของคลาสเดียวกันโดยใช้=. คุณสมบัติที่เป็นวัตถุจะไม่ได้รับcloned และจะไม่วัตถุภายในอาร์เรย์ สิ่งเดียวกันนี้จะไปสำหรับตัวแปรที่ถูกโยงโดยการอ้างอิง ทุกสิ่งทุกอย่างเป็นเพียงคุณค่าและได้รับการคัดลอกเช่นเดียวกับการมอบหมายใด ๆ
IMSoP

3
ที่สมบูรณ์แบบ! json_decode (json_encode ($ obj)); ไม่ได้โคลนคุณสมบัติส่วนตัว / การป้องกันและวิธีการใด ๆ ... unserialize (เป็นอันดับไม่ใช่วิธีการโคลนเกินไป ...
zloctb

! น่ากลัว ในที่สุดฉันก็กำจัดข้อผิดพลาดของ PhpStorm; Call to method __clone from invalid context:)
numediaweb

เพื่อนกำลังรับข้อผิดพลาดในการแยกวิเคราะห์ PHP เมื่อทำเช่น: $new_date = (clone $date_start)->subDays(1);มันล้มเหลวด้วย()ถ้าฉันลบพวกเขาฉันได้รับข้อผิดพลาดที่แตกต่างกัน สิ่งคือเราใช้ php 7.2.3 ที่แน่นอนเหมือนกันและทำงานได้ดี ความคิดใด ๆ ค้นหาได้ทุกที่ ..
สมจริง

21

ตามความคิดเห็นก่อนหน้าหากคุณมีวัตถุอื่นเป็นตัวแปรสมาชิกให้ทำดังต่อไปนี้:

class MyClass {
  private $someObject;

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

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

}

ตอนนี้คุณสามารถทำการโคลนได้:

$bar = new MyClass();
$foo = clone $bar;


4

เพียงชี้แจงให้ชัดเจนว่า PHP ใช้การคัดลอกเมื่อเขียนดังนั้นโดยพื้นฐานแล้วทุกอย่างเป็นการอ้างอิงจนกว่าคุณจะแก้ไข แต่สำหรับวัตถุที่คุณต้องใช้โคลนและวิธีมายากล __clone () เหมือนในคำตอบที่ยอมรับ


1

รหัสนี้ช่วยวิธีการโคลน

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

รหัสนี้เป็นบิตไม่มีประโยชน์ก็จะทำงานแม้ว่าคุณจะลบ __clone วิธี :)
amik

1

ฉันกำลังทำการทดสอบและได้รับสิ่งนี้:

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

ในตัวอย่างนี้เราจะสร้างคลาสiPhoneและทำสำเนาที่ถูกต้องโดยการโคลน

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

หากคุณต้องการคัดลอกคุณสมบัติของวัตถุในอินสแตนซ์อื่นอย่างสมบูรณ์คุณอาจต้องการใช้เทคนิคนี้:

ทำให้เป็นอนุกรมให้กับ JSON แล้วยกเลิกการทำให้เป็นอันดับเป็นวัตถุ


7
อืมฉันจะหลีกเลี่ยงมันเหมือนตกนรก
Jimmy Kane
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.