LogoPhly, boy, phly
the weblog and site of Matthew Weier O'Phinney

Friday, March 19. 2010

A Simple Resource Injector for ZF Action Controllers

Brandon Savage approached me with an interesting issue regarding ZF bootstrap resources, and accessing them in your action controllers. Basically, he'd like to see any resource initialized by the bootstrap immediately available as simply a public member of his action controller.

So, for instance, if you were using the "DB" resource in your application, your controller could access it via $this->db.

I quickly drafted up a proof of concept for him using an action helper:


class My_ResourceInjector extends Zend_Controller_Action_Helper_Abstract
{
    protected $_resources;

    public function __construct(array $resources = array())
    {
        $this->_resources = $resources;
    }
 
    public function preDispatch()
    {
        $bootstrap  = $this->getBootstrap();
        $controller = $this->getActionController();
        foreach ($this->_resources as $name) {
            if ($bootstrap->hasResource($name)) {
                $controller->$name = $bootstrap->getResource($name);
            }
        }
    }
 
    public function getBootstrap()
    {
        return $this->getFrontController()->getParam('bootstrap');
    }
}
 

In this action helper, you would specify the specific resources you want injected via the $_resources property - which would be values you pass in. Each resource name would then be checked against those available in the bootstrap, and, if found, injected into the action controller as a property of the same name.

You would initialize it in your bootstrap:


class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initResourceInjector()
    {
        Zend_Controller_Action_HelperBroker::addHelper(
            new My_ResourceInjector(array(
                'db',
                'layout',
                'navigation',
            ));
        );
    }
}
 

The above would map three resources: "db", "layout", and "navigation". This means you can refer to them directly as properties in your controllers:


class FooController extends Zend_Controller_Action
{
    public function barAction()
    {
        $this->layout->disableLayout();
        $model = $this->getModel();
        $model->setDbAdapter($this->db);
        $this->view->assign(
            'model'      => $this->model,
            'navigation' => $this->navigation,
        );
    }

    // ...
}
 

This approach leads to some nice brevity -- you no longer need to fetch the bootstrap from the instantiation arguments, and then fetch the resource.

I thought about it some more, and realized that there's a few problems: How do you know what is being injected from within the controller? How do you control what is being injected.

So, I revised it to pull the expected dependencies from the action controller itself:


class My_ResourceInjector extends Zend_Controller_Action_Helper_Abstract
{
    protected $_resources;

    public function preDispatch()
    {
        $bootstrap  = $this->getBootstrap();
        $controller = $this->getActionController();

        if (!isset($controller->dependencies)
            || !is_array($controller->dependencies)
        ) {
            return;
        }

        foreach ($controller->dependencies as $name) {
            if ($bootstrap->hasResource($name)) {
                $controller->$name = $bootstrap->getResource($name);
            }
        }
    }
 
    public function getBootstrap()
    {
        return $this->getFrontController()->getParam('bootstrap');
    }
}
 

You would still register this in your bootstrap, but now you would no longer need any constructor arguments:


class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initResourceInjector()
    {
        Zend_Controller_Action_HelperBroker::addHelper(
            new My_ResourceInjector();
        );
    }
}
 

Instead, you define the resources you need to retrieve in your controller:


class FooController extends Zend_Controller_Action
{
    public $dependencies = array(
        'db',
        'layout',
        'navigation',
    );

    public function barAction()
    {
        $this->layout->disableLayout();
        $model = $this->getModel();
        $model->setDbAdapter($this->db);
        $this->view->assign(
            'model'      => $this->model,
            'navigation' => $this->navigation,
        );
    }

    // ...
}
 

This makes it far more clear what your dependencies are, and also ensures that each controller only gets the dependencies it plans on using. However, I think it can still be improved: if the dependency is not found, we should likely throw an exception!


class My_ResourceInjector extends Zend_Controller_Action_Helper_Abstract
{
    protected $_resources;

    public function preDispatch()
    {
        $bootstrap  = $this->getBootstrap();
        $controller = $this->getActionController();

        if (!isset($controller->dependencies)
            || !is_array($controller->dependencies)
        ) {
            return;
        }

        foreach ($controller->dependencies as $name) {
            if (!$bootstrap->hasResource($name)) {
                throw new DomainException("Unable to find dependency by name '$name'");
            }
            $controller->$name = $bootstrap->getResource($name);
        }
    }
 
