Как мне создать копию объекта в 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 () по значению, а не по ссылке?

Ник Стинамантес
источник
3
Есть очень мало случаев, когда вы бы на самом деле хотели такого поведения. Так что, если вы обнаружите, что используете его часто, то, возможно, что-то более фундаментальное неправильно в том, как вы пишете свой код?
troelskn
1
Нет, еще не нужно было его использовать.
Ник Stinemates
(object) ((array) $objectA)может привести к таким же желаемым результатам с лучшей производительностью, чем использование clone $objectAили new stdClass.
Биньямин
Re «Даже операторы присваивания, кажется, не создают копию объекта». - Надеюсь, нет! Если они это сделают, результатом больше не будет язык ОО (для всех практических целей).
ToolmakerSteve

Ответы:

284

В PHP 5+ объекты передаются по ссылке. В PHP 4 они передаются по значению (поэтому во время выполнения он передавался по ссылке, что устарело).

Вы можете использовать оператор «клон» в PHP5 для копирования объектов:

$objectB = clone $objectA;

Кроме того, это просто объекты, которые передаются по ссылке, а не все, как вы сказали в своем вопросе ...

Эран Гальперин
источник
Просто хочу добавить к любому, кто читает это, что клонирование сохранит ссылку на исходный объект. Из-за этого выполнение запросов MySQL с использованием клонированного объекта может привести к непредсказуемым результатам, поскольку выполнение может происходить не линейно.
13:30
20
Чтобы исправить распространенное заблуждение (я думаю, что даже документы PHP ошибаются!) Объекты PHP 5 не «передаются по ссылке». Как и в Java, они имеют дополнительный уровень косвенности - переменная указывает на «указатель объекта», а это указывает на объект. Таким образом, две переменные могут указывать на один и тот же объект, не будучи ссылками на одно и то же значение. Это видно из этого примера: $a = new stdClass; $b =& $a; $a = 42; var_export($b);здесь $bссылка на переменную $a ; если вы замените =&его нормальным =, это не ссылка, а указатель на исходный объект.
IMSoP
Передача по ссылке во время выполнения - плохая идея, потому что она делает эффект вызова функции зависящим от реализации функции, а не от спецификации. Это не имеет ничего общего с передачей по значению по умолчанию.
Освальд
1
@ Алекс Можете ли вы уточнить свой комментарий? (Либо здесь, либо в другом месте.) Ваша точка зрения немного неясна ИМО.
Крис Миддлтон
@ChrisMiddleton Подумайте о терминах C или C ++: если вы клонировали ссылку на объект, находящийся в свободном доступе, находящийся вне области действия или освобожденный, то ваша клонированная ссылка становится недействительной. Таким образом, вы можете получить неопределенное поведение в зависимости от того, что произошло с исходным объектом, на который вы держите ссылку посредством клонирования.
Aprlex
104

Ответы обычно можно найти в книгах по Java.

  1. клонирование: если вы не переопределяете метод клонирования, по умолчанию используется мелкая копия. Если ваши объекты имеют только примитивные переменные-члены, это нормально. Но в языке без типов с другим объектом в качестве переменных-членов это головная боль.

  2. сериализации / десериализации

$new_object = unserialize(serialize($your_object))

Это позволяет получить глубокое копирование с большими затратами в зависимости от сложности объекта.

yogman
источник
4
+1 отличный, отличный, отличный способ сделать глубокую копию на PHP, тоже очень просто. Позвольте мне вместо этого спросить вас о стандартной мелкой копии, предлагаемой ключевым словом PHP clone, вы сказали, что копируются только примитивные переменные-члены: считаются ли массивы / строки PHP примитивными переменными-членами, поэтому они копируются, я прав?
Марко Демайо
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
1
Это добавляет много времени выполнения. Вы сериализуете объект в строку, а затем анализируете эту строку обратно в новую переменную. Хотя это и делает то, что вы намереваетесь сделать, оно делает это очень медленно. Вы не только конвертируете весь свой объект в строку и обратно, используя время и память, вы также нарушаете PHP-механизм CopyOnWrite. Гораздо лучший способ - правильно реализовать ваш __cloneметод, как указано в stackoverflow.com/a/186191/1614903 ниже. См. Phpinternalsbook.com/php5/zvals/memory_management.html для подробного объяснения
Хольгер Бонке
22

Согласно предыдущему комментарию, если у вас есть другой объект в качестве переменной-члена, сделайте следующее:

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 использует copy при записи, поэтому в основном все является справочной, пока вы не измените ее, но для объектов вам нужно использовать clone и магический метод __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());
zloctb
источник
этот код немного бесполезен, он будет работать, даже если вы удалите метод __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" }

?>
Pyetro
источник
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.

diy_nunez
источник
7
Хм, я бы избежал этого, как ад.
Джимми Кейн