scandir()配合递归是最直接可靠的PHP目录遍历方式,需手动过滤.和..、检查目录可读性及限制递归深度以防爆栈,比RecursiveDirectoryIterator更稳定、比glob()更兼容且可控。

用 scandir() + 递归是最直接的实现方式
PHP 原生没有内置“递归列出所有文件”的函数,但 scandir() 配合自己写的递归逻辑足够可靠、可控,且不依赖扩展。它返回当前目录下所有条目(含 . 和 ..),需手动过滤;相比 glob() 或 RecursiveIteratorIterator,它更轻量、更易调试。
关键点在于:每次进入子目录前,先判断是否为目录(is_dir())、是否可读(is_readable()),避免权限错误中断整个流程。
示例核心逻辑:
function scanDir($path, &$files = []) {
$items = scandir($path);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$full = $path . '/' . $item;
if (is_dir($full) && is_readable($full)) {
scanDir($full, $files); // 递归进入
} elseif (is_file($full)) {
$files[] = $full; // 只存文件,不含目录
}
}
return $files;
}
为什么不用 RecursiveDirectoryIterator?
它确实能一行写出递归遍历,但实际项目中容易踩坑:默认会抛出异常(比如遇到无权限目录或符号链接断裂),不加 try/catch 就崩;而且返回的是迭代器对象,不是简单数组,后续处理(如 array_filter)要多绕一层。
立即学习“PHP免费学习笔记(深入)”;
常见报错:Uncaught UnexpectedValueException: RecursiveDirectoryIterator::__construct(...): failed to open dir —— 这不是你代码写错了,是它没帮你兜住系统级访问失败。
如果坚持用它,至少得这样包一层:
function safeScanWithIterator($path) {
$files = [];
try {
$iter = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS)
);
foreach ($iter as $file) {
if ($file->isFile()) {
$files[] = (string)$file;
}
}
} catch (Exception $e) {
// 忽略单个目录失败,继续其他分支
}
return $files;
}
glob() 的递归模式在 PHP 5.6+ 可用,但有路径长度和性能隐患
glob($path . '/**/*.php', GLOB_BRACE | GLOB_ONLYDIR) 看似简洁,但注意两点:
-
**是 shell 风格通配符,PHP 仅从 5.6 开始支持,低版本直接返回空数组,不报错也不提示 - 深层嵌套目录(比如 20 层)可能触发
glob()内部栈溢出或超时,尤其在 NFS 或慢速挂载文件系统上 - 它无法跳过不可读目录 —— 遇到权限问题就静默跳过该分支,你甚至不知道漏了哪些
所以它适合已知结构扁平、路径可信的小范围扫描,不适合通用工具函数。
递归深度过大时,必须考虑内存和超时
PHP 默认栈深度有限(通常 100 层左右),极深目录(如 /var/log/journal/xxx/xxx/...)可能导致 Fatal error: Maximum function nesting level of '100' reached。这不是死循环,是递归调用帧太多。
解决办法不是调高 xdebug.max_nesting_level(那只是掩盖问题),而是改用迭代方式(栈模拟)或加深度限制:
在递归函数里加一个 $depth 参数:
function scanDir($path, &$files = [], $depth = 0, $maxDepth = 16) {
if ($depth > $maxDepth) return;
// ... 后续逻辑不变
if (is_dir($full) && is_readable($full)) {
scanDir($full, $files, $depth + 1, $maxDepth);
}
}
这个 $maxDepth = 16 是经验值:Linux 默认最大路径深度约 40,但绝大多数项目目录不会超过 8 层;设成 16 足够覆盖异常情况,又防爆栈。
真正难处理的不是深度,而是符号链接循环引用 —— 比如 A 目录里软链到 B,B 又软链回 A。scandir() 不会自动检测,需要你自己用 realpath() + 全局 visited 集合去重,否则无限递归。这点几乎没人提,但线上真出过。



















