刪除非空目錄必須先刪除子檔案/子目錄,再刪除父目錄。使用 RecursiveIteratorIterator::CHILD_FIRST 模式可自動由葉節點向根節點遍歷,配合 unlink/rmdir 完成安全刪除。
<?php
function deleteDirectory(string $dir): bool {
if (!is_dir($dir)) {
return false;
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $item) {
if ($item->isDir()) {
@rmdir($item->getPathname());
} else {
@unlink($item->getPathname());
}
}
return @rmdir($dir);
}
// 使用
deleteDirectory('/tmp/cache');
?>
FilesystemIterator::SKIP_DOTS:避免嘗試刪除 . .. 導致無限遞迴。CHILD_FIRST:確保檔案先於其所在目錄被刪除,目錄先於父目錄被刪除。@ 抑制錯誤:權限不足、檔案被鎖定等情況下繼續刪除其他項目,最後回傳整體結果。isDir() 對 symlink 回傳 true,但 rmdir 僅刪除連結不影響目標,行為正確。function deleteDirectorySafe(string $dir, array &$deleted = [], array &$failed = []): bool {
if (!is_dir($dir)) return false;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $item) {
$path = $item->getPathname();
try {
if ($item->isDir()) {
if (rmdir($path)) $deleted[] = $path;
else $failed[] = $path;
} else {
if (unlink($path)) $deleted[] = $path;
else $failed[] = $path;
}
} catch (Throwable $e) {
$failed[] = $path;
}
}
if (rmdir($dir)) $deleted[] = $dir;
else $failed[] = $dir;
return empty($failed);
}
| 方法 | 優點 | 缺點 |
|---|---|---|
| RecursiveIteratorIterator (CHILD_FIRST) | 標準庫、無外部依賴、記憶體效率好 | 極深目錄樹可能較慢 |
| system('rm -rf ' . escapeshellarg($dir)) | 極快、系統級 | 需 shell 權限、跨平台差異、安全風險 |
| 自訂遞迴函式 (opendir/readdir) | 完全控制邏輯 | 程式碼冗長、易出錯 |
推薦使用 SPL 迭代器版本,兼顧安全性、可讀性與效能。