Front Controller Plugins in Zend Framework

April 14, 2008

Tutorials, Zend Framework

Like Action Helpers, which I’ve discussed in a previous
article, Front Controller Plugins in href="http://framework.zend.com/">Zend Framework are often
considered an esoteric, advanced subject. They are, however, remarkably
simple to implement, and provide an easy way to extend the functionality and
behavior of your entire web application.

What is a Front Controller Plugin?

In Zend Framework, plugins are used to listen for certain events in the
front controller. Events in the front controller bookend each of the major
actions that occur: routing, the dispatch loop, and dispatching an
individual action. The actual hooks defined are:

  • routeStartup(): prior to routing the request
  • routeShutdown(): after routing the request
  • dispatchLoopStartup(): prior to entering the dispatch loop
  • preDispatch(): prior to dispatching an individual action
  • postDispatch(): after dispatching an individual action
  • dispatchLoopShutdown(): after completing the dispatch loop

As you start thinking about the hooks listed above, a few questions may come
to mind, such as, “Why is there both a routeShutdown()
and dispatchLoopStartup() hook, when nothing occurs
between them?” The main reason is because of semantics: you may want to do
something to alter the results of routing after routing, or you may want to
modify the dispatcher prior to entering the dispatch loop, and these are
semantically different. Having different hooks helps keep these distinctions
clear.

Another question I’ve fielded is, “Why are there
dispatchLoopStartup/Shutdown() hooks and
pre/postDispatch() hooks?” In ZF, we actually have a dispatch
loop — which allows you to use the router to create multiple
requests for dispatch, or use logic in your controllers to request
additional actions. Thus, we have hooks on either end of the loop
(dispatchLoopStartup() and
dispatchLoopShutdown()), as well as within the loop
bookending the actual dispatch (preDispatch() and
postDispatch()).

An actual plugin is simply a class that extends
Zend_Controller_Plugin_Abstract. That class defines empty
methods for each of these hooks. A concrete plugin then simply overrides any
of these methods necessary for implementing its functionality. In all cases
except for dispatchLoopShutdown(), the hook methods take a
single $request argument of type
Zend_Controller_Request_Abstract (the base request class within
the ZF MVC):

public function preDispatch(Zend_Controller_Request_Abstract $request)
{
}

I will often speak of “early-running plugins” or “late-running plugins”. The
former refers to routeStartup(), routeShutdown(),
and dispatchLoopStartup() — hooks that run before the dispatch
loop begins, and thus would have application-wide effects. Late-running
plugins refer to postDispatch() and
dispatchLoopShutdown() — more typically the latter — plugins
that trigger after actions have been dispatched.

Registering Plugins with the Front Controller

Plugins themselves need to be instantiated and registered with the front
controller, which can be done with
Zend_Controller_Front::registerPlugin():

$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new FooPlugin());

This can be done at any time during the request. However, only hooks that
are triggered after the plugin is registered will be called.

You can optionally pass a stack index when registering plugins. This allows
you to specify an order in which plugins are triggered. If no stack index is
provided, then plugins are triggered in the order in which they are
registered. When a stack index is provided, then that index will be honored
for that plugin.

You can specify the stack index as the second parameter when registering the
plugin; the index should be numeric, and a lower number will indicate
earlier execution:

$front->registerPlugin(new FooPlugin(), 1);   // will trigger early
$front->registerPlugin(new FooPlugin(), 100); // will trigger late

Retrieving Plugins from the Front Controller

Occasionally, you may have need to gather state information from a plugin,
or configure it, after it has been registered with the front
controller. You can retrieve a plugin by passing the plugin’s class name to
Zend_Controller_Front::getPlugin():

$front     = Zend_Controller_Front::getInstance();
$fooPlugin = $front->getPlugin('FooPlugin');

How Plugins are used in Zend Framework

Okay, now that you know what a plugin is, and how to register one with the
front controller, the burning question is: what uses exist for plugins? To
answer this question, let’s first look at how plugins are used in existing
ZF components.

