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

PHP 遞迴刪除目錄完整實現:兩種方法對比與安全性加強

     編輯:PHP 相關知識

PHP 遞迴刪除目錄完整實現:兩種方法對比與安全性加強

刪除非空目錄是常見檔案操作需求。本文提供兩種遞迴刪除實作,分析優缺點,並補充路徑驗證、權限檢查、錯誤處理等生產級安全措施。

方法一:opendir/readdir 經典遞迴 (PHP 4+ 相容)

<?php
/**
 * 遞迴刪除目錄及其所有內容
 *
 * @param string $dir 目錄絕對路徑
 * @return bool 成功返回 true
 * @throws InvalidArgumentException 路徑非目錄或不存在
 * @throws RuntimeException 刪除失敗
 */
function recursiveDelete(string $dir): bool {
    // 1. 規範化路徑
    $dir = rtrim(realpath($dir), DIRECTORY_SEPARATOR);
    if ($dir === false || !is_dir($dir)) {
        throw new InvalidArgumentException("Directory not found: $dir");
    }

    // 2. 安全檢查:防止刪除系統關鍵目錄
    $protected = ["/", "/etc", "/var", "/usr", "/home", "C:\\Windows", "C:\\Program Files"];
    foreach ($protected as $p) {
        if (strpos($dir, realpath($p)) === 0) {
            throw new RuntimeException("Protected directory: $dir");
        }
    }

    // 3. 開啟目錄句柄
    $handle = @opendir($dir);
    if (!$handle) {
        throw new RuntimeException("Cannot open directory: $dir");
    }

    // 4. 遍歷刪除
    while (($entry = readdir($handle)) !== false) {
        if ($entry === '.' || $entry === '..') continue;

        $path = $dir . DIRECTORY_SEPARATOR . $entry;

        if (is_dir($path)) {
            recursiveDelete($path); // 遞迴刪除子目錄
        } else {
            if (!@unlink($path)) {
                throw new RuntimeException("Failed to delete file: $path");
            }
        }
    }
    closedir($handle);

    // 5. 刪除空目錄本身
    if (!@rmdir($dir)) {
        throw new RuntimeException("Failed to remove directory: $dir");
    }

    return true;
}
?>

方法二:scandir + 迭代器 (PHP 5+ 更簡潔)

<?php
function recursiveDeleteScandir(string $dir): bool {
    $dir = rtrim(realpath($dir), DIRECTORY_SEPARATOR);
    if ($dir === false || !is_dir($dir)) {
        throw new InvalidArgumentException("Directory not found: $dir");
    }

    // 同樣加入受保護目錄檢查...

    $entries = scandir($dir);
    foreach ($entries as $entry) {
        if ($entry === '.' || $entry === '..') continue;

        $path = $dir . DIRECTORY_SEPARATOR . $entry;

        if (is_dir($path)) {
            recursiveDeleteScandir($path);
        } else {
            if (!@unlink($path)) {
                error_log("Warning: unlink failed for $path");
                // 視業務決定是否拋出異常或繼續
            }
        }
    }

    if (!@rmdir($dir)) {
        throw new RuntimeException("Failed to remove directory: $dir");
    }
    return true;
}
?>

方法三:現代物件導向 - RecursiveDirectoryIterator (PHP 5.3+ 推薦)

<?php
function recursiveDeleteIterator(string $dir): bool {
    $dir = rtrim(realpath($dir), DIRECTORY_SEPARATOR);
    if ($dir === false || !is_dir($dir)) {
        throw new InvalidArgumentException("Directory not found: $dir");
    }

    // 受保護目錄檢查...

    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST // 先刪子項,再刪父目錄
    );

    foreach ($iterator as $item) {
        $path = $item->getPathname();
        if ($item->isDir()) {
            if (!@rmdir($path)) {
                error_log("Warning: rmdir failed for $path");
            }
        } else {
            if (!@unlink($path)) {
                error_log("Warning: unlink failed for $path");
            }
        }
    }

    // 最後刪除根目錄
    if (!@rmdir($dir)) {
        throw new RuntimeException("Failed to remove root directory: $dir");
    }
    return true;
}
?>

三種方法對比

特性opendir 遞迴scandir 遞迴RecursiveIterator
代碼行數較多中等最少
記憶體佔用低 (流式)中 (scandir 回傳陣列)低 (迭代器)
錯誤處理手動控制手動控制統一 try-catch
符號連結處理需額外判斷需額外判斷RecursiveDirectoryIterator::FOLLOW_SYMLINKS
推薦場景極舊版 PHP、嵌入式一般專案、簡單邏輯現代專案 (PHP 7.1+)、標準庫風格

生產環境安全加強清單

  1. 絕對路徑 + realpath():消除相對路徑、符號連結繞過風險。
  2. 受保護目錄白名單/黑名單:嚴禁刪除系統目錄、專案根目錄、上傳根目錄。
  3. 權限檢查:確認 PHP 進程對目標目錄擁有 write + execute 權限。
  4. 符號連結處理:決定是刪除連結本身 (unlink) 還是跟隨刪除目標 (rmdir/unlink 目標),預設建議不跟隨。
  5. 大目錄效能:超大目錄 (萬級檔案) 遞迴易超時/爆記憶體,建議改用 exec('rm -rf ' . escapeshellarg($dir)) (僅 Linux、極謹慎) 或分批異步處理。
  6. 記錄審計日誌:記錄操作者、時間、目標路徑、結果,便於事後追溯。

使用範例

<?php
try {
    // 刪除上傳目錄下某用戶的臨時資料夾
    $target = '/var/www/app/uploads/temp/user_12345';
    recursiveDeleteIterator($target);
    echo "刪除成功\n";
} catch (Exception $e) {
    error_log("Delete failed: {$e->getMessage()}");
    echo "刪除失敗: {$e->getMessage()}\n";
}
?>

選擇適合專案 PHP 版本與複雜度的實作,務必加上路徑驗證與權限檢查,避免「一行代碼刪光硬碟」的慘劇。

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