php-http/httplug
HTTPlug provides HTTP client abstraction interfaces for PHP, built on PSR-7 messages. Defines async HttpAsyncClient plus a sync HttpClient similar to PSR-18, enabling libraries to stay client-agnostic while supporting multiple HTTP implementations.
HTTPlug defines two core interfaces for HTTP clients: HttpClient (synchronous) and HttpAsyncClient (asynchronous), both building on PSR-7 messages and returning PSR-7 responses. As of v2.4.0, HTTPlug is effectively a transition layer—the package authors now recommend using PSR-18 directly for synchronous clients, and only reach for HTTPlug if you need async support or legacy compatibility.
Start by installing:
composer require php-http/httplug php-http/discovery
httplug: defines the interfacesdiscovery: enables auto-discovery of installed HTTP clients (e.g., Guzzle, curl, React)The first use case is typically resolving a client via HTTPlug’s discovery mechanism:
use Http\Discovery\HttpClientDiscovery;
$client = HttpClientDiscovery::find();
$response = $client->sendRequest($psr7Request);
💡 Prefer
HttpClientDiscovery::find()over manual instantiation when building reusable packages—this keeps client selection decoupled.
If you only need synchronous requests (95% of apps), just depend on psr/http-client and use Psr\Http\Client\ClientInterface. HTTPlug’s HttpClient now mirrors PSR-18 and is deprecated—its inclusion is mainly for backwards compatibility.
HttpAsyncClientUse HttpAsyncClient when you need concurrency (e.g., batch external API calls, background sync tasks):
use Http\Client\HttpAsyncClient;
use Http\Discovery\Psr17Factory;
use function Http\Promise\unwrap;
// Build multiple requests
$requests = array_map(
fn ($url) => Psr17Factory::createRequestFactory()->createRequest('GET', $url),
['https://api.a.com', 'https://api.b.com']
);
/** @var HttpAsyncClient $asyncClient */
$responsesPromise = $asyncClient->sendAsyncRequests($requests);
// Wait for all
$responses = unwrap($responsesPromise);
You’ll typically inject HttpAsyncClient (via DI) and combine with php-http/promise utilities like unwrap() or HttpFulfilledPromise.
HTTPlug ships with plugins (php-http/plugins) to add retries, logging, etc. Use HTTPlug’s promise system to support async:
use Http\Client\Common\Plugin\RetryPlugin;
use Http\Client\Common\PluginClient;
$plugins = [new RetryPlugin()];
$client = new PluginClient($decoratedClient, $plugins);
⚠️ Plugins rely on
Http\Promise\Promise(not PSR-18), so async logic remains compatible.
Http\Client\HttpClient is deprecated as of 2.4.0. New code should use Psr\Http\Client\ClientInterface for sync.NetworkException no longer extends RequestException (aligns with PSR-18). Update catch blocks accordingly.unwrap() or use then() to surface failures:
$promise = $asyncClient->sendAsyncRequest($request);
try {
$response = $promise->wait(); // or unwrap([$promise])[0]
} catch (\Throwable $e) {
// Network exceptions end up here
}
$onRejected callback in v2.3.0+ can return another promise—use this for smart retry logic:
$promise = $asyncClient->sendAsyncRequest($request);
$retryPromise = $promise->then(
fn ($r) => $r,
fn ($e) => $shouldRetry ? $this->retry($e) : throw $e
);
httplug with a client implementation (e.g., php-http/guzzle6-adapter).php-http/discovery cautiously in production—it scans installed packages, which adds overhead. Prefer explicit client binding via DI in critical paths.httplug Discovery configuration (not covered in README—requires subclassing DiscoveryStrategy).How can I help you explore Laravel packages today?