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?
A 'nice' controller actions return $response->success($data);
can be done via
This a bad practice
{"status":"", "data":[], "messages": []}
format may changeForgot 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();
}
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:
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With