Laravel - transform field name with uppercase capital in json response - laravel

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

Related

Eloquent create method returning "id" 0 after storing data

I am facing an strange issue. This is my migration:
Schema::create('calendars', function (Blueprint $table) {
$table->id();
$table->string("start");
$table->string("end");
$table->foreignId("learner_id")->constrained()->cascadeOnDelete();
$table->foreignId("driving_instructor_id")->nullable()->constrained()->cascadeOnDelete();
$table->foreignId("event_type_id")->constrained()->cascadeOnDelete();
$table->foreignId("licence_class_id")->constrained()->cascadeOnDelete();
$table->text("description")->nullable();
$table->timestamps();
});
This is my model:
class Calendar extends Model
{
use SoftDeletes;
protected $fillable = [
"id",
"start",
"end",
"learner_id",
"driving_instructor_id",
"event_type_id",
"licence_class_id",
"description",
];
protected $appends = ["title"];
public function eventType()
{
return $this->belongsTo(EventType::class);
}
public function learner()
{
return $this->belongsTo(Learner::class);
}
public function transaction()
{
return $this->hasOne(Transaction::class);
}
public function drivingInstructor()
{
return $this->belongsTo(DrivingInstructor::class);
}
public function licenceClass()
{
return $this->belongsTo(LicenceClass::class);
}
public function getTitleAttribute()
{
return $this->learner->full_name;
}
}
When use the create method, it successfully creates a new entry in the database however it is returning and id 0.
$calendarIds[]=(Calendar::create($data))->id;
The $data variable holds:
array:12 [
"title" => ""
"start" => "2020-11-30T23:00:00.000Z"
"startTimezone" => ""
"end" => "2020-11-30T23:00:00.000Z"
"endTimezone" => ""
"recurrenceRule" => ""
"recurrenceException" => ""
"isAllDay" => true
"description" => ""
"driving_instructor_id" => 3
"event_type_id" => 1
"licence_class_id" => 1
]
I have never faced this issue before. When i do the same thing with tinker however it returns the correct id. I have tried using the $calendar->save() method as well and it is returning id as 0 as well. Any help would be appreciated.
Ok so after a lot of googling I found out that Telescope query watcher was causing the problem. I had to turn off the watcher. Here is the thread https://github.com/laravel/telescope/issues/289
You should use in the migration script:
$table->bigIncrements('id');

Get specific field from one to many table relationship with laravel eloquent?

I have table like this following image:
so, user have many balance_transactions and last inserted balance_transactions record will be user account balance. my question, how to make user model have property account_balance with value last inserted total in balance_transactions table
I was tried using something like this in user model
public function balance {
return $this->hasMany(App\BalanceTransaction::class);
}
public function account_balance {
return $this->balance()->orderBy('id', 'DESC')->limit(1);
}
And I get the data like this
$user = User::where('id', 1)->with('account_balance')->first();
return response()->json($user);
and the result look like this folowing json:
{
"id": 1,
"username": "john0131",
"full_name": "john doe",
"email": john#test.com,
"account_balance": [
{
"id": 234,
"user_id": 1,
"total": 7850
"added_value": 50
"created_date": "2020-02-28 12:18:18"
}
]
}
but what I want, the return should be like this following json:
{
"id": 1,
"username": "john0131",
"full_name": "john doe",
"email": "john#test.com",
"account_balance": 7850
}
my question, how to make something like that in laravel eloquent proper way? so I can get account_balance data only with simple code like $user = user::find(1);.
Thanks in advance
One way to tackle this is, you could use laravel Accessors
So in your User model you can create a method as follows
/**
* Get the user's balance.
*
* #return float
*/
public function getAccountBalanceAttribute()
{
return $this->balance->last()->total;
}
then wherever you want to use it you can use it by:
$user->account_balance;
I would suggest loading only one row from your transaction table with an eye on performance. Additionally you can append the value of the accessor to the serialised output of the model (e.g. __toArray()) and return the acutal value only if the relationship has already been loaded.
class User extends Authenticatable
{
protected $appends = ['account_balance'];
public function balance()
{
return $this->hasMany(App\BalanceTransaction::class);
}
public function latestBalance()
{
return $this
->hasOne(App\BalanceTransaction::class)
->latest();
}
public function getAcountBalanceAttribute()
{
if (! $this->relationLoaded('latestBalance')) {
return null;
}
return $this->latestBalance->total;
}
}

