Zend Framework 2 Service Manager

Published on January 17, 2013 by

The service manager in Zend Framework 2 is an implementation of the service locator design pattern. Hence, the terms service locator and service manager will be used interchangeably throughout this section. It is a central registry known as a service locator whose purpose is to retrieve services — or less formally — objects. The service manager knows how to lazily instantiate services when they are needed. This approach implements the inversion of control (IoC) technique such that object coupling is bound at runtime rather than compile time. It furthermore makes dependency injection simple.

In Zend Framework 1, the Zend_Registry component was the closest thing to a service manager. It was, as the name suggests, simply a registry consisting of key-value pairs. The service manager in Zend Framework 2 is much more than that as will be seen in this section.

The approaches discussed are usually defined in the modules’ configuration files in practice. These configuration files are often large PHP arrays, where the service manager would be configured under the service_manager key. To keep the examples simple and intuitive, an imperative approach is taken instead.

Please note that the keys used for the service manager should be unique across all modules. The service manager is commonly configured using a configuration within each module. These configuration files are merged by the module manager, hence why entries will be lost in the case of duplicates. The entries are stored in arrays within the Zend\ServiceManager\ServiceManager class, and thus two identical keys cannot exist within the same array. This is also why using an object oriented approach to configure the service manager will lead to the same conclusion. As a result, keys should at least be prepended by the module name, but using the entire namespace would be best practice. That being said, this is not the case for the examples to come; for the sake of simplicity, simpler keys are used.

Invokables

An invokable is a string that contains a fully qualified class name. Fully qualified means that namespaces are included. When the service manager receives a request for an invokable, it instantiates the fully qualified class and returns the object. Below are examples of how an invokable can be added to the service manager as well as how to use it.

// Add invokable to service manager
$serviceManager->setInvokableClass('user_mapper', 'User\Mapper\UserMapper');

In the above line of code, an invokable with the name user_mapper is added to the service manager instance. When this invokable is invoked, an instance of the User\Mapper\UserMapper class is returned. This is demonstrated below.

// Use invokable to retrieve an object
$userMapper = $serviceManager->get('user_mapper');

The service manager’s get method is a generic method for obtaining a service, independent of the way the service is fetched. Thus, it is used regardless of whether the service is fetched by using an invokable or factory, for instance. What happens if the key matches an invokable is simply that the invokable’s class is instantiated. When the invokable was added to the service manager, it specified the User\Mapper\User class. This value is looked up and used to instantiate the class, like shown below.

// $className contains 'User\Mapper\UserMapper'
return new $className();

Factories

Another approach is to use so-called factories, which are used to perform any setup or dependency injection for the requested object.

A factory can be either a PHP callable, an object or a fully qualified class name. If an object or a class name is provided, the class must implement Zend\ServiceManager\FactoryInterface, which defines a createService method. This method takes a ServiceLocatorInterface instance as its sole parameter, meaning that the service locator can be used to resolve dependencies needed to instantiate the object. The method is called by the service manager and should return the object that it instantiates. The same principles apply if a PHP callable is used instead.

Expanding the previous example, imagine that the UserMapper class depends upon a database adapter. By using dependency injection, a database adapter can be injected into its constructor. To accomplish this, a factory can be used instead of an invokable. An anonymous function — or closure — is specified, which is responsible for instantiating the requested service along with its dependencies.

$serviceManager->setFactory('user_mapper', function($serviceManager) {
	$dbAdapter = $serviceManager->get('Zend\Db\Adapter\Adapter');
	return new \User\Mapper\UserMapper($dbAdapter);
});

$userMapper = $serviceManager->get('user_mapper'); // Get user mapper

In this particular example, the service manager is used to obtain a database adapter so that it can be injected into the user mapper’s constructor. The user mapper is then returned and is ready for use.

Abstract Factories

