前面我们已经介绍过:属性按静态、非静态分别保存在两个数组中,各属性按照定义的先后顺序编号(offset),同时按照这个编号顺序存储排列,而这些编号信息通过zend_property_info
结构保存,全部静态、非静态属性的zend_property_info
保存在一个以属性名为key的HashTable中,所以检索属性时首先根据属性名找到此属性的zend_property_info
,然后拿到其属性值的offset,再根据静态、非静态分别到default_static_members_count
、default_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哈希表,所以需要将父类的属性索引表与子类的索引表合并。在合并的过程中就牵扯到父子类属性的继承、覆盖问题了,各种情况具体处理如下:
ZEND_ACC_CHANGED
的flag,这种属性父子类隔离,互不干扰;这个地方相对比较复杂,具体的合并策略在do_inherit_property()
中,这里不再罗列代码。
所以,继承类实际上是把父类的属性、常量、方法合并到了子类里面,上一节介绍实例化时会将普通成员属性值复制到对象中去,这样在实例化时子类就与普通的类的操作没有任何差别了。