
本文详解如何用 php 的 scandir() 编写健壮的递归目录遍历函数,解决因路径拼接缺失、`.`/`..` 处理不当及参数类型混淆导致的“foreach() argument must be of type array|object”警告。
原始代码存在两个关键错误:
- 参数类型错乱:首次调用 scan($dir) 时传入的是 scandir('.') 返回的数组,但递归调用 scan($path) 时 $path 是相对文件名(如 "images"),而非完整路径;scandir($path) 尝试在当前工作目录下打开该子目录,若路径不存在或权限不足,则返回 false,导致后续 foreach(false) 触发致命警告。
- 未过滤特殊目录项:scandir() 总会返回 '.'(当前目录)和 '..'(父目录),若不显式跳过,将引发无限递归或跨目录遍历风险。
✅ 正确做法是:每次递归前,先用 scandir() 获取目标目录下的真实条目,并基于完整路径判断类型。需使用 realpath() 或手动拼接路径(推荐 dirname($location) . '/' . $path 或更安全的 rtrim($location, '/') . '/' . $path),同时严格过滤 '.' 和 '..'。
以下是修复后的完整可运行示例:
function scan($location) {
// 确保路径存在且为目录
if (!is_dir($location)) {
return;
}
// 获取当前目录下所有条目
$entries = scandir($location);
if ($entries === false) {
echo "Warning: Cannot read directory '$location'
";
return;
}
foreach ($entries as $entry) {
// 跳过当前目录和上级目录
if ($entry === '.' || $entry === '..') {
continue;
}
$fullPath = rtrim($location, '/') . '/' . $entry;
if (is_dir($fullPath)) {
echo "? $entry/
"; // 可视化层级
scan($fullPath); // 递归进入子目录
} else {
echo "? $entry
";
}
}
}
// 启动遍历(从当前目录开始)
scan('.');? 进阶建议:
- 若需更高性能与健壮性,推荐使用 SPL 迭代器,例如 RecursiveDirectoryIterator + RecursiveIteratorIterator,它自动处理符号链接、权限异常并支持过滤规则;
- 使用 DirectoryIterator 可替代基础 scandir(),代码更简洁且面向对象;
- 生产环境务必添加错误抑制(如 @scandir())或异常捕获,并对 is_dir() / is_readable() 做双重校验,避免因权限问题中断遍历。
总结:递归目录扫描的核心在于——始终操作绝对/规范路径、主动过滤元目录项、确保每次 scandir() 输入合法目录、并对返回值做空值检查。遵循这三点,即可写出稳定、可维护的目录树遍历逻辑。










