Làm cách nào để tạo một bản sao của một đối tượng trong PHP?


168

Dường như trong các đối tượng PHP được truyền bằng tham chiếu. Ngay cả các toán tử gán cũng không xuất hiện để tạo một bản sao của Object.

Đây là một bằng chứng đơn giản, dễ hiểu:

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

?>

Trong cả hai trường hợp in tôi đều nhận được 'sau'

Vậy, làm thế nào để tôi chuyển $ a đến set_b () theo giá trị chứ không phải bằng tham chiếu?


2
Có rất ít trường hợp, nơi bạn thực sự muốn hành vi này. Vì vậy, nếu bạn thấy bản thân mình sử dụng nó thường xuyên, thì có lẽ có điều gì đó cơ bản hơn sai với cách bạn viết mã của mình?
troelskn

1
Không, chưa cần sử dụng nó.
Nick Stinemates

(object) ((array) $objectA)có thể dẫn đến kết quả bạn mong muốn với hiệu suất tốt hơn sau đó sử dụng clone $objectAhoặc new stdClass.
Binyamin

Câu trả lời:


284

Trong PHP 5+ đối tượng được truyền bằng tham chiếu. Trong PHP 4, chúng được truyền theo giá trị (đó là lý do tại sao nó có thời gian chạy qua tham chiếu, trở nên không dùng nữa).

Bạn có thể sử dụng toán tử 'clone' trong PHP5 để sao chép các đối tượng:

$objectB = clone $objectA;

Ngoài ra, đó chỉ là các đối tượng được chuyển qua tham chiếu, không phải mọi thứ như bạn đã nói trong câu hỏi của bạn ...


Chỉ muốn thêm vào bất cứ ai đang đọc này, nhân bản đó sẽ tiếp tục tham chiếu đến đối tượng ban đầu. Chạy các truy vấn MySQL sử dụng đối tượng nhân bản có thể có kết quả không thể đoán trước vì điều này, vì việc thực thi có thể không diễn ra theo kiểu tuyến tính.
Ælex

20
Để sửa một quan niệm sai lầm phổ biến (tôi nghĩ ngay cả các tài liệu PHP cũng hiểu sai!) Các đối tượng của PHP 5 không được "chuyển qua tham chiếu". Như trong Java, chúng có một mức độ bổ sung bổ sung - biến chỉ đến một "con trỏ đối tượng" và trỏ đến một đối tượng. Do đó, hai biến có thể trỏ đến cùng một đối tượng mà không cần tham chiếu đến cùng một giá trị. Điều này có thể được nhìn thấy từ ví dụ này: $a = new stdClass; $b =& $a; $a = 42; var_export($b);đây $blà một tham chiếu đến biến $a ; nếu bạn thay thế =&bằng một bình thường =, nó không phải là một tham chiếu và vẫn trỏ đến đối tượng ban đầu.
IMSoP

Thời gian chạy qua tham chiếu là một ý tưởng tồi, bởi vì nó làm cho hiệu quả của một lệnh gọi hàm phụ thuộc vào việc thực hiện hàm, hơn là vào đặc tả. Không có gì để làm với việc vượt qua bởi giá trị là mặc định.
Oswald

1
@Alex Bạn có thể giải thích về nhận xét của bạn? (Hoặc ở đây hoặc ở nơi khác.) Quan điểm của bạn đưa ra một chút IMO không rõ ràng.
Chris Middleton

@ChrisMiddleton Hãy nghĩ về các điều khoản của C hoặc C ++: nếu bạn đã sao chép một tham chiếu đến một đối tượng là miễn phí, ngoài phạm vi hoặc được phát hành, thì tham chiếu nhân bản của bạn bị vô hiệu. Do đó, bạn có thể nhận được hành vi không xác định tùy thuộc vào những gì đã xảy ra với đối tượng ban đầu mà bạn giữ một tham chiếu thông qua nhân bản.
Ælex

103

