Laravel Category资源嵌套自关联实现及循环避免问题咨询
分类资源嵌套父分类及解决无限循环问题
问题背景
现有Category资源和模型,需要将接口返回中的父分类ID替换为嵌套的父分类资源结构,但修改后出现Attempt to read property "id" on int错误,同时需要避免父分类继续返回其自身父分类导致的无限循环。
原始资源代码
<?php namespace Domain\Category\Resources; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; class CategoriesResource extends JsonResource { 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, ] ]; } }
错误的模型关联代码
<?php namespace Domain\Category\Models; use Domain\Product\Models\Product; use Domain\Shared\BaseModel; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Str; class Category extends BaseModel { protected $fillable = [ 'name', 'parent', 'description', 'image', ]; // 方法名与数据库字段名冲突 public function parent() : BelongsTo { return $this->belongsTo(Category::class, 'parent'); } }
修改后的资源(引发错误)
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, ] ]; }
错误信息
Attempt to read property "id" on int
解决方案
1. 修复"Attempt to read property 'id' on int"错误
核心原因:模型的关联方法名parent与数据库字段名parent完全一致,Eloquent会优先返回数据库字段的整数值,而非关联模型实例,导致将整数传入CategoriesResource时,资源尝试读取id属性引发错误。
解决步骤:
修改模型中的关联方法名,避免与字段名冲突,例如改为parentCategory:
<?php namespace Domain\Category\Models; use Domain\Product\Models\Product; use Domain\Shared\BaseModel; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Str; class Category extends BaseModel { protected $fillable = [ 'name', 'parent', 'description', 'image', ]; // 修改关联方法名,避免字段名冲突 public function parentCategory() : BelongsTo { return $this->belongsTo(Category::class, 'parent'); } }
2. 实现嵌套父分类并避免无限循环
有两种常用方式实现需求,同时避免无限循环:
方式一:在主资源中直接控制父分类返回结构
在资源中使用whenLoaded判断关联是否加载,同时为父分类返回不包含parent字段的简化结构:
public function toArray($request): array { return [ 'id' => (string)$this->id, 'type' => 'categories', 'attributes' => [ 'name' => $this->name, 'slug' => $this->slug, // 仅当关联加载时返回嵌套结构,否则返回原始ID 'parent' => $this->whenLoaded('parentCategory', function () { return [ 'id' => (string)$this->parentCategory->id, 'type' => 'categories', 'attributes' => [ 'name' => $this->parentCategory->name, 'slug' => $this->parentCategory->slug, 'description' => $this->parentCategory->description, 'image' => $this->parentCategory->image, 'created_at' => $this->parentCategory->created_at, 'updated_at' => $this->parentCategory->updated_at, // 不返回parent字段,避免无限循环 ] ]; }, $this->parent), 'description' => $this->description, 'image' => $this->image, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ] ]; }
方式二:创建单独的父分类资源类
创建一个不包含parent字段的简化资源类,专门用于返回父分类数据:
// Domain\Category\Resources\ParentCategoryResource.php <?php namespace Domain\Category\Resources; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; class ParentCategoryResource extends JsonResource { public function toArray($request): array { return [ 'id' => (string)$this->id, 'type' => 'categories', 'attributes' => [ 'name' => $this->name, 'slug' => $this->slug, 'description' => $this->description, 'image' => $this->image, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, // 省略parent字段,彻底避免循环 ] ]; } }
然后在主资源中调用该简化资源:
public function toArray($request): array { return [ 'id' => (string)$this->id, 'type' => 'categories', 'attributes' => [ 'name' => $this->name, 'slug' => $this->slug, 'parent' => $this->whenLoaded('parentCategory', function () { return new ParentCategoryResource($this->parentCategory); }, $this->parent), 'description' => $this->description, 'image' => $this->image, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ] ]; }
3. 预加载关联避免N+1查询
在控制器查询分类时,务必预加载parentCategory关联,避免数据库性能问题:
// 控制器示例代码 $category = Category::with('parentCategory')->find($id); return new CategoriesResource($category);
内容的提问来源于stack exchange,提问作者MrCujo
相关产品推荐
相关产品推荐

