Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Service Contracts Laravel Package

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.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Install the package**:
   ```bash
   composer require symfony/service-contracts

No configuration is needed—this package provides only interfaces, so it integrates seamlessly with Laravel’s native PSR-11 container (Illuminate\Container).

  1. First use case: Lazy-loading a service in a Job Replace constructor injection with ServiceSubscriberInterface for optional dependencies:

    use Symfony\Contracts\Service\ServiceSubscriberInterface;
    use Symfony\Contracts\Service\ServiceLocator;
    
    class ProcessPayment implements ServiceSubscriberInterface
    {
        public static function getSubscribedServices(): array
        {
            return ['payment.gateway']; // Lazy-loaded via ServiceLocator
        }
    
        public function handle(ServiceLocator $locator)
        {
            $gateway = $locator->get('payment.gateway');
            $gateway->charge(...);
        }
    }
    

    Register the Job normally (e.g., via HandleJobsMiddleware or dispatch()).

  2. Verify PSR-11 compliance: Test service resolution in bootstrap/app.php:

    $container = app();
    $container->get('mailer'); // Should work without errors
    

Implementation Patterns

1. Lazy-Loading Services

Pattern: Use ServiceSubscriberInterface for services that are:

  • Rarely used (e.g., backup services, fallback handlers).
  • Expensive to instantiate (e.g., database connections, API clients).
  • Optional (e.g., third-party integrations).

Example: A Command with conditional dependencies:

class ExportData implements ServiceSubscriberInterface
{
    public static function getSubscribedServices(): array
    {
        return ['s3.client', 'mailer']; // Only 's3.client' is required
    }

    public function handle(ServiceLocator $locator)
    {
        $s3 = $locator->get('s3.client');
        $mailer = $locator->has('mailer') ? $locator->get('mailer') : null;

        // Use $mailer only if available
    }
}

2. Bulk Service Injection

Pattern: Inject collections of services (e.g., event subscribers, middleware) via ServiceLocator. Use case: Avoid circular dependencies when multiple services depend on each other.

Example: Registering event subscribers dynamically:

use Symfony\Contracts\EventDispatcher\EventSubscriberInterface;

class EventDispatcherService implements ServiceSubscriberInterface
{
    public static function getSubscribedServices(): array
    {
        return ['event.subscribers']; // Array of subscribers
    }

    public function __invoke(ServiceLocator $locator)
    {
        $subscribers = $locator->get('event.subscribers');
        foreach ($subscribers as $subscriber) {
            if ($subscriber instanceof EventSubscriberInterface) {
                $dispatcher->addSubscriber($subscriber);
            }
        }
    }
}

3. Framework-Agnostic Libraries

Pattern: Type-hint for contracts in shared packages to avoid hard dependencies. Use case: Build reusable libraries (e.g., laravel-notifications) that work with Symfony or Laravel.

Example: A notification service interface:

use Symfony\Contracts\Service\ServiceLocator;

interface NotificationService
{
    public function send(ServiceLocator $locator, string $message);
}

Implementation in Laravel:

class LaravelNotificationService implements NotificationService
{
    public function send(ServiceLocator $locator, string $message)
    {
        $mailer = $locator->get('mailer');
        $mailer->send(new Mailable($message));
    }
}

4. Testing with Mocked Services

Pattern: Replace the container with a ServiceLocator in tests to isolate dependencies. Use case: Unit testing Jobs, Commands, or domain services without bootstrapping the full app.

Example: Mocking a service in a test:

use Symfony\Contracts\Service\ServiceLocator;

public function testProcessPayment()
{
    $mockGateway = $this->createMock(PaymentGateway::class);
    $locator = new ServiceLocator([
        'payment.gateway' => $mockGateway,
    ]);

    $job = new ProcessPayment();
    $job->handle($locator);

    $mockGateway->expects($this->once())
        ->method('charge');
}

5. Integrating Symfony Components

Pattern: Use contracts to bridge Laravel with Symfony’s ecosystem (e.g., Messenger, HTTP Client). Use case: Adopt Symfony’s HttpClient or Messenger while keeping Laravel’s frontend.

Example: Using HttpClient with Laravel’s container:

use Symfony\Contracts\HttpClient\HttpClientInterface;

class ApiClient implements ServiceSubscriberInterface
{
    public static function getSubscribedServices(): array
    {
        return ['http.client']; // Symfony HttpClient
    }

    public function fetch(ServiceLocator $locator, string $url)
    {
        $client = $locator->get('http.client');
        return $client->request('GET', $url);
    }
}

Register the client in Laravel:

$container->bind('http.client', function () {
    return SymfonyHttpClient::create();
});

Gotchas and Tips

Pitfalls

  1. Missing Service Exceptions

    • Issue: ServiceLocator::get() throws ServiceNotFoundException if a service is unbound.
    • Fix: Use has() to check availability or provide default services:
      $locator->get('service', function () {
          return new FallbackService();
      });
      
  2. Request Scoping Leaks

    • Issue: Storing ServiceLocator instances across requests (e.g., in static properties) causes stale references.
    • Fix: Avoid global ServiceLocator instances. Use constructor injection for request-scoped services:
      class MyService {
          public function __construct(private ServiceLocator $locator) {}
      }
      
  3. Circular Dependencies

    • Issue: Two ServiceSubscriberInterface classes depending on each other.
    • Fix: Restructure to use a third-party service (e.g., event.dispatcher) or lazy-load only one side.
  4. Laravel-Specific Quirks

    • Issue: Laravel’s container doesn’t auto-wire ServiceSubscriberInterface like Symfony.
    • Fix: Manually bind subscribers or use a package like spatie/laravel-service-container for auto-registration.
  5. Performance Overhead

    • Issue: Lazy-loading critical services (e.g., database) adds resolution time.
    • Fix: Eager-load essential services in bootstrap/app.php:
      $container->get('db.connection'); // Force load early
      

Debugging Tips

  • Log service resolution:
    $locator->get('service', function () {
        logger()->debug('Creating fallback service');
        return new FallbackService();
    });
    
  • Validate PSR-11 compliance:
    composer require --dev symfony/dependency-injection
    php vendor/bin/dependency-injection-checker check
    
  • Inspect bound services:
    dd(app()->getBindings()); // Laravel 8+
    // or
    dd(app()->getServiceNames()); // Laravel 9+
    

Extension Points

  1. Custom Service Locator Extend ServiceLocator to add Laravel-specific features (e.g., request scoping):

    class LaravelServiceLocator extends ServiceLocator
    {
        public function getRequestScoped(string $id)
        {
            return app('request')->offsetGet($id);
        }
    }
    
  2. Auto-Registration Build a Laravel service provider to auto-register ServiceSubscriberInterface classes:

    use Symfony\Contracts\Service\ServiceSubscriberInterface;
    
    class ServiceSubscriberProvider extends ServiceProvider
    {
        public function register()
        {
            foreach (glob(app_path('Services/*Subscriber.php')) as $file) {
                $class = str_replace('.php', '', basename($file));
                $this->app->bind($class, $file);
            }
        }
    }
    
  3. Fallback Services Implement a global fallback for missing services:

    $locator = new ServiceLocator([
        'defaultServices' => [
            'logger' => fn() => new NullLogger(),
        ],
    ]);
    

Laravel-Specific Workarounds

  • Binding Subscribers: Laravel doesn’t auto-wire ServiceSubscriberInterface. Manually bind subscribers:
    $container->bind(ProcessPayment::class, function () {
        return new ProcessPayment();
    });
    
  • Request-Scoped Services: Avoid ServiceLocator for request-scoped services. Use Laravel’s native scoping:
    $container->bindWhen(
        RequestScopedService::class,
        fn() => new RequestScopedService(),
        fn($c) => $c->isRequestScoped()
    );
    
  • Testing: Use Laravel’s MockApplication
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
calmfox/watch-sylius
damienfern/grpc-symfony-bundle
atoolo/index-bundle
atoolo/genai-bundle
coprotoai/laravel-ticket
davidjln/llm-carbon-bundle
cryonighter/valid-request-bundle
coolms/taxonomy-bundle
coolms/field-bundle
articulate-orm/symfony
aaix/laravel-tall-architect
ephoto/akeneo-connector
emmanuelballery/eb-plantumlbundle
emielburgman/symfony-visitor-beacon
emielburgman/symfony-visit-storage
emielburgman/symfony-security-headers
emielburgman/symfony-log-viewer
emarref/xdebug-bundle
emarref/pubnub-bundle
elriseio/finance-money-bundle