scandir()配合递归是最直接的方式,需过滤.和..、校验is_dir()与is_readable()避免无限递归和警告;recursivedirectoryiterator更健壮但须try/catch捕获unexpectedvalueexception;路径安全应仅在入口处用realpath()校验,递归中避免重复解析;windows下注意max_path限制及符号链接循环。

用 scandir() 配合递归是最直接的方式
PHP 没有内置的“一键递归列出所有文件”函数,但 scandir() 返回目录下所有条目(含 . 和 ..),配合自己写的递归逻辑就能稳定工作。关键不是“能不能”,而是怎么安全跳过特殊目录、避免无限递归。
常见错误是没过滤 . 和 ..,导致当前目录和父目录被反复进入;还有没判断 is_dir() 就直接递归,遇到符号链接或权限不足时会报 Warning: scandir(): Failed to open dir。
实操建议:
- 每次进入子目录前,先用
is_dir()和is_readable()双重校验 - 显式跳过
.和..—— 不要依赖array_diff()后再遍历,容易漏掉其他隐藏项 - 把路径拼接统一交给
dirname(__FILE__)或realpath()开头,避免相对路径歧义
function listFiles($dir) {
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = $dir . '/' . $item;
if (is_dir($path)) {
echo "DIR: $path
";
listFiles($path); // 递归
} else {
echo "FILE: $path
";
}
}
}
用 RecursiveDirectoryIterator 更健壮但要注意异常捕获
面向对象方式更接近现代 PHP 风格,RecursiveDirectoryIterator 自动跳过 ./..,也支持 SkipDots 标志,但它在遇到不可读目录时默认抛出 UnexpectedValueException,不 catch 就会中断整个遍历。
适用场景:需要过滤特定扩展名、按修改时间排序、或后续要转成 RecursiveIteratorIterator 做扁平化处理。
实操建议:
- 必须用
try/catch包裹foreach循环体,不能只包构造函数 - 用
getSubPathname()而非getPathname()获取相对路径,方便控制输出粒度 - 如果只需要文件(不含目录),加一层
isFile()判断,别只靠isDir()取反
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($it as $file) {
try {
if ($file->isFile()) {
echo "FILE: " . $file->getPathname() . "
";
}
} catch (UnexpectedValueException $e) {
// 权限不足或路径失效,跳过不中断
continue;
}
}
性能和路径安全:别在递归里反复调用 realpath()
有人习惯在每次递归入口都对 $dir 调用 realpath() 防止路径穿越,但这个函数开销不小,尤其在深层嵌套时会明显拖慢速度。更糟的是,如果传入的是软链接且目标不可达,realpath() 会返回 false,导致后续 scandir() 报错。
真正需要防御的是用户输入的初始路径。只要入口处做一次校验(比如检查是否在允许根目录下),递归过程中保持路径拼接干净即可。
实操建议:
- 入口处用
realpath($input)+strpos()判断是否在白名单根目录内 - 递归中统一用
$dir . '/' . $item拼接,不重复解析 - 避免在循环体内调用
file_exists()或is_writable()等 I/O 函数,能合并判断就合并
Windows 下注意路径分隔符和长路径限制
PHP 在 Windows 上对超过 260 字符的路径(MAX_PATH)支持不稳定,scandir() 可能静默失败或返回空数组,而 RecursiveDirectoryIterator 有时会抛出“Invalid argument”错误。这不是代码写错了,是系统级限制。
没有银弹,但可降低风险:
- 用
str_replace('\', '/', $dir)统一为正斜杠,减少跨平台差异 - 在调用前用
strlen($dir) > 240做粗略预警(留 20 字符给子路径) - 如需处理超长路径,考虑用
\?C:ullpath前缀调用exec()走 PowerShell,但得权衡安全性
最常被忽略的其实是符号链接循环:A → B → A 这种结构,RecursiveDirectoryIterator 默认不检测,scandir() 递归也不防——得靠记录已访问的 realpath() 哈希值来断循环,但代价是内存增长。真遇到,优先确认是否必须遍历符号链接。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











