PHP copy() 函数

说明: copy() 函数用于将一个文件复制到另一个位置。

语法

bool copy ( string $source , string $dest [, resource $context ] )

参数说明

参数 描述 必需
source 源文件路径
dest 目标文件路径
context 上下文资源,用于流操作
通常用于处理HTTP/FTP等协议

返回值

  • 成功时返回 TRUE
  • 失败时返回 FALSE

注意事项

  • 如果目标文件已存在,它将被覆盖
  • 需要源文件的读取权限和目标目录的写入权限
  • 不能用于复制目录,只能复制文件
  • 对于大文件,可能需要设置适当的超时时间
  • 在Windows系统上,需要确保文件路径正确
  • 复制后不会保留原文件的权限,可以使用chmod()重新设置

示例

示例 1:基本文件复制

<?php
// 源文件路径
$source = "original.txt";
// 目标文件路径
$dest = "copy.txt";

// 创建源文件
file_put_contents($source, "这是原始文件内容");

// 复制文件
if (copy($source, $dest)) {
    echo "文件复制成功!\n";

    // 验证复制结果
    if (file_exists($dest)) {
        echo "目标文件已创建,内容:\n";
        echo file_get_contents($dest);
    }
} else {
    echo "文件复制失败!";
}

// 清理
unlink($source);
unlink($dest);
?>

示例 2:复制到不同目录

<?php
// 源文件
$source = "data.txt";
file_put_contents($source, "重要数据");

// 目标目录(确保目录存在且有写入权限)
$destination_dir = "backups/";
if (!is_dir($destination_dir)) {
    mkdir($destination_dir, 0755, true);
}

// 目标文件路径
$dest = $destination_dir . "data_backup_" . date('Ymd_His') . ".txt";

// 复制文件
if (copy($source, $dest)) {
    echo "文件已备份到: $dest\n";

    // 显示备份文件信息
    echo "备份文件大小: " . filesize($dest) . " 字节\n";
    echo "备份时间: " . date('Y-m-d H:i:s', filemtime($dest));
} else {
    echo "备份失败!";
    echo "错误信息: " . error_get_last()['message'] ?? '未知错误';
}

// 清理
unlink($source);
// 保留备份文件用于演示
?>

示例 3:复制并保留文件权限

<?php
// 创建源文件并设置权限
$source = "config.php";
file_put_contents($source, "<?php\n// 配置文件\n\$config = [];");
chmod($source, 0640); // 设置源文件权限

// 复制文件
$dest = "config_backup.php";
if (copy($source, $dest)) {
    echo "文件复制成功!\n";

    // 获取源文件和目标文件的权限
    $source_perms = fileperms($source);
    $dest_perms = fileperms($dest);

    echo "源文件权限: " . decoct($source_perms & 0777) . "\n";
    echo "目标文件权限(默认): " . decoct($dest_perms & 0777) . "\n";

    // 将目标文件权限设置为与源文件相同
    if (chmod($dest, $source_perms & 0777)) {
        clearstatcache();
        $new_dest_perms = fileperms($dest);
        echo "目标文件权限(已更新): " . decoct($new_dest_perms & 0777) . "\n";
    }
} else {
    echo "复制失败!";
}

// 清理
unlink($source);
unlink($dest);
?>

示例 4:批量复制文件

<?php
/**
 * 批量复制文件函数
 */
function batch_copy_files($source_dir, $dest_dir, $pattern = "*") {
    if (!is_dir($source_dir)) {
        return ['error' => '源目录不存在'];
    }

    if (!is_dir($dest_dir)) {
        // 创建目标目录
        if (!mkdir($dest_dir, 0755, true)) {
            return ['error' => '无法创建目标目录'];
        }
    }

    $results = [
        'total' => 0,
        'success' => 0,
        'failed' => 0,
        'files' => []
    ];

    // 获取匹配的文件
    $files = glob($source_dir . '/' . $pattern);

    foreach ($files as $source_file) {
        if (!is_file($source_file)) {
            continue;
        }

        $filename = basename($source_file);
        $dest_file = $dest_dir . '/' . $filename;

        $results['total']++;

        // 检查目标文件是否已存在
        if (file_exists($dest_file)) {
            $results['files'][] = [
                'file' => $filename,
                'status' => 'skipped',
                'message' => '目标文件已存在'
            ];
            continue;
        }

        // 复制文件
        if (copy($source_file, $dest_file)) {
            $results['success']++;
            $results['files'][] = [
                'file' => $filename,
                'status' => 'success',
                'size' => filesize($source_file)
            ];
        } else {
            $results['failed']++;
            $results['files'][] = [
                'file' => $filename,
                'status' => 'failed',
                'error' => error_get_last()['message'] ?? '未知错误'
            ];
        }
    }

    return $results;
}

