I was wondering if it is possible to define different data for item resource and collection resource.
For collection I only want to send ['id', 'title', 'slug'] but the item resource will contain extra details ['id', 'title', 'slug', 'user', etc.]
I want to achieve something like:
class PageResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'user' => [
'id' => $this->user->id,
'name' => $this->user->name,
'email' => $this->user->email,
],
];
}
}
class PageResourceCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
];
}
}
PageResourceCollection will not work as expected because it uses PageResource so it needs
return [
'data' => $this->collection,
];
I could duplicate the resource into PageFullResource / PageListResource and PageFullResourceCollection / PageListResourceCollection but I am trying to find a better way to achieve the same result.
The Resource class has a collection method on it. You can return that as the parameter input to your ResourceCollection, and then specify your transformations on the collection.
Controller:
class PageController extends Controller
{
public function index()
{
return new PageResourceCollection(PageResource::collection(Page::all()));
}
public function show(Page $page)
{
return new PageResource($page);
}
}
Resources:
class PageResource extends Resource
{
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'user' => [
'id' => $this->user->id,
'name' => $this->user->name,
'email' => $this->user->email,
],
];
}
}
class PageResourceCollection extends ResourceCollection
{
public function toArray($request)
{
return [
'data' => $this->collection->transform(function($page){
return [
'id' => $page->id,
'title' => $page->title,
'slug' => $page->slug,
];
}),
];
}
}
If you want the response fields to have the same value in the Resource and Collection, you can reuse the Resource inside the Collection
PersonResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class PersonResource extends Resource
{
/**
* 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,
'person_type' => $this->person_type,
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'created_at' => (string) $this->created_at,
'updated_at' => (string) $this->updated_at,
];
}
}
PersonCollection.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class PersonCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function toArray($request)
{
// return parent::toArray($request);
return PersonResource::collection($this->collection);
}
}
The accepted answer works, if you are not interested in using links and meta data. If you want, simply return:
return new PageResourceCollection(Page::paginate(10));
in your controller. You should also look to eager load other dependent relationships before passing over to the resource collection.
Related
I am working on a Laravel 8 app with users and posts.
The objective is to create a bunch of posts (I already have users).
namespace Database\Factories;
// import Post model
use App\Models\Post;
// import User model
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory {
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Post::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition() {
return [
'title' => $this->faker->sentence(3),
'description' => $this->faker->text,
'content' => $this->faker->paragraph,
'user_id' => $this->faker->factory(App\Models\User::class),
];
}
}
The problem
I run php artisan tinker then Post::factory()->count(100)->create() in the terminal and I get:
InvalidArgumentException with message 'Unknown format "factory"'
UPDATE
I replace my return statement with:
return [
'title' => $this->faker->sentence(3),
'description' => $this->faker->text,
'content' => $this->faker->paragraph,
'user_id' => User::factory(),
];
I get this in the terminal:
Class 'Database\Factories\UserFactory' not found
Questions:
Where is my mistake?
Does the fact that I get the error Class 'Database\Factories\UserFactory' not found mean that I need to
create a UserFactory factory? Because there isn't one. (I wanted
to create posts, not users).
I don't suppose there is $this->faker->factory(..).
You can do
'user_id' => App\Models\User::factory()->create()->id,
EDIT:
'user_id' => App\Models\User::factory(),
Creating a UserFactory factory and using the below return statement did the trick:
return [
'title' => $this->faker->sentence(3),
'description' => $this->faker->text,
'content' => $this->faker->paragraph,
'user_id' => User::factory(),
];
So, the PostFactory class looks like this:
class PostFactory extends Factory {
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Post::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition() {
return [
'title' => $this->faker->sentence(3),
'description' => $this->faker->text,
'content' => $this->faker->paragraph,
'user_id' => User::factory(),
];
}
}
guys I'm new to Laravel. I currently building an API, and I have a couple of endpoints (the one that we will discuss are the Get All Tasks and Get Single Task)
The problem is that whenever I call the Get All Tasks endpoint it returns me the task resource + the user resource there. However when I call the Get Single Task endpoint it only returns me the TaskResource without the User inside of it. Any idea ?
Here is the code for the resourses
class TaskResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'estimate' => $this->estimate,
'status' => $this->status,
'type' => $this->type,
'user' => new UserResource($this->whenLoaded('user'))
];
}
}
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'avatar' => $this->avatar,
'tasks' => TaskResource::collection($this->whenLoaded('tasks'))
];
}
}
And here is how I get the Tasks in the controller.
https://prnt.sc/16q0rlh
The reason was that I was missing the 'with('user')' before the 'findOrFail' in my controller so ->
/**
* Description: Get single task
* Method: GET
* api/tasks/{id}
* #param TaskRequest $request
*/
public function actionGetTask(Request $request, $id) {
try {
return new TaskResource(Task::with('user')->findOrFail($request->route('id')));
} catch (\Exception $ex) {
return Response::json([
'success' => false,
'message' => 'There is no such id in the DB'
], 400);
}
}
I want to create a Request made by php artisan make:request wherein rules I can add a param, for instance I have the following validator in the controller:
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:{{number}}',
'body' => 'required',
]);
Where number is from url parameter
How can I get this url parameter in my Request class?
Here is my Request class:
<?php
namespace Modules\Blog\Http\Requests;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class SaveBlogCategoriesRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
dd($request->param);
return [
'lang' => 'required',
'name' => 'required',
'slug' => "required|unique:blog_categories_id,slug,", // here I want to add id from param
];
}
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
protected function failedValidation(Validator $validator)
{
$data = [
'status' => false,
'validator' => true,
'msg' => [
'e' => $validator->messages(),
'type' => 'error'
],
];
throw new HttpResponseException(response()->json($data));
}
}
For the request class, you dont need to instantiate the validator
class ModelCreateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
//if you need the input, you can access it via $this->request;
$param = $this->request->get('param');
//or you can also access it directly (yeah I know it not intuitive)
$param = $this->param
return [
'lang' => 'required',
'name' => 'required',
'slug' => "required|unique:blog_categories_id,slug,".$param,
];
}
}
Use dd($this) to get the request object in request class
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
I'm using a Laravel Json Resource in my controller, as follows
public function index(Request $request)
{
$itemsWithTranslations = MenuItem::where(['menu_id' => $request->id, 'parent_id' => null])
->with(['children', 'translations'])
->orderBy('sort_order', 'asc')
->get();
return MenuItemResource::collection($itemsWithTranslations);
}
Now I would like to generate a collection, inside this collection with the children for the item that's being shown.
The following code works fine. Notice how I commented out the children reference
class MenuItemResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'text' => $this->title,
// 'children' => MenuItemResource::collection($this->whenLoaded('children')),
'data' => [
'id' => [
'value' => $this->id,
'type' => 'hidden'
],
'title' => [
'value' => $this->title,
'type' => 'text',
'label' => 'Title'
],
'resource_link' => [
'value' => $this->resource_link,
'type' => 'text',
'label' => 'Resource Link'
],
'translations' => MenuItemTranslationResource::collection($this->whenLoaded('translations'))->keyBy(function ($translation) {
return $translation['locale'];
})
]
];
}
}
When I uncomment the children, I get the following error
"Call to undefined method Illuminate\Http\Resources\Json\AnonymousResourceCollection::keyBy()"
Is it wrong, to include a Resource inside a resource? Or how should I go about this?
Model
class MenuItem extends Model
{
protected $table = 'menu_items';
protected $fillable = ['menu_id', 'parent_id', 'title', 'order', 'resource_link', 'html_class', 'is_blank'];
public function translations()
{
return $this->hasMany(MenuItemTranslation::class, 'menu_item_id');
}
public function children()
{
return $this->hasMany(MenuItem::class, 'parent_id');
}
}
Extra Information
When I return the following data, it does return empty as a collection for the children.
MenuItemResource::collection($this->children);
This returns
While if I return the children without a collection, it returns them (for 1 item, which is correct)
return $this->children;
returns
you should use ChildrenResource::collection
'children' => ChildrenResource::collection($this->whenLoaded('children'))
hope this works.
create a ChildrenResource class if not exists.