递归统计多层数组元素总数须用 is_array() 判断类型,非数组返回 0 或计 1,避免 foreach/count 对非数组调用报错;count($arr, COUNT_RECURSIVE) 会重复计数嵌套数组本身,不适用;深度过大时应改用迭代或设深度阈值。

递归函数里用 is_array() 判断是否继续深入
PHP 递归统计多层数组元素总数,核心是识别「当前项是不是数组」——不是就计 1,是就递归调用自己。关键在于不能误把字符串、对象或 null 当成数组处理,否则会触发警告甚至死循环。is_array() 是唯一可靠判断方式,isset() 或 !empty() 都不适用。
常见错误:直接对变量做 foreach 而不先判断类型,遇到字符串时 PHP 会抛出 Warning: Invalid argument supplied for foreach();或者用 count() 对非数组调用,返回 0 导致漏计。
实操建议:
- 函数入口统一接收一个参数(如
$arr),第一行就用if (!is_array($arr)) return 0;快速兜底 - 避免在递归体中修改原数组(如
array_shift()),会破坏结构且影响可读性 - 不要依赖全局变量累计,易出竞态,坚持用返回值累加
array_walk_recursive() 更快但无法区分键值类型
如果数组结构干净(纯数值/字符串/嵌套数组,不含对象、资源、闭包),array_walk_recursive() 是更轻量的选择:它自动跳过非数组子项,只把叶子节点传给回调函数,省去手动判断开销。
立即学习“PHP免费学习笔记(深入)”;
但它有硬伤:所有标量值(包括整数 0、布尔 false、空字符串)都会被当成有效元素计入,且无法知道某个值原本是键还是值,也不支持跳过特定类型(比如你想忽略 null 或 '')。
实操建议:
- 确认数据源可控时再用,例如配置数组、JSON 解析后的结果
- 配合引用计数变量:用
$count = 0; array_walk_recursive($data, function($v) use (&$count) { $count++; }); - 若需过滤(如排除
null),必须改用自定义递归函数,因为array_walk_recursive()不提供上下文判断能力
注意 count() 的第三个参数和递归陷阱
PHP 7.2+ 的 count() 支持第三个参数 COUNT_RECURSIVE,看似能一行解决:count($arr, COUNT_RECURSIVE)。但它统计的是「所有层级的数组元素个数」,包括中间层的数组本身——也就是说,每个嵌套数组会被算作一个「元素」,同时它的内容又被展开计数,造成重复。
例如:[1, [2, 3]] 用 count($arr, COUNT_RECURSIVE) 返回 4(1 + [2,3] + 2 + 3),而实际叶子元素只有 3 个。这不是 bug,是设计如此,但不符合「统计所有最终值数量」的需求。
实操建议:
- 永远别用
count($arr, COUNT_RECURSIVE)替代递归计数逻辑 - 如果真要借用
count(),只能用于单层判定(如count($arr) === 0判断空数组),不能依赖其递归模式 - 开启
declare(strict_types=1);后,传非数组给count()会报TypeError,这点比is_array()更严格,也更早暴露问题
深度大时要考虑栈溢出和性能边界
PHP 默认递归限制约 100 层(受 xdebug.max_nesting_level 或 zend_extension 影响),超深嵌套数组(如解析大型 JSON 树、无限层级菜单)可能直接 Fatal error: Maximum function nesting level。
此时递归不是写法问题,而是架构问题。强行调高限制只是掩耳盗铃,更稳的方式是改用栈模拟递归(while + array_pop())或分批处理。
实操建议:
- 上线前用
print_r($arr)或json_encode($arr)观察实际嵌套深度,别靠猜 - 简单场景下,加一层保护:递归函数内用静态变量记录当前深度,超过阈值(如 50)就
trigger_error()并返回 0 - 对超大结构,优先考虑迭代方案,哪怕多写几行 —— 稳定性比代码短更重要
真正麻烦的不是写不出递归函数,而是没想清楚「要数什么」:是所有标量值?是否排除 null 和空字符串?是否要跳过某些键名?这些业务逻辑一旦混进通用计数函数,很快就会变成谁都不敢动的祖传代码。



















