Zend Framework Bootstrapping Modules

I am working on a small API for my own backend and i wanted a modular architecture. I have done this before using the Zend Framework. But this time i wanted a bit more control while loading the modules. And adding a Bootstrap class would seem like a good option. The only example i could find involved loading all bootstraps on every request. Which doesn’t seem like a good idea. So after reading through the Manual and some blog posts. I decided to give it s shot my self.

The structure i want looks like this.

The application.ini file has the following contents:

  • includePaths.library = APPLICATION_PATH “/../library”
  • bootstrap.path = APPLICATION_PATH “/Bootstrap.php”
  • bootstrap.class = “Bootstrap”
  • resources.frontController.moduleDirectory = APPLICATION_PATH “/modules”
  • resources.modules[] = “default”
  • resources.modules[] = “admin”

includePaths This sets the applications local library location. Any shared code for this application goes here.

bootstrap.path & class Define the location and type of the Bootstrap class.

resources Define the modules location and create a list of modules.

The main Bootstrap class

application/Bootstrap.php

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{

Load the config parameters for this application and set some debugging settings if needed.

protected function _initConfiguration()
{
  $app = $this->getApplication();
  $config = $app->getOptions();

  if (APPLICATION_ENV == 'development') {
    error_reporting(E_ALL & E_STRICT);
    if (isset($config['phpsettings'])) {
      foreach ($config['phpsettings'] as $setting => $value) {
        ini_set($setting, $value);
      }  
    }
  }
}

We need autoloading here because we are using a class from the application library. Right now this causes a problem. A notice is thrown

Warning: include_once(FrontController.php) [function.include-once]: failed to open stream: No such file or directory in Zend/Loader.php on line 147

The application responds fine. And this problem seems to be a recurring issue (ZF-7224, ZF-7550) for the framework. Until now i have not find a graceful fix for this. besides a small patch reversion.

protected function _initAutoload()
{
  $autoloader = Zend_Loader_Autoloader::getInstance();
  $autoloader->setFallbackAutoloader(true);

  return $autoloader;
}

Setup the controller to register the Bluess_Modules_Loader plug-in. And set the prefixDefaultModule parameter so we can prefix the default module controllers as well. Just for the sake of consistency. The Bluess_ namespace is part of my API. And can be changed at will.

protected function _initController()
{
  $this->bootstrap('FrontController'); 
  $controller = $this->getResource('FrontController');
  $modules = $controller->getControllerDirectory();
  $controller->setParam('prefixDefaultModule', true);
        
  $controller->registerPlugin(
    new Bluess_Modules_Loader($modules)
  );
        
  return $controller;
}

Now the last method. which is a bit weird. And i am probably missing a key factor here. But if this method resource is not declared only the default module functions. When declared empty all modules function as they should. This would indicate that this method could be used to load the modules. But i haven’t found a way to achieve this yet. Except for loading all modules in a row. Which makes no sense for this purpose. So we leave it empty.

protected function _initModules()
{
  // Call to prevent ZF from loading all modules
}

The most important part here is the controller plug-in. This will be the place where module bootstraps are called from.

application/../library/Bluess/Modules/Loader.php

class Bluess_Modules_Loader extends Zend_Controller_Plugin_Abstract
{
  protected $_modules;

Setup the plug-in by passing the applications module list.

public function __construct(array $modulesList) 
{
  $this->_modules = $modulesList;
}

The dispatchLoopStartup method will be called on every request and will do the magic. Based on the current module name we create a new Zend_Application with the current modules config file module.ini. And we bootstrap it.

public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
{
  $module = $request->getModuleName();
        
  if (!isset($this->_modules[$module])) {
    throw new Exception("Module does not exist!");
  }

  $bootstrapPath = $this->_modules[$module];
        
  $bootstrapFile = dirname($bootstrapPath) . '/Bootstrap.php';
  $class         = ucfirst($module) . '_Bootstrap';
  $application   = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/modules/' . $module . '/configs/module.ini'
  );  

  if (Zend_Loader::loadFile('Bootstrap.php', dirname($bootstrapPath)) 
      && class_exists($class)) {
    $bootstrap = new $class($application);
    $bootstrap->bootstrap();
  }
}

Now setup the default module. Once this is done it’s a nice example for further modules. Make sure the module has it’s own layout set.

application/modules/default/configs/module.ini

default.resources.layout.layout = “default” default.resources.layout.layoutPath = APPLICATION_PATH “/modules/default/layout”

Setup the modules bootstrap and use it to set the modules model location.

application/modules/default/Bootstrap.php

class Default_Bootstrap extends Zend_Application_Module_Bootstrap 
{
  protected $_moduleName = 'default';

  protected function _initConfiguration()
  {
    $options = $this->getApplication()->getOptions();
        
    set_include_path(implode(PATH_SEPARATOR, array(
      realpath(APPLICATION_PATH . '/modules/' . $this->_moduleName . '/models'),
      get_include_path(),
    )));
        
    return $options;
  }
}

That’s all. Now make sure your layout is set correctly and the controllers are prefixed

application/modules/default/layout/default.phtml

echo $this->layout()->content;

application/modules/default/controllers/IndexController.php

class Default_IndexController extends Zend_Controller_Action
{

It took me a while to get this working like i had it in my mind. But it’s going the right way. If your interested in a working copy. You can download one here.

UPDATE

Matthew has a nice post about some do’s and don’ts concerning module based applications

comments powered by Disqus