Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make custom PSR-7 ResponseInterface to reduce boilerplate?

Tags:

php

psr-7

I'm implementing a service using Slim 4 framework which will pretty much always be returning JSON responses. I'm trying to keep all of the responses uniform with a structure similar to this:

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

In the most basic format, this is the code that I would need to do to make responses like this:


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);
}

Right now, I've been using a basic helper class that essentially wraps most of that boilerplate into a few static functions, so I can write a response like this:

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

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

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

However, now I'm encountering an issue where I'd like to make the responses slightly more complex which would require additional dependencies, such as custom serializer classes. The naive option would be to continue to pass additional dependencies to APIResponse::success($response, $serializer, ..., $data), but that's obviously bad and not a good long-term option.

Another option I thought of would be to make an APIResponseFactory, which would take in any dependencies in the constructor and be filled via PHP-DI. That would be a little cleaner, but every route handler would need to have the factory injected and I'd still need to manually pass $response every time.

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

So, what I'm considering now is trying to build a custom class that would implement ResponseInterface, thus allowing me to build in the boilerplate helpers into every request handler automatically. I was looking over the current PSR7 ResponseInterface implementation that's being used in my project, and the code comments mentioned that the class should never be extended, and suggested using a Decorator Pattern. So, this is a basic pseudocode implementation I made for my current idea.

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;
    }
}

Thus, I would (hopefully) be able to return responses like this:

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

My actual questions: Is this the proper way to implement custom helper methods for PSR-7 Response handlers? Is there a better way? Is writing helper functions like this bad practice? PSR-7 interfaces seem to be, for lack of a better description, low-levelish and verbose, which makes me worry that writing a wrapper like this somehow goes against the intent of the standard. Are there any other ways to follow the standard but reduce boilerplate and keep responses uniform?

like image 626
404 Not Found Avatar asked Nov 12 '21 19:11

404 Not Found


Video Answer


2 Answers

A 'nice' controller actions return $response->success($data); can be done via

  • https://php-di.org/doc/definition-overriding.html#decorators
  • https://php-di.org/doc/best-practices.html#using-libraries - interface with a implementation assotiation

This a bad practice

  • The {"status":"", "data":[], "messages": []} format may change
  • Appearing file, stream, etc responses

Forgot about the ResponseInterface $response argument, how was it done with the array $args argument.

Inject the response factory to each controller, so your actions would look like

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

    return $this->responseFactory->createJson($data);
    // OR
    //return $this->responseFactory->createSomethingElse();
}
like image 176
cetver Avatar answered Oct 21 '22 21:10

cetver


You've written multiple times that you'd like to avoid injecting the dependency for every controller, I don't think you should avoid using dependency injection since it's not that much work load and it comes with a lot of advantages such as:

  • testability You can mock all the dependencies at truly test everything
  • modularity Thus way your class is isolated from others and does not depend on global and not clear dependencies
  • maintainability You have a clear definition of what that class needs to work, and I assure you that's going to be essential a few years from now

A dependency injection container really helps with the amount of code written to apply di.

My controllers usually end up looking something like this:

    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
            );
        }

Your presenter can have it's dependencies too and it's all kept clean and tidy.

The Slim 4 routing setup will be something like:

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

This way it's DI Container manner to inject everything and routing page is clean and tidy too!

like image 1
Davide Bicego Avatar answered Oct 21 '22 22:10

Davide Bicego