Zend_Layout

Zend_Layout can optionally be used with the MVC components, and
when it is, it registers a plugin with the front controller. This plugin
listens to the postDispatch() hook, and is registered with a
late stack index to ensure it runs after all other plugins have
executed, as well as to ensure no other actions exist to loop over.

The Layout plugin allows us to implement a Two Step View pattern in Zend
Framework; it captures the content in the response, and then passes it to
the layout object to process so that the content can be injected in the
layout view script.

Error Handling

As another example, the ErrorHandler plugin listens to
postDispatch(), too, also with a late stack index. It checks to
see if an application exception has been registered with the response, and,
if so, will request another action for the dispatch loop to iterate over –
the error action in the error controller, so as to report the exception.

Potential Uses for Plugins in Your Applications

Now that you’ve seen some concrete examples of how plugins are already used,
what potential uses can you find for them in your own applications? Some
examples that are often given include:

  • Application initialization
  • Caching
  • Routing initialization and customization
  • Authentication and ACLs
  • Output filter of final XHTMl

Example: Application Initialization Plugin

Let’s consider the first idea, application initialization. In most examples
of Zend Framework MVC apps, we show a bootstrap file that contains the
entire application initialization — loading configuration, loading all
plugins, initializing the view and database, etc. This works well, but it
can lead to a somewhat sloppy file, and also leaves the potential to leak
important information about your system should the file ever be displayed
without processing (ala the Facebook fiasco last year).

We can solve this by pushing most initialization into an early-running
plugin — specifically, a routeStartup() plugin. Here’s an example:

/**
 * Application initialization plugin
 * 
 * @uses Zend_Controller_Plugin_Abstract
 */
class My_Plugin_Initialization extends Zend_Controller_Plugin_Abstract
{
    /**
     * Constructor
     * 
     * @param  string $env Execution environment
     * @return void
     */
    public function __construct($env)
    {
        $this->setEnv($env);
    }

    /**
     * Route startup hook
     * 
     * @param  Zend_Controller_Request_Abstract $request
     * @return void
     */
    public function routeStartup(Zend_Controller_Request_Abstract $request)
    {
        $this->loadConfig()
             ->initView()
             ->initDb()
             ->setRoutes()
             ->setPlugins()
             ->setActionHelpers()
             ->setControllerDirectory();
    }

    // ...
}

Your bootstrap would then look like this:

require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new My_Plugin_Initialization('production'));
$front->dispatch();

I won’t go into the various methods called here, as they should be fairly
self-explanatory. The main thing to get from this, however, is that we’ve
now moved the complexity of the bootstrap into a class, and also provided a
way to group common tasks — helping make our application setup more
maintainable. We do this by simply leveraging the infrastructure provided by
the plugin system.

Example: Caching Plugin

As another example, consider a simple caching plugin. Oftentimes most pages
on a site are remarkably static. We can build a simple plugin that utilizes
Zend_Cache to seed and pull from the cache.

Our cache criteria will be as follows:

  • Cache configuration will be passed to the constructor
  • Only GET requests will be cached
  • Redirects will not be cached
  • Any given action can tell the plugin to skip caching

For this plugin, we will need to implement two different hooks. First,
routing needs to have finished, but the dispatch loop should not yet have
run when we test to see if we have a cache hit. Second, we want to cache
only when we’re certain that all actions have finished. So, we’ll implement
the dispatchLoopStartup() and
dispatchLoopShutdown() hooks to accomplish our task.

/**
 * Caching plugin
 * 
 * @uses Zend_Controller_Plugin_Abstract
 */
class My_Plugin_Caching extends Zend_Controller_Plugin_Abstract
{
    /**
     *  @var bool Whether or not to disable caching
     */
    public static $doNotCache = false;

    /**
     * @var Zend_Cache_Frontend
     */
    public $cache;

    /**
     * @var string Cache key
     */
    public $key;

