PHP Conference Japan 2024

set_exception_handler

(PHP 5, PHP 7, PHP 8)

set_exception_handler 設定使用者定義的例外處理函式

描述

set_exception_handler(?callable $callback): ?callable

如果例外在 try/catch 區塊中未被捕捉,則設定預設例外處理函式。在 callback 被呼叫後,執行將停止。

參數

callback

當發生未捕捉的例外時要呼叫的函式。此處理函式需要接受一個參數,該參數將是被拋出的 Throwable 物件。ErrorException 都實作了 Throwable 介面。這是處理函式的簽名

handler(Throwable $ex): void

可以傳遞 null 來將此處理函式重設為其預設狀態。

回傳值

回傳先前定義的例外處理函式,若發生錯誤則回傳 null。如果先前沒有定義處理函式,也會回傳 null

範例

範例 1 set_exception_handler() 範例

<?php
function exception_handler(Throwable $exception) {
echo
"Uncaught exception: " , $exception->getMessage(), "\n";
}

set_exception_handler('exception_handler');

throw new
Exception('Uncaught Exception');
echo
"Not Executed\n";
?>

參見

新增註解

使用者貢獻的註解 17 則註解

91
Anonymous
14 年前
您應該注意的事項

例外處理函式會處理先前未捕捉的例外。例外的本質是它會中止您的程式執行 - 因為它宣告了一個程式無法繼續的例外情況(除非您捕捉它)。

由於它尚未被捕捉,您的程式碼表示它沒有意識到此情況且無法繼續。

這表示:當例外處理函式已被呼叫時,要返回腳本是不可能的,因為未捕捉的例外不是通知。對於此類情況,請使用您自己的除錯或通知記錄系統。

此外:雖然仍然可以從您的腳本中呼叫函式,但由於例外處理函式已被呼叫,從該程式碼區塊中冒出的例外將不會再次觸發例外處理函式。php 將會終止,而不會留下任何資訊,除了「具有未知堆疊框架的未捕捉例外」。因此,如果您從您的腳本中呼叫函式,請確保您透過例外處理函式內的 try..catch 捕捉任何可能發生的例外。

對於那些誤解了例外處理函式本質含義的人:它唯一的用途是優雅地處理您腳本的中止,例如在 facebook 或維基百科之類的專案中:呈現一個漂亮的錯誤頁面,最終隱藏不應洩露給公眾的資訊(相反地,您可能想要寫入您的日誌或郵件給系統管理員或其他類似的操作)。

換句話說:如果您不希望每次未設定變數(例如)時腳本都中止,那麼將所有 php 錯誤(包括通知)從錯誤處理函式中重新導向到使用例外的做法是非常愚蠢的。

我個人的看法。
9
ohcc at 163 dot com
4 年前
在 PHP 7.4 中,使用者定義的關閉函式中拋出的例外可以被使用者定義的例外處理函式捕捉。

<?php
set_error_handler
(
function(
$level, $error, $file, $line){
if(
0 === error_reporting()){
return
false;
}
throw new
ErrorException($error, -1, $level, $file, $line);
},
E_ALL
);

register_shutdown_function(function(){
$error = error_get_last();
if(
$error){
throw new
ErrorException($error['message'], -1, $error['type'], $error['file'], $error['line']);
}
});

set_exception_handler(function($exception){
// ... 更多程式碼 ...
});

require
'NotExists.php';
19
Glen
17 年前
如果您希望類別實例處理例外,這是您的做法

<?php
class example {
public function
__construct() {
@
set_exception_handler(array($this, 'exception_handler'));
throw new
Exception('DOH!!');
}

public function
exception_handler($exception) {
print
"Exception Caught: ". $exception->getMessage() ."\n";
}
}

$example = new example;

?>

請參閱第一篇文章(Sean 的)以取得靜態範例。正如 Sean 所指出的,exception_handler 函式必須宣告為 public。
9
pinkgothic at gmail dot com
14 年前
如果您正在處理敏感資料,而且不希望例外記錄詳細資訊(例如當您拋出它們時的變數內容),您可能會沮喪地尋找組成一般堆疊追蹤輸出的部分,以便您可以保留其可讀性,但只更改一些內容。在這種情況下,這可能會對您有所幫助

