7.4 钩子函数

PHP为扩展提供了5个钩子函数,PHP执行到不同阶段时回调各个扩展定义的钩子函数,扩展可以通过这些钩子函数介入到PHP生命周期的不同阶段中去,这些钩子函数的定义非常简单,PHP提供了对应的宏,定义完成后只需要设置zend_module_entry对应的函数指针即可。

前面已经介绍过PHP生命周期的几个阶段,这几个钩子函数执行的先后顺序:module startup -> request startup -> 编译、执行 -> request shutdown -> post deactivate -> module shutdown。

7.4.1 module_startup_func

这个函数在PHP模块初始化阶段执行,通常情况下,此过程只会在SAPI启动后执行一次。这个阶段可以进行内部类的注册,如果你的扩展提供了类就可以在此函数中完成注册;除了类还可以在此函数中注册扩展定义的常量;另外,扩展可以在此阶段覆盖PHP编译、执行的两个函数指针:zend_compile_file、zend_execute_ex,从而可以接管PHP的编译、执行,opcache的实现原理就是替换了zend_compile_file,从而使得PHP编译时调用的是opcache自己定义的编译函数,对编译后的结果进行缓存。

此钩子函数通过PHP_MINIT_FUNCTION()ZEND_MINIT_FUNCTION()宏完成定义:

  1. PHP_MINIT_FUNCTION(extension_name)
  2. {
  3. ...
  4. }

展开后:

  1. zm_startup_extension_name(int type, int module_number)
  2. {
  3. ...
  4. }

最后通过PHP_MINIT()ZEND_MINIT()宏将zend_module_entry的module_startup_func设置为上面定义的函数。

  1. #define PHP_MINIT ZEND_MODULE_STARTUP_N
  2. #define ZEND_MINIT ZEND_MODULE_STARTUP_N
  3. #define ZEND_MODULE_STARTUP_N(module) zm_startup_##module

7.4.2 request_startup_func

此函数在编译、执行之前回调,fpm模式下每一个http请求就是一个request,脚本执行前将首先执行这个函数。如果你的扩展需要针对每一个请求进行处理则可以设置这个函数,如:对请求进行filter、根据请求ip获取所在城市、对请求/返回数据加解密等。此函数通过PHP_RINIT_FUNCTION()ZEND_RINIT_FUNCTION()宏定义:

  1. PHP_RINIT_FUNCTION(extension_name)
  2. {
  3. ...
  4. }

展开后:

  1. zm_activate_extension_name(int type, int module_number)
  2. {
  3. ...
  4. }

获取函数地址的宏:PHP_RINIT()ZEND_RINIT()

  1. #define PHP_RINIT ZEND_MODULE_ACTIVATE_N
  2. #define ZEND_RINIT ZEND_MODULE_ACTIVATE_N
  3. #define ZEND_MODULE_ACTIVATE_N(module) zm_activate_##module

7.4.3 request_shutdown_func

此函数在请求结束时被调用,通过PHP_RSHUTDOWN_FUNCTION()ZEND_RSHUTDOWN_FUNCTION()宏定义:

  1. PHP_RSHUTDOWN_FUNCTION(extension_name)
  2. {
  3. ...
  4. }

函数地址通过PHP_RSHUTDOWN()ZEND_RSHUTDOWN()获取:

  1. #define PHP_RSHUTDOWN ZEND_MODULE_DEACTIVATE_N
  2. #define ZEND_RSHUTDOWN ZEND_MODULE_DEACTIVATE_N
  3. #define ZEND_MODULE_DEACTIVATE_N(module) zm_deactivate_##module

7.4.4 post_deactivate_func

这个函数比较特殊,一般很少会用到,实际它也是在请求结束之后调用的,它比request_shutdown_func更晚执行:

  1. void php_request_shutdown(void *dummy)
  2. {
  3. ...
  4. //调用各扩展的request_shutdown_func
  5. if (PG(modules_activated)) {
  6. zend_deactivate_modules();
  7. }
  8. //关闭输出:发送http header
  9. php_output_deactivate();
  10. //释放超全局变量:$_GET、$_POST...
  11. ...
  12. //关闭编译器、执行器
  13. zend_deactivate();
  14. //调用每个扩展的post_deactivate_func
  15. zend_post_deactivate_modules();
  16. ...
  17. }

从上面的执行顺序可以看出,request_shutdown_func、post_deactivate_func是先后执行的,此函数通过ZEND_MODULE_POST_ZEND_DEACTIVATE_D()宏定义,ZEND_MODULE_POST_ZEND_DEACTIVATE_N()获取函数地址:

  1. #define ZEND_MINIT ZEND_MODULE_STARTUP_N
  2. #define ZEND_MODULE_POST_ZEND_DEACTIVATE_N(module) zm_post_zend_deactivate_##module

7.4.5 module_shutdown_func

模块关闭阶段回调的函数,与module_startup_func对应,此阶段主要可以进行一些资源的清理,通过PHP_MSHUTDOWN_FUNCTION()ZEND_MSHUTDOWN_FUNCTION()定义:

  1. PHP_MSHUTDOWN_FUNCTION(extension_name)
  2. {
  3. ...
  4. }

通过PHP_MSHUTDOWN()ZEND_MSHUTDOWN()获取函数地址:

  1. #define PHP_MSHUTDOWN ZEND_MODULE_SHUTDOWN_N
  2. #define ZEND_MSHUTDOWN ZEND_MODULE_SHUTDOWN_N
  3. #define ZEND_MODULE_SHUTDOWN_N(module) zm_shutdown_##module

7.4.6 小节 上面详细介绍了各个阶段定义的钩子函数的格式,使用gdb调试扩展时可以根据展开后实际的函数名称设置断点。这些钩子实际已经为扩展构造了一个整体的框架,通过这几个钩子扩展已经能实现很多功能了,后面我们介绍的很多内容都是在这几个函数中完成的,比如内部类的注册、常量注册、资源注册等。如果扩展名称为mytest,则最终定义的扩展:

  1. PHP_MINIT_FUNCTION(mytest)
  2. {
  3. ...
  4. }
  5. PHP_RINIT_FUNCTION(mytest)
  6. {
  7. ...
  8. }
  9. PHP_RSHUTDOWN_FUNCTION(mytest)
  10. {
  11. ...
  12. }
  13. PHP_MSHUTDOWN_FUNCTION(mytest)
  14. {
  15. ...
  16. }
  17. zend_module_entry mytest_module_entry = {
  18. STANDARD_MODULE_HEADER,
  19. "mytest",
  20. NULL, //mytest_functions,
  21. PHP_MINIT(mytest),
  22. PHP_MSHUTDOWN(mytest),
  23. PHP_RINIT(mytest),
  24. PHP_RSHUTDOWN(mytest),
  25. NULL, //PHP_MINFO(mytest),
  26. "1.0.0",
  27. STANDARD_MODULE_PROPERTIES
  28. };
  29. ZEND_GET_MODULE(mytest)