League Container

repository·6.x·Indexed 21 days ago

https://github.com/thephpleague/container

A fast, simple, and interoperable dependency injection container for PHP. It supports constructor and setter injection, singleton registration, invokables, and service providers for lazy registration. Features include auto-wiring via ReflectionContainer, object manipulation using inflectors, delegate containers for fallback resolution, and a `container-compile` CLI tool to compile the container into a high-performance PHP class.

Tokens
59.2K
Snippets
185
Records
211
Agent score
72%

What's inside league/container

  1. What is Container and its key features?

    6.x

    Container is a lightweight dependency injection container designed to decouple components in your application for better testability and cleanliness.

    Key features include:

    • Interoperability: Implements the container-interop project standards.
    • Speed: Optimized for high performance due to its small footprint.
    • Service Providers: Allows packaging code or configuration for reusable packages.
    • Inflectors: Enables manipulation of objects resolved through the container based on their type.
    • Delegate Containers: Allows registering parent containers to resolve services when the current container does not provide them.
    • Extensibility: Modular design for easy functional extensions.
  2. New features in 6.0

    6.x

    Version 6.0 introduces several powerful capabilities:

    • Contextual Binding: Use addContextualArgument() on definitions to inject different implementations of the same interface depending on the consumer class.
    • Improved Error Messages: Enhanced feedback for NotFoundException, runtime circular dependency detection, and resolution chain reporting.
    • Container Introspection: Inspect the container using getDefinitionIds() and getServiceProviderIds(). The compile CLI also supports a --dump flag.
    • #[Shared] Attribute: Use this PHP attribute to declare singleton intent at the class level when using ReflectionContainer for auto-wiring.
  3. Key features of League Container

    6.x

    Container provides several advanced features for managing dependencies:

    • PSR-11 Interoperability: Fully implements the PSR-11 container standard.
    • Service Providers: Allows packaging code or configuration for reusable packages.
    • Event System: Use afterResolve() to hook into the lifecycle and apply cross-cutting behavior to resolved services.
    • Container Compilation: Compiles the container into a standalone PHP class for production, removing the need for reflection at runtime.
    • Contextual Binding: Injects different implementations of the same interface depending on which class is requesting it.
    • Introspection: Provides improved error messages for debugging configuration issues.
  4. Understand Event Execution Order and Removal

    6.x

    Execution Order

    1. Direct listeners: Registered via addListener(), executed in registration order.
    2. Filtered listeners: Registered via listen()->then(), executed in registration order.

    If a direct listener calls $event->stopPropagation(), no filtered listeners will execute.

    Listener Removal

    • removeListener() only works for direct listeners registered via addListener().
    • Filtered listeners (from listen()) cannot be individually removed.
    • To clear everything, use removeListeners() to clear all listeners and filters for a specific event type.
  5. Use Bootable Service Providers for eager configuration

    6.x

    If you need to execute logic immediately when a service provider is added to the container (such as setting up inflectors or loading configuration files), implement the League\Container\ServiceProvider\BootableServiceProviderInterface.

    By implementing this interface, you can define a boot(): void method. Unlike the register() method which is lazily invoked only when a service is requested, the boot() method is invoked eagerly as soon as the provider is registered with the container.

    Use the boot() method for:

    • Applying inflectors.
    • Registering further service providers.
    • Loading configuration files.

    Note: If you attempt to register further service providers from a non-bootable provider, they will be ignored.

    <?php 
    
    namespace Acme\ServiceProvider;
    
    use League\Container\ServiceProvider\AbstractServiceProvider;
    use League\Container\ServiceProvider\BootableServiceProviderInterface;
    
    class SomeServiceProvider extends AbstractServiceProvider implements BootableServiceProviderInterface
    {
        public function boot(): void
        {
            $this->getContainer()
                 ->inflector('SomeType')
                 ->invokeMethod('someMethod', ['some_arg'])
             ;
        }
    
        public function provides(string $id): bool
        {
            // ...
        }
    
        public function register(): void
        {
            // ...
        }
    }
  6. Understand event execution order and listener management

    6.x

    Execution Order

    When an event is dispatched, listeners execute in this order:

    1. Direct listeners (registered via addListener()): Executed in registration order.
    2. Filtered listeners (registered via listen()->then()): Executed in registration order.

    If a direct listener calls $event->stopPropagation(), no filtered listeners will execute.

    Listener Removal

    • removeListener(): Only removes listeners registered via addListener().
    • removeListeners(): Clears all listeners and filters for a specific event type.

    Performance

    The system has near-zero overhead when not in use because events are only dispatched if listeners are registered for that specific type. You can check for listeners using $container->getEventDispatcher()->hasListenersFor(EventClass::class).

  7. Use Inflectors to manipulate objects during retrieval

    6.x

    Inflectors allow you to define automated manipulations that occur on an object after it is resolved by the container but before it is returned to the caller. This is particularly useful for applying cross-cutting concerns, such as injecting dependencies into any class that implements a specific interface.

    To use an inflector, you call inflector() on the container with the target type (class name or interface name), and then chain methods like invokeMethod() to define the action. Note that if you pass a string as an argument to invokeMethod(), the container will attempt to resolve that string as a dependency from the container itself.

    $container
        ->inflector('LoggerAwareInterface')
        ->invokeMethod('setLogger', ['Acme\Logger']);
  8. Create a Bootable Service Provider for eager loading

    6.x

    Standard service providers are lazy. If you need to perform actions immediately when the provider is added to the container (such as setting up inflectors or loading configuration files), you must implement the League\Container\ServiceProvider\BootableServiceProviderInterface.

    By implementing this interface, you can define a boot() method. Unlike register(), the boot() method is invoked eagerly as soon as the service provider is registered with the container.

    Note: If you intend to apply inflectors or register further service providers from within a provider, that provider must be a bootable service provider.

    <?php
    
    namespace Acme\ServiceProvider;
    
    use League\Container\ServiceProvider\AbstractServiceProvider;
    use League\Container\ServiceProvider\BootableServiceProviderInterface;
    
    class SomeServiceProvider extends AbstractServiceProvider implements BootableServiceProviderInterface
    {
        protected $provides = [
            // ...
        ];
    
        public function boot()
        {
            // This is executed eagerly upon registration
            $this->getContainer()
                 ->inflector('SomeType')
                 ->invokeMethod('someMethod', ['some_arg']);
        }
    
        public function register()
        {
            // ...
        }
    }
  9. Understand compiled container limitations

    6.x

    The compiled container is optimized for performance but introduces the following constraints:

    • No Event Dispatch: Methods like on(), afterResolve(), and afterResolving() are ignored. Use addMethodCall() for configuration instead.
    • No Runtime Additions: You cannot call add(), addShared(), or any definition methods after compilation. All services must be defined at compile time. If you need runtime flexibility, use the dynamic container.
    • No getNew() Method: The compiled container implements Psr\\\Container\\\ContainerInterface only. Use get() on non-shared definitions to obtain new instances.
    • No Closure Support: Closures cannot be serialized. Refactor them into factory classes or static methods.
    • No Custom Delegates: Only the ReflectionContainer delegate (used for autowiring) is compiled. Custom delegates must be replaced with explicit service definitions.
  10. How Auto-Wiring works with Modern PHP features

    6.x

    The ReflectionContainer supports modern PHP type system features, including:

    • Union Types: Resolving dependencies that use TypeA|TypeB syntax.
    • Nullable Types: Handling parameters marked with ?Type.
    • Promoted Constructor Properties: Automatically resolving dependencies defined directly in the constructor signature.

    When using union types, ensure that the specific implementation required by the union is registered in the container (e.g., via $container->add(Interface::class, Implementation::class)).

    // Example of a service using union types and nullable dependencies
    class AdvancedService
    {
        public function __construct(
            private readonly CacheInterface $cache,
            private readonly DatabaseLogger|FileLogger $logger,
            private readonly ?string $apiKey = null
        ) {}
    }
    
    $container = new League\Container\Container();
    $container->delegate(new League\Container\ReflectionContainer());
    
    // You must register implementations for interfaces used in union types
    $container->add(CacheInterface::class, RedisCache::class);
    $container->add(DatabaseLogger::class);
    
    $service = $container->get(AdvancedService::class);
  11. How Auto Dependency Resolution works

    6.x

    The container can automatically resolve objects and their dependencies recursively by inspecting constructor type hints.

    Limitations:

    1. It is limited to constructor injection.
    2. All injected dependencies must be objects (it cannot automatically resolve scalar types like strings or integers without explicit configuration).
    // If Foo depends on Bar and Baz, and Bar depends on Bam,
    // the container resolves the entire tree automatically:
    $container = new League\Container\Container;
    $foo = $container->get('Foo');
  12. How to create a Bootable Service Provider

    6.x

    If you need to execute logic immediately when a provider is added (such as configuring inflectors or loading configuration files), implement the League\Container\ServiceProvider\BootableServiceProviderInterface.

    Unlike the register() method which is called lazily, the boot() method is invoked eagerly as soon as the service provider is registered with the container.

    Important: If you intend to apply inflectors or register further service providers from within a provider, that provider must be a BootableServiceProviderInterface implementation; otherwise, those actions will be ignored.

    <?php 
    
    namespace Acme\ServiceProvider;
    
    use League\Container\ServiceProvider\AbstractServiceProvider;
    use League\Container\ServiceProvider\BootableServiceProviderInterface;
    
    class SomeServiceProvider extends AbstractServiceProvider implements BootableServiceProviderInterface
    {
        public function boot(): void
        {
            $this->getContainer()
                 ->inflector('SomeType')
                 ->invokeMethod('someMethod', ['some_arg'])
             ;
        }
    
        public function provides(string $id): bool
        {
            // ...
        }
    
        public function register(): void
        {
            // ...
        }
    }