    /**
     * Constructor: initialize cache
     * 
     * @param  array|Zend_Config $options 
     * @return void
     * @throws Exception
     */
    public function __construct($options)
    {
        if ($options instanceof Zend_Config) {
            $options = $options->toArray();
        }
        if (!is_array($options)) {
            throw new Exception('Invalid cache options; must be array or Zend_Config object');
        }

        if (array('frontend', 'backend', 'frontendOptions', 'backendOptions') != array_keys($options)) {
            throw new Exception('Invalid cache options provided');
        }

        $options['frontendOptions']['automatic_serialization'] = true;

        $this->cache = Zend_Cache::factory(
            $options['frontend'],
            $options['backend'],
            $options['frontendOptions'],
            $options['backendOptions']
        );
    }

    /**
     * Start caching
     *
     * Determine if we have a cache hit. If so, return the response; else,
     * start caching.
     * 
     * @param  Zend_Controller_Request_Abstract $request 
     * @return void
     */
    public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
    {
        if (!$request->isGet()) {
            self::$doNotCache = true;
            return;
        }

        $path = $request->getPathInfo();

        $this->key = md5($path);
        if (false !== ($response = $this->getCache())) {
            $response->sendResponse();
            exit;
        }
    }

    /**
     * Store cache
     * 
     * @return void
     */
    public function dispatchLoopShutdown()
    {
        if (self::$doNotCache
            || $this->getResponse()->isRedirect()
            || (null === $this->key)
        ) {
            return;
        }

        $this->cache->save($this->getResponse(), $this->key);
    }
}

During dispatchLoopStartup(), the plugin does several things.
First, it checks to see if certain initial conditions are met — for
instance, that we have a GET request. It then sets up a cache key, based on
the current request, and checks to see if we have a cache hit. If so, it
sends the response from the cache. In dispatchLoopShutdown(),
we check to see if we’ve indicated that the plugin should not cache, if it’s
a redirect, or if for some reason we have no cache key; if any of these
conditions are met, we return early. Otherwise, we cache the response
object.

How do you suppress caching for an action? You may have noticed the public
static member, $doNotCache. In an action, simply set this to a
true value:

My_Plugin_Caching::$doNotCache = true;

This will suppress storing a cache for the current request, and thus mean no
cache hit is ever found on subsequent requests to the same location.

Savvy readers may be wondering why I used dispatchLoopStartup()
instead of routeStartup(), particularly as I’m looking only at
the request object. The rationale is for future considerations I may need to
make: I could easily expand on this to allow specifying specific routes,
modules, controllers, or actions that should never be cached; specifying
alternate cache keys for custom routes (as you may need logic to include URI
parameters as part of the caching logic to ensure that individual resource
pages are cached separately), etc. These would all depend on routing having
finished.

However, the main purposes of this example stand: using multiple hooks to
achieve an overall goal — caching — as well as methods for interacting
with a plugin.

Forwarding to Additional Actions

One topic that is asked quite often is how to forward to another action, or
determine if the current request is already forwardng to another action.

The request object contains that information in a special flag,
isDispatched. When that flag is false, then the current request
has not yet been dispatched (typically true when checking prior to the
dispatch loop, or after a call to _forward() in an action); in
other words, it’s a new request. If the flag is true, then that indicates
that the current request has already been dispatched.

Thus, to dispatch another action, simply update the state of the
request, and set the flag to false. As an example, to forward to
SearchController::formAction(), you might have code like the
following in your hook:

$request->setModuleName('default')
        ->setControllerName('search'))
        ->setActionName('form')
        ->setDispatched(false);
}

To check and see if a request has been dispatched, do the following:

if ($requst->isDispatched()) {
    // request has already been handled
} else {
    // new request, not yet dispatched
}

Note: you may want to check out the ActionStack plugin and helper, added in
1.5.0, which allows you to add actions to a stack; the plugin pulls off that
stack on each iteration of the dispatch loop (unless another action is
already waiting to dispatch), allowing you to pass several actions at once
for the application to loop over.

