Action Helpers in Zend Framework

April 7, 2008

News, Zend Framework

Action Helpers
in Zend Framework are often
considered a fairly arcane subject, something for experts only. However,
they are meant to be an easy way to extend the capabilities of href="http://framework.zend.com/manual/en/zend.controller.action.html">Action
Controllers, negating the need to create your own base controller
with custom functionality. The aim of this tutorial is to show you how to
quickly and easily create and use Action Helpers to your advantage.

Basics

Many tutorials on Zend Framework would have you believe you should create a
base class extending Zend_Controller_Action to provide base
functionality for your controllers:

/**
 * Your concrete controllers would now extend My_Controller_Action
 */
abstract class My_Controller_Action extends Zend_Controller_Action
{
    // create your utility methods here...
}

However, this is not only not necessary, but typically not a great move for
extensibility. You may find later that a given controller only needs a
subset of the methods in your base controller — or that you’re constantly
adding methods that only a few of your controllers need, creating bloat.

The better solution is to use action helpers. Action helpers are intended
to provide run-time, use-at-will capabilities to action controllers. In
other words, you can use them if you need them, but they aren’t loaded by
default.

Action helpers are handled by a broker.
Zend_Controller_Action_HelperBroker maintains a static registry
of registered helpers, and also serves as a factory for loading helpers on
demand. By default, the $_helper property of
Zend_Controller_Action contains an instance of the broker.

When an action controller is instantiated, a new broker instance is
registered with it, and the action controller is in turn registered with the
broker. When you retrieve or access helpers, they then have access to the
controller — allowing integration with it. So, for example, you can set
public properties or call public methods on the action controller via your
helper — and vice versa.

In general, you retrieve your helper by using the last segment of the class
name. So, for example, if your helper is named ‘Foo_Helper_Bar’, you’d refer
to it as ‘bar’. You then have two options for retrieving it: as a property
of the broker, or via the getHelper() method:

$bar = $this->_helper->bar;
$bar = $this->_helper->getHelper('bar');

However, this is just the tip of the iceberg.

The direct() Method

Action helpers can use the href="http://en.wikipedia.org/wiki/Strategy_pattern">Strategy Pattern.
If you define the method direct() in your helper, you can call
your helper as if it were a method of the broker.

An illustration is worth a thousand words. Let’s look at the
Url
helper, which returns a URL based on the input received:

$url = $this->_helper->url('bar', 'foo'); // "/foo/bar"

Implementing the direct() method in your action helpers is an
easy way to add virtual functionality to your actions.

Event Hooks

As if that wasn’t enough, Action Helpers also have several event hooks to
help automate functionality. The three hooks provided are:

  • init(): called when the action controller is intialized
    (but only if an instance of the helper already exists in the broker)
  • preDispatch(): called after plugin
    preDispatch() routines, but prior to the action controller
    preDispatch() routines — but only if an instance of the
    helper already exists in the broker.
  • postDispatch(): called after the action controller
    postDispatch() routines, but prior to plugin
    postDispatch() routines — but only if an instance of the
    helper already exists in the broker.

Note the caveat on each: only if an instance of the helper exists in the
broker already
. Typically, you’ll load helpers on-demand — i.e., only when
you need them. However, there are some cases where you may want to add
automatic functionality similar to plugins — but with the ability to
introspect the current controller. This is where the action helper hooks
come in handy.

As an example, the href="http://framework.zend.com/manual/en/zend.controller.actionhelpers.html#zend.controller.actionhelpers.viewrenderer">ViewRenderer,
which is enabled by default in the ZF MVC, is an action helper. It uses the
hooks as follows:

  • init(): Initializes the view object, sets appropriate script,
    helper, and filter paths for the current controller, and registers the
    current view object as the controller’s “view” member.
  • postDispatch(): determines if it should render anything,
    and, if so, renders a view script based on the current (or requested)
    action to the appropriate response segment.

As another example, you could add a preDispatch() hook to an
action helper that checks a public member of your action controller to
determine which actions require authentication — and redirect to a login
form when a match is made. This works better than using a standard plugin,
as it allows you to keep the information about authentication requirements
with the controller — where it belongs.

Registering Helpers with the Broker

