scandir递归遍历卡在Permission denied是因为它遇权限不足时直接返回false并报Warning,导致foreach接收空数组而中断;必须前置is_readable()校验并用DIRECTORY_SEPARATOR拼接路径、array_diff过滤.和..。

scandir 递归遍历为什么总卡在 Permission denied?
直接用 scandir() 写递归,最常遇到的不是逻辑错,而是权限中断——比如碰到 /proc/1/fd 这类目录时,scandir() 报 Warning: scandir(): Failed to open dir: Permission denied,然后整个流程就断了。
这不是你路径写错了,是 PHP 默认不帮你兜底。它不会跳过、也不会静默,而是直接 warning 并返回 false,后续 foreach 就会收到空数组或触发 Invalid argument supplied for foreach()。
- 每次调用
scandir()前,先用is_readable($dir)检查,失败就continue或记录日志,别硬闯 - 拼子路径时别用
$dir.'/'.$file硬连——Windows 下反斜杠可能被当转义,改用$dir . DIRECTORY_SEPARATOR . $file - 过滤
.和..必须用array_diff($files, ['.', '..']),不能只用in_array($item, ['.', '..'])跳过——因为scandir()返回的数组键可能是数字索引,in_array()可能漏判 - 进入子目录前,必须先
is_dir($fullPath)+is_readable($fullPath)双重校验,顺序不能反:对不可读路径调is_dir()在某些系统上会静默返回false,你以为是文件,其实只是没权限
RecursiveDirectoryIterator 为什么遍历结果里没有某些子目录?
用 RecursiveDirectoryIterator + RecursiveIteratorIterator 组合,看似省心,但常发现“明明有子目录,却没遍历到”——这不是 bug,是它的默认行为:遇到不可读目录,RecursiveDirectoryIterator 会静默跳过,不报错也不提醒。
它不像 scandir() 那样抛 warning,而是直接把这个分支从迭代器树里剪掉。你 foreach 一圈,根本不知道哪块丢了。
立即学习“PHP免费学习笔记(深入)”;
- 构造
RecursiveDirectoryIterator时,显式传FilesystemIterator::SKIP_DOTS,避免依赖默认行为 - 必须用
try/catch包裹整个 foreach,捕获UnexpectedValueException——这是它遇到权限问题时唯一会抛的异常 - 如果需要软链接支持,得加
RecursiveDirectoryIterator::FOLLOW_SYMLINKS,否则符号链接会被当成文件处理(isDir()返回false) -
RecursiveIteratorIterator的第二个参数控制遍历策略:RecursiveIteratorIterator::SELF_FIRST是常用值;若用LEAVES_ONLY,目录本身不会出现在结果里,只返回文件,容易误以为子目录没被扫到
怎么安全提取图像文件并保留相对路径结构?
单纯遍历所有文件再用 pathinfo($file, PATHINFO_EXTENSION) 判断扩展名,容易漏判:比如 photo.JPG、icon.jpeg?v=2、或 MIME 类型是 image/png 但扩展名是 .dat 的伪装文件。
更稳的做法是扩展名 + MIME 双校验,同时保持相对于根目录的路径层级,方便前端映射或批量重命名。
- 用
$item->getExtension()取扩展名,统一转小写再比对,避免大小写敏感问题 - 对可疑文件(如无扩展名、扩展名不在白名单),用
finfo_file(finfo_open(FILEINFO_MIME_TYPE), $item->getPathname())查 MIME,只认image/开头的类型 - 相对路径不要靠
str_replace($root, '', $item->getPathname())——万一$root出现在子路径里(如/a/b/root/c.jpg)就炸了;改用mb_substr($item->getPathname(), mb_strlen($root) + 1) - 构建结果数组时,用
$item->getFilename()和$item->getPathname(),别自己basename()或dirname(),避免路径解析错误
大目录下内存爆掉或超时怎么办?
用 glob('**/*.jpg') 或一次性 scandir() 全量加载,遇到含数万文件的目录,PHP 很快 OOM 或触发 max_execution_time。而 RecursiveIteratorIterator 虽然惰性加载,但初始化时仍会尝试探测整个目录树结构,遇到深嵌套或权限死角,前期开销也不小。
真正可控的方式,是手动控制深度 + 分批处理 + 异常隔离。
- 给递归函数加
$depth参数,初始设为 0,每进一层 +1,超过阈值(如 8)直接 return,防无限嵌套 - 不用全量收集到数组再处理,改成边遍历边处理:比如发现图片就立即 resize 或写入数据库,而不是攒一堆路径再循环
- 对每个子目录单独 try/catch,不让一个目录的失败影响整体流程;日志里记下出问题的路径,便于事后排查
- CLI 环境下记得调
set_time_limit(0)和ini_set('memory_limit', '-1'),但线上 Web 环境别这么干——应改用队列分片处理



