<?php

function exceptionHandler($exception) {

// 這些是我們的模板
$traceline = "#%s %s(%s): %s(%s)";
$msg = "PHP 嚴重錯誤:未捕獲的異常 '%s',訊息為 '%s',發生於 %s:%s\n堆疊追蹤:\n%s\n拋出於 %s 行的 %s";

// 請隨意修改你的追蹤紀錄
$trace = $exception->getTrace();
foreach (
$trace as $key => $stackPoint) {
// 我將參數轉換為它們的類型
// (防止密碼被記錄為除了 'string' 之外的任何類型)
$trace[$key]['args'] = array_map('gettype', $trace[$key]['args']);
}

// 建立你的追蹤行
$result = array();
foreach (
$trace as $key => $stackPoint) {
$result[] = sprintf(
$traceline,
$key,
$stackPoint['file'],
$stackPoint['line'],
$stackPoint['function'],
implode(', ', $stackPoint['args'])
);
}
// 追蹤紀錄總是結束於 {main}
$result[] = '#' . ++$key . ' {main}';

// 將追蹤行寫入主模板
$msg = sprintf(
$msg,
get_class($exception),
$exception->getMessage(),
$exception->getFile(),
$exception->getLine(),
implode("\n", $result),
$exception->getFile(),
$exception->getLine()
);

// 根據你的喜好記錄或輸出
error_log($msg);
}

?>

如果你不喜歡 sprintf() 或重複呼叫 $exception->getFile() 和 $exception->getLine(),當然可以隨意替換,這裡只是將各部分組合起來而已。
12
mastabog at hotmail dot com
18 年前
一個沒有被充分記錄或討論的行為,但卻很常見的情況是,如果從全域異常處理器中拋出異常,則會發生嚴重錯誤(拋出異常時沒有堆疊框架)。也就是說,如果你透過呼叫 set_exception_handler() 定義你自己的全域異常處理器,並且你從其中拋出一個異常,那麼就會發生這個嚴重錯誤。這很自然,因為 set_exception_handler() 定義的回呼函數只會在未捕獲(未處理)的異常上被呼叫,所以如果你從那裡拋出一個異常,那麼你會得到這個嚴重錯誤,因為沒有剩餘的異常處理器(你透過呼叫 set_exception_handler() 覆蓋了 PHP 內部的處理器),因此沒有它的堆疊框架。

範例

<?php

function myExceptionHandler (Exception $ex)
{
throw
$ex;
}

set_exception_handler("myExceptionHandler");

throw new
Exception("這應該會導致嚴重錯誤,而且這個訊息將會遺失");

?>

將會導致一個嚴重錯誤:拋出異常時沒有堆疊框架

如果你跳過/註解掉 set_exception_handler("...") 這行,那麼 PHP 內部的全域處理器將會捕獲異常,並將異常訊息和追蹤(字串形式)輸出到瀏覽器,至少讓你看到異常訊息。

雖然使用 set_exception_handler() 函數定義你自己的全域異常處理器是一個好主意,但你應該注意,永遠不要從中拋出異常(或者如果你這樣做了,請捕獲它)。

最後,每個認真的程式設計師都應該使用具有除錯功能的 IDE。透過使用簡單的除錯「逐步進入」命令,追蹤像這樣的錯誤變得非常簡單(我個人推薦在撰寫本文時使用 Zend IDE v5.2)。我已經在網路上看到許多訊息,人們想知道為什麼會彈出這個訊息。

乾杯

附註:導致此錯誤的其他原因,與此有些無關,是你從解構函數中拋出異常時(背後的原因類似,由於 PHP 引擎關閉頁面,全域處理器可能不再存在)。
4
frank at netventures dot com dot au
17 年前
大家好,我剛開始使用異常套件而不是一般的 PHP 錯誤套件。對於那些正在尋找以物件導向的方式來做這件事,而又不想看 Glen 和 Sean 的範例的人(第一課:永遠要看日誌!),這是給你們的。

