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

PHP 目錄操作全攻略:遞歸遍歷、複製、刪除與大小計算

     編輯:PHP 綜合知識

前言

文件系統操作是 PHP 開發中的高頻需求。本文系統整理了目錄遞歸遍歷、整體複製、遞歸刪除、大小統計四大核心操作的標準實現模式,並深度解析遞歸算法在文件系統中的應用精髓,助你構建健壯的文件管理功能。

核心操作實現

1. 遞歸遍歷目錄(列出所有文件)

<?php

/**
 * 遞歸遍歷目錄,輸出所有文件名
 * @param string $dirPath 目錄路徑
 * @param int $depth 當前遞歸深度(內部使用)
 */
function listDir(string $dirPath, int $depth = 0): void {
    // 規範化路徑
    $dirPath = rtrim($dirPath, '/\\');
    
    if (!is_dir($dirPath)) {
        throw new InvalidArgumentException("目錄不存在: {$dirPath}");
    }
    
    $handle = opendir($dirPath);
    if (!$handle) {
        throw new RuntimeException("無法打開目錄: {$dirPath}");
    }
    
    try {
        while (($entry = readdir($handle)) !== false) {
            // 跳過特殊目錄
            if ($entry === '.' || $entry === '..') continue;
            
            $fullPath = $dirPath . DIRECTORY_SEPARATOR . $entry;
            
            if (is_dir($fullPath)) {
                // 目錄:遞歸處理
                echo str_repeat('  ', $depth) . "[DIR] {$entry}\n";
                listDir($fullPath, $depth + 1);
            } else {
                // 文件:直接輸出
                echo str_repeat('  ', $depth) . "[FILE] {$entry}\n";
            }
        }
    } finally {
        closedir($handle);
    }
}

// 使用示例
listDir(__DIR__ . '/storage/app');

2. 遞歸複製目錄(保持結構)

<?php

/**
 * 遞歸複製整個目錄結構
 * @param string $srcDir 源目錄
 * @param string $dstDir 目標目錄
 * @param int $mode 目錄權限(默認 0755)
 * @return bool 是否成功
 */
function copyDir(string $srcDir, string $dstDir, int $mode = 0755): bool {
    $srcDir = rtrim($srcDir, '/\\');
    $dstDir = rtrim($dstDir, '/\\');
    
    if (!is_dir($srcDir)) {
        throw new InvalidArgumentException("源目錄不存在: {$srcDir}");
    }
    
    // 創建目標目錄
    if (!is_dir($dstDir)) {
        if (!mkdir($dstDir, $mode, true) && !is_dir($dstDir)) {
            throw new RuntimeException("無法創建目標目錄: {$dstDir}");
        }
    }
    
    $handle = opendir($srcDir);
    if (!$handle) {
        throw new RuntimeException("無法打開源目錄: {$srcDir}");
    }
    
    try {
        while (($entry = readdir($handle)) !== false) {
            if ($entry === '.' || $entry === '..') continue;
            
            $srcPath = $srcDir . DIRECTORY_SEPARATOR . $entry;
            $dstPath = $dstDir . DIRECTORY_SEPARATOR . $entry;
            
            if (is_dir($srcPath)) {
                // 遞歸複製子目錄
                if (!copyDir($srcPath, $dstPath, $mode)) {
                    return false;
                }
            } else {
                // 複製文件
                if (!copy($srcPath, $dstPath)) {
                    throw new RuntimeException("文件複製失敗: {$srcPath} -> {$dstPath}");
                }
                // 可選:保持文件權限
                @chmod($dstPath, 0644);
            }
        }
        return true;
    } finally {
        closedir($handle);
    }
}

3. 遞歸刪除目錄(清空並移除)

<?php

/**
 * 遞歸刪除目錄及其所有內容
 * @param string $dirPath 目錄路徑
 * @return bool 是否成功
 */
