Dingo API remove "data" envelope - laravel

is it there an easy way to remove the "data" envelope from the Dingo API response.
When I use this Transformer to transform user models:
class UserTransformer extends EloquentModelTransformer
{
/**
* List of resources possible to include
*
* #var array
*/
protected $availableIncludes = [
'roles'
];
protected $defaultIncludes = [
'roles'
];
public function transform($model)
{
if(! $model instanceof User)
throw new InvalidArgumentException($model);
return [
'id' => $model->id,
'name' => $model->name,
'email' => $model->email
];
}
/**
* Include Roles
*
* #param User $user
* #return \League\Fractal\Resource\Item
*/
public function includeRoles(User $user)
{
$roles = $user->roles;
return $this->collection($roles, new RoleTransformer());
}
I get this response:
{
data : [
"id": 102,
"name": "Simo",
"email": "mail#outlook.com",
"roles": {
"data": [
{
"id": 1
"name": "admin"
}
]
}
}
]
}
I read some articles about RESTful APIs and a lot of them stated that such enveloped responses arent very modern (You should use the HTTP Header instead).
How can I disable this behaviour at least for the includes?
Thank you

For those who fall on this later and as I had really hard time to make it, I'd like to share how I made it working in my API :
1) Create a Custom Serializer, NoDataArraySerializer.php :
namespace App\Api\V1\Serializers;
use League\Fractal\Serializer\ArraySerializer;
class NoDataArraySerializer extends ArraySerializer
{
/**
* Serialize a collection.
*/
public function collection($resourceKey, array $data)
{
return ($resourceKey) ? [ $resourceKey => $data ] : $data;
}
/**
* Serialize an item.
*/
public function item($resourceKey, array $data)
{
return ($resourceKey) ? [ $resourceKey => $data ] : $data;
}
}
2) Set new the Serializer. In bootstrap/app.php, add :
$app['Dingo\Api\Transformer\Factory']->setAdapter(function ($app) {
$fractal = new League\Fractal\Manager;
$fractal->setSerializer(new App\Api\V1\Serializers\NoDataArraySerializer);
return new Dingo\Api\Transformer\Adapter\Fractal($fractal);
});
That's it.
Now, in your UserController (for instance), you can use it like this :
namespace App\Api\V1\Controllers;
use App\Api\V1\Models\User;
use App\Api\V1\Transformers\UserTransformer;
class UserController extends Controller
{
public function index()
{
$items = User::all();
return $this->response->collection($items, new UserTransformer());
}
}
And the response will look like :
[
{
"user_id": 1,
...
},
{
"user_id": 2,
...
}
]
Or, I you want to add an enveloppe, you just need to set the resource key in the Controller. Replace :
return $this->response->collection($items, new UserTransformer());
by
return $this->response->collection($items, new UserTransformer(), ['key' => 'users']);
And the response will look like :
{
"users": [
{
"user_id": 1,
...
},
{
"user_id": 2,
...
}
]
}

One addition to the solution of YouHieng. The preferred way to register the NoDataArraySerializer in Laravel 5.3 and above is to write a custom ServiceProvider and add the logic into the boot() method and not the bootstrap/app.php file.
For Example:
php artisan make:provider DingoSerializerProvider
Then:
public function boot(){
$this->app['Dingo\Api\Transformer\Factory']->setAdapter(function ($app) {
$fractal = new League\Fractal\Manager;
$fractal->setSerializer(new App\Http\Serializers\NoDataArraySerializer());
return new Dingo\Api\Transformer\Adapter\Fractal($fractal);
});
}

Have a look to http://fractal.thephpleague.com/serializers/#arrayserializer. They explain exactly what to do when
Sometimes people want to remove that 'data' namespace for items

Related

Laravel - transform field name with uppercase capital in json response