    public function getBootstrap()
    {
        return $this->getFrontController()->getParam('bootstrap');
    }
}
 

This better satisfies the goals and needs of dependency tracking. Dependencies are defined by the object that needs them, they're injected by a collaborator, and missing dependencies results in an exception.

One potential improvement would be to allow specifying "default" resources to inject into all controllers; this could be accomplished with a constructor argument similar to the second example provided, and merging that value with the controller dependencies. I'll leave that as an exercise for the reader, though.

Action helpers are an area that is largely unexplored by many ZF users. Hopefully this post will show just how powerful they can be, and how much they can automate common tasks.

Posted by Matthew Weier O'Phinney in PHP at 16:20 | Comments (34) | Trackbacks (0)
Defined tags for this entry: mvc, php, zend framework
Related entries by tags:
Autoloading Benchmarks
Applying FilterIterator to Directory Iteration
Running mod_php and FastCGI side-by-side
Creating Zend_Tool Providers
State of Zend Framework 2.0

Trackbacks
Trackback specific URI for this entry

No Trackbacks

Comments
Display comments as (Linear | Threaded)

This is awesome! I noticed you guys discussing this in irc, but I didn't stick around to follow the entire conversation. Thanks for posting this.
#1 Court (Link) on 2010-03-19 16:35 (Reply)
Very useful. I was using a simple init include for this that accepted the keywords for the resources in the constructor.

And how about auto-injecting the resources in the helper?
This way all new resources created in the bootstrap would be available for usage.

Also, how does this work with resources created on other modules?
#2 Nuno Gomes (Link) on 2010-03-19 17:42 (Reply)
I don't like auto-injection, as it makes it hard to determine where the dependencies are coming from, or how to handle issues when the dependencies are missing.

I'm not sure what you mean by "how about auto-injecting the resources in the helper?" -- the point of this is for the helper to inject resources into the controller so that they're immediately available in your controllers. I think that's the more salient point.

Finally, for this to work with resources in other modules, you would need to pull those resources from the individual module bootstraps -- which is a non-trivial operation at this time.
#2.1 Matthew Weier O'Phinney (Link) on 2010-03-19 18:44 (Reply)
I think a better solution is an inversion of controll container, you can configure all dependencies in a file (xml, ini etc), simple and powerfull
#3 sokzzuka (Link) on 2010-03-19 18:01 (Reply)
Zend_Application_Bootstrap can actually utilize a DI container within it instead of using a registry instance, and several folks have blogged that. The problem currently is that you'd have to go to some significant lengths to use it to get a controller instance -- which is why solutions why the one presented here are more relevant and useful for ZF apps.
#3.1 Matthew Weier O'Phinney (Link) on 2010-03-19 18:40 (Reply)
Often you'll need those resource plugins within your models. One nice solution: A DI solution that is used all across ZF applications (being in both ZF as well as the app utilizing it).

It's not my intention to start the to-DI-or-not-to-DI discussion again, so can we please just start implementing it? :-D
#4 Freeaqingme on 2010-03-19 19:15 (Reply)
I've said it before and I'll say it again: DI containers are interesting, but I'm not convinced they're a great idea. They complicate bootstrapping for many folks, and if the object graph is large enough, can sometimes make it difficult to determine exactly where a dependency derives. With an MVC framework, the number of objects can be quite large, and this aspect becomes much more difficult to grasp. In particular, newcomers to the framework need to be able to understand things easily and simply, and I'm not sure a DI container will assist in that regard.

I'm not completely against them -- I think our architecture should make it simple to use DI containers, and Zend_Application_Bootstrap already does. I'm just not sure if we should develop one, use one from another framework, or simply not offer one.
#4.1 Matthew Weier O'Phinney (Link) on 2010-03-20 09:03 (Reply)
Why not just use the registry?
#5 Andy Daykin (Link) on 2010-03-19 21:39 (Reply)
One word: testing. Global static registries are more difficult to test against, and it's more difficult to understand where the dependencies originate. With the method outlined here, we know exactly where the dependencies originate.

