Laravel: Move model attributes position - php

I was used relationships in my model and it's working fine but the sort of output array is wrong.I want to move created_at and updated_at in the end of the array
The output is:
{
"id": 1,
"title": "Test home",
"created_at": "2022-01-01T15:27:31.000000Z", <---------
"updated_at": "2022-01-01T15:27:31.000000Z", <---------
"doors": [
{
"id": 1,
"home_id": 1,
"button_count": 2,
"created_at": null,
"updated_at": null
}
]
}
But it's should be:
{
"id": 1,
"title": "Test home",
"doors": [
{
"id": 1,
"home_id": 1,
"button_count": 2,
"created_at": null,
"updated_at": null
}
],
"created_at": "2022-01-01T15:27:31.000000Z", <---------
"updated_at": "2022-01-01T15:27:31.000000Z" <---------
}
And it's my model class:
class Home extends Model
{
use HasFactory;
protected $table = "homes";
protected $hidden = ["device_id"];
protected $with = ["doors"];
public function device(){
return $this->hasOne(Device::class,"id","device_id");
}
}

The cleanest approach if you are building an API (not sure if your scenario) is by using Resources where you can format the response as you wish. This is also helpful for being sure that Models will be returned with the same structure in all endpoints.
Assuming you want to fetch all homes with their devices, you can create a DeviceResource for returning specific device properties
PHP artisan make:resource DeviceResource
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class DeviceResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'home_id' => $this->home_id,
'button_count' => $this->button_count,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
}
Then create a HomeResource which will includes DeviceResource
PHP artisan make:resource HomeResource
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class HomeResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'devices' => DeviceResource::collection($this->whenLoaded('devices')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
}
Then you just return the HomeResource collection in your controller
$homes = Home::with('devices')->get();
$response = HomeResource::collection($homes);
return $response;
Or in case you want to return only 1 Home...
$home = Home::with('devices')->findOrFail($home_id);
$response = new HomeResource($home);
return $response;
Be aware that if you don't load devices relationships, the HomeResource won't return the devices property.

Related

Whenever I use Laravel resource with paginate method, I get a response with "links" and "meta" keys. I want to change these keys to a new format

I used laravel resources for my api responses and added paginate method. Upon using paginate method I always get a result like this where laravel by default gives three keys namely "data", "links" and "meta". But I want to change the resource to my own need.
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28#example.com",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort#example.com",
}
],
"links":{
"first": "http://example.com/pagination?page=1",
"last": "http://example.com/pagination?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/pagination",
"per_page": 15,
"to": 10,
"total": 10
}
}
But I want a result like this
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28#example.com",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort#example.com",
}
],
"metadata": {
"pagination": {
"offset": 50,
"limit": 25,
"previousOffset": 25,
"nextOffset": 75,
"currentPage": 3,
"pageCount": 40,
"totalCount": 1000
}
}
}
How can I be able to achieve this. I am using Laravel 7.*
My controller code:
public function index(Request $request)
{
return DiscussionResource::collection($this->discussion->getDiscussionList($request));
}
My Model method looks like this:
public function getDiscussionList($request){
return $this->ofSearch($request)
->orderBy('created_at', config('settings.pagination.order_by'))
->paginate(config('settings.pagination.per_page'));
}
My resource looks like this:
class DiscussionResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'question_id' => $this->question_id,
'user_id' => $this->user_id,
'user_image' => $this->user->userProfile->image,
'user_role' => $this->user->type,
'comment' => $this->comment,
'is_pinned' => $this->is_pinned,
'created_at' => $this->created_at->toDateString()
];
}
}
There are so many ways to do that in laravel and here you go 2 ways of it:
First Way: you can create a custom PaginatedResourceResponse and override the default paginationLinks. for example like below:
use Illuminate\Http\Resources\Json\PaginatedResourceResponse;
class CustomPaginatedResourceResponse extends PaginatedResourceResponse
{
protected function paginationLinks($paginated)
{
return [
'prev' => $paginated['prev_page_url'] ?? null,
'next' => $paginated['next_page_url'] ?? null,
];
}
protected function meta($paginated)
{
$metaData = parent::meta($paginated);
return [
'current_page' => $metaData['current_page'] ?? null,
'total_items' => $metaData['total'] ?? null,
'per_page' => $metaData['per_page'] ?? null,
'total_pages' => $metaData['total'] ?? null,
];
}
}
then override toResponse method(Actually, the toResponse method converts resource collection to responses)
public function toResponse($request)
{
return $this->resource instanceof AbstractPaginator
? (new CustomPaginatedResourceResponse($this))->toResponse($request)
: parent::toResponse($request);
}
You may consider overriding other methods if you want to customize your response furthermore.
Second Way: you can just override the toResponse method in the ResourceCollection and make it as you wish!
More Ways HERE!
If you want to customize the metadata you can take the help of the with() method that comes with laravel for collections.
// in DiscussionResource file
public function with($request)
{
return [
'meta' => [
'key' => 'value',
],
];
}
If you want to customize it from controller you may do something like this
return (DiscussionResource::collection($this->discussion->getDiscussionList($request)))
->additional(['meta' => [
'key' => 'value',
]]);
And in case you want it for a single resource you can modify it at the toArray() method
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
For more details you can check this out https://laravel.com/docs/7.x/eloquent-resources#adding-meta-data