An abstract factory is essentially a fallback in case a non-existing service is requested from the service manager. In such a scenario, the attached abstract factories will be queried to see if one of them can return the requested service.

With abstract factories, there are two possible approaches; a string representing a fully qualified class name or an object. In both cases, the class must implement Zend\ServiceManager\AbstractFactoryInterface. This interface specifies two methods which will be explained below.

public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName);

The method above does, as its name indicates, determine whether or not the abstract factory can create a service with the provided name. To determine this, the service that was requested is also provided. The method must return a boolean value.

public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName);

The above method is responsible for creating a service with a given name and returning it.

Initializers

Sometimes it is useful to be able to perform additional initialization tasks when an object is fetched from the service manager. Initializers specify a block of code to be executed when a service is retrieved from the service manager. The newly created instance is passed and it can thus be manipulated.

An initializer can be either a PHP callable, object or string containing a fully qualified class name. If an object or string is used, then the class must implement Zend\ServiceManager\InitializerInterface.

In a previous example, a mapper class was instantiated by using a factory method. It would quickly get tedious to write a factory for every mapper class. Instead, an anonymous function can be added as an initializer. The created object instance is passed to the function along with the service manager. This makes it possible to initialize a newly created object instance before it is returned from the call to the service manager’s get method.

$serviceManager->addInitializer(function($instance, $serviceManager) {
	if ($instance instanceof Zend\Db\Adapter\AdapterAwareInterface) {
$instance->setDbAdapter($serviceManager->get('Zend\Db\Adapter\Adapter'));
	}
});

In this example, it is assumed that mapper classes implement AdapterAwareInterface which specifies the setDbAdapter method. As a result, a database adapter can be set on the object if it implements this interface, and this is therefore done automatically for all objects of this type. This means that fetching the database adapter and assigning it to a mapper does not have to be done once for each mapper class. It is, however, important to note that the function above is only executed when a service is fetched from the service manager, and therefore the service manager should have knowledge on how to create an instance. In this case, a fully qualified class name would be sufficient; the initializer could then handle the rest of the initialization.

Another thing to note is that every service that is retrieved from the service manager will trigger all of the initializers. Since the service manager is heavily used in Zend Framework 2, this impacts performance. These “global” initializers should therefore be kept at a minimum. It is true that initializers can also be added to controllers, for instance, which means that they will be executed when a controller is instantiated. Either way, it is important to consider how frequently an initialization will actually be carried out, or the “hit rate” of the initialization. If the initialization should only happen for very few objects, it would probably not be worth it to suffer the performance impact. Should it, on the other hand, be done for the majority of the created objects, then it would indeed be a viable solution. In many cases, optimizations like these are not very important, so the main point is to avoid stuffing things into initializers for convenience.

Aliases

An alias points from one service to another, so when a service with an aliased name is requested, a different name should be used instead. For instance, if a service A is an alias for a service B, then a request for service A would actually return service B. Aliases can be of invokables, factories or other aliases, and hence they can be recursive. This means that a service A can be an alias for service B, which in turn can be an alias for service C, and so on. In this scenario, requesting service A from the service manager would return service C.

This gives a lot of flexibility, particularly because of Zend Framework 2’s modular structure. An application may be using Zend\Db\Adapter\Adapter for database connectivity and hence a module can refer directly to this key in the service manager. This may, however, not be the case, in which case a lot of references would have to be updated. To make matters even worse, refactoring strings can prove to be a difficult task, even for modern IDEs. If an alias was used instead, one could do like shown in the below example.

$serviceManager->setAlias('user_db_adapter', 'Zend\Db\Adapter\Adapter');
$dbAdapter = $serviceManager->get('user_db_adapter');

An alias is created such that a request for user_db_adapter actually retrieves Zend\Db\Adapter\Adapter, which could theoretically be another alias. The point to note here is that the actual database adapter has only been defined once in the module. A user module could thus refer to user_db_adapter whenever it needs to retrieve a database adapter. Should the database adapter be changed, the changes to be done in the modules are minimal. Module developers also do not have to make any assumptions of which database adapter people are using, because if an alias is used, this can easily be configured when the module is installed.

