symfony/service-contracts
Symfony Service Contracts provides lightweight, battle-tested abstractions extracted from Symfony components. Use these shared interfaces to build interoperable libraries and apps with proven semantics and consistent behavior across the Symfony ecosystem.
Install via Composer:
composer require symfony/service-contracts
First use case: Implement ServiceSubscriberInterface in a Laravel service to declare dependencies declaratively (e.g., for jobs, commands, or domain services). Example:
use Symfony\Contracts\Service\ServiceSubscriberInterface;
class PaymentProcessor implements ServiceSubscriberInterface
{
public static function getSubscribedServices(): array
{
return [
'payment.gateway' => '?', // Optional dependency
'logger' => '?',
];
}
}
Where to look first:
ServiceSubscriberInterface – Core for dependency declaration.ServiceLocator – For lazy service resolution.Illuminate\Container (PSR-11 compliant) – Bridge to integrate contracts.Use ServiceSubscriberInterface to avoid constructor injection for optional/rarely-used services (e.g., third-party APIs, logging, or caching). Example in a Laravel job:
use Symfony\Contracts\Service\ServiceSubscriberInterface;
class SendWelcomeEmail implements ShouldQueue, ServiceSubscriberInterface
{
public static function getSubscribedServices(): array
{
return [
'mailer' => '?', // Optional
'analytics.tracker' => '?',
];
}
public function handle()
{
$mailer = $this->getServiceLocator()->get('mailer');
$mailer->send(...);
}
}
Integration: Bind the service locator in Laravel’s container:
app()->bind(ServiceLocatorInterface::class, function ($app) {
return new ServiceLocator($app);
});
For reusable packages (e.g., a NotificationService), type-hint ServiceLocatorInterface instead of concrete services:
use Symfony\Contracts\Service\ServiceLocatorInterface;
class NotificationService
{
public function __construct(private ServiceLocatorInterface $locator) {}
public function send(string $channel): void
{
$service = $this->locator->get($channel . '.service');
$service->send();
}
}
Benefit: Works with any PSR-11 container (Symfony, Laravel, PHP-DI).
Mock ServiceLocatorInterface in unit tests to isolate dependencies:
$locator = new ServiceLocator([
'mailer' => $mockMailer,
'logger' => $mockLogger,
]);
$service = new PaymentProcessor($locator);
Use ServiceLocator to resolve services conditionally (e.g., based on config):
$locator = new ServiceLocator([
'storage' => $this->config['storage'] === 's3' ? $s3Adapter : $localAdapter,
]);
No Runtime Logic:
ServiceLocatorTrait or wrap Laravel’s container:
class LaravelServiceLocator implements ServiceLocatorInterface
{
use ServiceLocatorTrait;
public function get($id)
{
return app()->make($id);
}
}
Service Not Found:
ServiceLocator throws ServiceNotFoundException by default. Override or extend:
class CustomLocator extends ServiceLocator
{
protected function defaultServices(): array
{
return ['logger' => fn() => new NullLogger()];
}
}
Laravel Container Quirks:
app() is PSR-11 compliant, but custom container extensions (e.g., bindIf, when) may break ServiceLocator resolution.app()->bound($id) before calling get($id).Request Scope Leaks:
ServiceLocator instances with request-scoped services (e.g., request(), session()).Deprecated Symfony Patterns:
ServiceSubscriberInterface) are Symfony-centric. Laravel lacks native support for ServiceSubscriberTrait’s auto-wiring.AppServiceProvider:
$this->app->when(PaymentProcessor::class)
->needs('serviceLocator')
->give(fn($app) => new ServiceLocator($app));
$container = app();
assert($container instanceof Psr\Container\ContainerInterface);
ServiceLocator calls in a try-catch to log unresolved services:
try {
$service = $locator->get('missing.service');
} catch (ServiceNotFoundException $e) {
Log::warning("Service not found: {$e->getServiceId()}");
}
Custom Service Locator:
Extend ServiceLocator to add caching or logging:
class CachingLocator extends ServiceLocator
{
public function get($id)
{
return cache()->remember("service.{$id}", 3600, fn() => parent::get($id));
}
}
Laravel Service Provider Integration: Auto-register subscribers in a provider:
public function register()
{
$this->app->bind(ServiceLocatorInterface::class, function ($app) {
$locator = new ServiceLocator($app);
foreach (config('services.subscribers') as $subscriber) {
$locator->set($subscriber, $app->make($subscriber));
}
return $locator;
});
}
Domain-Specific Exceptions:
Replace ServiceNotFoundException with custom exceptions:
class DomainServiceException extends \RuntimeException {}
// Override ServiceLocator::get() to throw DomainServiceException.
How can I help you explore Laravel packages today?