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

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.
  • Laravel’s Illuminate\Container (PSR-11 compliant) – Bridge to integrate contracts.

Implementation Patterns

1. Decoupled Service Dependencies in Laravel

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);
});

2. Framework-Agnostic Libraries

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).

3. Test Doubles Without Full Container

Mock ServiceLocatorInterface in unit tests to isolate dependencies:

$locator = new ServiceLocator([
    'mailer' => $mockMailer,
    'logger' => $mockLogger,
]);

$service = new PaymentProcessor($locator);

4. Dynamic Service Resolution

Use ServiceLocator to resolve services conditionally (e.g., based on config):

$locator = new ServiceLocator([
    'storage' => $this->config['storage'] === 's3' ? $s3Adapter : $localAdapter,
]);

Gotchas and Tips

Pitfalls

  1. No Runtime Logic:

    • The package provides only interfaces. You must implement concrete logic (e.g., bind services in Laravel’s container).
    • Fix: Use ServiceLocatorTrait or wrap Laravel’s container:
      class LaravelServiceLocator implements ServiceLocatorInterface
      {
          use ServiceLocatorTrait;
      
          public function get($id)
          {
              return app()->make($id);
          }
      }
      
  2. Service Not Found:

    • ServiceLocator throws ServiceNotFoundException by default. Override or extend:
      class CustomLocator extends ServiceLocator
      {
          protected function defaultServices(): array
          {
              return ['logger' => fn() => new NullLogger()];
          }
      }
      
  3. Laravel Container Quirks:

    • Laravel’s app() is PSR-11 compliant, but custom container extensions (e.g., bindIf, when) may break ServiceLocator resolution.
    • Tip: Test with app()->bound($id) before calling get($id).
  4. Request Scope Leaks:

    • Avoid storing ServiceLocator instances with request-scoped services (e.g., request(), session()).
    • Fix: Use constructor injection for per-request dependencies.
  5. Deprecated Symfony Patterns:

    • Some interfaces (e.g., ServiceSubscriberInterface) are Symfony-centric. Laravel lacks native support for ServiceSubscriberTrait’s auto-wiring.
    • Workaround: Manually bind subscribers in AppServiceProvider:
      $this->app->when(PaymentProcessor::class)
          ->needs('serviceLocator')
          ->give(fn($app) => new ServiceLocator($app));
      

Debugging Tips

  • Verify PSR-11 Compliance:
    $container = app();
    assert($container instanceof Psr\Container\ContainerInterface);
    
  • Log Missing Services: Wrap 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()}");
    }
    

Extension Points

  1. 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));
        }
    }
    
  2. 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;
        });
    }
    
  3. Domain-Specific Exceptions: Replace ServiceNotFoundException with custom exceptions:

    class DomainServiceException extends \RuntimeException {}
    // Override ServiceLocator::get() to throw DomainServiceException.
    
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.
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope
anil/file-picker
broqit/fields-ai