首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 遞歸刪除目錄及其所有文件:完整函數實現與安全增強版

     編輯:PHP 相關知識

PHP 遞歸刪除目錄及其所有文件:完整函數實現與安全增強版

刪除非空目錄是文件系統操作常見需求。本文提供基礎遞歸刪除函數、安全增強版(權限檢查、符號鏈接保護、錯誤處理)、以及併發安全的刪除策略。

基礎遞歸刪除函數

<?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。

常見坑點

  1. readdir() 返回 false 時需用 !== 判斷,避免文件名為 '0' 導致循環提前結束。
  2. Windows 下 unlink() 失敗常因文件被佔用,需重試或排查進程。
  3. 權限不足導致 rmdir() 失敗,建議刪除前 chmod()。
  4. 超大目錄(萬級文件)遞歸會耗盡棧內存,改用 RecursiveDirectoryIterator + RecursiveIteratorIterator 迭代器模式。
  5. 現代寫法:SPL 迭代器

    <?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);
    }
    ?>

    優勢: 無遞歸深度限制、內存效率高、代碼簡潔、自動處理 . 和 ..。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved