Skip to content


Working with the Service Manager in Zend Framework 2

The Service Manager in Zend Framework 2 creates and stores instances of your objects: an instance manager. The Service Manager can assist developers in decoupling object dependencies which arguably results in cleaner more maintainable code.

It may not be immediately clear how to interact with the service manager. I will attempt to walk you through the basic capabilities of the Service Manager and hopefully fill some in of the gaps left by the official documentation.

Use Case: API Service

Let’s assume you are tasked with writing a ZF2 module that will interact with a web service. To do this you will likely use ZF2′s Http\Client, further you have a requirement that the connection must be made via SSL so you decide to use the Curl Adapter.

Example project structure:
config/
    module.config.php
src/
    MyNamspace/
        Api/
            AbstractSomeApiResponseFactory.php
            ConfigAwareInterface.php
            SomeApi.php
            SomeApiResponse.php
            SomeApiService.php
Module.php

A typical, simplified, solution may look something like this:

<?php
class SomeApiService
{
    protected $httpClient;
 
    public function __construct()
    {
        $this->httpClient = new Zend\Http\Client();
        $adapter = new Zend\Http\Client\Adapter\Curl; 
        $adapter->setOptions(array(CURLOPT_FOLLOWLOCATION => true));
        $client->setAdapter($adapter);
 
    }
    ...
    public function send(SomeApi $api)
    {
 
        $this->httpClient->setRawBody($api); //calls $api::__toString()
        $response = $this->httpClient->send();
 
        return new Response($response->getBody());
    }
    ...
}
 
//Create an instance of our SomeApiService class
$service = new SomeApiService();
$response = $service->send($api);

In this case the class SomeApiService has a dependency on Zend\Http\Client. Any changes to the configuration of the Client class, for let’s say proxy support, would require modifying the SomeApiService class.

Factories

Expanding on the example above, we can utilize the factories feature of the Service Manager to handle the injection of a Client class into our SomeApiService class.

class SomeApiService
{
	...
	setHttpClient(\Zend\Http\Client $httpClient)
	{
		$this->httpClient = $httpClient;
		return $this;
	}
	...
}

In the Module class we will return a service config array that contains an entry in the factories key for our SomeApiService:

class Module implements AutoloaderProviderInterface
{
    public function getServiceConfig()
    {
        return array(
            'factories' => array(
                'SomeApiService' => function($sm) {
                    $httpClient = new \Zend\Http\Client;
                    $httpClient->setAdapter(new \Zend\Http\Client\Adapter\Curl);
 
                    $client = new  MyNamespace\Api\SomeApiService;
                    $client->setHttpClient($httpClient);
 
                    return $client;
                }
            )
        );
    }
    ...
}

To utilize our SomeApiService class we will request it from the service manager which will handle the instantiation, if necessary, and return the instance. An added benefit of the service manager is that unless specified it will only create and manage one instance of your object.

$service = $serviceManager->get('SomeApiService');
$response = $service->send($api);

Invokables

Now that we have defined our SomeApiService, we also need to define a SomeApi class. This class will hold the API request details, there may be several such classes. For the sake of this example we will utilize the Service Manager to manage these objects for us which we will do via invokables. With invokables we define a key, that we will use to interact with the Service Manager, and a path to the FQCN (Fully qualified class name) to instantiate. Our new configuration will look like this:

<?php
class Module implements AutoloaderProviderInterface
{
    public function getServiceConfig()
    {
        return array(
            'factories' => array( ... ),
            'invokables' => array(
                'SomeApi' => 'MyNamespace\Api\SomeApi'
            )
        );
    }
    ...
} 
 
//we can then get an instance of our SomeApi class like this:
$instance = $serviceManager->get('SomeApi');

Shared

That leads us to our next configuration item. There may be times when we want the service manager to ALWAYS create new instances of our objects when we call the ServiceManager::get method. Luckily, the architects anticipated this and allow us to configure how objects are “shared”. To continue our example above, let’s assume we now have a SomeApi class that we will use to interact with our SomeApiService. This SomeApi class will hold the details of our API request and we want to ensure we receive a new instance each time.

<?php
class Module implements AutoloaderProviderInterface
{
    public function getServiceConfig()
    {
        return array(
            'factories' => array( ... ),
            'invokables' => array(
                'SomeApi' => 'MyNamespace\Api\SomeApi'
            ),
            'shared' => array(
                'SomeApi' => false
            )
        );
    }
    ...
}

Initializers

Let’s expand this example and say we have 10 SomeApi classes and each of these classes need to receive a configuration object. We have a few options, we could use Factories, Abstract Factories or Intializers. Initializers are a way to inject objects into your classes by utilizing interfaces (would also work with a base class – although I would encourage you to work with interfaces).

Create the interface, this might look something like:

<?php
namespace MyNamespace\Api;
 
interface ConfigAwareInterface
{
    public function setConfig($config);
}

Now implement this interface with your SomeApi class.

<?php
class SomeApi implements ConfigAwareInterface
{
    protected $config;
 
