继承属性

3.4.3.1 继承属性

前面我们已经介绍过:属性按静态、非静态分别保存在两个数组中,各属性按照定义的先后顺序编号(offset),同时按照这个编号顺序存储排列,而这些编号信息通过zend_property_info结构保存,全部静态、非静态属性的zend_property_info保存在一个以属性名为key的HashTable中,所以检索属性时首先根据属性名找到此属性的zend_property_info,然后拿到其属性值的offset,再根据静态、非静态分别到default_static_members_countdefault_properties_table数组中取出属性值。

当类存在继承关系时,操作方式是:将属性从父类复制到子类 。子类会将父类的公共、受保护的属性值数组全部合并到子类中,然后将全部属性的zend_property_info哈希表也合并到子类中。

合并的步骤:

(1)合并非静态属性default_properties_table: 首先申请一个父类+子类非静态属性大小的数组,然后先将父类非静态属性复制到新数组,然后再将子类的非静态数组接着父类属性的位置复制过去,子类的default_properties_table指向合并后的新数组,default_properties_count更新为新数组的大小,最后将子类旧的数组释放。

if (parent_ce->default_properties_count) {
    zval *src, *dst, *end;
    ...
    zval *table = pemalloc(sizeof(zval) * (ce->default_properties_count + parent_ce->default_properties_count), ...);

    ce->default_properties_table = table;

    //复制父类、子类default_properties_table
    do {
        ...
    }while(dst != end);

    //更新default_properties_count为合并后的大小
    ce->default_properties_count += parent_ce->default_properties_count;
}

示例合并后的情况如下图。

__(2)合并静态属性default_static_members_table:__ 与非静态属性相同,新申请一个父类+子类静态属性大小的数组,依次将父类、子类静态属性复制到新数组,然后更新子类default_static_members_table指向新数组。

(3)更新子类属性offset: 因为合并后原子类属性整体向后移了,所以子类属性的编号offset需要加上前面父类属性的总大小。

ZEND_HASH_FOREACH_PTR(&ce->properties_info, property_info) {
    if (property_info->ce == ce) {
        if (property_info->flags & ZEND_ACC_STATIC) {
            //静态属性offset为数组下标,直接加上父类default_static_members_count即可
            property_info->offset += parent_ce->default_static_members_count;
        } else {
            //非静态属性offset为内存偏移值,按zval大小递增
            property_info->offset += parent_ce->default_properties_count * sizeof(zval);
        }
    }
} ZEND_HASH_FOREACH_END();

__(4)合并properties_info哈希表:__ 这也是非常关键的一步,上面只是将父类的属性值合并到了子类,但是索引属性用的是properties_info哈希表,所以需要将父类的属性索引表与子类的索引表合并。在合并的过程中就牵扯到父子类属性的继承、覆盖问题了,各种情况具体处理如下:

  • 父类属性不与子类冲突 且 父类属性是私有: 即父类属性为private,且子类中没有重名的,则将此属性插入子类properties_info,但是更新其flag为ZEND_ACC_SHADOW,这种属性将不能被子类使用;
  • 父类属性不与子类冲突 且 父类属性是公有: 这种比较简单,子类可以继承使用,直接插入子类properties_info;
  • 父类属性与子类冲突 且 父类属性为私有: 不继承父类的,以子类原属性为准,但是打上ZEND_ACC_CHANGED的flag,这种属性父子类隔离,互不干扰;
  • 父类属性与子类冲突 且 父类属性是公有或受保护的:
    • 父子类属性一个是静态一个是非静态: 编译错误;
    • 父子类属性都是非静态: 用父类的offset,但是值用子类的,父子类共享;
    • 父子类属性都是静态: 不继承父类属性,以子类原属性为准,父子类隔离,互不干扰;

这个地方相对比较复杂,具体的合并策略在do_inherit_property()中,这里不再罗列代码。

所以,继承类实际上是把父类的属性、常量、方法合并到了子类里面,上一节介绍实例化时会将普通成员属性值复制到对象中去,这样在实例化时子类就与普通的类的操作没有任何差别了。

联系我们

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

Copyright © 2015-2024

备案号:京ICP备15003423号-3