symfony/http-client-contracts
Symfony HttpClient Contracts provides stable interfaces for HTTP clients and responses, extracted from Symfony. Build libraries against these battle-tested abstractions and swap implementations easily while staying compatible with Symfony’s HttpClient ecosystem.
Install via Composer:
composer require symfony/http-client-contracts
This package provides only interfaces—no implementations. Pair it with a concrete HTTP client like:
symfony/http-client (recommended for async, HTTP/2, and pooling)guzzlehttp/guzzle (via guzzlehttp/psr7 or php-http/guzzle7-adapter)php-http/client (auto-discovers installed clients)First use case: Inject HttpClientInterface into a service and type-hint against it:
use Symfony\Contracts\HttpClient\HttpClientInterface;
class ApiService
{
public function __construct(private HttpClientInterface $client) {}
public function fetchData(string $endpoint): array
{
$response = $this->client->request('GET', $endpoint);
return $response->toArray(); // Parses JSON response
}
}
Key first step: Always depend on HttpClientInterface, not concrete implementations. This enables swapping clients (e.g., for testing or production).
Register the interface in config/services.php:
'http_client' => [
'default' => Symfony\Contracts\HttpClient\HttpClientInterface::class,
],
Bind a concrete client (e.g., Symfony’s HttpClient) in a service provider:
$this->app->bind(
Symfony\Contracts\HttpClient\HttpClientInterface::class,
fn () => new Symfony\Component\HttpClient\HttpClient()
);
HttpClientInterface with MockResponse:
$mockResponse = MockResponse::fromJsonString('{"status": "ok"}');
$mockClient = new MockHttpClient([$mockResponse]);
$service = new ApiService($mockClient);
TestHttpClient (from symfony/http-client) to verify requests:
$client = new TestHttpClient();
$client->request('GET', 'https://api.example.com/data')
->toArray();
$this->assertCount(1, $client->getRequests());
$response = $client->request('GET', $url);
if ($response->getStatusCode() >= 400) {
throw new RuntimeException('API request failed');
}
AsyncHttpClient (from symfony/http-client) for non-blocking calls:
$asyncClient = new AsyncHttpClient();
$promise = $asyncClient->request('GET', $url);
$response = $promise->await();
Wrap the client to add cross-cutting concerns (e.g., logging, retries):
class RetryHttpClient implements HttpClientInterface
{
public function __construct(private HttpClientInterface $client) {}
public function request(string $method, string $url, array $options = []): ResponseInterface
{
try {
return $this->client->request($method, $url, $options);
} catch (TransportException $e) {
// Retry logic here
return $this->client->request($method, $url, $options);
}
}
}
HttpClientInterface alone won’t work—install a PSR-18-compliant client (e.g., symfony/http-client).toArray() throws on empty bodies. Use toArray(false) to skip JSON decode:
$response->toArray(false); // Returns `null` for empty responses
request() is synchronous. Use AsyncHttpClient for non-blocking calls.HttpClient:
# config/packages/http_client.yaml
framework:
http_client:
debug: '%kernel.debug%'
TestHttpClient in tests:
$client = new TestHttpClient();
$client->request('GET', 'https://api.example.com');
$this->assertEquals('GET', $client->getRequests()[0]->getMethod());
class AuthHttpClient implements HttpClientInterface
{
public function __construct(private HttpClientInterface $client) {}
public function request(string $method, string $url, array $options = []): ResponseInterface
{
$options['headers']['Authorization'] = 'Bearer ' . $this->token;
return $this->client->request($method, $url, $options);
}
}
symfony/http-client's retry middleware or implement a custom decorator.Http facade: Not directly compatible. Use HttpClientInterface with a PSR-18 adapter (e.g., php-http/laravel-adapter).symfony/psr-http-message-bridge to unify PSR-7/18 types.How can I help you explore Laravel packages today?