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\Handler中 render方法處理,格式化,處理邏輯如下:
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);
}
資源搜尋網站大全 http://www.szhdn.com 廣州VI設計公司https://www.houdianzi.com
更好的方式
上面的這種做法有一個弊端,如果某些模組下想要的資料格式返回不一樣,對應異常情況的處理會比較麻煩。因為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 功能,格式化統一的資料輸出。