PHP中的ArrayAccess——通过引用分配给偏移量

Posted

技术标签:

【中文标题】PHP中的ArrayAccess——通过引用分配给偏移量【英文标题】:ArrayAccess in PHP -- assigning to offset by reference 【发布时间】:2012-01-30 21:27:36 【问题描述】:

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

此函数不会在通过引用分配或以其他方式间接更改以ArrayAccess 重载的数组维度进行调用(间接的意思是它们不是通过直接更改维度,而是通过更改子维度或子属性来实现的或通过引用另一个变量来分配数组维度)。 而是调用ArrayAccess::offsetGet()。只有当该方法通过引用返回时,操作才会成功,这只有从 PHP 5.3.4 起才有可能

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

所以,现在是一个测试 sn-p:

(忽略没有验证和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 的对象,如示例 sn-p 所示。我之前对此进行了调查,但我认为这是不可能的,但由于不确定性,我(重新-)在手册中发现了这一点。现在我只是一头雾水。


更新:当我点击发布您的问题时,我意识到这可能只是指通过引用到另一个变量的赋值,例如作为$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);

hasgetsetremove而言,ArrayAccess支持的方法都没有问题。绑定功能是我不知所措的地方,并且我开始接受 ArrayAccess 和 php 的限制只是禁止这样做。

【问题讨论】:

+1 这是一个很好的问题的完美例子。 【参考方案1】:

这不适用于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 :)

【讨论】:

谢谢@hakre——我已经有了一个面向公众的bind_data($key, &$variable) 方法,我只是想给它起别名。 5.4 本来是解决这个问题的好时机(除其他外)——嗯,6 很快就会出现! :P 可能还有其他方法,比如分配一个表示引用的特殊对象类型。我将尝试概述它并添加一些代码。 谢谢@hakre——我知道这很棘手;如果没有无偿的解决方法,这可能是不可能的。最终$obj[$key] = &$value 需要透明地调用$obj->bind_data($key, $value),但是鉴于不可能,它不会造成重大损失。我已经学会接受 PHP 的局限性,但有时我忍不住质疑它们。 我添加了一些其他的小代码块,我不认为它看起来那么糟糕,也许类名有点长;) 感谢@hakre -- 这是一个可靠的解决方案,但是它偏离了我希望通过这种别名提供的简单性。我之前考虑过一个类似(基本相同)的解决方案,但是引入任意对象作为替代,只是为了强制引用处理,这是我在生产中看不到的。毫无疑问,它有效,但它很笨重。我更新了我的问题,只是为了消除我所追求的任何歧义,但你知道我要去哪里。【参考方案2】:

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

$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';

所以$array['foo']首先被ref获取,然后修改。如果您的 offsetGet 通过 ref 返回,这将成功。如果没有,你会得到一些间接修改错误。


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

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

【讨论】:

感谢@NikiC -- 我知道这个功能,但它是情况列表中的最后一个,“...或通过引用另一个变量来分配数组维度。" 这让我明白了。在我的兴奋中,我相信我误解了;而是试图做相反的事情。就实际完成我所追求的而言,这似乎是不可能的,不是吗? 关于您的更新——我正在开发一个微型框架外壳,它利用回调和嵌套的“上下文”对象以及级联数据映射,允许任意模块加载/合并。在这方面,我对引用的行为有了很好的处理,因为需要绑定数据(通过引用)才能从范围传递到范围(上下文到上下文 i>) 但是,当我认为我已经阅读了我所阅读的内容时,我想我只是有点尿裤子了。不过,这对于bind_data() 方法来说是一个很好的语法糖别名。不管怎样,谢谢@NikiC :)

以上是关于PHP中的ArrayAccess——通过引用分配给偏移量的主要内容,如果未能解决你的问题,请参考以下文章

如何通过“引用”将其分配给c#中的类字段?

将本地 std::vector 分配给 C++ 中的引用

PHP、SPL、ArrayAccess 接口

PHP 预定义接口 ArrayAccess(数组式访问对象)

PHP ArrayAccess 设置多维

PHP预定义接口:ArrayAccess