I recently dove into the Zend Framework 2 (ZF2) for a smaller website that booj is redesigning. Although the complexity of the site is minimal, I wanted to use the backend design as an opportunity to learn and strive for best practices. I wanted clean, uncoupled, reusable code, even if just for a few entities and forms.

One of the tricks to having uncoupled code is to avoid the new operator like it’s the plague. Every time the new operator is called and an object is constructed, the structure of that class' constructor arguments is carved into stone on that particular line, in that particular file. Part of carving less stone is to reduce the number of places a particular object is ever created. The fewer places that need to be refactored for a simple code change, the less coupled the code will become.

Additionally, creating objects with dependencies inside of other objects with dependencies creates a coupling between unrelated classes within a codebase. If one were to gather all objects created within a particular object, then recursively do that for each additional object that's created, and then at the end find themselves staring at every class inside the application, a realization should strike that the application is a web of tightly coupled, non-reusable code. I picture a giant amalgamation of multi-colored lego blocks, all super-glued together.

ZF2 comes packaged with a particularly useful tool called the ServiceManager (SM). The SM allows for the creation of objects via PHP callbacks and factories all in a singular place. When a service or object is called from the SM, dependencies can be created, passed to the object being requested, and the object returned. The dependencies of any particular object can be retrieved via the SM as well. Ultimately, the new operator has to be used and an object actually created, but the idea is to reduce any object's creation to one place with all dependencies created and passed via the constructor.

Here's an example for a login form:

// module\Application\config\module.config.php
return array(
    'service_manager' => array(
        'factories' => array(
            'Application\Form\LoginForm' => function($sm) {
	        $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
		$login_form = new \Application\Form\LoginForm($dbAdapter);
		return $login_form;
	    },
	    'login-form' => function($sm) {
		return $sm->get('Application\Form\LoginForm');
	    }
        ),
    ),
)

// module\Application\src\Application\Controller\AccountController.php
public function loginAction() 
{
    // ...
    $login_form = $this->getServiceLocator()->get('Application\Form\LoginForm');
    // ...
}

public function anotherLoginAction() 
{
    // ...
    $login_form = $this->getServiceLocator()->get('login-form');
    // ...
}

For this example, within the login action, we request the login form from the service manager via the class's fully qualified name (FQN). SM factory keys can be any string, and I've provided an example by creating a factory key of 'login-form' which just retrieves the object via the previous key. The FQN isn't necessary for SM factory keys, but it allows for extra functionality and also keeps everything a bit more organized.  

Before the LoginForm object is returned, a db adapter is created within the closure and passed as a dependency. I do not recommend passing db adapters to forms, but this is just an example. Leaving the object construction job to the service manager keeps the action cleaner and skinnier, and encourages best practices with dependency injection and keeps code less coupled.

One thing to avoid is passing the service manager itself as a dependency to objects.

// module\Application\config\module.config.php
return array(
    'service_manager' => array(
        'factories' => array(
            'Application\Form\LoginForm' => function($sm) {
	        $login_form = new \Application\Form\LoginForm($sm);
		return $login_form;
	    },
        ),
    ),
)

// module\Application\src\Applicaiton\Form\LoginForm.php
public function __construct(ServiceManager $sm) 
{
    // ...
    $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
    // ...
}

Doing so has now coupled the domain layer with Zend's SM. The SM should be viewed as a tool for reducing the work in regards to retrieving dependencies, building services and objects, and centralizing object creation. It's entirely possible to just pass the SM and only the SM as a dependency for every object, but this leads to tightly coupled, non-reusable code.

Looking back at the module.config.php, the objects were being created via a closure, but it's recommended to actually use the factory interface and point to the FQN of the factory for any particular object being created. The documentation for the service manager points out why this is preferred:

"Typically, you should not have your configuration files create new instances of objects or even closures for factories; at the time of configuration, not all autoloading may be in place, and if another configuration overwrites this one later, you’re now spending CPU and memory performing work that is ultimately lost."

So, for the example of the LoginForm, we could create a factory and then reference its FQN like so:

// module\Application\src\Form\Factory\LoginFormFactory.php
class LoginFormFactory implements FactoryInterface
{

