处理嵌套数组,很多人第一反应是 array_walk_recursive,但真用它来“展平”数组时,往往会碰一鼻子灰。最稳妥的方案,还是自己写一个递归函数——既能控制深度,又能决定是否保留键名,远比依赖内置函数灵活。

一句话总结:array_walk_recursive 只负责“遍历叶子节点”,不负责“重组结构”,它根本就不是为展平而生的。
为什么 array_walk_recursive 不能用来展平数组
它的设计目标就是“访问”所有标量值,而不是“重组”数组。遇到子数组,它直接跳进去递归,但不会把子数组里的元素提到上一层来。举个例子:
- 输入
[1, [2, [3, 4]], 5],array_walk_recursive确实能逐个拿到1、2、3、4、5,但你要它生成一个[1,2,3,4,5]的新数组?它做不到。 - 它没有返回值,所有修改只能靠引用传参或闭包捕获变量,代码写出来又绕又容易出错。
- 更关键的是,它无法控制展平深度(比如只展平一层),也不支持保留键名或丢弃键名这些常见需求。
说白了,它只是一个“遍历器”,不是“转换器”。
手动递归展平:支持键名保留与深度控制
自己写递归函数,可控性最强,逻辑也最直观。下面这个版本默认展平全部层级,同时支持两个可选参数:
function flatten_array($arr, $depth = null, $preserve_keys = true) {
$result = [];
foreach ($arr as $key => $value) {
if (is_array($value) && ($depth === null || $depth > 0)) {
$sub = flatten_array($value, $depth === null ? null : $depth - 1, $preserve_keys);
if ($preserve_keys) {
$result += $sub; // 合并时保留键名,可能覆盖同名键
} else {
$result = array_merge($result, $sub);
}
} else {
$result[$preserve_keys ? $key : count($result)] = $value;
}
}
return $result;
}
- 调用
flatten_array([1, [2, [3]]])返回[1,2,3]。 - 将
$preserve_keys设为false,可以避免键名冲突,适合纯数值索引的场景。 - 如果只想展平一层,设
$depth = 1:输入[1, [2, [3]], [4]]返回[1,2,[3],4]。
用 array_reduce + 匿名递归(PHP 7.4+)实现无副作用展平
如果你偏爱函数式风格,且 PHP 版本不低于 7.4,可以用 array_reduce 配合自引用匿名函数,避免在全局命名空间里定义函数:
$flatten = function ($arr, $depth = null) use (&$flatten) {
return array_reduce($arr, function ($carry, $item) use ($flatten, $depth) {
if (is_array($item) && ($depth === null || $depth > 0)) {
return array_merge($carry, $flatten($item, $depth === null ? null : $depth - 1));
}
$carry[] = $item;
return $carry;
}, []);
};
- 这个版本天生不保留原始键名,适合简单的数值展平需求。
- 注意:PHP 7.4 才支持在
use中引用自身匿名函数;低版本会报Cannot use lexical variable $flatten by reference。 - 性能上略低于普通递归函数,但语义更清晰,也没有副作用。
最后说一个容易被忽略的坑:大部分“展平失败”的案例,其实不是因为递归写法有问题,而是没意识到 PHP 数组键名不会自动重排。展平后如果数字键有冲突,array_values() 或 array_merge([]) 才是重置索引的正确姿势——别指望哪个递归函数能帮你自动处理这件事。