A free and open-source book on Zend Framework for beginners


4.9. Controller Registration

All controller classes belonging to a module should be registered in the module.config.php configuration file. If your controller class doesn't need to use some services (doesn't have dependencies), you can register it as follows:

<?php
use Zend\ServiceManager\Factory\InvokableFactory;

return [
    // ...
    
    'controllers' => [
        'factories' => [
            Controller\IndexController::class => InvokableFactory::class
            // Put other controllers registration here
        ],
    ],
    
    // ...
];

In line 7, we have the controllers key, which contains the factories subkey. To register a controller class, you add the line in form of key=>value pair. The key should be the fully qualified name of the controller class, like \Application\Controller\IndexController (we can use the PHP ::class keyword for class name resolution), and value should be the name of a factory class that would create the controller class for use. In our case, we use the standard InvokableFactory, but you can create your own if you need.

By using the InvokableFactory, you tell Zend Framework that it can invoke the controller by instantiating it with the new operator. This is the most simple way of instantiating the controller. As an alternative, you can register your own factory to create the controller instance, and inject dependencies into controller.

4.9.1. Registering a Controller Factory

If your controller class needs to call some service (this happens very often), you need to request that service from the service manager (we discussed the service manager in the Website Operation chapter) and pass that service to controller's constructor, and the controller saves the service you passed in a private property for internal use (this also called dependency injection).

This procedure is typically implemented inside of a factory class. For example, assume our controller class needs to use some CurrencyConverter service which will convert money from USD to EUR. The factory class for our controller will look like below:

<?php 
namespace Application\Controller\Factory;

use Zend\ServiceManager\Factory\FactoryInterface;
use Application\Service\CurrencyConverter;
use Application\Controller\IndexController;

// Factory class
class IndexControllerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, 
                     $requestedName, array $options = null) 
    {
        // Get the instance of CurrencyConverter service from the service manager.
        $currencyConverter = $container->get(CurrencyConverter::class);
        
        // Create an instance of the controller and pass the dependency 
        // to controller's constructor.
        return new IndexController($currencyConverter);	
    }
}

Then you register the controller the same way, but specify the factory class we have just written:

<?php
return [
    // ...
    
    'controllers' => [
        'factories' => [
            Controller\IndexController::class => Controller\Factory\IndexControllerFactory::class
        ],
    ],
    
    // ...
];

If you have some experience with Zend Framework 2, you may notice that the things are now a little different than before. In ZF2, there was getServiceLocator() method in the AbstractActionController base class allowing to get dependencies of the controller even without the factory. In ZF3, you have to pass dependencies explicitly. It is a little more boring, but it removes "hidden" dependencies and makes your code more clear and easier to understand.

4.9.2. LazyControllerAbstractFactory

Writing a factory for almost every controller may seem boring at first sight. If you are so lazy that you don't want to do that, you can use the standard LazyControllerAbstractFactory factory class.

The LazyControllerAbstractFactory factory uses reflection to determine which services your controller wants to use. You just need to typehint the arguments of controller's constructor, and the factory will itself retrieve the needed services and pass it to the constructor.

For example, to inject the CurrencyConverter service in your controller, make sure its constructor looks like below:

namespace Application\Controller;

use Application\Service\CurrencyConverter;

class IndexController extends AbstractActionController
{
    // Here we will save the service for internal use.
    private $currencyConverter;
    
    // Typehint the arguments of constructor to get the dependencies.
    public function __construct(CurrencyConverter $currencyConverter)
    {
        $this->currencyConverter = $currencyConverter;
    }
}

Then you register the controller the same way, but specify the LazyControllerAbstractFactory factory:

<?php
use Zend\Mvc\Controller\LazyControllerAbstractFactory;

return [
    // ...
    
    'controllers' => [
        'factories' => [
            Controller\IndexController::class => LazyControllerAbstractFactory::class
        ],
    ],
    
    // ...
];

Top