Расширение cURL в PHP — основной инструмент для отправки HTTP-запросов: забрать данные из API, отправить форму, скачать файл, загрузить картинку на сторонний сервер. В этой статье разберём все ключевые сценарии работы с cURL — от простого GET-запроса до мультизагрузки файлов и параллельных запросов.
Как работает cURL в PHP
PHP-расширение php-curl — обёртка над библиотекой libcurl. Оно поддерживает HTTP, HTTPS, FTP, SMTP и другие протоколы. Работа всегда строится по одной схеме: инициализация → настройка опций → выполнение → закрытие.

Минимальный пример — GET-запрос:
$ch = curl_init('https://api.example.com/data');
// Вернуть ответ как строку, а не выводить в браузер
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Ошибка cURL: ' . curl_error($ch);
} else {
echo $response;
}
curl_close($ch);
Ключевая опция — CURLOPT_RETURNTRANSFER. Без неё curl_exec() выведет ответ прямо в браузер и вернёт true. С ней — вернёт тело ответа как строку.
GET-запрос к REST API с заголовками
Большинство API требуют авторизацию через заголовки. Вот как отправить GET-запрос с Bearer-токеном и обработать JSON-ответ:
function apiGet(string $url, string $token): ?array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Accept: application/json',
],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
error_log('cURL error: ' . curl_error($ch));
curl_close($ch);
return null;
}
curl_close($ch);
if ($httpCode >= 200 && $httpCode < 300) {
return json_decode($response, true);
}
error_log("API вернул HTTP $httpCode: $response");
return null;
}
// Использование
$users = apiGet('https://api.example.com/users', 'your-token-here');
if ($users) {
foreach ($users as $user) {
echo $user['name'] . PHP_EOL;
}
}
Обратите внимание на curl_setopt_array() — она удобнее, чем множество вызовов curl_setopt(). Также мы проверяем HTTP-код ответа через curl_getinfo() — это обязательная практика, потому что curl_exec() возвращает тело даже при 4xx/5xx ошибках.
POST-запрос: отправка JSON
Для отправки данных на сервер используется POST. Самый частый формат тела запроса — JSON:
function apiPost(string $url, array $data, string $token): ?array
{
$ch = curl_init($url);
$json = json_encode($data, JSON_UNESCAPED_UNICODE);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $json,
CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'Content-Length: ' . strlen($json),
],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 201 || $httpCode === 200) {
return json_decode($response, true);
}
error_log("POST $url — HTTP $httpCode: $response");
return null;
}
// Создаём нового пользователя через API
$result = apiPost(
'https://api.example.com/users',
['name' => 'Иван', 'email' => 'ivan@example.com'],
'your-token'
);
if ($result) {
echo 'Создан пользователь ID: ' . $result['id'];
}
Важные моменты:
CURLOPT_POST => true— переключает метод на POSTCURLOPT_POSTFIELDS— тело запроса. Если передать строку, она отправляется как есть. Если массив — cURL автоматически закодирует какmultipart/form-data- Заголовок
Content-Type: application/jsonобязателен, иначе сервер может не распознать формат
PUT и DELETE запросы
REST API использует и другие HTTP-методы. Для PUT и DELETE метод задаётся через CURLOPT_CUSTOMREQUEST:
// PUT — обновление ресурса
function apiPut(string $url, array $data, string $token): ?array
{
$ch = curl_init($url);
$json = json_encode($data, JSON_UNESCAPED_UNICODE);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_POSTFIELDS => $json,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
],
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// DELETE — удаление ресурса
function apiDelete(string $url, string $token): bool
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
],
]);
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode === 200 || $httpCode === 204;
}
// Обновляем email пользователя
apiPut('https://api.example.com/users/42', ['email' => 'new@example.com'], $token);
// Удаляем пользователя
if (apiDelete('https://api.example.com/users/42', $token)) {
echo 'Пользователь удалён';
}
Загрузка файлов на сервер (multipart/form-data)
Для загрузки файлов через cURL используется класс CURLFile. Не передавайте путь к файлу через устаревший синтаксис @/path/to/file — он отключён начиная с PHP 5.6:
function uploadFile(string $url, string $filePath, string $token): ?array
{
if (!file_exists($filePath)) {
throw new RuntimeException("Файл не найден: $filePath");
}
$ch = curl_init($url);
// CURLFile — безопасный способ загрузки файлов
$file = new CURLFile(
$filePath,
mime_content_type($filePath), // автоопределение MIME
basename($filePath) // имя файла на сервере
);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'file' => $file,
'description' => 'Загружено через PHP cURL',
],
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200 || $httpCode === 201) {
return json_decode($response, true);
}
return null;
}
// Загружаем аватарку
$result = uploadFile(
'https://api.example.com/upload',
'/tmp/avatar.jpg',
'your-token'
);
echo 'Файл загружен: ' . $result['url'];
Когда CURLOPT_POSTFIELDS получает массив, cURL автоматически выставляет заголовок Content-Type: multipart/form-data. Не указывайте его вручную — cURL сам добавит правильный boundary.
Скачивание файлов
Скачать файл в PHP через cURL можно двумя способами: в строку (для небольших файлов) или напрямую в файл (для больших):
// Способ 1: скачать маленький файл в строку
$ch = curl_init('https://example.com/report.csv');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$content = curl_exec($ch);
curl_close($ch);
file_put_contents('/tmp/report.csv', $content);
// Способ 2: потоковое скачивание (для больших файлов)
function downloadFile(string $url, string $savePath): bool
{
$fp = fopen($savePath, 'wb');
if (!$fp) {
return false;
}
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_FILE => $fp, // писать в файл напрямую
CURLOPT_FOLLOWLOCATION => true, // следовать редиректам
CURLOPT_TIMEOUT => 300, // 5 минут на скачивание
CURLOPT_FAILONERROR => true, // ошибка при HTTP 4xx/5xx
]);
$success = curl_exec($ch);
if (!$success) {
error_log('Ошибка скачивания: ' . curl_error($ch));
}
curl_close($ch);
fclose($fp);
// Удалить пустой файл при ошибке
if (!$success && file_exists($savePath)) {
unlink($savePath);
}
return (bool)$success;
}
// Скачиваем бэкап базы
if (downloadFile('https://example.com/backup.sql.gz', '/tmp/backup.sql.gz')) {
echo 'Файл скачан, размер: ' . filesize('/tmp/backup.sql.gz') . ' байт';
}
Второй способ не загружает файл целиком в оперативную память — CURLOPT_FILE пишет данные в файловый дескриптор по мере получения. Это критически важно для файлов размером в сотни мегабайт.
Обработка ошибок и отладка
cURL различает два типа ошибок: сетевые (таймаут, DNS, SSL) и HTTP (4xx/5xx). Функция curl_errno() ловит только сетевые ошибки. HTTP-коды нужно проверять отдельно:
$ch = curl_init('https://api.example.com/data');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10, // таймаут запроса (сек)
CURLOPT_CONNECTTIMEOUT => 5, // таймаут подключения (сек)
CURLOPT_SSL_VERIFYPEER => true, // проверять SSL (не отключайте!)
CURLOPT_FOLLOWLOCATION => true, // следовать 301/302
CURLOPT_MAXREDIRS => 3, // макс. редиректов
]);
$response = curl_exec($ch);
// 1. Сетевая ошибка
if (curl_errno($ch)) {
$errorCode = curl_errno($ch);
$errorMsg = curl_error($ch);
// Типичные коды ошибок
match ($errorCode) {
CURLE_OPERATION_TIMEOUTED => error_log("Таймаут: $errorMsg"),
CURLE_COULDNT_RESOLVE_HOST => error_log("DNS ошибка: $errorMsg"),
CURLE_SSL_CERTPROBLEM => error_log("SSL ошибка: $errorMsg"),
default => error_log("cURL [$errorCode]: $errorMsg"),
};
curl_close($ch);
return;
}
// 2. HTTP-ошибка
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$totalTime = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
if ($httpCode >= 400) {
error_log("HTTP $httpCode за {$totalTime}с: $response");
}
// 3. Подробная отладка — включить verbose-режим
// curl_setopt($ch, CURLOPT_VERBOSE, true);
// curl_setopt($ch, CURLOPT_STDERR, fopen('/tmp/curl-debug.log', 'w'));
curl_close($ch);
Никогда не отключайте CURLOPT_SSL_VERIFYPEER в продакшне. Если есть проблемы с SSL — укажите путь к CA-бандлу через CURLOPT_CAINFO.
Универсальная функция для HTTP-запросов
Чтобы не дублировать код, оберните всю логику cURL в одну функцию:
function httpRequest(
string $method,
string $url,
array $headers = [],
mixed $body = null,
int $timeout = 10
): array {
$ch = curl_init($url);
$options = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTPHEADER => $headers,
];
// Настройка метода и тела
if ($method === 'POST') {
$options[CURLOPT_POST] = true;
$options[CURLOPT_POSTFIELDS] = $body;
} elseif ($method !== 'GET') {
$options[CURLOPT_CUSTOMREQUEST] = $method;
if ($body !== null) {
$options[CURLOPT_POSTFIELDS] = $body;
}
}
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
$result = [
'status' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
'body' => $response,
'time' => curl_getinfo($ch, CURLINFO_TOTAL_TIME),
'error' => curl_errno($ch) ? curl_error($ch) : null,
];
curl_close($ch);
return $result;
}
// GET-запрос
$res = httpRequest('GET', 'https://api.example.com/users', [
'Authorization: Bearer token123',
]);
// POST с JSON
$res = httpRequest('POST', 'https://api.example.com/users', [
'Content-Type: application/json',
], json_encode(['name' => 'Иван']));
// DELETE
$res = httpRequest('DELETE', 'https://api.example.com/users/42', [
'Authorization: Bearer token123',
]);
// Проверка результата
if ($res['error']) {
echo "Ошибка: {$res['error']}";
} elseif ($res['status'] >= 400) {
echo "HTTP {$res['status']}: {$res['body']}";
} else {
$data = json_decode($res['body'], true);
print_r($data);
}
Полезные опции cURL
Справочник часто используемых опций, которые пригодятся в реальных проектах:
CURLOPT_USERAGENT— строка User-Agent. Некоторые API отдают 403 без негоCURLOPT_COOKIE— отправить cookies в формате'name=value; name2=value2'CURLOPT_COOKIEJAR/CURLOPT_COOKIEFILE— сохранять и загружать cookies из файлаCURLOPT_PROXY— HTTP/SOCKS прокси:'socks5://127.0.0.1:1080'CURLOPT_USERPWD— HTTP Basic Auth:'login:password'CURLOPT_ENCODING— принять сжатый ответ (gzip, deflate). Передайте пустую строку''для автовыбораCURLOPT_HEADER— включить заголовки ответа в выводCURLOPT_NOBODY— отправить HEAD-запрос (только заголовки, без тела)
Пример — проверить доступность URL без скачивания содержимого:
function isUrlAccessible(string $url): bool
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true, // HEAD-запрос
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_USERAGENT => 'PHP Link Checker/1.0',
]);
curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $code >= 200 && $code < 400;
}
// Проверяем массив ссылок
$urls = ['https://example.com', 'https://broken-link.test'];
foreach ($urls as $url) {
$status = isUrlAccessible($url) ? 'OK' : 'FAIL';
echo "$url — $status\n";
}
Итог
Чеклист для работы с cURL в PHP:
- Всегда включайте
CURLOPT_RETURNTRANSFER - Устанавливайте
CURLOPT_TIMEOUTиCURLOPT_CONNECTTIMEOUT— без них запрос может висеть бесконечно - Проверяйте и сетевые ошибки (
curl_errno), и HTTP-коды (curl_getinfo) - Для загрузки файлов используйте
CURLFile, а не устаревший@path - Для скачивания больших файлов используйте
CURLOPT_FILE - Не отключайте
CURLOPT_SSL_VERIFYPEERв продакшне - Оборачивайте повторяющуюся логику в универсальную функцию
- Для сложных сценариев с cookies, прокси и аутентификацией — смотрите в сторону Guzzle HTTP
