cURL (Client URL) -- самый мощный и гибкий инструмент для HTTP-запросов в PHP. Расширение предоставляет доступ к библиотеке libcurl, поддерживающей десятки протоколов: HTTP, HTTPS, FTP, SMTP и другие.
Основы: curl_init, curl_setopt, curl_exec, curl_close
<?php
declare(strict_types=1);
// Basic GET request
$ch = curl_init(); // Initialize cURL session
curl_setopt($ch, CURLOPT_URL, 'https://api.example.com/users');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return response as string
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // Timeout in seconds
$response = curl_exec($ch);
if ($response === false) {
echo 'cURL Error: ' . curl_error($ch);
}
curl_close($ch); // Close session (free resources)
// Shorthand: pass URL to curl_init()
$ch = curl_init('https://api.example.com/users');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
// Set multiple options at once
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/users',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_USERAGENT => 'MyApp/1.0',
]);
$response = curl_exec($ch);
curl_close($ch);
Важно: Без
CURLOPT_RETURNTRANSFER = trueфункцияcurl_exec()выведет ответ напрямую в stdout и вернетtrue/false. Всегда устанавливайте эту опцию для программной обработки ответа.
GET запросы с параметрами
<?php
declare(strict_types=1);
// Query parameters via URL
$params = [
'page' => 1,
'per_page' => 25,
'sort' => 'created_at',
'order' => 'desc',
'search' => 'John Doe',
];
$url = 'https://api.example.com/users?' . http_build_query($params);
// Result: https://api.example.com/users?page=1&per_page=25&sort=created_at&order=desc&search=John+Doe
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPGET => true, // Explicitly set GET (default)
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true, flags: JSON_THROW_ON_ERROR);
POST запросы
<?php
declare(strict_types=1);
// === POST with form data (application/x-www-form-urlencoded) ===
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/users',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'name' => 'John Doe',
'email' => '[email protected]',
'role' => 'admin',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
// === POST with JSON body ===
$payload = json_encode([
'name' => 'John Doe',
'email' => '[email protected]',
'roles' => ['admin', 'editor'],
], JSON_THROW_ON_ERROR);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/users',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
'Content-Length: ' . strlen($payload),
],
]);
$response = curl_exec($ch);
curl_close($ch);
// === POST with raw body ===
$xmlBody = '<?xml version="1.0"?><user><name>John</name></user>';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/users',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $xmlBody,
CURLOPT_HTTPHEADER => [
'Content-Type: application/xml',
],
]);
$response = curl_exec($ch);
curl_close($ch);
PUT, PATCH, DELETE запросы
<?php
declare(strict_types=1);
// === PUT — full resource update ===
$data = json_encode(['name' => 'Jane Doe', 'email' => '[email protected]']);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/users/42',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_POSTFIELDS => $data,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
],
]);
$response = curl_exec($ch);
curl_close($ch);
// === PATCH — partial update ===
$patch = json_encode(['status' => 'active']);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/users/42',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_POSTFIELDS => $patch,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
]);
$response = curl_exec($ch);
curl_close($ch);
// === DELETE ===
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/users/42',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 204 No Content
curl_close($ch);
Заголовки и аутентификация
<?php
declare(strict_types=1);
// === Custom headers ===
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/data',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer eyJhbGciOiJIUzI1NiIs...',
'Accept: application/json',
'X-Request-ID: ' . bin2hex(random_bytes(16)),
'X-Custom-Header: my-value',
],
]);
$response = curl_exec($ch);
curl_close($ch);
// === Basic authentication ===
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/protected',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERPWD => 'username:password', // Basic auth
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
]);
$response = curl_exec($ch);
curl_close($ch);
// === Bearer token authentication ===
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/resource',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $accessToken,
],
]);
$response = curl_exec($ch);
curl_close($ch);
// === Read response headers ===
$responseHeaders = [];
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/data',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADERFUNCTION => function ($ch, string $header) use (&$responseHeaders): int {
$len = strlen($header);
$parts = explode(':', $header, 2);
if (count($parts) === 2) {
$name = strtolower(trim($parts[0]));
$responseHeaders[$name] = trim($parts[1]);
}
return $len; // Must return number of bytes processed
},
]);
$body = curl_exec($ch);
curl_close($ch);
echo $responseHeaders['content-type'] ?? 'unknown';
Основные CURLOPT опции
<?php
declare(strict_types=1);
$ch = curl_init();
curl_setopt_array($ch, [
// URL and method
CURLOPT_URL => 'https://api.example.com/data',
CURLOPT_CUSTOMREQUEST => 'POST',
// Response handling
CURLOPT_RETURNTRANSFER => true, // Return string instead of outputting
CURLOPT_HEADER => false, // Don't include headers in output
CURLOPT_NOBODY => false, // Set true for HEAD request
// Timeouts
CURLOPT_TIMEOUT => 30, // Total timeout in seconds
CURLOPT_CONNECTTIMEOUT => 10, // Connection timeout in seconds
CURLOPT_TIMEOUT_MS => 30000, // Total timeout in milliseconds
CURLOPT_CONNECTTIMEOUT_MS => 5000, // Connection timeout in ms
// SSL/TLS
CURLOPT_SSL_VERIFYPEER => true, // Verify SSL certificate (NEVER set to false in production!)
CURLOPT_SSL_VERIFYHOST => 2, // Check CN matches hostname
CURLOPT_CAINFO => '/etc/ssl/certs/ca-certificates.crt',
// Redirects
CURLOPT_FOLLOWLOCATION => true, // Follow HTTP redirects
CURLOPT_MAXREDIRS => 5, // Max number of redirects
CURLOPT_AUTOREFERER => true, // Set Referer on redirect
// User agent and cookies
CURLOPT_USERAGENT => 'MyApp/2.0 (PHP/' . PHP_VERSION . ')',
CURLOPT_COOKIE => 'session_id=abc123; theme=dark',
CURLOPT_COOKIEFILE => '/tmp/cookies.txt', // Read cookies from file
CURLOPT_COOKIEJAR => '/tmp/cookies.txt', // Save cookies to file
// Encoding
CURLOPT_ENCODING => '', // Accept all encodings (gzip, deflate, br)
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0, // HTTP/2
// Proxy
CURLOPT_PROXY => 'http://proxy.example.com:8080',
CURLOPT_PROXYUSERPWD => 'user:pass',
]);
$response = curl_exec($ch);
curl_close($ch);
curl_getinfo() -- информация об ответе
<?php
declare(strict_types=1);
$ch = curl_init('https://api.example.com/users');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
// Get specific info
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 200
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); // 'application/json'
$totalTime = curl_getinfo($ch, CURLINFO_TOTAL_TIME); // 0.234 seconds
$dnsTime = curl_getinfo($ch, CURLINFO_NAMELOOKUP_TIME); // 0.012 seconds
$connectTime = curl_getinfo($ch, CURLINFO_CONNECT_TIME); // 0.045 seconds
$sslTime = curl_getinfo($ch, CURLINFO_APPCONNECT_TIME); // 0.089 seconds (TLS handshake)
$downloadSize = curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD); // bytes downloaded
$speed = curl_getinfo($ch, CURLINFO_SPEED_DOWNLOAD); // bytes per second
$effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); // Final URL after redirects
$redirectCount = curl_getinfo($ch, CURLINFO_REDIRECT_COUNT); // Number of redirects
// Get all info at once
$info = curl_getinfo($ch);
print_r($info);
curl_close($ch);
// Practical: logging request performance
function logRequestPerformance(CurlHandle $ch, string $url): void
{
$info = curl_getinfo($ch);
$log = sprintf(
"[%s] %s -> %d | DNS: %.3fs, Connect: %.3fs, TLS: %.3fs, Total: %.3fs | %d bytes",
date('Y-m-d H:i:s'),
$url,
$info['http_code'],
$info['namelookup_time'],
$info['connect_time'],
$info['appconnect_time'],
$info['total_time'],
$info['size_download'],
);
error_log($log);
}
Обработка ошибок
<?php
declare(strict_types=1);
$ch = curl_init('https://api.example.com/data');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_FAILONERROR => false, // Don't fail on HTTP errors (4xx, 5xx)
]);
$response = curl_exec($ch);
// Check for cURL errors (network, DNS, timeout, SSL)
if ($response === false) {
$errorCode = curl_errno($ch);
$errorMessage = curl_error($ch);
match ($errorCode) {
CURLE_OPERATION_TIMEDOUT => throw new RuntimeException("Request timed out: $errorMessage"),
CURLE_COULDNT_RESOLVE_HOST => throw new RuntimeException("DNS resolution failed: $errorMessage"),
CURLE_COULDNT_CONNECT => throw new RuntimeException("Connection failed: $errorMessage"),
CURLE_SSL_CONNECT_ERROR => throw new RuntimeException("SSL error: $errorMessage"),
default => throw new RuntimeException("cURL error [$errorCode]: $errorMessage"),
};
}
// Check HTTP status code
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400) {
$body = json_decode($response, true);
$message = $body['error']['message'] ?? 'Unknown error';
throw match (true) {
$httpCode === 401 => new RuntimeException("Unauthorized: $message"),
$httpCode === 403 => new RuntimeException("Forbidden: $message"),
$httpCode === 404 => new RuntimeException("Not found: $message"),
$httpCode === 429 => new RuntimeException("Rate limited: $message"),
$httpCode >= 500 => new RuntimeException("Server error [$httpCode]: $message"),
default => new RuntimeException("HTTP error [$httpCode]: $message"),
};
}
curl_multi -- параллельные запросы
<?php
declare(strict_types=1);
// Execute multiple HTTP requests in parallel
$urls = [
'users' => 'https://api.example.com/users',
'products' => 'https://api.example.com/products',
'orders' => 'https://api.example.com/orders',
'stats' => 'https://api.example.com/stats',
];
// Initialize multi handle
$mh = curl_multi_init();
$handles = [];
// Create individual handles and add to multi
foreach ($urls as $name => $url) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
curl_multi_add_handle($mh, $ch);
$handles[$name] = $ch;
}
// Execute all requests in parallel
$running = null;
do {
$status = curl_multi_exec($mh, $running);
if ($status > CURLM_OK) {
throw new RuntimeException('cURL multi error: ' . curl_multi_strerror($status));
}
// Wait for activity (avoids busy loop)
if ($running > 0) {
curl_multi_select($mh, 1.0); // Block up to 1 second
}
} while ($running > 0);
// Collect results
$results = [];
foreach ($handles as $name => $ch) {
$error = curl_error($ch);
if ($error !== '') {
$results[$name] = ['error' => $error];
} else {
$results[$name] = [
'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
'body' => json_decode(curl_multi_getcontent($ch), true),
'time' => curl_getinfo($ch, CURLINFO_TOTAL_TIME),
];
}
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
// All 4 requests completed in ~time of slowest one (not sum!)
foreach ($results as $name => $result) {
echo sprintf("%s: %d (%.3fs)\n", $name, $result['code'] ?? 0, $result['time'] ?? 0);
}
Загрузка файлов
<?php
declare(strict_types=1);
// === Upload file with CURLFile ===
$file = new CURLFile(
filename: '/path/to/document.pdf',
mime_type: 'application/pdf',
posted_filename: 'my-document.pdf', // Name seen by server
);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/upload',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'file' => $file,
'description' => 'Important document',
],
// Content-Type: multipart/form-data is set automatically with array POSTFIELDS
]);
$response = curl_exec($ch);
curl_close($ch);
// === Upload from string (without temp file) ===
$csvData = "name,email\nJohn,[email protected]\nJane,[email protected]";
$tempFile = tmpfile();
fwrite($tempFile, $csvData);
$tempPath = stream_get_meta_data($tempFile)['uri'];
$file = new CURLFile($tempPath, 'text/csv', 'users.csv');
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/import',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => ['file' => $file],
]);
$response = curl_exec($ch);
curl_close($ch);
fclose($tempFile); // Cleanup temp file
// === Download file ===
$ch = curl_init();
$fp = fopen('/tmp/downloaded-file.zip', 'wb');
curl_setopt_array($ch, [
CURLOPT_URL => 'https://example.com/large-file.zip',
CURLOPT_FILE => $fp, // Write directly to file (memory efficient)
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 300,
]);
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
fclose($fp);
if ($httpCode !== 200) {
unlink('/tmp/downloaded-file.zip');
throw new RuntimeException("Download failed with HTTP $httpCode");
}
// === Download with progress callback ===
$ch = curl_init('https://example.com/large-file.zip');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_NOPROGRESS => false,
CURLOPT_PROGRESSFUNCTION => function (
CurlHandle $ch,
int $downloadTotal,
int $downloadNow,
int $uploadTotal,
int $uploadNow,
): int {
if ($downloadTotal > 0) {
$percent = round($downloadNow / $downloadTotal * 100);
echo "\rDownload: {$percent}% ({$downloadNow}/{$downloadTotal} bytes)";
}
return 0; // Return non-zero to abort
},
]);
$data = curl_exec($ch);
curl_close($ch);
Практический пример: REST API клиент
<?php
declare(strict_types=1);
final class HttpClient
{
private string $baseUrl;
/** @var array<string, string> */
private array $defaultHeaders;
private int $timeout;
public function __construct(
string $baseUrl,
string $bearerToken = '',
int $timeout = 30,
) {
$this->baseUrl = rtrim($baseUrl, '/');
$this->timeout = $timeout;
$this->defaultHeaders = [
'Accept: application/json',
'Content-Type: application/json',
];
if ($bearerToken !== '') {
$this->defaultHeaders[] = "Authorization: Bearer $bearerToken";
}
}
/** @param array<string, mixed> $query */
public function get(string $path, array $query = []): array
{
$url = $this->baseUrl . $path;
if ($query !== []) {
$url .= '?' . http_build_query($query);
}
return $this->request('GET', $url);
}
/** @param array<string, mixed> $data */
public function post(string $path, array $data): array
{
return $this->request('POST', $this->baseUrl . $path, $data);
}
/** @param array<string, mixed> $data */
public function put(string $path, array $data): array
{
return $this->request('PUT', $this->baseUrl . $path, $data);
}
public function delete(string $path): array
{
return $this->request('DELETE', $this->baseUrl . $path);
}
/** @param array<string, mixed>|null $data */
private function request(string $method, string $url, ?array $data = null): array
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $this->defaultHeaders,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_ENCODING => '',
]);
if ($data !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_THROW_ON_ERROR));
}
$response = curl_exec($ch);
if ($response === false) {
$error = curl_error($ch);
curl_close($ch);
throw new RuntimeException("HTTP request failed: $error");
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$decoded = json_decode($response, true, flags: JSON_THROW_ON_ERROR);
if ($httpCode >= 400) {
throw new RuntimeException(
sprintf('HTTP %d: %s', $httpCode, $decoded['message'] ?? 'Unknown error')
);
}
return $decoded;
}
}
// Usage
$api = new HttpClient('https://api.example.com/v1', bearerToken: 'my-token');
$users = $api->get('/users', ['page' => 1]);
$user = $api->post('/users', ['name' => 'John', 'email' => '[email protected]']);
$updated = $api->put('/users/42', ['name' => 'Jane']);
$api->delete('/users/42');
PHP 8.5: curl_share_init_persistent
<?php
declare(strict_types=1);
// PHP 8.5+: Persistent share handles for connection reuse across requests
// DNS cache, SSL sessions, and connections shared between cURL handles
$share = curl_share_init_persistent('my-app-share');
curl_share_setopt($share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
curl_share_setopt($share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
curl_share_setopt($share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
// First request: establishes connection, DNS lookup, TLS handshake
$ch1 = curl_init('https://api.example.com/users');
curl_setopt($ch1, CURLOPT_SHARE, $share);
curl_setopt($ch1, CURLOPT_RETURNTRANSFER, true);
$response1 = curl_exec($ch1);
curl_close($ch1);
// Subsequent requests: reuse DNS cache, SSL session, connection
$ch2 = curl_init('https://api.example.com/orders');
curl_setopt($ch2, CURLOPT_SHARE, $share);
curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
$response2 = curl_exec($ch2);
curl_close($ch2);
// Much faster — no DNS lookup, no TLS handshake
// Persistent share survives across PHP requests (in FPM)
// Name 'my-app-share' links to same shared data in next request