
本文详解如何在 Laravel 中通过 Eloquent 关系预加载(with)结合数组映射,将分类(Category)及其关联页面(pages)构造成带 children 嵌套结构的层级数组,避免平铺式错误拼接。
本文详解如何在 laravel 中通过 eloquent 关系预加载(with)结合数组映射,将分类(category)及其关联页面(pages)构造成带 `children` 嵌套结构的层级数组,避免平铺式错误拼接。
在 Laravel 开发中,常需将一对多关系(如 Category → Page)转换为前端友好的树形结构。原始代码的问题在于:外层 foreach 将每个 category 和其 pages 分别追加到 $data 数组中,导致分类项与子页面项并列平铺,无法体现隶属关系;且 children 被错误地定义为单个对象而非数组,违背了典型的嵌套菜单/导航数据规范。
正确的做法是:为每个分类构造一个独立数组项,并在其内部以 children 键承载所有关联页面的映射结果。推荐使用 array_map() 配合匿名函数进行清晰、函数式的数据转换,同时用 array_values() 确保 children 为索引数组(避免因模型集合键名不连续导致 JSON 序列化异常)。
以下是优化后的完整实现:
public function getCategory($lang): array
{
$data = [];
$categories = Category::where('language', $lang)->with('pages')->get();
foreach ($categories as $category) {
$item = [
'title' => $category->title,
'path' => $category->slug,
];
// 仅当存在子页面时才添加 children 字段
if ($category->pages->isNotEmpty()) {
$item['children'] = $category->pages->map(function ($page) {
return [
'title' => $page->title,
'path' => $page->slug,
];
})->values()->toArray(); // ->values() 重置键为数字索引,->toArray() 转为纯数组
}
$data[] = $item;
}
return $data;
}
✅ 关键改进点说明:
- 使用 Eloquent 访问器($category->title)替代数组下标($category['title']),更安全、支持访问器与属性转换;
- 利用 Laravel 集合的 map() 和 values() 方法替代原生 array_map() + array_values(),语义更清晰、链式调用更简洁;
- 通过 isNotEmpty() 判断替代 !empty($category['pages']),兼容 Eloquent Collection 类型,避免潜在类型警告;
- children 始终为数组(即使仅一项),符合前端组件(如 Vue 的
或 Ant Design 的 Tree)对 children 字段的约定。
⚠️ 注意事项:
- 确保 Category 模型已正确定义 pages 关系:public function pages() { return $this->hasMany(Page::class); };
- 若页面需按特定顺序展示,应在 with('pages') 前添加 orderBy,或在 map() 前调用 sortBy('sort_order');
- 如数据量较大,建议增加缓存(如 Cache::remember())避免重复查询。
最终输出结构示例如下,严格满足嵌套导航需求:
[
{
"title": "corporate",
"path": "corporate",
"children": [
{ "title": "corporatealt", "path": "corporatealt" },
{ "title": "corporatealt2", "path": "corporatealt2" }
]
},
{
"title": "blabla",
"path": "blabla"
}
]










