如果您的脚本超过最大执行时间并因此终止
致命错误:在 - 的第 12 行超过 20 秒的最大执行时间
注册的关闭函数仍将执行。
我认为这应该明确说明!
(PHP 4, PHP 5, PHP 7, PHP 8)
register_shutdown_function — 注册一个在关闭时执行的函数
注册一个callback
,在脚本执行完成或调用exit()后执行。
可以多次调用register_shutdown_function(),并且每次调用都将按照注册的顺序执行。如果在一个注册的关闭函数中调用exit(),则处理将完全停止,并且不会调用其他注册的关闭函数。
关闭函数也可以自己调用register_shutdown_function()来将关闭函数添加到队列的末尾。
callback
要注册的关闭回调。
关闭回调作为请求的一部分执行,因此可以从中发送输出并访问输出缓冲区。
args
可以通过传递附加参数来向关闭函数传递参数。
不返回任何值。
示例 #1 register_shutdown_function() 示例
<?php
function shutdown()
{
// 这是我们的关闭函数,
// 在这里我们可以执行脚本完成之前的任何最后操作。
echo '脚本执行成功', PHP_EOL;
}
register_shutdown_function('shutdown');
?>
注意:
在某些 Web 服务器(例如 Apache)下,脚本的工作目录在关闭函数内可能会更改。
注意:
如果进程被 SIGTERM 或 SIGKILL 信号杀死,则不会执行关闭函数。虽然无法拦截 SIGKILL,但可以使用pcntl_signal()为 SIGTERM 安装一个处理器,该处理器使用exit()干净地结束。
注意:
关闭函数独立于max_execution_time跟踪的时间运行。这意味着即使进程由于运行时间过长而终止,关闭函数仍将被调用。此外,如果
max_execution_time
在关闭函数运行时耗尽,它不会被终止。
如果您的脚本超过最大执行时间并因此终止
致命错误:在 - 的第 12 行超过 20 秒的最大执行时间
注册的关闭函数仍将执行。
我认为这应该明确说明!
如果要在注册到 register_shutdown_function() 的函数中处理文件,请使用文件的绝对路径而不是相对路径。因为当脚本处理完成时,当前工作目录会更改为 ServerRoot(参见 httpd.conf)
许多有用的服务可以委托给这个有用的触发器。
它非常有效,因为它是在脚本结束但任何对象销毁之前执行的,因此所有实例仍然存在。
这是一个简单的关闭事件管理器类,它允许管理函数或静态/动态方法,并具有无限数量的参数,无需使用任何反射,通过 func_get_args() 和 call_user_func_array() 特定函数进行内部处理
<?php
// 管理关闭回调事件:
class shutdownScheduler {
private $callbacks; // 存储用户回调的数组
public function __construct() {
$this->callbacks = array();
register_shutdown_function(array($this, 'callRegisteredShutdown'));
}
public function registerShutdownEvent() {
$callback = func_get_args();
if (empty($callback)) {
trigger_error('没有回调传递到 '.__FUNCTION__.' 方法', E_USER_ERROR);
return false;
}
if (!is_callable($callback[0])) {
trigger_error('无效的回调传递到 '.__FUNCTION__.' 方法', E_USER_ERROR);
return false;
}
$this->callbacks[] = $callback;
return true;
}
public function callRegisteredShutdown() {
foreach ($this->callbacks as $arguments) {
$callback = array_shift($arguments);
call_user_func_array($callback, $arguments);
}
}
// 测试方法:
public function dynamicTest() {
echo '_REQUEST 数组有 '.count($_REQUEST).' 个元素。<br />';
}
public static function staticTest() {
echo '_SERVER 数组有 '.count($_SERVER).' 个元素。<br />';
}
}
?>
一个简单的应用程序
<?php
// 一个通用的函数
function say($a = 'a generic greeting', $b = '') {
echo "Saying {$a} {$b}<br />";
}
$scheduler = new shutdownScheduler();
// 注册一个全局作用域函数:
$scheduler->registerShutdownEvent('say', 'hello!');
// 尝试注册一个动态方法:
$scheduler->registerShutdownEvent(array($scheduler, 'dynamicTest'));
// 使用静态调用尝试:
$scheduler->registerShutdownEvent('scheduler::staticTest');
?>
很容易想到如何在更复杂的上下文中扩展这个例子,其中用户定义的函数和方法应该根据特定变量的优先级进行处理。
希望它能帮到某些人。
编程愉快!
您绝对需要小心在关闭函数被调用后使用相对路径,但是当前工作目录不会(一定)更改为 Web 服务器的 ServerRoot——我已经在两台不同的服务器上进行了测试,它们的工作目录都被更改为“/”(这并非 ServerRoot)。
这演示了这种行为
<?php
function echocwd() { echo 'cwd: ', getcwd(), "\n"; }
register_shutdown_function('echocwd');
echocwd() and exit;
?>
输出
cwd: /path/to/my/site/docroot/test
cwd: /
注意:CLI 脚本不受影响,并保持其工作目录为调用脚本的目录。
您可能会想到从关闭函数内部调用 debug_backtrace 或 debug_print_backtrace 来追踪致命错误发生的位置。不幸的是,这些函数在关闭函数内部不起作用。
与“标题始终已发送”的注释以及下面的某些注释相反——您可以在关闭函数中像在任何其他地方一样使用 header();当 headers_sent() 为 false 时。您可以通过这种方式进行自定义的致命错误处理。
<?php
ini_set('display_errors',0);
register_shutdown_function('shutdown');
$obj = new stdClass();
$obj->method();
function shutdown()
{
if(!is_null($e = error_get_last()))
{
header('content-type: text/plain');
print "this is not html:\n\n". print_r($e,true);
}
}
?>
使用 php-fpm 时,应使用 fastcgi_finish_request() 代替 register_shutdown_function() 和 exit()。
例如,在 nginx 和 php-fpm 5.3+ 下,这将使浏览器等待 10 秒才能显示输出。
<?php
echo "You have to wait 10 seconds to see this.<br>";
register_shutdown_function('shutdown');
exit;
function shutdown(){
sleep(10);
echo "Because exit() doesn't terminate php-fpm calls immediately.<br>";
}
?>
这个不行
<?php
echo "You can see this from the browser immediately.<br>";
fastcgi_finish_request();
sleep(10);
echo "You can't see this form the browser.";
?>
我发现当将关闭函数与输出缓冲回调一起使用时,PHP 5.0.4 到 PHP 5.1.2 的行为发生了变化。
在 PHP 5.0.4(以及我相信的早期版本)中,关闭函数在输出缓冲回调之后被调用。
在 PHP 5.1.2(不确定更改发生的时间)中,关闭函数在输出缓冲回调之前被调用。
测试代码
<?php
function ob_callback($buf) {
$buf .= '<li>' . __FUNCTION__ .'</li>';
return $buf;
}
function shutdown_func() {
echo '<li>' . __FUNCTION__ .'</li>';
}
ob_start('ob_callback');
register_shutdown_function('shutdown_func');
echo '<ol>';
?>
PHP 5.0.4
1. ob_callback
2. shutdown_func
PHP 5.1.2
1. shutdown_func
2. ob_callback
警告:除了 SIGTERM 和 SIGKILL 之外,关闭函数也不会响应 SIGINT。(在 Windows 7 SP1 x64 + cygwin 上的 php 7.1.16 和 Ubuntu 18.04 上的 php 7.2.15 上观察到)
我对 register_shutdown_function() 进行了两次测试,以查看在什么条件下调用它,以及是否可以从类中调用静态方法。以下是结果
<?php
/**
* 测试关闭函数是否能够调用静态方法
*/
class Shutdown
{
public static function Method ($mixed = 0)
{
// 我们需要绝对路径
$ap = dirname (__FILE__);
$mixed = time () . " - $mixed\n";
file_put_contents ("$ap/shutdown.log", $mixed, FILE_APPEND);
}
}
// 3. 抛出异常
register_shutdown_function (array ('Shutdown', 'Method'), 'throw');
throw new Exception ('bla bla');
// 2. 使用 exit 命令
//register_shutdown_function (array ('Shutdown', 'Method'), 'exit');
//exit ('exiting here...')
// 1. 正常退出
//register_shutdown_function (array ('Shutdown', 'Method'));
?>
要测试,只需取消注释三行测试代码中的一行并执行。自下而上执行产生:
1138382480 - 0
1138382503 - exit
1138382564 - throw
希望能帮到您
如果您需要 register_shutdown_function 的旧行为(<4.1),如果您知道已发送数据的精确大小(这可以通过输出缓冲轻松捕获),则可以使用“Connection: close”和“Content-Length: xxxx”标题来实现相同的效果。
一个例子
<?php
header("Connection: close");
ob_start();
phpinfo();
$size=ob_get_length();
header("Content-Length: $size");
ob_end_flush();
flush();
sleep(13);
error_log("do something in the background");
?>
注册函数也可以使用相同的方法。
根据 http 规范,浏览器应该在获取 Content-Length 标头中指定的数据量后关闭连接。至少在我的 IE6 和 Opera7 中运行良好。
> 关闭函数独立于 max_execution_time 追踪的时间运行。这意味着即使进程由于运行时间过长而终止,关闭函数仍将被调用。此外,如果在关闭函数运行时 max_execution_time 耗尽,它也不会被终止。
这在我们的测试中似乎并不正确,特别是“此外,如果在关闭函数运行时 max_execution_time 耗尽,它不会被终止”。例如
<?php
set_time_limit(5);
register_shutdown_function(function() {
$start = time();
for($i = 0; $i < 40; $i++) {
echo "Run 1: $i\n";
while(1) {
$elapsed = time() - $start;
if($elapsed > $i) {
break;
}
}
}
});
?>
这将打印出
Run 1: 0
Run 1: 1
Run 1: 2
Run 1: 3
Run 1: 4
Run 1: 5
然后它将以以下错误终止:
执行时间超过5秒,已发送SIGTERM信号并终止
如果注册了多个关闭函数,而较早的函数执行时间超过限制,则不会运行后面的函数。
如果需要关闭函数具有无限时间(就像文档中建议的那样),一种解决方案是在代码早期注册一个这样的关闭函数:
<?php
register_shutdown_function(function() {
set_time_limit(0);
});
?>
这样,您就可以在后续的关闭函数中拥有无限的时间。
请参见此处的示例:https://www.tehplayground.com/wJLtMi3Z5c1sTi9Y(注意,在这个网站上,您最多可以设置5秒。如果您移除第一个关闭函数,它将在3秒后被终止)。
我想使用类属性从关闭函数设置CLI应用程序的退出代码,
不幸的是(根据手册),“如果您在一个注册的关闭函数中调用exit(),处理将完全停止,并且不会调用任何其他注册的关闭函数。”
因此,如果我在关闭函数中调用exit,我将中断其他关闭函数(例如将致命错误记录到syslog的函数)
但是!(根据手册)“可以多次调用register_shutdown_function(),并且每个函数都将按注册顺序调用。”
幸运的是,您还可以在关闭函数内注册关闭函数(至少在PHP 7.0.15和5.6.30中)。
换句话说,如果您在一个关闭函数内注册一个关闭函数,它将被添加到关闭函数队列中。
<?php
class SomeApplication{
private $exitCode = null;
public function __construct(){
register_shutdown_function(function(){
echo "some registered shutdown function";
register_shutdown_function(function(){
echo "last registered shutdown function";
// 人们甚至可以考虑另一个register_shutdown_function,如果预期其他关闭函数做同样的事情……
exit($this->exitCode === null ? 0 : $this->exitCode);
});
});
}
}
?>