掃描目錄樹、構建文件索引、批量處理文件時,需遞歸獲取所有文件及子目錄路徑。本文提供標準遞歸函數、SPL 迭代器現代寫法、生成器版 (內存友好)、常見過濾需求擴展。
<?php
/**
* 遞歸讀取目錄所有文件與子目錄
* @param string $dir 絕對路徑
* @return array ['dir' => [路徑=>子樹], 'file' => [文件路徑...]]
*/
function readAllDir(string $dir): array {
$result = ['dir' => [], 'file' => []];
$handle = @opendir($dir);
if (!$handle) return $result;
while (($file = readdir($handle)) !== false) {
if ($file === '.' || $file === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
$result['dir'][$path] = readAllDir($path); // 遞歸
} else {
$result['file'][] = $path;
}
}
closedir($handle);
return $result;
}
// 使用
$tree = readAllDir('/var/www/project');
print_r($tree);
/*
Array
(
[dir] => Array
(
[/var/www/project/app] => Array
(
[dir] => Array(...)
[file] => Array([0] => /var/www/project/app/config.php, ...)
)
)
[file] => Array
(
[0] => /var/www/project/index.php
...
)
)
*/
?>
<?php
function getAllFilesSpl(string $dir): array {
$files = [];
$dirs = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS),
RecursiveIteratorIterator::SELF_FIRST // 先返回目錄再文件
);
foreach ($iterator as $path => $info) {
if ($info->isDir()) {
$dirs[] = $path;
} else {
$files[] = $path;
}
}
return ['dir' => $dirs, 'file' => $files];
}
// 只獲取文件 (最常用)
function getFilesOnly(string $dir): array {
$files = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)
);
foreach ($iterator as $info) {
if ($info->isFile()) $files[] = $info->getPathname();
}
return $files;
}
?>
<?php
/**
* 生成器逐個產出文件路徑,不一次性載入所有路徑到內存
*/
function yieldFiles(string $dir): Generator {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)
);
foreach ($iterator as $info) {
if ($info->isFile()) {
yield $info->getPathname();
}
}
}
// 使用:逐個處理,內存常數級
foreach (yieldFiles('/var/www/huge_project') as $file) {
// 處理單個文件 (解析、壓縮、上傳...)
if (pathinfo($file, PATHINFO_EXTENSION) === 'php') {
// do something
}
}
?>
<?php
function getFilteredFiles(string $dir, array $exts = ['php', 'html', 'css', 'js']): array {
$exts = array_map('strtolower', $exts);
$files = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)
);
foreach ($iterator as $info) {
if ($info->isFile()) {
$ext = strtolower($info->getExtension());
if (in_array($ext, $exts, true)) {
$files[] = $info->getPathname();
}
}
}
return $files;
}
// 過濾隱藏文件/目錄 (以 . 開頭)
$iterator = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
// SKIP_DOTS 已跳過 . 和 ..,但不跳過 .git、.env 等
// 需額外判斷
if ($info->isFile() && $info->getBasename()[0] !== '.') { ... }
?>
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 遞歸函數 | 無依賴、邏輯可控、返回結構化樹 | 深層目錄可能棧溢出、大樹內存佔用高 | 中小目錄、需樹結構 |
| SPL 迭代器 | 標準庫、面向對象、支持過濾器鏈 | 返回扁平列表、需額外構建樹 | 通用掃描、生產環境首選 |
| 生成器 | **內存 O(1)**、可中斷、流式處理 | 不可隨機訪問、無法直接獲取總數 | **超大目錄 (10萬+ 文件)、批量處理管道** |
<?php
function buildMtimeIndex(string $dir): Generator {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)
);
foreach ($iterator as $info) {
if ($info->isFile()) {
yield $info->getPathname() => $info->getMTime();
}
}
}
// 使用: 找出 24 小時內修改的文件
$recent = [];
$cutoff = time() - 86400;
foreach (buildMtimeIndex('/var/www/project') as $file => $mtime) {
if ($mtime > $cutoff) $recent[] = $file;
}
print_r($recent);
?>