Per Module Zend_Layout

Sometimes when you are building a web application, you want to use different layouts for different parts of the site. For example, in a content management system, you may want one layout for normal users and another, completely different layout for site administrators.

Assuming that you have the standard zend directory structure, you will have something like this:

/application
  /admin
    /controllers
    /layouts
    /views
  /default
    /controllers
    /layouts
    /views
  /feeds
    /controllers
    /views

Or something of the sort. When the user is in the admin section we want to use the layout folder from /admin, and when they are in the default section or in the feeds section of the site we would like to use the layout folder from /default.

We could simply go through all the controllers in the admin section and set the layout path in the init or preDisptach function:

public function preDispatch(){
    Zend_Layout::getMvcInstance()->setLayoutPath('../application/admin/layouts');
}

This may work well for this simple example. However, every time we wish to add a controller to the admin section it would be necessary to setup the layout path and if ever we want to change the layout path for the admin module, there would be a lot of search-replacing.

A Much better solution is to write a plugin for the Zend Front controller. This plugin extends the Zend_Controller_Plugin_Abstract type and will be run each time the front controller is invoked, before anything is rendered.

Our plugin must have some very basic functionality:

  1. Ability to register a layout for use with a specified module
  2. Ability to fall back to the default layout if there is no layout registered for the current module

The Layout Plugin is as follows:

class Module_LayoutPlugin extends Zend_Controller_Plugin_Abstract {

    /**
     * Array of layout paths associating modules with layouts
     */
    protected $_moduleLayouts;

    /**
     * Registers a module layout.
     * This layout will be rendered when the specified module is called.
     * If there is no layout registered for the current module, the default layout as specified
     * in Zend_Layout will be rendered
     *
     * @param String $module        The name of the module
     * @param String $layoutPath    The path to the layout
     * @param String $layout        The name of the layout to render
     */
    public function registerModuleLayout($module, $layoutPath, $layout=null){
        $this->_moduleLayouts[$module] = array(
            'layoutPath' => $layoutPath,
            'layout' => $layout
        );
    }

    public function preDispatch(Zend_Controller_Request_Abstract $request){
        if(isset($this->_moduleLayouts[$request->getModuleName()])){
            $config = $this->_moduleLayouts[$request->getModuleName()];

            $layout = Zend_Layout::getMvcInstance();
            if($layout->getMvcEnabled()){
                $layout->setLayoutPath($config['layoutPath']);

                if($config['layout'] !== null){
                    $layout->setLayout($config['layout']);
                }
            }
        }
    }
}

The $_moduleLayouts property maintains the mapping between modules and layouts.

The registerModuleLayout function allows us to register a layoutpath and an (optional) layout name for a module.

The actual magic happens in the preDispatch function (which is called by the front controller before dispatching the request). The function checks to see if there is a layout set for the current module; if there is, it switches the layout path to use the module-specific path. Next it checks to see if there is a layout name associated with the layout registered for this module. If there is, it uses it, otherwise it falls back to the default layout name.

In our bootstrapper file (html/index.php) we must setup the default layout and module layout paths and then register the plugin with the front controller. The following code snippit is only a sliver of the bootstrapper and assumes that $controller is an instance of Zend_Controller_Front (eg: $controller = Zend_Controller_Front::getInstance();)

//setup the layout
Zend_Layout::startMvc(array(
    'layoutPath' => '../application/default/layouts',
    'layout' => 'main'
));

$layoutModulePlugin = new Module_LayoutPlugin();
$layoutModulePlugin->registerModuleLayout('admin','../application/admin/layouts');

$controller->registerPlugin($layoutModulePlugin);

It is important to setup the default layout so the plugin has a layout to fall back to if no module-specific layout is registered.

I hope that this tutorial has served as a quick and easy introduction to Zend Front Controller plugins and module-specific layouts.