I have been looking over Zend Framework modules for some time now. Although I have some experience with the Zend Framework, my experience with modules is quite limited.
Setting up modules is not that difficult. Tutorials like the one of Jeroen will guide you flawlessly through this process. Jeroen uses another approach to layout switching though. I prefer to put all layouts in the layouts folder and give them the same name as their module. Like this, switching can be done easily with the following plugin.
<?php class Mist_Controller_Plugin_ModularLayout extends Zend_Controller_Plugin_Abstract { public function preDispatch(Zend_Controller_Request_Abstract $request) { $layout = Zend_Layout::getMvcInstance(); $layout->setLayout($request->getModuleName()); } }
Now, let’s get back to the problem.
The big misconception
The big problem with Zend_Application, for me, was that it did not do what I supposed it would do.
In a non-modular application I would use the Bootstrap to set up the View and other things (like illustrated in the tutorial of Rob Allen).
protected function _initView() { $this->bootstrap('layout'); $layout = $this->getResource('layout'); $view = $layout->getView(); $view->doctype(Zend_View_Helper_Doctype::HTML5); $view->headTitle('Some Application'); $view->headTitle()->setSeparator(' - '); }
One might think you could extend this behaviour to modules (by adding Zend_Application_Module_Bootstrap classes) and set up a different title for each module. Think again! Zend_Application bootstraps the entire application. So, if you were to make separate Bootstrap classes with _initView() methods, you would end up with a title ‘*title set in Bootstrap* – *title set in Module1_Bootstrap* – *title set in Module2_Bootstrap* – …’ on each page.
The answer to this behaviour is simple, but it hard to find (I did not find it in the manual anyway). There are three basic steps from request to response when working with Zend Framework.
- Bootstrapping
- Routing
- Dispatching
Zend_Application only takes care of the bootstrapping. So, at that point, it is not aware of the routing. That is why your application gets fully bootstrapped all the time. As this happens for each request, it is a good practise to make the bootstrap as light as possible.
How does this resolve my problem? It does not. All module specific loading should happen using Plugins or Helpers. But how do you implement module specific Plugins? Adding this line to the application.ini file does not do the trick.
admin.resources.frontController.plugins.view = Admin_Plugin_View
The plugin will be used in every module. That is not what I (and probably you) want.
The solution
Because Zend_Application registered my plugins with all modules, I decided to register only two plugins: my layout switcher and a plugin to manage module specific plugins. It looks like this.
<?php class Mist_Controller_Plugin_ModuleSpecificPluginLoader extends Zend_Controller_Plugin_Abstract { public function preDispatch(Zend_Controller_Request_Abstract $request) { if($request->getModuleName() != 'default') { $plugins = $this->getModuleSpecificPlugins($request->getModuleName()); foreach($plugins as $plugin) { $instance = new $plugin(); $instance->preDispatch($request); } } } public function routeShutdown(Zend_Controller_Request_Abstract $request) { if($request->getModuleName() != 'default') { $plugins = $this->getModuleSpecificPlugins($request->getModuleName()); foreach($plugins as $plugin) { $instance = new $plugin(); $instance->routeShutdown($request); } } } protected function getModuleSpecificPlugins($module) { $result = array(); $fc = Zend_Controller_Front::getInstance(); $pluginsPath = realpath($fc->getModuleDirectory() . "/plugins"); $files = scandir($pluginsPath); foreach($files as $file) { if($file != '.' && $file != '..') { if(!in_array(realpath($pluginsPath . '/' . $file), get_included_files())) { require($pluginsPath . '/' . $file); } $reflectionFile = new Zend_Reflection_File($pluginsPath . '/' . $file); $classes = $reflectionFile->getClasses(); foreach($classes as $class) { /* @var $class Zend_Reflection_Class */ if($class->isSubclassOf('Zend_Controller_Plugin_Abstract')) { $result[] = $class->getName(); } } } } return $result; } }
It just looks for classes in the module plugin directory, checks whether they are plugins, and uses them if they are called for.
Conclusion
You might have noticed that this is not an elegant solution. There is definitely another way to register specific plugins for a module. However, with my limited knowledge of the Zend MVC, it is nearly the best/only solution I can think of.
To summarize: this is a quick and dirty fix. It does the job, but it is not quite elegant.
To be completely honest, I do not even use this myself. I did use it, but it did not last long.