📜 ⬆️ ⬇️

Zend Framework 2: Service Manager

Service Manager (SM, CM) in ZF2.


Service Manager is one of the key components of Zend Framework 2, which significantly simplifies the developer’s life by eliminating code duplication and routine operations of creating and configuring services, allowing them to be configured at the highest possible level. CM, by its nature, is a registry of services, whose main task is the creation and storage of services. It can be said CM is a very advanced version of the Zend Framework 1's Zend_Registry component.
CM implements the Service Locator pattern. In many parts of the application (for example, in AbstractActionController) you can find the function getServiceLocator (), which returns the class Zend \ ServiceManager \ ServiceManager. Such a mismatch between the method name and the return type is easily explained by the fact that getServiceLocator () returns an object that implements the ServiceLocatorInterface interface:

namespace Zend\ServiceManager; interface ServiceLocatorInterface { public function get($name); public function has($name); } 

Zend \ ServiceManager \ ServiceManager is as it is. This is done because the framework itself uses several other types of CM and, by the way, no one forbids us to use our own service manager in the application.

Services.


A service is a regular variable of absolutely arbitrary type (not necessarily an object, see the comparison with Zend_Registry):

 // IndexController::indexAction() $arrayService = array('a' => 'b'); $this->getServiceLocator()->setService('arrayService', $arrayService); $retrievedService = $this->getServiceLocator()->get('arrayService'); var_dump($retrievedService); exit; 

will output:
 array (
     a => 'b'
 )

CM configuration


Service management can be configured in four ways:
1. Through the config of the module (module.config.php):
 return array( 'service_manager' => array( 'invokables' => array(), 'services' => array(), 'factories' => array(), 'abstract_factories' => array(), 'initializators' => array(), 'delegators' => array(), 'shared' => array(), 'aliases' => array() ) ); 

2. defining the getServiceConfig () method (for the beauty of the code, you can also add the Zend \ ModuleManager \ Feature \ ServiceProviderInterface interface), which will return an array or Traversable in the format from clause 1;
')
3. creating a service by hand and inserting it into the CM:
 // IndexController::indexAction() $arrayService = array('a' => 'b'); $this->getServiceLocator()->setService('arrayService', $arrayService); 

4. having described the service in application.config.php in the format from item 1.

It must be remembered that the names of the services should be unique for the entire application (unless, of course, the goal is to override the existing service). During application initialization, Zend \ ModuleManager \ ModuleManager will merge all configs into one, erasing duplicate keys. A good practice is to add a namespace module to the name of the service. Or use the absolute name of the class of service.

Creation of services through SM.


Objects \ simple types

The easiest type. To create such a service, you simply need to manually create an object (array, string, resource, etc.) and transfer it to the CM:
 $myService = new MyService(); $serviceManager->setService('myService', $myService); 

either through the config:
 array( 'service_manager' => array( 'services' => array( 'myService' => new MyService() ) ) ); 

$ serviceManager-> setService ($ name, $ service) will put the object directly into the internal variable ServiceManager :: $ instances, which stores all initialized services. When referring to this type, the SM will not try to create it and give it away as it is.
Using this type, you can store arbitrary data that will be available throughout the application (as was the case with Zend_Registry).

Invokable

To create, you must transfer to the manager the full name of the target class. CM will create it using the new operator.

 // ServiceManager::createFromInvokable() protected function createFromInvokable($canonicalName, $requestedName) { $invokable = $this->invokableClasses[$canonicalName]; if (!class_exists($invokable)) { // cut } $instance = new $invokable; return $instance; } 

 $myService = new MyService(); $serviceManager->setInvokableClass('myService', $myService); 

either through the config:
 array( 'service_manager' => array( 'invokables' => array( 'myService' => 'MyService' ) ) ); 

Application: if you just need to create a class without direct dependencies through the CM.
In this case, the delegates and instantiators will still be called and will implement dependencies if necessary.

Factories.

Services can be created and configured in a factory. Factories can be of two types: closure and a class that implements Zend \ ServiceManager \ FactoryInterface.

Implementation through closure:
 array( 'service_manager' => array( 'factories' => array( 'myService' => function (ServiceLocator $serviceManager) { return new MyService(); } ) ) ); 

This approach, although it reduces the number of lines of code, but keeps a pitfall in it: closures cannot be correctly serialized into a string.
A real-life example: if you enable caching of the combined config in application.config.php, then the next time you start the application will not be able to compile it and will fall with the error: Fatal error: Call to undefined method Closure :: __ set_state () in / data / cache / module- config-cache..php

To avoid such problems, services should be created through factory classes that implement Zend \ ServiceManager \ FactoryInterface:
 // Appliction/Service/ConfigProviderFactory.php class ConfigProviderFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { return new ConfigProvider($serviceLocator->get('Configuration')); } } 

