专栏首页写PHP的老王Laravel 如何优雅的实现输出结构统一的功能?

Laravel 如何优雅的实现输出结构统一的功能?

背景

一般的项目需求都会要求统一的输出结构,特别是对于api应用而言。因此,如果有beforeResponse的功能,则可以在数据输出之前对response进行统一格式化处理。

假设这么一种场景,应用做api开发,使用抛异常的方式(自定义异常类ApiException)返回无效非法请求的情况。正常请求则返回合法数据(数组或可序列化的模型),希望返回的数据格式

正常请求返回数据格式:

{
  "code":0,
  "data":[

  ],
  "message":""
}

异常请求返回数据格式:

{
  "code":400,
  "data":[

  ],
  "message":"错误提示"
}

Laravel 的设计如何实现

Laravel中的中间件确实支持beforeResponse操作,支持在中间件中进行格式化。但是,这里仅限于正常返回。那么如果控制器抛了异常又改怎么办呢?

Laravel的调用链使得控制器里的异常在正常情况下,还没有抛到中间件就被系统注册的ExceptionHandler类拦截处理了。github上也有关于中间件不能捕获控制器异常的问题Can't catch exception in middleware

作者给出的结论是,Laravel本身的设计就是将异常处理放在ExceptionHandler中。

Yes, this is the beavhiour starting from L5.2. Throwing an exception causes the response to be set as that returned from the exception handler, and then the middleware is allowed to backout from that point.

We don't recommend you write try catch blocks in middleware. Instead, handle exceptions inside the exception handler. Perhaps https://github.com/GrahamCampbell/Laravel-Exceptions would be of use to you?

那么,按照Laravel的设计,正常的请求,我们在一个中间件FormaterResponse处理,处理逻辑如下:

<?php
namespace App\Http\Middleware;
use App\Http\Middleware\Closure;
use \Exception;
class FormaterResponse
{
  public function handle($request, \Closure $next)
  {
    $response = $next($request);
    $content = $response->getData();
    $content = [
      'code'=>0,
      'message'=>'',
      'data'=>$content
    ];
    $response->setData($content);
    return $response;
  }
}


错误返回,我们在app\Exceptions\Handlerrender方法处理,格式化,处理逻辑如下:

public function render($request, Exception $e)
{
  if($e instanceof ApiException)
  {
    $response = [
      'code'=>$e->getCode(),
      'message'=>$e->getMessage(),
      'data'=>[]
    ];
    return response()->json($response, 200);
  }
  parent::render($request,$e);
}

更好的方式

上面的这种做法有一个弊端,如果某些模块下想要的数据格式返回不一样,对应异常情况的处理会比较麻烦。因为ExceptionHandler是对一个全局的处理。如果能把数据格式化都放在中间件处理,则可以非常灵活。

其实需要改动的内容非常上,只需要在ExceptionHandler中的handle方法中,对于自定义异常类ApiException继续向上抛出去就可以在middleware捕获到异常,进而对异常放回进行格式化。

修改之后App\Exceptions\Handler 中render的代码如下:

public function render($request, Exception $e)
{
  if($e instanceof ApiException)
  {
    throw $e;
  }
  parent::render($request,$e);
}
<?php

namespace App\Http\Middleware;
use App\Http\Middleware\Closure;
use App\Exceptions\ApiException;
class FormaterResponse
{
  public function handle($request, \Closure $next)
  {
    $code = 0;
    $msg = '';
    $data = [];
    try{
      $response = $next($request);
      $data = $response->getData();
    }catch(ApiException $e){
      $code = $e->getCode();
      $msg = $e->getMessage();
      $response = response()->json([],200);
    }
    $content = [
      'code'=>$code,
      'message'=>$msg,
      'data'=>$data
    ];
    $response->setData($content);
    return $response;
  }
}

这样就可以在所有应用FormaterResponse的路由中实现beforeRespons 功能,格式化统一的数据输出。

本文分享自微信公众号 - 写PHP的老王(laowang_php),作者:写PHP的老王

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 对于魔术方法__call,__callStatic 新的认识

    其实上面的解释在某些情况下是正确的。但是在一些特殊情形,如果按照这个解释来理解,就会觉得结果不可思议了。

    写PHP的老王
  • Laravel 软删除存在的问题

    1、软删除使用的标记类型是时间类型,通过is null条件查询,删除标记取值不支持定义

    写PHP的老王
  • elasticsearch知识点六问六答

    ES根据数据ID路由到分片方式为: shard = hash % primary_shard_num 。因此主分片的数目必须在索引创建之前确定好。 由于新加入节...

    写PHP的老王
  • 面向对象封装的web服务器

    skylark
  • 返回用户指定页面的web服务器

    skylark
  • Response响应

    在去发送一个请求时,先会找到主机服务器再找到对应的Service,找到Servoce对应的引擎

    木瓜煲鸡脚
  • flask reponse对象(flask 10)

    from flask import Flask,make_response,json

    用户5760343
  • 【小家Spring】Spring MVC容器的web九大组件之---ViewResolver源码详解---视图View详解

    上篇文章已经重点讲解过了:ViewResolver视图解析器 【小家Spring】Spring MVC容器的web九大组件之—ViewResolver源码详解...

    YourBatman
  • Python入门网络爬虫之精华版

    首先列举一下本人总结的相关文章,这些覆盖了入门网络爬虫需要的基本概念和技巧:宁哥的小站-网络爬虫

    IT派
  • Servlet第三篇【request和response介绍、response的常见应用】

    response、request对象 Tomcat收到客户端的http请求,会针对每一次请求,分别创建一个代表请求的request对象、和代表响应的respon...

    Java3y

扫码关注云+社区

领取腾讯云代金券