9

我正在使用 Slim 4 框架实现一项服务,该框架几乎总是会返回 JSON 响应。我正在尝试使用类似于以下结构的所有响应保持统一:

{
    "status": "success",
    "data": {"foo": "bar"} // the actual data relevant to the request
    "messages": []
}

在最基本的格式中,这是我做出这样的响应所需的代码:


public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface {
    
    // Do something 

    $response
        ->getBody()
        ->write(json_encode([
            'status' => 'success',
            'data' => [
                'foo' => 'bar'
            ],
            'messages' => [],
        ]));
    return $response
        ->withHeader('Content-Type', 'application/json')
        ->withStatus(200);
}

现在,我一直在使用一个基本的帮助类,它基本上将大部分样板文件包装成几个静态函数,所以我可以写一个这样的响应:

public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface {
    
    // Do something 

    $data = ['foo' => 'bar'];

    return APIResponse::success($response, $data)->into();
}

但是,现在我遇到了一个问题,我想让响应稍微复杂一些,这需要额外的依赖项,例如自定义序列化程序类。天真的选择是继续将额外的依赖项传递给APIResponse::success($response, $serializer, ..., $data),但这显然很糟糕,而且不是一个好的长期选择。

我想到的另一个选择是制作一个APIResponseFactory,它将接受构造函数中的任何依赖项并通过 PHP-DI 填充。那会更干净一些,但是每个路由处理程序都需要注入工厂,而且我仍然需要$response每次都手动通过。

return $responseFactory->success($response, $data);

所以,我现在正在考虑的是尝试构建一个可以实现的自定义类ResponseInterface,从而允许我将样板助手自动构建到每个请求处理程序中。我正在查看我的项目中正在使用的当前 PSR7 ResponseInterface 实现,代码注释提到该类永远不应扩展,并建议使用装饰器模式。因此,这是我为当前想法制作的基本伪代码实现。

class MyCustomResponse implements ResponseInterface {

    private $serializer; 

    private $actualResponse;

    // any other dependencies

    public function __construct(ResponseInterface $actualResponse, Serializer $serializer /*, other dependencies */) {
        $this->actualResponse = $actualResponse;
        $this->serializer = $serializer;
    }

    // Use this class as a decorator and pass all ResponseInterface calls to the external implementation
    // EDIT: It looks like I can't use `__call` to fulfill the interface, so I'd need to manually define to functions, but you get the gist. 
    public function __call($name, $args) {
        return $this->actualResponse->$name(...$args);
    }

    public function success($data) {
        $this->actualResponse
            ->getBody()
            ->write($this->serializer->serialize([
                'status' => 'success',
                'data' => $data,
                'messages' => [],
            ]));
        $this->actualResponse
            ->withHeader('Content-Type', 'application/json')
            ->withStatus(200);
        return $this;
    }
}

因此,我(希望)能够返回这样的响应:

public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface {
    $data = ['foo' => 'bar'];
    return $response->success($data);
}

我的实际问题:这是为 PSR-7 响应处理程序实现自定义帮助方法的正确方法吗?有没有更好的办法?编写这样的辅助函数是不好的做法吗?由于缺乏更好的描述,PSR-7 接口似乎是低级和冗长的,这让我担心编写这样的包装器会以某种方式违背标准的意图。是否有其他方法可以遵循标准但减少样板并保持响应统一?

4

2 回答 2

2

一个“不错”的控制器动作return $response->success($data);可以通过

这是一个不好的做法

  • {"status":"", "data":[], "messages": []}格式可能会改变
  • 出现文件、流等响应

忘记了ResponseInterface $response争论,争论是如何完成的array $args

将响应工厂注入每个控制器,因此您的操作看起来像

public function __invoke(ServerRequestInterface $request): ResponseInterface 
{
    // Do something 

    return $this->responseFactory->createJson($data);
    // OR
    //return $this->responseFactory->createSomethingElse();
}
于 2021-11-17T21:46:03.953 回答
1

您已经多次写过您希望避免为每个控制器注入依赖项,我认为您不应该避免使用依赖项注入,因为它的工作量并不大,而且它具有很多优点,例如:

  • 可测试性您可以在真正测试所有内容时模拟所有依赖项
  • 模块化因此,您的类与其他类隔离,不依赖于全局且不明确的依赖关系
  • 可维护性你对这个类需要做什么有一个明确的定义,我向你保证,几年后这将是必不可少的

依赖注入容器确实有助于减少为应用 di 编写的代码量。

我的控制器通常最终看起来像这样:

    class MyController implements RequestHandlerInterface
    {
        private Presenter $presenter;
       
        public function __construct(
            Presenter $presenter
        ) {
            $this->presenter = $presenter;
        }
    
        public function handle(ServerRequestInterface $request): ResponseInterface
        {
            return $this->presenter->present(
                200, 
                [
                    'status' => 'success',
                    'data' => $data,
                    'messages' => [],
                ],
                //other presenter arguments here such as a class instance that defines a standard Metadata object to be included in all Responses
            );
        }

您的演示者也可以拥有它的依赖项,并且保持干净整洁。

Slim 4 路由设置将类似于:

$app->get('/route_name', MyController::class);

这样注入一切都是DI Container方式,路由页面也干净整洁!

于 2021-11-29T22:50:29.150 回答