Using Action Helpers To Implement Re-Usable Widgets

Using Action Helpers To Implement Re-Usable Widgets

I had a twitter/IRC exchange yesterday with Andries Seutens and Nick Belhomme regarding applications that include widgets within their layout. During the exchange, I told Andriess not to use the action() view helper, and both Andriess and Nick then asked how to implement widgets if they shouldn’t use that helper. While I ended up having an IRC exchange with Nick to give him a general idea on how to accomplish the task, I decided a longer writeup was in order.

Background

The situation all started when Andries tweeted asking about what he considered some mis-behavior on the part of the action() view helper — a situation that turned out not to be an issue, per se, but more a case of bad architecture within Zend Framework. His assumption was that calling action() would fire off another circuit of the front controller’s dispatch loop — which would mean he could rely on plugins he’d established to fire. However, action() does nothing of the sort. It instead pulls the dispatcher from the front controller, and manually calls dispatch() on a new action. As such, action helpers will trigger, but no front controller plugins will. Additionally, if a redirect or “forward” condition is detected, it simply returns an empty string.

The helper was done this way because Zend Framework does not render views a single time — it instead renders after each action, and accumulates views to render in the layout. If we were accumulating view variables and rendering once, and if we were using a finite state machine of some sort, we could probably operate the way one would expect — within the dispatch loop. Since we don’t, any solution around looping over actions (such as the ActionStack action helper/front controller plugin) or rendering the content of executing an action will be a hack. Note: ZF2’s MVC layer may make this possible… though still not necessarily recommended.

There are other reasons to avoid the use of these solutions, though. If you are invoking additional controller actions in order to help populate your view, you’re likely putting domain logic into your controllers. Think about it. The controller should only be responsible for taking the input, funneling it to the correct model or models, and then passing information on to the views

With that in mind, here’s the approach I recommended to Nick and Andries.

The Secret Weapon: Action Helpers

I’ve blogged about action helpers before. They’re a built-in mechanism in Zend Framework to allow you to extend your action controllers in a way that uses composition instead of inheritance.

One approach to widgets for Zend Framework makes use of these. Consider the following “user” module:

via read more.

Zend Framework: Module Specific Layout Plugin

Graham Anderson posted an useful workaround for the module specific layout problem;
The default layout plugin will accept a stack of paths in LIFO order.
This allows a very simple hack to always ensure that any module can have it’s own default layout which will automatically override the default module layout.


class App_Controller_Plugin_Layout extends Zend_Controller_Plugin_Layout {

public function __construct ($layout = null)
{
parent::__construct ($layout);
}

public function preDispatch(Zend_Controller_Request_Abstract $request)
{
// Insert current module layout dir to to overide any default layouts
if ( $request->getModuleName() != 'default' ) {

$layoutPath = APPLICATION_PATH . '/modules/' .
$request->getModuleName() . '/views/layouts';

$paths = array();
$paths[] = $this->getLayout()->getViewScriptPath();
$paths[] = $layoutPath;

$this->getLayout()->setViewScriptPath($paths);
}
}
}

Asssuming you set the following application config value:


resources.layout.layout = "default"

Now any module with a default.phtml layout will override the default module layout.

e.g APPLICATION_PATH/modules/foobar/views/layouts/default.phtml

Cheers the noo,
Graham