<?php

class NewException extends Exception
{
public function
__construct($message, $code=NULL)
{
parent::__construct($message, $code);
}

public function
__toString()
{
return
"程式碼: " . $this->getCode() . "<br />訊息: " . htmlentities($this->getMessage());
}

public function
getException()
{
print
$this; // 這將會印出上述 __toString() 方法的回傳值
}

public static function
getStaticException($exception)
{
$exception->getException(); // $exception 是這個類別的實例
}
}

set_exception_handler(array("NewException", "getStaticException"));
throw new
NewException("抓住我!!!", 69);

?>

如果我遺漏了什麼顯而易見的東西,請告訴我,因為我把眼鏡忘在家裡了,而且我剛從墨爾本盃回來(如果我贏了,我就不會還在工作了!)。
5
marques at displague dot com
16 年前
frank,

你的異常處理器被設定為處理所有異常,但如果拋出一個基本的 'Exception',你的靜態方法將會出錯,因為 'Exception' 沒有 'getException'。因此,我不認為將未捕獲的處理器設為繼承 Exception 的類別有什麼實際目的。

我確實喜歡使用一般異常處理類別的靜態方法的想法。

<?php
class ExceptionHandler {
public static function
printException(Exception $e)
{
print
'Uncaught '.get_class($e).', code: ' . $e->getCode() . "<br />Message: " . htmlentities($e->getMessage())."\n";
}

public static function
handleException(Exception $e)
{
self::printException($e);
}
}

set_exception_handler(array("ExceptionHandler", "handleException"));

class
NewException extends Exception {}
try {
throw new
NewException("Catch me once", 1);
} catch (
Exception $e) {
ExceptionHandler::handleException($e);
}

throw new
Exception("Catch me twice", 2);
?>

結果會是:
Uncaught NewException, code: 1<br />Message: Catch me once
Uncaught Exception, code: 2<br />Message: Catch me twice

其實可以做更多有趣的事情,像是重新格式化並選擇性地顯示或以電子郵件寄出它們。但這個類別作為這些函式的容器來說很不錯。
4
匿名
10 年前
預設情況下,堆疊追蹤非常難以閱讀,因此您可能會想將其包裝在 <pre> 標籤中。在這裡,我也將其包裝在 <div> 中,並設定 class 為 'alert alert-danger',這些是 Bootstrap CSS 框架中的 CSS class,將它們的樣式設定為紅色。
<?php

function exception_handler($exception) {
echo
'<div class="alert alert-danger">';
echo
'<b>Fatal error</b>: Uncaught exception \'' . get_class($exception) . '\' with message ';
echo
$exception->getMessage() . '<br>';
echo
'Stack trace:<pre>' . $exception->getTraceAsString() . '</pre>';
echo
'thrown in <b>' . $exception->getFile() . '</b> on line <b>' . $exception->getLine() . '</b><br>';
echo
'</div>';
}

set_exception_handler('exception_handler');
5
Josef Spak
10 年前
在 GNU/Linux 上,當呼叫異常處理程式時,PHP 會以退出狀態碼 0 而不是 255 結束。

您可以使用自訂錯誤處理程式結尾的 exit() 呼叫來變更退出狀態碼。
2
mc-php-doco at oak dot homeunix dot org
18 年前
當使用 '-r' 標誌呼叫 PHP 二進制檔案時,此方法似乎不起作用。

例如,如果我這樣執行它:

php -r '
function exception_handler($exception) {
echo "Uncaught exception: " , $exception->getMessage(), "\n";
}

set_exception_handler("exception_handler");

throw new Exception("Uncaught Exception");
echo "Not Executed\n";
'

或者,如果我將其放在檔案中並像這樣執行它:

php -r 'include "./tmp.php";'

我會得到堆疊追蹤,而不是呼叫 'exception_handler' 函式。如果像這樣執行它:

php tmp.php

它會正常運作。

