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

PHP 遞迴刪除目錄與檔案:健全函數實作與安全檢查

     編輯:PHP 相關知識

刪除非空目錄:遞迴遍歷 + 型別判斷 + 路徑遍歷防護

PHP rmdir() 僅能刪除空目錄,刪除包含檔案/子目錄的目錄需遞迴處理。原始函數缺少路徑安全驗證,易導致誤刪系統關鍵目錄。

原始函數問題

<?php
function dir_delete($file) {
    if (file_exists($file)) {
        if (is_dir($file)) {
            $handle = opendir($file);
            while (false !== ($filename = readdir($handle))) {
                if ($filename != "." && $filename != "..")
                    $this->dir_delete($file . "/" . $filename); // $this 在函數外無效
            }
            closedir($handle);
            rmdir($file);
            return true;
        } else {
            unlink($file);
        }
    }
}
?>

缺陷:$this->dir_delete 在普通函數中報錯;無路徑正規化與基準目錄檢查;無錯誤處理;無符號連結處理。

健全版實作

<?php
/**
 * 遞迴刪除目錄或檔案
 * @param string $path 目標路徑
 * @param string $baseDir 允許操作的基準目錄 (防路徑遍歷)
 * @return bool 成功與否
 * @throws InvalidArgumentException 路徑非法
 */
function recursiveDelete(string $path, string $baseDir = ''): bool {
    // 1. 路徑正規化
    $path = realpath($path);
    if ($path === false) return false; // 不存在

    // 2. 基準目錄檢查
    if ($baseDir) {
        $baseDir = realpath($baseDir);
        if ($baseDir === false || strpos($path, $baseDir) !== 0) {
            throw new InvalidArgumentException("路徑超出允許範圍: $path");
        }
    }

    // 3. 符號連結:僅刪除連結本身,不遞迴目標
    if (is_link($path)) {
        return unlink($path);
    }

    // 4. 目錄遞迴
    if (is_dir($path)) {
        $files = array_diff(scandir($path), ['.', '..']);
        foreach ($files as $file) {
            $childPath = $path . DIRECTORY_SEPARATOR . $file;
            if (!recursiveDelete($childPath, $baseDir)) {
                // 可選:記錄失敗、繼續或拋出異常
                error_log("刪除失敗: $childPath");
                return false;
            }
        }
        return rmdir($path);
    }

    // 5. 檔案刪除
    if (is_file($path)) {
        return unlink($path);
    }

    return false;
}

// 使用
try {
    $deleted = recursiveDelete('/var/www/uploads/temp_123', '/var/www/uploads');
    echo $deleted ? '刪除成功' : '刪除失敗';
} catch (InvalidArgumentException $e) {
    echo '安全攔截: ' . $e->getMessage();
}
?>

關鍵安全增強

防護實作
路徑遍歷realpath() 解析後檢查 strpos($path, $baseDir) === 0
符號連結is_link() 僅 unlink() 連結檔,不跟隨目標
權限檢查刪除前可加 is_writable() 或嘗試 touch() 驗證
錯誤處理回傳 bool + error_log,或拋出異常讓上層決定
效能大量檔案考慮 exec('rm -rf ' . escapeshellarg($path)) (需極謹慎驗證)

現代替代:FilesystemIterator + RecursiveIteratorIterator

<?php
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
use FilesystemIterator;

function recursiveDeleteSPL(string $path, string $baseDir = ''): bool {
    $path = realpath($path);
    if (!$path) return false;
    if ($baseDir && strpos($path, realpath($baseDir)) !== 0) {
        throw new InvalidArgumentException("路徑非法");
    }

    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST // 先刪子項
    );

    foreach ($iterator as $file) {
        if ($file->isDir()) {
            rmdir($file->getPathname());
        } else {
            unlink($file->getPathname());
        }
    }
    return rmdir($path);
}
?>

測試案例

<?php
// 建立測試結構
$base = sys_get_temp_dir() . '/del_test_' . uniqid();
mkdir("$base/a/b/c", 0755, true);
file_put_contents("$base/a/file1.txt", 'test');
file_put_contents("$base/a/b/file2.txt", 'test');
symlink("$base/a", "$base/link_to_a");

recursiveDelete($base); // 應完全刪除,不影響 link_to_a 指向的原目錄 (已一同刪除)
// 若要保留符號連結目標,需在 is_link 判斷處理
?>

遞迴刪除是高風險操作,務必實作基準目錄鎖定、符號連結隔離、錯誤記錄與權限驗證。生產環境建議使用 SPL 迭代器版本,程式碼更簡潔、行為更可預期。

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