laminas/laminas-servicemanager
Powerful dependency injection and service container for PHP. Manage factories, abstract factories, delegators, aliases, and shared services, with PSR-11 interoperability and robust configuration for complex applications.
The Service Manager was first introduced for Laminas 2.0.0. Its API remained the same throughout that version.
Version 3 is the first new major release of the Service Manager, and contains a number of backwards compatibility breaks. These were introduced to provide better performance and stability.
v2 normalized service names as follows:
This was done to help prevent typographical errors from creating configuration errors. However, it also presented a large performance hit, and led to some unexpected behaviors.
In v3, service names are case sensitive, and are not normalized in any way.
As such, you must refer to services using the same case in which they were registered.
A number of changes have been made to configuration of service and plugin managers:
ConfigInterface implementations and consumers will need updating.Configuration for v2 consisted of the following:
[
'services' => [
// service name => instance pairs
],
'aliases' => [
// alias => service name pairs
],
'invokables' => [
// service name => class name pairs
],
'factories' => [
// service name => factory pairs
],
'abstract_factories' => [
// abstract factories
],
'initializers' => [
// initializers
],
'delegators' => [
// service name => [ delegator factories ]
],
'shared' => [
// service name => boolean
],
'share_by_default' => boolean,
]
In v3, the configuration remains the same, with the following additions:
[
'lazy_services' => [
// The class_map is required if using lazy services:
'class_map' => [
// service name => class name pairs
],
// The following are optional:
'proxies_namespace' => 'Alternate namespace to use for generated proxy classes',
'proxies_target_dir' => 'path in which to write generated proxy classes',
'write_proxy_files' => true, // boolean; false by default
],
]
The main change is the addition of integrated lazy service configuration is now integrated.
The principal change to the ConfigInterface is the addition of the
toArray() method. This method is intended to return a configuration array in
the format listed above, for passing to either the constructor or the
configure() method of the ServiceManager..
Laminas\ServiceManager\Config has been updated to follow the changes to the
ConfigInterface and ServiceManager. This essentially means that it removes
the various getter methods, and adds the toArray() method.
Invokables no longer exist, at least, not identically to how they existed in Laminas.
Internally, ServiceManager now does the following for invokables entries:
factories entry mapping the
service name to Laminas\ServiceManager\Factory\InvokableFactory.aliases entry mapping the
service name to the class name, and a factories entry mapping the class
name to Laminas\ServiceManager\Factory\InvokableFactory.This means that you can use your existing invokables configuration from
version 2 in version 3. However, we recommend starting to update your
configuration to remove invokables entries in favor of factories (and aliases,
if needed).
Invokables and plugin managers
If you are creating a plugin manager and in-lining invokables into the class definition, you will need to make some changes.
$invokableClasseswill need to become$factoriesentries, and you will potentially need to add$aliasesentries.As an example, consider the following, from laminas-math v2.x:
class AdapterPluginManager extends AbstractPluginManager { protected $invokableClasses = [ 'bcmath' => Adapter\Bcmath::class, 'gmp' => Adapter\Gmp::class, ]; }Because we no longer define an
$invokableClassesproperty, for v3.x, this now becomes:use Laminas\ServiceManager\Factory\InvokableFactory; class AdapterPluginManager extends AbstractPluginManager { protected $aliases = [ 'bcmath' => Adapter\Bcmath::class, 'gmp' => Adapter\Gmp::class, ]; protected $factories = [ Adapter\BcMath::class => InvokableFactory::class, Adapter\Gmp::class => InvokableFactory::class, ]; }
In v2, if you wanted to create a lazy service, you needed to take the following steps:
config service, with a lazy_services key that contained
the configuration necessary for the LazyServiceFactory.LazyServiceFactoryFactory as a factory for the
LazyServiceFactoryLazyServiceFactory as a delegator factory for your service.As an example:
use Laminas\ServiceManager\Proxy\LazyServiceFactoryFactory;
$config = [
'lazy_services' => [
'class_map' => [
'MyClass' => 'MyClass',
],
'proxies_namespace' => 'TestAssetProxy',
'proxies_target_dir' => 'data/proxies/',
'write_proxy_files' => true,
],
];
return [
'services' => [
'config' => $config,
],
'invokables' => [
'MyClass' => 'MyClass',
],
'factories' => [
'LazyServiceFactory' => LazyServiceFactoryFactory::class,
],
'delegators' => [
'MyClass' => [
'LazyServiceFactory',
],
],
];
This was done in part because lazy services were introduced later in the v2 cycle, and not fully integrated in order to retain the API.
In order to reduce the number of dependencies and steps necessary to configure lazy services, the following changes were made for v3:
config service.LazyServiceFactory delegator factory, based on the configuration present.The above example becomes the following in v3:
use Laminas\ServiceManager\Factory\InvokableFactory;
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
return [
'factories' => [
'MyClass' => InvokableFactory::class,
],
'delegators' => [
'MyClass' => [
LazyServiceFactory::class,
],
],
'lazy_services' => [
'class_map' => [
'MyClass' => 'MyClass',
],
'proxies_namespace' => 'TestAssetProxy',
'proxies_target_dir' => 'data/proxies/',
'write_proxy_files' => true,
],
];
Additionally, assuming you have configured lazy services initially with the
proxy namespace, target directory, etc., you can map lazy services using the new
method mapLazyService($name, $class):
$container->mapLazyService('MyClass', 'MyClass');
// or, more simply:
$container->mapLazyService('MyClass');
The ServiceLocatorInterface now extends the
container-interop
interface ContainerInterface, which defines the same get() and has()
methods as were previously defined.
Additionally, it adds a new method:
public function build($name, array $options = null)
This method is defined to always return a new instance of the requested
service, and to allow using the provided $options when creating the instance.
Laminas\ServiceManager\ServiceManager remains the primary interface with which
developers will interact. It has the following changes in v3:
configure(), which allows configuring all instance
generation capabilities (aliases, factories, abstract factories, etc.) at
once.Laminas\ServiceManager\Config instance.
Config can be used, but is not needed.build(), for creating discrete service instances.The following methods are removed in v3:
setShareByDefault()/shareByDefault(); this can be passed during
instantiation or via configure().setThrowExceptionInCreate()/getThrowExceptionInCreate(); exceptions are
always thrown when errors are encountered during service instance creation.setRetrieveFromPeeringManagerFirst()/retrieveFromPeeringManagerFirst();
peering is no longer supported.The constructor now accepts an array of service configuration, not a
Laminas\ServiceManager\Config instance.
build() for discrete instancesThe new method build() acts as a factory method for configured services, and
will always return a new instance, never a shared one.
Additionally, it provides factory capabilities; you may pass an additional,
optional argument, $options, which should be an array of additional options a
factory may use to create a new instance. This is primarily of interest when
creating plugin managers (more on plugin managers below), which may pass that
information on in order to create discrete plugin instances with specific state.
As examples:
use Laminas\Validator\Between;
$between = $container->build(Between::class, [
'min' => 5,
'max' => 10,
'inclusive' => true,
]);
$alsoBetween = $container->build(Between::class, [
'min' => 0,
'max' => 100,
'inclusive' => false,
]);
The above two validators would be different instances, with their own configuration.
Internally, the ServiceManager now only uses the new factory interfaces
defined in the Laminas\ServiceManager\Factory namespace. These replace the
interfaces defined in version 2, and define completely new signatures.
For migration purposes, all original interfaces were retained, and now inherit from the new interfaces. This provides a migration path; you can add the methods defined in the new interfaces to your existing factories targeting v2, and safely upgrade. (Typically, you will then have the version 2 methods proxy to those defined in version 3.)
| Version 2 Interface | Version 3 Interface |
|---|---|
Laminas\ServiceManager\AbstractFactoryInterface |
Laminas\ServiceManager\Factory\AbstractFactoryInterface |
Laminas\ServiceManager\DelegatorFactoryInterface |
Laminas\ServiceManager\Factory\DelegatorFactoryInterface |
Laminas\ServiceManager\FactoryInterface |
Laminas\ServiceManager\Factory\FactoryInterface |
The version 2 interfaces now extend those in version 3, but are marked deprecated. You can continue to use them, but will be required to update your code to use the new interfaces in the future.
The previous signature of the AbstractFactoryInterface was:
interface AbstractFactoryInterface
{
/**
* Determine if we can create a service with name
*
* [@param](https://github.com/param) ServiceLocatorInterface $serviceLocator
* [@param](https://github.com/param) $name
* [@param](https://github.com/param) $requestedName
* [@return](https://github.com/return) bool
*/
public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName);
/**
* Create service with name
*
* [@param](https://github.com/param) ServiceLocatorInterface $serviceLocator
* [@param](https://github.com/param) $name
* [@param](https://github.com/param) $requestedName
* [@return](https://github.com/return) mixed
*/
public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName);
}
The new signature is:
interface AbstractFactoryInterface extends FactoryInterface
{
/**
* Does the factory have a way to create an instance for the service?
*
* [@param](https://github.com/param) ContainerInterface $container
* [@param](https://github.com/param) string $requestedName
* [@return](https://github.com/return) bool
*/
public function canCreate(ContainerInterface $container, $requestedName);
}
Note that it now extends the FactoryInterface (detailed below), and thus the
factory logic has the same signature.
In v2, the abstract factory defined the method canCreateServiceWithName(); in
v3, this is renamed to canCreate(), and the method also now receives only two
arguments, the container and the requested service name.
To prepare your version 2 implementation to work upon upgrade to version 3:
canCreate() and __invoke() as defined in version 3.canCreateServiceWithName() method to proxy to
canCreate()createServiceWithName() method to proxy to
__invoke()As an example, given the following implementation from version 2:
use Laminas\ServiceManager\AbstractFactoryInterface;
use Laminas\ServiceManager\ServiceLocatorInterface;
class LenientAbstractFactory implements AbstractFactoryInterface
{
public function canCreateServiceWithName(ServiceLocatorInterface $services, $name, $requestedName)
{
return class_exists($requestedName);
}
public function createServiceWithName(ServiceLocatorInterface $services, $name, $requestedName)
{
return new $requestedName();
}
}
To update this for version 3 compatibility, you will add the methods
canCreate() and __invoke(), move the code from the existing methods into
them, and update the existing methods to proxy to the new methods:
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\AbstractFactoryInterface;
use Laminas\ServiceManager\ServiceLocatorInterface;
class LenientAbstractFactory implements AbstractFactoryInterface
{
public function canCreate(ContainerInterface $container, $requestedName)
{
return class_exists($requestedName);
}
public function canCreateServiceWithName(ServiceLocatorInterface $services, $name, $requestedName)
{
return $this->canCreate($services, $requestedName);
}
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new $requestedName();
}
public function createServiceWithName(ServiceLocatorInterface $services, $name, $requestedName)
{
return $this($services, $requestedName);
}
}
After you have upgraded to version 3, you can take the following steps to remove the migration artifacts:
canCreateServiceWithName() and createServiceWithName() methods
from your implementation.From our example above, we would update the class to read as follows:
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\Factory\AbstractFactoryInterface; // <-- note the change!
class LenientAbstractFactory implements AbstractFactoryInterface
{
public function canCreate(ContainerInterface $container, $requestedName)
{
return class_exists($requestedName);
}
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new $requestedName();
}
}
The previous signature of the DelegatorFactoryInterface was:
interface DelegatorFactoryInterface
{
/**
* A factory that creates delegates of a given service
*
* [@param](https://github.com/param) ServiceLocatorInterface $serviceLocator the service locator which requested the service
* [@param](https://github.com/param) string $name the normalized service name
* [@param](https://github.com/param) string $requestedName the requested service name
* [@param](https://github.com/param) callable $callback the callback that is responsible for creating the service
*
* [@return](https://github.com/return) mixed
*/
public function createDelegatorWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $callback);
}
The new signature is:
interface DelegatorFactoryInterface
{
/**
* A factory that creates delegates of a given service
*
* [@param](https://github.com/param) ContainerInterface $container
* [@param](https://github.com/param) string $name
* [@param](https://github.com/param) callable $callback
* [@param](https://github.com/param) null|array $options
* [@return](https://github.com/return) object
*/
public function __invoke(ContainerInterface $container, $name, callable $callback, array $options = null);
}
Note that the $name and $requestedName arguments are now merged into a
single $name argument, and that the factory now allows passing additional
options to use (typically as passed via build()).
To prepare your existing delegator factories for version 3, take the following steps:
__invoke() method in your existing factory, copying the code
from your existing createDelegatorWithName() method into it.createDelegatorWithName() method to proxy to the new method.Consider the following delegator factory that works for version 2:
use Laminas\ServiceManager\DelegatorFactoryInterface;
use Laminas\ServiceManager\ServiceLocatorInterface;
class ObserverAttachmentDelegator implements DelegatorFactoryInterface
{
public function createDelegatorWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $callback)
{
$subject = $callback();
$subject->attach($serviceLocator->get(Observer::class);
return $subject;
}
}
To prepare this for version 3, we'd implement the __invoke() signature from
version 3, and modify createDelegatorWithName() to proxy to it:
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\DelegatorFactoryInterface;
use Laminas\ServiceManager\ServiceLocatorInterface;
class ObserverAttachmentDelegator implements DelegatorFactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, callable $callback, array $options = null)
{
$subject = $callback();
$subject->attach($container->get(Observer::class);
return $subject;
}
public function createDelegatorWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $callback)
{
return $this($serviceLocator, $requestedName, $callback);
}
}
After you have upgraded to version 3, you can take the following steps to remove the migration artifacts:
createDelegatorWithName() method from your implementation.From our example above, we would update the class to read as follows:
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\Factory\DelegatorFactoryInterface; // <-- note the change!
class ObserverAttachmentDelegator implements DelegatorFactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, callable $callback, array $options = null)
{
$subject = $callback();
$subject->attach($container->get(Observer::class);
return $subject;
}
}
The previous signature of the FactoryInterface was:
interface FactoryInterface
{
/**
* Create service
*
* [@param](https://github.com/param) ServiceLocatorInterface $serviceLocator
* [@return](https://github.com/return) mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator);
}
The new signature is:
interface FactoryInterface
{
/**
* Create an object
*
* [@param](https://github.com/param) ContainerInterface $container
* [@param](https://github.com/param) string $requestedName
* [@param](https://github.com/param) null|array $options
* [@return](https://github.com/return) object
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null);
}
Note that the factory now accepts an additional required argument,
$requestedName; v2 already passed this argument, but it was not specified in
the interface itself. Additionally, a third optional argument, $options,
allows you to provide $options to the ServiceManager::build() method;
factories can then take these into account when creating an instance.
Because factories now can expect to receive the service name, they may be re-used for multiple services, largely replacing abstract factories in version 3.
To prepare your existing factories for version 3, take the following steps:
__invoke() method in your existing factory, copying the code
from your existing createService() method into it.createService() method to proxy to the new method.Consider the following factory that works for version 2:
use Laminas\ServiceManager\FactoryInterface;
use Laminas\ServiceManager\ServiceLocatorInterface;
class FooFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $services)
{
return new Foo($services->get(Bar::class));
}
}
To prepare this for version 3, we'd implement the __invoke() signature from
version 3, and modify createService() to proxy to it:
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\FactoryInterface;
use Laminas\ServiceManager\ServiceLocatorInterface;
class FooFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new Foo($container->get(Bar::class));
}
public function createService(ServiceLocatorInter...
How can I help you explore Laravel packages today?