Relationship not returning any data

I am making a forums system with post likes, but for some reason, the relationship for the post likes doesn't return any data.
I have tried loading the data without the resource, and included with('all_likes') but this still doesnt return the data.
My get posts method:
public function getPosts($id)
{
$response = array();
$response['posts'] = ForumPostResource::collection(ForumPost::where('thread_id', $id)->get());
return $response;
}
My Forum Post Resource:
class ForumPostResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'user_id' => $this->user_id,
'thread_id' => $this->thread_id,
'body' => $this->body,
'like_data' => $this->all_likes,
'user_data' => new UserResource($this->user),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at
];
}
}
My forum post model:
class ForumPost extends Model
{
protected $fillable = [
'user_id', 'thread_id', 'body', 'likes', 'created_at', 'updated_at',
];
protected $appends = ['likes_total', 'user_data'];
public function user()
{
return $this->belongsTo('App\User');
}
public function all_likes()
{
return $this->hasMany('App\PostLike', 'id', 'post_id');
}
public function getCreatedAtAttribute($value)
{
return date('D, d F Y G:i', strtotime($value));
}
public function getUserDataAttribute()
{
return $this->user()->first();
}
public function getLikesTotalAttribute()
{
return $this->all_likes()->count();
}
}
Once you click on the like button the user id and post id are added to the post_likes database. Then when the view is loaded which displays all forum posts including the post likes relationship data.
The result I get is this:
{
"posts": [
{
"id": 14,
"user_id": 1501,
"thread_id": 3,
"body": "<p>Welcome everyone</p>",
"like_data": [],
"user_data": {
"name": "mikelmao",
"role_id": 1,
"avatar": "users/default.png"
},
"created_at": "Sat, 05 January 2019 13:04",
"updated_at": {
"date": "2019-01-11 03:22:27.000000",
"timezone_type": 3,
"timezone": "UTC"
}
}
]
}
This should be returning 1 post like result as my db looks like this:
id|user_id|post_id
1|1501|14
I had overlooked the fact that it was calling App\PostLike and the foreign key and local key were in the wrong order.
Method definition is:
public function hasMany($related, $foreignKey = null, $localKey = null)

Laravel conditional array using Resource

I've got People table with columns: person, name, text.
column person can have only values like: physician, dentist, nurse, etc...
I want to send it via api using Laravel Resource, so I created basic PeopleResource.php:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class PeopleResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'name' => $this->name,
'text' => $this->text
];
}
}
this is simple and gives me an array of objects with name and text.
Now I want to make more complicated array that will give me something like this:
Please note that I want to get all person types into my array here. This is the problem of making my array structured - so I can access it like result.physician.name, result.nurse.name etc..
result: {
physician: [{name: "name1", text: "text2"}, {name: "name2", text: "text2"}...],
nurse: [{name: "name1", text: "text2"}, {name: "name2", text: "text2"}...],
dentist: [{name: "name1", text: "text2"}, {name: "name2", text: "text2"}...],
otherTypeOfPerson: [], ....
}
How can I do it using Laravel Resource?
edit: my controller
<?php
namespace App\Http\Controllers;
use App\People;
use Illuminate\Http\Request;
use App\Http\Resources\PeopleResource;
class PeoplesController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$peoples= People::orderBy('id','asc')->get();
return PeopleResource::collection($peoples);
}
edit2: I was thinking about this solution, but this seems not working:
public function toArray($request)
{
return [
if($this->person == 'nurse') {
'nurse' => [
'name' => $this->name,
'text' => $this->text
]
}
if($this->person == 'physician') {
'physician' => [
'name' => $this->name,
'text' => $this->text
]
}
];
}
$result = YourModel:groupBy('person')->get();
dd($result);
//define your field name in model
Use groupBy() on collection.
public function index()
{
$peoples = People::select(['name', 'text', 'person'])->orderBy('id','asc')->get();
return $peoples->groupBy('person')->toArray();
}

Dingo API remove "data" envelope

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

Resources