
本文介绍如何改造原有的单目录递归扫描函数,使其支持传入目录路径数组,一次性扫描多个根目录下的所有文件与子目录,并保持原有过滤、递归及类型控制逻辑。
在实际开发中,我们常需批量扫描多个不相关的目录(例如:/var/log/app1、/var/log/app2、/tmp/cache),而原 scanFiles() 函数仅接受单一字符串路径,无法直接满足该需求。最简洁且兼容性强的解决方案是:将参数 $directory 改为可接受字符串或数组类型,并在函数入口做类型判断与统一处理——而非强制要求调用方始终传数组(兼顾向后兼容性)。
以下是优化后的完整实现,支持单路径(string)和多路径(array)两种调用方式:
public static function scanFiles($directories, $recursive = true, $listDirs = false, $listFiles = true, $exclude = '') {
$arrayItems = [];
// 兼容单路径字符串:自动转为单元素数组
$dirs = is_string($directories) ? [$directories] : $directories;
foreach ($dirs as $directory) {
// 跳过空路径或非目录
if (!is_dir($directory) || !is_readable($directory)) {
continue;
}
$handle = opendir($directory);
if (!$handle) {
continue;
}
while (false !== ($file = readdir($handle))) {
// 默认排除项:. / .. / .git / .svn / .md / Thumbs.db / .DS_Store / .html
$skip = preg_match("/(^(([\.]){1,2})$|(\.(svn|git|md))|(Thumbs\.db|\.DS_STORE|\.html))$/iu", $file);
// 自定义正则排除(如需)
$skipByExclude = false;
if ($exclude && !empty($exclude)) {
$skipByExclude = (bool) preg_match($exclude, $file);
}
if (!$skip && !$skipByExclude) {
$fullPath = $directory . DS . $file;
if (is_dir($fullPath)) {
if ($recursive) {
// 递归扫描子目录(注意:此处仍传单路径字符串,递归调用会再次自动适配)
$arrayItems = array_merge($arrayItems, self::scanFiles($fullPath, $recursive, $listDirs, $listFiles, $exclude));
}
if ($listDirs) {
$arrayItems[] = $fullPath;
}
} else {
if ($listFiles) {
$arrayItems[] = $fullPath;
}
}
}
}
closedir($handle);
}
return $arrayItems;
}✅ 使用示例:
// 扫描单个目录(完全兼容旧用法)
$files = MyScanner::scanFiles('/var/www/html');
// 扫描多个目录
$paths = ['/var/log/nginx', '/var/log/php-fpm', '/tmp/uploads'];
$allFiles = MyScanner::scanFiles($paths, true, false, true, '/^test_.*\.log$/i');
// 同时列出目录和文件(含递归)
$mixed = MyScanner::scanFiles(['/src', '/tests'], true, true, true);⚠️ 注意事项与最佳实践:
立即学习“PHP免费学习笔记(深入)”;
- 路径安全性:调用前建议对 $directories 中每个路径执行 realpath() 和白名单校验,防止路径遍历攻击(尤其在用户可控输入场景);
- 性能提示:大量目录或深层嵌套时,opendir() + readdir() 效率优于 glob(),但需注意 open_basedir 限制;
- 重复路径处理:函数本身不自动去重,若传入重复路径或存在符号链接交叉,需在调用层自行 array_unique($result, SORT_STRING);
- DS 常量:确保已定义 DS(如 define('DS', DIRECTORY_SEPARATOR);),以保证跨平台兼容性;
- 错误抑制:当前代码跳过不可读目录,如需调试,可添加 error_log("Cannot read directory: $directory");。
该方案在零破坏原有接口的前提下,显著提升函数实用性,适用于日志聚合、资源打包、静态分析等典型多源扫描场景。










