在 PHP / Laravel 中以抽象的方式包装 JSON 响应
Posted
技术标签:
【中文标题】在 PHP / Laravel 中以抽象的方式包装 JSON 响应【英文标题】:Wrapping JSON response in an abstracted way in PHP / Laravel 【发布时间】:2019-10-23 22:13:19 【问题描述】:我正在制作一个 REST API,它将根据正在调用的用户类型返回不同的 JSON 响应。
有一个端点:example.com/api/v1/collect
,它使用Laravel's API authentication 来获取带有$user = auth()->guard('api')->user();
的用户模型。
每个User
都将属于一个Type
。
如果User 1
(type_id 1) 拨打电话,响应将如下所示:
"example": 1,
"success": true,
"data" : [
...
]
如果User 2
(type_id 2) 拨打电话,响应可能会有所不同,具体取决于用户的类型。它可能看起来像:
"example": 2,
"response" : [
...
],
"custom": "string",
"success": 200
...
是我们发回的数据(例如帖子标题列表),它始终是相同的,但它周围的“信封”(或包装)将特定于每个用户(或用户类型)。
到目前为止,我已经找到了两种解决方案来以抽象的方式包装 ...
:
解决方案 1:使用Laravel Blade
// Api\V1\ApiController.php
$data = $user->posts->pluck('title');
// Each type of user will have a different blade filename
// There could be around a 100 types which will result in a 100 blade files
// The filename is stored in the database
$filename = $user->type->filename; // returns 'customBladeTemplate'
// Return a JSON response after passing the $data to the view
return response()->json([
view($filename, compact('data'))->render(),
]);
为每种类型的用户使用刀片文件允许我像这样包装数据:
// resources/views/customBladeTemplate.blade.php
// This filename has to match the one in the database column
"example": 1,
"success": true,
"data" : [
!! $data !!
]
这将为用户 1 输出 JSON 响应(示例 1)
解决方案 2:使用Laravel response macros
// Api\V1\ApiController.php
$data = $user->posts->pluck('title');
// Each type of user will have a different macro name
// There could be around a 100 types which will result in a 100 different macros
// The macro name is stored in the database
$macroName = $user->type->macro_name; // returns 'customMacroName'
return response()->macroName($data);
使用数据库中的宏名称为每种类型的用户创建一个宏:
// App\Providers\AppServiceProvider.php
use Illuminate\Http\Response;
public function boot()
Response::macro('customMacroName', function ($data)
return Response::json([
'example' => 2,
'response' => $data,
'custom' => 'string',
'success' => 200,
]);
);
该宏将为用户 2 输出 JSON 响应(示例 2)
这两个选项都很好,但我仍然想知道:
还有其他(可能更好)方法吗? 这两个解决方案是否有效或可以增强? 这两种解决方案中哪一种似乎更好,为什么?编辑: $data
实际上并不是来自一个雄辩的模型,而是来自一个序列化的 JSON 列 (JSON casting) - 这意味着我不能使用 Laravel API resources
【问题讨论】:
一开始为什么要这样做?提供基于谁请求的结构似乎有点不自然(而且不是非常 RESTfull)。听起来也像是一场文档噩梦…… 我已经简化了可读性的问题,但该应用程序实际上是一个 iPaaS(集成平台),它将来自外部 API(即 Magento)的数据映射到其他 API(即 QuickBooks)。每个 API 都有不同的 JSON 输出/输入,可以是 REST、SOAP 或 WebHooks。我必须使 JSON 输出适应每个连接器,同时尽量避免对有效负载进行硬编码,而是尽可能保持“抽象”。这就是为什么我首先选择刀片模板,但我同意,它并不是真正的 RESTful!我只是想不出任何其他解决方案。 【参考方案1】:如果您正在寻找响应格式,您应该使用Laravel API Resources
根据您的需求(两种用户的数据格式不同),您可以创建两个不同的 Api Resource 类。
AdminResource
& UserResource
.
在这里,您可以更灵活地控制字段或组织数据。
下面是定义资源类的方法:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class UserResource extends Resource
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
您可以将其用作:
use App\User;
use App\Http\Resources\UserResource;
Route::get('/user', function ()
return new UserResource(User::find(1));
);
如果您想在用户类型中包含条件检查,您可以创建一个名为 renderJson($userType, $data)
的通用函数并将其放置在您的父类中,或者可以使用特征包装,这一切都取决于您的应用程序架构。
在这里你可以找到 API 资源的 laravel 文档:https://laravel.com/docs/5.8/eloquent-resources
已编辑:
使用Laravel API Resource,你不仅可以解析模态对象,还可以解析任意数组对象。
本质上它们只是简单的对象,具有一项非常重要的工作 to do — 变换你的对象(有趣的是我说的是对象而不是 楷模)。要开箱即用,您所要做的就是实例化 具有 Arrayable 对象的资源(集合或个人)。如果 你除了生成一个标准资源并传入一个 Resource 将转换该对象的 Arrayable 对象 自动,因为模型是可排列的,这就是我得到的地方 因为如果你创建一个资源集合并实例化 它带有一组模型,然后模型得到 toArray'd 而不是 他们相应的资源。 源:https://medium.com/@dinotedesco/laravel-api-resources-what-if-you-want-to-manipulate-your-models-before-transformation-8982846ad22c
所以在你的情况下,如果你可以 collect()
json 数据并传递给 api 资源。
【讨论】:
$data
实际上并不是来自一个雄辩的模型,而是来自一个序列化的 JSON 列——这意味着我不能使用 Laravel API 资源。不过,这是一个很好的电话。我已经编辑了问题。
@PhilMarc 在这里,您可以使用 API 资源来解析任何 Arrable 对象,无论是模型还是任何其他标准对象。所以我觉得,你还是可以考虑这个的。【参考方案2】:
您可以使用middlewares 来更改响应的外观。
使用中间件,您可以在执行常规代码后更改响应,而无需在控制器本身中考虑这一点。使用下面的代码,您可以在响应执行后修改响应。
<?php
namespace App\Http\Middleware;
use Closure;
class AfterMiddleware
public function handle($request, Closure $next)
// Calls the controller and processes the request.
$response = $next($request);
// Here you can retrieve the user and modify the response however you want.
// Some example code:
$user = Auth::user();
if ($user->type == 1)
... //Change response for user type 1
if ($user->type == 2)
... //Change response for user type 2
// Etc...
return $response;
参考:https://laravel.com/docs/5.8/middleware
【讨论】:
这是一个很好的解决方案。但它不包括 JSON 响应的包装,因此它与第二种解决方案非常相似:每个用户类型有一个宏 / 有一个if
语句。
这样你就不需要把那个检查放在控制器中了。所以如果在某个时候系统有更多的路由,你只需添加这个中间件就可以了。【参考方案3】:
取决于响应之间的差异程度。我倾向于盘点每种类型的共同特征,并酌情构建一个响应数组。这可以在控制器或辅助函数中完成,然后使用 Laravel 的 JSON 响应类型返回。
$response = [];
// results common to all types
$response['example'] = $example;
$response['success'] = $success;
// customized results for specific types
if (in_array($type, [1, 3, 4, 5, ...]))
$response['data'] = $dotdotdot;
if (in_array($type, [2, 6, 7, 8, ...]))
$response['response'] = $dotdotdot;
$response['custom'] = $custom;
return response()->json($response);
【讨论】:
【参考方案4】:我不知道这是否是你要找的。几个月前我有类似的东西,并用 json 文件修复了它。由于 json 速度惊人,您可以创建数千种类型。
抱歉我的英语不好,我会在周末后修复它:-)
让我们开始吧。
首先用户使用 laravel 护照或 api 路由登录。 其次,api 调用一个控制器。(类)。我会根据你的信息创建一个类。
假设 api 调用 ApiController
和方法 handle
use Illuminate\Http\Request;
class ApiController
public function __construct()
/**
* Handle the incoming request
*
* @param Request $request
*/
public function handle(Request $request)
//first let's find the correct format
$type = $requets->user()->type; //lets say type_1
$config = $this->getUserType($type);
//i don't know where you data comes from but let's say $data is your data.
$data = json_encode(['some' => "data", 'to' => "return"]);
//lets fill the data
$response = $this->fillDataInConfig($data, $config);
return response()->json($response);
/**
* Find the user type config by type name
*
* @param string $type
* @return object
*/
private function getUserType(string $type) : string
//let store them in the local storage
$config = \Storage::disk('local')->get("api_types/$type.json");
return json_decode($config);
/**
* Fill the data
*
* @param mixed $data
* @param object $config
* @return object
*/
private function fillDataInConfig($data, object $config) : object
//as for your example. The reusl//
//
// "example": 2,
// "response" : *responseData*, <===== where the response should be
// "custom": "string",
// "success": 200
//
foreach($config as $index => $value)
if($value === '*responseData*')
$config->$idnex = $data;
//the data is filled in the response index
return $config;
【讨论】:
以上是关于在 PHP / Laravel 中以抽象的方式包装 JSON 响应的主要内容,如果未能解决你的问题,请参考以下文章