PHP 和 Zend Engine 為擴展提供了許多不同的鉤子,這些擴展允許擴展開發人員以 PHP userland 無法提供的方式控制 PHP 運行時。
本章將展示各種鉤子和從擴展鉤子到它們的常見用例。
鉤子到 PHP 功能的一般模式是 PHP 核心提供的擴展覆蓋函數指針。然后擴展函數通常執行自己的工作并調用原始 PHP 核心函數。使用此模式,不同的擴展可以覆蓋同一個鉤子而不會導致沖突。
掛鉤到函數的執行userland和內部函數的執行由Zend引擎中的兩個函數處理,您可以用自己的實現替換這兩個函數。覆蓋此鉤子的擴展的主要用例是通用函數級評測、調試和面向方面的編程。
鉤子在 Zend/zend_execute.h
中定義:
ZEND_API extern void (*zend_execute_ex)(zend_execute_data *execute_data);ZEND_API extern void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value);
如果要覆蓋這些函數指針,則必須在 Minit 中執行此操作,因為 Zend Engine 中的其他決策是根據指針是否被覆蓋這一事實提前做出的。
覆蓋的通常模式是這樣的:
static void (*original_zend_execute_ex) (zend_execute_data *execute_data);static void (*original_zend_execute_internal) (zend_execute_data *execute_data, zval *return_value);void my_execute_internal(zend_execute_data *execute_data, zval *return_value);void my_execute_ex (zend_execute_data *execute_data);PHP_MINIT_FUNCTION(my_extension){ REGISTER_INI_ENTRIES(); original_zend_execute_internal = zend_execute_internal; zend_execute_internal = my_execute_internal; original_zend_execute_ex = zend_execute_ex; zend_execute_ex = my_execute_ex; return SUCCESS;}PHP_MSHUTDOWN_FUNCTION(my_extension){ zend_execute_internal = original_zend_execute_internal; zend_execute_ex = original_zend_execute_ex; return SUCCESS;}
覆蓋 zend_execute_ex
的一個缺點是它將 Zend Virtual Machine 運行時的行為更改為使用遞歸,而不是在不離開解釋器循環的情況下處理調用。此外,沒有覆蓋zend_execute_ex
的 PHP 引擎也可以生成更優化的函數調用操作碼。
這些掛鉤對性能非常敏感,具體取決于原始函數封裝代碼的復雜性。
覆蓋內部功能在覆蓋執行鉤子時,擴展可以記錄每個函數調用,你還可以覆蓋用戶域,核心和擴展函數(和方法)的各個函數指針。如果擴展僅需要訪問特定的內部函數調用,則具有更好的性能特征。
#if PHP_VERSION_ID < 70200typedef void (*zif_handler)(INTERNAL_FUNCTION_PARAMETERS);#endif zif_handler original_handler_var_dump;ZEND_NAMED_FUNCTION(my_overwrite_var_dump){ // 如果我們想調用原始函數 original_handler_var_dump(INTERNAL_FUNCTION_PARAM_PASSTHRU);}PHP_MINIT_FUNCTION(my_extension){ zend_function *original; original = zend_hash_str_find_ptr(EG(function_table), "var_dump", sizeof("var_dump")-1); if (original != NULL) { original_handler_var_dump = original->internal_function.handler; original->internal_function.handler = my_overwrite_var_dump; }}
覆蓋類方法時,可以在 zend_class_entry
上找到函數表:
zend_class_entry *ce = zend_hash_str_find_ptr(CG(class_table), "PDO", sizeof("PDO")-1);if (ce != NULL) { original = zend_hash_str_find_ptr(&ce->function_table, "exec", sizeof("exec")-1); if (original != NULL) { original_handler_pdo_exec = original->internal_function.handler; original->internal_function.handler = my_overwrite_pdo_exec; }}修改抽象語法樹(AST)
當 PHP 7編譯 PHP 代碼時,它會先將其轉換為抽象語法樹(AST),然后最終生成持久存儲在 Opcache 中的操作碼。zend_ast_process
鉤子會被每個已編譯的腳本調用,并允許你在解析和創建 AST 之后修改 AST。
這是要使用的最復雜的鉤子之一,因為它需要完全了解 AST。在此處創建無效的 AST 可能會導致異常行為或崩潰。
最好看看使用此鉤子的示例擴展:
Google Stackdriver PHP調試器擴展基于 Stackdriver 的帶有 AST 的概念驗證器熟悉腳本/文件編譯每當用戶腳本調用include
/require
或其對應的include_once
/require_once
時,PHP內核都會在指針zend_compile_file處調用該函數
處理此請求。參數是文件句柄,結果是zend_op_array
。
zend_op_array * my_extension_compile_file(zend_file_handle * file_handle,int類型);
PHP核心中有兩個擴展實現了此掛鉤:dtrace和opcache。
-如果您使用環境變量USE_ZEND_DTRACE
啟動PHP腳本并使用dtrace支持編譯了PHP,則dtrace_compile_file
用于Zend / zend_dtrace.c
。
-Opcache將操作數組存儲在共享內存中以獲得更好的性能,因此,每當腳本被編譯時,其最終的操作數組都會從緩存中得到服務,而不是重新編譯。您可以在ext / opcache / ZendAccelerator.c
中找到此實現。
-名為compile_file
的默認實現是Zend / zend_language_scanner.l
中掃描程序代碼的一部分。
實施此掛鉤的用例是Opcode Accelerating,PHP代碼加密/解密,調試或概要分析。
您可以隨時在執行PHP進程時替換該掛鉤,并且替換后編譯的所有PHP腳本都將由該掛鉤的實現處理。
始終調用原始函數指針非常重要,否則PHP將無法再編譯腳本,并且Opcache將不再起作用。
此處的擴展覆蓋順序也很重要,因為您需要知道是要在Opcache之前還是之后注冊鉤子,因為Opcache如果在其共享內存緩存中找到操作碼數組條目,則不會調用原始函數指針。 Opcache將其鉤子注冊為啟動后鉤子,該鉤子在擴展的minit階段之后運行,因此默認情況下,緩存腳本時將不再調用該鉤子。
調用錯誤處理程序時的通知與PHP用戶區set_error_handler()
函數類似,擴展可以通過實現zend_error_cb
鉤子將自身注冊為錯誤處理程序:
ZEND_API void(* zend_error_cb)(int類型,const char * error_filename,const uint32_t error_lineno,const char * format,va_list args);
type
變量對應于E _ *
錯誤常量,該常量在PHP用戶區中也可用。
PHP核心和用戶態錯誤處理程序之間的關系很復雜:
1.如果未注冊任何用戶級錯誤處理程序,則始終調用zend_error_cb
。
2.如果注冊了userland錯誤處理程序,則對于E_ERROR
,E_PARSE
,E_CORE_ERROR
,E_CORE_WARNING
,E_COMPILE_ERROR的所有錯誤
和E_COMPILE_WARNING
始終調用zend_error_cb
掛鉤。
3.對于所有其他錯誤,僅在用戶態處理程序失敗或返回false
時調用zend_error_cb
。
另外,由于Xdebug自身復雜的實現,它以不調用以前注冊的內部處理程序的方式覆蓋錯誤處理程序。
因此,覆蓋此掛鉤不是很可靠。
再次覆蓋應該以尊重原始處理程序的方式進行,除非您想完全替換它:
void(* original_zend_error_cb)(int類型,const char * error_filename,const uint error_lineno,const char * format,va_list args);void my_error_cb(int類型,const char * error_filename,const uint error_lineno,const char * format,va_list args){ //我的特殊錯誤處理 original_zend_error_cb(type,error_filename,error_lineno,format,args);}PHP_MINIT_FUNCTION(my_extension){ original_zend_error_cb = zend_error_cb; zend_error_cb = my_error_cb; return SUCCESS;}PHP_MSHUTDOWN(my_extension){ zend_error_cb = original_zend_error_cb;}
該掛鉤主要用于為異常跟蹤或應用程序性能管理軟件實施集中式異常跟蹤。
引發異常時的通知每當PHP Core或Userland代碼引發異常時,都會調用zend_throw_exception_hook
并將異常作為參數。
這個鉤子的簽名非常簡單:
void my_throw_exception_hook(zval * exception){ if(original_zend_throw_exception_hook!= NULL){ original_zend_throw_exception_hook(exception); }}
該掛鉤沒有默認實現,如果未被擴展覆蓋,則指向NULL
。
static void(* original_zend_throw_exception_hook)(zval * ex);void my_throw_exception_hook(zval * exception);PHP_MINIT_FUNCTION(my_extension){ original_zend_throw_exception_hook = zend_throw_exception_hook; zend_throw_exception_hook = my_throw_exception_hook; return SUCCESS;}
如果實現此掛鉤,請注意無論是否捕獲到異常,都會調用此掛鉤。將異常臨時存儲在此處,然后將其與錯誤處理程序掛鉤的實現結合起來以檢查異常是否未被捕獲并導致腳本停止,仍然有用。
實現此掛鉤的用例包括調試,日志記錄和異常跟蹤。
掛接到eval()PHPeval
不是內部函數,而是一種特殊的語言構造。因此,您無法通過zend_execute_internal
或通過覆蓋其函數指針來連接它。
掛鉤到eval的用例并不多,您可以將其用于概要分析或出于安全目的。如果更改其行為,請注意可能需要評估其他擴展名。一個示例是Xdebug,它使用它執行斷點條件。
extern ZEND_API zend_op_array *(* zend_compile_string)(zval * source_string,char * filename);掛入垃圾收集器
當可收集對象的數量達到一定閾值時,引擎本身會調用gc_collect_cycles()
或隱式地觸發PHP垃圾收集器。
為了使您了解垃圾收集器的工作方式或分析其性能,可以覆蓋執行垃圾收集操作的函數指針掛鉤。從理論上講,您可以在此處實現自己的垃圾收集算法,但是如果有必要對引擎進行其他更改,則這可能實際上并不可行。
int(* original_gc_collect_cycles)(無效);int my_gc_collect_cycles(無效){ original_gc_collect_cycles();}PHP_MINIT_FUNCTION(my_extension){ original_gc_collect_cycles = gc_collect_cycles; gc_collect_cycles = my_gc_collect_cycles; return SUCCESS;}覆蓋中斷處理程序
當執行器全局EG(vm_interrupt)
設置為1時,將調用一次中斷處理程序。在執行用戶域代碼期間,將在常規檢查點對它進行檢查。引擎使用此掛鉤通過信號處理程序實現PHP執行超時,該信號處理程序在達到超時持續時間后將中斷設置為1。
當更安全地清理或實現自己的超時處理時,這有助于將信號處理推遲到運行時執行的后期。通過設置此掛鉤,您不會意外禁用PHP的超時檢查,因為它具有自定義處理的優先級,該優先級高于對zend_interrupt_function
的任何覆蓋。
ZEND_API void(* original_interrupt_function)(zend_execute_data * execute_data);void my_interrupt_function(zend_execute_data * execute_data){ if(original_interrupt_function!= NULL){ original_interrupt_function(execute_data); }}PHP_MINIT_FUNCTION(my_extension){ original_interrupt_function = zend_interrupt_function; zend_interrupt_function = my_interrupt_function; return SUCCESS;}
##替換操作碼處理程序
TODO
本文題目:PHP之鉤子
URL標題:http://vcdvsql.cn/article14/cheode.html
成都網站建設公司_創新互聯,為您提供網站維護、網站改版、網站設計、企業建站、手機網站建設、建站公司
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