快速撰寫一個「掃描資料夾回傳所有檔案路徑」的函數,常見於臨時腳本、部署工具、日誌清理。本文提供最精簡的遞迴寫法,並說明為何生產環境建議改用 SPL 迭代器。
function my_scandir($dir)
{
$files = [];
if (!is_dir($dir)) return $files;
$handle = opendir($dir);
if (!$handle) return $files;
while (($file = readdir($handle)) !== false) {
if ($file === '.' || $file === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
$files[$file] = my_scandir($path); // 子目錄以目錄名為鍵,值為遞迴結果
} else {
$files[] = $path; // 檔案直接存完整路徑
}
}
closedir($handle);
return $files;
}
$tree = my_scandir(__DIR__ . '/storage');
/*
Array
(
[0] => /var/www/storage/readme.txt
[logs] => Array
(
[0] => /var/www/storage/logs/2024-01-01.log
[0] => /var/www/storage/logs/2024-01-02.log
)
[images] => Array
(
[0] => /var/www/storage/images/photo.jpg
)
)
*/
opendir/readdir/closedir:底層目錄流操作,比 scandir() 記憶體效率略好(逐個讀取)。DIRECTORY_SEPARATOR:跨平台相容(Windows \、Linux /)。is_int($key)。function scan_dir_spl($dir, $filter = [])
{
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
$result = [];
foreach ($iterator as $path => $info) {
if ($info->isDir()) {
$result[$info->getFilename()] = []; // 佔位,實際樹狀需額外建構
} elseif (empty($filter) || in_array(strtolower($info->getExtension()), $filter)) {
$result[] = $path;
}
}
return $result;
}
| 指標 | 遞迴函數 | SPL 迭代器 |
|---|---|---|
| 記憶體峰值 | 高(遞迴堆疊 + 完整陣列) | 低(生成器逐個產出) |
| 最大深度限制 | 受限於 PHP 堆疊(~幾千層) | 無實質限制 |
| 錯誤處理 | 需自行 try-catch | 可掛 set_exception_handler |
| 可中斷/暫停 | 困難 | 容易(生成器特性) |
function flatten($tree)
{
$flat = [];
array_walk_recursive($tree, function($v) use (&$flat) { $flat[] = $v; });
return $flat;
}
$allFiles = flatten(my_scandir('/data'));
// 直接得到 ['/data/a.txt', '/data/sub/b.log', ...]
臨時腳本用遞迴函數最快;長期服務、大目錄、高併發請改用 SPL 迭代器 + 快取 + 非同步處理,架構更穩健。