PHP Conference Japan 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 則註解

260
jules at sitepointAASASZZ dot com
21 年前
如果您的腳本超出最大執行時間,並因此終止

致命錯誤:最大執行時間 20 秒已超過,位於 - 的第 12 行

仍會執行已註冊的關閉函式。

我認為有必要明確說明這一點!
82
http://livejournal.com/~sinde1/
19 年前
如果您想在於 register_shutdown_function() 中註冊的函式中使用檔案,請使用檔案的「絕對」路徑,而不是相對路徑。因為當腳本處理完成時,目前的工作目錄會變更為 ServerRoot (請參閱 httpd.conf)
22
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 = '一個通用的問候', $b = '') {
echo
"說 {$a} {$b}<br />";
}

$scheduler = new shutdownScheduler();

// 安排一個全域範圍的函式:
$scheduler->registerShutdownEvent('say', 'hello!');

// 嘗試安排一個動態方法:
$scheduler->registerShutdownEvent(array($scheduler, 'dynamicTest'));
// 嘗試使用靜態呼叫:
$scheduler->registerShutdownEvent('scheduler::staticTest');

?>

很容易猜到如何在更複雜的環境中擴展這個例子,在這種環境中,使用者定義的函式和方法應該根據特定變數的優先順序來處理。

希望對某些人有所幫助。
祝您編碼愉快!
14
pgl at yoyo dot org
15 年前
在呼叫關閉函式後,您絕對需要小心使用相對路徑,因為目前的工作目錄不一定會更改為網頁伺服器的 ServerRoot - 我在兩個不同的伺服器上測試過,它們的 CWD 都變更為 '/'(這不是 ServerRoot)。

這示範了此行為

<?php
function echocwd() { echo 'cwd: ', getcwd(), "\n"; }

register_shutdown_function('echocwd');
echocwd() and exit;
?>

輸出

cwd: /path/to/my/site/docroot/test
cwd: /

注意:CLI 腳本不受影響,並將其 CWD 保留為呼叫腳本的目錄。
25
ravenswd at gmail dot com
14 年前
您可能會想到從關閉函式內部呼叫 debug_backtrace 或 debug_print_backtrace,以追蹤發生嚴重錯誤的位置。不幸的是,這些函式在關閉函式內無法運作。
8
RLK
16 年前
與「標頭總是傳送」的註解以及下方的一些評論相反 - 您可以在 header() 為 false 時,像在其他地方一樣在關閉函式內使用 headers_sent()。您可以透過這種方式進行自訂的嚴重錯誤處理。

<?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
"這不是 html:\n\n". print_r($e,true);
}
}
?>
3
jawsper at aximax dot nl
14 年前
測試期間發現的一些事情

ini 的 auto_append_file 將會在呼叫已註冊的函式之前包含進來。
9
alexyam at live dot com
12 年前
當使用 php-fpm 時,應該使用 fastcgi_finish_request() 而不是 register_shutdown_function() 和 exit()

例如,在 nginx 和 php-fpm 5.3+ 下,這會讓瀏覽器等待 10 秒鐘才能顯示輸出

<?php
echo "您必須等待 10 秒才能看到此內容。<br>";
register_shutdown_function('shutdown');
exit;
function
shutdown(){
sleep(10);
echo
"因為 exit() 不會立即終止 php-fpm 的呼叫。<br>";
}
?>

這不會

<?php
echo "您可以在瀏覽器中立即看到此內容。<br>";
fastcgi_finish_request();
sleep(10);
echo
"您無法從瀏覽器看到此內容。";
?>
4
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
1
Anonymous
5 年前
警告:除了 SIGTERM 和 SIGKILL 之外,關閉函式也不會回應 SIGINT 而執行。(在 Windows 7 SP1 x64 + cygwin 上的 php 7.1.16 和 Ubuntu 18.04 上的 php 7.2.15 中觀察到)
1
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 ('在這裡結束...')

// 1. 正常結束
//register_shutdown_function (array ('Shutdown', 'Method'));
?>

若要測試,只需將三個測試行中的其中一個取消註解並執行。由下而上執行會產生

1138382480 - 0
1138382503 - exit
1138382564 - throw

希望有所幫助
1
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("在背景中執行一些操作");
?>

同樣的方法也適用於已註冊的函式。
根據 http 規範,當瀏覽器收到 Content-Length 標頭中指定的資料量時,瀏覽器應該關閉連線。至少在 IE6 和 Opera7 中,它對我來說運作良好。
1
josh at joshstrange dot com
1 年前
> 關機函式 (Shutdown functions) 的執行時間與 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 秒後被終止)。
2
raat1979 at gmail dot com
7 年前
我想要使用類別屬性從關機函式設定 CLI 應用程式的結束代碼。

不幸的是(根據手冊),「如果您在其中一個已註冊的關機函式中呼叫 exit(),則處理程序將完全停止,並且不會呼叫其他已註冊的關機函式。」

因此,如果我在關機函式中呼叫 exit,我會中斷其他關機函式(例如將嚴重錯誤記錄到系統日誌的函式)。

然而!(根據手冊) 「可以多次呼叫 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