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

PHP 遞迴刪除目錄與檔案完整實作

     編輯:PHP 相關知識

PHP 內建 rmdir() 僅能刪除**空目錄**,刪除含檔案/子目錄的資料夾需自行遞迴實作。本文提供健壯、可重用的刪除函式,含錯誤處理、符號連結保護、權限檢查。

一、核心遞迴函式

<?php
/**
 * 遞迴刪除目錄及其所有內容
 *
 * @param string $dirPath 目錄絕對路徑
 * @param bool   $deleteRoot 是否刪除根目錄本身 (false 則僅清空內容)
 * @return bool 成功與否
 * @throws InvalidArgumentException 路徑無效
 * @throws RuntimeException 刪除失敗
 */
function deleteDirectory(string $dirPath, bool $deleteRoot = true): bool {
    // 1. 正規化路徑,移除尾端斜線
    $dirPath = rtrim($dirPath, DIRECTORY_SEPARATOR);

    // 2. 基本驗證
    if (!file_exists($dirPath)) {
        throw new InvalidArgumentException("目錄不存在: $dirPath");
    }
    if (!is_dir($dirPath)) {
        throw new InvalidArgumentException("路徑非目錄: $dirPath");
    }
    if (!is_readable($dirPath)) {
        throw new RuntimeException("目錄不可讀: $dirPath");
    }

    // 3. 取得目錄控制代碼
    $handle = opendir($dirPath);
    if ($handle === false) {
        throw new RuntimeException("無法開啟目錄: $dirPath");
    }

    // 4. 遞迴處理每個項目
    while (($entry = readdir($handle)) !== false) {
        // 跳過 . 和 ..
        if ($entry === '.' || $entry === '..') {
            continue;
        }

        $fullPath = $dirPath . DIRECTORY_SEPARATOR . $entry;

        // 5. 處理符號連結:只刪除連結本身,不跟隨刪除目標
        if (is_link($fullPath)) {
            if (!unlink($fullPath)) {
                closedir($handle);
                throw new RuntimeException("無法刪除符號連結: $fullPath");
            }
            continue;
        }

        // 6. 遞迴刪除子目錄
        if (is_dir($fullPath)) {
            if (!deleteDirectory($fullPath, true)) {
                closedir($handle);
                throw new RuntimeException("子目錄刪除失敗: $fullPath");
            }
            continue;
        }

        // 7. 刪除檔案 (處理唯讀屬性)
        if (is_file($fullPath)) {
            // Windows 唯讀檔案需先移除屬性
            if (!is_writable($fullPath)) {
                @chmod($fullPath, 0666);
            }
            if (!unlink($fullPath)) {
                closedir($handle);
                throw new RuntimeException("無法刪除檔案: $fullPath");
            }
        }
    }
    closedir($handle);

    // 8. 刪除根目錄本身
    if ($deleteRoot) {
        if (!rmdir($dirPath)) {
            throw new RuntimeException("無法刪除目錄: $dirPath (可能非空或權限不足)");
        }
    }

    return true;
}

二、使用範例

<?php
try {
    // 刪除 /var/www/uploads/temp 及其所有內容
    deleteDirectory('/var/www/uploads/temp');
    echo "刪除成功";
} catch (Exception $e) {
    error_log("刪除失敗: " . $e->getMessage());
    echo "操作失敗: " . $e->getMessage();
}

三、進階版:支援通配符與排除規則

/**
 * 進階刪除:支援保留特定檔案/目錄
 *
 * @param string   $dirPath
 * @param array    $excludePatterns 要排除的 glob 模式 (相對於 $dirPath)
 * @param bool     $deleteRoot
 * @return int     刪除的檔案/目錄總數
 */
function deleteDirectoryAdvanced(string $dirPath, array $excludePatterns = [], bool $deleteRoot = true): int {
    $dirPath = rtrim($dirPath, DIRECTORY_SEPARATOR);
    $deletedCount = 0;

    $items = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($dirPath, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST // 先處理子項目,再處理父目錄
    );

    foreach ($items as $item) {
        $path = $item->getPathname();
        $relative = substr($path, strlen($dirPath) + 1);

        // 檢查排除規則
        $excluded = false;
        foreach ($excludePatterns as $pattern) {
            if (fnmatch($pattern, $relative) || fnmatch($pattern, $item->getFilename())) {
                $excluded = true;
                break;
            }
        }
        if ($excluded) {
            continue;
        }

        if ($item->isLink() || $item->isFile()) {
            if (@unlink($path)) {
                $deletedCount++;
            }
        } elseif ($item->isDir()) {
            if (@rmdir($path)) {
                $deletedCount++;
            }
        }
    }

    if ($deleteRoot && @rmdir($dirPath)) {
        $deletedCount++;
    }

    return $deletedCount;
}

// 使用:刪除 temp 但保留 .gitkeep 和 logs 目錄
deleteDirectoryAdvanced('/var/www/temp', ['.gitkeep', 'logs*']);

四、效能與安全注意事項

  • 大量檔案:遞迴可能耗盡記憶體/執行時間,建議分批處理或使用 exec('rm -rf ...') (僅限受信任路徑、Linux 環境)。
  • 權限問題:PHP 執行身分 (www-data, nginx, apache) 必須擁有目錄寫入/執行權限。
  • 符號連結攻擊:務必用 is_link() 檢測並只刪除連結本身,避免誤刪系統關鍵目錄。
  • 路徑遍歷:輸入路徑務必經 realpath() 正規化,並限制在允許根目錄下。

五、單行指令替代方案 (Linux)

<?php
// 僅限受信任、已驗證的絕對路徑
$path = escapeshellarg('/var/www/uploads/temp');
exec("rm -rf $path", $output, $returnCode);
if ($returnCode !== 0) {
    throw new RuntimeException("系統指令刪除失敗");
}

警告:exec() 有注入風險,**嚴禁**直接使用使用者輸入,必須經 escapeshellarg() 並驗證路徑在允許範圍內。

六、總結

遞迴刪除目錄是檔案管理常見需求,掌握 RecursiveDirectoryIterator 或手寫遞迴函式,並注意符號連結、權限、路徑安全,即能建立可靠的清理工具。生產環境建議封裝為 Service 類別,搭配日誌記錄與例外處理,便於追蹤與維運。

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