Modular application setup has often been considered as little problematic in Zend Framework (ZF) when it comes to creating self-contained or isolated modules. The main reason is that every module specific resource is initiated with every request regardless if the module is requested or not. This article will go over some important points in Zend_Application component and present a solution to the problem of setting up module specific configurations in Zend Framework applications.
Zend_Application presents a great way of setting up the environment and handling resources. There is no doubt that it presents convenience and makes the development fun. It’s the backbone of the framework and acts as a manager of an application. Zend Framework project lead Matthew Weier O’Phinney posted a very informative article on Module Bootstraps. After going over Zend Framework manual and reading Matthew’s article, I came up with the following in regards To Zend Application and module setup :
1- Zend_Application bootstraps the entire application. This means default resources and all other module specific resources are initiated all together at the same time.
2- Since that happens prior to dispatch() and bootstrap does not know the request parameters, there’s no way to determine which module to go after.
3- In a large application, there will be module specific controller plugins, services, layouts, helpers, additional db resources, session management… etc. Current Zend_Application class simply does not present a way to deal with it depending on the modules.
There are a few ideas mentioned in Matthew’s article to tackle these issues. Going over the resources presented, it seems like it’s mostly agreed upon to find a resolution by creating a controller plugin. I also tried the recommended approach but never had the relief that it was doing what expected. Moreover, whatever I tried in terms of setting up module specific application with a controller plugin, I saw that it’s actually doing nothing more than what has already been done.
In my opinion, all the ideas I tested whether that would be mine or others do not provide any way of initializing module specific resources, but add more complexity instead of flexibility and simplicity. By adding another layer of configuration with a controller plugin, there will be one more dependency to maintain in the application. Besides, the application’s resources cannot be enhanced or changed by the module’s resources. As long as Zend_Application is wrapping the front controller before the controller starts the dispatch, and as long as the bootstrap does not know the request parameters there’s no way to implement a complete module specific configuration.
Finally…
Well, how about getting rid of Zend_Application completely? Surely, that was not a problem.
...
require_once 'Zend/Controller/Front.php';
$front = Zend_Controller_Front::getInstance();
$front->addModuleDirectory(APPLICATION_PATH."/modules");
$front->dispatch();
Using Zend_Application has been a habit so giving up on it felt little weird I should say. So, I went back to pre Zend_Application era where you were alone with the almighty Front Controller. Actually the performance increase was HUGE simply because there were no resources initiated.
Then, I created a simple bootstrap class which only plays with the php.ini settings. I tested how to change the settings depending on the modules. I implemented the following idea, which worked perfectly.
Request -> Router -> Application Bootstrap -> Module -> Module Bootstrap
I applied the same logic to the following script which extends Zend_Application and allows self-contained modules. It might look like it creates an additional load to the default process but it does not. If you consider the fact that you only trigger the related module’s configuration instead of the entire application bootstrap, actually there’s much gain instead of loss.
Updated on 8/31/2010
require_once 'Zend/Application.php';
class My_App extends Zend_Application
{
/**
* Front Controller.
*
* @var Zend_Controller_Front
*/
protected $_front;
/**
* Router
*
* @var Zend_Controller_Router_Rewrite
*/
protected $_router;
/**
* Request.
*
* @var Zend_Controller_Request_Http
*/
protected $_request;
/**
* Response.
*
* @var Zend_Controller_Response_Http
*/
protected $_response;
/**
* Modules dir path.
*
* @var string
*/
protected $_moduleDir;
/**
* Application path.
*
* @var string
*/
protected $_appPath;
/**
* Sets up all the initial required pieces of the app.
*
* @param string $environment
* @param string $appPath
* @param string $moduleDir
*/
public function __construct($environment,
$appPath = APPLICATION_PATH,
$moduleDir = 'modules')
{
// set the environment
$this->_environment = (string) $environment;
// set the application path
$this->_appPath = $appPath;
// set the modules dir path
$this->_moduleDir = $this->_appPath .
DIRECTORY_SEPARATOR . $moduleDir;
// initiate autoloader
require_once 'Zend/Loader/Autoloader.php';
$this->_autoloader = Zend_Loader_Autoloader::getInstance();
// set up module autoloading
$this->_autoloader->pushAutoloader(array($this, 'moduleAutoload'));
// set front controller
$this->_front = Zend_Controller_Front::getInstance();
// add module directory
$this->_front->addModuleDirectory($this->_moduleDir);
// initiate request
if($this->_request === null) {
$this->_request = new Zend_Controller_Request_Http();
}
// initiate response
if($this->_response === null) {
$this->_response = new Zend_Controller_Response_Http();
}
// initiate router (Zend_Controller_Router_Rwrite)
$this->_router = $this->_front->getRouter();
// get application.ini options
$appOptions = $this->_getApplicationOptions();
// set routes in router from application.ini (if any)
$this->_addRoutesFromConfig($appOptions);
// update request with routes
$this->_route($this->_request);
// get module options
$moduleOptions = $this->_getModuleOptions();
// merge application and module options into one array
$options = $this->mergeOptions($appOptions, $moduleOptions);
// set options
$this->setOptions($options);
// update front controller request
$this->_front->setRequest($this->_request);
// update front controller response
$this->_front->setResponse($this->_response);
// to be used in dispatch
$this->_front->setRouter($this->_router);
}
/**
* Create options from application.ini.
*
* @return array
*/
private function _getApplicationOptions()
{
$appConfig = $this->_appPath . DIRECTORY_SEPARATOR .
'configs' . DIRECTORY_SEPARATOR .
'application.ini';
return $this->_loadConfig($appConfig);
}
/**
* Create requested module's options from the requested
* module's module.ini.
* Router resource can only be defined in application.ini.
*
* @return array
*/
private function _getModuleOptions()
{
$moduleName = $this->_request->getModuleName();
$moduleDir = $this->_moduleDir;
$modConfig = $moduleDir . DIRECTORY_SEPARATOR .
$moduleName . DIRECTORY_SEPARATOR .
'configs' . DIRECTORY_SEPARATOR . 'module.ini';
$options = $this->_loadConfig($modConfig);
if(isset($options['resources']['router'])) {
throw new Exception ('You can only set routes in application.ini');
}
return $options;
}
/**
* Add routes from options created from application.ini.
* Router resource cannot be declared in module.ini.
*
* @param array $options | from application.ini
*/
private function _addRoutesFromConfig($options)
{
if (!isset($options['resources']['router']['routes'])) {
$options['resources']['router']['routes'] = array();
}
if (isset($options['resources']['router']['chainNameSeparator'])) {
$this->_router->setChainNameSeparator($options['resources']['router']['chainNameSeparator']);
}
if (isset($options['resources']['router']['useRequestParametersAsGlobal'])) {
$this->_router->useRequestParametersAsGlobal($options['resources']['router']['useRequestParametersAsGlobal']));
}
$this->_router->addConfig(new Zend_Config($options['resources']['router']['routes']));
// don't trigger Zend_Application_Resource_Router
unset($options['resources']['router']);
}
/**
* Same route function in Zend_Controller_Front::dispatch().
* It updates the request with routes.
*
* @param Zend_Controller_Request_Abstract $request
*/
private function _route(Zend_Controller_Request_Abstract $request)
{
try {
$this->_router->route($request);
} catch (Exception $e) {
if ($this->_front->throwExceptions()) {
throw $e;
}
$this->_response->setException($e);
}
}
/**
* Instead of defining an autoloader for every module in
* its corresponsing bootstrap, this take care of all the
* autoloading for all the classes under modules. The only
* thing is that the file names under modules need to be
* exact in the class declaration.
*
* Example:
* application/modules/bugs/models/Tracker.php
*
* So the Tracker.php will have the following class:
* class Bugs_Models_Tracker
* {
* // code
* }
*
* You can call this class:
* $tracker = new Bugs_Models_Tracker();
*
* This can also be achieved within this function with
* a closure but then autoload won't work for any PHP
* version lower than PHP 5.3.
* private function _moduleAutoload()
* {
* $load = function($class) {
* $file = $this->_moduleDir . DIRECTORY_SEPARATOR .
* str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
* require $file;
* };
* $this->getAutoloader()->pushAutoloader($load);
* }
* Then in the constructor of this class you can call this function as
* $this->_moduleAutoload();
*/
public function moduleAutoload($class)
{
$file = $this->_moduleDir . DIRECTORY_SEPARATOR .
str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
require $file;
}
}
Now we need to make a change in the Front Controller’s dispatch() method to prevent double routing. We already processed routing in My_App::_route() method. So we need to remove it from the dispatch() method. One important thing to notice is that since the routing has already been completed, there’s no use for the routeStartUp() controller plugin call in Front Controller’s dispatch(). Even if a controller plugin with routeStartup() is registered with the Front Controller, it will perform the same as a routeShutdown() because the route has already been completed.
In the current version of My_App, if a specific routeStartup() plugin want to be registered, then My_App::__construct() has to be modified to run these plugins before the routing occurs in My_App::_route() method. A better way would be to separate the method calls in My_App::__construct() to small chunks of private methods in My_App and configure it to run routeStartup() plugin before the routing occurs. I will leave that up to the developer since routeStartup() is a very specialized plugin which actually can be done in many other ways as well and depends on the use case. Currently, Router resource can be defined in application.ini.
public function dispatch()
{
...
...
...
// remove the following in dispatch()
// you can remove routeStartup() as well if you'd like
// $this->_plugins->routeStartup($this->_request);
try {
$router->route($this->_request);
} catch (Exception $e) {
if ($this->throwExceptions()) {
throw $e;
}
$this->_response->setException($e);
}
...
...
...
}
That’s it. Let’s see the public/index.php
defined(
'APPLICATION_ENV') ||
define('APPLICATION_ENV', 'development');
defined(
'APPLICATION_PATH') ||
define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));
set_include_path(implode(
PATH_SEPARATOR, array(realpath(APPLICATION_PATH . '/../library'),
get_include_path())));
require_once 'My/App.php';
$application = new My_App(APPLICATION_ENV);
$application->bootstrap()
->run();
Let’s look at the directory structure.