I'm working with Laravel 6.x, I have an "Item" class like :
Schema::create('items', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('type');
$table->string('subtype');
});
In a controller, I have a route for get all items and return a json response like :
public function getItems()
{
return response()->json([
'datas' => Item::all()
]);
}
This function return this json :
{
"datas": [
{
"id": 1,
"name": "FIRE",
"type": "9",
"subtype": "5"
},
{
"id": 2,
"name": "FIRE",
"type": "9",
"subtype": "5"
}
]
}
I need to dynamically add an uppercase on the first letter of each field before returning the json response, without change the laravel field name and the migration. I need to keep the field in lowercase in Laravel.
What I need is :
{
"datas": [
{
"Id": 1,
"Name": "FIRE",
"Type": "9",
"Subtype": "5"
},
{
"Id": 1,
"Name": "FIRE",
"Type": "9",
"Subtype": "5"
}
]
}
How can I do that simply ? I have a lots of class with a lots of fields in real. Thanks !
You could use the Eloquent: API Resources like this:
php artisan make:resource Item
then you will get a class in the Resources folder. That's where you edit the keys
Resources/Item.php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class Item extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'Id' => $this->id,
'Name' => $this->name,
'Type' => $this->type,
'Subtype' => $this->subtype,
];
}
}
In your controller, import the newly created resource and use it.
Controllers/ItemController.php
use App\Http\Resources\Client as ClientResource;
public function getItems()
{
return response()->json([
'datas' => ItemResource::collection(Item::all());
]);
}
There you go!
You can define an additional getter in the model for the name attribute (with a new name). So you can have both name and the new attribute that you define. Use ucfirst along with strtolower.
public function getUCFirstNameAttribute($value)
{
return ucfirst(strtolower($value));
}
OR
You can define an Items Resource. Inside the toArray method, you can use ucfirst on the name field.
return [
...
'name' => ucfirst($this->name),
...
];
Both ways you can keep the original field as is.
Simple and Generic solution :
public function toArray() {
$array = parent::toArray();
$newArray = array();
foreach($array as $name => $value){
$newArray[str_replace('_', '', ucwords($name, '_'))] = $value;
}
return $newArray;
}
Put this in a Model class, and after that just do : Item::all()->toArray()
This function transorm my_awesome_field to MyAwesomeField

Does Laravel support "typed" requests?

To transform a database entity to an API response Laravel support resources, eg. UserResource extends JsonResource. The resource allows me to cleanly define which fields from the entity should be included in the response, how to transform them etc.
Is there a similar functionality for requests? My requests typically look like this:
public function create(JsonRequest $request): UserResource
{
$data = $request->json()->all();
/* Remove, transform, add request fields etc. */
$user = User::create($data);
$user->save();
return new UserResource($user);
}
In our case we have a legacy database behind a modern API so there are a number of fields that need to transformed, renamed etc. before pushing them into the entity class. The fields differ from request to request but the steps are very similar. Is there a less boilerplate-y way to do this, something similar to how resources transform entities to responses?
Something like:
class UserRequest extends JsonRequest {
public function fromArray(JsonRequest $request) {
…
}
}
Then the request could look like this:
public function create(UserRequest $request): UserResource
{
$user = User::create($request);
$user->save();
return new UserResource($user);
}
I suppose, that most of your problems can solve form request. See example below
Form request class:
namespace App\Http\Requests;
use Carbon\Carbon;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
class TestRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'date' => 'required|date_format:Y-m-d H:i:s',
'name' => 'required|string',
];
}
// here you can specify custom error messages
public function messages()
{
return [
'date.required' => 'No date specified',
'date.date_format' => 'Invalid date format',
'name.required' => 'No name specified',
'name.string' => 'Invalid name format',
];
}
// here you can implement some data mapping before validation
protected function validationData()
{
return $this->transform($this->all());
}
// some data transformation logic
// You can place it anywhere in your applciation services
protected function transform($input)
{
$transformed = [];
foreach ($input as $field => $value) {
if ($field == 'name') {
$value = strtoupper($value);
} elseif ($field == 'date') {
$value = Carbon::parse($value)->toDateTimeString();
}
$transformed[$field] = $value;
}
return $transformed;
}
public function failedValidation(Validator $validator)
{
// here you can implement custom validation failure
parent::failedValidation($validator);
}
}
Here is my test route: Route::get('/test', 'TestController#index');
And controller:
use App\Http\Requests\TestRequest;
class TestController extends Controller
{
public function index(TestRequest $request)
{
return response()->json($request->validated());
}
}
So, then requesting route: curl -H 'Accept: application/json' 'http://localhost:8000/test?date=01.01.2019&name=petya'
And getting response: {"date":"2019-01-01 00:00:00","name":"PETYA"}
And dont be shy to see source code of request and form request, cause of not all methods you wish are described in docs. Hope this helps

