如何在PHP脚本将要退出前执行代码

这篇文章,我们来介绍下如何通过PHP扩展在PHP脚本将要退出前执行代码。我们可以看一段Swoole的协程代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

use Swoole\Coroutine;

go(function () {
go(function () {
Coroutine::sleep(2);
var_dump("1");
});
go(function () {
Coroutine::sleep(2);
var_dump("2");
});
var_dump("3");
});
var_dump("4");

执行结果如下:

1
2
3
4
string(1) "3"
string(1) "4"
string(1) "1"
string(1) "2"

我们发现,在打印4之后,PHP脚本算是执行完了,这个时候,PHP进程也快要退出了。那为什么还会打印出12呢?

因为PHP是一门解释性语言,虽然PHP脚本的代码跑完了,但是PHP命令解释器还没有跑完,自然可以让PHP代码继续跑。所以,实际上,这个代码会这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

use Swoole\Coroutine;
use Swoole\Event;

go(function () {
go(function () {
Coroutine::sleep(2);
var_dump("1");
});
go(function () {
Coroutine::sleep(2);
var_dump("2");
});
var_dump("3");
});
var_dump("4");

Event::wait();

在脚本的最后会调用Event::wait()来等待事件的结束。(而这里的事件就是定时器的事件)

好的,我们现在通过一个小demo来实现这个功能。

首先,我们需要创建PHP扩展的基础骨架。通过ext_skel工具生成:

1
[root@64fa874bf7d4 ext]# php ext_skel.php --ext register

然后进入register目录:

1
[root@64fa874bf7d4 ext]# cd register/

替换config.m4为如下内容:

1
2
3
4
5
6
7
PHP_ARG_ENABLE(register, whether to enable register support,
Make sure that the comment is aligned:
[ --enable-register Enable register support])

if test "$PHP_REGISTER" != "no"; then
PHP_NEW_EXTENSION(register, register.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
fi

然后,我们创建和编写测试脚本:

1
[root@64fa874bf7d4 register]# touch test.php
1
2
3
4
5
<?php

function test() {
var_dump("codinghuang");
}

这段脚本只定义了一个test函数,并没有调用。现在我们的任务就是去调用它。

我们开始编写PHP扩展。在文件register.cPHP_RINIT_FUNCTION里面注册test函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "ext/standard/basic_functions.h"
#include "zend_API.h"

PHP_RINIT_FUNCTION(register)
{
#if defined(ZTS) && defined(COMPILE_DL_REGISTER)
ZEND_TSRMLS_CACHE_UPDATE();
#endif

php_shutdown_function_entry shutdown_function_entry;
shutdown_function_entry.arg_count = 1;
shutdown_function_entry.arguments = (zval *) safe_emalloc(sizeof(zval), 1, 0);
ZVAL_STRING(&shutdown_function_entry.arguments[0], "test");
register_user_shutdown_function("test", ZSTR_LEN(Z_STR(shutdown_function_entry.arguments[0])), &shutdown_function_entry);

return SUCCESS;
}

这里,我们首先对php_shutdown_function_entry结构进行初始化,php_shutdown_function_entry.arguments的第一个位置填函数的名字。shutdown_function_entry.arg_count填写1,因为函数名字也算做是arguments。初始化完php_shutdown_function_entry之后,我们调用register_user_shutdown_function函数即可注册test函数了。这样,就会在php请求shutdown阶段调用我们注册的函数了。

然后编译、安装扩展:

1
[root@64fa874bf7d4 register]# phpize && ./configure && make && make install

然后把扩展在php.ini文件里面进行开启:

1
2
; Enable zlib extension module
extension=register.so

然后执行脚本:

1
2
3
[root@64fa874bf7d4 register]# php test.php
string(11) "codinghuang"
[root@64fa874bf7d4 register]#

我们发现,成功的调用了test函数。

这让我想起了我之前面试腾讯的时候,有一道题目,说是如何在每一个PHP函数调用之前,都执行一段代码。这个问题以后补上。