Laravel如何实现适合Api的异常处理响应格式


Posted in PHP onJune 14, 2020

前言

Laravel全局捕获异常后,会把异常转为相应的数据格式返回给用户。如果想要规定的数据格式相应,那我们只需重写异常捕获后的处理方法即可。

异常处理流程

Illuminate\Foundation\Exception\Handler 中的 render 方法用来将异常转化为响应。

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() 中又调用了 prepareException() 对部分异常进行预处理,但并未执行转化为响应的操作。

ModelNotFoundException 一般在模型查找不到抛出,prepareException() 中它被转为 Symfony 包中NotFoundHttpException,默认状态码404;

AuthorizationException 在 Policy 权限未通过时抛出,prepareException() 中它被转为 Symfony 包中 AccessDeniedHttpException,默认状态码403;

TokenMismatchException 在 CSRF 验证未通过时抛出,prepareException() 中它被转为 Symfony 包中 HttpException,给定状态码419;

其他异常直接返回。

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;
}

在回到 render() ,预处理异常之后,又分别对 HttpResponseException、AuthenticationException 和 ValidationException 单独处理,并转为响应返回。

除此以外的异常,都在 prepareJsonResponse() 或 prepareResponse() 处理 ,expectsJson() 用来判断返回 json 响应还是普通响应。

修改异常响应格式

了解了异常处理流程,接下来就处理异常响应格式。

修改登录认证异常格式

由上文可知,AuthenticationException 被捕获后,调用 unauthenticated() 来处理。

protected function unauthenticated($request, AuthenticationException $exception)
{
 return $request->expectsJson()
    ? response()->json(['message' => $exception->getMessage()], 401)
    : redirect()->guest($exception->redirectTo() ?? route('login'));
}

在 appExceptionsHandler.php 中重写 unauthenticated() 使其返回我们想要的数据格式。

protected function unauthenticated($request, AuthenticationException $exception)
{
 return $request->expectsJson()
  ? response()->json([
   'code' => 0,
   'data' => $exception->getMessage(),
  ], 401)
  : redirect()->guest($exception->redirectTo() ?? route('login'));
}

修改验证异常格式

同样由上文可知,ValidationException 被捕获后交由 convertValidationExceptionToResponse() 处理,进入此方法后我们需要继续追踪,若是需要 json 响应,最终交由 invalidJson() 处理。

protected function convertValidationExceptionToResponse(ValidationException $e, $request)
{
 if ($e->response) {
  return $e->response;
 }

 return $request->expectsJson()
    ? $this->invalidJson($request, $e)
    : $this->invalid($request, $e);
}
protected function invalidJson($request, ValidationException $exception)
{
 return response()->json([
  'message' => $exception->getMessage(),
  'errors' => $exception->errors(),
 ], $exception->status);
}

我们继续在 appExceptionsHandler.php 重写 invalidJson() 即可自定义返回格式。

protected function invalidJson($request, ValidationException $exception)
{
 return response()->json([
  'code' => 0,
  'data' => $exception->errors(),
 ], $exception->status);
}

修改其他异常格式

其他异常是调用 prepareJsonResponse() 来处理,此方法又调用 convertExceptionToArray() 来处理响应格式。

protected function prepareJsonResponse($request, Exception $e)
{
 return new JsonResponse(
  $this->convertExceptionToArray($e),
  $this->isHttpException($e) ? $e->getStatusCode() : 500,
  $this->isHttpException($e) ? $e->getHeaders() : [],
  JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
 );
}
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',
 ];
}

在 appExceptionsHandler.php 中重写 convertExceptionToArray() 来自定义其他异常响应格式。

protected function convertExceptionToArray(Exception $e)
{
 return config('app.debug') ? [
  'code' => 0,
  'data' => $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(),
 ] : [
  'code' => 0,
  'data' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
 ];
}

强制 json 响应

代码中多次出现了 expectsJson() ,此方法是用来判断返回 json 响应还是普通响应。

public function expectsJson()
{
 return ($this->ajax() && ! $this->pjax() && $this->acceptsAnyContentType()) || $this->wantsJson();
}

以下两种条件下,会返回json响应。

非XML请求、非pjax并且 Headers 中 Accept 设置为接收所有格式响应;

Headers Accept 设置为 /json、+json。如:Accept:application/json。

除此之外的情况,将不会响应json。我们可以利用中间件强制追加 Accept:application/json,使异常响应时都返回json。(参考教程 L03 6.0 中提到的方法)

创建中间件 AcceptHeader

<?php

namespace App\Http\Middleware;

use Closure;

class AcceptHeader
{
 public function handle($request, Closure $next)
 {
  $request->headers->set('Accept', 'application/json');

  return $next($request);
 }
}

