PHP curl_file_create函数

定义和用法

curl_file_create() 函数用于创建一个 CURLFile 对象,用于通过cURL上传文件。这是PHP 5.5+中推荐的文件上传方式,取代了旧的 @ 前缀语法。

注意:此函数在PHP 5.5.0及以上版本可用,并且需要cURL支持。

语法

curl_file_create ( string $filename [, string $mimetype = "" [, string $postname = "" ]] ) : CURLFile

参数

参数 描述
filename 要上传的文件的路径。
mimetype 可选。文件的MIME类型。如果未指定,cURL会尝试自动检测。
postname 可选。文件在POST数据中的字段名称。如果未指定,使用文件名。

返回值

返回一个 CURLFile 对象。

示例

示例 1:基本用法 - 上传单个文件

// 检查文件是否存在
$filePath = '/path/to/your/file.jpg';

if (!file_exists($filePath)) {
    die("文件不存在: {$filePath}");
}

// 创建CURLFile对象
$cfile = curl_file_create(
    $filePath,           // 文件路径
    'image/jpeg',       // MIME类型
    'myfile.jpg'        // POST字段名
);

// 准备POST数据
$postData = [
    'file' => $cfile,
    'user_id' => 123,
    'description' => '上传文件示例'
];

// 初始化cURL
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => 'https://httpbin.org/post',
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $postData,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Content-Type: multipart/form-data'
    ]
]);

// 执行请求
$response = curl_exec($ch);

if (curl_errno($ch)) {
    echo 'cURL错误: ' . curl_error($ch);
} else {
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    echo "HTTP状态码: {$httpCode}\n";
    echo "响应内容长度: " . strlen($response) . " 字节\n";
}

curl_close($ch);

示例 2:上传多个文件

// 准备多个文件
$files = [
    [
        'path' => '/path/to/image1.jpg',
        'field' => 'images[]',
        'mime' => 'image/jpeg'
    ],
    [
        'path' => '/path/to/image2.png',
        'field' => 'images[]',
        'mime' => 'image/png'
    ],
    [
        'path' => '/path/to/document.pdf',
        'field' => 'documents',
        'mime' => 'application/pdf'
    ]
];

// 构建POST数据
$postData = [
    'user_id' => 456,
    'username' => 'john_doe'
];

foreach ($files as $file) {
    if (file_exists($file['path'])) {
        $cfile = curl_file_create(
            $file['path'],
            $file['mime'],
            basename($file['path'])
        );
        $postData[$file['field']] = $cfile;
    } else {
        echo "文件不存在: {$file['path']}\n";
    }
}

// 初始化cURL
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => 'https://httpbin.org/post',
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $postData,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30
]);

// 执行请求
$response = curl_exec($ch);

if ($response === false) {
    echo 'cURL错误: ' . curl_error($ch);
} else {
    $result = json_decode($response, true);
    if ($result && isset($result['files'])) {
        echo "成功上传 " . count($result['files']) . " 个文件\n";
        foreach ($result['files'] as $field => $filename) {
            echo "  {$field}: {$filename}\n";
        }
    }
}

curl_close($ch);

示例 3:带进度回调的文件上传

class FileUploader {
    private $ch;
    private $uploadedBytes = 0;
    private $totalBytes = 0;

    public function __construct($url) {
        $this->ch = curl_init();

        curl_setopt_array($this->ch, [
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_NOPROGRESS => false,
            CURLOPT_PROGRESSFUNCTION => [$this, 'progressCallback'],
            CURLOPT_TIMEOUT => 0, // 无超时限制,适用于大文件
            CURLOPT_HTTPHEADER => [
                'Expect: ' // 防止100-continue等待
            ]
        ]);
    }

    public function progressCallback($resource, $downloadSize, $downloaded, $uploadSize, $uploaded) {
        if ($uploadSize > 0) {
            $this->totalBytes = $uploadSize;
            $this->uploadedBytes = $uploaded;

            $percentage = ($uploadSize > 0) ? round(($uploaded / $uploadSize) * 100, 2) : 0;

            echo "上传进度: {$percentage}% ({$uploaded}/{$uploadSize} 字节)\r";

            if ($uploaded >= $uploadSize) {
                echo "\n上传完成!\n";
            }
        }
        return 0;
    }

