Laravel 错误处理:自定义异常响应
问题
程序出现或主动扔出异常,如何自定义这些异常的响应,以便为用户提供更友好的错误提示?
答案
Laravel 中所有异常都是由 App\Exceptions\Handler
类处理。打开此类文件,你可以发现 render
方法,render
方法负责将异常转换为 HTTP 响应。默认情况下,异常将传递给为你生成响应。
这里把异常分为「 内置异常」和「自定义异常」两大类别来分别处理。
内置异常
内置异常类包括了 PHP 和 Laravel 的内置异常。
例如需要自定义的 404
页面返回内容,先找到对应的异常类是 NotFoundHttpException
。
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class Handler extends ExceptionHandler
.
.
.
/**
* 将异常转换为 HTTP 响应。
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
if ($exception instanceof NotFoundHttpException) {
return response('抱歉,未找到数据!', 404);
}
return parent::render($request, $exception);
}
.
.
.
这时候如果访问一个未定义的 URL:
Laravel 可以轻松显示各种 HTTP 状态代码的自定义错误模板页面。文档详情
自定义异常
实际开发时,常常需要自定义一些异常类。
例如定义一个 ExampleException
异常类:
<?php
namespace App\Exceptions;
use Exception;
class ExampleException extends Exception
{
/**
* 转换异常为 HTTP 响应
*
* @param \Illuminate\Http\Request
* @return \Illuminate\Http\Response
*/
public function render($request)
{
return response($this->getMessage() ?: '发生异常啦');
}
}
当在自定义的异常类定义了
render
方法,它会被框架自动调用。
为了简单就在 web.php
路由的闭包中扔出该异常:
Route::get('/example', function () {
throw new \App\Exceptions\ExampleException('我是一个异常啦');
});
到浏览器中访问:
更多内置异常处理
App\Exceptions\Handler
的父类提供了异常更易理解的处理方法,重写这些方法能轻松处理对应的异常:
将身份验证异常转换为响应
/**
* 将身份验证异常转换为响应。
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Auth\AuthenticationException $exception
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
return $request->expectsJson()
? response()->json(['message' => $exception->getMessage()], 401)
: redirect()->guest($exception->redirectTo() ?? route('login'));
}
将给定的验证异常创建响应对象
/**
* Create a response object from the given validation exception.
*
* @param \Illuminate\Validation\ValidationException $e
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function convertValidationExceptionToResponse(ValidationException $e, $request)
{
if ($e->response) {
return $e->response;
}
return $request->expectsJson()
? $this->invalidJson($request, $e)
: $this->invalid($request, $e);
}
/**
* 将验证异常转换为JSON响应。
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Validation\ValidationException $exception
* @return \Illuminate\Http\JsonResponse
*/
protected function invalidJson($request, ValidationException $exception)
{
return response()->json([
'message' => $exception->getMessage(),
'errors' => $exception->errors(),
], $exception->status);
}
/**
* 将验证异常转换为响应。
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Validation\ValidationException $exception
* @return \Illuminate\Http\Response
*/
protected function invalid($request, ValidationException $exception)
{
return redirect($exception->redirectTo ?? url()->previous())
->withInput(Arr::except($request->input(), $this->dontFlash))
->withErrors($exception->errors(), $exception->errorBag);
}
为给定的异常准备 JSON 响应。
/**
* 为特定的异常准备 JSON 响应。
*
* @param \Illuminate\Http\Request $request
* @param \Exception $e
* @return \Illuminate\Http\JsonResponse
*/
protected function prepareJsonResponse($request, Exception $e)
{
//TODO 这里可以处理更多的异常并返回 JSON 响应
return new JsonResponse(
$this->convertExceptionToArray($e),
$this->isHttpException($e) ? $e->getStatusCode() : 500,
$this->isHttpException($e) ? $e->getHeaders() : [],
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
);
}
/**
* 将给定的异常转换为数组。
*
* @param \Exception $e
* @return array
*/
protected function convertExceptionToArray(Exception $e)
{
return config('app.debug') ? [
'message' => $e->getMessage(),
'exception' => get_class($e),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => collect($e->getTrace())->map(function ($trace) {
return Arr::except($trace, ['args']);
})->all(),
] : [
'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
];
}
准备给定异常的响应。
/**
* 准备给定异常的响应。
*
* @param \Illuminate\Http\Request $request
* @param \Exception $e
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function prepareResponse($request, Exception $e)
{
//TODO 这里可以处理更多的异常并返回响应
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
);
}
注意
为了保持 App\Exceptions\Handler
的 render
方法的洁净,不应该在该方法中进行太多的 instanceof
检查,这样当异常类多的时候就显得臃肿,自定义的异常类应该在类中定义 render
方法。
:pensive:怎么感觉还是没理解