在 app/Http/Kernel.php 中,将中间件加入路由组即可。

protected $middlewareGroups = [
 'web' => [
  .
  .
  .
 'api' => [
  \App\Http\Middleware\AcceptHeader::class,
  'throttle:60,1',
  'bindings',
 ],
];

大功告成。

总结

到此这篇关于Laravel如何实现适合Api的异常处理响应格式的文章就介绍到这了,更多相关Laravel适合Api的异常处理响应格式内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

PHP 相关文章推荐
PHP 函数语法介绍一
Jun 14 PHP
PHP下通过系统信号量加锁方式获取递增序列ID
Sep 25 PHP
支持数组的ADDSLASHES的php函数
Feb 16 PHP
PHP字符过滤函数去除字符串最后一个逗号(rtrim)
Mar 26 PHP
phpmailer在服务器上不能正常发送邮件的解决办法
Jul 08 PHP
PHP函数实现从一个文本字符串中提取关键字的方法
Jul 01 PHP
php防止网站被攻击的应急代码
Oct 21 PHP
PHP未登录自动跳转到登录页面
Dec 21 PHP
PHP递归实现汉诺塔问题的方法示例
Nov 25 PHP
PHP实现基本留言板功能原理与步骤详解
Mar 26 PHP
TP5框架实现签到功能的方法分析
Apr 05 PHP
phpcmsv9.0任意文件上传漏洞解析
Oct 20 PHP
Yii redis集合的基本使用教程
Jun 14 #PHP
php获取小程序码的实现代码(B类接口)
Jun 13 #PHP
laravel admin实现分类树/模型树的示例代码
Jun 10 #PHP
详解php反序列化
Jun 10 #PHP
实例讲解PHP表单
Jun 10 #PHP
如何在PHP中使用数组
Jun 09 #PHP
详解PHP中curl_multi并发的实现
Jun 08 #PHP
You might like
PHP5.2中date()函数显示时间与北京时间相差8小时的解决办法
2009/05/28 PHP
ThinkPHP框架设计及扩展详解
2014/11/25 PHP
PHP数组和explode函数示例总结
2015/05/08 PHP
Thinkphp微信公众号支付接口
2016/08/04 PHP
php base64 编码与解码实例代码
2017/03/21 PHP
Yii框架使用魔术方法实现跨文件调用功能示例
2017/05/20 PHP
js实现权限树的更新权限时的全选全消功能
2009/02/17 Javascript
判断js中各种数据的类型方法之typeof与0bject.prototype.toString讲解
2013/11/07 Javascript
javascript动画算法实例分析
2015/07/31 Javascript
jquery+正则实现统一的表单验证
2015/09/20 Javascript
解决jquery无法找到其他父级子集问题的方法
2016/05/10 Javascript
使用bootstrap实现多窗口和拖动效果
2016/09/22 Javascript
jquery延迟对象解析
2016/10/26 Javascript
详解angular2采用自定义指令(Directive)方式加载jquery插件
2017/02/09 Javascript
JavaScript使用FileReader实现图片上传预览效果
2020/03/27 Javascript
详解vue 项目白屏解决方案
2018/10/31 Javascript
nodejs中方法和模块用法示例
2018/12/24 NodeJs
微信小程序添加插屏广告并设置显示频率(一天一次)
2019/12/06 Javascript
vue实现移动端项目多行文本溢出省略
2020/07/29 Javascript
一行JavaScript代码如何实现瀑布流布局
2020/12/11 Javascript
Python深入学习之闭包
2014/08/31 Python
python使用线程封装的一个简单定时器类实例
2015/05/16 Python
matplotlib.pyplot画图 图片的二进制流的获取方法
2018/05/24 Python
分享8个非常流行的 Python 可视化工具包
2019/06/05 Python
Mac 使用python3的matplot画图不显示的解决
2019/11/23 Python
python中p-value的实现方式
2019/12/16 Python
pyecharts绘制中国2020肺炎疫情地图的实例代码
2020/02/12 Python
Python使用type动态创建类操作示例
2020/02/29 Python
Python自省及反射原理实例详解
2020/07/06 Python
加拿大服装和鞋类零售商:Mark’s
2021/01/04 全球购物
如何启动时不需输入用户名与密码
2014/05/09 面试题
关于递归的一道.NET面试题
2013/05/12 面试题
竞赛口号大全
2014/06/16 职场文书
你真的了解redis为什么要提供pipeline功能
2021/06/22 Redis
详解Go语言中配置文件使用与日志配置
2022/06/01 Golang
Python时间操作之pytz模块使用详解
2022/06/14 Python