### 3.4.5 魔术方法
PHP在类的成员方法中预留了一些特殊的方法,它们会在一些特殊的时机被调用(比如创建对象之初、访问成员属性时...),这类方法称为:魔术方法,包括:__construct()、__destruct()、__call()、__callStatic()、__get()、__set()、__isset()、__unset()、__sleep()、__wakeup()、__toString()、__invoke()、 __set_state()、 __clone() 和 __debugInfo(),关于这些方法的用法这里不作说明,不清楚的可以翻下官方文档。
魔术方法实际是PHP提供的一些特殊操作时的钩子函数,与普通成员方法无异,它们只是与一些操作的口头约定,并没有什么字段标识它们,比如我们定义了一个函数:my_function(),我们希望在这个函数处理对象时首先调用其成员方法my_magic(),那么my_magic()也可以认为是一个魔术方法。
魔术方法与普通成员方法一样保存在`zend_class_entry.function_table`中,另外针对一些内核常用到的成员方法在zend_class_entry中还有一些单独的指针指向具体的成员方法:
```c
struct _zend_class_entry {
...
union _zend_function *constructor;
union _zend_function *destructor;
union _zend_function *clone;
union _zend_function *__get;
union _zend_function *__set;
union _zend_function *__unset;
union _zend_function *__isset;
union _zend_function *__call;
union _zend_function *__callstatic;
union _zend_function *__tostring;
union _zend_function *__debugInfo;
...
}
```
在编译成员方法时如果发现与这些魔术方法名称一致,则除了插入`zend_class_entry.function_table`哈希表以外,还会设置zend_class_entry中对应的指针。
![](https://box.kancloud.cn/4191cc336f9e212ad35932cb608e925e_552x281.png)
具体在编译成员方法时设置:zend_begin_method_decl()。
```c
void zend_begin_method_decl(zend_op_array *op_array, zend_string *name, zend_bool has_body)
{
...
//插入类的function_table中
if (zend_hash_add_ptr(&ce->function_table, lcname, op_array) == NULL) {
zend_error_noreturn(..);
}
if (!in_trait && zend_string_equals_ci(lcname, ce->name)) {
if (!ce->constructor) {
ce->constructor = (zend_function *) op_array;
}
} else if (zend_string_equals_literal(lcname, ZEND_CONSTRUCTOR_FUNC_NAME)) {
ce->constructor = (zend_function *) op_array;
} else if (zend_string_equals_literal(lcname, ZEND_DESTRUCTOR_FUNC_NAME)) {
ce->destructor = (zend_function *) op_array;
} else if (zend_string_equals_literal(lcname, ZEND_CLONE_FUNC_NAME)) {
ce->clone = (zend_function *) op_array;
} else if (zend_string_equals_literal(lcname, ZEND_CALL_FUNC_NAME)) {
ce->__call = (zend_function *) op_array;
} else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_NAME)) {
ce->__callstatic = (zend_function *) op_array;
} else if (...){
...
}
...
}
```
除了这几个其它魔术方法都没有单独的指针指向,比如:__sleep()、__wakeup(),这两个主要是serialize()、unserialize()序列化、反序列化时调用的,它们是在这俩函数中写死的,我们简单看下serialize()的实现,这个函数是通过扩展提供的:
```c
//file: ext/standard/var.c
PHP_FUNCTION(serialize)
{
zval *struc;
php_serialize_data_t var_hash;
smart_str buf = {0};
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &struc) == FAILURE) {
return;
}
php_var_serialize(&buf, struc, &var_hash);
...
}
```
最终由`php_var_serialize_intern()`处理,这个函数会根据不同的类型选择不同的处理方式:
```c
static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_data_t var_hash)
{
...
switch (Z_TYPE_P(struc)) {
case IS_FALSE:
...
case IS_TRUE:
...
case IS_NULL:
...
case IS_LONG:
...
}
}
```
其中类型是对象时将先检查`zend_class_function.function_table`中是否定义了`__sleep()`,如果有的话则调用:
```c
//case IS_OBJEST:
...
if (ce != PHP_IC_ENTRY && zend_hash_str_exists(&ce->function_table, "__sleep", sizeof("__sleep")-1)) {
ZVAL_STRINGL(&fname, "__sleep", sizeof("__sleep") - 1);
//调用用户自定义的__sleep()方法
res = call_user_function_ex(CG(function_table), struc, &fname, &retval, 0, 0, 1, NULL);
if (res == SUCCESS) {
if (Z_TYPE(retval) != IS_UNDEF) {
if (HASH_OF(&retval)) {
php_var_serialize_class(buf, struc, &retval, var_hash);
} else {
smart_str_appendl(buf,"N;", 2);
}
zval_ptr_dtor(&retval);
}
return;
}
}
//后面会走到IS_ARRAY分支继续序列化处理
...
```
其它魔术方法与__sleep()类似,都是在一些特殊操作中固定调用的。
- 前言
- 第1章 PHP基本架构
- 1.1 PHP简介
- 1.2 PHP7的改进
- 1.3 FPM
- 1.3.1 概述
- 1.3.2 基本实现
- 1.3.3 FPM的初始化
- 1.3.4 请求处理
- 1.3.5 进程管理
- 1.4 PHP执行的几个阶段
- 第2章 变量
- 2.1 变量的内部实现
- 2.2 数组
- 2.3 静态变量
- 2.4 全局变量
- 2.5 常量
- 第3章 Zend虚拟机
- 3.1 PHP代码的编译
- 3.1.1 词法解析、语法解析
- 3.1.2 抽象语法树编译流程
- 3.2 函数实现
- 3.2.1 内部函数
- 3.2.2 用户函数的实现
- 3.3 Zend引擎执行流程
- 3.3.1 基本结构
- 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.1 什么是线程安全
- 6.2 线程安全资源管理器
- 第7章 扩展开发
- 7.1 概述
- 7.2 扩展的实现原理
- 7.3 扩展的构成及编译
- 7.3.1 扩展的构成
- 7.3.2 编译工具
- 7.3.3 编写扩展的基本步骤
- 7.3.4 config.m4
- 7.4 钩子函数
- 7.5 运行时配置
- 7.5.1 全局变量
- 7.5.2 ini配置
- 7.6 函数
- 7.6.1 内部函数注册
- 7.6.2 函数参数解析
- 7.6.3 引用传参
- 7.6.4 函数返回值
- 7.6.5 函数调用
- 7.7 zval的操作
- 7.7.1 新生成各类型zval
- 7.7.2 获取zval的值及类型
- 7.7.3 类型转换
- 7.7.4 引用计数
- 7.7.5 字符串操作
- 7.7.6 数组操作
- 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.1 概述
- 8.2 命名空间的定义
- 8.2.1 定义语法
- 8.2.2 内部实现
- 8.3 命名空间的使用
- 8.3.1 基本用法
- 8.3.2 use导入
- 8.3.3 动态用法
- 附录
- break/continue按标签中断语法实现
- defer推迟函数调用语法的实现
- 一起线上事故引发的对PHP超时控制的思考