Как создать копию объекта в PHP?

Похоже, что в 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'

?>

В обоих случаях для печати я получаю "после"

Итак, как мне передать $ а в set_b () по значению, а не по ссылке?

Очень мало случаев, когда вам действительно нужно такое поведение. Итак, если вы обнаружите, что часто используете его, то, возможно, есть что-то более фундаментальное неправильное в том, как вы пишете свой код?

troelskn 09.10.2008 12:03

Нет, пока не нужно было его использовать.

Nick Stinemates 09.10.2008 19:10

(object) ((array) $objectA) может дать вам те же желаемые результаты с лучшей производительностью, чем при использовании clone $objectA или new stdClass.

Binyamin 03.04.2019 02:17

Re «Кажется, даже операторы присваивания не создают копию объекта». - Надеюсь, что нет! Если бы они это сделали, результатом больше не было бы объектно-ориентированного языка (для всех практических целей).

ToolmakerSteve 21.07.2020 23:43
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
179
4
128 025
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Согласно документам (http://ca3.php.net/language.oop5.cloning):

$a = clone $b;

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

Bruno Guignard 28.01.2021 18:46
Ответ принят как подходящий

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

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

$objectB = clone $objectA;

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

Просто хочу добавить ко всем, кто это читает, что при клонировании будет сохраняться ссылка на исходный объект. Выполнение запросов MySQL с использованием клонированного объекта может привести к непредсказуемым результатам из-за этого, поскольку выполнение может происходить не линейно.

Ælex 26.03.2013 20:30

Чтобы исправить распространенное заблуждение (я думаю, что даже документация PHP ошибается!) Объекты PHP 5 не «передаются по ссылке». Как и в Java, они имеют уровень косвенности дополнительный - переменная указывает на «указатель объекта», а он указывает на объект. Таким образом, две переменные могут указывать на один и тот же объект без, являющийся ссылками на одно и то же значение. Это можно увидеть из этого примера: $a = new stdClass; $b =& $a; $a = 42; var_export($b); здесь $b - это ссылка на Переменная$a; если вы замените =& на нормальный =, это будет ссылка нет, которая по-прежнему будет указывать на исходный объект.

IMSoP 17.06.2013 01:14

Передача времени выполнения по ссылке - плохая идея, потому что из-за этого эффект вызова функции зависит от реализации функции, а не от спецификации. Это не имеет ничего общего с передачей по значению по умолчанию.

Oswald 04.10.2013 11:06

@Alex Не могли бы вы уточнить свой комментарий? (Либо здесь, либо где-нибудь еще.) Ваша точка зрения немного неясна, ИМО.

Chris Middleton 26.04.2015 06:11

@ChrisMiddleton Подумайте о терминах C или C++: если вы клонировали ссылку на объект, который свободен, вне области видимости или освобожден, то ваша клонированная ссылка становится недействительной. Таким образом, вы май получаете неопределенное поведение в зависимости от того, что произошло с исходным объектом, на который вы держите ссылку посредством клонирования.

Ælex 27.04.2015 20:55

@Alex Вы это проверяли? Я только что проверил возврат клона из функции (где оригинал выходит за рамки и не имеет проблем). php -r 'class Foo {} function getFoo () { $f = new Foo(); $f->bar = 5; return clone $f; } $f = getFoo(); var_dump($f);' Вы уверены, что не думаете о назначении по ссылке ($f2 = &$f1)?

Chris Middleton 27.04.2015 21:41

@ChrisMiddleton Я тестировал это давным-давно (PHP 5.4) в классе ORM и обнаружил, что это зависит от реализации класса. Clone, кажется, делает неглубокую копию, поэтому, если вы работаете с чем-то простым, все будет в порядке. Боюсь, что ничем не могу помочь, так как с тех пор, как я столкнулся с этой проблемой, прошло более двух лет. Объекты в PHP по сути являются указателями, поэтому, если исходный объект освобожден, вы столкнетесь с этой проблемой, упомянутой ранее.

Ælex 27.04.2015 21:45

Друг получает ошибку синтаксического анализа PHP, когда делает это, например: $new_date = (clone $date_start)->subDays(1); Он не работает с (), если я их удалю, я получаю другую ошибку. Дело в том, что мы используем тот же самый php 7.2.3, и мой работает нормально. Есть идеи? Искал везде ..

emotality 28.03.2018 17:33

@ Ælex - re "при клонировании будет сохранена ссылка на исходный объект" - Точнее, клонирование делает копию мелкий свойств объекта. Если объект содержит другие объекты, то эти вложенные объекты теперь будут иметь две ссылки на них. Иногда это именно то, что нужно, иногда это проблема. Чтобы решить эту проблему, класс объекта должен реализовать __clone(), как показано в ответе Станислава, и при необходимости исправить каждое поле.

ToolmakerSteve 21.07.2020 23:49

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

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

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

$new_object = unserialize(serialize($your_object))

Таким образом достигается глубокая копия с большими затратами, зависящими от сложности объекта.

+1 отличный, отличный, отличный способ сделать ГЛУБОКУЮ копию на PHP, тоже очень просто. Позвольте мне вместо этого спросить вас о стандартной мелкой копии, предлагаемой ключевым словом PHP clone, вы сказали, что копируются только примитивные переменные-члены: считаются ли массивы / строки PHP примитивными переменными-членами, поэтому они копируются, я прав?

Marco Demaio 11.07.2010 14:48

Для всех, кто это понимает: «неглубокая» копия ($a = clone $b, без задействованных магических методов __clone()) эквивалентна просмотру каждого свойства объекта $b в терминах и присвоению этому же свойству нового члена того же объекта. класс, используя =. Свойства, являющиеся объектами, не получат cloned, как и объекты внутри массива; то же самое касается переменных, связанных ссылкой; все остальное - это просто значение и копируется так же, как и при любом назначении.

IMSoP 17.06.2013 01:10

Идеальный! json_decode (json_encode ($ obj)); не клонировать частные / защищенные свойства и любой метод ... десериализовать (сериализовать, а не клонировать методы тоже ...

zloctb 04.10.2013 00:15

Потрясающий! Я наконец избавился от ошибки PhpStorm; Call to method __clone from invalid context :)

numediaweb 07.09.2015 11:33

Друг получает ошибку синтаксического анализа PHP, когда делает это, например: $new_date = (clone $date_start)->subDays(1); Он не работает с (), если я их удалю, я получаю другую ошибку. Дело в том, что мы используем тот же самый php 7.2.3, и мой работает нормально. Есть идеи? Искал везде ..

emotality 28.03.2018 17:33

Мы с коллегой постоянно сталкивались с этим. Клонирование не очень хорошо работает с более сложным объектом (постоянно возникают проблемы с обновлением неправильного элемента в памяти), но десериализация / сериализация работает. :) Спасибо за спасение!