// 使用示例
// 创建示例文件
mkdir('source_files', 0755);
mkdir('destination_files', 0755);

$files = ['file1.txt', 'file2.txt', 'file3.txt'];
foreach ($files as $file) {
    file_put_contents('source_files/' . $file, "这是 $file 的内容");
}

// 执行批量复制
$result = batch_copy_files('source_files', 'destination_files', '*.txt');

echo "批量复制结果:\n";
echo "总共处理: {$result['total']} 个文件\n";
echo "成功: {$result['success']} 个\n";
echo "失败: {$result['failed']} 个\n";

foreach ($result['files'] as $file) {
    echo "- {$file['file']}: {$file['status']}\n";
}

// 清理
foreach ($files as $file) {
    unlink('source_files/' . $file);
    unlink('destination_files/' . $file);
}
rmdir('source_files');
rmdir('destination_files');
?>

示例 5:使用上下文复制文件

<?php
/**
 * 使用流上下文复制文件
 * 可用于处理HTTP、FTP等协议的文件复制
 */
function copy_with_context($source, $dest, $context_options = null) {
    if ($context_options) {
        // 创建流上下文
        $context = stream_context_create($context_options);
        return copy($source, $dest, $context);
    }

    return copy($source, $dest);
}

// 示例1:使用HTTP上下文下载文件
$http_options = [
    'http' => [
        'method' => 'GET',
        'header' => "User-Agent: My PHP Script\r\n",
        'timeout' => 30
    ]
];

// 示例:从URL复制文件到本地
/*
$url = "https://example.com/file.zip";
$local_file = "downloaded_file.zip";

if (copy_with_context($url, $local_file, $http_options)) {
    echo "文件下载成功!";
} else {
    echo "文件下载失败!";
}
*/

// 示例2:复制文件时设置超时
$file_options = [
    'socket' => [
        'bindto' => '0:0', // 使用所有可用接口
    ]
];

// 示例:复制大文件时设置超时
$large_source = "large_file.iso";
$large_dest = "large_file_copy.iso";

// 设置脚本执行时间限制
set_time_limit(300); // 5分钟

if (copy_with_context($large_source, $large_dest, $file_options)) {
    echo "大文件复制成功!";
} else {
    echo "大文件复制失败: " . error_get_last()['message'];
}

// 示例3:复制FTP服务器上的文件(需要PHP支持ftp协议)
$ftp_options = [
    'ftp' => [
        'overwrite' => true,
        'resume_pos' => 0 // 从指定位置恢复下载
    ]
];

/*
$ftp_source = "ftp://username:password@ftp.example.com/remote/file.txt";
$local_dest = "local_file.txt";

if (copy_with_context($ftp_source, $local_dest, $ftp_options)) {
    echo "FTP文件复制成功!";
}
*/
?>

示例 6:安全的文件复制函数

<?php
/**
 * 安全的文件复制函数,包含错误处理和验证
 */