    public function uploadFile($filePath, $fieldName = 'file', $additionalData = []) {
        if (!file_exists($filePath)) {
            throw new Exception("文件不存在: {$filePath}");
        }

        // 获取文件信息
        $fileSize = filesize($filePath);
        $fileName = basename($filePath);
        $mimeType = mime_content_type($filePath) ?: 'application/octet-stream';

        echo "开始上传文件: {$fileName} ({$fileSize} 字节)\n";

        // 创建CURLFile
        $cfile = curl_file_create($filePath, $mimeType, $fileName);

        // 构建POST数据
        $postData = array_merge($additionalData, [
            $fieldName => $cfile
        ]);

        curl_setopt($this->ch, CURLOPT_POSTFIELDS, $postData);

        // 执行上传
        $startTime = microtime(true);
        $response = curl_exec($this->ch);
        $endTime = microtime(true);

        $uploadTime = $endTime - $startTime;
        $speed = ($fileSize > 0 && $uploadTime > 0) ? round($fileSize / $uploadTime / 1024, 2) : 0;

        echo "\n上传统计:\n";
        echo "  文件大小: {$fileSize} 字节\n";
        echo "  上传时间: " . round($uploadTime, 2) . " 秒\n";
        echo "  平均速度: {$speed} KB/秒\n";

        if ($response === false) {
            throw new Exception('上传失败: ' . curl_error($this->ch));
        }

        return [
            'response' => $response,
            'http_code' => curl_getinfo($this->ch, CURLINFO_HTTP_CODE),
            'upload_time' => $uploadTime,
            'speed_kbps' => $speed
        ];
    }

    public function __destruct() {
        if ($this->ch) {
            curl_close($this->ch);
        }
    }
}

// 使用示例
try {
    $uploader = new FileUploader('https://httpbin.org/post');

    $result = $uploader->uploadFile(
        '/path/to/large/file.zip',
        'uploaded_file',
        [
            'user_id' => 789,
            'category' => 'backup'
        ]
    );

    echo "上传成功! HTTP状态码: {$result['http_code']}\n";

} catch (Exception $e) {
    echo "错误: " . $e->getMessage() . "\n";
}

示例 4:验证和限制上传文件

class SecureFileUploader {
    private $allowedTypes = [
        'image/jpeg' => 'jpg',
        'image/png' => 'png',
        'image/gif' => 'gif',
        'application/pdf' => 'pdf',
        'text/plain' => 'txt'
    ];

    private $maxFileSize = 10 * 1024 * 1024; // 10MB

    public function validateFile($filePath) {
        // 检查文件是否存在
        if (!file_exists($filePath)) {
            return ['valid' => false, 'error' => '文件不存在'];
        }

        // 检查文件大小
        $fileSize = filesize($filePath);
        if ($fileSize > $this->maxFileSize) {
            return [
                'valid' => false,
                'error' => "文件大小超过限制: {$fileSize} > {$this->maxFileSize}"
            ];
        }

        // 检查MIME类型
        $mimeType = mime_content_type($filePath);
        if (!$mimeType || !isset($this->allowedTypes[$mimeType])) {
            return [
                'valid' => false,
                'error' => "不允许的文件类型: {$mimeType}"
            ];
        }

        // 检查文件扩展名
        $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
        $expectedExtension = $this->allowedTypes[$mimeType];

        if ($extension !== $expectedExtension) {
            return [
                'valid' => false,
                'error' => "文件扩展名不匹配: {$extension} != {$expectedExtension}"
            ];
        }

        // 安全检查:尝试读取文件前几个字节
        $handle = fopen($filePath, 'rb');
        $firstBytes = fread($handle, 256);
        fclose($handle);

        // 简单的恶意内容检查(实际应用中应该使用更严格的安全检查)
        if (preg_match('/<\?php|eval\(|base64_decode/i', $firstBytes)) {
            return ['valid' => false, 'error' => '文件可能包含恶意内容'];
        }

        return [
            'valid' => true,
            'size' => $fileSize,
            'mime_type' => $mimeType,
            'extension' => $extension,
            'filename' => basename($filePath)
        ];
    }

    public function uploadFile($filePath, $uploadUrl, $fieldName = 'file', $extraData = []) {
        // 验证文件
        $validation = $this->validateFile($filePath);
        if (!$validation['valid']) {
            return [
                'success' => false,
                'error' => $validation['error']
            ];
        }

        // 创建CURLFile
        $cfile = curl_file_create(
            $filePath,
            $validation['mime_type'],
            $validation['filename']
        );

        // 准备POST数据
        $postData = array_merge($extraData, [
            $fieldName => $cfile,
            'file_info' => json_encode([
                'original_name' => $validation['filename'],
                'size' => $validation['size'],
                'type' => $validation['mime_type']
            ])
        ]);

        // 初始化cURL
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $uploadUrl,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $postData,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 60,
            CURLOPT_HTTPHEADER => [
                'Content-Type: multipart/form-data',
                'X-File-Size: ' . $validation['size']
            ]
        ]);

        // 执行上传
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);

        curl_close($ch);

        if ($response === false) {
            return [
                'success' => false,
                'error' => "上传失败: {$error}",
                'http_code' => $httpCode
            ];
        }

        return [
            'success' => true,
            'response' => $response,
            'http_code' => $httpCode,
            'file_info' => $validation
        ];
    }

    public function setAllowedTypes($types) {
        $this->allowedTypes = $types;
    }

    public function setMaxFileSize($bytes) {
        $this->maxFileSize = $bytes;
    }
}

// 使用示例
$uploader = new SecureFileUploader();

// 设置允许的文件类型
$uploader->setAllowedTypes([
    'image/jpeg' => 'jpg',
    'image/png' => 'png',
    'application/pdf' => 'pdf'
]);

