4

I'm attempting to convert my beta DI code to the release version of ZF2. Right now I'm falling over at the very start and there doesn't seem to be any sort of documentation about injecting stuff into the controllers which gets me to think that it's not normal to have dependencies in a controller?

Right now I'm just doing a var_dump('blah');exit; just to try and get some code to run... I've tried a number of things and right now I expected this to work:

module.config.php

'controllers' => array(
    'invokables' => array(
        'indexController' => 'Application\Controller\IndexController',
    )
)

Module.php

public function getControllerConfig() {
    return array(
        'factories' => array(
            'indexController'    => function(ControllerManager $cm) {
                var_dump('blah');exit;
            },
        ),
    );
}

Right now nothing is happening and it's pretty frustrating... I read something about creating Factories for each Controller but I have 33 and I find that quite insane and stupid... ?

What I'm trying to inject is stuff like a userMapper for grabbing/saving users. So the registerAction creates a user in the database using the userMapper and when you try to login it uses the userMapper to check if there is a user there etc.

Florent
  • 12,310
  • 10
  • 49
  • 58
creamcheese
  • 2,524
  • 3
  • 29
  • 55
  • Did you ever find an acceptable answer to this? I've updated my answer with what I think might be the solution. – Reese Sep 11 '13 at 22:12

3 Answers3

2

The problem here is that 'indexController' is defined as both an invokable and a factory. I think it checks invokables first, so when it finds what it is looking for, it never attempts to run the code in the factory. Just remove the entry in the 'invokables' array.

I just wrote a post on this subject. Instead of creating a separate factory class for each controller, you can do it with closures. If the dependencies are invokable, or easily configured with an options array, it is even easier, all you need is an array listing the classes that can be injected. Check out http://zendblog.shinymayhem.com/2013/09/using-servicemanager-as-inversion-of.html

Reese
  • 1,746
  • 1
  • 17
  • 40
1

you can easily do it like this in any Module.php

public function onBootstrap(\Zend\EventManager\EventInterface $e)
{
    $serviceManager = $e->getApplication()->getServiceManager();
    $myDependency = /*something*/;

    $controllerLoader = $serviceManager->get('ControllerLoader');
    $controllerLoader->addInitializer(function ($controller) use ($myDependency) {
        if (method_exists($instance, 'injectMyDependency')) {
            $controller->injectMyDependency($myDependency);
        }
    });
}

a bit cleaner would to let the controllers which need the dependency implement an interface and check if the controller is an instance of that interface and then set it, not just check if the method exists...

Andreas Linden
  • 12,489
  • 7
  • 51
  • 67
  • Cool, this stuff seems a bit hacky and isn't mentioned anywhere... is what I'm trying to do not the general way of doing things? Like using a UserMapper to write new users to a database. $userMapper->register($user); for instance – creamcheese Sep 13 '12 at 21:10
  • Ah on here it says to add a method inside your controller to get what you want from the ServiceManager, like userMapper would be a method: getUserMapper() { $sm = $this->getServiceLocator(); $this->userMapper = $sm->get('App\Model\UserMapper');} sort of thing. As described here: http://zf2.readthedocs.org/en/latest/user-guide/database-and-models.html – creamcheese Sep 13 '12 at 21:23
  • 1
    It should not be in that way (onBootstrap). Another point is that `ServiceManager` is actually a `ServiceLocator` [which sometimes is seen as an anti-pattern](http://www.martinfowler.com/articles/injection.html#ServiceLocatorVsDependencyInjection) or glorified registry. ZF2 let you avoid this too. Anyway, when I got some spare time, if no one post the answer I'll come back =P – Keyne Viana Sep 13 '12 at 21:31
  • @Keyne, well this is the way how doctrine teams suggests to inject the entityManager into controllers... – Andreas Linden Sep 14 '12 at 09:04
  • @AndreasLinden Anyway, indeed, you should not use the entity manager within your controllers, otherwise it'll act more like a *service* than a *controller*. *Controllers* are part of the *presentation layer* and if you're following good practices, it should not be tight coupled with the *model layer*, instead you should have a *service-layer* and inject the *entity-manager* there. Likewise, the given *service* will be injected into the *controllers* and it will work with its interface only. – Keyne Viana Sep 14 '12 at 18:44
  • @Kayne, in the MVC concept the controller is not part of the presentation layer, only the view component is reponsible for presentation. an additional layer to access your database management only makes sense if you plan to allow different libraries to work with your application. ie. be able to switch between doctrine and zenddb. otherwise it's common to work with the db lib in the controllers. – Andreas Linden Sep 14 '12 at 20:27
  • @AndreasLinden The MVC cannot be implemented (without a lot of hacks) in web applications. I mean, the SmallTalk MVC: http://en.wikipedia.org/wiki/Model–view–controller. The ZF2, actually, implements the *Model2 MVC*:http://en.wikipedia.org/wiki/Model_2 So, the controller **is part of the presentation layer** as I said. You might be interested in this answer: http://stackoverflow.com/questions/7621832/architecture-more-suitable-for-web-apps-than-mvc/7622038#7622038 and this one: http://stackoverflow.com/questions/5863870/how-should-a-model-be-structured-in-mvc/5864000#5864000. – Keyne Viana Sep 16 '12 at 02:37
0

Below is my Initializer code to inject into an arbitrary class. It was sort of tricky at the beginning to grasp - to automagically inject into controller upon instantiation you have to define initializer in 'initializer' section of 'controllers' section of module.config.php - not in that of 'service_manager' section. Basically to create universal "Aware Interfaces" that will be effective for controllers and the rest - respective initializer keypairs should appear in both sections altogether...

// module/SkeletonClassmapGenerator/Item/ImplementedItem/ImplementedItemInitializer.php

namespace SkeletonClassmapGenerator\Item\ImplementedItem;

use Zend\ServiceManager\InitializerInterface;
use SkeletonClassmapGenerator\Provider\GenericInitializerTrait;

class ImplementedItemInitializer implements InitializerInterface
{
    static protected $T_NAMESPACE = __NAMESPACE__;
    static protected $T_CLASS = __CLASS__;
    use GenericInitializerTrait; 
}

Then for the trait (obviously shared among all initializers)...

// module/SkeletonClassmapGenerator/Provider/GenericInitializerTrait.php

namespace SkeletonClassmapGenerator\Provider;

use Zend\ServiceManager\ServiceLocatorInterface;

trait GenericInitializerTrait
{
    public function initialize($instance, ServiceLocatorInterface $serviceLocator)
    {       
       if (isset(static::$T_CLASS)&&(isset(static::$T_NAMESPACE))){
       $classname = explode('\\', static::$T_CLASS);        
       $class = end($classname);
       preg_match('/([\w]*)Initializer$/i', $class,$matches);
       $basename = $matches[1];
        if(is_subclass_of($instance,static::$T_NAMESPACE.'\\'.$basename.'AwareInterface')) {
            $sl = (method_exists($serviceLocator,'getServiceLocator'))?
            $serviceLocator->getServiceLocator():$serviceLocator;
             $dependency  = $sl->get(static::$T_NAMESPACE.'\\'.$basename.'Interface');  // I use 'Interface' as postfix for Service Manager invokable names           
             $instance->{'set'.$basename}($dependency);
            }
       }
   }

}