如何在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
不,还不需要使用它。
Nick Stinemates,

(object) ((array) $objectA)可能会比使用clone $objectA或带来更好的性能,从而获得相同的预期结果new stdClass
Binyamin

Answers:


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;如果替换=&为normal =,则它不是参考,并且仍指向原始对象。
IMSoP

通过引用运行时传递是一个坏主意,因为它使函数调用的效果取决于函数的实现,而不是规范。与默认值传递值无关。
奥斯瓦尔德(Oswald)

1
@Alex您能否详细说明您的评论?(无论在这里还是在其他地方。)您的意思是来自IMO不清楚的。
克里斯·米德尔顿

@ChrisMiddleton想一想C或C ++的术语:如果您已经克隆了对释放,超出范围或已释放的对象的引用,则克隆的引用将无效。因此,您可能会获得不确定的行为,具体取决于原始对象发生的情况,您可以通过克隆持有该对象。
Ælex

103

答案通常在Java书籍中找到。

  1. 克隆:如果不覆盖克隆方法,则默认行为是浅拷贝。如果您的对象只有原始成员变量,那完全可以。但是,在以另一个对象作为成员变量的无类型语言中,这很令人头疼。

  2. 序列化/反序列化

$new_object = unserialize(serialize($your_object))

根据对象的复杂程度,这会以沉重的成本实现深层复制。


4
+1很棒,很棒,很棒的方法来用PHP创建DEEP副本,也非常容易。让我问您有关PHP clone关键字提供的标准浅表副本的问题,您说过仅复制原始成员变量:PHP数组/字符串是否被视为原始成员变量,因此它们被复制了,对吗?
Marco Demaio 2010年

3
对于任何了解这一点的人:“浅”副本($a = clone $b,没有使用任何魔术__clone()方法)等效于查看$bterm 中对象的每个属性,并使用以下方法将其分配给相同类的新成员中=。作为对象的属性不会得到cloned,数组内的对象也不会得到d。对于引用绑定的变量也是如此;其他所有内容都只是一个值,并且像任何赋值一样被复制。
IMSoP

3
完善!json_decode(json_encode($ obj)); 不能克隆私有/受保护的属性以及任何方法... unserialize(序列化也不要克隆方法...
zloctb

太棒了!我终于摆脱了PhpStorm的错误;Call to method __clone from invalid context:)
numediaweb

Friend这样做时遇到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和__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
嗯,我会避免地狱。
吉米·凯恩
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.