    public function setConfig($config)
    {
        $this->config = $config;
        return $this;
    }
 
    public function getConfig()
    {
        return $this->config;
    }
    ...
}

We can now add a new configuration item to our Module class.

<?php
 
class Module implements AutoloaderProviderInterface
{
    public function getServiceConfig()
    {
        return array(
            'factories' => array( ... ),
            'invokables' => array( ... ),
            'shared' => array( ... ),
            'initializers' => array(
                function($instance, $sm) {
                    if($instance instanceof \MyNamespace\Api\ConfigAwareInterface) {
                        $config = $sm->get('application')->getConfig();
                        $apiConfig = isset($config['api-config']) ? $config['api-config'] : array();
                        $instance->setConfig($apiConfig);
                    }
                }
            )            
        );
    }
    ...
}

Now, when we pull an instance of our SomeApi class from the service manager it will contain our configuration.

$someApi = $serviceManager->get('SomeApi');
var_dump($someApi->getConfig());

Abstract Factories

Our SomeApiService will return an instance of SomeApiResponse. We will not know what response object to create until run-time, enter Abstract Factories. The service manager will attempt to create an object via factory and invokable and then search through any abstract factories that might be defined. If the abstract factory returns true to the canCreateServiceWithName method the service manager will call the classes createServiceWithName method. Although the below example is contrived I hope it will help represent how you might use abstract factories in your own Module.

Create your abstract factory class. This class must implement the Zend\ServiceManager\AbstractFactoryInterface.

class AbstractSomeApiResponseFactory implements AbstractFactoryInterface
{
    public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
    {
        if(class_exists($name)) {
            return true;
        }
 
        return false;
    }
 
    public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
    {
        return new $name;
    }
}

Configuration addition to the Module class:

<?php
class Module implements AutoloaderProviderInterface
{
    public function getServiceConfig()
    {
        return array(
            'factories' => array( ... ),
            'invokables' => array( ... ),
            'shared' => array( ... ),
            'initializers' => array( ... ),
            'abstract_factories' => array(
                'AbstractApiResponseFactory' => 'MyNamespce\Api\AbstractApiResponseFactory'
            ),            
        );
    }
    ...
}

Now our updated Client. This will use the FQCN from the $api and append ‘Response’ to the classname.

<?php
class SomeApiService implements Zend\ServiceManager\ServiceManagerAwareInterface
{
    ...
    public function send(SomeApi $api)
    {
 
        $this->httpClient->setRawBody($api); //calls $api::__toString()
        $response = $this->httpClient->send();
 
        $apiResponse = $this->serviceManager->get(get_class($api) . "Response");
        ... //error checking
 
        return $apiResponse->setData($response->getBody());
    }
    ...
}

Accessing the Service Manager instance

The service manager will automatically get injected into your controllers. See an example below.

<?php
 
namespace SomeApi\Controller;
 
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
 
class MyAwesomeController extends AbstractActionController
{
    public function indexAction()
    {
        $serviceManager = $this->getServiceLocator();
        $apiServiceResult = $serviceManager->get('SomeApiService')->getSomethingUseful();
        return new ViewModel(array('result' => $apiServiceResult));
    }
}

Conclusion

Hopefully this article has helped you better understand how to interact with the Service Manager in Zend Framework 2. There are some features of the Service Manager that I did not cover, such as Peering Services, that I will save for another time. If you would like to see a real example of an API consumer module utilizing most of the Service Manager features covered above you can review the SpeckAuthnet Module. The SpeckAuthnet module interacts with the Authorize.net payment gateway and was written for any Zend Framework 2 application to utilize.

Have feedback? Something not entirely clear? Please post a comment and I will happily get back to you! You can also find me in IRC as ‘srhoades’ in zftalk, zftalk.dev and speckcommerce.

Further Reading

Below are some great articles for further reading about Service Locator and the Service Manager in Zend Framework 2.
http://blog.evan.pro/introduction-to-the-zend-framework-2-servicemanager
http://martinfowler.com/articles/injection.html#UsingAServiceLocator
http://packages.zendframework.com/docs/latest/manual/en/modules/zend.service-manager.quick-start.html
http://akrabat.com/zend-framework-2/zendservicemanager-configuration-keys/
http://juriansluiman.nl/en/article/120/using-zend-framework-service-managers-in-your-application

Alternative Reading

This article outlines some inherent issues with the Service Manager and offers a solution via Doctrine Proxies.
http://ocramius.github.com/blog/zf2-and-symfony-service-proxies-with-doctrine-proxies/

Posted in Zend Framework 2.

2 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Issac said

    Where are you calling:
    $someApi = $serviceManager->get(‘SomeApi’);
    var_dump($someApi->getConfig());

    When I try to call it I just get $serviceManager not found.

  2. Steve Rhoades said

    @Issac you need to make sure that you have an instance of the service manager, the service manager gets injected into your Controller instance. @see http://bit.ly/TE3OoA for an example of pulling the service manager from your controller actions.

    I have updated the article to include an example.

Some HTML is OK

(required)

(required, but never shared)

or, reply to this post via trackback.