【问题标题】:How to clone an array of objects in PHP?如何在 PHP 中克隆对象数组?
【发布时间】:2011-09-19 02:53:33
【问题描述】:

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

有没有一种简单的方法来克隆一个数组,或者我必须循环遍历它来克隆每个对象?

【问题讨论】:

  • 对对象的引用是按值分配的。

标签: php arrays object clone


【解决方案1】:
$array = array_merge(array(), $myArray);

【讨论】:

  • 这很聪明!比 foreach 循环干净得多!
  • 这实际上与 $array = $myArray 不同吗?见stackoverflow.com/questions/1532618/…
  • @GeorgeLund 不,这没有什么不同。见php.net/manual/en/function.array-merge.php#92346
  • 在这个解决方案中,我们丢失了 $myArray 中的原始数字键:“输入数组中带有数字键的值将使用从结果数组中的零开始递增的键重新编号。”
  • 这不是克隆数组中的对象(就像被问到的那样)!所以我不明白这个答案和高投票?它几乎与 $arrB = $arrA 相同,只是数字键正在重新索引。
【解决方案2】:

复制数组时已经复制了对相同对象的引用。但听起来您想在创建第二个数组时 shallow-copy 深度复制第一个数组中引用的对象,因此您会得到两个不同但相似的对象数组。

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

$new = array();

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

【讨论】:

  • 他现在做的是浅拷贝,他要深拷贝。您的代码是正确的,但您的术语却不正确。
  • @Kristian Antonsen:我不是浅拷贝对象而不仅仅是浅拷贝数组吗?
  • @BoltClock:是否对对象进行浅拷贝取决于具体对象的克隆实现;这取决于对象的设计者,而不是用户。例如,如果数组中的一个对象包含对另一个对象的引用并且没有实现正确的__clone 函数,那么它仍然只是一个浅拷贝。换句话说:如果您要克隆的对象的开发人员做得对,那么它将是一个深层副本。
  • @BoltClock 不,你深度复制数组和数组元素。无论如何,您的答案代码是正确的。
  • 知道了,我忘了 PHP 提供了一个 __clone() 方法来实现深拷贝,并假设默认的浅拷贝行为是 clone。
【解决方案3】:

您需要克隆对象以避免引用同一对象。

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

【讨论】:

  • 简单而有效。我将它用作静态函数。
  • +1 我不小心偷走了你的功能,却没有意识到:stackoverflow.com/a/17729234/1134804。我已经多次访问此页面,但我总是向下滚动,直到找到一些高票数的答案(因此跳过你的)。几乎是一模一样的功能,直到现在我才看你的答案!
【解决方案4】:

正如 AndreKR 所建议的,如果您已经知道您的数组包含对象,那么使用 array_map() 是最好的方法:

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

【讨论】:

    【解决方案5】:

    我也选择了克隆。克隆一个数组不起作用(你可以考虑一些 arrayaccess 实现为你这样做),对于带有 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);
    

    不过,这可能有点冒险。

    【讨论】:

      【解决方案6】:

      纯 PHP 7.4 >= 解决方案:

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

          我是这样做的:

          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。

          【讨论】:

            【解决方案9】:

            这是我在对象数组和克隆方面的最佳实践。通常,为数组中使用的每个对象(或接口)类拥有一个 Collection 类是一个好主意。有了神奇的功能__clone,克隆就变成了正式的例程:

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

            要克隆您的数组,请将其用作 Collection,然后克隆它:

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

            更进一步,您应该为您的类和每个子类添加一个克隆方法。这对于深度克隆很重要,否则您可能会产生意想不到的副作用:

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

            使用 ArrayObject 的一个重要注意事项是,您不能再使用 is_array()。所以在重构代码时要注意这一点。

            【讨论】:

              【解决方案10】:

              默认情况下,对象是通过指向传递的,并且并不总是容易克隆,尤其是因为它们可能具有循环引用。您会更适合选择不同的数据结构。

              对于那些提供浅拷贝解决方案的人来说,更简单的方法是:

               $b = (array)$a;
              

              对于深拷贝,我不推荐这种解决方案:

              $nuarr = json_decode(json_encode($array));

              这是一个深拷贝。它仅支持 PHP 类型的子集,并将对象交换为数组或数组交换为可能不是您想要的对象以及可能破坏二进制值等等。

              如果您为深层副本创建手动递归函数,那么之后对于标量值和键的内存使用量将会大大减少,因此使用 json 或任何序列化程序会产生超出其执行点的影响。

              如果性能不是问题,它可能会更好地使用 unserialize(serialize($a)) 进行深层复制,因为它对对象等事物有更广泛的支持,但如果它因循环引用和其他几个原因而中断,我不会感到惊讶不寻常的事情。

              array_merge_recursive 或 array_walk_recursive 也可用于数组。

              您可以轻松创建自己的递归函数,使用 is_object 和 is_array 来选择适当的复制方式。

              【讨论】:

                【解决方案11】:

                对于 PHP 5 及以上版本,可以使用ArrayObject cunstructur 克隆如下数组:

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

                【讨论】:

                【解决方案12】:

                也可以

                $nuarr = json_decode(json_encode($array));
                

                但是很贵,我更喜欢 Sebastien 版本(array_map)

                【讨论】:

                  【解决方案13】:

                  如果你有多维数组或由对象和其他值组成的数组你可以使用这个方法:

                  $cloned = Arr::clone($array);
                  

                  来自that library。

                  【讨论】:

                    【解决方案14】:

                    只需将此函数包含在您的所有类中即可。如果您在对象本身中有对象数组,这将对所有对象进行深度克隆。它将触发这些类中的所有__clone() 函数:

                    /**
                     * Clone the object and its properties
                     */
                    public function __clone()
                    {
                        foreach ($this as $key => $property)
                        {
                            if(is_array($property))
                            {
                                foreach ($property as $i => $o)
                                {
                                    if(is_object($o)) $this->$key[$i] = clone $o;
                                    else $this->$key[$i] = $o;
                                }
                            }
                            else if(is_object($property)) $this->$key = clone $property;
                            else $this->$key = $property;
                        }
                    }
                    

                    【讨论】:

                      【解决方案15】:
                      $a = ['a'=>'A','b'=>'B','c'=>'C'];
                      $b = $a+[];
                      $a['a'] = 'AA'; // modifying array $a
                      var_export($a);
                      var_export($b); 
                      

                      结果:

                      array ( 'a' => 'AA', 'b' => 'B', 'c' => 'C', )
                      array ( 'a' => 'A', 'b' => 'B', 'c' => 'C', )
                      

                      【讨论】:

                      • 这些数组中的元素是标量值,所以不是对象引用。这没有回答问题。
                      • 如果您在数组中包含所有标量值,或者您希望在对象的情况下进行浅拷贝,这是一个很好的解决方案。
                      【解决方案16】:

                      我更喜欢递归方式:

                      function deepClone(mixed $object): mixed
                      {
                          switch (gettype($object)) {
                              case 'object':
                                  return clone $object;
                      
                              case 'array':
                                  $ret = [];
                                  foreach ($object as $key => $item) {
                                      $ret[$key] = deepClone($item);
                                  }
                                  return $ret;
                      
                              default:
                                  return $object;
                          }
                      }
                      
                      deepClone($array);
                      

                      【讨论】:

                      • 请不要只发布代码作为答案,还要解释您的代码的作用以及它如何解决问题的问题。带有解释的答案通常更有帮助、质量更好,并且更有可能吸引投票
                      猜你喜欢
                      • 2010-10-10
                      • 2018-05-26
                      • 1970-01-01
                      • 1970-01-01
                      • 2021-01-07
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多