Other Considerations

What sorts of things should you not do with plugins? My rule of
thumb is that if the functionality requires any sort of introspection of or
interaction with the action controller, you should use an action helper
instead. Additionally, if the functionality will be enabled based on the
module, controller, or action — i.e., if only a subset of your application
depends on the functionality or will be affected by it — action helpers are
again a better choice.

However, if the functionality deals with the site as a whole — such as the
initialization and caching plugin examples presented, plugins are the
appropriate approach.

Conclusion

Hopefully this tutorial has shown you that plugins are not an esoteric
topic, but instead something rather trivial to implement. Plugins provide an
excellent way to add functionality at some key flex points in Zend Framework
MVC applications, and can provide application wide coherency and
configuration.

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

19 Responses to “Front Controller Plugins in Zend Framework”

  1. erkinaka Says:

    in my actions, my codes run twice. for example my db queries run twice and records are inserting twice. When i use "disablelayout" command it works normally. but ihave to use my layout. how can i solve this problem

    in my bootsrap file code;

    protected function _initAutoload(){

    //
    $this->_acl= new Application_Model_Acl();
    $this->_auth= Zend_Auth::getInstance();

    $fc = Zend_Controller_Front::getInstance();
    $fc ->registerPlugin(new Application_Plugin_AccessCheck($this->_acl,$this->_auth));

    }

    protected function _initViewHelpers(){

    $this->bootstrap(‘layout’);
    $layout=$this->getResource(‘layout’);
    $view=$this->layout->getView();

    $view->addHelperPath("ZendX/JQuery/View/Helper", "ZendX_JQuery_View_Helper");
    ZendX_JQuery::enableView($view);

    $authSession = new Zend_Session_Namespace(‘Zend_Auth’);
    $authSession->setExpirationSeconds(600);
    ……………

  2. _____anonymous_____ Says:

    Thanks for the nice article!

    It totally makes sense to me to use controller plugins to initialize things such as the logged-in user object, or a database object.

    In this case I would like to pass these objects down to the action controller, or even the view.

    Obviously I can store these things in the registry, and then let the view get them from there, but I don’t really like that. I would like to avoid these things which smell like globals.

    I could also work with action helpers, but these want to be called from within every single controller. Which means, I have to either duplicate the helper call, or make a controller base class, which I both don’t want.

    —-

    This is my solution for passing things from the plugin to the action controller:
    - The plugin is initialized in the bootstrap, with the front controller as a constructor argument. This means, the plugin is now aware of the front controller.
    - The plugin method does a $this->_frontController->setParam($key, $value). Zend automatically passes such values to the action controller, where they can be retrieved using $this->getInvokeArg($key).

    Well, I have not yet tested that this works, but this is how I understand what I read so far.

    —-

    What I still don’t know is how to pass things to the view object, or directly to a specific controller, or if that is possible at all.

  3. oreales Says:

    This graph can help to get clear the lifecycle of Frontend Plugins during dispatch proccess:

    http://surlandia.com/2008/11/03/zend-framework-plugins-action-helpers-and-controllers-life-cycle-during-dispatch/

  4. beberlei Says:

    The method getCache() in the Action caching example via plugin is missing. It may look something like this:

    public function getCache()
    {
    if( ($response = $this->cache->load($this->key)) != false) {
    return $response;
    }
    return false;
    }

    Very nice tutorial!

  5. julien_braure Says:

    * Excuse me for the typo in the previous comment *

    One odd thing in the class My_Plugin_Caching

    The isGet() method was NOT available on a Zend_Controller_Request_Abstract.

    I did same kind of use with getRequestUri() method in one plugin of my project

    It works on runtime but this not really clean, POO speaking

    What do you think about this ?

  6. julien_braure Says:

    One odd thing in the class My_Plugin_Caching

    The isGet() method was available on a Zend_Controller_Request_Abstract.

    I did same kind of use with getRequestUri() method in one plugin of my project

    It works on runtime but this not really clean, POO speaking

    What do you think about this ?

  7. neriodavid Says:

    As it stated: "Action plugins need to be instantiated and registered".

    Is there a way to register and instantiate multiple plugins at once? Or you have to register each plugin one by one with optional stackindex?

    Are the built-in plugins shipped with Zend Framework registered and ready to use by defualt?

  8. weierophinney Says:

    @njcalugar: Sorry for not getting back to you sooner; there are no
    notifications sent by DevZone when new comments arrive. As to your question, Getting the front controller is quite simply
    Zend_Controller_Front:: getInstance(). However, the plugins
    have a convenience method for retrieving it as well:
    $this->getFrontController(). By the way, you’ll need to change
    setControllerDirectory() to
    addControllerDirectory() in your example for that to work.

  9. joselee Says:

    I am emplementing authentication in my application using Zend_Auth Component which works fine, I need to check authentication on every request by using plugin, in which files/Directory do I have to plugin Class codes?

  10. njcalugar Says:

    Matt,

    Great article, love the slim bootstrap.

    Matt or anyone,

    In the initialization example there is a function setControllerDirectory. Is this the correct way to access the singleton instance of the front controller or is it in scope some other way?

    private function setControllerDirectory()
    {
    $frontController = Zend_Controller_Front::getInstance(); $frontController->setControllerDirectory(‘../application/controllers’);
    }

    Thanks,

    -Nick

  11. weierophinney Says:

    @psilen: Please ask such questions on the mailing lists, where you can get
    better and more detailed support. You can find information on the mailing
    lists on the Framework Archives page.

  12. weierophinney Says:

    @flammon404: As mentioned in the article, it’s best to place your plugins in
    your library/ directory, preferably using your own namespace. For example,
    the “My_Plugin_Initialization” plugin in the article would be in
    library/My/Plugin/Initialization.php.

  13. flammon404 Says:

    Thank you for the article. I have a question. Where should the plugin class file be placed?

    I thought that the application/plugins directory would be nice but I don’t know how to set that as my plugins directory. I don’t want to put it in the same public directory as index.php because it would defeat the purpose of the security feature that you talked about.

  14. psilen Says:

    Nice article!

    But i have a problem when i try to change action, controller or module. I have a plugin that check with the ACL if you have access to the current moduler/controller/action, if you dont have access, it redirects you to an errorpage, that works like i expected, but you have access to the current action, then it runs the action twice, i can see in the DBProfiler that all of my queries are executed twice, and it’s only the action not the view.

    If i try to set dispatched to false i can’t even load the page, it gets timed out.

  15. weierophinney Says:

    @foundline: personally, I like to handle redirects via the web server. It’s faster and consumes fewer resources. However, I see nothing wrong with what you suggest, and have myself done similarly. :-)

  16. weierophinney Says:

    @wizhippo: yes, that’s why I had an initRoutes() method in that plugin, but I should have noted that if you use this technique, you would need to implement that method if you want to set custom routes.

  17. wizhippo Says:

    If you register a plugin in the setPlugins() method be aware that the newly register plugins routeStartup will not be called as the routeStartup hook is already being executed.

  18. foundline Says:

    I recently used a Front Controller Plugin to redirect non-www traffic to the www version of a website on routeStartup(). Sure, I could have implemented this a couple of different ways. I could have simply done this in my .htaccess file but the advantage of putting in the application itself is that I can easily configure the expected HTTP host. In other words, I have one configuration file to change for beta.example.org, http://www.example.org, etc. instead of changing a configuration file and the .htaccess file in each install. Another option would have been to simply do the redirect in my bootstrap file. However, this didn’t feel very OO and extensible to me – I like to keep my bootstrap code as small as possible. What do you think, does this seem like an appropriate problem to be solved by a Front Controller Plugin?

  19. skinauk Says:

    Matthew, your recent tutorials are very insightful and are allowing me to make better use of the tools ZF provides. Thanks and keep them coming!