function deleteDir(string $dirPath): bool {
    $dirPath = rtrim($dirPath, '/\\');
    
    if (!is_dir($dirPath)) {
        return true; // 已不存在視為成功
    }
    
    $handle = opendir($dirPath);
    if (!$handle) {
        throw new RuntimeException("無法打開目錄進行刪除: {$dirPath}");
    }
    
    try {
        while (($entry = readdir($handle)) !== false) {
            if ($entry === '.' || $entry === '..') continue;
            
            $fullPath = $dirPath . DIRECTORY_SEPARATOR . $entry;
            
            if (is_dir($fullPath)) {
                // 遞歸刪除子目錄
                if (!deleteDir($fullPath)) {
                    return false;
                }
            } else {
                // 刪除文件
                if (!unlink($fullPath)) {
                    throw new RuntimeException("文件刪除失敗: {$fullPath}");
                }
            }
        }
    } finally {
        closedir($handle);
    }
    
    // 最後刪除空目錄本身
    return rmdir($dirPath);
}

4. 遞歸計算目錄大小

<?php

/**
 * 遞歸計算目錄總大小(位元組)
 * @param string $dirPath 目錄路徑
 * @return int 總大小(位元組)
 */
function getDirSize(string $dirPath): int {
    $dirPath = rtrim($dirPath, '/\\');
    
    if (!is_dir($dirPath)) {
        throw new InvalidArgumentException("目錄不存在: {$dirPath}");
    }
    
    $totalSize = 0;
    $handle = opendir($dirPath);
    
    if (!$handle) {
        throw new RuntimeException("無法打開目錄: {$dirPath}");
    }
    
    try {
        while (($entry = readdir($handle)) !== false) {
            if ($entry === '.' || $entry === '..') continue;
            
            $fullPath = $dirPath . DIRECTORY_SEPARATOR . $entry;
            
            if (is_dir($fullPath)) {
                // 遞歸累加子目錄大小
                $totalSize += getDirSize($fullPath);
            } elseif (is_file($fullPath)) {
                // 累加文件大小
                $totalSize += filesize($fullPath);
            }
            // 忽略符號鏈接等特殊文件
        }
    } finally {
        closedir($handle);
    }
    
    return $totalSize;
}

/**
 * 格式化位元組為人類可讀格式
 */
function formatBytes(int $bytes, int $precision = 2): string {
    $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
    $pow = floor(($bytes > 0 ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);
    $bytes /= (1024 ** $pow);
    return round($bytes, $precision) . ' ' . $units[$pow];
}

// 使用示例
$size = getDirSize('/var/www/uploads');
echo "目錄大小: " . formatBytes($size); // 輸出如:目錄大小: 12.45 MB

遞歸算法核心解析

遞歸三要素

  1. 基線條件:遇到文件時直接處理,不再遞歸
  2. 遞歸步驟:遇到子目錄時調用自身處理
  3. 狀態傳遞:通過參數傳遞路徑、深度、累計值等上下文

關鍵實現細節

細節說明
路徑拼接使用 DIRECTORY_SEPARATOR 常量確保跨平台兼容
資源管理使用 try/finally 確保 closedir() 必執行
錯誤處理檢查 opendir() 返回值,異常時拋出具體異常
權限保持複製時可選保持文件權限(chmod)
符號鏈接生產環境建議添加 is_link() 判斷,避免循環引用

現代化替代方案:SPL 迭代器

PHP 5.3+ 提供了更優雅的 RecursiveDirectoryIterator:

<?php

// 遍歷所有文件(現代寫法)
$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('/path/to/dir', FilesystemIterator::SKIP_DOTS)
);

foreach ($iterator as $file) {
    echo $file->getPathname() . ' (' . $file->getSize() . " bytes)\n";
}

// 計算目錄大小(一行代碼)
$totalSize = iterator_reduce(
    new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator('/path/to/dir', FilesystemIterator::SKIP_DOTS)
    ),
    fn($carry, $file) => $carry + $file->getSize(),
    0
);

性能與安全建議

  • 大目錄優化:超大目錄(10萬+文件)考慮使用 scandir() + 非遞歸隊列算法,避免棧溢出
  • 權限最小化:Web 目錄操作應限制在特定子目錄,防止路徑遍歷攻擊
  • 異步處理:大規模複製/刪除操作應放入任務隊列(如 Laravel Queue)後台執行
  • 磁盤配額:結合 disk_free_space() 檢查剩餘空間,防止寫滿磁盤
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved