Как загружать файлы с помощью cURL в PHP: от новичка до продвинутых техник
Основы загрузки файлов через cURL
Наиболее эффективный и рекомендуемый способ загрузки файлов в PHP с помощью cURL - использование класса CURLFile. Этот подход появился в PHP 5.5 и полностью заменяет устаревший синтаксис с символом @. Он гарантирует корректную передачу multipart/form-data и работает с любыми типами файлов.
Базовый пример с CURLFile
<?php
$url = 'https://example.com/upload.php';
$filePath = '/tmp/photo.jpg';
$curl = curl_init();
$cfile = new CURLFile($filePath, 'image/jpeg', 'photo.jpg');
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => ['file' => $cfile],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_VERBOSE => true
]);
$response = curl_exec($curl);
if (curl_errno($curl)) {
$error = curl_error($curl);
// обработка ошибки
}
curl_close($curl);
echo $response;
?>
Конструктор CURLFile принимает три параметра: путь к файлу, MIME-тип (опционально) и имя файла (опционально). Если MIME не указан, cURL попытается определить его автоматически по расширению. Массив CURLOPT_POSTFIELDS может содержать как обычные поля, так и объекты CURLFile - cURL сам сформирует корректный multipart-запрос.
Типичные ошибки и их решение
- Ошибка 26 (CURLE_READ_ERROR) - cURL не может прочитать файл. Причина: неверный путь, отсутствие прав или файл удалён во время передачи. Проверьте is_readable() и абсолютный путь.
- Ошибка 23 (CURLE_WRITE_ERROR) - проблема записи в локальный файл при скачивании; для загрузки редко.
- Пустой ответ или HTTP 500 - сервер не принял файл. Убедитесь, что на принимающей стороне настроена обработка multipart-форм и лимиты post_max_size/upload_max_filesize в PHP.
Вариант 1: Использование устаревшего синтаксиса с @ (PHP < 5.5)
Как загрузить файл, если версия PHP не поддерживает CURLFile?
До PHP 5.5 файл передавался через префикс @ в значении поля. Этот способ считается устаревшим и отключён в PHP 5.6+ при настройке CURLOPT_SAFE_UPLOAD = true (по умолчанию). Однако для совместимости со старым кодом можно включить обратно через curl_setopt($ch, CURLOPT_SAFE_UPLOAD, false).
<?php
$curl = curl_init('https://example.com/upload.php');
$filePath = '/tmp/photo.jpg';
curl_setopt($curl, CURLOPT_SAFE_UPLOAD, false); // Включаем @ синтаксис
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, ['file' => '@' . $filePath]);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
curl_close($curl);
?>
Проблемы @ синтаксиса:
- Небезопасен - можно передать любой файл из системы, если пользователь контролирует путь.
- Не поддерживает указание MIME-типа
- Не работает с потоками (например, php://input).
- Будет удалён в будущих версиях PHP.
Вариант 2: Использование curl_file_create() (псевдоним CURLFile)
Как загрузить файл с минимальным кодом, используя функцию-псевдоним?
Функция curl_file_create() - это просто обёртка над new CURLFile. Принимает те же параметры и возвращает объект CURLFile. Удобно для написания в стиле функционального программирования.
<?php
$curl = curl_init('https://example.com/upload.php');
$file = curl_file_create('/tmp/doc.pdf', 'application/pdf', 'document.pdf');
curl_setopt($curl, CURLOPT_POSTFIELDS, ['file' => $file]);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
curl_close($curl);
?>
Вариант 3: Ручное формирование multipart/form-data
Как отправить файл, если нужно полный контроль над телом запроса?
Иногда требуется кастомный multipart-запрос, например, с дополнительными заголовками частей или нестандартной границей. Можно вручную сформировать тело и передать его через CURLOPT_POSTFIELDS со строкой, а также указать CURLOPT_HTTPHEADER с Content-Type.
<?php
$url = 'https://example.com/upload.php';
$filePath = '/tmp/photo.jpg';
$boundary = 'boundary12345';
$fileContents = file_get_contents($filePath);
$filename = basename($filePath);
$body = '';
$body .= '--' . $boundary . "\r\n";
$body .= 'Content-Disposition: form-data; name="file"; filename="' . $filename . '"' . "\r\n";
$body .= 'Content-Type: image/jpeg' . "\r\n\r\n";
$body .= $fileContents . "\r\n";
$body .= '--' . $boundary . '--' . "\r\n";
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: multipart/form-data; boundary=' . $boundary,
'Content-Length: ' . strlen($body)
],
CURLOPT_RETURNTRANSFER => true
]);
$response = curl_exec($curl);
curl_close($curl);
?>
Этот подход требует ручного вычисления Content-Length и осторожности с границами, особенно если файлы большие. Лучше использовать CURLFile для стандартных случаев.
Вариант 4: Загрузка нескольких файлов
Как отправить несколько файлов в одном запросе?
Массив CURLOPT_POSTFIELDS может содержать несколько элементов CURLFile с разными ключами или индексами. cURL сформирует multipart-запрос с несколькими полями file.
<?php
$curl = curl_init('https://example.com/upload.php');
$files = [
'images' => [
new CURLFile('/tmp/photo1.jpg', 'image/jpeg', 'photo1.jpg'),
new CURLFile('/tmp/photo2.png', 'image/png', 'photo2.png')
],
'document' => new CURLFile('/tmp/report.pdf', 'application/pdf', 'report.pdf')
];
curl_setopt($curl, CURLOPT_POSTFIELDS, $files);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
curl_close($curl);
?>
Обратите внимание: при использовании индексированного массива (например, ['file1', 'file2']) ключи будут числовыми, что может не соответствовать ожиданиям сервера. Лучше задавать имена полей явно.
Потенциальные проблемы:
- Если массив вложенный, как в примере выше, cURL обработает его корректно, но сервер должен ожидать массив файлов (например, с квадратными скобками).
- Превышение лимита max_file_uploads на стороне сервера может привести к отбрасыванию лишних файлов.
Вариант 5: Загрузка файла из строки (без временного файла)
Как загрузить данные напрямую из памяти, не записывая их на диск?
Класс CURLFile работает только с файлами на диске. Для отправки данных из переменной (например, сгенерированное изображение) можно использовать обёртку через php://temp или записать во временный файл. Более продвинутый способ - создать поток и использовать CURLOPT_INFILE и CURLOPT_INFILESIZE для отправки произвольных данных как тела запроса (не multipart). Однако для multipart потребуется ручное формирование, как в варианте 3, или использование класса CURLStringFile (появился в PHP 8.1).
<?php
// PHP 8.1+
$curl = curl_init('https://example.com/upload.php');
$data = '... binary data ...'; // например, содержимое файла
$stringFile = new CURLStringFile($data, 'image.png', 'image/png');
curl_setopt($curl, CURLOPT_POSTFIELDS, ['file' => $stringFile]);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
curl_close($curl);
?>
CURLStringFile - новый класс, позволяющий передать данные напрямую без записи в файл. Доступен с PHP 8.1 и работает только с cURL 7.56+.
Дополнительные примеры и продвинутые техники
Загрузка файла с указанием пользовательского MIME-типа
Как явно задать MIME-тип, если автоматическое определение неверно?
Передайте второй параметр в конструктор CURLFile или в curl_file_create. MIME-тип будет отправлен в заголовке Content-Type соответствующей части multipart.
<?php
$cfile = new CURLFile('/tmp/archive.bin', 'application/octet-stream', 'archive.bin');
?>
Загрузка файла через PUT-запрос
Как отправить файл методом PUT, а не POST?
Используйте CURLOPT_PUT или установите CURLOPT_CUSTOMREQUEST = 'PUT' вместе с опциями для чтения из файла.
<?php
$url = 'https://example.com/upload/archive.zip';
$filePath = '/tmp/archive.zip';
$fp = fopen($filePath, 'rb');
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_PUT => true,
CURLOPT_INFILE => $fp,
CURLOPT_INFILESIZE => filesize($filePath),
CURLOPT_RETURNTRANSFER => true
]);
$response = curl_exec($curl);
fclose($fp);
curl_close($curl);
echo $response;
?>
Этот метод передаёт файл в теле запроса без multipart-обрамления. Удобно, когда сервер ожидает прямой поток байтов.
Возможные ошибки:
- Не указан размер файла через CURLOPT_INFILESIZE - cURL может не отправить файл.
- Если файл большой, может не хватить памяти для буферизации при использовании CURLOPT_INFILE, но cURL сам читает кусками.
Потоковая загрузка большого файла с использованием CURLOPT_READFUNCTION
Как загрузить очень большой файл без загрузки его целиком в память?
Установите пользовательскую функцию чтения через CURLOPT_READFUNCTION. Она будет вызываться cURL для получения данных кусками. Это эффективно для файлов, превышающих лимит памяти.
<?php
$filePath = '/tmp/huge_file.iso';
$fp = fopen($filePath, 'rb');
$curl = curl_init('https://example.com/upload/huge_file.iso');
curl_setopt_array($curl, [
CURLOPT_PUT => true,
CURLOPT_INFILESIZE => filesize($filePath),
CURLOPT_READFUNCTION => function ($ch, $fd, $length) use ($fp) {
return fread($fp, $length);
},
CURLOPT_RETURNTRANSFER => true
]);
$response = curl_exec($curl);
fclose($fp);
curl_close($curl);
?>
Важно: не забудьте закрыть файловый дескриптор после завершения.
Параллельная загрузка нескольких файлов через curl_multi
Как ускорить отправку многих файлов, используя параллельные запросы?
Расширение cURL поддерживает мульти-обработчик, позволяющий выполнять запросы асинхронно. Это критически важно при загрузке множества файлов на один сервер.
<?php
$url = 'https://example.com/upload.php';
$files = ['photo1.jpg', 'photo2.jpg', 'photo3.jpg']; // пути к файлам
$multiHandle = curl_multi_init();
$curlHandles = [];
foreach ($files as $index => $filePath) {
$ch = curl_init($url);
$cfile = new CURLFile($filePath);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => ['file_' . $index => $cfile],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30
]);
curl_multi_add_handle($multiHandle, $ch);
$curlHandles[] = $ch;
}
$running = null;
do {
$status = curl_multi_exec($multiHandle, $running);
curl_multi_select($multiHandle); // ожидание активности
} while ($running > 0);
foreach ($curlHandles as $ch) {
$response = curl_multi_getcontent($ch);
$error = curl_error($ch);
// обработка результата
curl_multi_remove_handle($multiHandle, $ch);
curl_close($ch);
}
curl_multi_close($multiHandle);
?>
Загрузка файла с авторизацией через заголовки
Как отправить файл на защищённый эндпоинт, требующий Bearer токен или Basic Auth?
Добавьте заголовки через CURLOPT_HTTPHEADER. Для Bearer токена:
<?php
$curl = curl_init('https://api.example.com/upload');
$cfile = new CURLFile('/tmp/report.pdf', 'application/pdf', 'report.pdf');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => ['file' => $cfile],
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_ACCESS_TOKEN',
'X-Requested-With: XMLHttpRequest'
],
CURLOPT_RETURNTRANSFER => true
]);
$response = curl_exec($curl);
curl_close($curl);
?>
Для Basic Auth используйте CURLOPT_USERPWD:
curl_setopt($curl, CURLOPT_USERPWD, 'username:password');
Обработка ответа сервера и чтение заголовков
Как получить не только тело ответа, но и заголовки?
Включите опцию CURLOPT_HEADER = true и разделите полученный ответ по границе "\r\n\r\n". Альтернативно используйте CURLOPT_HEADERFUNCTION для построчной обработки.
<?php
$curl = curl_init('https://example.com/upload.php');
$cfile = new CURLFile('/tmp/test.txt');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => ['file' => $cfile],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true
]);
$response = curl_exec($curl);
$headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
echo 'Header: ' . $header;
echo 'Body: ' . $body;
?>
Загрузка файла с использованием прокси
Как отправить файл через HTTP/HTTPS прокси?
Укажите опции CURLOPT_PROXY, CURLOPT_PROXYPORT и при необходимости CURLOPT_PROXYUSERPWD.
<?php
$curl = curl_init('https://example.com/upload.php');
$cfile = new CURLFile('/tmp/file.zip');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => ['file' => $cfile],
CURLOPT_PROXY => '192.168.1.100',
CURLOPT_PROXYPORT => 3128,
CURLOPT_PROXYUSERPWD => 'user:pass',
CURLOPT_RETURNTRANSFER => true
]);
$response = curl_exec($curl);
curl_close($curl);
?>