PHP 的 rmdir() 只能刪除空目錄。要刪除包含檔案、子目錄的非空目錄,必須實作遞迴演算法:先刪除所有檔案,再由最深層目錄向上刪除。
<?php
/**
* 遞迴刪除目錄及其所有內容
*
* @param string $dirPath 目錄路徑
* @param bool $deleteSelf 是否刪除目錄本身(false 僅清空內容)
* @return bool 成功與否
*/
function deleteDirectory(string $dirPath, bool $deleteSelf = true): bool {
// 正規化路徑
$dirPath = rtrim($dirPath, '/\\');
if (!is_dir($dirPath)) {
trigger_error("路徑不存在或非目錄:$dirPath", E_USER_WARNING);
return false;
}
$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dirPath, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST // 關鍵:子優先(深度優先)
);
foreach ($items as $item) {
$path = $item->getPathname();
try {
if ($item->isDir()) {
if (!rmdir($path)) {
throw new RuntimeException("無法刪除目錄:$path");
}
} else {
if (!unlink($path)) {
throw new RuntimeException("無法刪除檔案:$path");
}
}
} catch (Throwable $e) {
error_log($e->getMessage());
return false;
}
}
if ($deleteSelf) {
return @rmdir($dirPath);
}
return true;
}
?>
<?php
// 刪除整個目錄樹
if (deleteDirectory('/var/www/cache')) {
echo "快取目錄已清除";
} else {
echo "清除失敗,請查看錯誤日誌";
}
// 僅清空目錄內容,保留目錄本身
deleteDirectory('/var/www/temp', false);
?>
<?php
function deleteDirectoryAdvanced(string $dirPath, array $options = []): array {
$options = array_merge([
'delete_self' => true,
'follow_symlinks' => false, // 不跟隨符號連結
'on_progress' => null, // callable($current, $total, $path)
'on_error' => null, // callable($path, $error)
'max_depth' => null, // 最大深度限制
], $options);
$results = [
'deleted_files' => 0,
'deleted_dirs' => 0,
'errors' => [],
'skipped' => []
];
$dirPath = rtrim($dirPath, '/\\');
if (!is_dir($dirPath)) {
$results['errors'][] = "路徑不存在:$dirPath";
return $results;
}
// 收集所有項目(深度優先)
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$dirPath,
RecursiveDirectoryIterator::SKIP_DOTS |
($options['follow_symlinks'] ? 0 : RecursiveDirectoryIterator::FOLLOW_SYMLINKS)
),
RecursiveIteratorIterator::CHILD_FIRST
);
$allItems = iterator_to_array($iterator);
$total = count($allItems);
$current = 0;
foreach ($allItems as $item) {
$current++;
$path = $item->getPathname();
// 深度檢查
if ($options['max_depth'] !== null && $item->getDepth() > $options['max_depth']) {
$results['skipped'][] = $path;
continue;
}
// 進度回呼
if ($options['on_progress']) {
($options['on_progress'])($current, $total, $path);
}
try {
if ($item->isLink() || ($item->isFile() && !$item->isDir())) {
if (unlink($path)) {
$results['deleted_files']++;
} else {
throw new RuntimeException("unlink 失敗");
}
} elseif ($item->isDir()) {
if (rmdir($path)) {
$results['deleted_dirs']++;
} else {
throw new RuntimeException("rmdir 失敗");
}
}
} catch (Throwable $e) {
$error = "刪除失敗 [$path]:" . $e->getMessage();
$results['errors'][] = $error;
if ($options['on_error']) {
($options['on_error'])($path, $e);
}
}
}
// 刪除根目錄
if ($options['delete_self']) {
if (@rmdir($dirPath)) {
$results['deleted_dirs']++;
} else {
$results['errors'][] = "無法刪除根目錄:$dirPath";
}
}
return $results;
}
// 使用範例
$results = deleteDirectoryAdvanced('/var/www/old_project', [
'on_progress' => fn($c, $t, $p) => echo "[$c/$t] $p\n",
'on_error' => fn($p, $e) => error_log("刪除錯誤:$p - " . $e->getMessage())
]);
echo "刪除檔案:{$results['deleted_files']}, 目錄:{$results['deleted_dirs']}";
if ($results['errors']) {
echo "錯誤:" . implode('; ', $results['errors']);
}
?>
<?php
// 適用大量檔案、生產環境部署腳本
function deleteDirectorySystem(string $path): bool {
$path = escapeshellarg(rtrim($path, '/\\'));
// Linux/Unix
if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
$cmd = "rm -rf $path";
} else {
// Windows: rmdir /s /q
$cmd = "rmdir /s /q $path";
}
exec($cmd, $output, $returnCode);
return $returnCode === 0;
}
?>
| 問題 | 原因 | 解決 |
|---|---|---|
| 權限拒絕 | PHP 執行用戶無寫入/執行權限 | 調整目錄擁有者/權限、或用 sudo 執行 |
| 目錄非空 | 用 rmdir 直接刪非空目錄 | 必須先清空內容(遞迴) |
| 符號連結誤刪 | 遞迴進入連結指向的目錄 | 用 SKIP_DOTS + 不跟隨連結、或檢查 isLink() |
| 掛載點刪除 | 遞迴進入網路磁碟/掛載點 | 檢查裝置號、限制 max_depth |
| 記憶體耗盡 | iterator_to_array 載入百萬檔案 | 直接迭代、或分批處理 |
RecursiveIteratorIterator::CHILD_FIRST 確保深度優先rm -rf / rmdir /s正確實作目錄遞迴刪除,是 PHP 開發者處理檔案系統操作的基本功,也是建立可靠部署、清理、快取管理工具的關鍵。