Posts Tagged Zend_Application

Module Specific Plugins with Zend Framework

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 layout folder and give them the same name as their module. Like this, switching can be easily done with the following plugin.

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::XHTML1_STRICT);
    $view->headTitle('Some Application');
    $view->headTitle()->setSeparator(' - ');
}

Ome 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 ‘Trunk – *title set in Module1_Bootstrap*- *title set in Module2_Bootstrap* – …’ on each page.

The answer to this behaviour is simple, but it is hard to find (I did not fins it in the manual). There are three basic steps from request to response when working with Zend Framework.

  1. Bootstrapping
  2. Routing
  3. 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 get fully bootstrapped first. As this happens for each request, it is a good practise to make bootstrapping 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 the following line to my application.ini file did not do the trick.

admin.resources.frontController.plugins.view = Admin_Plugin_View

The plugin was used in every module. That is not what I wanted.

The solution

Because Zend_Application registered my plugins with all modules, I decided to register only one plugin (and my layout switcher) to manage the plugins. And here it is!

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.

In closing

As you might have thought, this is not a very elegant approach to fix the module-specific-plugins problem, but it’s a quick one, and that’s all that mattered for me for now. Modules are – as Matthew said – second class citizens in ZF v1. I really hope for improvement in ZF2.0.

, , ,

2 Comments