Consistent REST API Response in Laravel+Dingo - php

I have been developing a set of rest APIs to be exposed for mobile apps. I am following the repository pattern for the development on the Laravel project. How do I implement a presenter and transformer for formatting a constant JSON output throughout the set of all my APIs?
For example I have the following controller for login
public function authenticate()
{
$request = Request::all();
try {
// If authenticated, issue JWT token
//Showing a dummy response
return $token;
} catch (ValidatorException $e) {
return Response::json([
'error' =>true,
'message' =>$e->getMessageBag()
]);
}
}
Now where does a transformer and presenter come into the picture? I know that both are used to format the output by converting the db object and produce a formatted JSON so that it remains uniform across my APIs.
The dingo API and fractal or even the framework (L5 repository) don't provide detailed documentation and I can't find any tutorials on this.
I have created the following presenter and transformer for another API which gives the list of products
namespace App\Api\V1\Transformers;
use App\Entities\Product;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract {
public function transform(\Product $product)
{
return [
'id' => (int) $product->products_id
];
}
}
Presenter
<?php
namespace App\Api\V1\Presenters;
use App\Api\V1\Transformers\ProductTransformer;
use Prettus\Repository\Presenter\FractalPresenter;
/**
* Class ProductPresenter
*
* #package namespace App\Presenters;
*/
class ProductPresenter extends FractalPresenter
{
/**
* Transformer
*
* #return \League\Fractal\TransformerAbstract
*/
public function getTransformer()
{
return new UserTransformer();
}
}
How will I set the presenter in the controller and respond back? Tried
$this->repository->setPresenter("App\\Presenter\\PostPresenter");
But it doesn't seems to work and the doc doesn't shows the complete steps.
In the above example, how can I make a template for an error response which I can use throughout my APIs and how will I pass my error exceptions to it?
It seems like presenter and transformer can be used to convert database objects into presentable JSON and not anything else. Is that right?
How do you use a presenter and a transformer for a success response and an error response? By passing exceptions, instead of DB objects to the transformer?

I had the same exact problem and here is how I used dingo with transformer
Controller:
public function update(Request $request)
{
$bus = new CommandBus([
$this->commandHandlerMiddleware
]);
$agency = $bus->handle(
new UpdateAgencyCommand($request->user()->getId(), $request->route('id'), $request->only('name'))
);
return $this->response->item($agency, new AgencyTransformer());
}
Transformer:
class AgencyTransformer extends TransformerAbstract
{
public function transform(AgencyEntity $agencyEntity)
{
return [
'id' => (int) $agencyEntity->getId(),
'name' => $agencyEntity->getName(),
];
}
}
and this is how I handle errors:
throw new UpdateResourceFailedException('Could not update agency.', $this->agencyUpdateValidator->errors());

I just now see your similar question here as well. So see my answer on your other question here: https://stackoverflow.com/a/34430595/429719.
From the other question I derived you're using Dingo, so use that as a structured response class. Make sure you're controller extends from Dingo and then you can just return items and collections in a structured way like:
return $this->response->item($user, new UserTransformer);
return $this->response->collection($users, new UserTransformer);
If you want a nice error handling look for the docs here: https://github.com/dingo/api/wiki/Errors-And-Error-Responses
Basically you can throw any of the core exceptions or a few custom Dingo ones. The Dingo layer will catch them and returns a structured JSON response.
As per the Dingo docs:
throw new Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException('Nope, no entry today!');
Will generate:
{
"message": "Nope, no entry today!",
"status_code": 403
}

You can try use,[Midresapi]: https://github.com/oktorino/midresapi. this will return consistens success or failled response, work in laravel 7 & 8 , Handling validation response, handling 500 response:
$users=\App\User::latest()->limit(2)->get();
return response($users);
#or
return fractal()
->collection($users)
->transformWith(new \App\Transformers\UserTransformer)
->toArray();
Response :
{
"status_code": 200,
"success": true,
"message": "ok",
"data": [
{
"username": "dany",
"email": "jancuk#sabarbanget.com"
},
{
"username": "scc-client-5150",
"email": "dancuk#gmail.com"
}
]
}

