
本文详解如何正确使用 scandir() 编写递归目录遍历函数,解决因路径类型混淆导致的“foreach() argument must be of type array|object”错误,并提供健壮、可读性强的实现方案。
在 PHP 中,scandir() 是一个基础但易出错的目录读取函数。初学者常犯的关键错误是:将字符串路径(如子目录名)直接传入 foreach 循环,而未重新调用 scandir() 获取其子项数组。原始代码中,首次调用 scan($dir) 时 $dir 是数组(合法),但递归调用 scan($path) 时 $path 是字符串(如 "images"),导致 foreach($path as ...) 报错。
此外,scandir() 默认返回包含 .(当前目录)和 ..(父目录)的数组,若不显式跳过,会引发无限递归或权限错误。
✅ 正确做法是:每次递归前,先用 scandir() 读取目标目录的完整路径内容,并拼接绝对/相对路径以确保 is_dir() 判断准确。以下是修复后的标准实现:
function scan($location) {
// 1. 使用 scandir 获取当前目录下的所有条目(返回数组或 false)
$entries = scandir($location);
// 2. 检查 scandir 是否执行成功
if ($entries === false) {
error_log("Failed to scan directory: $location");
return;
}
foreach ($entries as $entry) {
// 3. 跳过特殊目录项:. 和 ..
if ($entry === '.' || $entry === '..') {
continue;
}
// 4. 构建完整路径(关键!避免相对路径歧义)
$fullPath = $location . DIRECTORY_SEPARATOR . $entry;
// 5. 使用完整路径判断是否为目录
if (is_dir($fullPath)) {
echo str_repeat(' ', substr_count($location, DIRECTORY_SEPARATOR) * 2) . "? $entry/\n
";
scan($fullPath); // 递归进入子目录
} else {
echo str_repeat(' ', substr_count($location, DIRECTORY_SEPARATOR) * 2) . "? $entry\n
";
}
}
}
// 启动遍历(从当前目录开始)
scan('.');? 关键要点总结:
立即学习“PHP免费学习笔记(深入)”;
- ✅ 始终在递归函数内部调用 scandir($location),而非复用上层传入的字符串;
- ✅ 使用 DIRECTORY_SEPARATOR 替代硬编码 / 或 \,提升跨平台兼容性;
- ✅ 构建 $fullPath 再调用 is_dir(),否则 is_dir("subdir") 在非当前工作目录下会失败;
- ✅ 添加 scandir() 返回值校验,防止因权限不足等导致 false 引发后续逻辑崩溃;
- ✅ 可选增强:通过缩进层级(如 substr_count 计算深度)可视化目录树结构。
? 进阶建议:对于生产环境,推荐使用更现代、面向对象的替代方案,例如:
- DirectoryIterator:支持迭代器接口,自动跳过 . 和 ..;
- FilesystemIterator:DirectoryIterator 的增强版,可配置忽略特定项;
- RecursiveDirectoryIterator + RecursiveIteratorIterator:原生支持递归遍历,代码更简洁、性能更优。
掌握 scandir() 的正确用法,是理解 PHP 文件系统操作的基础;而善用迭代器类,则是构建健壮文件管理功能的进阶实践。