Laravel Resource API customize outer data wrapper $wrap

I am trying to rename my data wrapper for the resource I am fetching using Laravel resource. I read in the documentation here how you are supposed to do it, so I did:
ScanResource.php
class ScanResource extends JsonResource
{
public static $wrap = 'scan';
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
//return parent::toArray($request);
return ['id' => $this->id,
'rftag' => $this->rftag,
'jc_number' => $this->jc_number,
'station' => $this->station,
'agent' => $this->agent,
'created_at' => $this->created_at->format('d/m/Y H:i:s'),
'updated_at' => $this->updated_at->format('d/m/Y'),];
}
}
AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
//Paginator::useBootstrapThree();
Schema::defaultStringLength(191);
//JsonResource::withoutWrapping();
//Resource::withoutWrapping();
ScanResource::withoutWrapping();
}
public function register()
{
//
}
}
This is how I am trying to fetch the resource in my controller:
public function show($id)
{
$product = ScanDetail::find($id);
if (is_null($product)) {
return $this->sendError('Scan details not found.');
}
return $this->sendResponse(new ScanResource($product), 'Scan info retrieved successfully.');
}
currently I am getting the following JSON with Postman:
{
"success": true,
"data": {
"id": 1,
"rftag": "E200203204205212165166",
"jc_number": "15",
"station": "Repairing",
"agent": "kbailey",
"created_at": "11/06/2020 01:29:53",
"updated_at": "11/06/2020"
},
"message": "Scan info retrieved successfully."
}
But I want:
{
"success": true,
"scan": {
"id": 1,
"rftag": "E200203204205212165166",
"jc_number": "15",
"station": "Repairing",
"agent": "kbailey",
"created_at": "11/06/2020 01:29:53",
"updated_at": "11/06/2020"
},
"message": "Scan info retrieved successfully."
}
I tried this, this, this, this and this. This is not what I am using, so I did not think it would work. I also tried modifying my toArray to:
return [
'scan'=>['id' => $this->id,
'rftag' => $this->rftag,
'jc_number' => $this->jc_number,
'station' => $this->station,
'agent' => $this->agent,
'created_at' => $this->created_at->format('d/m/Y H:i:s'),
'updated_at' => $this->updated_at->format('d/m/Y'),]
];
but its giving the following JSON:
{
"success": true,
"data": {
"scan": {
"id": 1,
"rftag": "E200203204205212165166",
"jc_number": "15",
"station": "Repairing",
"agent": "kbailey",
"created_at": "11/06/2020 01:29:53",
"updated_at": "11/06/2020"
}
},
"message": "Scan info retrieved successfully."
}
Again, not what I want since I will be fetching different resources from the database using api calls. So I want to customize the outer wrapper. Any assistance is/will be greatly appreciated. Thanks in advance.
You don't need to do anything in your AppServiceProvider.php
You just need to create ScanResource.php as below:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ScanResource extends JsonResource
{
/**
* The "data" wrapper that should be applied.
*
* #var string
*/
public static $wrap = 'scan';
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'rftag' => $this->rftag,
'jc_number' => $this->jc_number,
'station' => $this->station,
'agent' => $this->agent,
'created_at' => $this->created_at->format('d/m/Y H:i:s'),
'updated_at' => $this->updated_at->format('d/m/Y')
];
}
public function with($request)
{
return [
"success" => true,
"message": "Scan info retrieved successfully."
];
}
}
And you need to use that ScanResource.php in the ScanController.php as below
// use the namespace at the top
use App\Http\Resources\UserResource;
// add the below action in the controller.
public function show($id)
{
$scan= Scan::find($id);
return new ScanResource($scan);
}
I have tested the code in my postman and I got below output
{
"scan": {
"id" : 7,
"rftag" : "abcde",
"jc_number" :"AB123FG" ,
"station" : "abc",
"agent" : "Stanton Satterfield",
"created_at": "29/10/2021 05:18:42",
"updated_at": "29/10/2021 05:18:42"
},
"success" : true,
"message": "Scan info retrieved successfully."
}
in your controller change to this
return ['scan' => YourResource::collection(YourModel::get())];
and in your AppServiceProvider.php file add this
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Resources\Json\JsonResource; // this
class AppServiceProvider extends ServiceProvider
{
public function register()
{
JsonResource::withoutWrapping(); // and this
}
}
hope it's work for you :)