Fractal is fully documented here: http://fractal.thephpleague.com/
There is an excellent book that I regularly read from the Phil Sturgeon https://leanpub.com/build-apis-you-wont-hate You can find most of the books code available in github https://github.com/philsturgeon/build-apis-you-wont-hate. You can find really nice examples of Fractal in there.
I would create an Api Controller and extend it from my Controllers.In there there should be all the respond functions (respondWithError, respondWithArray etc.)
Tranformers are transforming objects in a consistent json format so that all your endpoints return the same thing for each entity.
Dont really have an answer on this
There are enough examples in Fractal documentation.

Related

Custom error code for laravel form request

I have an endpoint I would like to post a bulk update request like this:
{
"resources": [
{
"id": 5,
"name": "ABC"
}
]
}
I am trying to prevent someone updating a resource they are not the owner of. I can create the following rule to prevent this:
'resources.*.id' => ['required', 'exists:resources,id,team_id,' . $this->team()->id],
I'd like to customize the error code so that I receive a 403 error code when this rule is violated. All other rules I am happy with the normal 422 error code.
I am aware I can customize the message in the messages() method. Is there something similar so I can return my own error code? At the moment I just get the standard 422 code.
I am also aware I could load all the team resources in the authorize() method, but I wonder if there is a better way?
Thanks.
Maybe I'm a bit late, but hopefully, my answer can still help you or someone in the future.
You can see the Form Request Validation Class you created extends from Illuminate\Foundation\Http\FormRequest. Then if you look at the source code on the Laravel Framework, there is a function to handle failed validation attempts.
So the solution, you can override the default failedValidation function in the Form Request Validation Class you created. Here is a sample code you can use:
// ./app/Http/Requests/UpdateFooBarRequest.php
...
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Response;
use Illuminate\Validation\ValidationException;
class UpdateFooBarRequest extends FormRequest
{
...
/**
* Handle a failed validation attempt.
*
* #param \Illuminate\Contracts\Validation\Validator $validator
* #return void
*
* #throws \Illuminate\Validation\ValidationException
*/
protected function failedValidation(Validator $validator)
{
throw (new ValidationException($validator))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl())
->status(Response::HTTP_FORBIDDEN);
}
}
Credit: Discussion from Laracasts - How to make a custom FormRequest error response in Laravel 5.5
If you are extending FormRequest, then you can add your custom messages in the messages function,
public function messages() {
$messages = [];
foreach ($this->get('resources') as $key => $val) {
$messages["resources.$key.id.exists"] = "The data :attribute"; // will send 'The data exists'
$messages["resources.$key.id.required"] = "The data is :attribute";
}
return $messages;
}
#Edit: 2nd Part of Question
If the authorize method returns false, an HTTP response with a 403 status code will automatically be returned and your controller method will not execute.
Source
public function authorize()
{
return false;
}
Then it will send 403 requests instead of 422.

Wrapping JSON response in an abstracted way in PHP / Laravel