function safe_copy($source, $dest, $options = []) {
    $defaults = [
        'overwrite' => false,
        'create_dir' => true,
        'preserve_perms' => false,
        'max_size' => 0, // 0表示无限制
        'allowed_extensions' => [] // 空数组表示允许所有
    ];

    $options = array_merge($defaults, $options);

    // 验证源文件
    if (!file_exists($source)) {
        return ['success' => false, 'error' => '源文件不存在'];
    }

    if (!is_file($source)) {
        return ['success' => false, 'error' => '源文件不是一个普通文件'];
    }

    if (!is_readable($source)) {
        return ['success' => false, 'error' => '源文件不可读'];
    }

    // 验证文件大小
    $source_size = filesize($source);
    if ($options['max_size'] > 0 && $source_size > $options['max_size']) {
        return ['success' => false, 'error' => "文件大小超过限制: {$options['max_size']} 字节"];
    }

    // 验证文件扩展名
    if (!empty($options['allowed_extensions'])) {
        $ext = strtolower(pathinfo($source, PATHINFO_EXTENSION));
        if (!in_array($ext, $options['allowed_extensions'])) {
            return ['success' => false, 'error' => "不允许的文件类型: .$ext"];
        }
    }

    // 处理目标目录
    $dest_dir = dirname($dest);
    if (!is_dir($dest_dir)) {
        if ($options['create_dir']) {
            if (!mkdir($dest_dir, 0755, true)) {
                return ['success' => false, 'error' => '无法创建目标目录'];
            }
        } else {
            return ['success' => false, 'error' => '目标目录不存在'];
        }
    }

    // 检查目标文件是否已存在
    if (file_exists($dest)) {
        if (!$options['overwrite']) {
            return ['success' => false, 'error' => '目标文件已存在'];
        }

        // 检查目标文件是否可写
        if (!is_writable($dest)) {
            return ['success' => false, 'error' => '目标文件不可写'];
        }
    } else {
        // 检查目标目录是否可写
        if (!is_writable($dest_dir)) {
            return ['success' => false, 'error' => '目标目录不可写'];
        }
    }

    // 保存源文件信息(如果需要保留权限)
    $source_info = [
        'perms' => fileperms($source),
        'size' => $source_size,
        'mtime' => filemtime($source)
    ];

    // 执行复制
    if (copy($source, $dest)) {
        $result = ['success' => true, 'message' => '文件复制成功'];

        // 保留权限
        if ($options['preserve_perms']) {
            chmod($dest, $source_info['perms']);
        }

        // 验证复制结果
        clearstatcache();
        $dest_size = filesize($dest);
        if ($dest_size === $source_info['size']) {
            $result['verified'] = true;
            $result['size'] = $dest_size;
        } else {
            $result['verified'] = false;
            $result['warning'] = "文件大小验证失败: 源文件 {$source_info['size']} 字节, 目标文件 {$dest_size} 字节";
        }

        return $result;
    } else {
        $error = error_get_last();
        return ['success' => false, 'error' => $error['message'] ?? '复制操作失败'];
    }
}

// 使用示例
$result = safe_copy('source.txt', 'backup/source_backup.txt', [
    'overwrite' => true,
    'create_dir' => true,
    'preserve_perms' => true,
    'max_size' => 1024 * 1024, // 1MB限制
    'allowed_extensions' => ['txt', 'log', 'csv']
]);

if ($result['success']) {
    echo "复制成功!\n";
    if (isset($result['verified']) && $result['verified']) {
        echo "文件验证通过,大小: {$result['size']} 字节";
    }
} else {
    echo "复制失败: " . $result['error'];
}
?>

常见错误及解决方法

故障排除
错误/问题 可能原因 解决方法
Warning: copy(): failed to open stream: Permission denied 权限不足 检查源文件读取权限和目标目录写入权限
Warning: copy(): The first argument to copy() function cannot be a directory 尝试复制目录 copy()只能复制文件,使用其他方法复制目录
目标文件被覆盖但大小不一致 复制过程中出错或中断 验证复制后的文件大小,使用安全复制函数
复制大文件超时 执行时间限制 使用set_time_limit()增加执行时间限制
copy()返回false但没有错误信息 PHP配置限制 检查php.ini中的safe_mode、open_basedir等设置

相关函数

最佳实践
  1. 错误处理:始终检查copy()的返回值,并使用error_get_last()获取错误信息
  2. 权限管理:确保源文件可读,目标目录可写
  3. 文件验证:复制后验证文件大小以确保数据完整性
  4. 大文件处理:对于大文件,考虑使用流式复制或分块复制
  5. 备份策略:复制前检查目标文件是否存在,避免意外覆盖
  6. 日志记录:记录复制操作的结果,便于调试和审计
  7. 性能考虑:批量复制大量文件时,考虑使用队列或分批处理
扩展知识:复制目录的方法

copy()函数只能复制文件,不能复制目录。如果需要复制目录,可以使用以下方法:

<?php
// 递归复制目录的函数
function copy_directory($source, $dest) {
    if (!is_dir($source)) {
        return false;
    }

    if (!is_dir($dest)) {
        mkdir($dest, 0755, true);
    }

    $dir = opendir($source);
    while (($file = readdir($dir)) !== false) {
        if ($file == '.' || $file == '..') {
            continue;
        }

        $source_path = $source . '/' . $file;
        $dest_path = $dest . '/' . $file;

        if (is_dir($source_path)) {
            copy_directory($source_path, $dest_path);
        } else {
            copy($source_path, $dest_path);
        }
    }

    closedir($dir);
    return true;
}
?>