(為什麼要從 '-r' 執行程式碼? 有時,在 include 周圍添加程式碼會很有用,例如呼叫 microtime 來進行基準測試,或者包含一個函式庫,然後從該函式庫呼叫一些函式,所有這些都以臨時的方式進行,而無需建立新檔案。)

PHP 版本 5.1.2 和 5.0.4。
2
sean at seanodonnell dot com
19 年前
在類別中使用 'set_exception_handler' 函式時,定義的 'exception_handler' 方法必須宣告為 'public' (如果使用 "array('example', 'exception_handler')" 語法,最好是 'public static')。

<?php
class example {
public function
__construct() {
@
set_exception_handler(array('example', 'exception_handler'));
throw new
Exception('DOH!!');
}

public static function
exception_handler($exception) {
print
"Exception Caught: ". $exception->getMessage() ."\n";
}
}

$example = new example;

echo
"Not Executed\n";
?>

將 'exception_handler' 函式宣告為 'private' 會導致致命錯誤。

[derick: 紅色。稍微更新了關於靜態的聲明]
3
joshua dot boyle-petrie at its dot monash dot edu
15 年前
感謝 mastabog,我們知道在異常處理程式中拋出異常會觸發致命錯誤和除錯噩夢。要避免在其中拋出異常應該很容易。

但是,如果您使用自訂錯誤處理程式將錯誤轉換為 ErrorExceptions,那麼突然之間,在異常處理程式中意外拋出異常的方式就會多出很多種。

<?php
function error_handler($code, $message, $file, $line)
{
if (
0 == error_reporting())
{
return;
}
throw new
ErrorException($message, 0, $code, $file, $line);
}
function
exception_handler($e)
{
// ... 正常的異常處理程式碼在此處執行
print $undefined; // 這是根本問題
}
set_error_handler("error_handler");
set_exception_handler("exception_handler");
throw new
Exception("Just invoking the exception handler");
?>
輸出:致命錯誤:在 Unknown 的第 0 行,拋出沒有堆疊框架的異常

我發現避免這種情況的最佳方法是將異常處理程式中的所有內容都包裝在 try/catch 區塊中。
<?php
function exception_handler($e)
{
try
{
// ... 正常的異常處理程式碼在此處執行
print $undefined; // 這是根本問題
}
catch (
Exception $e)
{
print
get_class($e)." 在異常處理程式中拋出。訊息:".$e->getMessage()." 在第 ".$e->getLine();
}
}
?>
輸出:ErrorException 在異常處理程式中拋出。訊息:Undefined variable: undefined 在第 14 行

這加快了除錯速度,並為異常處理程式中意外拋出的任何其他異常提供了一些可擴展性。

另一種解決方案是在異常處理程式的開頭還原錯誤處理程式。雖然這在避免 ErrorExceptions 方面是一個銀彈,但除錯訊息隨後會依賴於 error_reporting() 層級和 display_errors 指令。為什麼要提到這個?對於產品程式碼來說,這可能是更好的選擇,因為我們更關心的是對使用者隱藏錯誤,而不是方便的除錯訊息。
1
匿名
11 年前
截至 PHP 5.4.11....

而不是

輸出:致命錯誤:在 Unknown 的第 0 行,拋出沒有堆疊框架的異常

這個程式碼片段

<?php
function error_handler($code, $message, $file, $line)
{
if (
0 == error_reporting())
{
return;
}
throw new
ErrorException($message, 0, $code, $file, $line);
}
function
exception_handler($e)
{
// ... 正常的異常處理程式碼在此處執行
print $undefined; // 這是根本問題
}
set_error_handler("error_handler");
set_exception_handler("exception_handler");
throw new
Exception("Just invoking the exception handler");
?>

現在返回

致命錯誤:在 C:\Apache2\htdocs\error\test.php:13 中,拋出未捕獲的異常 'ErrorException',訊息為 'Undefined variable: undefined'。堆疊追蹤:#0 C:\Apache2\htdocs\error\test.php(13): error_handler(8, 'Undefined varia...', 'C:\Apache2\htdo...', 13, Array) #1 [internal function]: exception_handler(Object(Exception)) #2 {main} thrown in C:\Apache2\htdocs\error\test.php on line 13