Shared Services

Services registered with a service manager can be either shared or not shared. In this context, the term shared refers to whether or not a new instance of a service is created on every request. Thus, if a service is shared, then an arbitrary number of requests for a given service will return the exact same object instance. Otherwise, a new instance will be created on every request. Services are shared by default.

Using the previous mapper example, this behavior can be demonstrated with the following examples.

$mapper1 = $serviceManager->get('user_mapper');
$mapper2 = $serviceManager->get('user_mapper');

// The following line outputs: bool(true)
var_dump((spl_object_hash($mapper1) === spl_object_hash($mapper2)));

This example uses the default behavior and shows how the exact instance that was initially returned and stored in $mapper1 is returned on the next request for the service. This means that $mapper1 and $mapper2 contain exactly the same content; a reference to the same User\Mapper\UserMapper object. This becomes clear when comparing the hash IDs stored in the two variables; the output shows that the comparison returns true, and thus the hashes are indeed identical.

The next example shows how a new instance is returned if the shared option is set to false.

$serviceManager->setShared('user_mapper', false);
$mapper3 = $serviceManager->get('user_mapper');

// The following line outputs: bool(false)
var_dump((spl_object_hash($mapper2) === spl_object_hash($mapper3)));

This time, the service manager request returns a new instance because the service is no longer shared across requests. Therefore, a newly created and unique object of User\Mapper\UserMapper is stored in $mapper3. As with the previous example, this becomes evident when comparing the object hash IDs.

Featured

Learn Zend Framework today!

Take an online course and become a ZF2 ninja!

Here is what you will learn:

  • Understand the theory of Zend Framework in details
  • How to implement an enterprise-ready architecture
  • Develop professional applications in Zend Framework
  • Proficiently work with databases in Zend Framework
  • ... and much more!
Zend Framework logo
Author avatar
Bo Andersen

About the Author

I am a back-end web developer with a passion for open source technologies. I have been a PHP developer for many years, and also have experience with Java and Spring Framework. I currently work full time as a lead developer. Apart from that, I also spend time on making online courses, so be sure to check those out!

8 comments on »Zend Framework 2 Service Manager«

  1. Nice conclusion,
    anyone has tested difference methods yet in case of performance ?

  2. Tre Giles

    Very nice article. Thank you for this.

    • Ram

      Thanks for the article. This is what I have been looking for a long time.

      • You are both very welcome. I am happy to help.

  3. Vanarie

    I’m trying to grasp ZF2 and I appreciate the explanations of the different calls on the SM. IMO, it seems overly complex, but I guess most frameworks are complex when it comes down to it. I’m assuming that all the code examples you give are dynamic SM injection instead of these being defined in the module.config.php file in the service_manager => array(…) block. Is that correct or am I not understanding this correctly?

    • Hello Vanarie,

      Thank you for your comment. It is true that Zend Framework 2 seems quite complex at first. Indeed it is complex, and its learning curve seems steeper than other frameworks. However, once you get the hang of it, it does begin to make sense. :-)

      You are absolutely correct that my examples could just as well have been written as nested arrays in module.config.php. I chose not to do this to keep things simpler and more intuitive, as per the below quote from my post.

      The approaches discussed are usually defined in the modules’ configuration files in practice. These configuration files are often large PHP arrays, where the service manager would be configured under the service_manager key. To keep the examples simple and intuitive, an imperative approach is taken instead.

      Thus, one would almost always configure the service manager within a module’s configuration file. Thank you for reading this article.

      Best regards,
      Bo Andersen

  4. Tadas

    Thank you! This article should be in zf2 manual.

    • You are very welcome. I am happy that you liked the article! :-)

Leave a Reply

Your e-mail address will not be published.