Reputation: 1323
I've got a Category
resource that looks like this:
<?php
namespace Domain\Category\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class CategoriesResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param Request $request
* @return array
*/
public function toArray($request): array
{
return [
'id' => (string)$this->id,
'type' => 'categories',
'attributes' => [
'name' => $this->name,
'slug' => $this->slug,
'parent' => $this->parent,
'description' => $this->description,
'image' => $this->image,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
]
];
}
}
parent
is the ID of the category's parent, which is itself a Category
. When I call the endpoint that returns this resource I get:
{
"data": {
"id": "5",
"type": "categories",
"attributes": {
"name": "Paper",
"slug": "paper",
"parent": 1,
"description": "test",
"image": null,
"created_at": "2022-09-09T19:01:44.000000Z",
"updated_at": "2022-09-09T19:01:44.000000Z"
}
}
}
which is what I would expect. But now I'm thinking that it would be better to return the nested relationship, so instead of returning the above I'd like to return:
{
"data": {
"id": "5",
"type": "categories",
"attributes": {
"name": "Paper",
"slug": "paper",
"parent": {
"id": "5",
"type": "categories",
"attributes": {
"name": "Parent Paper",
"slug": "parent-paper",
"parent": 0,
"description": "test 2",
"image": "test.png",
"created_at": "2022-09-09T19:01:44.000000Z",
"updated_at": "2022-09-09T19:01:44.000000Z"
},
"description": "test",
"image": null,
"created_at": "2022-09-09T19:01:44.000000Z",
"updated_at": "2022-09-09T19:01:44.000000Z"
}
}
}
so I thought about adding the relationship to itself in the model:
<?php
namespace Domain\Category\Models;
use Domain\Product\Models\Product;
use Domain\Shared\BaseModel;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Str;
class Category extends BaseModel
{
protected $fillable = [
'name',
'parent',
'description',
'image',
];
public function parent() : BelongsTo
{
return $this->belongsTo(Category::class, 'parent');
}
}
and then updating the parent
field in the resource
:
public function toArray($request): array
{
return [
'id' => (string)$this->id,
'type' => 'categories',
'attributes' => [
'name' => $this->name,
'slug' => $this->slug,
'parent' => new CategoriesResource($this->parent),
'description' => $this->description,
'image' => $this->image,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
]
];
}
but this is not working. I'm getting an error that says:
Attempt to read property "id" on int
This error is pointing to the ID field:
public function toArray($request): array
{
return [
'id' => (string)$this->id,
``
Furthermore, I don't want it to get into a loop, meaning that when displaying the `parent` info I don't want it to display the parent's parent and so on.
Is this possible?
Upvotes: 1
Views: 1788
Reputation: 977
The error Attempt to read property "id" on int
can often be caused by naming your relationship same as your column name. Laravel always loads the column and ignores the relationship if they both have the same name.
So simple renaming of your relationship (or renaming your column to ie. parent_id
) in your model should do the trick:
class Category extends BaseModel
{
protected $fillable = [
'name',
'parent', // this is the DB column name
'description',
'image',
];
public function parentEntity() : BelongsTo // renamed from `parent()`
{
return $this->belongsTo(Category::class, 'parent');
}
}
or version with renamed column:
class Category extends BaseModel
{
protected $fillable = [
'name',
'parent_id', // renamed the DB column name
'description',
'image',
];
public function parent() : BelongsTo // now can use `parent()` since the column is `parent_id`
{
return $this->belongsTo(Category::class, 'parent');
}
}
Upvotes: 0
Reputation: 21
I see a couple of problems here:
Why use CategoryResource::collection on a HasOne relationship?
And, that parent could be nested creating an N+1 problem, you should use the resources conditionals when possible: https://laravel.com/docs/9.x/eloquent-resources#conditional-relationships
Edit: So applying this will prevent to load further down that Category::parent
relationship:
public function toArray($request): array
{
return [
'id' => (string)$this->id,
'type' => 'categories',
'attributes' => [
'name' => $this->name,
'slug' => $this->slug,
'parent' => new CategoriesResource($this->whenLoaded('parent')),
'description' => $this->description,
'image' => $this->image,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
]
];
}
Upvotes: 2