核心逻辑是:遇数组则递归,遇非数组则存入引用容器;不依赖深度预判或索引,仅以is_array()为唯一判断标准,确保只收集最深层的标量值。

直接用递归函数一层层钻到底,直到遇到的值不再是数组——那就是最后一级的“叶子节点”。不需要预判深度,也不依赖 count() 或索引,关键在每次进入前做 is_array() 判断。
怎么写一个只取最后一级值的递归函数
核心逻辑是:遇到数组就递归进去;遇到非数组就收集。不输出键、不保留结构,只拿最终的标量值(字符串、数字、布尔等)。
- 函数必须接收数组和一个引用结果容器(如
&$leaves),否则无法跨层级累积结果 - 不能用
return直接返回单个值,因为最后一级可能有多个元素 - 避免在递归调用后还执行后续逻辑(比如误把父级数组当叶子处理)
示例:
function getLeaves($array, &$leaves = []) {
foreach ($array as $value) {
if (is_array($value)) {
getLeaves($value, $leaves); // 继续往下钻
} else {
$leaves[] = $value; // ✅ 确认是最后一级,才存
}
}
}
// 调用
$multi = [1, [2, 3], [4, [5, 6, [7]]]];
$leaves = [];
getLeaves($multi, $leaves);
// $leaves === [1, 2, 3, 4, 5, 6, 7]
为什么不用 array_walk_recursive
array_walk_recursive 看似省事,但它跳过所有数组类型的值,只处理“叶子”,这点没错——但问题在于它无法区分“中间数组”和“最后一级数组”。比如你有个结构:['a' => ['b' => ['c' => 1]]],它确实会拿到 1;但如果你的数组里混着像 ['x' => [], 'y' => 42] 这种空数组,array_walk_recursive 会直接忽略 [],而你的自定义递归函数可以明确判断并决定是否把空数组视作“最后一级”(通常不视作,除非业务要求)。
立即学习“PHP免费学习笔记(深入)”;
- 它不提供当前深度、路径或父键信息,没法做条件过滤(例如“只要 depth === 3 的叶子”)
- 无法修改原数组值(除非用引用回调,但依然改不了结构)
- 如果数组含对象或资源,会跳过,且不报错——容易漏数据
容易踩的坑:无限递归和引用循环
PHP 本身不会自动检测数组里的循环引用(比如 $arr['self'] = &$arr),一旦存在,你的递归函数就会卡死或爆栈。
- 简单防御:加一个深度限制参数(如
$maxDepth = 20),到上限就return - 更稳妥的做法是维护一个已访问数组的 ID 列表,用
spl_object_hash()对数组做哈希标记(注意:普通数组没有对象哈希,需先转成ArrayObject) - 开发期可用
xdebug.max_nesting_level提前暴露问题,但生产环境别靠它兜底
最后一级不是靠“层数最多”来定义的,而是由 is_array($value) === false 这一判断唯一确定的。任何试图用 end()、key() 或路径字符串解析去“找最后一级”的方案,本质上都混淆了“末尾位置”和“最深层级”——前者是线性顺序概念,后者是树状结构属性。



















