Getting dependencies into ZF2 Controllers

Getting dependencies into ZF2 Controllers

Do your controllers need other object instances to do their work? Well, how do you get your hands on those dependencies inside your controllers? Forget new Xyz() – that’s a road to hell. There is a better way in ZF2. It has always been there (mostly undocumented), but only after a recent change it has become more evident and usable.

Controllers are services

Controllers are services just like any other service you might define. The important difference is they are instantiated not by you at some point in your user code, but automatically as a part of the MVC workflow. How can we do, say, a constructor injection then, when it is not our code which instantiates the controller? This might seem like a problem but it’s quite easy in fact.

DI

Prior to the introduction of the ServiceManager, we had DI to do the dirty work of object instantiation and wiring. As controllers were instantiated by MVC using DI, it was rather easy to instruct the DI instance manager to inject whatever was necessary and voilà we had a fully configured controller with dependencies injected – almost like magic. But there ain’t no such thing as a free magic and this magic cost speed. When the ServiceManager (i.e. a Service Locator) came it eliminated the speed penalty of DI by not doing anything too clever and time consuming (e.g. introspection) and it took over the DI’s responsibilities throughout the ZF2.

(I won’t go into detail about the DI way of configuring controllers, because the ServiceManager is a preferred way to do things in ZF2 now. It’s still possible to use DI for this nonetheless. Drop me a line if you are interested in the DI method and I will post some code to demonstrate this.)

ServiceManager

Controllers are services, remember? The MVC workflow retrieves the controller instances from the ServiceManager. But do not rush to trace and debug your app to check it’s ServiceManager ($application->getServiceManager()) and see with your own eyes the controller instances there. You won’t find them there. It’s not this ServiceManager. The controller instances are managed by a separate dedicated scoped ServiceManager instance. So we can use neither the service_manager key in a config file nor the getServiceConfig() method in the Module class to configure our controller instances. We are thus back to our initial question: How do we do a constructor injection on a controller?

The dedicated ServiceManager instance used exclusively for controllers can be configured in two places: controllers key in a config file (please note the recent change from singular controller to plural controllers!) and getControllerConfig() method in the Module class. Both of these are expected to return an array with the ServiceManager configuration.

The controller classes which do not have any dependencies are set-up under the invokables subkey as a 'controllerServiceName' => 'Fully\Qualified\Controller\Classname' pairs. Please note the recent subkey name change from classes to invokables. Other thant that this is nothing new as you must be doing this already otherwise your controllers would not work:

/* module.config.php */
<?php
return array(
    'controllers' => array(
        'invokables'    => array(
            //Suppose one of our routes specifies
            //a controller named 'myController'
            'myController'    => 'MyModule\Controller\MyController',
        ),
    ),
);

The easiest method to create controller instances which do have dependencies is to use a factory. You can either write a proper factory implementing the Zend\ServiceManager\FactoryInterface:

/* MyModule/src/MyModule/ControllerFactory/MyControllerFact.php */
namespace MyModule/ControllerFactory;
use \Zend\ServiceManager\FactoryInterface;
use \Zend\ServiceManager\ServiceLocatorInterface;

class MyControllerFact implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator) {
        /* @var $serviceLocator \Zend\Mvc\Controller\ControllerManager */
        $sm   = $serviceLocator->getServiceLocator();
        $depA = $sm->get('depA');
        $depB = $sm->get('depB');
        $controller = new \MyModule\Controller\MyController($depA, $depB);
        return $controller;
    }
}

And then simply put the reference to this factory under the factories subkey into the configuration file:

/* module.config.php */
<?php
return array(
    'controllers' => array(
        'factories'    => array(
            //Suppose one of our routes specifies
            //a controller named 'myController'
            'myController'    => 'MyModule\ControllerFactory\MyControllerFact',
        ),
    ),
);

Or you can use a closure and define the factory as a part of the configuration. Be careful though not to do this in the configuration file (please see the note Configuration and PHP on the ServiceManager Quick Start page for reasons). The proper place for this is the getControllerConfig() method:

/* MyModule/Module.php */
<?php
namespace MyModule;
use \Zend\Mvc\Controller\ControllerManager;

class Module
{
    public function getControllerConfig() {
        return array(
            'factories' => array(
                //Suppose one of our routes specifies
                //a controller named 'myController'
                'myController'    => function(ControllerManager $cm) {
                    $sm   = $cm->getServiceLocator();
                    $depA = $sm->get('depA');
                    $depB = $sm->get('depB');
                    $controller = new \MyModule\Controller\MyController($depA,
                                                                        $depB);
                    return $controller;
                },
            ),
        );

    //The getConfig() and getAutoloaderConfig() methods are omitted for brevity
}

Both ways you have the application’s ServiceManager instance available in your factory method (via the getServiceLocator() method) so it’s easy to pull any collaborators you might need to instantiate your controllers.

ServiceManager plugin fixes

This shape of things is a result of a recent ServiceManager rework. Please read Evan’s comments on the pull request #1695 to get a quick overview of the ServiceManager plugin configuration. The features described in this post are just a subset of the possibilities offered by various ZF2 ServiceManagers.

Conclusion

The ServiceManager (and DI) and its systematic utilization in ZF2 is a major improvement over ZF1. There is no similar facility in ZF1 (AFAIK) which would help you inject dependencies into controllers, you are on your own there. (It can be done though. If anyone is interested, I will happily share my ZF1 solution using an action helper, but it’s way more cumbersome than the elegant ServiceManager approach of ZF2.)

So there you have it. It’s so easy to configure your controllers in ZF2 now, that you really have no more excuses. Say ‘NO!’ to all news and singletons and do things the right way. You will thank yourself later.

Picture by Ali Hussein