// 设置最大文件大小为5MB
$uploader->setMaxFileSize(5 * 1024 * 1024);

// 尝试上传文件
$result = $uploader->uploadFile(
    '/path/to/test.jpg',
    'https://httpbin.org/post',
    'user_file',
    ['user_id' => 1001, 'category' => 'profile']
);

if ($result['success']) {
    echo "文件上传成功!\n";
    echo "HTTP状态码: {$result['http_code']}\n";
    echo "文件信息:\n";
    print_r($result['file_info']);
} else {
    echo "文件上传失败: {$result['error']}\n";
}

示例 5:从内存数据上传文件

function uploadFromMemory($data, $filename, $mimeType = 'application/octet-stream') {
    // 创建临时文件
    $tempFile = tempnam(sys_get_temp_dir(), 'curl_upload_');

    // 将数据写入临时文件
    file_put_contents($tempFile, $data);

    // 创建CURLFile
    $cfile = curl_file_create($tempFile, $mimeType, $filename);

    // 准备POST数据
    $postData = [
        'file' => $cfile,
        'filename' => $filename,
        'size' => strlen($data)
    ];

    // 初始化cURL
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL => 'https://httpbin.org/post',
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $postData,
        CURLOPT_RETURNTRANSFER => true
    ]);

    // 执行请求
    $response = curl_exec($ch);
    $error = curl_error($ch);

    // 清理临时文件
    unlink($tempFile);

    if ($response === false) {
        throw new Exception("上传失败: {$error}");
    }

    curl_close($ch);
    return $response;
}

// 使用示例:上传生成的图像
$imageData = generateSampleImage();
$result = uploadFromMemory($imageData, 'sample.png', 'image/png');
echo "内存文件上传完成,响应长度: " . strlen($result) . " 字节\n";

// 辅助函数:生成示例图像
function generateSampleImage() {
    $image = imagecreatetruecolor(200, 100);
    $backgroundColor = imagecolorallocate($image, 70, 130, 180);
    $textColor = imagecolorallocate($image, 255, 255, 255);

    imagefill($image, 0, 0, $backgroundColor);
    imagestring($image, 5, 50, 40, 'Sample Image', $textColor);

    ob_start();
    imagepng($image);
    $imageData = ob_get_clean();
    imagedestroy($image);

    return $imageData;
}

CURLFile对象方法

CURLFile 对象有以下可用方法:

方法 描述
getFilename() 获取文件路径
getMimeType() 获取MIME类型
getPostFilename() 获取POST字段中的文件名
setMimeType($mime) 设置MIME类型
setPostFilename($name) 设置POST字段中的文件名

使用示例:

// 创建CURLFile对象
$cfile = curl_file_create('/path/to/file.jpg', 'image/jpeg', 'upload.jpg');

// 使用方法获取信息
echo "文件路径: " . $cfile->getFilename() . "\n";
echo "MIME类型: " . $cfile->getMimeType() . "\n";
echo "POST文件名: " . $cfile->getPostFilename() . "\n";

// 修改属性
$cfile->setMimeType('image/png');
$cfile->setPostFilename('new_name.png');

echo "修改后MIME类型: " . $cfile->getMimeType() . "\n";
echo "修改后POST文件名: " . $cfile->getPostFilename() . "\n";

新旧语法对比

方式 PHP版本 示例 说明
旧语法 (@前缀) PHP 5.5 之前 CURLOPT_POSTFIELDS => ['file' => '@file.jpg'] 已废弃,不推荐使用
新语法 (CURLFile) PHP 5.5+ CURLOPT_POSTFIELDS => ['file' => curl_file_create('file.jpg')] 推荐使用

注意事项

版本要求:curl_file_create() 函数需要PHP 5.5.0或更高版本,并且需要cURL支持。
安全警告:
  • 始终验证上传文件的类型、大小和内容
  • 不要信任客户端提供的MIME类型
  • 将上传的文件存储在Web根目录之外
  • 为上传的文件生成唯一的文件名
  • 考虑使用病毒扫描程序检查上传的文件
最佳实践:
  • 使用 CURLFile 而不是旧的 @ 语法
  • 明确指定MIME类型以确保正确传输
  • 为上传的文件设置适当的POST字段名
  • 实现进度回调以监控大文件上传
  • 在生产环境中实施适当的错误处理和日志记录

常见问题

Q: 为什么使用CURLFile而不是@语法?

A: CURLFile 是PHP 5.5+中推荐的文件上传方式,它提供了更好的类型安全性和可维护性。旧的 @ 语法已废弃,可能在未来的PHP版本中被移除。

Q: 如何上传大文件?

A: 对于大文件上传: 1. 增加PHP的 memory_limitupload_max_filesize 设置 2. 使用 CURLOPT_TIMEOUT 设置适当的超时时间 3. 实现进度回调函数以监控上传进度 4. 考虑使用分块上传或断点续传

Q: 如何获取上传进度?

A: 使用 CURLOPT_PROGRESSFUNCTION 选项设置进度回调函数。回调函数会接收上传的字节数和总字节数,从而计算上传进度。

相关函数