If you want to make use of hooks, you will need to register your helpers
early — typically in the bootstrap or an early running plugin. To do this,
you register them with the broker:

Zend_Controller_Action_HelperBroker::addHelper(
    new Foo_Helper_Bar()
);

However, another reason to register with the broker is to ensure that your
custom helpers are found. To this end, you can simply tell the helper broker
the class prefix of your helpers, so it knows where to find them:

// By class prefix:
Zend_Controller_Action_HelperBroker::addPrefix('Foo_Helper');

// Alternately, providing the path to classes with that prefix, if they 
// are not on the include_path:
Zend_Controller_Action_HelperBroker::addPath($path, 'Foo_Helper');

Adding a path or prefix only tells the broker where to look for
helpers — it doesn’t instantiate them. If you want a helper loaded before
the dispatch cycle, so that event hooks can be utilized, you will still need
to either add an instance of the helper to the broker, or attempt to
retrieve it (which will create an instance). Which brings us to our next
topic.

Retrieving Helpers from the Broker Statically

Sometimes you may find that you want to utilize an action helper outside an
action controller — perhaps to configure it, or because it offers
functionality you need. The static method getStaticHelper() is
used to perform this.

As an example, I often find I need to configure the ViewRenderer — for
instance, to set some default view helper paths. I can do so as follows:

$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer');

$viewRenderer->initView(); // make sure the view object is initialized
$viewRenderer->view->addHelperPath($path); // set a helper path

It’s a lot to type, but you typically won’t need such functionality often.
However, the functionality it provides is useful — since only one instance
of a particular helper can exist in the broker at any given time, you can be
assured that you’re configuring it globally.

Creating Your Own Helper

Action helpers should extend the
Zend_Controller_Action_Helper_Abstract class. That class
contains the following utility methods:

  • setActionController(), for setting the current action
    controller
  • getActionController(), for retrieving the current action
    controller
  • getFrontController(), for retrieving the current front
    controller instance
  • getRequest(), for retrieving the current request object
    (uses the action controller first, then looks in the front controller)
  • getResponse(), for retrieving the current response object
    (uses the action controller first, then looks in the front controller)
  • getName(), to return the name of the helper

In addition, you can also define any of the event hook methods as listed
previously.

You can have your action helper do anything you want at this point. If it
will have a common action, you may want to expose it via the
direct() method, as detailed earlier. Otherwise, anything goes.

Example: Form Retrieval Helper

Now that we’ve learned about action helpers, let’s create one.

For our example, let’s say you have a suite of controllers that each use one
or more forms; furthermore, let’s say that a given form may be used in
multiple controllers. We’re going to create a helper that allows you to
fetch a form by class name.

We’ll assume that form classes are stored in the ‘forms’ subdirectory of the
current module. We’ll also assume that they are namespaced with the current
module (unless we’re in the default module), plus the prefix ‘Form_’; e.g.,
if we had a ‘news’ module, forms would be prefixed with ‘News_Form_’.
Finally we’ll use the name passed to the helper to determine which form
class to load, using the prefix. We’ll primarily use the
direct() method to interact with the helper, as we only really
have one thing we want the helper to do — load forms.

/**
 * Action Helper for loading forms
 * 
 * @uses Zend_Controller_Action_Helper_Abstract
 */
class My_Helper_FormLoader extends Zend_Controller_Action_Helper_Abstract
{
    /**
     * @var Zend_Loader_PluginLoader
     */
    public $pluginLoader;

    /**
     * Constructor: initialize plugin loader
     * 
     * @return void
     */
    public function __construct()
    {
        $this->pluginLoader = new Zend_Loader_PluginLoader();
    }

    /**
     * Load a form with the provided options
     * 
     * @param  string $name 
     * @param  array|Zend_Config $options 
     * @return Zend_Form
     */
    public function loadForm($name, $options = null)
    {
        $module  = $this->getRequest()->getModuleName();
        $front   = $this->getFrontController();
        $default = $front->getDispatcher()
                         ->getDefaultModule();
        if (empty($module)) {
            $module = $default;
        }
        $moduleDirectory = $front->getControllerDirectory($module);
        $formsDirectory  = dirname($moduleDirectory) . '/forms';

        $prefix = (('default' == $module) ? '' : ucfirst($module) . '_')
                . 'Form_';
        $this->pluginLoader->addPrefixPath($prefix, $formsDirectory);

        $name      = ucfirst((string) $name);
        $formClass = $this->pluginLoader->load($name);
        return new $formClass($options);
    }

