如果您的腳本超出最大執行時間,並因此終止
致命錯誤:最大執行時間 20 秒已超過,位於 - 的第 12 行
仍會執行已註冊的關閉函式。
我認為有必要明確說明這一點!
(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
在關閉函式執行時耗盡,則不會終止它。
如果您的腳本超出最大執行時間,並因此終止
致命錯誤:最大執行時間 20 秒已超過,位於 - 的第 12 行
仍會執行已註冊的關閉函式。
我認為有必要明確說明這一點!
如果您想在於 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 = '一個通用的問候', $b = '') {
echo "說 {$a} {$b}<br />";
}
$scheduler = new shutdownScheduler();
// 安排一個全域範圍的函式:
$scheduler->registerShutdownEvent('say', 'hello!');
// 嘗試安排一個動態方法:
$scheduler->registerShutdownEvent(array($scheduler, 'dynamicTest'));
// 嘗試使用靜態呼叫:
$scheduler->registerShutdownEvent('scheduler::staticTest');
?>
很容易猜到如何在更複雜的環境中擴展這個例子,在這種環境中,使用者定義的函式和方法應該根據特定變數的優先順序來處理。
希望對某些人有所幫助。
祝您編碼愉快!
在呼叫關閉函式後,您絕對需要小心使用相對路徑,因為目前的工作目錄不一定會更改為網頁伺服器的 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 保留為呼叫腳本的目錄。
您可能會想到從關閉函式內部呼叫 debug_backtrace 或 debug_print_backtrace,以追蹤發生嚴重錯誤的位置。不幸的是,這些函式在關閉函式內無法運作。
與「標頭總是傳送」的註解以及下方的一些評論相反 - 您可以在 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);
}
}
?>
當使用 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 "您無法從瀏覽器看到此內容。";
?>
我發現在 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 ('在這裡結束...')
// 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("在背景中執行一些操作");
?>
同樣的方法也適用於已註冊的函式。
根據 http 規範,當瀏覽器收到 Content-Length 標頭中指定的資料量時,瀏覽器應該關閉連線。至少在 IE6 和 Opera7 中,它對我來說運作良好。
> 關機函式 (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 秒後被終止)。
我想要使用類別屬性從關機函式設定 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);
});
});
}
}
?>