PHP中有一些特殊的函數和方法,這些函數和方法相比普通方法的特殊之處在于: 用戶代碼通常不會主動調用,而是在特定的時機會被PHP自動調用。在PHP中通常以"__"打頭的方法都作為魔術方法, 所以通常不要定義以"__"開頭的函數或方法。例如:__autoload()函數, 通常我們不會手動調用這個函數, 而如果在代碼中訪問某個未定義的方法, 如過已經定義了__autoload()函數,此時PHP將會嘗試調用__autoload()函數, 例如在類的定義中如果定義了__construct()方法,在初始化類的實例時將會調用這個方法, 同理還有__destuct()方法, 詳細內容請參考[PHP手冊](http://php.net/manual/en/language.oop5.magic.php)。
## 魔術函數和魔術方法[]()
前面提到魔術函數和魔術方法的特殊之處在于這些方法(在這里把函數和方法統稱方法)的調用時機是在某些特定的場景才會被觸發,這些方法可以理解為一些事件監聽方法, 在事件觸發時才會執行。
### 魔術方法[]()
根據前面的介紹, 魔術方法就是在類的某些場景下觸發的一些監聽方法。這些方法需要在類定義中進行定義,在存儲上魔術方法自然存儲于類中, 而類在PHP內部是一個**_zend_class_entry**結構體,與普通方法一樣,只不過這些類不是存儲在類的函數表, 而是直接存儲在類結構體中:
- 在**_zend_class_entry**結構體中的存儲位置不同;
- 由ZendVM自動分情境進行調用;
- 不是必須的,按需定義,自動調用
從以上三個方面可以發現,關于魔術變量的關鍵理解,主要集中在兩個方面:**一,定義在哪里; 二,如何判斷其存在并進行調用。**
首先,魔術變量的存儲在**_zend_class_entry**中的代碼如下:(完整的**_zend_class_entry**代碼見本章第一節)
struct _zend_class_entry {
...
//構造方法 __construct
union _zend_function *constructor;
//析構方法 __destruct
union _zend_function *destructor;
//克隆方法 __clone
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 *serialize_func;
//反序列化
union _zend_function *unserialize_func;
...
}
這段代碼明確的在對象內部定義了不同的指針來保存各種魔術變量。關于Zend VM對魔術方法的調用機制,由于每種方法的調用情境不同,筆者在這里也分開進行分析。
#### __construct[]()
__construct構造方法,在對象創建時被自動調用。與其它很多語言(如JAVA)不同的是,在PHP中,構造方法并沒有使用”與類定義同名“的約定方式,而是單獨用魔術方法來實現。**__construct**方法的調用入口是new關鍵字對應的ZEND_NEW_SPEC_HANDLER函數。Zend VM在初始化對象的時候,使用了new關鍵字,對其OPCODE進行分析后,使用GDB可以得到下面的堆棧信息:
#0 ZEND_NEW_SPEC_HANDLER (execute_data=0x100d00080) at zend_vm_execute.h:461
#1 0x000000010041c1f0 in execute (op_array=0x100a1fd60) at zend_vm_execute.h:107
#2 0x00000001003e9394 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /Volumes/DEV/C/php-5.3.4/Zend/zend.c:1194
#3 0x0000000100368031 in php_execute_script (primary_file=0x7fff5fbff890) at /Volumes/DEV/C/php-5.3.4/main/main.c:2265
#4 0x00000001004d4b5c in main (argc=2, argv=0x7fff5fbffa30) at /Volumes/DEV/C/php-5.3.4/sapi/cli/php_cli.c:1193
上面的椎棧信息清晰顯示了new關鍵的調用過程,可以發現new關鍵字對應了ZEND_NEW_SPEC_HANDLER的處理函數,在ZEND_NEW_SPEC_HANDLER中,Zend VM使用下面的代碼來獲取對象是否定義了**__construct**方法:
...
constructor = Z_OBJ_HT_P(object_zval)->get_constructor(object_zval TSRMLS_CC);
if (constructor == NULL){
...
} else {
...
}
?
//get_constructor的實現
ZEND_API union _zend_function *zend_std_get_constructor(zval *object TSRMLS_DC)
{
zend_object *zobj = Z_OBJ_P(object);
zend_function *constructor = zobj->ce->constructor;
?
if(constructor){ ... } else { ...}
...
}
從上面的代碼可以看出ZendVM通過讀取**zend_object->ce->constructor**的值來判斷對象是不是定義的構造函數。
> Z_OBJ_P(zval); Z_OBJ_P宏將一個zval類型變量構造為zend_object類型。
在判斷了**__construct**魔術變量存在之后,ZEND_NEW_SPEC_HANDLER中對當前EX(called_scope)進行了重新賦值,使ZEND_VM_NEXT_OPCODE();將opline指針指向__construct方法的op_array,開始執行__construct魔術方法
[c]
EX(object) = object_zval;
EX(fbc) = constructor;
EX(called_scope) = EX_T(opline->op1.u.var).class_entry;
ZEND_VM_NEXT_OPCODE();
### __destruct[]()
**__destruct**是析構方法,運行于對象被顯示銷毀或者腳本關閉時,一般被用于釋放占用的資源。**__destruct**的調用涉及到垃圾回收機制,在第七章中會有更詳盡的介紹。本文筆者只針對**__destruct**調用機制進行分析,其調用堆棧信息如下:
//省略部分內存地址信息后的堆棧:
#0 zend_call_function () at /..//php-5.3.4/Zend/zend_execute_API.c:767
#1 zend_call_method () at /..//php-5.3.4/Zend/zend_interfaces.c:97
#2 zend_objects_destroy_object () at /..//php-5.3.4/Zend/zend_objects.c:112
#3 zend_objects_store_del_ref_by_handle_ex () at /..//php-5.3.4/Zend/zend_objects_API.c:206
#4 zend_objects_store_del_ref () at /..//php-5.3.4/Zend/zend_objects_API.c:172
#5 _zval_dtor_func () at /..//php-5.3.4/Zend/zend_variables.c:52
#6 _zval_dtor () at zend_variables.h:35
#7 _zval_ptr_dtor () at /..//php-5.3.4/Zend/zend_execute_API.c:443
#8 _zval_ptr_dtor_wrapper () at /..//php-5.3.4/Zend/zend_variables.c:189
#9 zend_hash_apply_deleter () at /..//php-5.3.4/Zend/zend_hash.c:614
#10 zend_hash_reverse_apply () at /..//php-5.3.4/Zend/zend_hash.c:763
#11 shutdown_destructors () at /..//php-5.3.4/Zend/zend_execute_API.c:226
#12 zend_call_destructors () at /..//php-5.3.4/Zend/zend.c:874
#13 php_request_shutdown () at /..//php-5.3.4/main/main.c:1587
#14 main () at /..//php-5.3.4/sapi/cli/php_cli.c:1374
**__destruct**方法存在與否是在**zend_objects_destroy_object**函數中進行判斷的。在腳本執行結果時,ZendVM在**php_request_shutdown**階段會將對象池中的對象一一銷毀,這時如果某對象定義了**__destruct**魔術方法,此方法便會被執行。
在**zend_objects_destroy_object**中,與**__construct**一樣,ZendVM判斷**zend_object->ce->destructor**是否為空,如果不為空,則調用**zend_call_method**執行**__destruct**析構方法。進入**__destruct**的方式與**__construct**不同的是,**__destruct**的執行方式是由ZendVM直接調用**zend_call_function**來執行。
### __call與__callStatic[]()
- **__call**:在對對象不存在的方法進行調用時自動執行;
- **__callStatic**:在對對象不存在的靜態方法進行調用時自動執行;
**__call**與**__callStatic**的調用機制幾乎完全相同,關于函數的執行已經在上一章中提到,用戶對函數的調用是由**zend_do_fcall_common_helper_SPEC()**方法進行處理的。
#### __call:[]()
經過**[ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER]-> [zend_do_fcall_common_helper_SPEC]-> [zend_std_call_user_call]-> [zend_call_method]->[zend_call_function]**調用,經過**zend_do_fcall_common_helper_SPEC**的分發,最終使用**zend_call_function**來執行**__call**。
#### __callStatic:[]()
經過**[ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER]-> [zend_do_fcall_common_helper_SPEC]-> [zend_std_callstatic_user_call]-> [zend_call_method]->[zend_call_function]**調用,經過**zend_do_fcall_common_helper_SPEC**的分發,最終使用**zend_call_function**來執行**__callStatic**。
### 其他魔術方法[]()
PHP中還有很多種魔術方法,它們的處理方式基本與上面類似,運行時執行與否取決的判斷根據,最終都是**_zend_class_entry**結構體中對應的指針是否為空。這里列出它們的底層實現函數:
| 魔術方法 | 對應處理函數 | 所在源文件 |
|-----|-----|-----|
| __set | zend_std_call_setter() | Zend/zend_object_handlers.c |
| __get | zend_std_call_getter() | Zend/zend_object_handlers.c |
| __isset | zend_std_call_issetter() | Zend/zend_object_handlers.c |
| __unset | zend_std_call_unsetter() | Zend/zend_object_handlers.c |
| __sleep | php_var_serialize_intern() | ext/standard/var.c |
| __wakeup | php_var_unserialize() | ext/standard/var_unserializer.c |
| __toString | zend_std_cast_object_tostring() | Zend/zend_object_handlers.c |
| __invoke | ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER() | Zend/zend_vm_execute.h |
| __set_state | php_var_export_ex() | ext/standard/var.c |
| __clone | ZEND_CLONE_SPEC_CV_HANDLER() | Zend/zend_vm_execute.h |
## 延遲綁定[]()
在PHP手冊中,對延遲綁定有以下定義。
> 從PHP 5.3.0開始,PHP增加了一個叫做后期靜態綁定的功能,用于在繼承范圍內引用靜態調用的類。 該功能從語言內部角度考慮被命名為“后期靜態綁定”。 “后期綁定”的意思是說,static::不再被解析為定義當前方法所在的類,而是在實際運行時計算的。 也可以稱之為”靜態綁定“,因為它可以用于(但不限于)靜態方法的調用。
延遲綁定的實現關鍵在于static關鍵字,如果以static調用靜態方法,則在語法解析時:
function_call:
...//省略若干其它情況的函數調用
| class_name T_PAAMAYIM_NEKUDOTAYIM T_STRING '(' { $4.u.opline_num = zend_do_begin_class_member_function_call(&$1, &$3 TSRMLS_CC); }
function_call_parameter_list
')' { zend_do_end_function_call($4.u.opline_num?NULL:&$3, &$$, &$6, $4.u.opline_num, $4.u.opline_num TSRMLS_CC); zend_do_extended_fcall_end(TSRMLS_C);}
...//省略若干其它情況的函數調用
?
class_name:
T_STATIC { $$.op_type = IS_CONST; ZVAL_STRINGL(&$$.u.constant, "static", sizeof("static")-1, 1);}
如上所示,static將以第一個參數(class_name)傳遞給zend_do_begin_class_member_function_call函數。此時class_name的op_type字段為IS_CONST,但是通過zend_get_class_fetch_type獲取此類的類型為ZEND_FETCH_CLASS_STATIC。這個類型作為操作的extended_value字段存在,此字段在后面執行獲取類的中間代碼ZEND_FETCH_CLASS(ZEND_FETCH_CLASS_SPEC_CONST_HANDLER)時,將作為第三個參數(fetch_type)傳遞給獲取類名的最終執行函數zend_fetch_class。
EX_T(opline->result.u.var).class_entry = zend_fetch_class(Z_STRVAL_P(class_name),
Z_STRLEN_P(class_name), opline->extended_value TSRMLS_CC);
至于在后面如何執行,請查看下一小節:第六節 PHP保留類及特殊類
- 第一章 準備工作和背景知識
- 第一節 環境搭建
- 第二節 源碼結構、閱讀代碼方法
- 第三節 常用代碼
- 第四節 小結
- 第二章 用戶代碼的執行
- 第一節 生命周期和Zend引擎
- 第二節 SAPI概述
- Apache模塊
- 嵌入式
- FastCGI
- 第三節 PHP腳本的執行
- 詞法分析和語法分析
- opcode
- opcode處理函數查找
- 第四節 小結
- 第三章 變量及數據類型
- 第一節 變量的結構和類型
- 哈希表(HashTable)
- PHP的哈希表實現
- 鏈表簡介
- 第二節 常量
- 第三節 預定義變量
- 第四節 靜態變量
- 第五節 類型提示的實現
- 第六節 變量的生命周期
- 變量的賦值和銷毀
- 變量的作用域
- global語句
- 第七節 數據類型轉換
- 第八節 小結
- 第四章 函數的實現
- 第一節 函數的內部結構
- 函數的內部結構
- 函數間的轉換
- 第二節 函數的定義,傳參及返回值
- 函數的定義
- 函數的參數
- 函數的返回值
- 第三節 函數的調用和執行
- 第四節 匿名函數及閉包
- 第五節 小結
- 第五章 類和面向對象
- 第一節 類的結構和實現
- 第二節 類的成員變量及方法
- 第三節 訪問控制的實現
- 第四節 類的繼承,多態及抽象類
- 第五節 魔術方法,延遲綁定及靜態成員
- 第六節 PHP保留類及特殊類
- 第七節 對象
- 第八節 命名空間
- 第九節 標準類
- 第十節 小結
- 第六章 內存管理
- 第一節 內存管理概述
- 第二節 PHP中的內存管理
- 第三節 內存使用:申請和銷毀
- 第四節 垃圾回收
- 新的垃圾回收
- 第五節 內存管理中的緩存
- 第六節 寫時復制(Copy On Write)
- 第七節 內存泄漏
- 第八節 小結
- 第七章 Zend虛擬機
- 第一節 Zend虛擬機概述
- 第二節 語法的實現
- 詞法解析
- 語法分析
- 實現自己的語法
- 第三節 中間代碼的執行
- 第四節 PHP代碼的加密解密
- 第五節 小結
- 第八章 線程安全
- 第二節 線程,進程和并發
- 第三節 PHP中的線程安全
- 第九章 錯誤和異常處理
- 第十章 輸出緩沖
- 第十六章 PHP語言特性的實現
- 第一節 循環語句
- foreach的實現
- 第二十章 怎么樣系列(how to)
- 附錄
- 附錄A PHP及Zend API
- 附錄B PHP的歷史
- 附錄C VLD擴展使用指南
- 附錄D 怎樣為PHP貢獻
- 附錄E phpt測試文件說明
- 附錄F PHP5.4新功能升級解析
- 附錄G:re2c中文手冊