Як створити копію об'єкта в 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'

?>

В обох випадках друку я отримую "після"

Отже, як я можу передати $ a до set_b () за значенням, а не за посиланням?


2
Є дуже мало випадків, коли ви насправді хотіли б такої поведінки. Тож якщо ви часто знаходите себе, використовуючи це, то, можливо, в тому, як ви пишете свій код, є щось більш принципове не так?
— troelskn

1
Ні, ще не потрібно його використовувати.
— Нік Stinemates

(object) ((array) $objectA)може призвести до того ж бажаних результатів з кращою продуктивністю, ніж використання clone $objectAабо new stdClass.
— Біньямін

Відповіді:


284

У PHP 5+ об'єктів передаються за посиланням. У PHP 4 вони передаються за значенням (саме тому він мав пропускати час виконання посиланням, який став застарілим).

Ви можете використовувати оператор "клонування" в PHP5 для копіювання об'єктів:

$objectB = clone $objectA;

Крім того, це лише об'єкти, які передаються посиланням, а не все, як ви сказали у своєму питанні ...


Просто хочу додати кожному, хто це читає, що клонування буде зберігати посилання на оригінальний об'єкт. Запуск запитів MySQL з використанням клонованого об'єкта через це може мати непередбачувані результати, оскільки виконання може не відбуватися лінійно.
— Ælex

20
Щоб виправити поширене неправильне уявлення (я думаю, що навіть документи PHP помиляються!) Об'єкти PHP 5 не "передаються за посиланням". Як і в Java, вони мають додатковий рівень непрямості - змінна вказує на "вказівник об'єкта", і це вказує на об'єкт. Таким чином, дві змінні можуть вказувати на один і той же об'єкт, не посилаючись на одне значення. Це видно з цього прикладу: $a = new stdClass; $b =& $a; $a = 42; var_export($b);тут $bє посилання на змінну $a ; якщо ви заміните =&на звичайний =, він не є посиланням і все одно вказує на оригінальний об'єкт.
— IMSoP

Проходження виконання посилань є поганою ідеєю, оскільки це робить ефект виклику функції залежати від реалізації функції, а не від специфікації. Це не має нічого спільного з тим, що за промовчанням проходить значення.
— Освальд

1
@Alex Чи можете ви детальніше розглянути свій коментар? (Або тут, або деінде.) Ваша думка випливає дещо незрозумілою ІМО.
— Кріс Міддлтон

@ChrisMiddleton Подумайте про терміни C або C ++: якщо ви клонували посилання на об'єкт, який є вільним, вийшов за межі або вийшов, то ваша клонована посилання недійсна. Таким чином, ви можете отримати невизначену поведінку залежно від того, що сталося з оригінальним об'єктом, на який ви маєте посилання шляхом клонування.
— Ælex

103

Відповіді часто зустрічаються в книгах Java.

  1. клонування: Якщо ви не перекриєте метод клонування, поведінка за замовчуванням - це неглибока копія. Якщо у ваших об'єктів є лише примітивні змінні члена, це абсолютно нормально. Але в мові безтипу з іншим об'єктом як змінними-членами це головний біль.

  2. серіалізація / десеріалізація

$new_object = unserialize(serialize($your_object))

Це досягає глибокої копії з великою вартістю залежно від складності об'єкта.


4
+1 чудовий, чудовий, чудовий спосіб зробити копію DEEP у PHP, дуже просто. Дозвольте, замість того, запитаю вас про стандартну дрібну копію, запропоновану ключовим словом клонування PHP, ви сказали, що копіюються лише примітивні змінні члена: чи масиви / рядки PHP вважаються примітивними змінними членів, тому вони копіюються, я прав?
— Марко Демайо

3
Для кожного, хто забирає це: "неглибока" копія ( $a = clone $bбез магічних __clone()методів у грі) еквівалентна перегляду кожного з властивостей об'єкта $bв терміні та присвоєння цій же властивості новому члену того ж класу, використовуючи =. Властивості, що є об’єктами, не отримають cloned, а також об'єкти всередині масиву; те саме стосується змінних, пов'язаних посиланням; все інше - лише цінність, і копіюється так само, як і з будь-яким завданням.
— IMSoP

3
Ідеально! json_decode (json_encode ($ obj)); не клонувати приватні / захищені властивості та будь-який метод ... несеріалізувати (серіалізувати також не методи клонування ...
— 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, а потім десертуйте його назад до Object.


7
Хм, я б уникнув цього, як пекла.
— Джиммі Кейн
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.