Laravel | Validate generated value

I have an endpoint for data create.
The request is "name". I need to generate "slug" and validate that slug is unique.
So, let's say
book_genres table.
id | name | slug
Request is ["name" => "My first genre"].
I have a custom request with a rule:
"name" => "string|unique:book_genres,name".
I need the same check for the slug.
$slug = str_slug($name);
How can I add this validation to my custom request?
Custom request class:
class BookGenreCreate extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
"name" => 'required|string|unique:book_genres,name',
];
}
}
So basically what you want to do is try to manipulate the request data before validation occurs. You can do this in your FormRequest class by overriding one of the methods that is called before validation occurs. I've found that this works best by overriding getValidatorInstance. You can then grab the existing data, add your slug to it and then replace the data within the request, all before validation occurs:
protected function getValidatorInstance()
{
$data = $this->all();
$data['slug'] = str_slug($data['name']);
$this->getInputSource()->replace($data);
return parent::getValidatorInstance();
}
You can also add the rules for your slug to your rules method as well:
public function rules()
{
return [
"name" => 'required|string|unique:book_genres,name',
"slug" => 'required|string|unique:book_genres,slug',
];
}
So your class will look something like this:
class BookGenreCreate extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|string|unique:book_genres,name',
'slug' => 'required|string|unique:book_genres,slug',
];
}
protected function getValidatorInstance()
{
$data = $this->all();
$data['slug'] = str_slug($data['name']);
$this->getInputSource()->replace($data);
return parent::getValidatorInstance();
}
}
Now when the request comes through to your controller, it will have been validated and you can access the slug from the request object:
class YourController extends Controller
{
public function store(BookGenreCreate $request)
{
$slug = $request->input('slug');
// ...
}
}
You can add the 'slug' to the request, then use validations as usual.
rules() {
// set new property 'slug' to the request object.
$this->request->set('slug', str_slug($request->name));
// rules
return [
'name' => 'string|unique:book_genres,name',
'slug' => 'string|unique:book_genres,slug'
]
}

Laravel 5.4: JWTAuth, ErrorException in EloquentUserProvider.php

I am a newbie of laravel, so it might be my mistake. Using laravel with tymondesigns/jwt-auth
to verify user. I am watching this tutorial Complete JWT-AUTH api with Laravel and followed every step, the tymon package installation and logging in user. But i am getting this error. I posted code below, tell me if you need more code from any other file.
ErrorException in EloquentUserProvider.php line 120: Argument 1 passed
to Illuminate\Auth\EloquentUserProvider::validateCredentials() must be
an instance of Illuminate\Contracts\Auth\Authenticatable, instance of
App\User given
This is my user model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $hidden = ["password"];
protected $fillable = [
"id",
"name",
"password",
"mobile_number",
"gender",
"age",
"company_name",
"profile_image",
"email"
];
}
?>
This is my ApiAuthController.php
use JWTAuth;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Exceptions\JWTException;
class ApiAuthController extends Controller
{
public function authenticate(){
$credentaials = request()->only('email', 'password');
print_r($credentaials);
try {
$token = JWTAuth::attempt($credentaials);
if(!$token){
return response()->json(['error'=>'invalid credentaials'], 401);
}
} catch (JWTException $e) {
return response()->json(['error'=>'something went wrong'], 500);
}
return response()->json(['token'=>$token], 200);
}
}
User store function in my UsersController:
public function store(Request $request)
{
$payload = json_decode($request->payload, true);
$validator = Validator::make($payload, $this->rules);
if ($validator->passes()) {
$user = (new User())->fill($payload);
if($user->save()){
$response = [
"msg" => "User created",
"link" => "/api/users/" . $user->id
];
return response()->json($response, 200);
}
$response = [
"msg" => "An error occured"
];
return response()->json($response, 404);
}
else {
return response()->json($validator->messages(), 404);
}
}
In storing user request, payload is key and value is json object, the small sample object is given below:
payload={
"name": "Alexa",
"email": "alexa#gmail.com",
"password":"12345",
"gender": "Male",
"age": 24
}
Add this to your model
use Illuminate\Foundation\Auth\User as Authenticatable;
and change this line
class User extends Authenticatable
Edit :
Looks like you're storing passwords in plaintext. Add this to your user model.
public function setPasswordAttribute($value)
{
$this->attributes['password'] = bcrypt($value);
}