(Interestingly, the bootstrap uses an instance of Zend_Registry internally -- Zend_Registry can be used as either a static registry, or as discrete instances.)
#5.1 Matthew Weier O'Phinney (Link) on 2010-03-20 09:06 (Reply)
Isn't the bootstrap a registry? Those resources are stored somewhere.

This is an example of the over-architecture typical of ZF. In Kohana, if I want a database instance, all I need is Database::instance('name');. Simple and transparent. Testability is no excuse for complex code.

Why in the world would a resource be created in the bootstrap if it isn't used in the controller. The bootstrap is for setting up the environment and setting some global variables. But the controller is the gateway. Matthew said it himself: "How do you know what is being injected from within the controller? How do you control". The controller should create all the required resources, and then DI away.

I have high hopes for ZF 2, but this madness must stop.
#5.1.1 Rick Jolly on 2010-04-09 18:52 (Reply)
Testability is important if we want to ensure our users do not run into the same or new bugs time and time again. If the framework is untestable, then we're doing a disservice to the end users. Period.

You ask "Why in the world would a resource be created in the bootstrap if it isn't used in the controller?" Several reasons:

* It may be not be related to the controller, and the controller may never touch it. In fact, this is true quite often -- not every controller nor every action needs the same resources, but some resources need to be configured up front to ensure they will be available when needed. E.g., only some actions of some controllers will need to inject a database adapter into the models they use; some actions of some controllers may not require a layout or view object; only some actions of some controllers will require an ACL; etc.
* If I want to test my controller, or have it behave differently based on the environment in which it operates, and it's creating all the resources, I have no way to inject alternative implementations. This would be an inflexible design.
* The bootstrap is not solely for use with the MVC. It can be used to configure resources for service endpoints, CLI scripts, etc. If resources will be configured the same between the MVC and these other consumers, but the controllers were doing resource creation, then we'd be duplicating logic between the different use cases. That breaks the DRY principle.

You ask, "Isn't the bootstrap a registry?" -- a question I answered in the comment to which you replied. Yes, it maintains a registry within it. It's a local registry, so that we can potentially have multiple registries. And the bootstrap is _injected_ into the controller -- which means you have access to it. "$this->getInvokeArg('bootstrap')->getResource($name)" will let you get any resource you need. This is fundamentally the same as using any other registry -- but instead of it being a global registry, it's one that's injected into the controller. It's simply a little more verbose; more importantly, however, it's testable and flexible.

You raise one important question when you point out one of my own statements: "How do you know what is being injected from within the controller?" This is really the crux of the matter: how do we define a simple and understandable way to inject dependencies into the controller? This is actually a relatively simple problem to solve, but requires some backwards-incompatible changes to the action controller in order to do so -- and thus need to wait until ZF2. The basic mechanism is that we would define explicit setters and getters for each dependency, and either have the dispatcher detect these and inject them from the resources defined in the bootstrap, or use a DI container. The approach is yet to be determined, but it's easily possible with some minor changes in the infrastructure.
#5.1.1.1 Matthew Weier O'Phinney (Link) on 2010-04-10 08:14 (Reply)
Nice trick !
What would be very cool is a lazy initialization of those resources only if the current dispatched controller need them ;-)
#6 TheSorrow on 2010-03-20 03:37 (Reply)
This could be done by using a DI container in the bootstrap instead of a registry instance. :-)
#6.1 Matthew Weier O'Phinney (Link) on 2010-03-20 09:07 (Reply)
Thanks for sharing this, that's a very nice bit of code to know :-) Currently doing a couple of projects in Zend and things like this are great helpers
#7 Peter Lind (Link) on 2010-03-20 07:56 (Reply)
Why not make our own Controller_Action class?

class My_Controller_Action extends Zend_Controller_Action {

public function __call($method, $args) {

$bootstrap = $this->getInvokeArg('bootstrap');

if ($bootstrap->hasResource($method )) {
$this->$method = $bootstrap->getResource($method);
}

}

}

and Action_Controllers descent from it.

Than just a call $this->db()->whatever would get the resource. No need to specify resources.
The difference is it is a method, not a variable.

What do you think?

Sorry for ugly code display, but couldn't find how to format it. :-(
#8 vb on 2010-03-20 08:35 (Reply)
I like to build functionality that's re-usable. Creating an extension to Zend_Controller_Action doesn't work in this regard -- what if I have this particular extension defined, but also have another extension that contains code I want to re-use (i.e., two base classes extending Zend_Controller_Action that define different extension points)? What if controllers in one module need one set of functionality, and others need something else?

