symfony/http-client
Symfony HttpClient provides a modern HTTP client for PHP with sync and async requests, streaming responses, retries, and built-in support for common auth and options. Designed for performance, flexible transports, and smooth integration with Symfony apps.
Installation:
composer require symfony/http-client
For Laravel, use symfony/http-client as a dependency in composer.json.
Basic Request:
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\HttpClient\HttpClient;
$client = HttpClient::create();
$response = $client->request('GET', 'https://api.example.com/data');
$content = $response->getContent();
First Use Case: Fetch JSON data from an external API:
$response = $client->request('GET', 'https://api.example.com/users');
$users = $response->toArray(); // Automatically decodes JSON
HttpClient::create(): Default client with cURL transport.HttpClientInterface: Contract for dependency injection.request(): Core method for all HTTP requests (GET, POST, etc.).Register the client in Laravel's service container (config/app.php):
'providers' => [
// ...
Symfony\Contracts\HttpClient\HttpClientInterface::class => function ($app) {
return Symfony\HttpClient\HttpClient::create();
},
],
Inject via constructor:
public function __construct(private HttpClientInterface $client) {}
$response = $client->request('POST', '/api/data', [
'headers' => ['Authorization' => 'Bearer token'],
'body' => json_encode(['key' => 'value']),
]);
$response = $client->request('GET', '/api/data', [
'query' => ['page' => 1, 'limit' => 10],
]);
$promise = $client->request('GET', 'https://api.example.com/data');
$content = $promise->then(function ($response) {
return $response->getContent();
});
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Component\HttpClient\Cache\CacheClient;
$cache = new CacheClient($client, $cachePool);
use Symfony\Component\HttpClient\Retry\RetryClient;
$retryClient = new RetryClient($client, [
'max_retries' => 3,
'delay' => 100,
]);
$response = $client->request('GET', 'https://large-file.example.com/data');
$stream = $response->getContent(false); // Stream instead of buffering
$response = $client->request('GET', 'https://api.example.com/protected', [
'auth_basic' => ['user', 'pass'],
]);
$response = $client->request('GET', 'https://api.example.com/protected', [
'headers' => ['Authorization' => 'Bearer token123'],
]);
try {
$response = $client->request('GET', 'https://api.example.com/data');
$response->toArray();
} catch (\Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface $e) {
// Handle 4xx errors
} catch (\Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface $e) {
// Handle 5xx errors
} catch (\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface $e) {
// Handle network errors
}
Bind the client with custom configurations:
$this->app->singleton(HttpClientInterface::class, function ($app) {
return HttpClient::create([
'base_uri' => 'https://api.example.com/v1/',
'timeout' => 30,
'headers' => [
'Accept' => 'application/json',
'User-Agent' => 'Laravel-App/1.0',
],
]);
});
Use Laravel's middleware to modify requests:
$client = HttpClient::create();
$client->withOptions([
'on_headers' => function (ResponseInterface $response, array $options) {
// Modify headers or add logging
},
]);
Dispatch long-running requests to queues:
use Symfony\Component\HttpClient\Exception\TransportExceptionInterface;
class FetchDataJob implements ShouldQueue
{
public function handle(HttpClientInterface $client) {
try {
$response = $client->request('GET', 'https://slow-api.example.com/data');
// Process response
} catch (TransportExceptionInterface $e) {
$this->release(60); // Retry after 60 seconds
}
}
}
Create a service class to abstract API calls:
class UserApiService
{
public function __construct(private HttpClientInterface $client) {}
public function getUser(int $id): array {
$response = $this->client->request('GET', '/users/' . $id);
return $response->toArray();
}
}
finally blocks or Laravel's try-catch-finally to ensure cleanup:
$response = $client->request('GET', 'https://example.com/stream');
$stream = $response->getContent(false);
try {
// Process stream
} finally {
$stream->close();
}
CurlHttpClient reuses connections, which can cause issues with stateful APIs (e.g., OAuth tokens).$client->request('GET', 'https://api.example.com/token');
$client->reset(); // Clear connection state
$client = HttpClient::create(['timeout' => 60]);
// Or per request
$client->request('GET', 'https://slow-api.example.com', ['timeout' => 120]);
await promises can lead to unhandled exceptions.then() or await in async contexts:
$promise = $client->request('GET', 'https://api.example.com');
$promise->then(function ($response) {
// Handle response
})->wait(); // Block until completion (if needed)
CachingHttpClient may serve stale data if not configured properly.max_age and stale_while_revalidate:
$cacheClient = new CacheClient($client, $cachePool, [
'max_age' => 3600, // 1 hour
'stale_while_revalidate' => 60, // 1 minute
]);
$client = HttpClient::create([
'proxy' => 'tcp://proxy.example.com:8080',
]);
$client = HttpClient::create([
'verify_peer' => false, // Disable verification (insecure!)
'cafile' => '/path/to/certificate.pem',
]);
$client = HttpClient::create([
'debug' => true,
'on_headers' => function (ResponseInterface $response, array $options) {
error_log($
How can I help you explore Laravel packages today?