laravel eloquent models one to many relationship with dingo transformer

my codes are constructed by Laravel+dingo.
I have two models which are one to many relationship:
Reservation.php (Master)
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Reservation extends Model
{
protected $table = 'dbo.Reservation';
public function hasManyReservationDetails()
{
return $this->hasMany('App\Models\ReservationDetail', 'ReservationID', 'ReservationID');
}
}
ReservationDetail.php (Detail)
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ReservationDetail extends Model
{
protected $table = 'dbo.ReservationDetail';
public function belongsToReservation()
{
return $this->belongsTo('App\Models\Reservation', 'ReservationID', 'ReservationDetailID');
}
}
And two Transformers for the two models as following:
ReservationTransformer
public function transform(Reservation $reservation)
{
return [
'reservation_id' => (int) $reservation->ReservationID,
'reservation_no' => $reservation->ReservationNo,
];
}
ReservationDetail Transformer
public function transform(ReservationDetail $reservation_detail)
{
return [
'reservation_detail_id' => (int) $reservation_detail->ReservationDetailID,
'reservation_id' => (int) $reservation_detail->ReservationID,
'room_no' => $reservation_detail->RoomNo,
];
}
My controller and inquire
$reservation = Reservation::where('ReservationNo', '=', $reservation_no)
->with('ReservationDetails')
->get();
return $reservation;
I get the following return
{
"Reservations": [
{
"ReservationID": "1",
"ReservationNo": "2016-06-01 16:50:59.0659",
"reservation_details": [
{
"ReservationDetailID": "1",
"ReservationID": "1",
"RoomNo": "001",
},
{
"ReservationDetailID": "2",
"ReservationID": "1",
"RoomNo": "002",
}
]
}
]
}
I try the following but only return the translation of master table.
$reservation = $this->collection($reservation, new ReservationTransformer());
**
How can I transform the the data of master and detail table together?
**
I am not really understand how 'Custom Transformation Layer' works, anyone who can give me an example?
Many Thanks.
You can use fractals build in support for transformer includes. See http://fractal.thephpleague.com/transformers/
I would start by renaming public function hasManyReservationDetails() to public function reservationDetails().
And then your ReservationTransformer will take care of the rest:
use League\Fractal\TransformerAbstract;
class ReservationTransformer extends TransformerAbstract
{
/**
* List of resources to automatically include
*
* #var array
*/
protected $defaultIncludes = [
'reservationDetails',
];
public function transform(Reservation $reservation)
{
return [
'reservation_id' => (int) $reservation->ReservationID,
'reservation_no' => $reservation->ReservationNo,
];
}
/**
* Include ReservationDetail
*
* #param Reservation $reservation
*
* #return League\Fractal\Resource\Collection
*/
public function includeReservationDetails(Reservation $reservation)
{
return $this->collection($reservation->reservationDetails, new ReservationDetailTransformer);
}
}

Resources