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