因此,在異常處理程式中拋出的異常現在似乎會繞過異常處理程式。
0
dev at codesatori dot com
8 年前
對於那些想要將 PHP 錯誤轉換為 ErrorExceptions(很有用)但又對每次出現 E_NOTICE 等錯誤時腳本都會停止感到沮喪的人。在您的錯誤處理器中,只需建立 ErrorException,然後將其拋出(腳本停止),或將該物件直接傳遞(腳本繼續)到您的例外處理器函數。

<?php
const EXIT_ON_ALL_PHP_ERRORS = false; // 或 true

function proc_error($errno, $errstr, $errfile, $errline)
{
$e = new ErrorException($errstr, 0, $errno, $errfile, $errline);

if (
EXIT_ON_ALL_PHP_ERRORS) {
throw
$e; // 這會停止您的腳本。
} else {
proc_exception($e); // 這會讓它繼續。
}
}

set_error_handler("proc_error");
set_exception_handler("proc_exception");
?>

您可以進一步自訂錯誤嚴重性級別(從 $errno,將位元遮罩與錯誤級別常數匹配),以決定腳本是停止還是允許繼續。上述程式碼只是允許 PHP 錯誤的預設行為直接通過。

如果您的例外處理器同時接收到錯誤 ErrorExceptions(具有嚴重性級別等)和其他類型的未捕獲 Exceptions,那麼請使用 <?php if ($e instanceof ErrorException) { // ... } ?> 條件來處理差異。我還必須將其包裹在 <?php try { $errno = $e->getSeverity(); } catch (Exception $x) { $errno = 0 } ... ?>,因為某些 EE 神秘地缺少嚴重性級別屬性(尚待深入研究找出原因)。
-1
klaussantana at gmail dot com
7 年前
大家好。我不知道這是否是舊版本的行為,但目前您可以在可以存取該方法的情況下,將例外和錯誤處理器設定為 private 或 protected 方法,前提是您在可以存取該方法的上下文中呼叫 `set_exception_handler()` 或 `set_error_handler()`。

範例
<?PHP
$Handler
= new class ()
{
public function
__construct ()
{
set_error_handler([$this, 'HandleError']);
set_exception_handler([$this, 'HandleException']);
}
protected function
HandleError ( $Code, $Message, $File = null, $Line = 0, $Context = [] )
{
// 在此處處理錯誤。
}
private function
HandleException ( $Exception )
{
// 在此處處理例外。
}
}
?>

注意:這些方法必須符合回呼參數的簽名。我放了
-3
Anonymous
14 年前
當處理未捕獲的例外時,執行不會返回到腳本,而是(無論如何,在我的理解上)會終止。

我目前正在開發的系統中(在 Xampp 的 v5.3.0 上)同時使用 set_error_handler() 和 set_exception_handler()。假設觸發了兩個 E_USER_NOTICE,腳本會在處理第一個後停止執行。

<?php
set_exception_handler
( 'exc_handler' );
function
exc_handler($exception) {
echo
"未捕獲的例外: " , $exception->getMessage(), "\n";
}
function
errorone() {
throw new
Exception("測試 1");
}
function
errortwo() {
throw new
Exception("測試 2");
}
function
test() {
errorone();
errortwo();
}
test();
test();
?>

而不是列印(如我所預期的)「未捕獲的例外:測試 1\n未捕獲的例外:測試 2\n未捕獲的例外:測試 1\n未捕獲的例外:測試 2\n」

它只列印一次。我嘗試了許多不同的方法,但我無法弄清楚如何在例外處理器執行後將執行返回到腳本。

如果有人知道如何在執行後返回執行(因此允許腳本記錄未捕獲的例外但繼續處理),請透過電子郵件聯絡我!謝謝!
-2
ch at westend dot com
18 年前
似乎儘管 Exception 本身包含回溯,但在進入例外處理器時,debug_backtrace() 函數的堆疊為空。這非常不方便。請參閱錯誤 #36477。
To Top