    /**
     * Strategy pattern: call helper as broker method
     * 
     * @param  string $name 
     * @param  array|Zend_Config $options 
     * @return Zend_Form
     */
    public function direct($name, $options = null)
    {
        return $this->loadForm($name, $options);
    }
}

Place the above in a file named ‘FormLoader.php’ in the “My/Helper/”
directory of your library (or any directory on your include_path).

Okay, now, how would we use it? Let’s assume we’re in the LoginController in
the default module, and want to load the ‘login’ form. We’d name it
‘Form_Login’, and place the class file in ‘forms/Login.php’ in our
application directory:

application/
    controllers/
        LoginController.php
    forms/
        Login.php - Contains class 'Form_Login'

In our bootstrap file or an early-running plugin, we’d make sure that we
tell the broker where to find our helpers:

Zend_Controller_Action_HelperBroker::addPrefix('My_Helper');

And, finally, in our controller, we can now grab our form using the helper:

$loginForm = $this->_helper->formLoader('login');

Seem like a lot of work to simply load a form? Consider this: as long as you
follow the rules outlined by our FormLoader helper, you can now use this in
all your action controllers. So, you may have a UserController, and
need to grab the registration form:

$regForm = $this->_helper->formLoader('registration');

Additionally, once you’ve registered a particular helper prefix (e.g.,
‘My_Helper’), you can drop other helpers in that same location, and they’ll
automatically be found by the broker — you won’t have additional setup past
that original call to add the prefix to the broker.

The point here is that action helpers help you
DRY up your code — you push
the bits and pieces you think you’ll use again and again in your controllers
to your action helpers. After a while, you’ll have a library of
controller-related functionality that you can draw on for other projects –
without needing your own, custom base class for action controllers, which
ultimately leaves your library more extensible and flexible.

About Matthew Weier O'Phinney

Matthew is an open source software architect, specializing in PHP. He is currently project lead for Zend Framework, a project with which he has been involved since before the first public preview release. He is a Zend Certified Engineer, and a member of the Zend Education Advisory Board, the group responsible for authoring the Zend Certification Exam. He contributes to a number of open source projects, blogs on PHP-related topics, and presents talks and tutorials related to PHP development and the projects to which he contributes. You can read more of his thoughts on his blog, weierophinney.net/matthew/.

View all posts by Matthew Weier O'Phinney

