遞歸處理目錄樹是檔案系統操作的核心技能。本文提供安全、健壯的目錄遍歷與刪除實現,涵蓋邊界條件處理、錯誤捕獲、效能優化。
<?php
/**
* 遞歸刪除目錄及其所有內容
* @param string $path 目錄路徑
* @return bool 成功返回 true
*/
function deleteDirectory(string $path): bool {
// 1. 路徑標準化
$path = rtrim($path, '/\\');
// 2. 存在性檢查
if (!file_exists($path)) {
trigger_error("目錄不存在: $path", E_USER_WARNING);
return false;
}
// 3. 確保是目錄
if (!is_dir($path)) {
trigger_error("路徑非目錄: $path", E_USER_WARNING);
return false;
}
// 4. 讀取目錄內容 (排除 . 和 ..)
$items = array_diff(scandir($path), ['.', '..']);
// 5. 遞歸處理每個項目
foreach ($items as $item) {
$fullPath = $path . DIRECTORY_SEPARATOR . $item;
if (is_file($fullPath) || is_link($fullPath)) {
// 檔案或符號連結:直接刪除
if (!unlink($fullPath)) {
trigger_error("刪除檔案失敗: $fullPath", E_USER_WARNING);
return false;
}
} elseif (is_dir($fullPath)) {
// 子目錄:遞歸刪除
if (!deleteDirectory($fullPath)) {
return false; // 錯誤已在遞歸層觸發
}
}
}
// 6. 刪除空目錄本身
return rmdir($path);
}
// 使用範例
deleteDirectory('/var/www/uploads/temp');
?>
<?php
/**
* 遞歸遍歷目錄,返回所有檔案路徑
* @param string $path 起始目錄
* @param array $options 選項: ['recursive'=>true, 'pattern'=>'/*.php', 'full_path'=>true]
* @return array 檔案路徑陣列
*/
function scanDirectory(string $path, array $options = []): array {
$defaults = [
'recursive' => true,
'pattern' => null, // glob 模式
'full_path' => true, // 返回完整路徑
'skip_hidden' => true, // 跳過隱藏檔
];
$opts = array_merge($defaults, $options);
$path = rtrim($path, '/\\');
if (!is_dir($path)) return [];
$files = [];
$items = scandir($path);
foreach ($items as $item) {
if ($opts['skip_hidden'] && $item[0] === '.') continue;
if ($item === '.' || $item === '..') continue;
$fullPath = $path . DIRECTORY_SEPARATOR . $item;
if (is_file($fullPath)) {
if ($opts['pattern'] === null || fnmatch($opts['pattern'], $item)) {
$files[] = $opts['full_path'] ? $fullPath : $item;
}
} elseif (is_dir($fullPath) && $opts['recursive']) {
$files = array_merge($files, scanDirectory($fullPath, $opts));
}
}
return $files;
}
// 使用範例
$phpFiles = scanDirectory('/var/www/project', ['pattern' => '*.php']);
print_r($phpFiles);
?>
<?php
// 刪除目錄 (PHP 5.3+)
function deleteDirectorySPL(string $path): void {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST // 先刪除子項
);
foreach ($iterator as $item) {
$item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname());
}
rmdir($path);
}
// 遍歷列印
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/var/www', RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->isFile()) {
echo $file->getPathname() . " ({$file->getSize()} bytes)\n";
}
}
?>
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 自定義遞歸函數 | 完全可控、相容舊版 PHP | 需自行處理邊界、效能一般 | 教學、特殊邏輯、PHP < 5.3 |
| SPL 迭代器 | 內建優化、記憶體效率高、程式碼簡潔 | 需 PHP 5.3+、行為固定 | 生產環境首選、大目錄樹 |
系統命令 rm -rf | 極速、原子性 | 安全風險極高、跨平台差異 | 極謹慎的內部維護腳本 |
realpath() 驗證在允許根目錄下is_link() 避免跟隨連結刪除目標is_writable() 預檢count(scandir()) === 2 (. 和 ..)遞歸檔案操作威力巨大但風險同在,務必建立完善的測試用例、權限隔離與審計日誌,生產環境優先考慮軟刪除或備份後硬刪除。