Domain layer and ZendFramework2 using Zend\ServiceManager

UPDATE: ctrllib has been implemented in CtrlSkeleton, providing easy database management for Doctrine entities and implemented ViewHelpers for Twitter Bootstrap.

In the previous article I gave a brief overview of how the current implementation of my Domain Layer looks. To be able to use these classes inside ZF2 controllers I used a custom ServiceManager Factory. This custom ServiceManager will take care of Dependency Injection and register configured DomainServices

First things first

before you go off and start creating ServiceManager factories, make sure you need them. By default you can just configure the service manager to instantiate any class. By default the ServiceManager will inject a ServiceLocatorInterface (ServiceManager extends ServiceLocatorInterface, and is thus just a concrete implementation of a ServiceLocator) into all classes it creates and extend the Zend\ServiceManager\ServiceLocatorAwareInterface.

<?php
array(
    'service_manager' => array(
        'invokables' => array(
            'MyClass' => 'MySpace\MyDomain\MyServiceLocatorAwareClass',
        ),
    ),
);

Since Controllers are ServiceLocatorAware they have access to the ServiceManager and you can do the following:

<?php

namespace MySpace;

class controller extends Zend\Mvc\Controller\AbstractActionController
{
    public function indexAction()
    {
        $this->getServiceLocator()->get('MyClass');
    }
}

Note that the ServiceManager stores all instances it creates by lower case names, to you can not configure ‘MyClass’ and ‘myClass’. In the same respect the casing of the string passed to ServiceLocatorInterface::get() is also of no importance.

Creating a ServiceManager factory

What I want to do here is create a Factory that creates, configures and returns a service manager. This service manager will be able to load domain services I have registered in the config.

Creating the factory is straightforward as all you have to do is implement Zend\ServiceManager\FactoryInterface which defines 1 method:

<?php

namespace Ctrl\Service;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class DomainServiceLoaderFactory implements FactoryInterface
{
    /**
     * @param ServiceLocatorInterface|ServiceManager $serviceLocator
     * @return mixed
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        //create my serviceLoader
    }
}

In the DomainServiceLoaderFactory::createService() method We will fetch the application Configuration, and use it to configure the DomainServiceLoader:

As an example I will inject 3 objects into my services, if they require them. To determine if they require injection I used interfaces.

<?php

namespace Ctrl\Service;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\Config;
use Zend\EventManager\EventManagerAwareInterface;
use Ctrl\ServiceManager\EntityManagerAwareInterface;

class DomainServiceLoaderFactory implements FactoryInterface
{
    /**
     * @param ServiceLocatorInterface|ServiceManager $serviceLocator
     * @return mixed
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        //fetch key 'domain_services' from the application config
        $config = $serviceLocator->get('Configuration');
        $serviceConfig = new Config(
            isset($config['domain_services']) ? $config['domain_services'] : array()
        );

        $domainServiceFactory = new ServiceManager($serviceConfig);
        $serviceLocator->addPeeringServiceManager($domainServiceFactory);

        $domainServiceFactory->addInitializer(function ($instance) use ($serviceLocator) {
            if ($instance instanceof ServiceLocatorAwareInterface)
                $instance->setServiceLocator($serviceLocator->get('Zend\ServiceManager\ServiceLocatorInterface'));

            if ($instance instanceof EventManagerAwareInterface)
                $instance->setEventManager($serviceLocator->get('EventManager'));

            if ($instance instanceof EntityManagerAwareInterface)
                $instance->setEntityManager($serviceLocator->get('doctrine.entitymanager.orm_default'));
        });

        return $domainServiceFactory;
    }
}

We add our own ServiceManager as a peer to the main ServiceManager, this means that if the main ServiceManager can’t instantiate a service it will loop over its peers and try if any of them can create an instance for the given key. Each ServiceManager can have additional peers, and each ServiceManager can be configured to check its peers first, before trying to instantiate a service itself.

As you can see we configure our custom ServiceManager with the configuration key ‘domain_services’ so this is obviously where you have to configure you domain services :)

Using you ServiceManager

The DomainServiceLoaderFactory must first be registered so the main ServiceManager will load it up when it’s created. This is easily done in the config.
I’m gonna go ahead and also configure some DomainServices for usage:

<?php
namespace Ctrl;

return array(
    'service_manager' => array(
        'factories' => array(
            'DomainServiceLoader' => 'Ctrl\Service\DomainServiceLoaderFactory',
        ),
    ),
    'domain_services' => array(
        'invokables' => array(
            'UserService' => 'MySpace\Service\User',
        ),
    ),
);

And since MVC controllers are ServiceLocatorAware we can do the following:

<?php

namespace MyProject\Controller;

use Zend\Mvc\Controller\AbstractActionController as AbstractController;

class UserController extends AbstractController
{
    public function indexAction()
    {
        //again: all these strings passed are case insensitive!
        $userService = $this->getServiceLocator()->get('DomainServiceLoader')->get('User')
        return new ViewModel(array(
            'users' => $userService->getAll()
        ));
    }
}