cbloss793 03.06.2019 22:43

Это добавляет много накладных расходов времени выполнения. Вы сериализуете объект в строку, а затем анализируете эту строку обратно в новую переменную. Хотя это делает то, что вы собираетесь делать, это происходит очень медленно. Вы не только конвертируете весь свой объект в строку и обратно, используя время и память, вы также нарушаете возможный механизм CopyOnWrite PHP. Гораздо лучший способ - правильно реализовать метод __clone, как предлагает stackoverflow.com/a/186191/1614903 ниже. См. phpinternalsbook.com/php5/zval/memory_management.html для более подробного объяснения.

Holger Böhnke 20.01.2020 20:43

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

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;

Если вы хотите полностью скопировать свойства объекта в другом экземпляре, вы можете использовать этот метод:

Сериализуйте его в JSON, а затем десериализуйте обратно в Object.

Хм, я бы избегал этого как черт.

Jimmy Kane 09.12.2013 17:06

Этот код помогает клонировать методы

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 12.08.2016 13:31

Я проводил некоторое тестирование и получил следующее:

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" }

?>

Просто для пояснения, что PHP использует копию при записи, поэтому в основном все является ссылкой, пока вы ее не измените, но для объектов вам нужно использовать clone и магический метод __clone (), как в принятом ответе.

В этом примере мы создадим класс iPhone и сделаем его точную копию с помощью клонирование

class iPhone {

public $name;
public $email;

    public function __construct($n, $e) {

       $this->name = $n;
       $this->email = $e;

    }
}


$main = new iPhone('Dark', '[email protected]');
$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>";

Другие вопросы по теме