一、PHP 异常处理
异常处理通常是防止未知错误产生所采取的处理措施。异常处理的好处是不用再绞尽脑汁去考虑各种错误,这为处理某一类错误提供了一个很有效的方法,使编程效率大大提高。当异常被触发时,通常会发生:
- 当前代码状态被保存
- 代码执行被切换到预定义的异常处理器函数
- 根据情况,处理器也许会从保存的代码状态重新开始执行代码,终止脚本执行,或从代码中另外的位置继续执行脚本
二、异常
1. PHP 内置的异常类
Exception 是所有异常的基类:
class Exception
{
/* 属性 */
protected string $message ; // 异常信息
protected int $code ; // 用户自定义异常代码
protected string $file ; // 发生异常的文件名
protected int $line ; // 发生异常的代码行号
/* 方法 */
public __construct ([ string $message = "" [, int $code = 0 [, Throwable $previous = NULL ]]] )
final public getMessage ( void ) : string // 返回异常信息
final public getPrevious ( void ) : Throwable
final public getCode ( void ) : int // 返回异常代码
final public getFile ( void ) : string // 返回发生异常的文件名
final public getLine ( void ) : int // 返回发生异常的代码行号
final public getTrace ( void ) : array // backtrace() 数组
final public getTraceAsString ( void ) : string
public __toString ( void ) : string
final private __clone ( void ) : void
}
可以用自定义的异常处理类来扩展 PHP 内置的异常处理类,使用自定义的类来扩展内置异常处理类,并且要重新定义构造函数的话,建议同时调用 parent::__construct() 来检查所有的变量是否已被赋值。
2. 异常抛出
异常可以被触发,即抛出,被抛出的异常必须是 Exception 的实例或者 Exception 的子类实例,规定的异常触发的方式为 throw :
throw new Exception('An Exception occured!');
3. 异常处理
如果一个异常没有被捕获,而且又没有使用 set_exception_handler() 作相应的处理的话,那么 PHP 将会产生一个严重的错误,并且输出未能捕获异常 (Uncaught Exception ... ) 的提示信息:
Fatal error: Uncaught exception 'Exception' with message 'An Exception occured!' in /var/www/html/test/index.php on line 5
Exception: An Exception occured! in /var/www/html/test/index.php on line 5
Call Stack:
0.0005 330680 1. {main}() /var/www/html/test/index.php:0
4. 异常捕获
为了避免 Uncaught Exception 错误,需要将可能抛出异常的代码块放入 try 代码块内,如果没有抛出异常,代码继续执行,如果异常被触发,则 try 代码块需要至少有一个相应的 catch 或 finally 代码块匹配:
try {
throw new Exception('A error is thrown');
} catch(Exception $e) {
// 处理代码
}
5. 顶层异常处理器
在实际开发过程中,异常捕获仅靠 try 和 catch 是不能满足需求的。这时候需要一个处理所有未被捕获异常的异常处理器,而 set_exception_handler 可以设置用户自定义的异常处理函数:
function ExceptionHandler($e)
{
echo "Exception: " , $exception->getMessage();
}
set_exception_handler('ExceptionHandler');
throw new Exception('Uncaught Exception occured');
三、ThinkPHP 异常处理
在调试模式下,ThinkPHP 的默认异常处理与 PHP 的默认异常处理有所区别。
与 PHP 抛出的单纯错误信息不同,ThinkPHP 调试模式提供了更多的错误信息与细节,更具人性化,利于开发人员快速定位错误。
在调试模式下,ThinkPHP 默认异常处理的错误页面如下所示:
默认情况下,ThinkPHP 会对任何错误抛出异常,也可以设置配置文件的 error_reporting 来限制报告的错误级别:
// 异常错误报错级别,
error_reporting(E_ERROR | E_PARSE );
1. 渲染异常信息
ThinkPHP 的异常处理中,Http 请求异常都交由处理函数 render() 进行渲染,将异常信息渲染为 Http 响应进行返回。
首先会检查是否有自定义的异常处理器,如果有定义,则调用自定义异常处理器进行处理,如果没有则根据异常是否为 HttpException 区分处理。
/**
* Render an exception into an HTTP response.
*
* @param Exception $e
* @return Response
*/
public function render(Exception $e)
{
if ($this->render && $this->render instanceof Closure) {
$result = call_user_func_array($this->render, [$e]);
if ($result) {
return $result;
}
}
if ($e instanceof HttpException) {
return $this->renderHttpException($e);
} else {
return $this->convertExceptionToResponse($e);
}
}
四、自定义异常处理
虽然 ThinkPHP 异常处理具有更详细的错误信息,但是在生产环境往往不希望将详细的错误信息泄漏出去,防止暴露自身接口给用户,但又可以提供快速追溯问题的提示给开发人员,需要抛出的指定的异常信息ㄍ所以开发者需要自定义异常处理。
框架支持异常由开发者自定义的异常处理类进行处理,只需要配置参数 exception_handle :
// 异常处理handle类 留空使用 thinkexceptionHandle
'exception_handle' => 'appcommonexceptionExceptionHandler',
配置 exception_handle 后,异常将由默认异常处理类 thinkexceptionHandle 变为自定义异常处理类 appcommonexceptionExceptionHandler 进行处理。
1. 自定义异常类
需要抛出指定的异常信息时,异常信息包含在异常类中,所以需要自定义异常类。
下面给出一个自定义的异常类例子,该异常类为 API 项目所自定义的异常类,该异常类继承自 thinkException 包含的异常信息有:
- 错误信息 $message :用于描述 API 错误信息。
- 错误码 $code :错误异常的错误码,用于开发者查找错误代码。
- 状态码 $status :返回的 Http 响应状态码。
- $previous :异常链中前一个异常。
namespace appcommonexception;
use thinkException;
class ApiException extends Exception
{
// 状态码
public $status;
// 构造函数
public function __construct($message, $code = 0, int $status = 404, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->status = $status;
}
public function getStatus($status)
{
return $this->status;
}
}
2. 抛出异常
在程序运行中,如果检测到已知错误,即可抛出具有提示意义的自定义异常类,中断程序运行,并由异常处理类处理后返回期望的响应信息。
例如用户输入参数不符合规范,参数验证不通过,可以抛出参数验证异常:
function validateParams($class, $sence = '', $data)
{
try {
// 调用验证器
$validate = validate($class);
// 检查结果
$result = $validate->hasScene($sence) ? $validate->check($data, [], $sence) : true;
// 检查返回的信息,如果没有就不返回
if (!$result) {
throw new ApiException($validate->getError(), 30003, 400);
}
} catch (ClassNotFoundException $e) {
return;
}
}
3. 自定义异常处理类
自定义异常处理类需要继承默认异常处理类 thinkexceptionHandle ,并且重新实现 render 方法:
use Exception;
use thinkexceptionHandle;
use thinkexceptionHttpException;
class ExceptionHandler extends Handle
{
// 异常处理接管
public function render(Exception $e)
{
// 如果调试模式,异常交由系统处理
if (config('app_debug') == true) {
return parent::render($e);
}
// 异常分类处理
if ($e instanceof ApiException) {
return $this->apiError($e);
} else if ($e instanceof HttpException) {
return $this->httpError($e);
} else if ($e instanceof ValidateException) {
return json($e->getError(), 40000, 400);
} else {
return $this->serverError($e);
}
}
// Api 异常处理
public function apiError($e)
{
$result = [
'error_code' => $e->getCode(),
'message' => $e->getMessage(),
'data' => [
'requestUrl' => request()->url(),
'requestParams' => request()->param()
]
];
return json($result, $e->getStatus());
}
}
在 render 方法中,通过 config('app_debug') 判断是否开启了调试模式,如果开启了就将异常交由默认异常处理类 render 方法进行处理,否则根据自定义异常类进行调用不同方法进行处理。
参考资料
|