recursiveDirectoryIterator需配合RecursiveIteratorIterator使用,显式传SKIP_DOTS避免无限循环,CHILD_FIRST适合清理;数组递归需判非数组终止,防栈溢出;array_walk_recursive仅处理叶子值;JSON需防循环引用。

用 recursiveIteratorIterator 配合 recursiveDirectoryIterator 遍历目录树
PHP 原生不提供“递归遍历任意嵌套节点”的通用函数,但对文件系统这种典型树形结构,recursiveDirectoryIterator 是最稳妥的选择。它天然支持符号链接处理、跳过 ./..、按需过滤类型,比手写 scandir() + 递归函数更健壮。
常见错误是直接用 foreach 遍历 recursiveDirectoryIterator 实例——它本身不可直接遍历,必须包一层 recursiveIteratorIterator:
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/path', RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($it as $file) {
echo $file->getPathname() . "\n";
}
-
RecursiveDirectoryIterator::SKIP_DOTS必须显式传入,否则会把.和..当作真实目录遍历,导致无限循环 -
CHILD_FIRST比默认的SELF_FIRST更适合清理场景(先处理子项再删父目录) - 若需只取文件或只取目录,用
$file->isFile()或$file->isDir()判断,别依赖扩展名
手写递归函数遍历多维数组节点
当数据是 PHP 数组(比如从 JSON 解析来的树状结构),直接写递归函数最灵活。关键不是“能不能递归”,而是“怎么控制深度和中断”。
典型陷阱是没设终止条件或误判叶子节点:
立即学习“PHP免费学习笔记(深入)”;
function traverseArray($data, $depth = 0) {
if (!is_array($data)) {
echo str_repeat(' ', $depth) . "leaf: {$data}\n";
return;
}
foreach ($data as $key => $value) {
echo str_repeat(' ', $depth) . "node: {$key}\n";
traverseArray($value, $depth + 1);
}
}
- 必须先判断
!is_array($data),否则遇到字符串、数字等非数组值会报Invalid argument supplied for foreach() - 如果数组可能含对象(如
stdClass),需额外加is_object($data) && method_exists($data, '__toString')分支 - 深度过大时(如 >100 层),PHP 默认栈限制会触发
Fatal error: Maximum function nesting level,此时应改用栈模拟递归
用 array_walk_recursive() 处理纯值节点(不保留键和层级)
如果你只关心所有叶子值(比如批量转义 HTML、统一编码),array_walk_recursive() 是最轻量的选择。但它会跳过中间的数组键,也完全丢失层级信息。
它和手写递归的核心区别在于:不递归进入关联数组的键名,只深入到最终非数组/非对象的值。
$tree = ['a' => ['b' => 'val1'], 'c' => 'val2'];
array_walk_recursive($tree, function($val, $key) {
echo "{$key}: {$val}\n"; // 输出 b: val1 和 c: val2,但 a 不会出现
});
- 无法区分
'b' => 'val1'是二级键还是顶级键,所有键一视同仁 - 遇到对象时,仅当对象实现了
Traversable才会被展开;否则整个对象作为叶子值传入回调 - 不能修改原数组的键结构(它内部是值拷贝),要改结构必须用引用:
function(&$val, $key)
JSON 树结构递归解析时注意循环引用
从 API 或数据库拿到的 JSON 数据若存在父子双向引用(如组织架构中部门有 parent_id 又有 children 数组),直接递归解析可能陷入死循环。
PHP 的 json_decode() 默认不会检测循环引用,需要手动标记已访问节点:
function safeTraverse($node, &$seen = []) {
$id = is_object($node) ? spl_object_id($node) : (is_array($node) ? md5(serialize($node)) : null);
if ($id && isset($seen[$id])) return;
$seen[$id] = true;
// ... 处理逻辑
}
- 对数组用
md5(serialize($node))不可靠(顺序敏感、大数组性能差),更稳妥的是在原始数据里加唯一id字段并用它做键 - 如果数据来自不可信来源,优先考虑用迭代方式 + 显式栈管理,避免爆栈
-
json_decode($json, true)返回数组后,就不再有对象引用问题,但要注意数组内是否嵌套了stdClass对象



















