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

PHP 遞歸複製目錄與子目錄:完整實現與陷阱避坑指南

     編輯:PHP 相關知識

專案部署、備份、快取清理等場景常需「整個目錄樹」複製。PHP 無內建單一函數完成此任務,需自行實現遞歸遍歷。本文提供健壯的遞歸複製函數,並解析常見陷阱(權限、符號連結、路徑分隔符)。

核心遞歸邏輯

<?php
/**
 * 遞歸複製目錄
 * @param string $src 來源目錄絕對路徑
 * @param string $dst 目標目錄絕對路徑
 * @param bool $preservePermissions 是否保留權限(預設 true)
 * @return bool 成功與否
 */
function recursiveCopy(string $src, string $dst, bool $preservePermissions = true): bool
{
    // 1. 規範化路徑:移除尾部斜線,統一用 DIRECTORY_SEPARATOR
    $src = rtrim($src, DIRECTORY_SEPARATOR);
    $dst = rtrim($dst, DIRECTORY_SEPARATOR);

    // 2. 來源必須存在且為目錄
    if (!is_dir($src)) {
        trigger_error("來源非目錄或不存在: $src", E_USER_WARNING);
        return false;
    }

    // 3. 建立目標目錄(遞歸建立,權限 0755)
    if (!is_dir($dst)) {
        if (!mkdir($dst, 0755, true) && !is_dir($dst)) {
            trigger_error("無法建立目標目錄: $dst", E_USER_ERROR);
            return false;
        }
    }

    // 4. 開啟來源目錄控制代碼
    $dirHandle = opendir($src);
    if (!$dirHandle) {
        trigger_error("無法開啟來源目錄: $src", E_USER_WARNING);
        return false;
    }

    $success = true;

    while (($entry = readdir($dirHandle)) !== false) {
        // 跳過 . 和 ..
        if ($entry === '.' || $entry === '..') continue;

        $srcPath = $src . DIRECTORY_SEPARATOR . $entry;
        $dstPath = $dst . DIRECTORY_SEPARATOR . $entry;

        if (is_dir($srcPath)) {
            // 遞歸處理子目錄
            if (!recursiveCopy($srcPath, $dstPath, $preservePermissions)) {
                $success = false;
                break; // 可選:遇錯立即停止
            }
        } elseif (is_file($srcPath) || is_link($srcPath)) {
            // 複製檔案或符號連結
            if (!@copy($srcPath, $dstPath)) {
                trigger_error("複製失敗: $srcPath -> $dstPath", E_USER_WARNING);
                $success = false;
                break;
            }
            // 可選:保留檔案修改時間
            if ($preservePermissions) {
                @touch($dstPath, filemtime($srcPath));
                @chmod($dstPath, fileperms($srcPath));
            }
        }
        // 忽略 socket、FIFO 等特殊檔案
    }

    closedir($dirHandle);
    return $success;
}

// 使用範例
if (recursiveCopy('/var/www/project', '/backup/project_20240520')) {
    echo "目錄樹複製完成";
} else {
    echo "複製過程中發生錯誤";
}
?>

關鍵細節解析

  1. 路徑分隔符:使用 DIRECTORY_SEPARATOR 常數,確保 Windows/Linux 相容。
  2. 權限保留:copy() 不保留權限與時間,需額外 chmod()/touch()。
  3. 符號連結:is_link() 偵測連結,copy() 會複製連結指向的內容而非連結本身。若需保留連結結構,需用 readlink() + symlink() 處理。
  4. 錯誤抑制 @:生產環境建議移除 @,改用 try-catch 或檢查 error_get_last() 記錄詳細錯誤。
  5. 效能考量:超大目錄樹遞歸可能觸發記憶體/堆疊限制。極端情況可改用迭代器 (RecursiveDirectoryIterator) 或系統指令 (cp -r/robocopy)。

現代替代:SPL 迭代器寫法(PHP 5.3+)

<?php
function recursiveCopySpl(string $src, string $dst): bool
{
    if (!is_dir($src)) return false;
    if (!is_dir($dst) && !mkdir($dst, 0755, true)) return false;

    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($src, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );

    foreach ($iterator as $item) {
        $target = $dst . DIRECTORY_SEPARATOR . $iterator->getSubPathName();
        if ($item->isDir()) {
            if (!is_dir($target) && !mkdir($target, 0755, true)) return false;
        } else {
            if (!@copy($item->getPathname(), $target)) return false;
        }
    }
    return true;
}
?>

SPL 版本碼更簡潔、自動處理遞歸順序(SELF_FIRST 先建目錄再複製檔案),是現代 PHP 專案的推薦寫法。

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