另辟蹊径!如何在 Laravel 更优雅的响应 JSON 数据

传统 JSON 响应的做法

Laravel11 最近刚发布了,也想输出点内容。起这个标题的确有点那味了。因为社区里有很多关于怎么规范或者响应 JSON 数据的文章了。但我还是想输出一点不同的内容或者方法,因为我还没见过有文章讨论过这么做,可能不是最佳实践。回到正题,一般 Laravel 做 API 项目,响应数据格式都是这么做的。

php 复制代码
class  TestController  extends  Controller
{
 	public  function  index()
    {
 		return  json(['code'  =>  10000, 'message'  =>  '', 'data'  =>  1])
    }
}

再简便点,就是把这个 json 输出封装到基类,或者 trait 中供使用者自己调用。我也是这么使用,但在使用过程中,我在想能不能更加简单点,因为有时候仅仅是想返回数据而已。可不可以像下面的示例这样

php 复制代码
class  TestController  extends  Controller
{
 	public  function  index()
    {
 		return  1;
    }
}

只需要业务方法调用返回就可以了,不需要每次都使用类似下面的代码

php 复制代码
class  TestController  extends  Controller
{
 	public  function  index()
    {
 		// 基类方法
 		return  $this->json(1)
    }
}

更优雅的响应方式

在研究 Laravel 的源码时,我发现了一个有趣的事件 RequestHandled,这个事件在请求处理完成后被触发。通过监听这个事件,我们可以在数据响应阶段进行处理,从而实现更优雅的 JSON 数据响应方式。看下面这段代码,目前这段代码在 Laravel11 中的 Illuminate\Foundation\Http 类中

php 复制代码
public  function  handle($request)
{
 	$this->requestStartedAt  =  Carbon::now();
	 try {
 		$request->enableHttpMethodParameterOverride();
 		$response  =  $this->sendRequestThroughRouter($request);
	} catch (Throwable  $e) {
		 $this->reportException($e);
 		$response  =  $this->renderException($request, $e);
    }
	 // 在这里可以对响应做些什么
	 $this->app['events']->dispatch(
	 	new  RequestHandled($request, $response)
     );
 	return  $response;
}

首先创建一个 Listener,使用 php artisan make:listener RequestHandledListener ,创建完成之后,Laravel 框架会自动帮注册,不需要手动去注册。

下面只是简单的示例,因为只需要 Json 响应,所以这里过滤掉所有非 JsonRespons 响应

php 复制代码
class  RequestHandledListener
{
	public  function  handle(RequestHandled  $event):  void
	{
		 $response  =  $event->response;
		 if ($response  instanceof  JsonResponse) {
			 $exception  =  $response->exception;
			if ($response->getStatusCode() ==  SymfonyResponse::HTTP_OK  &&  !$exception) {
				$response->setData($this->formatData($response->getData()));
		   }
		}
	}
	// 拦截数据,然后格式化数据
	// 具体内容跟规则可以根据实际业务设置
	protected  function  formatData(mixed  $data):  array
	{
		 $responseData  = [
		 'code'  =>  10000, // 业务成功 code
		 'message'  =>  'success', // 成功信息
		];
		 $responseData['data'] =  $data;
		 return  $responseData;
	}
}

回到控制器的时候,再使用下面的代码进行输出

php 复制代码
class  TestController  extends  Controller
{
	 public  function  index()
	{
	 	return  1;
	}
}

发现根本没有任何作用。根本不是 Json 响应啊。想一想为啥? 为什么控制要输出 Json Response 对象呢?上面的代码到浏览器只会输出个 1,就是一个普通的响应,而不是 Json Response。所以需要想响应始终设置为 Json Reponse 对象。来写一个中间件解决这个问题。使用 php artisan make:middleware JsonResponseMiddleware 创建中间件

php 复制代码
namespace  App\Http\Middleware;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class  JsonResponseMiddleware
{

	 public  function  handle(Request  $request, \Closure  $next)
	{
		 $response  =  $next($request);
		 // response
		 if ($response  instanceof  Response) {
		 	return  new  JsonResponse($response->getContent());
		 }
		 return  $response;
	}
}

切换到路由 web.php 文件,加上路由

php 复制代码
Route::get('/', [\App\Http\Controllers\IndexController::class, 'index'])->middleware(\App\Http\Middleware\JsonResponseMiddleware::class);

回到浏览器,刷新下,你将会看到下面的输出

json 复制代码
{
 "code": 10000,
 "message": "success",
 "data": "1"
}

处理不同请求来源

有时,我们可能需要根据请求的来源不同来处理响应数据的格式。例如,后台管理系统和客户端的请求可能需要不同的数据格式。这时,我们可以通过设置请求头信息来区分不同的请求来源。下面只是一个示例。现在前端请求一般都用 axios, 所以只要在全局 axios 对象设置一个头信息。如下

