Like Action Helpers, which I've discussed in a previous article, Front Controller Plugins in 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 requestrouteShutdown(): after routing the requestdispatchLoopStartup(): prior to entering the dispatch looppreDispatch(): prior to dispatching an individual actionpostDispatch(): after dispatching an individual actiondispatchLoopShutdown(): 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.

Comments
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.
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.
@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.
@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.
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
@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 changesetControllerDirectory()toaddControllerDirectory()in your example for that to work.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?
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 ?
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 ?
public function getCache()
{
if( ($response = $this->cache->load($this->key)) != false) {
return $response;
}
return false;
}
Very nice tutorial!
http://surlandia.com/2008/11/03/zend-framework-plugins-action-helpers-and-controllers-life-cycle-during-dispatch/
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.