异常与错误处理
PHP的异常与错误是分开的,当程序出现异常时会
throw
一个\Exception
(或子类)对象,但是当出现错误时会触发一个错误。
1. 异常处理
1.1 通过try...catch
主动处理异常
<?php
/**
* Class UserNotExistsException
* @datetime 2020/7/2 5:11 下午
* @author roach
* @email jhq0113@163.com
*/
class UserNotExistsException extends \Exception
{
}
/**
* Class TransferException
* @datetime 2020/7/2 5:13 下午
* @author roach
* @email jhq0113@163.com
*/
class TransferException extends \Exception
{
}
/**
* @param int $inUserId
* @param int $outUserId
* @param float $amout
* @return bool
* @throws TransferException
* @throws UserNotExistsException
* @datetime 2020/7/2 5:15 下午
* @author roach
* @email jhq0113@163.com
*/
function transfer($inUserId, $outUserId, $amout)
{
if($inUserId < 1) {
throw new UserNotExistsException('入金用户不存在');
}
if($outUserId < 1) {
throw new UserNotExistsException('出金用户不存在');
}
if(bccomp($amout, '0', 2) <= 0) {
throw new TransferException('转账金额必须大于0');
}
if(bccomp($amout, '100000000', 2) > 0) {
throw new \Exception('服务器内部错误');
}
return true;
}
try {
$result = transfer(1,0, 5);
if($result) {
exit(json_encode([
'code' => 200,
'msg' => 'success',
'data' => []
]));
}
}catch (\UserNotExistsException $exception) {
exit(json_encode([
'code' => 404,
'msg' => $exception->getMessage(),
'data' => []
]));
} catch (\TransferException $exception) {
exit(json_encode([
'code' => 501,
'msg' => $exception->getMessage(),
'data' => []
]));
} catch (\Exception $exception) {
throw $exception;
}
以上例程输出:
{"code":404,"msg":"\u51fa\u91d1\u7528\u6237\u4e0d\u5b58\u5728","data":[]}
当开发人员对异常有明确的预期时,可以使用以上的方式,
catch
不用类型的异常,然后对不用的异常做各种不同的处理。
1.2 set_exception_handler
处理异常
PHP可以通过
set_exception_handler
方法注册一个回调函数,当没有用try...catch
捕获的异常都会交个回调函数处理。
同样调用以上转账逻辑,不同的是,把
try...catch
代码块注释掉,换成如下代码:
set_exception_handler(function ($exception){
echo $exception->getMessage().PHP_EOL;
});
transfer(1, 1, 0);
以上例程输出:
转账金额不能小于0
从以上例程我们可以看出,
set_exception_handler
方式适合做默认统一的异常处理,同时也可以加一些统一的处理(如:异常日志、监控报警等)
try...catch
适合做一些开发主动捕获异常,通过主动捕获异常做一些特殊需求处理。
2. 错误处理
PHP语言的
error
分为如下等级:
常量 | 值 | 说明 | 备注 |
---|---|---|---|
E_ERROR | 1 | 致命的运行时错误。这类错误一般是不可恢复的情况,例如内存分配导致的问题。后果是导致脚本终止不再继续运行 。 | |
E_WARNING | 2 | 运行时警告 (非致命错误)。仅给出提示信息,但是脚本不会终止运行 。 | |
E_PARSE | 4 | 编译时语法解析错误。解析错误仅仅由分析器产生。 | |
E_NOTICE | 8 | 运行时通知。表示脚本遇到可能会表现为错误的情况,但是在可以正常运行的脚本里面也可能会有类似的通知。 | |
E_CORE_ERROR | 16 | 在PHP初始化启动过程中发生的致命错误 。该错误类似E_ERROR ,但是是由PHP引擎核心产生的。 | |
E_CORE_WARNING | 32 | PHP初始化启动过程中发生的警告(非致命错误 ) 。类似E_WARNING ,但是是由PHP引擎核心产生的。 | |
E_COMPILE_ERROR | 64 | 编译时致命错误 。类似E_ERROR , 但是是由Zend脚本引擎产生的。 | |
E_COMPILE_WARNING | 128 | 编译时警告(非致命错误 )。类似E_WARNING ,但是是由Zend脚本引擎产生的。 | |
E_USER_ERROR | 256 | 用户产生的致命错误信息 。类似E_ERROR , 但是是由用户自己在代码中使用PHP函数trigger_error() 来产生的。 | |
E_USER_WARNING | 512 | 用户产生的警告(非致命 )信息。类似E_WARNING , 但是是由用户自己在代码中使用PHP函数trigger_error() 来产生的。 | |
E_USER_NOTICE | 1024 | 用户产生的通知信息。类似E_NOTICE , 但是是由用户自己在代码中使用PHP函数trigger_error() 来产生的。 | |
E_STRICT | 2048 | 启用PHP对代码的修改建议,以确保代码具有最佳的互操作性和向前兼容性。 | |
E_RECOVERABLE_ERROR | 4096 | 可被捕捉的致命错误。 它表示发生了一个可能非常危险的错误,但是还没有导致PHP引擎处于不稳定的状态。 如果该错误没有被set_error_handler() 捕捉,将成为一个E_ERROR 从而脚本会终止运行 。 | |
E_DEPRECATED | 8192 | 运行时通知。启用后将会对在未来版本中可能无法正常工作的代码给出警告。 | |
E_USER_DEPRECATED | 16384 | 用户产生的警告信息。 类似E_DEPRECATED , 但是是由用户自己在代码中使用PHP函数trigger_error() 来产生的。 | |
E_ALL | 30719 | E_STRICT 之外的所有错误和警告信息。 |
上面的值(数值或者符号)用于建立一个二进制位掩码,来制定要报告的错误信息。可以使用按位运算符来组合这些值或者屏蔽某些类型的错误。请注意,在 php.ini 之中,只有’|’, ‘~’, ‘!’, ‘^’ 和 ‘&’ 会正确解析。
Error层次结构
Throwable
Error
ArithmeticError
DivisionByZeroError
AssertionError
CompileError
ParseError
TypeError
ArgumentCountError
2.1 try...catch
处理错误
PHP7
改变了大多数错误的报告方式。不同于传统(PHP5)的错误报告机制,现在大多数错误被作为Error
异常抛出。
try...catch
捕捉错误代码格式
<?php
try {
//你的代码逻辑
} catch (\Throwable $t) {
//只会在PHP7版本执行,PHP5不会执行
}
catch (\Exception $e)
{
//只会在PHP5执行,PHP7不会执行
}
案例:
<?php
/**
* @param int $inUserId
* @param int $outUserId
* @param float $amout
* @return bool
* @datetime 2020/7/2 5:15 下午
* @author roach
* @email jhq0113@163.com
*/
function transfer($inUserId, $outUserId, $amout)
{
if($inUserId < 1) {
trigger_error('入金用户不存在', E_USER_WARNING);
}
if($outUserId < 1) {
trigger_error('出金用户不存在', E_USER_WARNING);
}
if(bccomp($amout, '0', 2) <= 0) {
trigger_error('转账金额必须大于0', E_USER_WARNING);
}
if(bccomp($amout, '100000000', 2) > 0) {
trigger_error('服务器内部错误', E_USER_ERROR);
}
return true;
}
try {
transfer(1, 0, 0);
fff();
}catch (\Throwable $throwable) {
echo $throwable->getMessage().PHP_EOL;
}
以上例程输出:
PHP Warning: 出金用户不存在 in Exception.php on line 18
PHP Warning: 转账金额必须大于0 in Exception.php on line 22
Call to undefined function fff()
从以上例程可以看出,并不是所有的
error
都能被try...catch
捕捉到,当调用不存在的方法fff()
时,被try...catch
捕捉到了。
2.2 set_error_handler
捕捉错误
同样调用以上转账逻辑,增加
set_error_handler
捕获错误,如下:
<?php
/**
* 致命错误
*/
$fatalError = [
E_ERROR => 1,
E_PARSE => 1,
E_CORE_ERROR => 1,
E_CORE_WARNING => 1,
E_COMPILE_ERROR => 1,
E_COMPILE_WARNING => 1
];
set_error_handler(function() use(&$fatalError){
$args = func_get_args();
$throwable = new \ErrorException($args[1], $args[0], 1, $args[2], $args[3]);
echo $throwable->getMessage().PHP_EOL;
if(isset($fatalError[ $throwable->getCode() ])) {
exit();
}
});
try {
transfer(1, 0, 0);
fff();
}catch (\Throwable $throwable) {
echo $throwable->getMessage().PHP_EOL;
}
以上例程输出:
出金用户不存在
转账金额必须大于0
Call to undefined function fff()
关于异常和错误处理就介绍这么多,大家自动动手封装一个面向对象的通用异常处理吧。