在 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 响应的主要内容,如果未能解决你的问题,请参考以下文章

如何在 laravel 中以编程方式即时设置 .env 值

Laravel 5.3 中以 API 为中心的应用程序

Laravel PHP 获取引用

在同一控制器中以其他方法访问会话数据在 laravel 中不起作用

在 PHP 中以编程方式组合图像

如何在 QQuickPaintedItem 中以有效的方式绘制顺序图像