and spelled out in the config:
 array( 'service_manager' => array( 'factories' => array( 'ConfigProvider' => 'ConfigEx\Service\ConfigProviderFactory', ) ) ); 

You can also transfer the factory object or class name directly to the CM:

 $serviceManager->setFactory('ConfigProvider', new ConfigEx\Service\ConfigProviderFactory()); 

Application: if you need to create a service that depends on other services or needs to be configured.

Abstract Factories

AF is the last attempt by the CM to create the requested service. If the SM cannot find the service, it will begin to poll all registered AFs (call the canCreateServiceWithName () method). If the AF returns an affirmative answer, the CM will call the createServiceWithName () method from the factory, delegating the creation of the service to the AF logic.

Transfer AF directly:
 $serviceManager->addAbstractFactory(new AbstractFactory); 

addAbstractFactory accepts an object, not a class!
Configuration via config:
 array( 'service_manager' => array( 'abstract_factories' => array( 'DbTableAbstractFactory' => 'Application\Service\'DbTableAbstractFactory' ) ), 

And factory class:
 class DbTableAbstractFactory implements \Zend\ServiceManager\AbstractFactoryInterface { public function canCreateServiceWithName(\Zend\ServiceManager\ServiceLocatorInterface $serviceLocator, $name, $requestedName) { return preg_match('/Table$/', $name); } public function createServiceWithName(\Zend\ServiceManager\ServiceLocatorInterface $serviceLocator, $name, $requestedName) { $table = new $name($serviceLocator->get('DbAdapter')); } } 

Then, you can ask the SM to create us 2 services:
 $serviceManager->get('UserTable'); $serviceManager->get('PostTable'); 

As a result, there will be 2 objects that have not been described in any of the types of services.
This is a very handy thing. But in my opinion, this behavior is not very predictable for other developers, so you need to use it wisely. Who would like to spend a lot of time on debag magic that creates objects from nothing?

Aliases


These are just aliases for other services.
 array( 'service_manager' => array( 'aliases' => array( 'myservice' => 'MyService' ) ) ); $serviceLocator->get('myservice') === $serviceLocator->get('MyService'); // true 

And now let's move on to other snacks.

Initializers


These are not services, but features of the CM itself. Allow for additional service initialization after the object has been created. They can be used to implement Interface Injection.
So, after the SM has created a new object, it enumerates all registered initializers, passing them the object for the last configuration step.

Registered in a similar way, like factories:
Through closure:
 array( 'service_manager' => array( 'initializers' => array( 'DbAdapterAwareInterface' => function ($instance, ServiceLocator $serviceLocator) { if ($instance instanceof DbAdapterAwareInterface) { $instance->setDbAdapter($serviceLocator->get('DbAdapter')); } } ) ) ); 

Through the class:
 class DbAdapterAwareInterface implements \Zend\ServiceManager\InitializerInterface { public function initialize($instance, \Zend\ServiceManager\ServiceLocatorInterface $serviceLocator) { if ($instance instanceof DbAdapterAwareInterface) { $instance->setDbAdapter($serviceLocator->get('DbAdapter')); } } } array( 'service_manager' => array( 'initializers' => array( 'DbAdapterAwareInterface' => 'DbAdapterAwareInterface' ) ) ); 

In this example, Interface Injection is implemented. If $ instance of type DbAdapterAwareInterface, then the initializer will pass the database adapter object.

Application: Interface Injection, object tuning.
It is important to know that the CM will cause all initializers for each object created, which can lead to a loss of performance.

Delegates.

Delegators are like initializers, the only difference is that they will be called for a specific service, and not for everyone.

Check in:
 array( 'service_manager' => array( 'delegators' => array( 'Router' => array( 'AnnotatedRouter\Delegator\RouterDelegatorFactory' ) ) ) ); 

And implementation:
 class RouterDelegatorFactory implements DelegatorFactoryInterface { public function createDelegatorWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $callback) { //   ,     ,    ,  $callback  . $service = $callback(); //   ,   $service->doSomeCoolStuff(); // ,     //    return $service; } } 

In this example, the delegator RouterDelegatorFactory is applied only to the Route service.

Application: additional configuration of the object, useful for donation of services from third-party modules. For example, in my module for routing through annotations, I used a delegator to add routes to a standard router. There was an option to register the subscriber EVENT_ROUTE in Module.php with a priority higher than that of the standard listener. But it somehow looks dirty ...

Shared services.


By default, the SM creates only one instance object, with each subsequent access, the same object will be returned (such is the singleton). To disable this behavior globally, you need to call the setShareByDefault (false) method. You can also disable this behavior for certain services using the config:
 array( 'service_manager' => array( 'shared' => array( 'MyService' => false ) ) ); $a = $serviceManager->get('MyService'); $b = $serviceManager->get('MyService'); spl_object_hash($a) === spl_object_hash($b); // false 

Source: https://habr.com/ru/post/241471/


All Articles