异常处理篇之异常信息报告、渲染及自定义处理
上篇教程学院君大致介绍了 Laravel 异常处理底层原理,今天我们来详细介绍下进行异常处理时如何报告和渲染异常信息。
异常信息的报告
通过上篇教程的介绍,你应该知道 Laravel 是通过系统注册的异常处理器提供的 report
方法来报告异常,那么我们就来看看系统自带的异常处理器是如何定义该方法的,如果你要自定义异常报告机制的话,也可以参考该方法的实现。该方法的源码位于 vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php
中:
public function report(Exception $e)
{
if ($this->shouldntReport($e)) {
return;
}
if (is_callable($reportCallable = [$e, 'report'])) {
return $this->container->call($reportCallable);
}
try {
$logger = $this->container->make(LoggerInterface::class);
} catch (Exception $ex) {
throw $e;
}
$logger->error(
$e->getMessage(),
array_merge($this->context(), ['exception' => $e]
));
}
首先调用处理器实例的 shouldntReport
方法判断当前异常是否是不需要报告的异常,讲到这里我们需要声明下在 Laravel 应用中,开发者可以指定哪些异常不需要报告,比如一些 HTTP 404 异常,验证失败异常,我们会通过页面渲染的方式显式提示用户,没有必要再报告给系统,在异常处理器基类中,就内置了一些不需要报告的异常:
protected $internalDontReport = [
AuthenticationException::class,
AuthorizationException::class,
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
TokenMismatchException::class,
ValidationException::class,
];
此外,我们还可以在继承了该基类的、应用级别的异常处理器类中通过 $dontReport
属性来指定额外的不需要报告的异常(如果使用系统默认的设置,对应的异常处理器类就是 App\Exceptions\Handler
):
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [
//
];
对于不需要报告的异常类型,Laravel 会直接跳过,不再进行报告处理,转而继续进行渲染或其它处理;否则,如果待处理异常类定义了 report
方法,将调用该异常实例上的 report
方法并返回;如果没有定义该方法,将会通过日志方式以错误级别的日志信息将该异常记录到位于 storage/logs
下的日志文件中。
以上就是 Laravel 框架底层报告异常信息的逻辑,如果你想要自定义异常信息的报告,比如跳过,可以将其配置到系统默认异常处理器的 $dontReport
属性中;而如果你想要自定义某个异常的报告逻辑,可以在该异常类中实现 report
方法,当然,你还可以选择在应用级别的异常处理器类中自定义 report
方法对异常信息的报告进行统一处理,比如将异常信息报告到 Sentry 或者 Bugsnag(相关实现参考这篇教程)。否则,系统都会将其记录到日志文件中。
异常信息的渲染
Laravel 异常渲染通过异常处理器的 render
方法完成,相应的核心逻辑也是位于 vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php
中:
public function render($request, Exception $e)
{
if (method_exists($e, 'render') && $response = $e->render($request)) {
return Router::toResponse($request, $response);
} elseif ($e instanceof Responsable) {
return $e->toResponse($request);
}
$e = $this->prepareException($e);
if ($e instanceof HttpResponseException) {
return $e->getResponse();
} elseif ($e instanceof AuthenticationException) {
return $this->unauthenticated($request, $e);
} elseif ($e instanceof ValidationException) {
return $this->convertValidationExceptionToResponse($e, $request);
}
return $request->expectsJson()
? $this->prepareJsonResponse($request, $e)
: $this->prepareResponse($request, $e);
}
首先,会判断异常类中是否包含 render
方法,如果包含的话,则执行该方法,然后将结果传递给 Router::toResponse
方法处理并以响应方式返回给客户端;如果不包含该方法的话,则判断异常实例是否是 Responsable
类型,如果是的话,也会调用实例上的 toResponse
方法以响应方式返回给客户端。
如果以上两个条件不满足的话,则继续往下处理,调用 prepareException
方法为待渲染异常做一些准备工作:
protected function prepareException(Exception $e)
{
if ($e instanceof ModelNotFoundException) {
$e = new NotFoundHttpException($e->getMessage(), $e);
} elseif ($e instanceof AuthorizationException) {
$e = new AccessDeniedHttpException($e->getMessage(), $e);
} elseif ($e instanceof TokenMismatchException) {
$e = new HttpException(419, $e->getMessage(), $e);
}
return $e;
}
比如将 ModelNotFoundException
异常转化为 NotFoundHttpException
异常进行处理等,具体看代码就一目了然了,就不一一列举了。
接下来,系统会对异常类型做一些判断:
- 如果是
HttpResponseException
异常的话,则直接调用该异常实例的getResponse
方法并返回,比如调用abort
辅助函数就会抛出该异常; - 如果是
AuthorizationException
异常的话,则调用异常处理器的unauthenticated
方法并返回响应,如果是 API 接口请求的话,会返回 JSON 格式的 401 响应,如果是 Web 请求的话,则重定向到登录页,比如认证中间件判断用户未登录则会抛出该异常; - 如果是
ValidationException
异常的话,则调用异常处理器的convertValidationExceptionToResponse
方法并返回响应,该方法主要返回验证失败信息,和上面一个判断一样,也分 JSON 接口和 Web 请求返回不同格式的响应数据,表单验证失败时通常会抛出该异常。
如果异常不是上述几种类型的异常,则最终会判断请求是否是期望 JSON 格式数据(比如 Ajax、Pjax 请求,或者请求 Content-Type 字段值是 application/json
),如果是的话调用 prepareJsonResponse
方法将异常转化为 JSON 响应返回;否则调用 prepareResponse
方法返回响应:
protected function prepareResponse($request, Exception $e)
{
if (! $this->isHttpException($e) && config('app.debug')) {
return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);
}
if (! $this->isHttpException($e)) {
$e = new HttpException(500, $e->getMessage());
}
return $this->toIlluminateResponse(
$this->renderHttpException($e), $e
);
}
在该方法中,会首先判断异常是否是 HttpException,如果不是并且应用 app.debug
配置值为 true
的话,则将异常转化为 Illuminate Response 并返回;如果 app.debug
配置值为 false
或未设置的话则将异常转化为 HttpException,并将错误码设置为 500;如果异常是 HttpException 的话,则将其渲染到异常视图并返回给客户端。
以上就是 Laravel 底层处理异常渲染的逻辑,如果你需要自定义的话,可以在自定义的异常处理器中重写相关逻辑,也可以在系统自带的异常处理器 App\Exceptions\Handler
中覆盖父类相关实现来实现异常渲染的局部自定义,对于个别需要特殊处理的异常,还可以在异常类中实现 render
方法来自定义异常渲染逻辑。
2 Comments
你好 ,我刚进 学院君和他的朋友们 怎么绑定这里账号在电脑上看相应的教程呢?
知识星球有web版本 你可以在这里通过目录快速阅读:
第一次扫码登录下即可。