刪除非空目錄是 PHP 文件系統操作的常見需求。rmdir() 僅能刪除空目錄,因此需遞歸清理子目錄與檔案。本文深度解析三種主流實現方式:純 PHP 遞歸、系統命令調用、目錄迭代器模式,對比效能、可移植性與安全性,助你選擇最適合場景的方案。
<?php
function deleteDir($dir) {
// 嘗試直接刪除(空目錄情況)
if (rmdir($dir) === false && is_dir($dir)) {
// 打開目錄讀取內容
if ($dp = opendir($dir)) {
while (($file = readdir($dp)) !== false) {
$fullPath = $dir . DIRECTORY_SEPARATOR . $file;
// 跳過 . 和 ..
if ($file === '.' || $file === '..') continue;
if (is_dir($fullPath)) {
// 子目錄遞歸刪除
deleteDir($fullPath);
} else {
// 檔案直接刪除
unlink($fullPath);
}
}
closedir($dp);
} else {
// 權限不足或非目錄
exit('Not permission or invalid directory: ' . $dir);
}
// 子內容清空後,再次嘗試刪除目錄本身
@rmdir($dir);
}
}
優化要點(原碼缺失):
DIRECTORY_SEPARATOR 確保跨平台路徑正確$fullPath 而非直接用 $file(原碼 Bug:is_dir($file) 判斷相對路徑會失效)@rmdir($dir) 清理空目錄| 維度 | 評價 |
|---|---|
| 跨平台 | ✅ 完美支援 Windows/Linux/macOS |
| 效能 | 中等(大量文件時函數調用開銷大) |
| 可控性 | ✅ 可加入刪除前鉤子、日誌、權限檢查、例外處理 |
| 安全性 | ✅ 無命令注入風險 |
| 記憶體 | 遞歸深度受限於 PHP 堆疊(極深目錄可能溢出) |
<?php
function del_dir($dir) {
// 防止命令注入:必須對 $dir 嚴格驗證、轉義
$dir = escapeshellarg($dir);
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
// Windows: rmdir /s /q 強制安靜刪除目錄樹
$cmd = "rmdir /s /q $dir";
} else {
// Linux/Unix: rm -rf 遞歸強制刪除
$cmd = "rm -rf $dir";
}
// 執行命令
exec($cmd, $output, $return_var);
if ($return_var !== 0) {
throw new RuntimeException("Directory deletion failed: $cmd");
}
return true;
}
關鍵安全警示:
escapeshellarg() 對路徑轉義,防止路徑包含空格、分號、反引號等導致命令注入exec()、system()、shell_exec(),需確認環境支援| 維度 | 評價 |
|---|---|
| 跨平台 | ⚠️ 需區分 Windows/Linux 命令語法 |
| 效能 | ✅ 極快(系統級調用,C 實現) |
| 可控性 | ❌ 難以加入細粒度邏輯(如刪除前備份、條件過濾) |
| 安全性 | ⚠️ 高風險(命令注入、路徑遍歷),需極謹慎 |
| 適用場景 | 大量文件、深層目錄、臨時目錄清理、CLI 腳本 |
<?php
function deltree($pathdir) {
// 規範化路徑
$pathdir = rtrim($pathdir, DIRECTORY_SEPARATOR);
if (!is_dir($pathdir)) {
throw new InvalidArgumentException("Not a directory: $pathdir");
}
// 使用 DirectoryIterator 遍歷(自動跳過 . 和 ..)
$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($pathdir,
RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST // 關鍵:子優先(後序遍歷)
);
foreach ($items as $item) {
if ($item->isFile() || $item->isLink()) {
unlink($item->getPathname());
} elseif ($item->isDir()) {
rmdir($item->getPathname());
}
}
// 最後刪除根目錄
rmdir($pathdir);
return true;
}
核心優勢:
. 和 ..,避免無限遞歸| 維度 | 評價 |
|---|---|
| 跨平台 | ✅ 完美 |
| 效能 | 優(原生 C 實現迭代器) |
| 可控性 | ✅ 易擴展邏輯 |
| 安全性 | ✅ 無注入風險 |
| PHP 版本 | ≥ 5.1 (SPL 標準庫) |
<?php
function is_empty_dir($pathdir) {
$handle = @opendir($pathdir);
if (!$handle) return false; // 不存在或權限不足視為非空
$count = 0;
while (($entry = readdir($handle)) !== false) {
if ($entry !== '.' && $entry !== '..') {
$count++;
if ($count > 0) break; // 發現非 . / .. 即非空
}
}
closedir($handle);
return $count === 0;
}
| 場景 | 推薦方案 | 理由 |
|---|---|---|
| Web 請求中刪除用戶上傳目錄(中小規模) | 方法三 | 現代、安全、可維護、無平台依賴 |
| CLI 維護腳本清理海量臨時文件(百萬級) | 方法二 | 系統命令效能碾壓,配合 escapeshellarg 安全可控 |
| 需加入業務邏輯(如:保留特定後綴、記錄審計日誌) | 方法一/三 | 純 PHP 完全可控,方法三擴展性更好 |
| Windows 獨立部署環境 | 方法一/三 | 避免命令語法差異,純 PHP 最穩健 |
| 嵌入式/受限環境(禁用 exec) | 方法一/三 | 純 PHP 無外部依賴 |
realpath() 解析符號鏈接,rtrim($path, DIRECTORY_SEPARATOR) 去除尾部分隔符is_dir(),避免誤刪unlink/rmdir 權限try-catch,記錄失敗詳情,避免靜默失敗.keep 檔案),刪除前檢查tree 或 find 驗證刪除結果PHP 刪除非空目錄的三大範式:
理解各方案權衡,結合業務場景與環境約束選擇,才能寫出安全、高效、可維護的文件系統操作代碼。