您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)PHP中鉤子是什么意思,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
PHP 和 Zend Engine 為擴(kuò)展提供了許多不同的鉤子,這些擴(kuò)展允許擴(kuò)展開(kāi)發(fā)人員以 PHP userland 無(wú)法提供的方式控制 PHP 運(yùn)行時(shí)。
本章將展示各種鉤子和從擴(kuò)展鉤子到它們的常見(jiàn)用例。
鉤子到 PHP 功能的一般模式是 PHP 核心提供的擴(kuò)展覆蓋函數(shù)指針。然后擴(kuò)展函數(shù)通常執(zhí)行自己的工作并調(diào)用原始 PHP 核心函數(shù)。使用此模式,不同的擴(kuò)展可以覆蓋同一個(gè)鉤子而不會(huì)導(dǎo)致沖突。
userland和內(nèi)部函數(shù)的執(zhí)行由Zend引擎中的兩個(gè)函數(shù)處理,您可以用自己的實(shí)現(xiàn)替換這兩個(gè)函數(shù)。覆蓋此鉤子的擴(kuò)展的主要用例是通用函數(shù)級(jí)評(píng)測(cè)、調(diào)試和面向方面的編程。
鉤子在 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);
如果要覆蓋這些函數(shù)指針,則必須在 Minit 中執(zhí)行此操作,因?yàn)?Zend Engine 中的其他決策是根據(jù)指針是否被覆蓋這一事實(shí)提前做出的。
覆蓋的通常模式是這樣的:
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
的一個(gè)缺點(diǎn)是它將 Zend Virtual Machine 運(yùn)行時(shí)的行為更改為使用遞歸,而不是在不離開(kāi)解釋器循環(huán)的情況下處理調(diào)用。此外,沒(méi)有覆蓋zend_execute_ex
的 PHP 引擎也可以生成更優(yōu)化的函數(shù)調(diào)用操作碼。
這些掛鉤對(duì)性能非常敏感,具體取決于原始函數(shù)封裝代碼的復(fù)雜性。
在覆蓋執(zhí)行鉤子時(shí),擴(kuò)展可以記錄每個(gè)函數(shù)調(diào)用,你還可以覆蓋用戶(hù)域,核心和擴(kuò)展函數(shù)(和方法)的各個(gè)函數(shù)指針。如果擴(kuò)展僅需要訪(fǎng)問(wèn)特定的內(nèi)部函數(shù)調(diào)用,則具有更好的性能特征。
#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){ // 如果我們想調(diào)用原始函數(shù) 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; }}
覆蓋類(lèi)方法時(shí),可以在 zend_class_entry
上找到函數(shù)表:
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; }}
當(dāng) PHP 7編譯 PHP 代碼時(shí),它會(huì)先將其轉(zhuǎn)換為抽象語(yǔ)法樹(shù)(AST),然后最終生成持久存儲(chǔ)在 Opcache 中的操作碼。zend_ast_process
鉤子會(huì)被每個(gè)已編譯的腳本調(diào)用,并允許你在解析和創(chuàng)建 AST 之后修改 AST。
這是要使用的最復(fù)雜的鉤子之一,因?yàn)樗枰耆私?AST。在此處創(chuàng)建無(wú)效的 AST 可能會(huì)導(dǎo)致異常行為或崩潰。
最好看看使用此鉤子的示例擴(kuò)展:
每當(dāng)用戶(hù)腳本調(diào)用include
/require
或其對(duì)應(yīng)的include_once
/require_once
時(shí),PHP內(nèi)核都會(huì)在指針zend_compile_file處調(diào)用該函數(shù)
處理此請(qǐng)求。參數(shù)是文件句柄,結(jié)果是zend_op_array
。
zend_op_array * my_extension_compile_file(zend_file_handle * file_handle,int類(lèi)型);
PHP核心中有兩個(gè)擴(kuò)展實(shí)現(xiàn)了此掛鉤:dtrace和opcache。
-如果您使用環(huán)境變量USE_ZEND_DTRACE
啟動(dòng)PHP腳本并使用dtrace支持編譯了PHP,則dtrace_compile_file
用于Zend / zend_dtrace.c
。
-Opcache將操作數(shù)組存儲(chǔ)在共享內(nèi)存中以獲得更好的性能,因此,每當(dāng)腳本被編譯時(shí),其最終的操作數(shù)組都會(huì)從緩存中得到服務(wù),而不是重新編譯。您可以在ext / opcache / ZendAccelerator.c
中找到此實(shí)現(xiàn)。
-名為compile_file
的默認(rèn)實(shí)現(xiàn)是Zend / zend_language_scanner.l
中掃描程序代碼的一部分。
實(shí)施此掛鉤的用例是Opcode Accelerating,PHP代碼加密/解密,調(diào)試或概要分析。
您可以隨時(shí)在執(zhí)行PHP進(jìn)程時(shí)替換該掛鉤,并且替換后編譯的所有PHP腳本都將由該掛鉤的實(shí)現(xiàn)處理。
始終調(diào)用原始函數(shù)指針?lè)浅V匾?,否則PHP將無(wú)法再編譯腳本,并且Opcache將不再起作用。
此處的擴(kuò)展覆蓋順序也很重要,因?yàn)槟枰朗且贠pcache之前還是之后注冊(cè)鉤子,因?yàn)镺pcache如果在其共享內(nèi)存緩存中找到操作碼數(shù)組條目,則不會(huì)調(diào)用原始函數(shù)指針。 Opcache將其鉤子注冊(cè)為啟動(dòng)后鉤子,該鉤子在擴(kuò)展的minit階段之后運(yùn)行,因此默認(rèn)情況下,緩存腳本時(shí)將不再調(diào)用該鉤子。
與PHP用戶(hù)區(qū)set_error_handler()
函數(shù)類(lèi)似,擴(kuò)展可以通過(guò)實(shí)現(xiàn)zend_error_cb
鉤子將自身注冊(cè)為錯(cuò)誤處理程序:
ZEND_API void(* zend_error_cb)(int類(lèi)型,const char * error_filename,const uint32_t error_lineno,const char * format,va_list args);
type
變量對(duì)應(yīng)于E _ *
錯(cuò)誤常量,該常量在PHP用戶(hù)區(qū)中也可用。
PHP核心和用戶(hù)態(tài)錯(cuò)誤處理程序之間的關(guān)系很復(fù)雜:
1.如果未注冊(cè)任何用戶(hù)級(jí)錯(cuò)誤處理程序,則始終調(diào)用zend_error_cb
。
2.如果注冊(cè)了userland錯(cuò)誤處理程序,則對(duì)于E_ERROR
,E_PARSE
,E_CORE_ERROR
,E_CORE_WARNING
,E_COMPILE_ERROR的所有錯(cuò)誤
和E_COMPILE_WARNING
始終調(diào)用zend_error_cb
掛鉤。
3.對(duì)于所有其他錯(cuò)誤,僅在用戶(hù)態(tài)處理程序失敗或返回false
時(shí)調(diào)用zend_error_cb
。
另外,由于Xdebug自身復(fù)雜的實(shí)現(xiàn),它以不調(diào)用以前注冊(cè)的內(nèi)部處理程序的方式覆蓋錯(cuò)誤處理程序。
因此,覆蓋此掛鉤不是很可靠。
再次覆蓋應(yīng)該以尊重原始處理程序的方式進(jìn)行,除非您想完全替換它:
void(* original_zend_error_cb)(int類(lèi)型,const char * error_filename,const uint error_lineno,const char * format,va_list args);void my_error_cb(int類(lèi)型,const char * error_filename,const uint error_lineno,const char * format,va_list args){ //我的特殊錯(cuò)誤處理 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;}
該掛鉤主要用于為異常跟蹤或應(yīng)用程序性能管理軟件實(shí)施集中式異常跟蹤。
每當(dāng)PHP Core或Userland代碼引發(fā)異常時(shí),都會(huì)調(diào)用zend_throw_exception_hook
并將異常作為參數(shù)。
這個(gè)鉤子的簽名非常簡(jiǎn)單:
void my_throw_exception_hook(zval * exception){ if(original_zend_throw_exception_hook!= NULL){ original_zend_throw_exception_hook(exception); }}
該掛鉤沒(méi)有默認(rèn)實(shí)現(xiàn),如果未被擴(kuò)展覆蓋,則指向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;}
如果實(shí)現(xiàn)此掛鉤,請(qǐng)注意無(wú)論是否捕獲到異常,都會(huì)調(diào)用此掛鉤。將異常臨時(shí)存儲(chǔ)在此處,然后將其與錯(cuò)誤處理程序掛鉤的實(shí)現(xiàn)結(jié)合起來(lái)以檢查異常是否未被捕獲并導(dǎo)致腳本停止,仍然有用。
實(shí)現(xiàn)此掛鉤的用例包括調(diào)試,日志記錄和異常跟蹤。
PHPeval
不是內(nèi)部函數(shù),而是一種特殊的語(yǔ)言構(gòu)造。因此,您無(wú)法通過(guò)zend_execute_internal
或通過(guò)覆蓋其函數(shù)指針來(lái)連接它。
掛鉤到eval的用例并不多,您可以將其用于概要分析或出于安全目的。如果更改其行為,請(qǐng)注意可能需要評(píng)估其他擴(kuò)展名。一個(gè)示例是Xdebug,它使用它執(zhí)行斷點(diǎn)條件。
extern ZEND_API zend_op_array *(* zend_compile_string)(zval * source_string,char * filename);
當(dāng)可收集對(duì)象的數(shù)量達(dá)到一定閾值時(shí),引擎本身會(huì)調(diào)用gc_collect_cycles()
或隱式地觸發(fā)PHP垃圾收集器。
為了使您了解垃圾收集器的工作方式或分析其性能,可以覆蓋執(zhí)行垃圾收集操作的函數(shù)指針掛鉤。從理論上講,您可以在此處實(shí)現(xiàn)自己的垃圾收集算法,但是如果有必要對(duì)引擎進(jìn)行其他更改,則這可能實(shí)際上并不可行。
int(* original_gc_collect_cycles)(無(wú)效);int my_gc_collect_cycles(無(wú)效){ 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;}
當(dāng)執(zhí)行器全局EG(vm_interrupt)
設(shè)置為1時(shí),將調(diào)用一次中斷處理程序。在執(zhí)行用戶(hù)域代碼期間,將在常規(guī)檢查點(diǎn)對(duì)它進(jìn)行檢查。引擎使用此掛鉤通過(guò)信號(hào)處理程序?qū)崿F(xiàn)PHP執(zhí)行超時(shí),該信號(hào)處理程序在達(dá)到超時(shí)持續(xù)時(shí)間后將中斷設(shè)置為1。
當(dāng)更安全地清理或?qū)崿F(xiàn)自己的超時(shí)處理時(shí),這有助于將信號(hào)處理推遲到運(yùn)行時(shí)執(zhí)行的后期。通過(guò)設(shè)置此掛鉤,您不會(huì)意外禁用PHP的超時(shí)檢查,因?yàn)樗哂凶远x處理的優(yōu)先級(jí),該優(yōu)先級(jí)高于對(duì)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
關(guān)于PHP中鉤子是什么意思就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。