本文介绍在 laravel 中如何将嵌套的树形结构数据(如无限级分类)高效转换为扁平化的 id-name 映射列表,既支持 eloquent 原生查询优化,也提供递归遍历方案,兼顾性能与可读性。
本文介绍在 laravel 中如何将嵌套的树形结构数据(如无限级分类)高效转换为扁平化的 id-name 映射列表,既支持 eloquent 原生查询优化,也提供递归遍历方案,兼顾性能与可读性。
在构建树形菜单、组织架构或分类系统时,常通过 parent_id 实现无限级递归关系,并使用 with('children') 预加载形成嵌套集合。但业务中往往还需一份
扁平化(flat)的元数据列表
——例如用于下拉选择、搜索建议或前端快速映射,此时仅需每个节点的 id 和 name,无需层级结构。
✅ 最优解:直接数据库查询(推荐)
若只需 id 和 name,
完全无需加载树形结构再递归处理
。Eloquent 提供最简洁高效的方案:
✅ 优势:
单次 SQL 查询,零 PHP 递归开销;
自动包含所有层级节点(因 select() 作用于全表);
兼容任意深度,无栈溢出风险;
可链式添加 orderBy、where 等条件。
⚠️ 注意:此方法适用于「所有节点均在同一张表」且逻辑上允许全量读取的场景。若需按树根过滤(如仅某几棵子树),仍需结合 whereNull('parent_id') 的递归查询,但扁平化步骤应移至 PHP 层。
? 备选方案:PHP 递归扁平化(适配已有嵌套结构)
当已存在 $hierarchicalData(如 with('children') 加载后的集合),需从中提取扁平列表时,可使用以下无副作用的递归函数:
? 提示:$node->relationLoaded('children') 可避免未预加载时触发 N+1 查询;$node->children 是 Collection,天然支持递归遍历。
❌ array_walk_recursive() 不适用的原因
原问题中尝试的 array_walk_recursive() 无法满足需求,因其:
仅遍历
数组值
,会丢失键名(如 'id'/'name' 字段名不可靠);
对 Eloquent 模型对象(非纯数组)遍历时行为不可控;
无法区分同名字段(如多层 name)或控制输出结构。
因此,
显式递归遍历模型集合是更安全、可维护的选择
。
总结
场景推荐方案关键代码仅需所有节点的 id+name全表 select() 查询Hierarchy::select('id','name')->get()已加载嵌套结构,需从中提取自定义递归扁平函数flattenNodes($collection)需保留父子上下文(如带 level/path)扩展递归函数传入深度参数flattenNodes($nodes, $level = 0)
最终,优先用数据库解决数据形态问题;复杂逻辑才交由 PHP 处理——这是 Laravel 应用性能与可维护性的黄金准则。
$result = Hierarchy::select('id', 'name')->get()->toArray();
// 输出示例:
// [
// ["id" => 1, "name" => "parent 1"],
// ["id" => 2, "name" => " parent 2"],
// ["id" => 21, "name" => "child A of 2"],
// ...
// ]public function flattenNodes($nodes): array
{
$result = [];
foreach ($nodes as $node) {
// 当前节点基础字段
$result[] = ['id' => $node->id, 'name' => $node->name];
// 递归处理子节点(自动处理无限深度)
if ($node->relationLoaded('children') && $node->children->isNotEmpty()) {
$result = array_merge($result, $this->flattenNodes($node->children));
}
}
return $result;
}
// 使用示例
$hierarchical = Hierarchy::whereNull('parent_id')
->with('children')
->get();
$flatList = $this->flattenNodes($hierarchical);