如何在PHP中克隆一个对象数组?

69

我有一个对象数组。我知道对象是通过“引用”分配的,而数组是通过“值”分配的。但是当我分配数组时,每个数组元素都引用对象,因此当我在数组中修改对象时,另一个数组中的更改也会反映出来。

是否有简单的方法可以克隆一个数组,或者必须循环遍历每个对象以克隆它们?


2
对象的引用是按值分配的。 - BoltClock
16个回答

67
$array = array_merge(array(), $myArray);

2
这实际上有什么不同吗?$array = $myArray?请参阅https://dev59.com/DXI_5IYBdhLWcg3wFu_L - George Lund
7
不不不,这没有任何区别。请参见http://www.php.net/manual/en/function.array-merge.php#92346 - Andrew Larsson
3
在这个解决方案中,我们从 $myArray 中丢失了原始的数字键:"在结果数组中,具有数值键的输入数组中的值将使用从零开始递增的键重新编号。" - TomaszKane
61
这并不是对数组对象进行克隆(就像问题中所要求的那样)!因此,我不理解这个答案以及为什么有很高的投票?这几乎与$arrB = $arrA相同,只是数值键被重新索引了。 - flori
2
最聪明的做法是:仔细阅读问题,并意识到这个答案并没有解决问题所要求的内容 - 这些评论中提供的变体也不行! - ToolmakerSteve
显示剩余4条评论

62

在复制数组时,引用相同对象的引用已经被复制了。但是看起来你想要在创建第二个数组时浅拷贝深拷贝第一个数组中被引用的对象,这样你就可以得到两个具有不同但相似对象的数组。

我目前能想到的最直观的方法是循环;可能还有更简单或更优雅的解决方案:

$new = array();

foreach ($old as $k => $v) {
    $new[$k] = clone $v;
}

13
他现在正在进行的是"浅复制(shallow copying)",他想要进行"深复制(deep copy)"。你的代码是正确的,但术语不对。 - kba
@Kristian Antonsen:我不是只浅复制了数组,而是浅复制了对象吗? - BoltClock
2
@BoltClock:无论你是浅复制对象还是深复制对象,这取决于特定对象的克隆实现方式;这由对象的设计者而非用户来决定。例如,如果数组中的一个对象包含对另一个对象的引用,并且没有实现适当的__clone函数,则仍然只会进行浅复制。换句话说:如果你要克隆的对象的开发人员做得对,那么它将是一个深层复制。 - kba
2
我明白了,我忘记了PHP提供了一个__clone()方法来实现深拷贝,并且一直以为clone的默认行为是浅拷贝。 - BoltClock
@DisgruntledGoat,仅供参考:clone不能总是创建独立的副本,请阅读手册“任何作为对其他变量的引用的属性都将保持为引用。” - OZ_
显示剩余2条评论

26

为了避免引用同一个对象,您需要克隆对象。

function array_copy($arr) {
    $newArray = array();
    foreach($arr as $key => $value) {
        if(is_array($value)) $newArray[$key] = array_copy($value);
        else if(is_object($value)) $newArray[$key] = clone $value;
        else $newArray[$key] = $value;
    }
    return $newArray;
}

简单而有效。我将其用作静态函数。 - John P
+1 我无意中窃取了你的函数:https://dev59.com/DXI_5IYBdhLWcg3wFu_L#17729234。我已经多次访问过这个页面,但我总是滚动查找那些高票答案(从而跳过了你的答案)。这个函数几乎完全相同,而且我直到现在都没有看过你的答案! - Andrew Larsson

24

根据AndreKR的建议,如果您已经知道您的数组包含对象,那么使用array_map()是最佳选择:

$clone = array_map(function ($object) { return clone $object; }, $array);

6

我也选择克隆。直接克隆数组是行不通的(你可以考虑实现一些数组访问来替代),所以对于array_map中的array clone

class foo {
    public $store;
    public function __construct($store) {$this->store=$store;}
}

$f = new foo('moo');
$a = array($f);

$b = array_map(function($o) {return clone $o;}, $a);

$b[0]->store='bar';    
var_dump($a, $b);

使用序列化和反序列化进行数组克隆

如果您的对象支持序列化,您甚至可以通过将它们转换为睡眠状态再转回来,实现一种深浅拷贝/克隆:

$f = new foo('moo');
$a = array($f);

$b = unserialize(serialize($a));

$b[0]->store='bar';
var_dump($a, $b);

然而,这可能有点冒险。

4

纯 PHP 7.4及以上版本的解决方案:

$cloned = array_map(fn ($o) => clone $o, $original);

2

以下是关于对象数组和克隆的最佳实践。通常,为每种对象(或接口)使用一个集合类是一个好主意,这些对象在数组中使用。使用魔术函数__clone,克隆变成了一种正式的例程:

class Collection extends ArrayObject
{
     public function __clone()
     {
        foreach ($this as $key => $property) {
            $this[$key] = clone $property;
        }
     }
}

要克隆您的数组,请将其用作集合,然后进行克隆:

$arrayObject = new Collection($myArray);
$clonedArrayObject = clone $arrayObject;

更进一步,您应该为您的类及其每个子类添加一个克隆方法。这对于深度克隆非常重要,否则可能会导致意外的副作用:

class MyClass
{
     public function __clone()
     {
        $this->propertyContainingObject = clone $this->propertyContainingObject;
     }
}

使用ArrayObject时需要注意的一点是,你不能再使用is_array()函数。因此,在重构代码时请注意这一点。

2
我这样做了:
function array_clone($array) {
    array_walk_recursive($array, function(&$value) {
        if(is_object($value)) {
            $value = clone $value;
        }
    });
    return $array;
}

函数arg会复制数组,但不会克隆对象,而是克隆每个嵌套的对象。因此,如果算法不在函数内使用,则无法正常工作。

请注意,此函数会递归克隆数组。如果您不希望发生这种情况,可以使用array_walk代替array_walk_recursive


2

你需要循环它 (可能使用像array_map()这样的函数),没有PHP函数可以自动执行数组的深度复制。


1

对于 PHP 5 及以上版本,可以使用 ArrayObject 构造函数来克隆数组,如下所示:

$myArray = array(1, 2, 3);
$clonedArray = new ArrayObject($myArray);

不需要显式地创建新对象,只需在原始对象上调用copy方法即可获得克隆。请参阅:https://www.php.net/manual/en/arrayobject.getarraycopy.php - Stefano Mtangoo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接