I am making a REST API that will return different JSON responses depending on which type of User is making the call.
There is a single endpoint: example.com/api/v1/collect that uses Laravel's API authentication to get the User model with $user = auth()->guard('api')->user();.
Each User will belong to a Type.
If User 1 (type_id 1) makes the call, the response will look like:
{
"example": 1,
"success": true,
"data" : [
...
]
}
If User 2 (type_id 2) makes the call, the response can be different, depending on the user's type. It could look like:
{
"example": 2,
"response" : [
...
],
"custom": "string",
"success": 200
}
The ... is the data that we are sending back (for example a list of Post titles) and it will always be the same, but the "envelope" (or wrapper) around it would be specific to each user (or type of user).
So far, I've found two solutions to wrap that ... in an abstracted way:
Solution 1: Using Laravel Blade
// Api\V1\ApiController.php
$data = $user->posts->pluck('title');
// Each type of user will have a different blade filename
// There could be around a 100 types which will result in a 100 blade files
// The filename is stored in the database
$filename = $user->type->filename; // returns 'customBladeTemplate'
// Return a JSON response after passing the $data to the view
return response()->json([
view($filename, compact('data'))->render(),
]);
Using a blade file for each type of user allows me to wrap the data like this:
// resources/views/customBladeTemplate.blade.php
// This filename has to match the one in the database column
{
"example": 1,
"success": true,
"data" : [
{!! $data !!}
]
}
That will output a JSON response for the User 1 (example 1)
Solution 2: Using Laravel response macros
// Api\V1\ApiController.php
$data = $user->posts->pluck('title');
// Each type of user will have a different macro name
// There could be around a 100 types which will result in a 100 different macros
// The macro name is stored in the database
$macroName = $user->type->macro_name; // returns 'customMacroName'
return response()->{macroName}($data);
Creating a Macro for each type of user, using the macro name from the DB:
// App\Providers\AppServiceProvider.php
use Illuminate\Http\Response;
public function boot()
{
Response::macro('customMacroName', function ($data) {
return Response::json([
'example' => 2,
'response' => $data,
'custom' => 'string',
'success' => 200,
]);
});
}
That macro will output a JSON response for the User 2 (example 2)
Both options work fine but I am still wondering:
Is there another (possibly better) way to do it?
Are those two solutions valid or can they be enhanced?
Which of those two solutions seem to be better and why?
Edit: The $data is not actually coming from an eloquent model, it is rather from a serialized JSON column (JSON casting) - which means I can't use the Laravel API resources
If you are looking for the response formatting you should go with the Laravel API Resources
Based on your requirement(data formate different for two type of users), you can create two different Api Resource classes.
AdminResource & UserResource.
Here you have more flixibility on controlling fields or orgnizing data.
Here is how you can define the resource class:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class UserResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
And you can use this as:
use App\User;
use App\Http\Resources\UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
If you would like to include with condition check in with user type, you can create a common function called renderJson($userType, $data) and place this in your parent class or can wrap with traits, all depends on your application architecture.
Here you can find laravel documentation for API Resource: https://laravel.com/docs/5.8/eloquent-resources
Edited:
With Laravel API Resource, you not only parse the modal object, you can parse any arrayble object.
Essentially they are just simple objects with one very important job
to do — transform your objects (interesting I said objects and not
models). To do this out of the box, all you have to do is instantiate
the Resource (collection or individual) with an Arrayable object. If
you did nothing else but generate a standard Resource and pass in an
Arrayable object the Resource would transform that object
automatically, and because Models are Arrayable this is where I got
caught out because if you create a resource collection and instantiate
it with a collection of models then the models get toArray'd and not
their corresponding resource.
Src: https://medium.com/#dinotedesco/laravel-api-resources-what-if-you-want-to-manipulate-your-models-before-transformation-8982846ad22c
So in your case if you can just collect() the json data and pass to api resource.
You could use middlewares to change what the response looks like.
With middleware you could change the response after the execution of your regular code, without having to take this into account in the controller itself. Using the below code you modify the response AFTER it has been executed.
<?php
namespace App\Http\Middleware;
use Closure;
class AfterMiddleware
{
public function handle($request, Closure $next)
{
// Calls the controller and processes the request.
$response = $next($request);
// Here you can retrieve the user and modify the response however you want.
// Some example code:
$user = Auth::user();
if ($user->type == 1) {
... //Change response for user type 1
}
if ($user->type == 2) {
... //Change response for user type 2
}
// Etc...
return $response;
}
}
Reference: https://laravel.com/docs/5.8/middleware
Depends on how different the responses are from each other. I'm inclined to take inventory of each type's common features and build a response array as appropriate. This could be done in the controller or a helper function and then returned using Laravel's JSON response type.
$response = [];
// results common to all types
$response['example'] = $example;
$response['success'] = $success;
// customized results for specific types
if (in_array($type, [1, 3, 4, 5, ...])) {
$response['data'] = $dotdotdot;
}
if (in_array($type, [2, 6, 7, 8, ...])) {
$response['response'] = $dotdotdot;
$response['custom'] = $custom;
}
return response()->json($response);
I don't know if this is what you are looking for. I had something similiar a few months ago and fixed it with json files. As json is amazingly fast and you can create thousands of types.
Sorry for my bad english i will fix it after the weekend :-)
Let's get started.
First the user logs in using laravel passport or api routes.
Second the api calls a controller.(class). I will create a class based on your info.
let's say the api calls the ApiController and the method handle
use Illuminate\Http\Request;
class ApiController
{
public function __construct()
{
}
/**
* Handle the incoming request
*
* #param Request $request
*/
public function handle(Request $request)
{
//first let's find the correct format
$type = $requets->user()->type; //lets say type_1
$config = $this->getUserType($type);
//i don't know where you data comes from but let's say $data is your data.
$data = json_encode(['some' => "data", 'to' => "return"]);
//lets fill the data
$response = $this->fillDataInConfig($data, $config);
return response()->json($response);
}
/**
* Find the user type config by type name
*
* #param string $type
* #return object
*/
private function getUserType(string $type) : string
{
//let store them in the local storage
$config = \Storage::disk('local')->get("api_types/$type.json");
return json_decode($config);
}
/**
* Fill the data
*
* #param mixed $data
* #param object $config
* #return object
*/
private function fillDataInConfig($data, object $config) : object
{
//as for your example. The reusl//
// {
// "example": 2,
// "response" : *responseData*, <===== where the response should be
// "custom": "string",
// "success": 200
// }
foreach($config as $index => $value){
if($value === '*responseData*'){
$config->{$idnex} = $data;
}
}
//the data is filled in the response index
return $config;
}
}

