### 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中對應的指針。

具體在編譯成員方法時設置: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超時控制的思考