如何使用 scandir() 实现安全可靠的递归目录遍历

4次阅读

如何使用 scandir() 实现安全可靠的递归目录遍历

本文详解如何用 php 的 scandir() 编写健壮的递归目录遍历函数,解决因路径拼接缺失、`.`/`..` 处理不当及参数类型混淆导致的“foreach() argument must be of type Array|Object”警告。

原始代码存在两个关键错误:

  1. 参数类型错乱:首次调用 scan($dir) 时传入的是 scandir(‘.’) 返回的数组,但递归调用 scan($path) 时 $path 是相对文件名(如 “images”),而非完整路径;scandir($path) 尝试在当前工作目录下打开该子目录,若路径不存在或权限不足,则返回 false,导致后续 foreach(false) 触发致命警告。
  2. 未过滤特殊目录项: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() 输入合法目录、并对返回值做空值检查。遵循这三点,即可写出稳定、可维护的目录树遍历逻辑。

text=ZqhQzanResources