12 Responses to “Action Helpers in Zend Framework”

  1. stricks1984 Says:

    I had a module MyModule and to make this work I needed to change the loadForm function to:
    [code]
    public function loadForm($name, $options = null)
    {
    $module = $this->getRequest()->getModuleName();
    $front = $this->getFrontController();
    $default = $front->getDispatcher()
    ->getDefaultModule();
    if (empty($module)) {
    $module = $default;
    }
    $moduleDirectory = $front->getControllerDirectory($module);
    $formsDirectory = dirname($moduleDirectory) . '/forms';

    // Rename the module from my-module to MyModule
    // WARNING: Do this after the moduleDirectory is created.
    $module = explode("-", $module);
    $module = array_map("ucfirst", $module);
    $module = implode("", $module);

    $prefix = (('default' == $module) ? '' : ucfirst($module) . '_')
    . 'Form_';
    $this->pluginLoader->addPrefixPath($prefix, $formsDirectory);

    $name = ucfirst((string) $name);
    $formClass = $this->pluginLoader->load($name);

    return new $formClass($options);
    }
    [/code]

  2. joedevon Says:

    P.S. Sorry if I sounded a bit grumpy in the last post. I actually *was* grumpy last night because I spent a good chunk of time on the bug instead of what I planned to do.

  3. joedevon Says:

    These articles are wonderful, but I wish after writing an article like this, the documentation was reviewed and bad advice updated. Integrating the best practices into the documentation is vital.

    People are being led down the wrong path regularly.

    Now I’ve got this major problem:

    http://stackoverflow.com/questions/3774768/how-to-handle-zend-exceptions-in-predispatch-or-init-of-an-action-when-using-a-b

    and refactoring lots of code that’s been written in more than one project will not be fun.

    On the positive side, the new comments feature in the docs are wonderful, and I will be linking to this article on the offending docs page.

    I wish we could insert the comments on a per paragraph basis like @padraicb’s Survive the Deep End Book.

  4. jplazaarguello Says:

    As you mentioned in your article, one of the uses of a Action_Helper could be for authentication purposes. I’m now building my second ZF application and I’m using the same authentication Helper I used for my first app.

    Everything seemed to be working smoothly ( Important note: there’s big difference between my 1st and 2nd app, the second one has a modular project structure ) with the default module. Unluckily for other modules, the preDispatch() hook of the Action Helper is not being called, despite of the helper is being instantiated by the HelperBroker.

    Any clues?

    PS: the authentication works fine if I explicitly call the preDispatch method inside the init() Controller Action method.

  5. josh@vertive.com Says:

    After explaining why and how we extended Zend_Controller_Action to suit our needs, and after blank stares and scolding from Ralph Schindler, I now understand why what we were doing should be done with action helpers. Great article.

  6. mrkabuya Says:

    First of all, thank you for such an awesome article.
    I have tried this action helper and it works like a charm.
    However, I noticed that the validators in my form stopped to display ‘failed validation’ messages. On the other hand, the decorators work fine and keep displaying ‘failed authentication’ messages.
    Any comments/advise on this.

    This is how my standard form class looks like:

    class Form_Login extends Zend_Form {

    public function init() {
    $username = $this->addElement(‘text’, ‘username’, array(
    ‘filters’ => array(‘StringTrim’, ‘StringToLower’),
    ‘validators’ => array(
    ‘Alpha’,
    array(‘StringLength’, false, array(3, 20)),
    ),
    ‘required’ => true,
    ‘label’ => ‘Your username:’,
    ));

    $password = $this->addElement(‘password’, ‘password’, array(
    ‘filters’ => array(‘StringTrim’),
    ‘validators’ => array(
    ‘Alnum’,
    array(‘StringLength’, false, array(6, 20)),
    ),
    ‘required’ => true,
    ‘label’ => ‘Password:’,
    ));

    $login = $this->addElement(‘submit’, ‘login’, array(
    ‘required’ => false,
    ‘ignore’ => true,
    ‘label’ => ‘Login’,
    ));

    $this->setDecorators(array(
    ‘FormElements’,
    array(‘HtmlTag’, array(‘tag’ => ‘dl’, ‘class’ => ‘zend_form’)),
    array(‘Description’, array(‘placement’ => ‘prepend’)),
    ‘Form’
    ));
    }
    }

  7. neriodavid Says:

    The ViewRenderer helper is really very useful while you are writing Action Controller. However, Action Controller needs interact with model too.
    Is there a way in Zend Framework to autoload the model classes in the models folder?

    I need use

    $frontController = Zend_Controller_Front::getInstance();

    require_once dirname($frontController->getControllerDirectory(‘moduleName’))
    . ‘/models/TestModel.php’;

    every time I need communicate with model class.

  8. dbowen Says:

    I have been extending Zend_Controller_Action to add some default functionality but that is starting to break down quickly. This is a lot more flexible.

    One thing that has plagued me is where to put my acl checking. I’d love to see a tutorial for combining action controllers and Zend_Acl to control access to different modules, controllers, and actions.

  9. robinsk Says:

    Good work in this article! It will now be easy to paste the URL to this page when people start asking questions about action helpers in #zftalk :)

  10. julien_braure Says:

    In complement, I will say :

    the empty init() function of Zend_Controller_Action is called in the end of Zend_Controller_Action->__construct(), and I think it’s a nice way for developper to not forget about invoking parent::__construct() (with the several args) in its concrete controllers.

    So, if you extends Zend_Controller_Action to create an abstract base class for your application, it will result in a mess during the construction process.

    You can also not use init() method, in order to bypass the problem, but is all the team aware of that ?

    worst : You can put the code in the init() of you abstract base controller, and be forced to call parent::init() in your concretes controllers

    Definitely a bad idea ! Use actions helpers !!

    regards.

  11. justinwoods Says:

    Awesome article! This should be made a part of the official online documentation, in my humble opinion.