Action helpers answer this question. They use composition instead of inheritance to provide functionality to the action controllers, which allows you to mix and match functionality as needed.
#8.1 Matthew Weier O'Phinney (Link) on 2010-03-20 09:14 (Reply)
Interesting idea. There could be some perfomance problem if you compare this method with classical dependency pulling, though. E.g.: one controller has 3 actions and uses 5 dependencies overall. One action however uses only 1 dependency. Using injection all 5 dependencies are bootstrapped even if the current action does not use them.

My solution to this problem would be to inject the dependencies directly into action methods as arguments. It is possible if you change the format of the "dependencies" array and allow per action configuration.
#9 sas171 on 2010-03-22 09:02 (Reply)
What you discuss is micro-optimization at best; pushing all dependencies will take a few microseconds at worst.
#9.1 Matthew Weier O'Phinney (Link) on 2010-03-22 09:10 (Reply)
Well, I think it depends on the nature of the dependencies. If the bootstrapping of a dependency requires for example reading a huge XML file or talking to the database the optimization is not so minor.
#9.1.1 sas171 on 2010-03-22 16:51 (Reply)
I think you'll find all resources are bootstrapped in either case. It's only the injection into the controller that is handled here.

If you want lazy loading, you're probably after a DI container as mentioned in other comments.
#9.1.1.1 Brenton Alker (Link) on 2010-03-22 19:41 (Reply)
Is there a smart way to provide setters for controller dependencies? I am looking for a good way to inject dependencies from unit tests without the need of using an action helper at all.

Thanks
Michael
#10 Michael (Link) on 2010-03-23 16:59 (Reply)
I did something similar to this. Love the idea.

In my case, I still had an (optional) array argument for the constructor which sets up the default dependencies required of every controller. In my case, I wanted access to the log resource in every controller. So when adding the helper to the broker I pass array('log') to the constructor and I have my error/debug log available everywhere.

Do you see any other ways to accomplish this? I wanted to avoid having 'public $dependencies = array('log');' in every single controller.
#11 Jeff Carouth (Link) on 2010-04-01 10:11 (Reply)
Application needs vary widely. I've found that making things more explicit makes it easier to track down issues, as I know both the dependencies as well as where they're coming from. That said, if you know you will need logging globally, this is a good place to inject it from. Just make sure you document it somewhere. :-)
#11.1 Matthew Weier O'Phinney (Link) on 2010-04-01 10:43 (Reply)
Very nice, if a dependency is missing Errorhandler_Plugin is doing the rest of it.
#12 Christian on 2010-04-24 17:00 (Reply)
Just some comments:

1.) The API of your controller does not reveal its needs - it's not even possible to make a proper (UML) diagram, unless you (api) document, what you do not implement.

2.) Its likely, that you either a.) misspell the names of the requested resources in the controller and/or bootstrapper or b.) find yourself in just another namespace pollution conflict.

3.) There's no way the controller knows if it's been initialized correctly, accessing a null pointer or getting a "foo" when it requested a "bar".

But hey, it's an example for a "Simple" Resource Injector and a nice one for Helpers. I'd prefer interfaces at any cost and if you want lazy initialization you could ask for a "ResourcePrvider" instead of the resource itself, which then would introduce another level of indirection, but would greatly enrich possible implementations (e.g. be the provider yourself unless another one was injected).

Regards, Kyle
#13 Kyle Caine on 2010-04-28 09:49 (Reply)
Actually, the bootstrap acts as a resource provider already. What this "injector" does is take resources from that provider and inject them directly into your controller -- the main purpose being to simplify the code you use.

One potential change would be to use explicit setters and getters in the action controller, and allow lazy-loading in the getters whenever the resources are not injected. This would satisfy all of your issues. As for 2b -- I'm failing to see how there would be namespace pollution, as the resources have unique names.
#13.1 Matthew Weier O'Phinney (Link) on 2010-04-28 10:17 (Reply)
The namespace pollution (in your example code) comes from requesting a 'db', which is a string, not a class or interface. When working in large teams and a catalog of separately developed modules, it becomes a minefield to ask for some resource like 'db' and not a type.

