您能否按值显式复制包含“引用”元素的 PHP 数组


Can you explicitly copy a PHP array containing "referenced" elements by value?

对 PHP 数组元素的引用背景

假设您使用嵌套的 PHP 数组创建一个复杂的数据结构,如下所示:

$a1 = array(
    'b' => array('foo' => 1),
    'c' => array('bar' => 1)
);

想象一下,数组嵌套得更深,具有更多的元素和更长、更有意义的名称。

如果需要经常访问 $a 1 的子结构以进行读写,则可能会想创建一个这样的"别名":

$b = &$a1['b'];

然而,这导致了巨大的混乱,因为"分配"实际上改变了$a 1

我认为许多没有经验的PHP开发人员(像我一样)会认为$b是在分配后对$a 1['b']的引用。真正发生的是$b和$a 1['b']都变成了对元素array('foo' => 1)的引用,产生了意想不到的后果。我觉得这很不直观。

假设您需要按原样保留 $a 1,但您还需要 $a 1 的副本,我们将副本称为 $a 2,并更改 $a 2 的一些元素:

$a2 = $a1;           // Copy $a1 to $a2
$a2['b']['foo'] = 2; // GOTCHA! This will change $a1['b']['foo'] as well!
$a2['c']['bar'] = 2; // This will not change $a1['c']['bar'] however

因为我们之前创建了对 $a 1['b'] 的引用,所以该元素将通过引用分配,而 $a 1 的其余部分则按值复制。这花了我几个小时才弄清楚。

var_dump($a1);
var_dump($a2);

将输出

array (size=2)
  'b' => &
    array (size=1)
      'foo' => int 2
  'c' => 
    array (size=1)
      'bar' => int 1
array (size=2)
  'b' => &
    array (size=1)
      'foo' => int 2
  'c' => 
    array (size=1)
      'bar' => int 2

请注意"b"元素后面的 &,表示它是一个引用。并且 $a 1['b']['foo'] 的值从 1 更改为 2。

我的问题

当引用源数组中的元素时,是否有一种万无一失的方法按值复制数组

或者换句话说:有没有办法确保在将$a 1复制到$a 2时,$a 1['b']是按值复制的,以便更改$a 2['b']不会破坏$a 1['b']?

我发现你可以这样做:

unset($b);      // Remove reference to $a1['b']
$a2 = $a1;      // Now all of $a1 will be copied to $a2 by value
$b = &$a1['b']; // Recreate reference

当然,如果有更多对 $a 1['b'] 或 $a 1 的其他元素的引用,则需要将它们全部取消设置。我希望有一种更聪明的方法,例如对=&(引用赋值)运算符的"逆"或按值递归复制深度嵌套的关联数组的函数。即使创建了对您不知道的数组元素的引用,也始终有效。

这是我迄今为止找到的最接近的解决方案。它似乎有效,但它并不"漂亮":

$a2 = json_decode(json_encode($a1), true);

完成的研究

我的问题与SO上的其他几个问题有关。但是,我还没有找到我具体问题的答案。甚至是对这种奇怪和意外行为背后的机制的直观理解的答案。例如,PHP中数组引用混淆的答案引用了PHP手册,其中说:

但请注意,数组中的引用可能是 危险。使用 右侧的引用不会将左侧变成 引用,但数组中的引用保留在这些普通中 作业。

我已经阅读了手册的部分,发现它没有帮助。它没有提供更深入的理解,也没有解释如何安全地使用对数组元素的引用。PHP手册页What References Do上的一条评论提到了这种奇怪的行为,但没有就如何处理它提出建议。

这只是那些记录不佳的PHP"怪癖"之一,你只需要学会忍受吗?或者对这个主题有更深入的理解会有所帮助,在这种情况下,我最有可能在哪里找到启蒙?

你做了很好的研究。

只是不明白确切的问题是什么?

我想我找到了另一个非常接近您的 json 编码解码变体的解决方案:

有没有办法确保在将$a 1复制到$a 2时,$a 1['b']是按值复制的,以便更改$a 2['b']不会破坏$a 1['b']?

请检查:

$a1 = array(
    'b' => array('foo' => 1),
    'c' => array('bar' => 1)
);
$b = &$a1['b'];
$a2 = unserialize(serialize($a1));           // Copy $a1 to $a2
$a2['b']['foo'] = 2; // GOTCHA! 
$a2['c']['bar'] = 2; // 
var_dump($a1);
var_dump($a2);

这为我输出:

array(2) {
  ["b"]=>
  &array(1) {
    ["foo"]=>
    int(1)
  }
  ["c"]=>
  array(1) {
    ["bar"]=>
    int(1)
  }
}
array(2) {
  ["b"]=>
  array(1) {
    ["foo"]=>
    int(2)
  }
  ["c"]=>
  array(1) {
    ["bar"]=>
    int(2)
  }
}

这是你要求的吗?

顺便说一下,这里有一个比较json_encode与序列化的链接可能会有所帮助

存储 PHP 数组的首选方法(json_encode vs 序列化)