Các câu trả lời thường được tìm thấy trong sách Java.

  1. Nhân bản: Nếu bạn không ghi đè phương thức nhân bản, hành vi mặc định là bản sao nông. Nếu các đối tượng của bạn chỉ có các biến thành viên nguyên thủy, nó hoàn toàn ổn. Nhưng trong một ngôn ngữ không chữ với một đối tượng khác là các biến thành viên, đó là một vấn đề đau đầu.

  2. tuần tự hóa / giải tuần tự hóa

$new_object = unserialize(serialize($your_object))

Điều này đạt được bản sao sâu với chi phí nặng tùy thuộc vào độ phức tạp của đối tượng.


4
+1 cách tuyệt vời, tuyệt vời, tuyệt vời để tạo một bản sao DEEP trong PHP, cũng rất dễ dàng. Thay vào đó, hãy hỏi tôi một vài điều về bản sao nông tiêu chuẩn được cung cấp bởi từ khóa clone PHP, bạn nói rằng chỉ các biến thành viên nguyên thủy mới được sao chép: các mảng / chuỗi PHP được coi là biến thành viên nguyên thủy, vì vậy chúng có bị sao chép không?
Marco Demaio

3
Đối với bất kỳ ai nhặt thứ này: một bản sao "nông" ( $a = clone $bkhông có __clone()phương thức ma thuật nào trong trò chơi) tương đương với việc xem xét từng thuộc tính của đối tượng $bvà gán cho cùng một thuộc tính trong một thành viên mới của cùng một lớp, sử dụng =. Các thuộc tính là các đối tượng sẽ không nhận được cloned, cũng như các đối tượng bên trong một mảng; điều tương tự cũng xảy ra với các biến bị ràng buộc bởi tham chiếu; mọi thứ khác chỉ là một giá trị và được sao chép giống như với bất kỳ nhiệm vụ nào.
IMSoP

3
Hoàn hảo! json_decode (json_encode ($ obj)); không sao chép các thuộc tính riêng tư / được bảo vệ và bất kỳ phương thức nào ... unserialize (tuần tự hóa các phương thức không sao chép quá ...
zloctb

Tuyệt vời! Cuối cùng tôi đã thoát khỏi lỗi của PhpStorm; Call to method __clone from invalid context:)
numediaweb

Bạn bè đang gặp lỗi phân tích cú pháp PHP khi thực hiện như sau: $new_date = (clone $date_start)->subDays(1);Nó không thành công (), nếu tôi xóa chúng, tôi sẽ gặp một lỗi khác. Vấn đề là, chúng tôi sử dụng cùng một php 7.2.3 và tôi hoạt động tốt. Có ý kiến ​​gì không? Tìm kiếm ở khắp mọi nơi ..
emotality

21

Theo nhận xét trước đó, nếu bạn có một đối tượng khác là biến thành viên, hãy làm như sau:

class MyClass {
  private $someObject;

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

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

}

Bây giờ bạn có thể thực hiện nhân bản:

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


4

Chỉ cần làm rõ PHP sử dụng bản sao trên ghi, vì vậy về cơ bản mọi thứ đều là tham chiếu cho đến khi bạn sửa đổi nó, nhưng đối với các đối tượng bạn cần sử dụng phương thức ma thuật clone và __clone () như trong câu trả lời được chấp nhận.


1

Mã này giúp phương pháp nhân bản

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

mã này hơi vô dụng, nó sẽ hoạt động ngay cả khi bạn xóa phương thức __clone :)
amik

1

Tôi đã làm một số thử nghiệm và nhận được điều này:

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

Trong ví dụ này, chúng tôi sẽ tạo lớp iPhone và tạo bản sao chính xác từ nó bằng cách nhân bản

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

Nếu bạn muốn sao chép đầy đủ các thuộc tính của một đối tượng trong một trường hợp khác, bạn có thể muốn sử dụng kỹ thuật này:

Tuần tự hóa nó thành JSON và sau đó tái tuần tự hóa nó trở lại Object.


7
Hmm tôi sẽ tránh nó như địa ngục.
Jimmy Kane
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.