May 112012
 

First, I should point out that the title of this post is a bit of an intentional misnomer. There’s really no such thing as “module-specific” anything in ZF2, so what we’re really talking about is the topmost namespace of the controller being dispatched. So in the case of MyModule\Controller\SomeController, the topmost namespace would be MyModule. In most cases, this will be the name of a given module.

UPDATE: The information in this post is still correct and applicable, but I’ve made a very simple module called EdpModuleLayouts to make it even easier.

Here’s how you can easily switch the layout (or perform any other arbitrary logic) for a specific module in Zend Framework 2.0 (as of d0b1dbc92):

<?php
namespace MyModule;
 
use Zend\ModuleManager\ModuleManager;
 
class Module
{
    public function init(ModuleManager $moduleManager)
    {
        $sharedEvents = $moduleManager->getEventManager()->getSharedManager();
        $sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
            // This event will only be fired when an ActionController under the MyModule namespace is dispatched.
            $controller = $e->getTarget();
            $controller->layout('layout/alternativelayout');
        }, 100);
    }
}

This event listener will only be triggered if an ActionController under the MyModule namespace is dispatched, so you do not need to perform any additional logic to check which “module” or namespace the controller being dispatched is under.

Keep in mind, as of writing this, ZF2 is still in beta. There are plans to add a convenience layer to the framework before the GA release which will likely make common tasks like this much simpler.

