Reputation: 1881
I'm using Laravel to create a JSON REST API, and it has been quite present so far. However I'm needing to wrap my JSON outputs with a bit of meta status JSON created by say a metaDataController
(or probably model) and I am curious what a good approach to this might be.
For instance, all responses would take on the following format:
{
"meta": {
"status": 200,
"notifications": 2
},
"response": {
//JSON from the route's Controller/Model/etc
}
}
From what I can tell I either need to modify the Laravel Response
defaults and delegate to a metaDataController, or create some sort of Route::any
that merges the two sections of JSON as mentioned in Returning Multiple Laravel Eloquent Models as JSON. Although I always know metaDataController, the other controller is in flux depending on the route.
I'm thinking there must be a way to declare this structure as a default for all routes or a Route::group.
Thanks!
Upvotes: 2
Views: 2903
Reputation: 342
I don't think doing json_decode->json_encode cycle is an acceptable solution (as in Chris answer).
Here is another solution
use Illuminate\Http\Response;
use Illuminate\Http\Request;
Route::filter('apisuccess', function($route, Request $request, Response $response = null) {
$response->setContent(json_encode([
'data' => $response->original,
'meta' => ['somedata': 'value']
]));
});
Then I would attach this filter to my REST API routes.
Edit: another solution (more complex).
use Illuminate\Http\Response;
class ApiResponse extends Response
{
protected $meta;
protected $data;
public function __construct($content = '', $status = 200, $headers = array())
{
parent::__construct([], $status, $headers);
$this->meta = [];
}
public function withMeta($property, $value = null)
{
if (is_array($property))
$this->meta = $property;
else
array_set($this->meta, $property, $value);
return $this;
}
public function withData($data)
{
$this->data = $data;
return $this;
}
public function sendContent()
{
echo json_encode(['success' => true, 'data' => $this->data, 'meta' => $this->meta, 'echo' => ob_get_contents()]);
}
}
Put it as a singleton in IOC container:
$this->app->bindShared('ApiResponse', function() {
return new \Truinject\Http\ApiResponse();
});
Finally, create filter and use it as "before" on your routes:
Route::filter('apiprepare', function(Illuminate\Routing\Route $route, Illuminate\Http\Request $request) {
$data = $route->run();
return App::make('ApiResponse')->withData($data);
});
So we are basically overriding default response class with our own, but still calling the appropriate controller with $route->run() to get the data.
To set meta data, in your controller do something like this:
\App::make('ApiResponse')->withMeta($property, $value);
I've added method "meta" in my base API controller class, which encapsulates this.
Upvotes: 2
Reputation: 58142
You could use the global after filter in app.php
to catch all responses, then reconfigure it however you please:
App::after(function($request, $response)
{
if(is_a($response, 'Illuminate\Http\JsonResponse')) {
$response->setContent(json_encode(array(
'data' => json_decode($response->getContent()),
'foo' => 'bar',
'cat' => 'dog'
)));
}
});
In the above example, you're taking all the existing json data and putting it in a child data element (this would be "response" in your example) then adding foo and bar. So foo, bar and data would be top level json objects.
If you don't like the global positioning, after
is an event sent, so you could also listen to it inside a controller/elsewhere.
Upvotes: 0