### 3.4.4 动态属性
前面介绍的成员属性都是在类中明确的定义过的,这些属性在实例化时会被拷贝到对象空间中去,PHP中除了显示的在类中定义成员属性外,还可以动态的创建非静态成员属性,这种属性不需要在类中明确定义,可以直接通过:`$obj->property_name=xxx`、`$this->property_name = xxx`为对象设置一个属性,这种属性称之为动态属性,举个例子:
```php
class my_class {
public $id = 123;
public function test($name, $value){
$this->$name = $value;
}
}
$obj = new my_class;
$obj->test("prop_1", array(1,2,3));
//或者直接:
//$obj->prop_1 = array(1,2,3);
print_r($obj);
```
在`test()`方法中直接操作了没有定义的成员属性,上面的例子将输出:
```
my_class Object
(
[id] => 123
[prop_1] => Array
(
[0] => 1
[1] => 2
[2] => 3
)
)
```
前面类、对象两节曾介绍,非静态成员属性值在实例化时保存到了对象中,属性的操作按照编译时按顺序编好的序号操作,各对象对其非静态成员属性的操作互不干扰,那么动态属性是在运行时创建的,它是如何存储的呢?
与普通非静态属性不同,动态创建的属性保存在`zend_object->properties`哈希表中,查找的时候首先按照普通属性在`zend_class_entry.properties_info`找,没有找到再去`zend_object->properties`继续查找。动态属性的创建过程(即:修改属性的操作):
```c
//zend_object->handlers->write_property:
ZEND_API void zend_std_write_property(zval *object, zval *member, zval *value, void **cache_slot)
{
...
zobj = Z_OBJ_P(object);
//先在zend_class_entry.properties_info查找此属性
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)) {
//普通属性,直接根据根据属性ofsset取出属性值
} else if (EXPECTED(zobj->properties != NULL)) { //有动态属性
...
//从动态属性中查找
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;
}
}
}
if (zobj->ce->__set) {
//定义了__set()魔法函数
}else if (EXPECTED(property_offset != ZEND_WRONG_PROPERTY_OFFSET)){
if (EXPECTED(property_offset != ZEND_DYNAMIC_PROPERTY_OFFSET)) {
...
} else {
//首次创建动态属性将在这里完成
if (!zobj->properties) {
rebuild_object_properties(zobj);
}
//将动态属性插入properties
zend_hash_add_new(zobj->properties, Z_STR_P(member), value);
}
}
}
```
上面就是成员属性的修改过程,普通属性根据其offset再从对象中取出属性值进行修改,而首次创建动态属性将通过`rebuild_object_properties()`初始化`zend_object->properties`哈希表,后面再创建动态属性直接插入此哈希表,`rebuild_object_properties()`过程并不仅仅是创建一个HashTable,还会将普通成员属性值插入到这个数组中,与动态属性不同,这里的插入并不是增加原zend_value的refcount,而是创建了一个IS_INDIRECT类型的zval,指向原属性值zval,具体结构如下图。
![](https://box.kancloud.cn/19f9986d6b50fbff93a07693a1e76829_584x258.png)
> __Note:__ 这里不清楚将原有属性也插入properties的用意,已知用到的一个地方是在GC垃圾回收获取对象所有属性时(zend_std_get_gc()),如果有动态属性则直接返回properties给GC遍历,假如不把普通的显式定义的属性"拷贝"进来则需要返回、遍历两个数组。
>
> 另外一个地方需要注意,把原属性"转移"到properties并不仅仅是创建动态属性时触发的,调用对象的get_properties(即:zend_std_get_properties())也会这么处理,比如将一个object转为array时就会触发这个动作: $arr = (array)$object,通过foreach遍历一个对象时也会调用get_properties获取属性数组进行遍历。
成员属性的读取通过`zend_object->handlers->read_property`(默认zend_std_read_property())函数完成,动态属性的查找过程实际与`write_property`中相同:
```c
zval *zend_std_read_property(zval *object, zval *member, int type, void **cache_slot, zval *rv)
{
...
zobj = Z_OBJ_P(object);
//首先查找zend_class_entry.properties_info,普通属性可以在这里找到
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)) {
//普通属性
retval = OBJ_PROP(zobj, property_offset);
} else if (EXPECTED(zobj->properties != NULL)) {
//动态属性从zend_object->properties中查找
retval = zend_hash_find(zobj->properties, Z_STR_P(member));
if (EXPECTED(retval)) goto exit;
}
}
...
}
```
- 目录
- 第1章 PHP基本架构
- 1.1 PHP简介
- 1.2 PHP7的改进
- 1.3 FPM
- 1.4 PHP执行的几个阶段
- 第2章 变量
- 2.1 变量的内部实现
- 2.2 数组
- 2.3 静态变量
- 2.4 全局变量
- 2.5 常量
- 3.1 PHP代码的编译
- 3.1.1 词法解析、语法解析
- 3.1.2 抽象语法树编译流程
- 第3章 Zend虚拟机
- 3.2.1 内部函数
- 3.2.2 用户函数的实现
- 3.3 Zend引擎执行流程
- 3.3.1 基本结构
- 3.2 函数实现
- 3.3.2 执行流程
- 3.3.3 函数的执行流程
- 3.3.4 全局execute_data和opline
- 3.4 面向对象实现
- 3.4.1 类
- 3.4.2 对象
- 3.4.3 继承
- 3.4.4 动态属性
- 3.4.5 魔术方法
- 3.4.6 类的自动加载
- 3.5 运行时缓存
- 3.6 Opcache
- 3.6.1 opcode缓存
- 3.6.2 opcode优化
- 3.6.3 JIT
- 第4章 PHP基础语法实现
- 4.1 类型转换
- 4.2 选择结构
- 4.3 循环结构
- 4.4 中断及跳转
- 4.5 include/require
- 4.6 异常处理
- 第5章 内存管理
- 5.1 Zend内存池
- 5.2 垃圾回收
- 第6章 线程安全
- 6.2 线程安全资源管理器
- 第7章 扩展开发
- 7.1 概述
- 6.1 什么是线程安全
- 7.2 扩展的实现原理
- 7.3 扩展的构成及编译
- 7.4 钩子函数
- 7.5 运行时配置
- 7.6 函数
- 7.7 zval的操作
- 7.8 常量
- 7.9 面向对象
- 7.9.1 内部类注册
- 7.9.2 定义成员属性
- 7.9.3 定义成员方法
- 7.9.4 定义常量
- 7.9.5 类的实例化
- 7.10 资源类型
- 7.11 经典扩展解析
- 7.8.1 Yaf
- 7.8.2 Redis
- 第8章 命名空间
- 8.2 命名空间的定义
- 8.2.1 定义语法
- 8.2.2 内部实现
- 8.3 命名空间的使用
- 8.3.1 基本用法
- 8.3.2 use导入
- 8.3.3 动态用法
- 附录
- 附录1:break/continue按标签中断语法实现
- 附录2:defer推迟函数调用语法的实现
- 8.1 概述