See also: Rob Allen’s post on module specific bootstrapping in ZF2

  50 Responses to “Module-specific layouts in Zend Framework 2”

  1. Hi I got this error.

    Catchable fatal error: Argument 1 passed to Administration\Module::init() must be an instance of Zend\Module\Manager, instance of Zend\ModuleManager\ModuleManager given

    No idea why. I use the ZF 2 beta4 and the Module Skletton described here: http://akrabat.com/zend-framework-2-tutorial/

    I would be happy about any hint.

    Thanks

    • Hey Michael,

      I’ve just updated the code in my post — I was type hinting the “Manager” class (Zend\Module\Manager) for init(), which has since been renamed to Zend\ModuleManager\ModuleManager. The code in my post should work for you now. Don’t forget the use statement at the top.

      Thanks for pointing this out!

  2. My fault. I have better to read the error message.

    You must just change the Manager to Modulemanager. Would be great you change it in your example.

    events()->getSharedManager();
            $sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
                // This event will only be fired when an ActionController under the MyModule namespace is dispatched.
                $controller = $e->getTarget();
                $controller->layout('layout/alternativelayout');
            }, 100);
        }
    }
    
  3. It’s not working for me, any way to make this working is changing module namespace name to “Zend\Stdlib\DispatchableInterface” – but like I guess it will make my event running in any module, not only this.

    Any idea way I have this problem?

  4. $this->events() is now $this->getEventManager(), rights ?

  5. Thanks! I was looking for that :)

  6. how to set parameter or variable from module.php to the controller ? like

    $e->getApplication()->getMvcEvent()->setParam('thisistestparam','thisistestvalue');
    and in the controller get :
    $e->getApplication()->getMvcEvent()->getParam('thisistestparam');

    thanks

    • @samsonasik, while you probably could use the MvcEvent to carry parameters from a Module class method like onBootstrap() to the controller, that’s not really the intended use. A more powerful and “correct” approach would be to use a factory or initializer for your controllers:

      < ?php
      namespace MyModule;
      
      class Module
      {
          public function getControllerConfig()
          {
              return array(
                  // Set stuff in the controller just for 'somecontroller'
                  'factories' => array(
                      'somecontroller' => function($sm) {
                          $controller = new Controller\MyController;
                          $controller->setTestParam('testvalue');
                          // You could also pass service from the service manager:
                          $controller->setSomeDependency($sm->get('some_serivce'));
                          return $controller;
                      }
                  ),
                  // Or run all controllers through an initializer
                  'initializers' => array(
                      function($controllerInstance, $sm) {
                          if ($controllerInstance instanceof TestParamAwareInterface) {
                              $controllerInstance->setTestParam('testvalue');
                          }
                          return $controllerInstance;
                      }
                  ),
              );
          }
      }
      
  7. thank you, i just done by getControllerConfiguration not getControllerConfig

  8. Hey!

    Works great, thanks. I was just wondering: Why actually is this working? Where in the process is the “emitting identifier” named after “MyModule” registered in the SharedEventManager and who’s the guy emitting the event under that name “MyModule” then?

    I kind of confused understanding what’s happening in the framework here … :-(

    Thanks, Mike

    • Hey Mike,

      Great question. What we did was add the first level of the namespace of the controller being dispatched as an event identifier in the AbstractActionController. See AbstractActionController.php:L178.

      • Got it! Thanks much for your quick response. To some extend, this all feels a bit awkward and this kind of “magic” makes things harder to grasp, at least for me. But, I guess, that’s how it always is in non-trivial systems. So keep up the good work – looking forward to ZF2 GA release!

        • I don’t disagree — I was hesitant to add that namespace identifier due to the “magic” feel it has. However, the problem was that without it, we had no way to a listener that would only be triggered for requests to controllers within a specific module. So what we had to do was attach to the generic dispatch event, then do an if statement to check the namespace of the controller being dispatched to see if you should perform a particular action (like change the layout). You can still do that, but I thought it’d be nice to provide a more elegant solution to the problem.

  9. Don’t forget that if you want your module to contain its own layout instead of looking for it in application/view/layout, you need to register a template map in the view_manager in the module config, like so:

    'view_manager' => array(
        'template_path_stack' => array(
            'AuthorPanel' => DIR . '/../view',
        ),
        'template_map' => array(
            'customLayout' => DIR.'/../view/layout/layout.phtml'
        )
    ),
    

    and then you can address the customLayout in the Module.php file inside a module. I did what Evan suggests, but slightly modified it:

    public function init(ModuleManager $moduleManager) {
        $sharedEvents = $moduleManager->getEventManager()->getSharedManager();
        $sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
            $controller = $e->getTarget();
            if (is_readable(DIR . '/view/layout/layout.phtml')) {
                $controller->layout('customLayout');
            }
        }, 100);
    }
    

    As you can see, the module will get its custom layout only if a custom layout exists, otherwise it will use the application’s default layout.

  10. Hey Michael -

    It might be somewhat off-topic, but I’ve put up a visual representation of the Zend Framework 2 Request Dispatch Flow and made it available here: http://zendframework2.de/en/cheat-sheet.html . It might be of help to the readers of your blog.

    The graphic is the result of some debugging-sessions and it may still have some errors in it and should be considered as “work-in-progress”. So if you find something to improve, please let me know, guys.

    Thanks, Michael

    • Awesome diagram! This is pretty helpful. I’ve dug through the code myself but it takes a few sessions to get a grasp on what’s going on.

    • Very cool diagram, Michael! We have one we put together around beta3 or so using Google docs but it was never updated, unfortunately. It’s great to have an up to date visual representation of the dispatch process for those who are trying to understand it better. Great job!

  11. hi

    whats the probleme withe this function :

    public function init(ModuleManager $moduleManager)
    {
        $sharedEvents = $moduleManager->getEventManager()->getSharedManager();
        $sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
            if (!$this->zfcUserAuthentication()->hasIdentity()) {
                return $this->redirect()->toRoute('zfcuser/login');
            }
        }, 100);
    }
    • You’re using $this in the closure, which won’t work. You need to pull the controller instance from the event and use it like this:

      public function init(ModuleManager $moduleManager)
      {
          $sharedEvents = $moduleManager->getEventManager()->getSharedManager();
          $sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
              $controller = $e->getTarget();
              if (!$controller->zfcUserAuthentication()->hasIdentity()) {
                  return $controller->redirect()->toRoute('zfcuser/login');
              }
          }, 100);
      }
      
      • Thank you

      • If you want to change admin layout in zf2 then you have to add this code in module.config.php in admin module

        ‘view_manager’ => array( ‘template_path_stack’ => array( ‘manager’ => DIR . ‘/../view’, ), ‘template_map’ => array( ‘customLayout’ => DIR.’/../view/customlayout/layout.phtml’ ) ),

  12. Please tell me, why all configs from all modules are merged together. I dont get it, when I have two layout files with the same name the last one module in config is overwritten whole configuration for other modules… I really dont get it.

  13. Thanks! This was what I was looking for. However, I find it confusing that you can configure whatever for the ‘layout/layout’ key in the configs and only the config of the last module will be picked up. I know that the configs are stacked and merged and therefor, the order matters. What it comes down to, I think, is this: Say we have an application with ten modules and a certain layout. Everything is fine. Now there comes a module that uses a different layout, but the same key ‘layout/layout’. Now all our ten modules use this new layout, if the module is added last in the application config. Am I right about this? It worries me. It worries me, because now we have to dig into the Module.php of the drop in Module, to make the layout ‘module specific’. And then the concept of ‘Drop in module’ is gone.

    What I would expect would be some module awareness, which, as you pointed out, is just absent. I know this is done on purpose, likely for good reasons, but I feel I’m missing it. If a module states that the views should use layouts from a different template_path, that should be used. Not the template path of the last module registered in application.config.

    • It worries me. It worries me, because now we have to dig into the Module.php of the drop in Module, to make the layout ‘module specific’. And then the concept of ‘Drop in module’ is gone.

      That’s not true though. The example I gave would very much allow you to just drop in a module that uses a specific layout. Alternatively, you could make it use a config key.

      In fact, I went ahead and made a nice little module for this while writing this reply. :)

  14. 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());
    }
  15. hi, thanks for you great article. this is another way :

    // /MyProject/module/MyModule/config/module.config.php
     array(
            'invokables' => array(
                //...
            ),
        ),
    
        'router' => array(
            //...
        ),
    
        'view_manager' => array(
    
            // this 3 lines is important
            'template_map' => array(
                'layout/layout'           => __DIR__ . '/../view/layout/layout.phtml',
            ),
    
            'template_path_stack' => array(
                //...
            ),
        ),
    );
    ?>
    

    also you need to create layout.phtml in /MyProject/module/MyModule/view/layout directory.

    • @Dan — Your example is the correct way to simply set the layout for the entire application, but will not use a different layout depending on which module (top level namespace) the controller being requested is in, which is what this post is showing. However, thanks for posting this example!

  16. Hi people,

    How can I get the ControllerName?

    I have this:

    
    $sharedEvents->attach(__NAMESPACE__ . "Admin", 'dispatch', function($e) {
        $controller = $e->getTarget();
    
        $auth = new AuthenticationService();
        $auth->setStorage(new SessionStorage("LivrariaAdmin"));
    
        $controller = ??????
        if (!$auth->hasIdentity() and $controller<>"Auth" ) {
            return $controller->redirect()->toRoute('livraria-admin-auth');
        }
    }, 99);
    
    • Are you trying to get the name of the controller? You could simply use get_class($controller)… Perhaps instead it might be more reliable to use the route name that you wish to allow. I have a similar example here.

      • Hi,

        Thanks! My question is: how do you know this methods? $matchedRoute = $controller->getEvent()->getRouteMatch()->getMatchedRouteName(); or others??

        Is there any docs to see this?

        A friend of my told me that I should register a listener to the MvcEvent::EVENT_DISPATCH or at least one with a negativ priority to the MvcEvent::EVENT_ROUTE event irrc, so it runs after the router. How is this possibility?? I need to understand the concepts… do you have any reference?

        Thanks body!

  17. Its seems that in final release $moduleManager->getEventManager()->getSharedManager()->attach event only works if both folders (Module & src/folder) are same. check this link http://stackoverflow.com/questions/12406507/modulemanager-geteventmanager-getsharedmanager-attach-is-not-working-in

  18. Works great, but if you have a problem where the wrong layouts get loaded or you only get one layout for all of your modules: make sure you name each module layout file differently.

    As an example Module called Dialogue will have its layout file here

    yourApplication/module/Dialogue/view/layout/dialogue.phtml

    and configuration set like so:

        'module_layouts' => array(
            'Dialogue' => 'layout/dialogue.phtml',
        ),
    

    And module called Moderation would have its layout file here

    yourApplication/module/Moderation/view/layout/moderation.phtml

    and configuration set like so:

        'module_layouts' => array(
            'Moderation' => 'layout/moderation.phtml',
        ),
    
  19. I moved “layout” folder into module named folder in view:

    before: view/layout/layout.phtml
    after: view/modulename/layout/layout.phtml

    So I don’t have to rename layout file. And then in module.config.php

    'module_layouts' => array(
        'Modulename' => 'modulename/layout/layout',
    ),
    
  20. Can we use that in onBoostrap() instead of init()?

    What I am doing at the moment in my onBoostrap() is: $eventManager = $e->getApplication()->getEventManager(); $eventManager->attach('dispatch', array($this, 'checkPermissions'), 2); Where checkPermissions() is a method checking ACL.

    Can I do the following instead in order to check ACL just in the current module? Is it correct?: $sharedEvents = $e->getApplication()->getEventManager()->getSharedManager(); $sharedEvents->attach(NAMESPACE, 'dispatch', array($this, 'checkPermissions'), 2);

  21. thanks man .. 4 hours trying to find this info. Fuckng crazy

  22. Thanks, helped a lot!

  23. Changing the layout works great with the code above. My question/problem is I need to attach a partial to the layout. It needs to render in the specified layout on a per request basis. Is there an easy way to do this in your Module.php? Specifying the partial in all my actions works but is overkill.

  24. Thanks very much.

    It’s useful.

  25. Hi,

    your code for specific layout works pretty well but it don’t affect my zfc-user module. I mean i’m inside my “site” module, i use your code and it change my layout but the zfc-user inside “site” layout is not changed. Help ? :o

  26. Been looking for something that can do this, thanks for sharing it.

    Is it possible to install it from the github repository using composer?

  27. Thany you Very Much it work!

  28. Hi Evans,

    I am trying to implement this but using the module you came up with.

    I am still not able to have different layouts even after following all your steps.

    And am not getting any error.

    I do not know where am going wrong.

    Please help

    Thanks

  29. I am sure people are finding the correct way to handle this, but I keep ending up here on Google searches, when there is more current and better information to change a layout template. In my case, I just wanted to do some testing and swap the layout at the module level to override the default set in config. It was simple actually, but thought I’d share what I did in case others end up here.

    make sure you have the alt_layout.phtml file you need, of course. In the MVC Module.php onBootstrap(): $this->view = $e->getViewModel(); if($maybe_i_want_some_alt_layout == true){ $this->view->setTemplate('layout/alt_layout'); }

    I can still override at the controller level manually if needed. Use this in the actual controller pre dispatch (if setup that way) or in the action. But this is not very flexible. I just had need for a specific action to show a special layout. $this->layout('layout/special_controller_layout');

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre user="" computer="" color="" escaped="">