Install via Composer:
composer require psr-discovery/discovery
For a specific PSR implementation (e.g., PSR-18 HTTP Client):
composer require psr-discovery/http-client-implementations
First Use Case: Discover a PSR-18 HTTP Client in a Laravel service:
use Psr\Discovery\DiscoveryInterface;
use Psr\Discovery\DiscoveryExceptionInterface;
use Psr\Http\Client\ClientInterface;
// In a service provider or constructor
$discovery = new \Psr\Discovery\Discovery();
$client = $discovery->discover(ClientInterface::class);
Where to Look First:
Http\Client\PendingRequest, Http\Client\Factory).Discovery in Service Providers:
Bind discovered interfaces to Laravel’s container in register():
$this->app->singleton(ClientInterface::class, function ($app) {
return resolve(DiscoveryInterface::class)->discover(ClientInterface::class);
});
Lazy-Loading Dependencies: Use discovery in constructors to defer resolution until needed:
public function __construct(private ClientInterface $client) {}
Integration with Laravel’s HTTP Client:
Prefer Laravel’s native Http facade for simplicity, but use discovery for third-party SDKs:
// Instead of:
$client = new \GuzzleHttp\Client();
// Use:
$client = resolve(ClientInterface::class);
Fallback Chains: Combine discovery with manual fallbacks for critical paths:
try {
$client = resolve(DiscoveryInterface::class)->discover(ClientInterface::class);
} catch (DiscoveryExceptionInterface) {
$client = new \GuzzleHttp\Client(); // Fallback
}
Testing with Mocks: Override discovery in tests to inject mocks:
$this->app->instance(DiscoveryInterface::class, new MockDiscovery());
Dynamic PSR Selection:
Use discovery to switch implementations based on environment (e.g., stub in tests, guzzle in production):
$discovery = new \Psr\Discovery\Discovery();
$discovery->addPsr18ClientImplementation(\GuzzleHttp\Client::class);
Missing Implementations:
DiscoveryException if no implementations are found.psr-discovery/http-client-implementations).composer why-not psr-discovery/http-client-implementations to check dependencies.Priority Conflicts:
$discovery = new \Psr\Discovery\Discovery();
$discovery->addPsr18ClientImplementation(\Laravel\Http\Client\PendingRequest::class, 10); // Higher priority
Singleton Caching:
$discovery->clearCache();
Laravel-Specific Quirks:
HttpClient facade). Use one or the other for consistency.// ❌ Avoid: Mixing discovery with Laravel's HttpClient
$this->app->bind(ClientInterface::class, fn() => resolve(DiscoveryInterface::class)->discover(ClientInterface::class));
$this->app->bind(ClientInterface::class, \Laravel\Http\Client\Factory::class); // Overrides!
List Available Implementations:
$discovery = new \Psr\Discovery\Discovery();
$implementations = $discovery->getPsr18ClientImplementations();
dd($implementations); // Debug available classes
Check Autoload Order:
Use composer dump-autoload to refresh classmap if discovery behaves unexpectedly.
Environment-Specific Config:
Override discovery in config/app.php for environment-specific implementations:
'discovery' => [
'psr18_clients' => [
env('APP_ENV') === 'testing' ? \Nyholm\Psr7\MockClient::class : \GuzzleHttp\Client::class,
],
],
Custom Discovery Logic:
Extend \Psr\Discovery\Discovery to add custom resolution rules:
class CustomDiscovery extends \Psr\Discovery\Discovery {
public function discoverWithFallback(string $interface, callable $fallback): object {
try {
return $this->discover($interface);
} catch (DiscoveryExceptionInterface) {
return $fallback();
}
}
}
PSR-15 Middleware Integration: Use discovery to dynamically load middleware for HTTP clients:
$client = resolve(ClientInterface::class);
$client->withMiddleware(new \Psr\Http\Message\Middleware\MiddlewareStack());
Testing Utilities: Create a test helper to reset discovery state:
function resetDiscovery(): void {
$discovery = resolve(DiscoveryInterface::class);
$discovery->clearCache();
$discovery->clearImplementations();
}
How can I help you explore Laravel packages today?