Yii2 RESTful: Content Negotiation languages not work

I implemented a structure of API Rest with the models in Yii2. Everything works great for actions (index, create, update etc ...) and methods (GET, POST, PUT etc ..) but I have a problem with the ContentNegotiator class.
Specifically, if I pass as a parameter of GET the language in which the response is to be translated, this is ignored.
According to the documentation for setting the language of response we need to set allowed languages of ContentNegotiator (look at my behaviors()) and make a request like this:
http://localhost/api/v1/users?_lang=it-IT
But the response continues to be in English. Why??? Nothing against the English =)
This is my ActiveController child class that extend from yii\rest\Controller.
use yii\rest\ActiveController;
use yii\filters\VerbFilter;
class AActiveController extends ActiveController
{
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['verbFilter'] = [
'class' => VerbFilter::className(),
'actions' => $this->verbs(),
];
$behaviors['contentNegotiator']['languages'] = [
'en-EN',
'it-IT',
'de-DE',
'ru-RU',
];
return $behaviors;
}
...
N.B.: I debug through yii\filters\ContentNegotiator class of the framework and at this point the app language is set correctly but the response is always in English.
negotiate() public method
public function negotiate()
{
$request = $this->request ?: Yii::$app->getRequest();
$response = $this->response ?: Yii::$app->getResponse();
if (!empty($this->formats)) {
$this->negotiateContentType($request, $response);
}
if (!empty($this->languages)) {
Yii::$app->language = $this->negotiateLanguage($request);
}
debug(Yii::$app->language); // result OK!: it-IT
}
Looks like some build-in errors not translated, for example yii\rest\Action:103 throws throw new NotFoundHttpException("Object not found: $id") its not translated. You have different ways to solve this problem:
Good way. Extend this Action and throw correct exception with translate
Bad way. Edit framework file itself.
Best way. Create patch to framework and send to maintainers.
For more information about i18n see documentation.

Model binding in nested resources in Laravel

I am building a RESTful api in Laravel, with my front end app in Angular.
I have a number of models / controllers and routes. I am struggling to get to grips with creating restful route for nested resources.
For example, my route to show a list of clients is simple:
Route::resource('clients', 'ClientsController');
A client can have many campaigns:
class Client extends Model
{
public function campaigns()
{
return $this->hasMany('App\Campaign');
}
}
I do not need a route to show all campaigns, but I do need a route to show all campaigns based on a client.
Route::resource('clients.campaigns', 'CampaignsController');
My intention was for the endpoint requested by the Angular app to be:
myapp/api/clients/6/campaigns
Where '6' is the client ID. This would return a list of campaigns that belong to client with id 6.
In my index method, I am trying to use implicit model binding to get this client Id, but I always get an empty result set:
class CampaignsController extends ApiController
{
public function index(Client $client)
{
$campaigns = Campaign::where('client_id', $client->id)->get();
if (!count($campaigns)) {
Log::warning('Campaigns list is empty');
return $this->respondNotFound('No Campaigns Found');
}
try {
return $this->respond([
'data' => $this->campaignTransformer->transformCollection($campaigns->all())
]);
} catch (\Exception $e) {
Logging::logException($e, 'API error showing campaigns list');
return $this->respondInternalError('An internal error occurred');
}
}
}
Clearly my route isn't binding a client - a var_dump on $client shows this.
Where am I going wrong?
For anyone who comes across this issue - Laravel was injecting the client_id for me.
Therefore index method becomes:
public function index($client_id)
{
$campaigns = Campaign::where('client_id', $client_id)->get();
}

Laravel nesting all json responses

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!
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).
Create a custom Response class:
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.
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.

Categories