psr-discovery/http-client-implementations
Discovers an installed PSR-18 HTTP client at runtime by checking for well-known implementations and returning the first available instance. Ideal for SDKs/libraries to support PSR-18 without hard dependencies or extra user configuration.
composer require psr-discovery/http-client-implementations
composer require guzzlehttp/guzzle
use PsrDiscovery\Discover;
use Psr\Http\Message\RequestFactoryInterface;
$httpClient = Discover::httpClient();
$requestFactory = Discover::httpMessageFactory(); // Optional: If using PSR-17
config/app.php: Ensure no conflicting HTTP client bindings exist (e.g., in bindings).composer.json: Verify installed HTTP client implementations (e.g., guzzlehttp/guzzle).HttpClient::macro()).Leverage Laravel’s service container to bind the discovered client as a singleton for reuse:
// In a service provider (e.g., AppServiceProvider)
use PsrDiscovery\Discover;
use Psr\Http\Client\ClientInterface;
public function register()
{
$this->app->singleton(ClientInterface::class, function () {
return Discover::httpClient(singleton: true);
});
}
Usage in Controllers/Services:
use Psr\Http\Client\ClientInterface;
public function __construct(private ClientInterface $httpClient) {}
Use prefer() or use() to enforce specific implementations (e.g., for testing or environment-specific needs):
// Prefer Guzzle in production, mock in tests
if (app()->environment('testing')) {
Discover\Implementations\Psr18\Clients::use('php-http/mock-client');
} else {
Discover\Implementations\Psr18\Clients::prefer('guzzlehttp/guzzle');
}
Pair with psr-discovery/http-factory-implementations for request/response factories:
use PsrDiscovery\Discover;
use Psr\Http\Message\RequestFactoryInterface;
$requestFactory = Discover::httpMessageFactory();
$request = $requestFactory->createRequest('GET', 'https://api.example.com');
$response = $httpClient->sendRequest($request);
Attach middleware to the discovered client (e.g., logging, retries):
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
$httpClient = Discover::httpClient();
$httpClient = new class($httpClient) implements ClientInterface {
public function __construct(private ClientInterface $client) {}
public function sendRequest(RequestInterface $request): ResponseInterface {
// Pre-process request (e.g., add headers)
$response = $this->client->sendRequest($request);
// Post-process response
return $response;
}
};
Prioritize mock clients in tests:
// In phpunit.xml or setupTests()
Discover\Implementations\Psr18\Clients::use('php-http/mock-client');
// Test example
$mockClient = Discover::httpClient();
$mockClient->expects($this->once())
->method('sendRequest')
->willReturn($this->createMock(ResponseInterface::class));
Null Returns:
Discover::httpClient() returns null. Always check:
$client = Discover::httpClient() ?: throw new RuntimeException('No HTTP client found');
Singleton Conflicts:
singleton: false if needed:
$client = Discover::httpClient(singleton: false);
Version Mismatches:
^7.0 | ^8.0). Installing incompatible versions may break discovery.composer why guzzlehttp/guzzle to verify installed version.Mocking Priority:
php-http/mock-client) are prioritized over real clients. To bypass this:
Discover\Implementations\Psr18\Clients::use('guzzlehttp/guzzle');
PsrDiscovery\Implementations\Psr18\Clients class for supported packages.
var_dump(Discover\Implementations\Psr18\Clients::getAvailable());
$client = Discover::httpClient();
if (!$client) {
\Log::error('No PSR-18 client found. Available packages: ' . implode(', ', array_keys(Discover\Implementations\Psr18\Clients::getAvailable())));
}
Custom Implementations:
Extend the package to support additional clients by modifying the PsrDiscovery\Implementations\Psr18\Clients class or creating a custom discovery strategy:
// Example: Add a custom client
Discover\Implementations\Psr18\Clients::add('my/custom-client', MyCustomClient::class);
Environment-Specific Config: Use Laravel’s config to override client preferences:
// config/http-client.php
return [
'preferred_client' => env('HTTP_CLIENT', 'guzzlehttp/guzzle'),
];
Then integrate in a service provider:
Discover\Implementations\Psr18\Clients::prefer(config('http-client.preferred_client'));
Fallback Logic: Implement a fallback chain for critical paths:
$client = Discover::httpClient() ?: new \GuzzleHttp\Client();
singleton: true to avoid redundant instantiation in high-traffic applications.$client = fn() => Discover::httpClient(singleton: true);
How can I help you explore Laravel packages today?