在文件管理、緩存清理、臨時目錄銷毀等場景中,需要將一個目錄及其所有子目錄、文件徹底刪除。PHP 的 SPL DirectoryIterator 提供了面向對象的目錄遍歷能力,配合遞歸即可實現極簡、可讀性強的刪除邏輯。
<?php
/**
* 遞歸刪除目錄及其所有內容
*
* @param string $dir 目標目錄絕對路徑
* @return bool 是否全部刪除成功
*/
function cleanup_directory(string $dir): bool
{
if (!is_dir($dir)) {
return false;
}
$iterator = new DirectoryIterator($dir);
foreach ($iterator as $fileInfo) {
// 跳過 . 和 ..
if ($fileInfo->isDot()) {
continue;
}
$pathname = $fileInfo->getPathname();
if ($fileInfo->isDir()) {
// 子目錄遞歸刪除
cleanup_directory($pathname);
} else {
// 文件直接刪除
@unlink($pathname);
}
}
// 最後刪除空目錄本身
return @rmdir($dir);
}
// 使用示例
cleanup_directory('/var/www/html/cache/temp');
?>
DirectoryIterator 實現 Iterator 接口,可直接 foreach 遍歷。isDot() 用於過濾 .(當前目錄)和 ..(父目錄),避免無限遞歸。isDir() 區分目錄與文件,目錄需遞歸,文件直接 unlink()。rmdir() 刪除目錄本身。@ 抑制警告(如權限不足),生產環境建議改用 try-catch 或檢查返回值並記錄日誌。<?php
function cleanup_directory_safe(string $dir, array &$errors = []): bool
{
if (!is_dir($dir)) {
$errors[] = "目錄不存在:$dir";
return false;
}
$iterator = new DirectoryIterator($dir);
foreach ($iterator as $fileInfo) {
if ($fileInfo->isDot()) continue;
$path = $fileInfo->getPathname();
try {
if ($fileInfo->isDir()) {
cleanup_directory_safe($path, $errors);
} else {
if (!unlink($path)) {
$errors[] = "刪除文件失敗:$path";
}
}
} catch (Throwable $e) {
$errors[] = "異常:{$e->getMessage()}($path)";
}
}
if (!rmdir($dir)) {
$errors[] = "刪除目錄失敗:$dir";
return false;
}
return true;
}
$errors = [];
cleanup_directory_safe('/tmp/myapp_cache', $errors);
if ($errors) {
error_log("清理異常:" . implode('; ', $errors));
}
?>
| 方案 | 優點 | 缺點 |
|---|---|---|
| DirectoryIterator 遞歸(本文) | 代碼清晰、面向對象、無額外依賴 | 深層目錄可能觸發遞歸深度限制 |
| RecursiveDirectoryIterator + RecursiveIteratorIterator (CHILD_FIRST) | 單次遍歷、無手動遞歸、效率略高 | 代碼稍顯抽象,需理解 SPL 迭代器 |
| system('rm -rf ' . escapeshellarg($dir)) | 極快、一行代碼 | 依賴 Shell、Windows 不通用、安全風險高 |
| symfony/filesystem 或 league/flysystem | 跨平台、支援雲存儲、測試友好 | 引入第三方依賴 |
對於大多數 PHP 項目,DirectoryIterator 遞歸方案在可讀性、可維護性與性能之間取得了良好平衡。若目錄層級極深(超過 1000 層),建議改用 RecursiveIteratorIterator::CHILD_FIRST 非遞歸模式或成熟的文件系統抽象庫。