Laravel error: Call to a member function first() on string when using Resource::collection

I'm creating an backend with multiple stores. Each stores with their own products
I've created a store and a product and am trying to resource the products to all the stores.
When I try add my products to my store in StoreResource.php
using:
'products' => ProductResource::collection($this->products),
I get the error:
Call to a member function first() on string
I've looked on line for numerous explanations and tutorials, but get met with the same error
* STORE MODEL *
public function products()
{
return $this->hasMany(Product::class);
}
*STORE CONTROLLER *
public function index()
{
return StoreResource::collection(Store::with('products')->paginate(5));
}
STORE Resource
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class StoreResource 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,
'description' => $this->description,
'image' => $this->image,
'created_at' => (string) $this->created_at,
'updated_at' => (string) $this->updated_at,
'products' => ProductResource::collection($this->products),
];
}
}
* Product MODEL *
public function stores()
{
return $this->belongsTo(Store::class);
}
*Product CONTROLLER *
public function index()
{
return ProductResource::collection(Product::with('stores')->paginate(5));
}
Product Resource
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class Purchaseresource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'productName' => $this->productName,
'amount' => $this->amount,
'total_product_price' => $this->total_product_price,
'week' => $this->week,
'day' => $this->day,
'month' => $this->month,
'year' => $this->year,
'created_at' => (string) $this->created_at,
'updated_at' => (string) $this->updated_at,
];
}
}
What I expect is to get a relation between my store and products so they are displayed as this in the API repsonse
{
"id": 0,
"name": "Store",
"image": "image",
"products": [
{
"id": 1,
"name": "product",
"price": 7,
"qty": 100,
"total": 0
},
]
}
So the product would become nested in the store.
you first need to make a query in your controller like:
$query = Store::all();
and after something like:
$stores = StoreResource::collection($query);
and in your Resource you can pass:
'product' => $this->product
in the resource array

How to change json format using API Resource?

