PHP 中的数组访问 -- 通过引用分配偏移

2022-08-31 01:14:07

首先,引用 ArrayAccess::offsetSet() 上的 ole' 手册:

在赋值中,此函数不是通过引用调用的,也不会间接更改数组维度,而数组维度重载了 ArrayAccess(间接地不是通过直接更改维度,而是通过更改子维度或子属性或通过引用另一个变量来分配数组维度来实现的)。相反,调用 ArrayAccess::offsetGet()。仅当该方法通过引用返回时,操作才会成功,这仅在 PHP 5.3.4 之后才有可能

我对此有点困惑。这似乎表明(从5.3.4开始),可以在实现类中定义通过引用返回,从而通过引用处理赋值。offsetGet()

所以,现在是一个测试片段:

(忽略缺少验证和 isset() 检查)

class Test implements ArrayAccess
{
    protected $data = array();

    public function &offsetGet($key)
    {
        return $this->data[$key];
    }

    public function offsetSet($key, $value)
    {
        $this->data[$key] = $value;
    }

    public function offsetExists($key) { /* ... */ }

    public function offsetUnset($key) { /* ... */ }

}

$test = new Test();

$test['foo'] = 'bar';
$test['foo'] = &$bar; // Fatal error: Cannot assign by reference to
                      // overloaded object in

var_dump($test, $bar);    

好吧,所以这不起作用。那么这个手册笔记指的是什么呢?

原因
我想允许通过数组运算符通过引用分配给实现的对象,如示例代码段所示。我以前调查过这个问题,我不认为这是可能的,但是由于不确定性而回到这一点,我(重新)在手册中发现了这一点。现在我只是感到困惑。ArrayAccess


更新:当我点击时,我意识到这可能只是通过引用另一个变量来指代赋值,例如.如果是这样的话,那么道歉;尽管知道如何(如果可能的话)通过引用来分配重载对象将是很棒的。Post Your Question$bar = &$test['foo'];


进一步详细说明:归根结底,我希望具有以下方法别名:

isset($obj[$key]);       // $obj->has_data($key);

$value = $obj[$key];     // $obj->get_data($key);

$obj[$key] = $value;     // $obj->set_data($key, $value);

$obj[$key] = &$variable; // $obj->bind_data($key, $variable);

// also, flipping the operands is a syntactic alternative
$variable = &$obj[$key]; // $obj->bind_data($key, $variable);

unset($obj[$key]);       // $obj->remove_data($key);

就 、 、 和 而言,它们与 受支持的方法没有问题。绑定功能是我不知所措的地方,我开始接受ArrayAccess和PHP的限制只是禁止这样做。hasgetsetremoveArrayAccess


答案 1

手册所指的所谓“间接修改”。请考虑以下脚本:

$array = new ArrayObject;
$array['foo'] = array();
$array['foo']['bar'] = 'foobar';

在上面的脚本中将触发 . 另一方面将触发.为什么会这样?最后一行将大致如下:$array['foo'] = array();offsetSet('foo', array())$array['foo']['bar'] = 'foobar';offsetGet('foo')

$tmp =& $array['foo'];
$tmp['bar'] = 'foobar';

因此,首先由ref获取,然后进行修改。如果您通过 ref 返回,这将成功。否则,您将收到一些间接修改错误。$array['foo']offsetGet


另一方面,你想要的恰恰相反:不是通过引用获取值,而是分配它。这在理论上需要签名,但实际上这是不可能的offsetSet($key, &$value)

顺便说一句,参考文献很难掌握。你会得到很多不明显的行为,对于数组项引用尤其如此(这些引用有一些特殊的规则)。我建议你完全避免它们。


答案 2

这不适用于 ,您可以为自己添加一个公共函数,该函数允许您设置对偏移量的引用(当然,这看起来与使用数组语法不同,因此这并不是一个充分的答案):ArrayAccess

class Test implements ArrayAccess{

    protected $_data = array();

    public function &offsetGet($key){
        return $this->_data[$key];
    }

    ... 

    public function offsetSetReference($key, &$value)
    {
        $this->_data[$key] = &$value;
    }
}

$test = new Test();
$test['foo'] = $var = 'bar';
$test->offsetSetReference('bar', $var);    
$var = 'foo';    
echo $test['bar']; # foo    
$alias = &$test['bar'];    
$alias = 'hello :)';    
echo $var; # hello :)

可能这样的功能在首次实现时被遗忘了。ArrayAccess

编辑:将其作为“参考赋值”传递:

class ArrayAccessReferenceAssignment
{
    private $reference;
    public function __construct(&$reference)
    {
        $this->reference = &$reference;
    }
    public function &getReference()
    {
        $reference = &$this->reference;
        return $reference;
    }
}


class Test implements ArrayAccess{
    ...
    public function offsetSet($key, $value){
        if ($value instanceof ArrayAccessReferenceAssignment)
        {
           $this->offsetSetReference($key, $value->getReference());
        }
        else
        {
           $this->_data[$key] = $value;
        }
    }

然后,它可以完美地工作,因为您实现了它。这可能比上面更明确的变体更能很好地连接:offsetSetReference

$test = new Test();
$test['foo'] = $var = 'bar';
$test['bar'] = new ArrayAccessReferenceAssignment($var);

$var = 'foo';
echo $test['bar']; # foo
$alias = &$test['bar'];
$alias = 'hello :)';
echo $var; # hello :)