js 复制代码
axios.defaults.headers['Request-from'] =  'WhereFrom'

后端可以根据头信息来判断是否需要使用该返回,可以这么做,回到 RequestHandledListener

php 复制代码
public  function  handle(RequestHandled  $event):  void
{
 // 只有后台请求才处理
	if (Request::hasHeader('Request-from')&&  Str::of(Request::header('Request-from'))->exactly('WhereFrom')) {
		 $response  =  $event->response;
		 if ($response  instanceof  JsonResponse) {
			 $exception  =  $response->exception;
			 if ($response->getStatusCode() ==  SymfonyResponse::HTTP_OK  &&  !$exception) {
				 $response->setData($this->formatData($response->getData()));
            }
        }
    }
}

如何响应错误码

不对啊,这里写死的 success,那么失败请求该怎么做?整个项目失败都是用异常处理就行,所以只需要在全局异常这里处理下。首先确定下我们数据格式是这样的

json 复制代码
{
 "code": 10000,
 "message": "success",
 "data": "1"
}

当到控制器中 Index 方法抛出一个异常

php 复制代码
public  function  index()
{
 throw  new  \Exception('test');
}

会输出这样的内容

json 复制代码
{
 code: 10000,
 message: "success",
 data: '<!DOCTYPE  html>
 <html  lang="en"  class="auto">
 <!--'  // 非常一大段的文本,html 输出
}

这里有三个问题

  • Code 码没有使用异常的还是固定的
  • message 信息不是异常抛出来的
  • data 不需要有数据,可以不用返回

为了解决这三个问题,可以这么做,首先我们创建一个基类 abstract class Exception, php artisan make:exception BaseException, 内容如下

php 复制代码
use Symfony\Component\HttpKernel\Exception\HttpException;

abstract  class  BaseException  extends  HttpException
{
	 protected  $code  =  0;
	 public  function  __construct(string  $message  =  '', int  $code  =  0)
    {
 parent::__construct($this->statusCode(), $message  ?:  $this->message, null, [], $code);
    }


 	public  function  statusCode():  int
    {
		 // 对于异常统一返回 500,需要更改可以通过子类修改对应的 code
		 return  500;
    }

 	public  function  render():  array
    {
		 return [
		 'code'  =>  $this->code,
		 'message'  =>  $this->message,
        ];
    }
}

基类创建好之后呢,再来创建子类,因为业务中的错误类型各种各样,所以异常子类也是很多的,但是记住都需要继承 BaseException,还是用上面的 artisan 创建一个失败异常,代码如下

php 复制代码
namespace  App\Exceptions;
use  Exception;
class  FailException  extends  BaseException
{
 //
 protected  $code  =  10001;
 protected  $message  =  'fail';
}

再回到控制中

php 复制代码
public  function  index()
{
     throw  new  FailException();
}

刷新浏览器之后,浏览器会输出正确的错误返回了。很不错,解决了上面的三个问题

json 复制代码
{
 "code": 10001,
 "message": "fail"
}

总结

这个方式多多少少有点黑盒了。对于刚接手的人,如果不去翻代码或者有人来讲解的话,可能多多少有点懵的 😂。但是如果了解了,我认为将 response json 这样输出从业务代码中解构出来还是蛮好的。如果应用的响应数据几乎不变动,个人更喜欢这样的方式去返回,不然控制器大量的 response()->json() 这样的代码看着挺烦人的。如有更好的方法,欢迎讨论。

原文链接: 另辟蹊径!如何在 Laravel 更优雅的响应 JSON 数据

相关推荐
码小凡12 小时前
优雅!用了这两款插件,我成了整个公司代码写得最规范的码农
java·后端
星星电灯猴12 小时前
Charles抓包工具深度解析:如何高效调试HTTPHTTPS请求与API接口
后端
isfox12 小时前
Hadoop 版本进化论:从 1.0 到 2.0,架构革命全解析
大数据·后端
normaling13 小时前
四、go语言指针
后端
yeyong13 小时前
用springboot开发一个snmp采集程序,并最终生成拓扑图 (二)
后端
掉鱼的猫14 小时前
Solon AI 五步构建 RAG 服务:2025 最新 AI + 向量数据库实战
java·redis·后端
HyggeBest14 小时前
Mysql之undo log、redo log、binlog日志篇
后端·mysql
java金融14 小时前
FactoryBean 和BeanFactory的傻傻的总是分不清?
java·后端
独立开阀者_FwtCoder14 小时前
Nginx 部署负载均衡服务全解析
前端·javascript·后端