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 条注释

匿名
14 年前
您应该注意的事项

异常处理程序处理之前未捕获的异常。异常的本质在于它会停止程序的执行 - 因为它声明了一个程序无法继续的异常情况(除非您捕获它)。

由于它尚未被捕获,因此您的代码表明它没有意识到这种情况并且无法继续。

这意味着:当异常处理程序已被调用时,返回脚本根本不可能,因为未捕获的异常不是通知。为此类情况使用您自己的调试或通知日志系统。

此外:虽然仍然可以从您的脚本中调用函数,因为异常处理程序已被调用,来自该代码段的异常不会再次触发异常处理程序。php 将退出,而不会留下任何信息,除了“未捕获的异常,未知堆栈帧”。因此,如果您从脚本中调用函数,请确保通过 try..catch 在异常处理程序内部捕获可能发生的任何异常。

对于那些误解了异常处理程序基本含义的人:它唯一的用途是优雅地处理脚本的中断,例如在 Facebook 或维基百科等项目中:呈现一个漂亮的错误页面,最终隐藏不应该泄露到公众的信息(相反,您可能希望写入日志或向系统管理员发送邮件或类似操作)。

换句话说:使用异常重定向来自错误处理程序的所有 php 错误 - 包括通知 - 这是一个非常愚蠢的想法,如果您不打算每次未设置变量(例如)时都让您的脚本中止。

我的两分钱。
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';
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。
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) {
// 我正在将参数转换为其类型
// (防止密码以外任何其他类型被记录)
$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() 调用,当然可以根据需要替换它们 - 将此视为各个部分的简单组合。
[email protected]
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 引擎关闭页面,全局处理程序可能不再存在)。
[email protected]
17 年前
大家好,我刚刚开始使用异常套件而不是普通的 PHP 错误套件。对于那些正在寻找面向对象的方法来执行此操作而无需查看 Glen 和 Sean 的示例(第 1 课:始终阅读日志!)的人,请看这里

<?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);

?>

如果我遗漏了什么显而易见的东西,请告诉我,因为我把眼镜落在家里了,我刚从墨尔本杯赛回来(如果我赢了,我就不会还在上班了!)。
[email protected]
16年前
弗兰克,

您的异常处理程序配置为所有异常的处理程序,但是如果抛出一个基本的“Exception”,您的静态方法将出错,因为“Exception”没有“getException”。因此,我不认为将未捕获的处理程序设为扩展 Exception 的类有什么实际意义。

我喜欢使用通用异常处理类的静态方法的想法。

<?php
class ExceptionHandler {
public static function
printException(Exception $e)
{
print
'未捕获 '.get_class($e).', 代码: ' . $e->getCode() . "<br />消息: " . 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("抓住我一次", 1);
} catch (
Exception $e) {
ExceptionHandler::handleException($e);
}

throw new
Exception("抓住我两次", 2);
?>

给出
未捕获 NewException,代码:1<br />消息:抓住我一次
未捕获 Exception,代码:2<br />消息:抓住我两次

可以做更多有趣的事情,例如重新格式化并选择性地显示或通过电子邮件发送它们。但是此类充当这些函数的良好容器。
匿名
10年前
默认情况下,堆栈跟踪的可读性很差,因此您可能希望将其包装在 <pre> 标记中。在这里,我还将其包装在一个 <div> 中,并设置类“alert alert-danger”,它们是 Bootstrap CSS 框架中的 CSS 类,用于将其样式设置为红色。


<?php

function exception_handler($exception) {
echo
'<div class="alert alert-danger">';
echo
'<b>致命错误</b>: 未捕获异常 \'' . get_class($exception) . '\' with message ';
echo
$exception->getMessage() . '<br>';
echo
'堆栈跟踪:<pre>' . $exception->getTraceAsString() . '</pre>';
echo
'抛出在 <b>' . $exception->getFile() . '</b> on line <b>' . $exception->getLine() . '</b><br>';
echo
'</div>';
}

set_exception_handler('exception_handler');
Josef Spak
10年前
在 GNU/Linux 上,当调用异常处理程序时,PHP 将以退出状态码 0 而不是 255 结束。

您可以在自定义错误处理程序的末尾使用 exit() 调用更改退出状态码。
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。
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: red. 更新了关于静态的语句]
joshua dot boyle-petrie at its dot monash dot edu
15 年前
感谢 mastabog,我们知道在异常处理程序中抛出异常将触发致命错误和调试噩梦。避免在那里抛出异常应该很容易。

