bishop
bishop

Reputation: 39364

ZF2: Injecting action-specific configuration into controller

There are at least three methods -- Zend\Di, ConfigAwareInterface, and ControllerFactory -- I've considered for injecting configuration into controllers.

The more or less official recommendation is the ControllerFactory, which leads to this code (or this code if you prefer closures -- I don't, as they're harder to test):

// module.config.php
'controllers' => array (
    'factories' => array (
        'Module\Concept\Index' => 'Concept\ControllerFactory\IndexControllerFactory',
    ),
),
'some_config_key' => array (
    'x' => 'foo'
),

// src/Concept/ControllerFactory/IndexControllerFactory.php
class IndexControllerFactory implements FactoryInterface {
    public function createService(ServiceLocatorInterface $serviceLocator) {
        $config = $serviceLocator->getServiceLocator()->get('config');
        $controller = new \Html\Controller\IndexController($config['some_config_key']);
        return $controller;
    }
}

// src/Concept/Controller/IndexController.php
class IndexController {
    public function __construct($config) {
        // here we go, yay!  we have $config['x'] == 'foo'
    }
}

If each action needs different configuration (eg, one might need DB, another might need paypal), it doesn't seem proper to pass all controller configuration when only some of it is needed for the action.

So, should the knowledge of which configuration is needed for which action go into the factory or into the controller? That is, should the factory give the controller only what it needs based on the action at hand, or should the controller be given all its configuration and internally figure it out?

Update
Action-specific dependencies are just that: specific. The controller declares what it needs then uses that in whatever ways are necessary. That two actions have radically different needs suggests that perhaps the controller has too broad a scope, and may be a candidate for separation.

But regardless, the best way -- the way that was clear, robust, and apparently in line with future direction of ZF -- is to manufacture the needed services in a service factory, then manufacture a controller with the requisite services.

Two factories: service and controller. The controller declares its __construct contract with the services it must have, then the factories manufacture those as needed from the module config.

Upvotes: 2

Views: 364

Answers (1)

Carlos Robles
Carlos Robles

Reputation: 10947

This question is mostly opinion-based, so you can get many diferent answers.

The two approaches are good, since:

  • As you know, what we see a lot in ZF2 is the service manager all around, with all the merged configuration running around all the application. So even if this were not the best approach, it is widely used.
  • If we have in mind the dependency inversion principle (that we have, indeed) and also the high cohesion and low coupling principles, that tell us that the isolation is good, that all the components does not have to know two much about the others, and that everything inside a module should be strongly related, we can think that having all the merged config in every controller, and even let a controller know what key in the associative merged configuration array contains its configuration, probably is not the best thing, and probably what a module should receive is its configuration, no matter if its comes from a bigger array, from a factory, from hell, or from somewhere else, since, in fact, it doesn't need to know that, and that doesn't matter.

So probably the best approach is to inject only the configuration that it is expecting. Actually, that is what we use to do when creating a ViewHelper, where ususally we dont inject everything but only what we need.

This could be confusing, since sometimes you could need some other general config also, and not only the config purely related to the module, and in that cases you will have to think about it. But in most cases, the more elegant solution probably will be to inject just what you need.

As a further and important argument, i read in sam minds blog that

...there are still a couple of things that are just not ideal. A little example would be that a lot of people use $this->getServiceLocator() inside their Controllers. While this is OK, it’s actually considered bad practice. The way things are looking right now, this feature will be removed in Zend Framework 3 (ZF3) to basically force people to use proper dependency injection using the ServiceManager.

so we can think is that the proper approach is to bring the dependency injection as far as we can and let every component know as little as we can.

Upvotes: 1

Related Questions