在 PHP 專案中,刪除目錄及其內部檔案是常見的檔案系統操作。由於 rmdir() 僅能刪除空目錄,開發者必須先遞迴清空子目錄與檔案。本文整理四種實用的刪除策略,從基礎遞迴到依時間條件清理,並提供生產環境可用的最佳實踐程式碼。
所有目錄刪除函數的核心流程一致:
opendir())readdir()),跳過 . 與 ..unlink(),目錄遞迴呼叫自身closedir())rmdir())原始範例有語法錯誤 (雙層大括號、未使用變數),以下為修正並加入錯誤處理的版本:
<?php
/**
* 遞迴刪除目錄及其內容
* @param string $dir 目標目錄絕對路徑
* @return bool 成功返回 true,失敗拋出異常
*/
function deleteDirectory(string $dir): bool {
if (!is_dir($dir)) {
throw new InvalidArgumentException("路徑不存在或非目錄: $dir");
}
$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($items as $item) {
if ($item->isFile()) {
if (!unlink($item->getPathname())) {
throw new RuntimeException("無法刪除檔案: {$item->getPathname()}");
}
} elseif ($item->isDir()) {
if (!rmdir($item->getPathname())) {
throw new RuntimeException("無法刪除子目錄: {$item->getPathname()}");
}
}
}
return rmdir($dir);
}
// 使用範例
try {
deleteDirectory('/var/www/html/uploads/temp_123');
echo '目錄刪除成功';
} catch (Exception $e) {
error_log($e->getMessage());
echo '刪除失敗: ' . $e->getMessage();
}
?>
優點:使用 SPL 迭代器,程式碼簡潔、自動處理遞迴順序 (CHILD_FIRST 先刪子項)。
注意:需 PHP 5.3+,大量檔案時記憶體佔用較高。
若專案運行於 PHP 5.2 或無法使用 SPL,可使用改良版傳統寫法:
<?php
function delDirTraditional(string $dir): bool {
if (!is_dir($dir)) return false;
$dh = opendir($dir);
if (!$dh) return false;
while (($file = readdir($dh)) !== false) {
if ($file === '.' || $file === '..') continue;
$fullPath = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($fullPath)) {
delDirTraditional($fullPath);
} else {
@unlink($fullPath); // 抑制警告,由最終結果判斷
}
}
closedir($dh);
return @rmdir($dir);
}
?>
自動判斷傳入路徑是檔案還是目錄,統一刪除介面:
<?php
function removePath(string $path): bool {
if (!file_exists($path)) return true; // 不存在視為成功
if (is_file($path) || is_link($path)) {
return unlink($path);
}
// 是目錄
$items = scandir($path);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
if (!removePath($path . DIRECTORY_SEPARATOR . $item)) {
return false;
}
}
return rmdir($path);
}
?>
保留最近 N 天的檔案,自動清理過期項目:
<?php
/**
* 清理指定目錄中超過 $days 天的檔案 (不刪除子目錄結構)
* @param string $dir 目標目錄
* @param int $days 保留天數
* @return int 刪除的檔案數量
*/
function cleanOldFiles(string $dir, int $days = 30): int {
if (!is_dir($dir)) return 0;
$deleted = 0;
$cutoff = time() - ($days * 86400);
$files = new FilesystemIterator($dir, FilesystemIterator::SKIP_DOTS);
foreach ($files as $file) {
if ($file->isFile() && $file->getMTime() < $cutoff) {
if (@unlink($file->getPathname())) {
$deleted++;
}
}
}
return $deleted;
}
// 每日定時任務範例:清理 30 天前的上傳暫存檔
// cleanOldFiles('/var/www/html/uploads/tmp', 30);
?>
| 場景 | 建議方案 | 關鍵注意事項 |
|---|---|---|
| 使用者上傳目錄定期清理 | 方案四 + Cron Job | 僅刪除檔案保留目錄結構;記錄刪除日誌供稽核 |
| 暫存目錄完整移除 | 方案一 (SPL) | 執行前檢查權限;大量檔案考慮分批處理避免超時 |
| 部署腳本清理舊版本 | 方案三 | 先備份重要資料;使用絕對路徑防止誤刪 |
| 高併發環境 | 加入檔案鎖 (flock) | 防止多程序同時操作同一目錄導致競態條件 |
ls -la 與 chown。lsof +D /path/to/dir 找出佔用進程。Maximum function nesting level,可調整 xdebug.max_nesting_level 或改用迭代器。is_dir() 會跟隨連結,刪除時可能誤刪連結目標,建議加入 is_link() 判斷處理。PHP 刪除目錄看似簡單,實際部署需考慮權限、效能、錯誤處理與安全性。現代專案優先使用 SPL RecursiveIteratorIterator 方案,程式碼更健壯且符合 PSR 標準。舊專案可用傳統遞迴函數並加入 @ 抑制警告配合錯誤日誌。定期清理任務務必加入執行日誌與失敗通知機制,確保系統穩定運行。