刪除非空目錄是文件系統操作常見需求。本文提供基礎遞歸刪除函數、安全增強版(權限檢查、符號鏈接保護、錯誤處理)、以及併發安全的刪除策略。
<?php
function deldir($dir) {
// 1. 打開目錄
$dh = opendir($dir);
if (!$dh) return false;
// 2. 遍歷條目
while (($file = readdir($dh)) !== false) {
if ($file === '.' || $file === '..') continue;
$fullpath = $dir . DIRECTORY_SEPARATOR . $file;
// 3. 區分文件/目錄
if (is_dir($fullpath)) {
// 遞歸刪除子目錄
if (!deldir($fullpath)) {
closedir($dh);
return false;
}
} else {
// 刪除文件
if (!unlink($fullpath)) {
closedir($dh);
return false;
}
}
}
closedir($dh);
// 4. 刪除當前空目錄
return rmdir($dir);
}
// 使用
deldir('/path/to/target_dir');
?>
<?php
function safeDeleteDir($dir, $options = []) {
$defaults = [
'follow_symlinks' => false, // 是否跟隨符號鏈接
'max_depth' => 100, // 最大遞歸深度防死循環
'chmod_files' => 0644, // 刪除前嘗試修改文件權限
'chmod_dirs' => 0755, // 刪除前嘗試修改目錄權限
'on_error' => 'throw', // 'throw' | 'skip' | 'return_false'
];
$opts = array_merge($defaults, $options);
$dir = rtrim($dir, DIRECTORY_SEPARATOR);
if (!is_dir($dir)) {
if ($opts['on_error'] === 'throw') throw new InvalidArgumentException("Not a directory: $dir");
return false;
}
$depth = 0;
return _safeDeleteDirRecursive($dir, $opts, $depth);
}
function _safeDeleteDirRecursive($dir, $opts, &$depth) {
if ($depth++ > $opts['max_depth']) {
throw new RuntimeException("Max recursion depth exceeded");
}
$dh = @opendir($dir);
if (!$dh) {
if ($opts['on_error'] === 'throw') throw new RuntimeException("Cannot open dir: $dir");
return false;
}
while (($file = readdir($dh)) !== false) {
if ($file === '.' || $file === '..') continue;
$fullpath = $dir . DIRECTORY_SEPARATOR . $file;
// 符號鏈接保護
if (is_link($fullpath) && !$opts['follow_symlinks']) {
// 僅刪除鏈接本身,不遞歸目標
if (!unlink($fullpath)) {
if ($opts['on_error'] === 'throw') throw new RuntimeException("Failed to unlink symlink: $fullpath");
}
continue;
}
if (is_dir($fullpath)) {
if (!_safeDeleteDirRecursive($fullpath, $opts, $depth)) {
closedir($dh);
return false;
}
} else {
// 嘗試修改權限後刪除
@chmod($fullpath, $opts['chmod_files']);
if (!unlink($fullpath)) {
if ($opts['on_error'] === 'throw') throw new RuntimeException("Failed to delete file: $fullpath");
closedir($dh);
return false;
}
}
}
closedir($dh);
@chmod($dir, $opts['chmod_dirs']);
if (!rmdir($dir)) {
if ($opts['on_error'] === 'throw') throw new RuntimeException("Failed to remove directory: $dir");
return false;
}
return true;
}
// 使用示例
try {
safeDeleteDir('/var/www/cache', [
'follow_symlinks' => false,
'on_error' => 'throw'
]);
echo "刪除成功";
} catch (Exception $e) {
echo "刪除失敗: " . $e->getMessage();
}
?>
rename($dir, $dir . '.deleting.' . uniqid()) 原子操作將目錄移出業務路徑,再異步刪除重命名後的目錄,避免業務進程訪問到半刪除狀態。flock(fopen($lockFile, 'w'), LOCK_EX),防止多進程同時刪除同一目錄。deleted_at,定時任務掃描物理刪除,業務層只查 deleted_at IS NULL。readdir() 返回 false 時需用 !== 判斷,避免文件名為 '0' 導致循環提前結束。unlink() 失敗常因文件被佔用,需重試或排查進程。rmdir() 失敗,建議刪除前 chmod()。RecursiveDirectoryIterator + RecursiveIteratorIterator 迭代器模式。<?php
function deleteDirSpl($dir) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST // 先刪子項
);
foreach ($iterator as $item) {
if ($item->isDir()) {
rmdir($item->getPathname());
} else {
unlink($item->getPathname());
}
}
rmdir($dir);
}
?>
優勢: 無遞歸深度限制、內存效率高、代碼簡潔、自動處理 . 和 ..。