但是,如果您使用自定义错误处理程序将错误转换为 ErrorException,那么突然之间就有很多新的方法会在异常处理程序中意外抛出异常。

<?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");
?>
输出: Fatal error: Exception thrown without a stack frame in Unknown on line 0

我发现避免这种情况的最佳方法是将异常处理程序中的所有内容都包装在 try/catch 块中。
<?php
function exception_handler($e)
{
try
{
// ... 正常的异常处理在这里
print $undefined; // 这是根本问题
}
catch (
Exception $e)
{
print
get_class($e)." thrown within the exception handler. Message: ".$e->getMessage()." on line ".$e->getLine();
}
}
?>
输出: ErrorException thrown within the exception handler. Message: Undefined variable: undefined on line 14

这加快了调试速度,并为异常处理程序中意外抛出的任何其他异常提供了一些可扩展性。

另一种解决方案是在异常处理程序的开头恢复错误处理程序。虽然这在避免 ErrorException 方面是灵丹妙药,但调试消息随后依赖于 error_reporting() 级别和 display_errors 指令。为什么要提到这一点?对于生产代码来说,这可能是更好的选择,因为我们更关心隐藏用户错误而不是方便的调试消息。
Anonymous
11 年前
从 PHP 5.4.11 开始……

而不是

输出: Fatal error: Exception thrown without a stack frame in Unknown on line 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");
?>

现在返回

Fatal error: Uncaught exception 'ErrorException' with message 'Undefined variable: undefined' in C:\Apache2\htdocs\error\test.php:13 Stack trace: #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

因此,似乎在异常处理程序中抛出的异常现在绕过了异常处理程序。
dev at codesatori dot com
8 年前
对于那些想要将 PHP 错误转换为 ErrorException(有用),但对脚本在每个 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(带有严重性级别等)和其他类型的未捕获异常,则可以使用 <?php if ($e instanceof ErrorException) { // ... } ?> 条件来处理差异。我不得不进一步将其包装到 <?php try { $errno = $e->getSeverity(); } catch (Exception $x) { $errno = 0 } ... ?> 中,因为某些 EE 莫名其妙地缺少严重性级别属性(尚未深入研究并找出原因)。
klaussantana at gmail dot com
7 年前
大家好。我不知道这是否是先前版本的一个旧行为,但目前,您可以将异常和错误处理程序设置为私有或受保护的方法,前提是您在可以访问该方法的上下文中调用 `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 )
{
// 在这里处理异常。
}
}
?>

注意:这些方法必须与回调参数签名匹配。我放了
Anonymous
14 年前
当处理未捕获的异常时,执行不会返回到脚本,而是(出乎意料地,至少在我这边是这样)终止。

我正在结合使用 `set_error_handler()` 和 `set_exception_handler()` 来开发我当前正在开发的系统(在带有 Xampp 的 v5.3.0 上)。假设触发了两个 E_USER_NOTICE,则脚本将在第一个被处理后死亡。

<?php
set_exception_handler
( 'exc_handler' );
function
exc_handler($exception) {
echo
"Uncaught exception: " , $exception->getMessage(), "\n";
}
function
errorone() {
throw new
Exception("Test 1");
}
function
errortwo() {
throw new
Exception("Test 2");
}
function
test() {
errorone();
errortwo();
}
test();
test();
?>

而不是打印(正如我预期的那样)“Uncaught exception: Text 1\nUncaught exception: Text 2\nUncaught exception: Text 1\nUncaught exception: Text 2\n”

它只打印一次。我尝试了许多不同的方法,但我无法弄清楚如何在异常处理程序运行后将执行返回到脚本。

如果有人有关于如何返回执行(因此,允许脚本记录未捕获的异常但继续处理)的解决方案,请给我发电子邮件!谢谢!
ch at westend dot com
18年前
似乎尽管 Exception 本身包含回溯,但在进入异常处理程序时,`debug_backtrace()` 函数的堆栈为空。这非常不方便。请参阅错误 #36477。
To Top