Как загружать файлы с помощью cURL в PHP: от новичка до продвинутых техник

Раздел: Разработка на 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);
?>
  

Работа с cURL в PHP (загрузка файлов) - comments

En
Php curl файл (php)