成员属性的读写

3.4.2.3 成员属性的读写

普通成员属性的读写处理handler分别为zend_object.handlers中的:read_property、write_property,默认对应的函数为:zend_std_read_property()、zend_std_write_property(),访问获取修改一个普通成员属性时就是由这两个函数完成的。

(1)读取属性:

通过对象或方法内通过$this访问属性,比如:echo $obj->name;,具体的实现:

zval *zend_std_read_property(zval *object, zval *member, int type, void **cache_slot, zval *rv)
{
    zend_object *zobj;
    uint32_t property_offset;

    zobj = Z_OBJ_P(object);

    //根据属性名在zend_class.zend_property_info中查找zend_property_info,得到属性值在zend_object中的存储offset
    //注意:zend_get_property_offset()会对属性的可见性(public、private、protected)进行验证
    property_offset = zend_get_property_offset(zobj->ce, Z_STR_P(member), (type == BP_VAR_IS) || (zobj->ce->__get != NULL), cache_slot);

    if (EXPECTED(property_offset != ZEND_WRONG_PROPERTY_OFFSET)) {
        if (EXPECTED(property_offset != ZEND_DYNAMIC_PROPERTY_OFFSET)) {
            //普通属性,直接根据offset取到属性值:((zval*)((char*)(zobj) + offset))
            retval = OBJ_PROP(zobj, property_offset);
        } else if (EXPECTED(zobj->properties != NULL)) {
            //动态属性的情况,没有在类中显式定义的属性,后面一节会单独介绍
            ....
        }
    } else if (UNEXPECTED(EG(exception))) {
        ...
    }

    //没有找到属性
    //调用魔术方法:__isset()
    if ((type == BP_VAR_IS) && zobj->ce->__isset) {
        ...
    }

    //调用魔术方法:__get()
    if (zobj->ce->__get) {
        zend_long *guard = zend_get_property_guard(zobj, Z_STR_P(member));
        ...
        if(!((*guard) & IN_ISSET)){
            *guard |= IN_ISSET;
            zend_std_call_issetter(&tmp_object, member, &tmp_result);
            *guard &= ~IN_ISSET;
            ...
        }
    }
    ...
}

普通成员属性的查找比较容易理解,首先是从zend_class的属性信息哈希表中找到zend_property_info,并判断其可见性(public、private、protected),如果可以访问则直接根据属性的offset在zend_object.properties_table数组中取到属性值,如果没有在属性哈希表中找到且定义了get()魔术方法则会调用get()方法处理。

> Note: 如果类存在get()方法,则在实例化对象分配属性内存(即:properties_table)时会多分配一个zval,类型为HashTable,每次调用get($var)时会把输入的$var名称存入这个哈希表,这样做的目的是防止循环调用,举个例子: > > ***public function get($var) { return $this->$var; }** > > 这种情况是调用get()时又访问了一个不存在的属性,也就是会在get()方法中递归调用,如果不对请求的$var作判断则将一直递归下去,所以在调用get()前首先会判断当前$var是不是已经在get()中了,如果是则不会再调用__get(),否则会把$var作为key插入那个HashTable,然后将哈希值设置为:guard |= IN_ISSET,调用完get()再把哈希值设置为:*guard &= ~IN_ISSET。 > > 这个HashTable不仅仅是给__get()用的,其它魔术方法也会用到,所以其哈希值类型是zend_long,不同的魔术方法占不同的bit位;其次,并不是所有的对象都会额外分配这个HashTable,在对象创建时会根据 zend_class_entry.ce_flags 是否包含 ZEND_ACC_USE_GUARDS 确定是否分配,在类编译时如果发现定义了get()、set()、unset()、isset()方法则会将ce_flags打上这个掩码。

(2)设置属性:

与读取属性不同,设置属性是对属性的修改操作,比如:$obj->name = "pangudashu";,看下具体的实现过程:

ZEND_API void zend_std_write_property(zval *object, zval *member, zval *value, void **cache_slot)
{
    zend_object *zobj;
    uint32_t property_offset;

    zobj = Z_OBJ_P(object);

    //与读取属性相同
    property_offset = zend_get_property_offset(zobj->ce, Z_STR_P(member), (zobj->ce->__set != NULL), cache_slot);

    if (EXPECTED(property_offset != ZEND_WRONG_PROPERTY_OFFSET)) {
        if (EXPECTED(property_offset != ZEND_DYNAMIC_PROPERTY_OFFSET)) {
            //普通属性
            variable_ptr = OBJ_PROP(zobj, property_offset);
            if (Z_TYPE_P(variable_ptr) != IS_UNDEF) {
                goto found;
            }
        } else if (EXPECTED(zobj->properties != NULL)) {
            //动态属性哈希表已经初始化,直接插入zobj->properties哈希表,后面单独介绍
            ...
            if ((variable_ptr = zend_hash_find(zobj->properties, Z_STR_P(member))) != NULL) {
found:
                //赋值操作,与普通变量的操作相同
                zend_assign_to_variable(variable_ptr, value, IS_CV);
                goto exit;
            }
        }
    } else if (UNEXPECTED(EG(exception))) {
        ...
    }

    //没有找到属性
    //如果定义了__set()则调用
    if (zobj->ce->__set) {
        //与__get()相同,也会判断set的变量名是否已经在__set()中
        ...
        ZVAL_COPY(&tmp_object, object);
        (*guard) |= IN_SET; //防止循环__set()
        if (zend_std_call_setter(&tmp_object, member, value) != SUCCESS) {
        }
        (*guard) &= ~IN_SET;
    }else if (EXPECTED(property_offset != ZEND_WRONG_PROPERTY_OFFSET)) {
        ...
    }
}

首先与读取属性的操作相同:先找到zend_property_info,判断其可见性,然后根据offset取到具体的属性值,最后对其进行赋值修改。

> Note: 属性读写操作的函数中有一个cache_slot的参数,它的作用涉及PHP的一个缓存机制:运行时缓存,后面会单独介绍。

联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2024

备案号:京ICP备15003423号-3