Exception和Error的处理机制
1, 背景
我们经常会在代码中进行异常处理,那么,当一个异常被抛出,却没有被捕获时,它会造成程序的终止,并返回异常信息,类似于:
PHP Fatal error: Uncaught Exception: aaaa in /opt/wwwroot/test/test1.php:35Stack trace:
#0 /opt/wwwroot/test/test1.php(38): a()
#1 {main}
thrown in /opt/wwwroot/test/test1.php on line 35
看错误信息,我们发现,这里报的 PHP Fatal error。
而在PHP的配置文件php.ini中,有设置错误屏蔽等级error_reporting。
那么问题来了,一个未被捕获的异常的暴露,会受到error_reporting设置的影响吗?
答案是:不会。
2,原因
这里涉及到PHP对错误和异常的定义了。
PHP的错误是指会导致PHP脚本运行不正常的情况,属于自身的情况,是一种非语法或是环境导致的,让编译器无法通过检查,甚至无法运行的情况。错误来自于Error类。
异常是指程序运行中,出现了不符合预期或与正常的流程不同的情况。比如按照正常逻辑不会出错,但是因为某些原因出错了,比如它调用的服务不可用了,执行SQL出错了等等,这可以看做是逻辑和业务流程的一种中断,而不是语法错误。异常属于Exception类。
2.1 Error 和 Exception 的异同
- Exception 需要通过
throw new Exception
手动抛出 - Error 可以在 PHP 脚本执行发生错误时自动触发,也可以通过
trigger_errors()
手动触发 - 都实现了 Throwable 接口,可以通过
catch (Throwable $t) {...}
同时捕获 Error 和 Exception - 如果不捕获并处理 Exception,程序会终止,并报出 Fatal Error 错误,但捕获后程序可以继续执行
- 用
catch (Error $e) { ... }
,或者通过注册错误处理函数( set_error_handler())来捕获 Error - 用
catch (Exception $e) { ... }
或者通过注册异常处理函数( set_exception_handler())来捕获 Exception - 用
catch (Throwable $e) { ... }
可以同时捕获 Exception 和 Error
2.2 Throwable
用户定义的类无法实现 Throwable,所以用户只能抛出 Exception 或 Error 的实例。扩展 Throwable 的接口只能通过扩展 Exception 或 Error 的类来实现。
2.2.1 继承关系
Error 类 和 Exception 类 都继承自 Throwable 接口,不同版本的继承关系可以参考 这里。下面是 7.2.0 - 7.2.7 的继承关系:
Error ArithmeticError
DivisionByZeroError
AssertionError
ParseError
TypeError
ArgumentCountError
Exception
ClosedGeneratorException
DOMException
ErrorException
IntlException
LogicException
BadFunctionCallException
BadMethodCallException
DomainException
InvalidArgumentException
LengthException
OutOfRangeException
PharException
ReflectionException
RuntimeException
OutOfBoundsException
OverflowException
PDOException
RangeException
UnderflowException
UnexpectedValueException
SodiumException
2.2.2 常用方法
完整的接口可以参考 这里。
- getMessage — 获取异常消息内容
- getCode — 获取异常代码
- getFile — 导致异常的程序文件名称
- getLine — 导致异常的行号
2.3 Error
从 PHP 7 开始,大多数错误(致命错误(fatal)和可恢复错误(recoverable))被作为 Error 异常抛出,从而可以捕获并处理,防止脚本终止执行。与任何其他 Exception 异常一样,可以使用 try / catch 块捕获 Error 对象。
从致命(fatal)和可恢复(recoverable)的错误中抛出的异常并没有继承 Exception,而是继承自 Error。
2.3.1 Error 的严重等级
Parse error > Fatal Error > Waning > Notice > Deprecated
Parse error
语法错误
代码解析失败
中断执行
PHP7 之后可以用 catch (Error $e) { ... }
捕获
Fatal Error
运行时错误
实例化不存在的类,调不存在的方法
中断执行
PHP7 之后可以用 catch (Error $e) { ... }
捕获
可以使用 register_shutdown_function() 函数设置一个在 PHP 中止前执行收尾工作的函数
Waning
警告
四则运算时出现非数字
继续执行
可以用 set_error_handler() 捕获
Notice
注意
变量或数组下标未定义
继续执行
可以用 set_error_handler() 捕获
Deprecated
使用了废弃函数
函数已经废弃
继续执行
可以用 set_error_handler() 捕获
2.3.2 Error 处理流程
- 先看看有没有匹配的 catch 块(注意是 Error 类型而不是 Exception 类型:
catch (Error $e) { ... }
),如果有则被第一个匹配的 try / catch 块所捕获。 - 如果没有没有匹配的 catch 块,则去调用异常处理函数(事先通过 set_error_handler() 注册)进行处理(仅用于 Deprecated、Notice、Waning 这三种级别)。
- 如果尚未注册异常处理函数,则按照传统方式处理:报告错误(Fatal Error 等)。
PHP 生成的每个错误都包含一个类型。类型列表以及它们的行为及其产生方式的简短描述可以参考 这里。常用的有:
1
E_ERROR
致命的运行时错误。不可捕捉,不可恢复。脚本终止运行。
2
E_WARNING
运行时警告 (非致命错误)。仅给出提示信息,但是脚本不会终止运行。
256
E_USER_ERROR
用户产生的错误信息。类似 E_ERROR, 但是是由用户自己在代码中使用函数 trigger_error()
触发的。
512
E_USER_WARNING
用户产生的警告信息。类似 E_WARNING, 但是是由用户自己在代码中使用PHP函数 trigger_error()
触发的。
2048
E_STRICT (integer)
启用 PHP 对代码的修改建议,以确保代码具有最佳的互操作性和向前兼容性。
4096
E_RECOVERABLE_ERROR
可被捕捉的致命错误。它表示发生了一个可能非常危险的错误,但是还没有导致 PHP 引擎处于不稳定的状态。如果该错误没有被用户自定义处理程序捕获(set_error_handler()),将成为一个 E_ERROR 从而脚本会终止运行。
8192
E_DEPRECATED
运行时通知。对在未来版本中可能无法正常工作的代码给出警告。
30719
E_ALL
E_STRICT 外的所有错误和警告信息。
2.3.4 设置 PHP 配置文件来处理错误
2.3.4.1 设置报告错误的等级
如果未设置错误处理程序,则 PHP 将根据 php.ini
配置文件处理发生的任何错误。error_reporting 指令控制报告和忽略哪些错误。虽然也可以在运行时通过调用 error_reporting() 函数来控制,但强烈建议设置配置指令,因为在脚本开始执行之前也可能会发生一些错误。
在开发环境中,为了了解并解决 PHP 引发的问题,最好将 error_reporting
设置为 E_ALL
来记录所有的错误。生产环境中,可以将 error_reporting
设置为 E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
来避免记录过多信息,但是在多数情况因为下 E_ALL
可以提供早期预警,记录潜在的问题,也可以用于生产环境。
2.3.4.2 显示错误或记录日志
发生错误时,PHP 可以采取两种措施,由另外两个 php.ini
指令设置:
- display_errors:输出错误。生产环境中必须禁用,因为它可能包含机密信息(如数据库密码),但可用于开发环境,确保立即报告问题。
- log_errors:记录错误日志。这会将任何错误记录到 error_log 定义的文件或 syslog 中。这在生产环境中非常有用,可以记录发生的错误,然后根据这些错误生成报告。
2.3.5 用户自定义错误处理程序
如果 PHP 的默认错误处理不满足需求,还可以使用 set_error_handler() 安装自己的自定义错误处理程序来处理许多类型的错误。
一般用于处理用户通过 trigger_error 触发的错误,大部分 PHP 内置错误类型无法以这种方式处理。可以按照脚本认为合适的方式处理那些可以处理的错误类型:例如,向用户显示自定义错误页面,然后直接发送电子邮件报告错误,而不是通过日志。
set_error_handler("myErrorHandler");function myErrorHandler($severity, $message, $filepath, $line) {
echo "错误信息:".$message;
// 发送电子邮件...
exit(1); // 必要时手动终止脚本
}
function myDiv($a, $b) {
return $a/$b;
}
myDiv(1, 0);
eval("echo 66"); // 无法用自定义的错误处理程序
输出
错误信息:Division by zero
将 Error 变为 ErrorException:
set_error_handler("myErrorHandler");set_exception_handler("myExceptionHandler");
function myExceptionHandler($exception) {
echo $exception->getMessage();
}
function myErrorHandler($severity, $message, $file, $line)
{
if (!(error_reporting() & $severity)) {
// This error code is not included in error_reporting, so let it fall
// through to the standard PHP error handler
return false;
}
throw new ErrorException($message, 0, $severity, $file, $line);
}
function myDiv($a, $b) {
return $a/$b;
}
myDiv(1, 0);
输出
Division by zero
2.3.6 具体的 Error 类
2.3.6.1 ArithmeticError 算术错误
两种可能的原因:
- 使用负数移位
- 调用
intdiv()
方法时,分子是PHP_INT_MIN
且分母为 -1(此时将返回浮点数)。
try { $value = 1 << -1;
intdiv(PHP_INT_MIN, -1);
} catch (ArithmeticError $e) {
echo $e->getMessage(), "
";
}
2.3.6.2 DivisionByZeroError
两种可能的原因:
- 模数(%)运算时,分母为 0。
- 调用
intdiv()
方法时,分母为 0。
注意,在除法(/)运算符中使用零做分母仅发出警告。
try { echo 1/0; // 仅警告
intdiv(1, 0);
echo 1%0;
} catch (DivisionByZeroError $e) {
echo $e->getMessage();
}
2.3.6.3 AssertionError
使用 assert() 语言结构进行断言时,可能抛出这个错误:
ini_set("zend.assertions", 1); // 执行代码ini_set("assert.exception", 1); // 允许抛异常
$test = 1;
assert($test === 0);
2.3.6.4 ParseError
- 通过 included 或 required 引入文件有语法错误
eval()
解析的字符串有语法错误
try { eval("ech 66");
include "has-error.php";
} catch (ParseError $e) {
echo $e->getMessage(), "
";
}
syntax error, unexpected "66" (T_LNUMBER)
2.3.6.5 TypeError
函数的参数或返回值跟类型不匹配时,抛 TypeError:
function add(int $left, int $right){
return $left + $right;
}
try {
$value = add("left", "right");
} catch (TypeError $e) {
echo $e->getMessage(), "
";
}
Argument 1 passed to add() must be of the type integer, string given, called in D:workspaceszhzapplicationcontrollers uanIndex.php on line 312
2.4 Exception
2.4.1 Exception 出现的原因
PHP 在使用异常机制之前,通过返回错误码来表示函数的执行结果。部分函数返回 TRUE 或 FALSE,部分函数返回 0 或 1、-1。难以统一且无法包含足够的报错原因等信息。例如 strtotime() 函数,成功则返回时间戳,否则返回 FALSE,但是在 PHP 5.1.0 之前本函数在失败时返回 -1。
异常机制避免了错误码机制的一些不足,可以在 一次捕获多个异常。异常对象包含错误信息、错误码、错误行号、文件、上下文,更方便定位问题。
2.4.2 Exception 特点
Exception 是必须手动抛出并且可被捕获的。如果抛出的异常未被捕获,则导致 Fatal error,并使得代码停止执行。
function myDiv($a, $b) { if ($b == 0)
throw new Exception("Divided by zero");
return $a/$b;
}
try {
myDiv(1, 0); // 如果不捕获异常,则报错 Fatal error,并停止执行
} catch (Exception $e) {
echo $e->getMessage(), "
";
}
// 异常捕获后,可以继续执行后面的代码
...
2.4.3 自定义 Exception
自定义的 Exception 需要继承已有异常,定义完成后就可以在代码中抛出自定义的这些异常。
<?phpclass pdoDbException extends PDOException {
public function __construct(PDOException $e) {
if(strstr($e->getMessage(), "SQLSTATE[")) {
echo "this is my exception";
}
}
}
function f() {
try {
$pdo = new PDO("123.207.7.188", "$username", "$password", []);
} catch (PDOException $e) {
throw new pdoDbException($e);
}
}
try {
f();
} catch (pdoDbException $e) {
print_r($e);
}
2.4.4 用户自定义异常处理程序
set_exception_handler("myExceptionHandler");function myExceptionHandler($exception) {
echo $exception->getMessage();
}
function myDiv($a, $b) {
if ($b == 0)
throw new Exception("Divided by zero");
return $a/$b;
}
myDiv(1, 0);
// 自定义异常处理程序执行后,不会继续执行后面的代码
2.5 总结
exception和error分属于不同的类型,而php.ini中的error_reporting错误等级过滤只对error生效,所以该设置不会对未被捕获的异常生效。
文章参考: https://www.cnblogs.com/kika/p/10851557.html
以上是 Exception和Error的处理机制 的全部内容, 来源链接: utcz.com/z/512227.html