
本文详解如何在 Laravel JSON API 风格资源中安全地嵌套自关联模型(如 Category 的 parent 关系),通过 whenLoaded 避免空值错误、无限递归和 N+1 查询问题,并确保响应结构符合 JSON:API 规范。
本文详解如何在 laravel json api 风格资源中安全地嵌套自关联模型(如 category 的 parent 关系),通过 `whenloaded` 避免空值错误、无限递归和 n+1 查询问题,并确保响应结构符合 json:api 规范。
在构建树形结构数据(如分类目录)时,常需在资源响应中嵌套父级信息而非仅返回父 ID。但直接使用 new CategoriesResource($this->parent) 会引发两个关键问题:一是当 parent 为 null 或未加载时,CategoriesResource 构造器尝试访问 $this->id 导致 “Attempt to read property 'id' on int” 错误;二是若未显式控制加载深度,可能意外触发无限递归(如 parent → parent→parent…)或 N+1 查询。
✅ 正确做法:结合关系预加载与 whenLoaded()
首先,在控制器中必须预加载 parent 关系,避免运行时懒加载:
// 在控制器中(例如 CategoryController@show)
public function show(Category $category)
{
// 预加载 parent 关系,且仅一层(默认即如此)
$category->load('parent');
return new CategoriesResource($category);
}
接着,在 CategoriesResource 的 toArray() 方法中,使用 whenLoaded() 包裹嵌套资源:
public function toArray($request): array
{
return [
'id' => (string) $this->id,
'type' => 'categories',
'attributes' => [
'name' => $this->name,
'slug' => $this->slug,
'parent' => $this->whenLoaded('parent', fn() => new CategoriesResource($this->parent)),
'description' => $this->description,
'image' => $this->image,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
]
];
}
?
whenLoaded('parent', ...)的作用是:仅当parent关系已被 Eloquent 加载(即$this->relationLoaded('parent') === true)时,才执行闭包并实例化嵌套资源;否则该字段将被自动忽略(不会出现在响应中),彻底规避空值或原始整型 ID 引发的访问错误。
Laravel Cloud下载Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
⚠️ 关键注意事项
-
禁止在 Resource 中调用
load():资源类应保持无副作用,所有关系加载必须在控制器或服务层完成。 -
避免递归嵌套:上述方案天然防止递归——因为
CategoriesResource本身不自动加载parent.parent,除非你在控制器中显式调用load('parent.parent')。如需多层(如两级父级),应明确指定并谨慎评估性能。 -
优化 N+1:若需批量返回多个分类及其父级(如列表页),务必使用
with('parent')而非逐个加载:$categories = Category::with('parent')->get(); return CategoriesResource::collection($categories); -
处理根节点:当分类无父级(
parent_id为null或0)时,$this->parent为null,whenLoaded自动跳过,响应中parent字段将不存在,符合语义(无需返回"parent": null)。
✅ 最终效果
请求成功时,响应将严格遵循预期结构:
{
"data": {
"id": "5",
"type": "categories",
"attributes": {
"name": "Paper",
"slug": "paper",
"parent": {
"id": "1",
"type": "categories",
"attributes": {
"name": "Parent Paper",
"slug": "parent-paper",
"parent": null,
"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"
}
}
}
通过 whenLoaded() + 显式预加载,你既能获得清晰的嵌套数据,又能保障健壮性与性能,是 Laravel 资源处理自关联的标准实践。











