## 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()`宏完成定义：
```c
PHP_MINIT_FUNCTION(extension_name)
{
	...
}
```
展开后：
```c
zm_startup_extension_name(int type, int module_number)
{
	...
}
```
最后通过`PHP_MINIT()`或`ZEND_MINIT()`宏将zend_module_entry的module_startup_func设置为上面定义的函数。
```c
#define PHP_MINIT          ZEND_MODULE_STARTUP_N
#define ZEND_MINIT          ZEND_MODULE_STARTUP_N

#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()`宏定义：
```c
PHP_RINIT_FUNCTION(extension_name)
{
	...
}
```
展开后：
```c
zm_activate_extension_name(int type, int module_number)
{
	...
}
```
获取函数地址的宏：`PHP_RINIT()`或`ZEND_RINIT()`：
```c
#define PHP_RINIT          ZEND_MODULE_ACTIVATE_N
#define ZEND_RINIT          ZEND_MODULE_ACTIVATE_N

#define ZEND_MODULE_ACTIVATE_N(module)      zm_activate_##module
```
### 7.4.3 request_shutdown_func
此函数在请求结束时被调用，通过`PHP_RSHUTDOWN_FUNCTION()`或`ZEND_RSHUTDOWN_FUNCTION()`宏定义：
```c
PHP_RSHUTDOWN_FUNCTION(extension_name)
{
	...
}
```
函数地址通过`PHP_RSHUTDOWN()`或`ZEND_RSHUTDOWN()`获取：
```c
#define PHP_RSHUTDOWN      ZEND_MODULE_DEACTIVATE_N
#define ZEND_RSHUTDOWN      ZEND_MODULE_DEACTIVATE_N

#define ZEND_MODULE_DEACTIVATE_N(module)    zm_deactivate_##module
```
### 7.4.4 post_deactivate_func
这个函数比较特殊，一般很少会用到，实际它也是在请求结束之后调用的，它比request_shutdown_func更晚执行：
```c
void php_request_shutdown(void *dummy)
{
	...
	//调用各扩展的request_shutdown_func
	if (PG(modules_activated)) {
		zend_deactivate_modules();
	}
	//关闭输出:发送http header
	php_output_deactivate();

	//释放超全局变量：$_GET、$_POST...
	...
	//关闭编译器、执行器
	zend_deactivate();

	//调用每个扩展的post_deactivate_func
	zend_post_deactivate_modules();
	...
}
```
从上面的执行顺序可以看出，request_shutdown_func、post_deactivate_func是先后执行的，此函数通过`ZEND_MODULE_POST_ZEND_DEACTIVATE_D()`宏定义，`ZEND_MODULE_POST_ZEND_DEACTIVATE_N()`获取函数地址：
```c
#define ZEND_MINIT          ZEND_MODULE_STARTUP_N
#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()`定义：
```c
PHP_MSHUTDOWN_FUNCTION(extension_name)
{
	...
}
```
通过`PHP_MSHUTDOWN()`或`ZEND_MSHUTDOWN()`获取函数地址：
```c
#define PHP_MSHUTDOWN      ZEND_MODULE_SHUTDOWN_N
#define ZEND_MSHUTDOWN      ZEND_MODULE_SHUTDOWN_N

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

PHP_RINIT_FUNCTION(mytest)
{
	...
}

PHP_RSHUTDOWN_FUNCTION(mytest)
{
	...
}

PHP_MSHUTDOWN_FUNCTION(mytest)
{
	...
}

zend_module_entry mytest_module_entry = {
    STANDARD_MODULE_HEADER,
    "mytest",
    NULL, //mytest_functions,
    PHP_MINIT(mytest),
    PHP_MSHUTDOWN(mytest),
    PHP_RINIT(mytest),
    PHP_RSHUTDOWN(mytest),
    NULL, //PHP_MINFO(mytest),
    "1.0.0",
    STANDARD_MODULE_PROPERTIES
};

ZEND_GET_MODULE(mytest)
```