    /**
     * Create service
     *
     * @param ServiceLocatorInterface $serviceLocator
     *
     * @return LoginForm
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
	$dbAdapter = $serviceLocator->get('Zend\Db\Adapter\Adapter');
	$login_form = new \Application\Form\LoginForm($dbAdapter);
	return $login_form;
    }
}

// module\Application\config\module.config.php
return array(
    'service_manager' => array(
        'factories' => array(
            'Application\Form\LoginForm' => 'Application\Form\Factory\LoginFormFactory'
        ),
    ),
)

So now when the LoginForm is requested from the SM, the SM will create the factory and return the call on the createService. At first I was put off by the idea of having to create a factory for every concrete class, but there are strategies to avoid having to do that. For instance, if every single form ever created is likely to need the dbAdapter as a dependency, it makes more sense to then create a FormService that takes the dbAdapter as a dependency, and then asks the service for any particular form.

Here's an example:

// module\Application\src\Service\Form.php
class Form
{
    /**
     * @var AdapterInterface
     */
    protected $db;

    public function __construct(AdapterInterface $db)
    {
        $this->db = $db;
    }

    public function getLoginForm()
    {
	    return new LoginForm($this->db);
    }
	
    public function getRegisterForm()
    {
	 return new RegisterForm($this->db);
    }
}

// module\Application\src\Service\Form\Factory\FormFactory.php
class FormFactory implements FactoryInterface
{

    /**
     * Create service
     *
     * @param ServiceLocatorInterface $serviceLocator
     *
     * @return LoginForm
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $dbAdapter = $serviceLocator->get('Zend\Db\Adapter\Adapter');
	$form_service = new \Application\Service\Form($dbAdapter);
	return $form_service;
    }
}

// module\Application\config\module.config.php
return array(
    'service_manager' => array(
        'factories' => array(
            'Application\Service\Form' => 'Application\Service\Factory\FormFactory'
        ),
    ),
)

// module\Application\src\Application\Controller\AccountController.php
public function loginAction()
{
    // ...
    /** \Application\Service\Form $form_service */
    $form_service = $this->getServiceLocator()->get('Application\Service\Form');
    $login_form = $form_service->getLoginForm();
    // ...
}

public function registerAction()
{
    // ...
    /** \Application\Service\Form $form_service */
    $form_service = $this->getServiceLocator()->get('Application\Service\Form');
    $register_from = $form_service->getRegisterForm();
    // ...
}

What if the form service is needed inside of every action of the AccountController?

If that were the case, the form service could be considered a dependency of that controller. With ZF2's SM, the form service can be passed as a dependency to the controller's constructor.

// module\Application\src\Application\Controller\Factory\AccountControllerFactory.php
class AccountControllerFactory implements FactoryInterface
{

    /**
     * Create service
     *
     * @param ServiceLocatorInterface $serviceLocator
     *
     * @return LoginForm
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $form_service = $this->getServiceLocator()->get('Application\Service\Form');
	return new \Application\Controller\AccountController($form_service);
    }
}

// module\Application\src\Application\Controller\AccountController.php
class AccountController extends AbstractActionController
{
	
    /**
    * @var \Application\Service\Form
    */
    private $form_service;

    public function __construct(\Application\Service\Form $form_service)
    {
	$this->form_service = $form_service;
    }
	
    public function loginAction()
    {
        // ...
	$login_form = $this->form_service->getLoginForm();
	// ...
    }
}

// module\Application\config\module.config.php
return array(
    'controllers' => array(
        'factories' => array(
            'Application\Controller\AccountController' => 'Application\Controller\Factory\AccountControllerFactory'
        ),
    ),
)

When ZF2 routes to the AccountController, the controller will be created via the configured factory, and the factory will create the form service via the SM and return the controller. When the form service is created for the controller via the SM, its factory is called, which first creates a db adapter and passes it as a dependency. Now we can create any action we want in the AccountController and have instant access to the form service knowing all dependencies have been fulfilled.

I hope this article helps give an idea to the benefits of Zend Framework 2's Service Manager. Again, the SM should be viewed only as a tool and not a necessity (or dependency) within an application. The SM should remain completely agnostic to the dependencies of the application's domain layer and only assist in the centralization of object creation as well as assist in the construction of objects with dependencies.

The SM coupled with Doctrine 2's Entity Manager has so far allowed for a very elegant looking codebase. In the next iteration, I'd like to look into using ZF2 in conjunction with Doctrine 2 via the DoctrineORMModule which is currently being openly developed.