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.

, , ,

  1. #1 by Pavel on August 27, 2008 - 2:02 pm

    Its very interesting your perspective. Personally I think Zend Framework development team should consider to include any official implementation about this issue, because this is a truly sharp way to work around it and solve it.

    Thanks for the tips, it was very helpful for me.

  2. #2 by David on September 17, 2008 - 6:14 am

    This was very helpful to me. Although I don’t see the usefulness of registering a layout as the registration would need to take place before the plugin code.

    I have implemented this in a more dynamic way for my application. I have a Module Setup Plugin that will add module paths to the include path and then also checks to see if a module has a folder layouts with a main layout file with the same name of the module. If so, I use your code above to setLayoutPath and setLayout. It works nicely and is dynamic.

    I can see maybe taking this one step further and creating a module registration system where the module configuration is read and based on that setting up the module environment including layout.

    Thanks again,
    David

  3. #3 by Antonio on November 10, 2008 - 9:03 am

    Hi. I am newbie to Zend Framework and i have a question about the layout management. In my application i have 3 modules accordingly the following directory structure.

    /application
    /admin
    /controllers
    /layouts
    /views
    /default
    /controllers
    /layouts
    /views
    /photogallery
    /controllers
    /views

    I use the “Module_LayoutPlugin” approch to switch between the default and the admin layouts.

    In the photogallery module there are different Controllers. Consider for example the CategoryController, a controller to manage the categories of the photogallery. In this controller there are the following actions: index, view, add, edit and delete. Now, when i am in the admin module and i want to add a category, i use this url http://mysite/photogallery/category/add but the application switch, obviously to the default layout while i want to remain in the admin layout.

    What’s the best strategy to fix it?

    Hello from Italy.

  4. #4 by Jason on November 15, 2008 - 7:15 pm

    Thanks for showing me how to do this.

  5. #5 by Andrew Chen on July 27, 2009 - 5:34 pm

    hello! How do I get ahold of you? I’d like to talk more.

    Here’s my bio: http://andrewchenblog.com/about/

    Write me back at voodoo at gmail, please. Thanks.

  6. #6 by Gites France on August 7, 2009 - 2:31 am

    Thanks – it was easy to follow.

  7. #7 by Deitrich Zook on September 15, 2009 - 8:08 pm

    Thanks for showing the short simple way and the more extensive way using individual layouts for various modules. I am going to use the simple way for the time being as I only have one controller in the new module that needs a custom layout.

    For anyone wanting an example based on this one, there is an example here:
    http://www.zfforums.com/zend-framework-components-13/model-view-controller-mvc-21/disable-layout-entire-module-1723.html

  8. #8 by EM on October 5, 2009 - 1:07 pm

    Thanks – it was easy to follow.

  9. #9 by DA on October 19, 2009 - 1:20 am

    I did the following:

    created a LayoutPlugin.php in the modules folder.
    included that into the public/index.php
    /setup the layout
    Zend_Layout::startMvc(array(
    ‘layoutPath’ => APPLICATION_PATH . ‘modules/default/layouts/scripts’,
    ‘layout’ => ‘layout’
    ));

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

    $controller->registerPlugin($layoutModulePlugin);

    and it keeps giving me an error

    Fatal error: Class ‘Zend_Controller_Front’ not found in C:\wamp\www\project\public\index.php on line 20

  10. #10 by behrang on May 22, 2010 - 9:04 pm

    Hi. I am newbie to Zend Framework I really need this but my problem is unfortunately I don’t know where to put the above code please explain actually path and file name.

  11. #11 by Dustin on May 22, 2010 - 10:31 pm

    behrang :

    Hi. I am newbie to Zend Framework I really need this but my problem is unfortunately I don’t know where to put the above code please explain actually path and file name.

    Now that there is the Zend_application Component, the plugin registration should be done in the Bootstrap Class File. The actual plugin file should be placed somewhere where the Autoloader will find it, or it can be directly required using require_once.

    For more info, please see the Zend Framework documentation at http://framework.zend.com/manual/en/zend.controller.plugins.html

    I haven’t tried this code on the newer versions of Zend. can anyone confirm its working?

  12. #12 by Seif on July 23, 2010 - 2:50 pm

    well , it’s a very clean , perfect and easy solution
    I was searching all the day and this is the best solution
    Thank you

  13. #13 by rad on December 7, 2010 - 3:54 pm

    Alternatively you can setup the module specific layout directory in you custom action controller:

    abstract class App_Controller_Action
    extends Zend_Controller_Action
    {

    /**
    * Returnns current module directory
    *
    * @return string
    */
    protected function getCurrentModuleDirectory() {
    return Zend_Controller_Front::getInstance()->getModuleDirectory(
    $this->getRequest()->getModuleName());
    }

    public function preDispatch(){
    parent::preDispatch();
    Zend_Layout::getMvcInstance()->setLayoutPath(
    $this->getCurrentModuleDirectory() . ‘/layouts/scripts’);
    }
    }

  14. #14 by Bazmo on February 9, 2011 - 4:21 pm

    Hi,

    I’ve just done a very similar plugin as this myself, however I made it invoke the postDispatch function. I’m curious if there is any need for it to be in pre/post. I’m guessing pre would be better as this then allows the controller to change it if it needs, in the action.

    One more issue I’m having is now that I’ve changed my layout path, my partials in my default layout path aren’t working.

    Cheers,

    Baz

  15. #15 by Rafael on May 21, 2011 - 1:44 pm

    Thanks!
    This was very helpful to me.

  16. #16 by amit on July 6, 2012 - 11:04 pm

    where i put this plugin file my dir is like this
    application
    admin
    controller
    models
    views
    default
    controller
    models
    views
    library
    public

  17. #17 by Dustin on July 8, 2012 - 6:11 pm

    The Module_LayoutPlugin class can go anywhere in your application that the Loader can find it. I tend to put them under library myself.

  18. #18 by manish singh on August 17, 2012 - 9:07 pm

    you can set own layout selector in few steps

    step 1:
    make module admin and default.

    step 2:
    create layout folder in each module as admin/layouts/scripts
    and default/layouts/scripts
    put into layout.phtml

    step 3:
    delete the layout.phtml file from Application/layouts/scripts.

    step 4:
    make the the Plugin folder inside library and make Plugin.php
    as

    class Plugin_Layout extends Zend_Controller_Plugin_Abstract {

    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
    $layoutPath = APPLICATION_PATH . ‘/modules/’ . $request->getModuleName() . ‘/layouts/scripts/’;
    Zend_Layout::getMvcInstance()->setLayoutPath($layoutPath);
    }
    }

    step 5:

    open Application/configs/Appication.ini file
    and edit it
    as

    ;resources.layout.layoutPath = APPLICATION_PATH “/layouts/scripts/”
    resources.layout.layout = “layout”
    ;register your plugin

    autoloaderNamespaces[] = “Plugin”
    resources.frontController.plugins[] = “Plugin_Layout”

    Step 6:

    open bootstrap file Application/Bootstrap
    put the code inside

    protected function _initAutoload() {

    $loader = new Zend_Application_Module_Autoloader(array(
    ‘namespace’ => ”,
    ‘basePath’ => APPLICATION_PATH . ‘/modules/’
    ));

    return $loader;
    }
    protected function _initPlugins()
    {
    // Access plugin

    $this->bootstrap(‘frontcontroller’);
    $fc = $this->getResource(‘frontcontroller’);
    $fc->registerPlugin(new Plugin_Layout());
    }

  19. #19 by Juan on November 25, 2012 - 8:36 pm

    It is easier than that unless in Zend Framework 1.12.

    Step 1.

    Make the modules.

    Step 2.

    Enable layouts usin zend tool or by creating the layout resources in application.ini such as

    resources.layout.layoutPath = APPLICATION_PATH “layouts/script”

    Step 3.
    Create a layout inside each module’s path “views/scripts”… the default would be called “layout.phtml”

    *** DO NOT CREATE THE DEFAULT LAYOUT INSIDE APPLICATION_PATH “layouts/scripts” (KEEP THIS FOLDER EMPTY) ***

    …and you are running!

    If a module layout is not found it will fallback to the default module’s layout.

    If you need finer tunning you can create a plugin class and set it up for the layout in the application.ini such as:

    resources.layout.pluginClass = “MyLibrary_Controller_Plugin_Layout”

    …or in the Bootstrap by calling the layouts method setPluginClass()… for example:

    Zend_Layout::getMvcInstance()->setPluginClass(“MyLibrary_Controller_Plugin_Layout”)

    Cheers!

  20. #20 by complaints number on July 7, 2014 - 2:13 pm

    Hi there i am kavin, its mmy first time to commenting anywhere, wgen i read this article i thought i could also create comment due to this brilliant piece oof writing.

(will not be published)