專案常需「掃描上傳目錄產生檔案清單」、「遞迴刪除暫存檔」、「建立檔案索引」。本文提供一個健壯的 scan_dir() 函數,支援副檔名白名單過濾、自動跳過 ./..、回傳巢狀陣列(Tree 結構),並處理權限錯誤與空目錄。
/**
* 遞迴掃描目錄,回傳 Tree 結構陣列
*
* @param string $dir 絕對或相對路徑
* @param array $filter 允許的副檔名白名單(如 ['jpg','png','pdf']),空陣列=不過濾
* @return array|false 結構化陣列,失敗回傳 false
*/
function scan_dir($dir, $filter = [])
{
// 1. 基礎驗證
if (!is_dir($dir) || !is_readable($dir)) {
trigger_error("scan_dir: 目錄不存在或不可讀:$dir", E_USER_WARNING);
return false;
}
// 2. 讀取項目並移除 . ..
$items = array_diff(scandir($dir), ['.', '..']);
if (empty($items)) return [];
$result = [];
foreach ($items as $item) {
$path = $dir . DIRECTORY_SEPARATOR . $item;
// 3. 子目錄 → 遞迴
if (is_dir($path)) {
$sub = scan_dir($path, $filter);
if ($sub !== false) {
$result[$item] = $sub; // 以目錄名為鍵,值為子樹
}
continue;
}
// 4. 檔案 → 副檔名過濾
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
if (!empty($filter) && !in_array($ext, $filter, true)) {
continue; // 不在白名單,跳過
}
// 5. 檔案資訊(可擴充 size、mtime、mime 等)
$result[] = [
'name' => $item,
'path' => $path,
'ext' => $ext,
'size' => filesize($path),
'time' => filemtime($path),
];
}
return $result;
}
// 1. 掃描所有檔案(不過濾)
$all = scan_dir(__DIR__ . '/uploads');
print_r($all);
/*
Array
(
[0] => Array ( [name]=>readme.txt [path]=>... [ext]=>txt [size]=>123 [time]=>1700000000 )
[images] => Array
(
[0] => Array ( [name]=>photo.jpg [path]=>... [ext]=>jpg [size]=>45678 [time]=>1700000001 )
[thumbs] => Array ( [0]=>Array(...) )
)
)
*/
// 2. 只掃描圖片與 PDF
$docs = scan_dir('/var/www/files', ['jpg','png','gif','pdf']);
// 3. 扁平化輸出(遞迴展開)
function flatten_tree($tree, &$flat = [], $prefix = '')
{
foreach ($tree as $key => $val) {
if (is_int($key)) { // 檔案節點
$val['full_path'] = $prefix . $val['name'];
$flat[] = $val;
} else { // 目錄節點
flatten_tree($val, $flat, $prefix . $key . DIRECTORY_SEPARATOR);
}
}
return $flat;
}
$flat = flatten_tree(scan_dir('/data', ['log']));
// $flat 為單層陣列,每筆含 full_path,適合直接寫入資料庫或 CSV
RecursiveDirectoryIterator + RecursiveIteratorIterator,避免遞迴呼叫堆疊溢位與記憶體峰值。pcntl_fork 或 symfony/process 多進程平行掃描。json_encode 存入 Redis/檔案,前端樹狀元件直接讀取,避免即時掃描阻塞請求。trigger_error 改為拋出自訂例外 DirectoryScanException,統一由錯誤處理器記錄日誌。根據專案規模選擇實作方式:中小型目錄直接用遞迴函數最簡潔;大型、高併發場景請升級為 SPL 迭代器 + 快取機制。