以数组形式访问对象的成员变量

在Yii框架中我们可以直接以数组的方式访问对象的成员变量,查看其源码得这些类都实现了ArrayAccess接口。 如果你想让一个类的实例可以以数组的方式访问,实现ArrayAccess接口就可以了。如下示例

class Foo implements ArrayAccess {

    private $_container = array();

    public function __construct() {
    }

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

    public function offsetExists($offset) {
        return isset($this->_container[$offset]);
    }

    public function offsetUnset($offset) {
        unset($this->_container[$offset]);
    }

    public function offsetGet($offset) {
        return isset($this->_container[$offset]) ? $this->_container[$offset] : NULL;
    }

}

$foo = new Foo;

$foo['test'] = 100;
echo $foo["test"];

这是官网上的一个例子,修改了一些代码,非常简单,它实现了一个类,这个类实现了ArrayAccess接口。 从而我们可以以数组的方式访问或设置值。

什么原因导致可以以这样的方式访问呢?难道仅仅是因为那个接口吗? 归根结底应该是类中的我们约定好的方法,而这些方法中只是接口的形式表现出来了。 如果我们没有实现这个接口,而仅仅某个类拥有了这些方法呢? 当程序执行时,程序会输出: Fatal error: Cannot use object of type Foo as array…

这表示实现这个接口是必须的,如果实现这个接口,那么就一定需要实现这个接口定义的所有方法。 比如我们要以数组的方式读取一个成员变量,那在PHP内核中是如何实现的呢? 通过表象看本质,这里是以数组的方式读取,那么其实现的位置应该还是在方括号符的实现位置。 以VLD查看其中间代码,我们可以得知数组读取变量的中间代码为:ZEND_FETCH_DIM_R 在此中间代码的执行过程中最终都会调用zend_fetch_dimension_address_read函数来读取值。 在这个函数中,它会依据不同容器类型做不同的处理,这些类型包括:数组,字符串、NULL、对象等。 虽然我们是以数组的方式调用对象的属性,但在放对象属性的窗口还是对象。因此,此处程序走的分支是对象, 在此分支中,对于对象,它会调用对象的read_dimension方法,默认情况下,Zend引擎的read_dimension方法默认实现是 zend_std_read_dimension函数(Zend/zend_object_handlers.c)。我们看zend_std_read_dimension函数的实现,如下:

zval *zend_std_read_dimension(zval *object, zval *offset, int type TSRMLS_DC) /* {{{ */
{
    zend_class_entry *ce = Z_OBJCE_P(object);
    zval *retval;

    /* 判断是否为ArrayAccess的子类 */
    if (instanceof_function_ex(ce, zend_ce_arrayaccess, 1 TSRMLS_CC)) {
        if(offset == NULL) {
            /* [] construct */
            ALLOC_INIT_ZVAL(offset);
        } else {
            SEPARATE_ARG_IF_REF(offset);
        }
        zend_call_method_with_1_params(&object, ce, NULL, "offsetget", &retval, offset);

        zval_ptr_dtor(&offset);

        if (!retval) {
            if (!EG(exception)) {
                zend_error(E_ERROR, "Undefined offset for object of type %s used as array", ce->name);
            }
            return 0;
        }

        /* Undo PZVAL_LOCK() */
        Z_DELREF_P(retval);

        return retval;
    } else {
        zend_error(E_ERROR, "Cannot use object of type %s as array", ce->name);
        return 0;
    }
}

从上面的代码我们可以看出:程序会先判断所给对象的类是否为ArrayAccess的子类,如果不是,则会显示错误,这在前面已经猜测,在此证实了。 如果是其子类,则调用offsetget方法获取值。

虽然我们在使用SPL时会比较简单,但是如果要开发一个SPL有时可能就没这么简单了,特别是那些有语言特性的SPL功能(如ArrayAccess),则在实现时可能就需要调用相关语言实现的代码了, 从上面看SPL与语言结构产生了较为严重的耦合,如果这个SPL要去掉,则需要修改的地方不只一处,是否有其它方案? SPL现在本来就是以扩展的形式存在于PHP中,以扩展的方式加载,却不能以扩展的方式卸载。优雅乎?

以数组形式访问对象的成员变量》上有2条评论

  1. Pingback引用通告: Thinking In LAMP Blog » Blog Archive » PHP每月通讯(2011年9月)

发表评论

电子邮件地址不会被公开。 必填项已用*标注


*

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>