PHP 日本会议 2024

register_shutdown_function

(PHP 4, PHP 5, PHP 7, PHP 8)

register_shutdown_function注册一个在关闭时执行的函数

描述

register_shutdown_function(callable $callback, mixed ...$args): void

注册一个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在关闭函数运行时耗尽,它不会被终止。

参见

添加注释

用户贡献的注释 14 个注释

jules at sitepointAASASZZ dot com
21 年前
如果您的脚本超过最大执行时间并因此终止

致命错误:在 - 的第 12 行超过 20 秒的最大执行时间

注册的关闭函数仍将执行。

我认为这应该明确说明!
http://livejournal.com/~sinde1/
19 年前
如果要在注册到 register_shutdown_function() 的函数中处理文件,请使用文件的绝对路径而不是相对路径。因为当脚本处理完成时,当前工作目录会更改为 ServerRoot(参见 httpd.conf)
emanueledelgrande ad email dot it
14 年前
许多有用的服务可以委托给这个有用的触发器。
它非常有效,因为它是在脚本结束但任何对象销毁之前执行的,因此所有实例仍然存在。

这是一个简单的关闭事件管理器类,它允许管理函数或静态/动态方法,并具有无限数量的参数,无需使用任何反射,通过 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');

?>

很容易想到如何在更复杂的上下文中扩展这个例子,其中用户定义的函数和方法应该根据特定变量的优先级进行处理。

希望它能帮到某些人。
编程愉快!
pgl at yoyo dot org
15年前
您绝对需要小心在关闭函数被调用后使用相对路径,但是当前工作目录不会(一定)更改为 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 脚本不受影响,并保持其工作目录为调用脚本的目录。
ravenswd at gmail dot com
14 年前
您可能会想到从关闭函数内部调用 debug_backtrace 或 debug_print_backtrace 来追踪致命错误发生的位置。不幸的是,这些函数在关闭函数内部不起作用。
RLK
16年前
与“标题始终已发送”的注释以及下面的某些注释相反——您可以在关闭函数中像在任何其他地方一样使用 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);
}
}
?>
jawsper at aximax dot nl
14 年前
在测试期间发现的一些内容

ini auto_append_file 将在注册的函数被调用之前包含。
alexyam at live dot com
12年前
使用 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.";
?>
dweingart at pobox dot com
18年前
我发现当将关闭函数与输出缓冲回调一起使用时,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
Anonymous
5年前
警告:除了 SIGTERM 和 SIGKILL 之外,关闭函数也不会响应 SIGINT。(在 Windows 7 SP1 x64 + cygwin 上的 php 7.1.16 和 Ubuntu 18.04 上的 php 7.2.15 上观察到)
kenneth dot kalmer at gmail dot com
18年前
我对 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

希望能帮到您
sts at mail dot xubion dot hu
20年前
如果您需要 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 中运行良好。
josh at joshstrange dot com
1年前
> 关闭函数独立于 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秒后被终止)。
raat1979 at gmail dot com
7年前
我想使用类属性从关闭函数设置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);
});
});
}
}
?>
To Top