And yes, accessors would satisfy most of the issues, but introduce a convention (how to distinguish a requested resource from a property). Implementing an specific Requestor interface indicates the need for a specific resource. If this request is resolved by the bootstraper or a DI container - who cares, at least the controller should not care.
#13.1.1 Kyle Caine on 2010-04-28 11:12 (Reply)
We'll have to respectfully disagree on the aspect of namespace pollution; Zend_Application enforces unique resource names, and these have a 1:1 correspondence with classes within the framework. It's trivial to do typechecks on the return values from getResource(), as well as to look in the bootstrap and/or configuration to determine what classes are in play.
#13.1.1.1 Matthew Weier O'Phinney (Link) on 2010-04-28 11:51 (Reply)
I don't want to put too much stress on this topic, but true or false:

We have two separately developed modules X and Y, each requesting a resource named 'log' for logging purposes. The one expects a class 'Log' from PEAR and the other expects Zend_Log from ZF.

An application developer (architect) decides to use both modules for his software.

Well, enjoy either the debugging sessions or the meetings with the developers to get one of the maintainers change the name for the requested resource.

And imagine you have a bunch of applications in the filed each depending on these modules - no chance to change the name for the resource.

But you're right: All of this just might not be a big problem for the average development scenario and again it's a simple example. All I wanted to say is that it might come with risks to request resources by names.
#13.1.1.1.1 Kyle Caine on 2010-04-29 05:35 (Reply)
It's a non-issue: the bootstrap doesn't allow two resources by the same name, so they must be initialized in the bootstrap as separate resources, with different names (e.g., "log" and "pearlog"). When you retrieve them, you select the specific resource you want.

If a developer changes the name of a resource, that's an internal communication problem.
#13.1.1.1.1.1 Matthew Weier O'Phinney (Link) on 2010-04-29 10:14 (Reply)
I think a ressource is in most cases essential to the Application and Exception occuring when one is Missing or misspelled is the best what can happen to you, a simple test of your application will reveal this error.
#13.2 Christian on 2010-04-28 10:21 (Reply)
You are using preDispatch(), can the same be done by having the implementatio in the init() method of the action helper? Since we are initializing the controller that would make some sense. Or am I completely wrong here?
#14 Onno on 2010-05-28 08:39 (Reply)
Yes -- you can move the hook to init() if desired.
#14.1 Matthew Weier O'Phinney (Link) on 2010-05-28 09:50 (Reply)
so simple ! brilliant :-))
#15 Jack (Link) on 2010-05-31 21:54 (Reply)

Add Comment

Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
 
  • Home
  • Resume
  • Blog
  • Phly PEAR Channel
  • Twitter
  • Contact Me
  • About this site

ZCE

Zend Education Advisory Board Member

Add to Technorati Favorites

Calendar

Back September '10
Mon Tue Wed Thu Fri Sat Sun
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30      

Quicksearch

Links

  • PHLY - PHp LibrarY
  • Planet PHP
  • Zend Framework, where I'm project lead
  • Sebastian Bergmann
  • Cal Evans
  • Shahar Evron
  • Paul M. Jones
  • Bill Karwin
  • Mike Naberezny
  • Fabien Potencier
  • Ben Ramsey
  • Derick Rethans
  • Ralph Schindler
  • Marco Tabini

Archives

September 2010
August 2010
July 2010
Recent...
Older...

Categories

XML Linux
XML Personal
XML Aikido
XML Family
XML Programming
XML Dojo
XML Perl
XML PHP

All categories

Syndicate This Blog

XML RSS 0.91 feed
XML RSS 1.0 feed
XML RSS 2.0 feed
ATOM/XML ATOM 0.3 feed
ATOM/XML ATOM 1.0 feed
XML RSS 2.0 Comments

Show tagged entries

xml apache
xml best practices
xml books
xml conferences
xml cw09
xml decorators
xml dojo
xml dpc08
xml file_fortune
xml git
xml linux
xml mvc
xml oop
xml pear
xml perl
xml personal
xml php
xml phpworks08
xml programming
xml rest
xml ubuntu
xml vim
xml webinar
xml zendcon
xml zendcon08
xml zendcon09
xml zend framework
© 2004 - present, Matthew Weier O'Phinney
matthew-web <at> weierophinney.net