Installation:
composer require psr-discovery/all
Add to composer.json under require-dev if only needed for testing.
First Use Case: Discover a PSR-18 HTTP Client (most common in Laravel):
use Psr\Discovery\DiscoveryInterface;
use Psr\Discovery\DiscoveryException;
use Psr\Http\Client\ClientInterface;
$discovery = new \Psr\Discovery\Discovery();
$client = $discovery->discover(ClientInterface::class);
Where to Look First:
config/app.php for default PSR bindings (e.g., HttpClient, Cache).Dependency Injection (DI) Integration:
Bind discovered services in Laravel’s AppServiceProvider:
public function register()
{
$discovery = new \Psr\Discovery\Discovery();
$this->app->singleton(ClientInterface::class, fn() => $discovery->discover(ClientInterface::class));
}
Conditional Discovery: Useful for optional features (e.g., caching):
try {
$cache = $discovery->discover(\Psr\SimpleCache\CacheInterface::class);
} catch (DiscoveryException $e) {
// Fallback to in-memory cache
$cache = new \Symfony\Component\Cache\Adapter\ArrayAdapter();
}
Testing: Mock discovery in PHPUnit:
$discovery = $this->createMock(DiscoveryInterface::class);
$discovery->method('discover')->with(\Psr\Log\LoggerInterface::class)->willReturn($mockLogger);
Service Provider Bootstrapping:
Discover and bind PSR services in boot():
public function boot()
{
$discovery = new \Psr\Discovery\Discovery();
$this->app->when(\App\Services\ApiService::class)
->needs(ClientInterface::class)
->give(fn() => $discovery->discover(ClientInterface::class));
}
Config-Driven Discovery:
Override defaults via config/psr-discovery.php:
return [
'http_client' => env('PSR_HTTP_CLIENT', \GuzzleHttp\Client::class),
'cache' => env('PSR_CACHE', \Stash\Pool::class),
];
Then extend Discovery:
class AppDiscovery extends \Psr\Discovery\Discovery
{
public function discover($interface)
{
$config = config('psr-discovery');
if (isset($config[$interface])) {
return new $config[$interface];
}
return parent::discover($interface);
}
}
Event Dispatcher Integration: Replace Laravel’s event dispatcher with PSR-14:
$dispatcher = $discovery->discover(\Psr\EventDispatcher\EventDispatcherInterface::class);
$this->app->singleton(\Illuminate\Contracts\Events\Dispatcher::class, fn() => new \Illuminate\Events\Dispatcher($dispatcher));
No Guaranteed Implementations:
guzzlehttp/guzzle for PSR-18).try-catch or provide fallbacks (e.g., MockHttpClient in tests).Class Loading Order:
composer autoload wins. Prioritize dependencies explicitly:
composer require guzzlehttp/guzzle:^7.0 --sort-packages
Laravel’s Built-in PSRs:
Cache, Log). Overriding may cause conflicts.vendor/laravel/framework/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php for defaults.Performance:
$discovery = new \Psr\Discovery\Discovery();
$client = $discovery->discover(ClientInterface::class);
$this->app->singleton(ClientInterface::class, fn() => $client);
Verify Installed Implementations:
composer show --installed | grep -E 'guzzle|symfony/cache|monolog'
Check Autoload:
composer dump-autoload
Discovery Logs: Enable verbose logging to debug missing classes:
$discovery = new \Psr\Discovery\Discovery();
$discovery->setLogger(new \Monolog\Logger('discovery', [new \Monolog\Handler\StreamHandler('php://stderr')]));
Custom Discovery Strategies:
Extend Discovery to support non-PSR interfaces or custom logic:
class CustomDiscovery extends \Psr\Discovery\Discovery
{
public function discover($interface)
{
if ($interface === \App\Contracts\CustomInterface::class) {
return new \App\Services\CustomService();
}
return parent::discover($interface);
}
}
Priority-Based Discovery:
Override findImplementation() to enforce priority:
protected function findImplementation(string $interface): ?string
{
$priorities = [
\Psr\Http\Client\ClientInterface::class => [
\GuzzleHttp\Client::class,
\Symfony\Contracts\HttpClient\HttpClientInterface::class,
],
];
return $priorities[$interface][0] ?? null;
}
Integration with Laravel Packages:
Use discovery in package development to avoid hard dependencies:
// In your package's service provider
$this->app->bind(
\YourPackage\Contracts\ApiClient::class,
fn() => $this->app->make(\Psr\Discovery\Discovery::class)->discover(\Psr\Http\Client\ClientInterface::class)
);
Artisan Commands: Discover services dynamically in commands:
protected $httpClient;
public function __construct()
{
$this->httpClient = app(\Psr\Discovery\Discovery::class)->discover(\Psr\Http\Client\ClientInterface::class);
}
Testing with Mocks: Replace discovery in tests:
$this->app->instance(\Psr\Discovery\DiscoveryInterface::class, $mockDiscovery);
Environment-Specific Discovery:
Use config('app.env') to switch implementations:
$discovery = new \Psr\Discovery\Discovery();
$client = app()->environment('local')
? $discovery->discover(\Psr\Http\Client\ClientInterface::class)
: new \GuzzleHttp\Client(['base_uri' => 'https://api.prod.com']);
How can I help you explore Laravel packages today?