PHP 文档:
Error
Exception
参考:
深入理解PHP原理之异常机制
我们什么时候应该使用异常
异常和错误
所有示例基于 PHP7。
应用中,关于错误的最佳实践是:
throw new Exception
手动抛出trigger_errors()
手动触发catch (Throwable $t) {...}
同时捕获 Error 和 Exceptioncatch (Error $e) { ... }
,或者通过注册错误处理函数( set_error_handler())来捕获 Errorcatch (Exception $e) { ... }
或者通过注册异常处理函数( set_exception_handler())来捕获 Exceptioncatch (Throwable $e) { ... }
可以同时捕获 Exception 和 Error
echo 1/0;
echo 666;
echo 1%0;
echo 666;
PHP Warning: Division by zero in /code/main.php on line 3
INF666
PHP Fatal error: Uncaught DivisionByZeroError: Modulo by zero in /code/main.php:5
用户定义的类无法实现 Throwable,所以用户只能抛出 Exception 或 Error 的实例。扩展 Throwable 的接口只能通过扩展 Exception 或 Error 的类来实现。
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
完整的接口可以参考 这里。
从 PHP 7 开始,大多数错误(致命错误和可恢复错误)被作为 Error 异常抛出,从而可以捕获并处理,防止脚本终止执行。与任何其他 Exception 异常一样,可以使用 try / catch 块捕获 Error 对象。
从致命(fatal)和可恢复(recoverable)的错误中抛出的异常并没有继承 Exception,而是继承自 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() 捕获 |
catch (Error $e) { ... }
),如果有则被第一个匹配的 try / catch 块所捕获。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 外的所有错误和警告信息。 |
如果未设置错误处理程序,则 PHP 将根据 php.ini
配置文件处理发生的任何错误。error_reporting 指令控制报告和忽略哪些错误。虽然也可以在运行时通过调用 error_reporting() 函数来控制,但强烈建议设置配置指令,因为在脚本开始执行之前也可能会发生一些错误。
在开发环境中,为了了解并解决 PHP 引发的问题,最好将 error_reporting
设置为 E_ALL
来记录所有的错误。生产环境中,可以将 error_reporting
设置为 E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
来避免记录过多信息,但是在多数情况因为下 E_ALL
可以提供早期预警,记录潜在的问题,也可以用于生产环境。
发生错误时,PHP 可以采取两种措施,由另外两个 php.ini
指令设置:
如果 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('ech 66'); // 无法用自定义的错误处理程序
将 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);
两种可能的原因:
intdiv()
方法时,分子是 PHP_INT_MIN
且分母为 -1(此时将返回浮点数)。try {
$value = 1 << -1;
intdiv(PHP_INT_MIN, -1);
} catch (ArithmeticError $e) {
echo $e->getMessage(), "\n";
}
两种可能的原因:
intdiv()
方法时,分母为 0。注意,在除法(/)运算符中使用零做分母仅发出警告。
try {
echo 1/0; // 仅警告
intdiv(1, 0);
echo 1%0;
} catch (DivisionByZeroError $e) {
echo $e->getMessage();
}
使用 assert() 语言结构进行断言时,可能抛出这个错误:
ini_set('zend.assertions', 1); // 执行代码
ini_set('assert.exception', 1); // 允许抛异常
$test = 1;
assert($test === 0);
eval()
解析的字符串有语法错误try {
eval('ech 66');
include 'has-error.php';
} catch (ParseError $e) {
echo $e->getMessage(), "\n";
}
syntax error, unexpected '66' (T_LNUMBER)
函数的参数或返回值跟类型不匹配时,抛 TypeError:
function add(int $left, int $right)
{
return $left + $right;
}
try {
$value = add('left', 'right');
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
Argument 1 passed to add() must be of the type integer, string given, called in D:\workspace\szhz\application\controllers\tuan\Index.php on line 312
PHP 在使用异常机制之前,通过返回错误码来表示函数的执行结果。部分函数返回 TRUE 或 FALSE,部分函数返回 0 或 1、-1。难以统一且无法包含足够的报错原因等信息。例如 strtotime() 函数,成功则返回时间戳,否则返回 FALSE,但是在 PHP 5.1.0 之前本函数在失败时返回 -1。
异常机制避免了错误码机制的一些不足,可以在 一次捕获多个异常。异常对象包含错误信息、错误码、错误行号、文件、上下文,更方便定位问题。
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(), "\n";
}
// 异常捕获后,可以继续执行后面的代码
...
自定义的 Exception 需要继承自已有异常,定义完成后就可以在代码中抛出自定义的这些异常。
class 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);
}
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);
// 自定义异常处理程序执行后,不会继续执行后面的代码