I am trying to push an item status into array and then return through API Resource, but API resource returning error. I am trying following script to do that
$Games = Game::all();
return new GameResource($Games);
And it is return as following
{
"data": [
{
"id": 1,
"name": "similique",
"type_id": 3,
"created_at": "2018-10-30 11:23:27",
"updated_at": "2018-10-30 11:23:27"
}
]
}
I am trying following to achieve my desire json array
$Games = Game::all();
$DataArray = ['status' => 'success', 'data' =>$Games ];
return new GameResource($DataArray);
But it is returning error
Call to a member function toBase() on array in fil
My desire json array is following
{
"status": "success",
"data": [
{
"id": 1,
"name": "similique",
"type_id": 3,
"created_at": "2018-10-30 11:23:27",
"updated_at": "2018-10-30 11:23:27"
}
]
}
In your GameResource.php change the toArray() method:
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
'status' => 'success', // Here goes the logic which checks for success or failure. However, this depends on what you consider as "success".
];
}
You can try this code:
$games = Game::get();
return response()->json(new GameResource($games));
data is a public property of your GameResource ? then your code should be
return ['status' => 'success', 'data' => (new GameResource($Games))->data ];
Brother, I am using this way.
Step 1: I create a generalOutput Resource.php and modify like below.
class GeneralOutputResource extends JsonResource
{
private $status;
private $data;
public function __construct($status, $data)
{
$this->status = $status;
$this->data = $data;
}
public function toArray($request)
{
return ['status' => $this->status, 'data' => $this->data];
}
}
Step 2:
Then I just call the class like this
$a_user = User::find(1);
return new GeneralOutputResource(1, $a_user);
Everytime I return the response, I will call the GeneralOutputResponse.

Laravel pagination append data in one variable and other in another variable

I am facing a problem in laravel pagination. In laravel when I called paginate() method it returns
{
"total": 50,
"per_page": 15,
"current_page": 1,
"last_page": 4,
"first_page_url": "http://laravel.app?page=1",
"last_page_url": "http://laravel.app?page=4",
"next_page_url": "http://laravel.app?page=2",
"prev_page_url": null,
"path": "http://laravel.app",
"from": 1,
"to": 15,
"data":[
{
// Result Object
},
{
// Result Object
}
]
}
This type of Object. What I want is that I want to set data in one vairable for example $a and except data all other value in $b.
But when I added appends('data') of my paginate variable it did not working correctly. I did not find a solution after googling it. Please help me to solve this.
Here is User model
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Facades\Auth;
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Authenticatable {
use Notifiable;
use SoftDeletes;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password', 'status', 'role_id',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
}
My Controller Code is
public function index() {
$users = User::where('status', 1)->paginate(10);
return response()->json(
[
'error' => 0,
'errorMsg' => '',
'data' => [
'users' => $users->appends('data')->toArray(),
],
]
);
}
I tried this code
return response()->json(
[
'error' => 0,
'errorMsg' => '',
'data' => [
'users' => $users->only($user['data'])->toArray(),
'users_pagination' => $users->except($user['data'])->toArray(),
],
]
);
In this users work correctly but users_pagination not working correctly. In both the users, users_pagination returns same value
Try this
$paginateData = User::where('status', 1)->paginate(10);
$arrPaginateData = $paginateData->toArray();
$users = $arrPaginateData['data'];
unset($arrPaginateData['data']); //remove data from paginate array
$pageInfo = $arrPaginateData;
Return in response
return response()->json(
[
'error' => 0,
'errorMsg' => '',
'data' => [
'users' => $users,
'users_pagination' => $pageInfo
],
]
);
Why not try to iterate the object? the below code will attached user specific data into each users.
public function index() {
$users = User::where('status', 1)->paginate(10);
foreach($users as $users){
$users->data = 'your data here';
}
return response()->json([
'error' => 0,
'errorMsg' => '',
'data' => [
'users' => $users,
],
]
);
}
If you want to use Laravel appends you have to follow as per the document.

Categories