Laminas ServiceManager

repository·4.6.x·Indexed 41 days ago

https://github.com/laminas/laminas-servicemanager

A PHP component implementing the Service Locator design pattern to provide a centralized mechanism for retrieving and managing the lifecycle of objects and services. Features include support for factories, abstract factories, delegators, and the ConfigAbstractFactory for simplified service creation via configuration maps.

Tokens
30K
Snippets
82
Records
113
Agent score
97%

What's inside Laminas ServiceManager

  1. What is laminas-servicemanager?

    4.6.x
    The Laminas\ServiceManager component implements the Service Locator design pattern. It acts as a service/object locator, providing a centralized mechanism for retrieving and managing the lifecycle of other objects and services within an application.
  2. Compare ReflectionBasedAbstractFactory and ConfigAbstractFactory

    4.6.x

    When deciding how to instantiate services, consider these alternatives to ReflectionBasedAbstractFactory:

    Use CaseRecommended Factory
    Automatic resolution via constructor typehintsReflectionBasedAbstractFactory
    Mapping to a specific implementationConfigAbstractFactory
    Mapping to a service that returns a scalar or array (e.g., a subset of the 'config' service)ConfigAbstractFactory
    High-performance production environmentsConfigAbstractFactory or a custom factory
  3. Use Initializers to inject dependencies

    4.6.x

    An initializer is a callable or a class implementing Laminas\ServiceManager\Initializer\InitializerInterface that runs every time a service is created. They are typically used to inject dependencies into objects that implement specific interfaces (e.g., injecting an EventManager into objects implementing EventManagerAwareInterface).

    Warning: Initializers are primarily maintained for backward compatibility. Their use is highly discouraged because:

    • They can lead to fragile code where objects exist in an "incomplete state" if the initializer fails to run.
    • They impact performance as they run for every single service instance created.

    Recommended Alternative: Use Factories for constructor injection, or Delegator Factories for setter/interface injection.

    use Interop\
    Container\ContainerInterface;
    use Laminas\ServiceManager\Initializer\InitializerInterface;
    use Laminas\ServiceManager\ServiceManager;
    
    // Using a class-based initializer
    class MyInitializer implements InitializerInterface
    {
        public function __invoke(ContainerInterface $container, $instance)
        {
            if ($instance instanceof EventManagerAwareInterface) {
                $instance->setEventManager($container->get(EventManager::class));
            }
        }
    }
    
    $serviceManager = new ServiceManager([
        'initializers' => [
            new MyInitializer()
        ]
    ]);
  4. Choose between Factories and Abstract Factories

    4.6.x

    In laminas-servicemanager (version 3 and later), you can choose between using explicit Factories or Abstract Factories to instantiate services.

    Key Differences

    FeatureAbstract FactoryFactory
    SetupOne-time setup; handles multiple services automatically.Requires explicit mapping for every service.
    PerformanceSlower; requires looping through factories and invoking discovery methods.Faster; uses optimized hash table lookups.
    ExplicitnessHarder to trace which factory is responsible for a service.Clear, explicit mapping of service to factory.
    ComplexityLow boilerplate; good for rapid prototyping.Higher boilerplate; requires more configuration.

    When to use which?

    • Use Abstract Factories during early development stages to avoid writing boilerplate for every new class. Examples include ReflectionBasedAbstractFactory or ConfigAbstractFactory.
    • Use Factories for production-ready code where performance is critical and service dependencies are stable.
    // Example of mapping a specific service to a factory (Explicit Factory approach)
    return [
        'factories' => [
            SomeService::class => AnAbstractFactory::class,
        ],
    ];
  5. Configure service sharing and instance behavior

    4.6.x

    By default, services in the ServiceManager are shared, meaning get() returns the same instance every time. You can change this behavior in three ways:

    1. Per-service configuration: Use the shared key in the configuration array to set a service to false.
    2. The build() method: Use $serviceManager->build(ServiceName::class) instead of get(). This always creates a new instance and never caches it.
    3. Global default: Set shared_by_default to false in the configuration to make all services non-shared by default.
    // 1. Per-service configuration
    $serviceManager = new ServiceManager([
        'factories' => [stdClass::class => InvokableFactory::class],
        'shared' => [stdClass::class => false]
    ]);
    
    // 2. Using build() for a discrete instance
    $object1 = $serviceManager->build(stdClass::class);
    
    // 3. Disabling sharing globally
    $serviceManager = new ServiceManager([
        'factories' => [stdClass::class => InvokableFactory::class],
        'shared_by_default' => false,
    ]);
  6. Manage service sharing and caching

    4.6.x

    By default, services in the ServiceManager are shared. This means calling $serviceManager->get(ServiceName::class) multiple times returns the exact same instance, which optimizes memory and performance.

    To change this behavior, you have three options:

    1. Disable sharing for a specific service: Use the shared configuration key with a boolean false value.
    2. Use the build() method: Calling $serviceManager->build(ServiceName::class) always creates a new instance and never retrieves a cached one.
    3. Disable sharing globally: Set the shared_by_default configuration key to false.
    // 1. Disable sharing for a specific service via config
    $serviceManager = new ServiceManager([
        'factories' => [stdClass::class => InvokableFactory::class],
        'shared' => [
            stdClass::class => false
        ]
    ]);
    
    // 2. Use build() to get a fresh instance regardless of sharing config
    $object1 = $serviceManager->build(stdClass::class);
    $object2 = $serviceManager->build(stdClass::class);
    var_dump($object1 === $object2); // false
    
    // 3. Disable sharing globally
    $serviceManager = new ServiceManager([
        'shared_by_default' => false,
    ]);
  7. What are Lazy Services and when to use them

    4.6.x

    Lazy services are proxies that delay the instantiation of a service until it is actually used. Instead of creating the real object immediately, the ServiceManager provides a proxy object that holds a reference to the real instance. When a method is called on the proxy, it triggers the instantiation of the real service.

    Use cases:

    • When a service is instantiated frequently but not always used during a request.
    • When a service has a high instantiation overhead (e.g., establishing a database connection or performing heavy resource allocation).
    • To reduce latency in applications where certain heavy dependencies are optional for specific execution paths.
  8. Use Abstract Factories for dynamic service creation

    4.6.x

    An abstract factory is used when you cannot know the service names in advance (e.g., they are generated at runtime) but they follow a common creation pattern.

    An abstract factory must implement Laminas\ServiceManager\Factory\AbstractFactoryInterface and provides two methods:

    1. canCreate(ContainerInterface $container, $requestedName): Returns true if the factory is capable of creating the requested service.
    2. __invoke(ContainerInterface $container, $requestedName, array $options = null): Creates and returns the service.

    Resolution Process

    When get($name) is called:

    1. The Service Manager checks for an explicit factory mapping.
    2. If none is found, it iterates through registered abstract_factories in order.
    3. It calls canCreate() on each. If true, it calls __invoke().

    Best Practices

    Limit the number of abstract factories. Because the Service Manager must iterate through them sequentially during lookups, having many abstract factories can be costly for performance. Mapping the same factory to multiple explicit services is usually a more efficient alternative.

    use Interop\Container\ContainerInterface;
    use Laminas\ServiceManager\Factory\AbstractFactoryInterface;
    
    class MyAbstractFactory implements AbstractFactoryInterface
    {
        public function canCreate(ContainerInterface $container, $requestedName)
        {
            // Logic to determine if this factory can handle the request
            return in_array('Traversable', class_implements($requestedName), true);
        }
    
        public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
        {
            return new $requestedName();
        }
    }
    
    $serviceManager = new ServiceManager([
        'abstract_factories' => [
            MyAbstractFactory::class
        ]
    ]);
  9. What are plugin managers and when to use them

    4.6.x

    Plugin managers are specialized service managers designed to create homogeneous objects of a specific type. They extend the standard service manager functionality but provide a separation of concerns by being used in specific contexts and offering additional instance validation.

    Laminas components use them to manage groups of services that share common functionalities (e.g., a ValidatorPluginManager for all validator services).

  10. What are delegators and when to use them

    4.6.x

    Delegators allow Laminas\\ServiceManager to instantiate and decorate services using the delegation pattern. This is useful when you want to wrap a real service in a decorator or intercept actions being performed on the service (Aspect-Oriented Programming) without modifying the original service's instantiation logic.

    Key characteristics:

    • Decoration: You can wrap an existing service in a new class that implements the same interface.
    • Multiple Delegators: You can specify multiple delegators for a single service. Each delegator adds a layer of decoration around the instantiation logic, creating a chain of decorators.
  11. Create service aliases

    4.6.x

    An alias provides an alternative name for a registered service. Aliases can be mapped to other service keys or even to other aliases, and they are resolved recursively.

    Best Practice: Use the ::class language construct (FQCN) for mapping instead of strings whenever possible. This improves discoverability and allows IDEs to perform refactoring safely.

    use Laminas\ServiceManager\Factory\InvokableFactory;
    use Laminas\ServiceManager\ServiceManager;
    use stdClass;
    
    $serviceManager = new ServiceManager([
        'factories' => [
            stdClass::class => InvokableFactory::class
        ],
        'aliases' => [
            'A' => stdClass::class,
            'B' => 'A'
        ]
    ]);
    
    // Resolves 'B' -> 'A' -> stdClass::class
    $object = $serviceManager->get('B');