Zend Framework 2: Service Manager

Service Manager (SM, CM) in ZF2.


Service Manager is a key component of Zend Framework 2, which greatly simplifies the life of developers having code duplication and routine operations for the creation and configuration of services, allowing them to be configured at the highest level. CM, by nature, is a service registry, whose main task — the creation and storage services. You can say CM is a very advanced version of the component Zend_Registry Zend Framework 1.
CM implements the Service Locator pattern. In many parts of the application (for example, in the AbstractActionController) can meet the function getServiceLocator () that returns the class Zend\ServiceManager\ServiceManager. This mismatch of method name and return type are easily explained by the fact that getServiceLocator() returns an object that implements the interface ServiceLocatorInterface:

the
namespace Zend\ServiceManager;

interface ServiceLocatorInterface
{
public function get($name);
public function has($name);
}

Zend\ServiceManager\ServiceManager as is so. This is because in the framework uses several other types of CM and by the way, nobody forbids us to use your own service Manager in the application.

Services.


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

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

output is:
the
array (
a => 'b'
)

Configuration, SEE


To configure the service managet is possible in four ways:
1. Through the config module (module.config.php):
the
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 method getServiceConfig() (for beauty code, you can still add the interface Zend\ModuleManager\Feature\ServiceProviderInterface), which will return an array or Traversable format from paragraph 1;

3. creating the service by hand and inserting it in CM:
the
// in IndexController::indexAction()
$arrayService = array('a' => 'b');
$this->getServiceLocator()->setService('arrayService', $arrayService);

4. describing the service application.config.php format of p. 1.

You need to remember that service names must be unique for the whole application (unless, of course, is not intended to override an existing service). During application initialization, Zend\ModuleManager\ModuleManager will merge all configs into one, overwriting duplicate keys. It is good practice to add the namespace of the module to the name service. Either use the absolute class name of the service.

Creating the service via CM.


Objects\simple types

The most basic type. To create such a service, you just need to manually create the object (array, string, resource, etc.) and pass it to CM:
the
$myService = new MyService();
$serviceManager- > setService(‘myService’, $myService);

either via configuration:
the
array(
'service_manager' => array(
'services' => array(
'myService' = > new MyService()
)
)
);

$serviceManager- > setService($name, $service) will put the object directly in the internal variable ServiceManager::$instances, which keeps all initialized services. When referring to this type, CM will not attempt to create it and give it 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

It requires that you tell the Manager the full name of the target class. CM will create it using operator new.

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

the
$myService = new MyService();
$serviceManager->setInvokableClass(‘myService’, $myService);

either via configuration:
the
array(
'service_manager' => array(
'invokables' => array(
'myService' => 'MyService'
)

);

Application: if you just need to create a class without a direct dependency via CM.
Delegatory and instantiators will still be called and will introduce dependencies when required.

Factory.

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

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

This approach though reduces the number of lines of code, but keeps caveat: circuit may not be correctly serializerbean in line.
Real example: if application.config.php enable the cache the merged config, then when you next start the application will fail to compile and will fall with an error: Fatal error: Call to undefined method Closure::__set_state() in /data/cache/module-config-cache..php

To avoid such problems, services need to create using classes-factories that implement Zend\ServiceManager\FactoryInterface:
the
// Appliction/Service/ConfigProviderFactory.php
ConfigProviderFactory class implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new ConfigProvider($serviceLocator- > get('Configuration'));
}
}

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

Also, the factory object or class name can be passed directly in CM:

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

Application: if you want to create a service that depends on other services or needs adjustment.

Abstract Factory

AF is the latest attempt by SM to create the requested service. If CM cannot find a service, it will poll all registered AF (to call the method canCreateServiceWithName()). If AF will return an affirmative response, the CM will invoke the method createServiceWithName() from the factory, delegating the creation of the service logic of AF.

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

addAbstractFactory takes an object, not a class!
Setting via configuration:
the
array(
'service_manager' => array(
'abstract_factories' => array(
'DbTableAbstractFactory' => 'Application\Service\‘DbTableAbstractFactory'
)
),

And the factory class:
the
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 CM to give us 2 services:
the
$serviceManager- > get('UserTable');
$serviceManager- > get('PostTable');

As a result, we will have 2 object 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 use it wisely. Who would like to spend a lot of time to debug magic that creates objects out of nothing?

Aliases


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

And now for other Goodies.

the Initializers.


It is not the services and features of the CM. A provisioning service after the object has been created. With their help you can implement Interface Injection.
So, after the CM has created a new object, it iterates through all the registered initializers, passing the object to the last step of the configuration.

Recorded the same way as the factory:
Through the circuit:
the
array(
'service_manager' => array(
'initializers' => array(
'DbAdapterAwareInterface' = > function ($instance, ServiceLocator $serviceLocator) {
if ($instance instanceof DbAdapterAwareInterface) {
$instance->setDbAdapter($serviceLocator- > get('DbAdapter'));

}
)
)
);

Through the class:
the
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, the implemented Interface Injection. If $instance is of type DbAdapterAwareInterface, the initializer will give the object database adapter.

Application: Interface Injection, ponastroila object.
It is important to know that CM will be for each created object will call all initializers, which can lead to lost productivity.

Delegator.

Delegatory similar to the initializers, the only difference is that they will be called for a specific service and not for everyone.

Registration:
the
array(
'service_manager' => array(
'delegators' => array(
'Router' => array(
'AnnotatedRouter\Delegator\RouterDelegatorFactory'
)
)
)
);

And implementation:
the
class implements RouterDelegatorFactory DelegatorFactoryInterface
{
public function createDelegatorWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $callback)
{
// at this stage, the target service has not yet been created, they are created after the $callback will be executed.
$service = $callback();
// service has already been created, initializers worked

$service->doSomeCoolStuff(); // what created delegator
// other initialization code
return $service;
}
}

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

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

Shared services.


By default, CM creates only one instance of the object, each subsequent treatment will return the same object (a singleton here). To prevent this behavior this behavior globally, you need to call the method setShareByDefault(false). You can also disable this behavior for certain services, using the config:
the
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
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Fresh hay from the cow, or 3000 icons submitted!

Knowledge base. Part 2. Freebase: make requests to the Google Knowledge Graph

Group edit the resources (documents) using MIGXDB