PHP不设置影响全局作用域的局部引用


php unset local reference affecting global scope

我遇到了一些非常奇怪的PHP行为(ubuntu 10.04的5.3.2)。本应在局部作用域中发生的取消设置影响了调用方函数的作用域。下面的代码片段是对我的代码的简化,我只能假设它显示了一个bug:

<?php
function should_not_alter($in)
{
    $in_ref =& $in['level1'];
    should_only_unset_locally($in);
    return $in;
}
function should_only_unset_locally($in)
{
    unset($in['level1']['level2_0']);
}
$data = array('level1' => array('level2_0' => 'first value', 'level2_1' => 'second value'));
$data = should_not_alter($data); //test 1
//should_only_unset_locally($data); //test 2
print_r($data);
?>

如果您运行上面的命令,您将看到值'first value'已经从全局作用域中的$data数组中取消设置。但是,如果您注释掉test 1并运行test 2,则不会发生这种情况。

i只能假设PHP不喜欢引用数组中的元素。在我的代码,我需要改变$in_ref -因此$in_ref =& $in['level1'];行在上面的代码的原因。我意识到删除这一行将解决'first value'在全局作用域中未设置的问题,但这不是一个选项。

谁能确认这是否是php的预期行为?

我怀疑这是一个bug,而不是一个特性,因为这种行为与PHP处理普通(非数组)变量的作用域和引用的方式不一致。例如,使用字符串而不是数组函数should_only_unset_locally()对全局作用域没有影响:

<?php
function should_not_alter($in)
{
    $in_ref =& $in;
    should_only_unset_locally($in);
    return $in;
}
function should_only_unset_locally($in)
{
    unset($in);
}
$data = 'original';
$data = should_not_alter($data); //test 1
//should_only_unset_locally($data); //test 2
print_r($data);
?>

test1或test2都输出original。实际上,即使$data是一个数组,但$in_ref被引用到整个数组(即$in_ref =& $in;),那么错误的行为就消失了。

我已经提交了一个bug报告

是啊,看起来像个bug。

正如函数名所暗示的那样,should_not_alter不应该改变数组,因为它是按值传递的。(当然,我不是仅仅基于名字——它也不应该根据它的定义改变任何东西。)

注释$in_ref =& $in['level1'];使它离开$in的事实似乎进一步证明它是一个bug。这是一个相当奇怪的小怪癖。不知道内部发生了什么会导致这种情况。

我会在PHP bug跟踪器上提交一个bug报告。不管怎样,它在5.4.6中仍然存在。

$data = should_not_alter($data)

这一行用should_not_alter的返回值$in覆盖$data数组。这是正常的行为。

同样,当你创建一个引用$in_ref =& $in['level1'];,但你没有做任何事情。对程序输出没有影响

短答:

在调用should_only_unset_locally()函数之前通过unset($in_ref)删除引用变量

长答:

创建对数组元素的引用时,将数组元素替换为引用。这种行为很奇怪,但它不是一个bug——这是语言的一个特性,也是设计出来的。

考虑以下PHP程序:

<?php
$a = array(
    'key1' => 'value1',
    'key2' => 'value2',
);
$r = &$a['key1'];
$a['key1'] = 'value3';
var_dump($a['key1']);
var_dump($r);
var_dump($a['key1'] === $r);
Output:
string(6) "value3"
string(6) "value3"
bool(true)

$a['key1']赋值会改变$r的值,因为它们引用了相同的值。相反,更新$r将更新数组元素:

$r = 'value4';
var_dump($a['key1']);
var_dump($r);
Output:
string(6) "value4"
string(6) "value4"

值不在$r$a['key']中-它们只是引用。就好像它们都在引用某个诡异的隐藏值。奇怪,是吧?

对于大多数用例,这是期望和有用的行为。

现在将其应用到程序中。下面一行修改了本地$in数组,并用引用替换了'level1'元素:

$in_ref = &$in['level1'];

$in_ref不是对$in['level1']的引用——相反,它们都引用了同一个怪异的值。所以当这行出现时:

unset($in['level1']['level2_0']);

PHP将$in['level1']视为对一个怪异值的引用,并删除'level2_0'元素。因为它是一个引用,所以在should_not_alter()函数的范围内也可以感受到删除。

你的特殊问题的解决方案是销毁引用变量,这将自动恢复$in['level1']的正常行为:

function should_not_alter($in) {
    $in_ref =& $in['level1'];
    // Do some stuff with $in_ref
    // After you're done with it delete the reference to restore $in['level1']
    unset($in_ref);
    should_only_unset_locally($in);
    return $in;
}