array_walk_recursive仅遍历叶节点且丢失路径与键名,无法保留层级关系;需手动递归+路径拼接或array_reduce累积实现带parent_id/level的扁平化。

用 array_walk_recursive 会丢掉层级信息
很多人第一反应是用 array_walk_recursive 把树形数组“扁平化”,但它只遍历叶子节点,且完全丢失键名和嵌套路径。比如 ['name' => 'A', 'children' => [['name' => 'B']]] 经它处理后只剩 ['A', 'B'],父子关系、原始字段全没了。
真正需要的不是“递归取值”,而是“带路径地展开”,得自己控制遍历过程。
手动递归 + 路径拼接是最可控的方式
核心思路:每深入一层,把当前节点的字段(如 id、name)存进结果数组,并带上一个 level 字段或父级 parent_id 字段,用于还原关系。
$flat = [];
function flattenTree($tree, &$flat, $parentId = null, $level = 0) {
foreach ($tree as $item) {
$flat[] = [
'id' => $item['id'],
'name' => $item['name'],
'parent_id' => $parentId,
'level' => $level,
];
if (!empty($item['children'])) {
flattenTree($item['children'], $flat, $item['id'], $level + 1);
}
}
}
flattenTree($tree, $flat);
- 必须传引用
&$flat,否则每次递归都新建数组,结果无法累积 - 别漏掉空
children的判断,否则 PHP 会报Warning: Invalid argument supplied for foreach() - 如果原始数据用的是
pid或parent字段,记得同步改$item['parent_id']的取值逻辑
用 array_reduce + 匿名函数可避免全局变量
如果想函数式一点、不依赖外部变量,可以用 array_reduce 逐层累积:
立即学习“PHP免费学习笔记(深入)”;
$flattenTree = function($tree, $parentId = null, $level = 0) use (&$flattenTree) {
return array_reduce($tree, function($carry, $item) use ($flattenTree, $parentId, $level) {
$carry[] = [
'id' => $item['id'],
'name' => $item['name'],
'parent_id' => $parentId,
'level' => $level,
];
if (!empty($item['children'])) {
$carry = array_merge($carry, $flattenTree($item['children'], $item['id'], $level + 1));
}
return $carry;
}, []);
};
$flat = $flattenTree($tree);
- 注意
array_merge在深层嵌套时有性能开销,万级节点慎用 - PHP 7.4+ 支持箭头函数,但无法递归引用自身,所以仍得用传统匿名函数写法
- 如果原数组键不是数字索引(比如用
id当键),先用array_values转下,否则array_reduce可能跳过非数字键
注意 children 字段名不统一带来的兼容问题
实际项目里,树结构的子节点字段名五花八门:children、subs、nodes、甚至 _children。硬编码 $item['children'] 很容易出 Notice。
更健壮的做法是提前约定或动态检测:
- 在调用前传入子字段名:
flattenTree($tree, $flat, 'subs'),函数内用$childrenKey替代写死的'children' - 或者用
array_filter($item, 'is_array')找出唯一数组字段——前提是每个节点只含一个子数组字段 - 别依赖
isset($item['children']),有些接口返回'children' => null,得用!empty($item['children'])或is_array($item['children'])
树转平级本身不难,难的是怎么让转换结果能反向建树、支持排序、兼容不同后端格式——这些细节往往比递归逻辑本身更消耗调试时间。



