Now remember that the module.ini merges with application.ini resources. As you see above, there is no Bootstrap class specified in application.ini because module.ini will decide where its Bootstrap class is and its name. Since module is independent, all the module resources need to be declared in the module.ini. Below you will see I declared a layout path for Test module’s layout, which includes a simple background color (gray). There’s no layout defined in default module’s ini. Additionally, I created a Test controller plugin and registered it with Test module only. Default module has no plugin. More on this subject in a little while.
[production]
bootstrap.path = APPLICATION_PATH "/modules/test/Bootstrap.php"
bootstrap.class = "Test_Bootstrap"
resources.layout.layoutPath = APPLICATION_PATH "/modules/test/layouts/scripts"
resources.frontcontroller.plugins.test = "My_Test"
[staging : production]
[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
resources.frontController.params.displayExceptions = 1
Here’s My_Test controller plugin. It’s only registered with the test module.
class My_Test extends Zend_Controller_Plugin_Abstract
{
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
var_dump($request);
}
}
Here’s one of the Bootstrap files in the module. The module bootstrap does not extend Zend_Application_Module_Bootstrap. It’s like the main bootstrap file, it extends Zend_Application_Bootstrap_Bootstrap.
class Test_Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
}
Let’s pull up the page. http://localhost/test

See the var_dump() above coming from the My_Test plugin. It says “_dispatched: false” and you can see all the request parameters before the routeStartup(). The only resource that cannot be initiated in module.ini is the Router resource. If you define routes in module.ini, then an Exception will be thrown. The reasoning behind this is that if you’re defining routes, this is an application wide setting. So it has to be done in application.ini. Keep your application wide settings in application.ini and module settings in module.ini. It’s straightforward.
The restriction I see is the configuration file types because currently they are fixed in My_App to .ini . Is it possible to extend to cover other ones like xml, yaml…? Sure it is. Again, this depends on the use case and I will leave that up to the developer. I use .ini because it has a much cleaner look and easier for me. If you would like to have more than one configuration file under /configs directory, then a method can be created in My_App to iterate through the files under /configs directory and these can also be added to the options.
DOWNLOAD
Conclusion
My_App creates isolated modules for your application and it can be modified depending on the application needs. It presents an alternative to the conventional Zend Framework’s module based application setup. The best part of My_App is that we do not have to rely on application wide settings and it does not present a dependency to maintain. We can override the application.ini settings in the modules if we want to. We can declare any resource in application.ini and as long as module.ini does not override it, it will be available to the requested modules. A lot of possibilities come into play, which I will try to cover it in another post.
Your thoughts?

August 24th, 2010 at 3:02 pm
[...] Omercan Sebboy hat den zweiten Teil seines Tutorials für die Modulbasierte Konfiguration einer Zend Framework Anwendung veröffentlicht. [...]