萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

PHP 模擬 xcopy 指令:xCopy() 遞迴複製目錄完整實作

函數簽名與參數

/**
 * 遞迴複製目錄內容
 * @param string $source      來源目錄
 * @param string $destination 目的目錄
 * @param bool   $child       是否包含子目錄
 * @return int  1=成功, 0=失敗
 */
function xCopy($source, $destination, $child) { ... }

完整實作代碼

<?php
function xCopy($source, $destination, $child) {
    // 1. 來源必須是目錄
    if (!is_dir($source)) {
        trigger_error("Error: \$source ($source) is not a directory!", E_USER_WARNING);
        return 0;
    }
    
    // 2. 目的目錄不存在則建立
    if (!is_dir($destination)) {
        if (!mkdir($destination, 0777, true)) {
            trigger_error("Error: Cannot create directory $destination", E_USER_WARNING);
            return 0;
        }
    }
    
    $handle = @opendir($source);
    if (!$handle) {
        trigger_error("Error: Cannot open directory $source", E_USER_WARNING);
        return 0;
    }
    
    while (($entry = readdir($handle)) !== false) {
        if ($entry === '.' || $entry === '..') continue;
        
        $srcPath = $source . '/' . $entry;
        $dstPath = $destination . '/' . $entry;
        
        if (is_dir($srcPath)) {
            // 3. 子目錄:遞迴呼叫
            if ($child) {
                $result = xCopy($srcPath, $dstPath, $child);
                if ($result === 0) return 0;  // 向上傳遞失敗
            }
        } else {
            // 4. 檔案:直接複製
            if (!@copy($srcPath, $dstPath)) {
                trigger_error("Warning: Failed to copy $srcPath to $dstPath", E_USER_WARNING);
                // 可選:決定是否繼續或中止
            }
        }
    }
    closedir($handle);
    return 1;
}
?>

使用範例

// 複製完整目錄樹(含子目錄)
xCopy("/var/www/html/project", "/backup/project_".date("Ymd"), true);

// 僅複製第一層檔案(不含子目錄)
xCopy("/var/www/html/project", "/backup/project_files_only", false);

進階強化版

function xCopyAdvanced($source, $destination, $options = []) {
    $defaults = [
        'recursive' => true,           // 遞迴子目錄
        'overwrite' => true,           // 覆蓋既有檔案
        'preserve_permissions' => true, // 保留權限/擁有者/時間
        'exclude_patterns' => [],      // 排除模式 ['.git','*.log','tmp/']
        'callback' => null,            // 進度回呼 function($src,$dst,$status)
        'max_file_size' => 0,          // 0=不限制
    ];
    $opts = array_merge($defaults, $options);
    
    // 正規化路徑
    $source = realpath($source);
    if (!$source || !is_dir($source)) throw new InvalidArgumentException("Invalid source");
    $destination = realpath($destination) ?: $destination;
    
    return _xCopyRecursive($source, $destination, $opts, 0);
}

function _xCopyRecursive($src, $dst, $opts, $depth) {
    $handle = @opendir($src);
    if (!$handle) return false;
    
    if (!is_dir($dst)) {
        if (!mkdir($dst, 0777, true)) return false;
        if ($opts['preserve_permissions']) {
            $stat = stat($src);
            @chmod($dst, $stat['mode']);
            @chown($dst, $stat['uid']);
            @chgrp($dst, $stat['gid']);
        }
    }
    
    while (($entry = readdir($handle)) !== false) {
        if ($entry === '.' || $entry === '..') continue;
        
        // 排除模式匹配
        if ($opts['exclude_patterns']) {
            $matched = false;
            foreach ($opts['exclude_patterns'] as $pattern) {
                if (fnmatch($pattern, $entry) || fnmatch($pattern, $src.'/'.$entry)) {
                    $matched = true; break;
                }
            }
            if ($matched) continue;
        }
        
        $srcPath = $src . '/' . $entry;
        $dstPath = $dst . '/' . $entry;
        
        if ($file->isDir()) {
            if ($opts['recursive']) {
                if (!_xCopyRecursive($srcPath, $dstPath, $opts, $depth+1)) return false;
            }
        } else {
            // 檔案大小限制
            if ($opts['max_file_size'] > 0 && filesize($srcPath) > $opts['max_file_size']) {
                if ($opts['callback']) $opts['callback']($srcPath, $dstPath, 'skipped_size');
                continue;
            }
            
            // 覆蓋檢查
            if (file_exists($dstPath) && !$opts['overwrite']) {
                if ($opts['callback']) $opts['callback']($srcPath, $dstPath, 'skipped_exists');
                continue;
            }
            
            if (@copy($srcPath, $dstPath)) {
                if ($opts['preserve_permissions']) {
                    $stat = stat($srcPath);
                    @chmod($dstPath, $stat['mode']);
                    @touch($dstPath, $stat['mtime']);
                }
                if ($opts['callback']) $opts['callback']($srcPath, $dstPath, 'copied');
            } else {
                if ($opts['callback']) $opts['callback']($srcPath, $dstPath, 'failed');
                return false;
            }
        }
    }
    closedir($handle);
    return true;
}

現代替代方案

方案優點適用場景
cp -r (shell_exec)系統級、最快、保留所有屬性Linux/Unix 環境、大量資料
rsync -a增量同步、斷點續傳、網路傳輸備份、部署、跨伺服器同步
Symfony Finder + FilesystemOOP、測試友善、跨平台現代 PHP 專案、Composer 專案
PHP 內建 copy() + RecursiveIterator無依賴、細粒度控制需自訂邏輯、過濾、進度回報

常見坑

  • Windows 路徑分隔符 \ vs / → 用 DIRECTORY_SEPARATOR 或統一 /
  • 權限不足:PHP 進程需具備來源讀取、目的寫入權限
  • 符號連結:is_dir() 返回 true,copy() 會複製連結而非目標 → 需 is_link() 特殊處理
  • 開啟檔案過多:大目錄遞迴可能耗盡檔案描述符 → 分批或增加 ulimit -n
copyright © 萬盛學電腦網 all rights reserved