I'm trying to create a middleware which wraps the returned response into a uniform JSON response for my API. This will ensure that all API responses have the same base structure which looks like this:
{
"success": true,
"data": {...}
}
Now this is the middleware I am currently using:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class ApiResponseWrapper
{
/**
* Handle an incoming request.
*
* @param Request $request
*
* @return mixed
*/
public function handle($request, Closure $next)
{
/** @var Response $response */
$response = $next($request);
$original = $response->getOriginalContent();
$content = [
'success' => $response->isSuccessful(),
'data' => $original,
];
$jsonContent = json_encode($content, JsonResponse::DEFAULT_ENCODING_OPTIONS);
$response->setContent($jsonContent);
return $response;
}
}
The two key points of this middleware are:
$original = $response->getOriginalContent();
line gets the content of the response to wrap.json_encode
the $content
with the default encoding options for a JSON response.The problem arises when I want to pass a paginated ResourceCollection
into the middleware. The controller method looks like this:
public function index(): AnonymousResourceCollection
{
/** @var User $user */
$user = auth()->user();
return PostResource::collection($user->posts->paginate(20));
}
Normally, the ->paginate(...)
method does not exist in a Laravel Collection. It is a macro taken from here: gist.github.com/simonhamp/549e8821946e2c40a617c85d2cf5af5e.
If I now hit the endpoint that calls the index()
method, the expected output is something like this:
{
"success": true,
"data": {
"data": [
<PostResource as JSON object>
],
"meta": {...},
"links": {...},
}
}
But what I actually get is this:
{
"success": true,
"data": [
<Post Model as array>
]
}
I thought I might have to call a method to encode the $original
(which is of type Illuminate\Support\Collection
) into a paginated resource collection, but I didn't find one.
All other responses are fine. For example on another controller method I simply return an array or a string and they are wrapped as expected.
Does anyone have an idea what is wrong here?
Edit:
I also tried to add this before the $content
variable is set:
if (method_exists($original, 'toArray')) {
$original = $original->toArray();
}
It results in the same output for ResourceCollections
.
After investigating the variable types and looking through the Laravel API, I found that the easiest solution was to just check if the response is a JsonResponse
and get the rendered pagination data from it:
public function handle($request, Closure $next)
{
/** @var Response $response */
$response = $next($request);
$content = [
'success' => $response->isSuccessful(),
];
if ($response instanceof JsonResponse) {
/** @var JsonResponse $response */
$data = $response->getData(true);
$content = array_merge($content, $data);
} else {
$content['data'] = $response->getOriginalContent();
}
$jsonContent = json_encode($content, JsonResponse::DEFAULT_ENCODING_OPTIONS);
$response->setContent($jsonContent);
return $response;
}
This results in the expected responses for my use case:
{
"success": true,
"data": [
<PostResource as JSON object>
],
"meta": {...},
"links": {...},
}
Also note that I originally specified the data
, meta
and links
keys within the wrapping data
key.
I find the result above a bit prettier because I got around double wrapped data
.
I hope this will help anyone who came across this question!