Latest

Offshoot Blog

Module Bootstrapping in Zend Framework

It seems that ever since the 1.8 release of Zend Framework (where they introduced the new application bootstrapping), there has been a lot of discussion about the way that module bootstrapping is handled. I think most programmers, myself included, tend to think of “modules” as self-contained entities that can be plugged in or removed from a larger application, with little to no change to the application itself. So when we hear ZF start talking about Module bootstrapping, people tend to assume that a module bootstrap (Zend_Application_Module_Bootstrap) will perform exactly the same as the default bootstrap (Zend_Application_Bootstrap_Bootstrap), but only for the module that is being requested. Unfortunately this assumption is incorrect, for a very important reason. The default bootstrap sets up paths, loads any resources, and prep’s your application — all before your application even knows what module has been requested. All of the module bootstraps are called immediately after the default bootstrap, which is still before your application knows which module has been requested. The module bootstrap can set up paths and add to or alter loaded resources like an ACL or navigation, but they’re unable to load module specific resources or execute any module specific code.

For some background on this, you’ll want to start with Matthew Weier O’Phinney’s article “Module Bootstraps in Zend Framework: Do’s and Don’ts”. In that article you’ll get a good run down of how module bootstrapping works in ZF and why. In particular, you’ll find a more in depth answer to the most popular question: “Why are all module bootstraps run on every request, and not just the one for the requested module?”. The gist of it, as I mentioned above, is that the default module bootstrapping is intended mostly for setting up paths and initializing or altering resources that are available to the application as a whole. Module bootstrapping is not to be used for running module specific code or loading resources that should only be available to the active module.

So how do we get to a solution that allows us to run module specific code, only when that module is active? Weier O’Phinney offers a Plugin based solution. Quick and to the point. It offers us the ability to run module specific code, but has a number of important drawbacks: 1) it strips us of the powerful functionality that we get from bootstrapping (resource loading, application.ini, etc); 2) It bloats our dispatch cycle with a minimum of 1 Plugin per module (this may or may not be a big deal to you, depending on your application); and 3) possibly the most important — it requires modifications to our default bootstrapping every time a new module is added or taken away.

I’m really looking for a solution that allows my modules to be self-contained and independent of their application container. I don’t want to have to alter my default bootstrapping if I want to add or remove a module. This lead me to an article, “Zend Framework Module Config The Easy Way” by Leonard Dronkers. This was a short code example that illustrates how we can have module specific configuration, that is contained within our module and doesn’t require plugins or anything more than a one-time change to our default bootstrapping. This is a good start, but suffers from some of the same drawbacks as the last solution we looked at. It still strips us of the powerful automagical resource loading, and also delegates to the controllers to deal with the module specific configuration. The latter can lead to a bloated controller layer and even potentially to duplicated code. This is definitely on the right track, but still isn’t what I’m looking for.

From here, I came across “Active Module Based Config with Zend Framework” and “Active Module Config V2″ by Kathryn “BinaryKitten”. The former article is her first attempt at solving this problem and an interesting read, but the latter article is the important one. Finally we’re getting closer to the kind of solution that I’ve been looking for. Yet still, something is missing. The plugin appears to be doing too much and some of code is adapted(or duplicated) from some of the ZF bootstrapping classes. It also forces us to bloat our module bootstrap classes with an additional set of activeInit* methods (not to be confused with the __init* methods). It doesn’t appear to allow for the module specific configuration offered by Dronkers’ solution, expecting all the work to be custom coded in the activeInit* methods. Though a great start, this solution falls short without a solution for storing the result of the activeInit* methods back on the bootstrap for access elsewhere in the application.

Time to take what I’ve learned from the above articles and throw my 2 cents into the mix! Big disclaimer here: this code has NOT been tested and should NOT be used in a production environment. This is just an initial phase of this solution and is lacking a considerable amount of error checking and unit testing. It appears to work for the few cases that I’ve run it through, which is promising, but it is by no means a complete solution yet.

Ok, let’s get down to business…

I started by adapting Dronkers’ solution into a module bootstrap class that loads a module specific ini file

 
class Offshoot_Application_Module_Bootstrap 
    extends Zend_Application_Module_Bootstrap
{
 
   /**
     * Constructor
     *
     * @param  Zend_Application|Zend_Application_Bootstrap_Bootstrapper 
     *     $application
     * @return void
     */
    public function __construct($application)
    {
        parent::__construct($application);
        $this->_loadModuleConfig();
    }
 
    /**
     *
     * load a module specific config file
     */
    protected function _loadModuleConfig()
    {
        // would probably better to use 
        // Zend_Controller_Front::getModuleDirectory() ?
        $configFile = APPLICATION_PATH 
            . '/modules/' 
            . strtolower($this->getModuleName()) 
            . '/configs/module.ini';
 
        if (!file_exists($configFile)) {
            return;
        }
 
        $config = new Zend_Config_Ini($configFile, $this->getEnvironment());
        $this->setOptions($config->toArray());
 
    }
 
}

Now all of my module specific bootstrap classes will extend this class and optionally look for a module.ini configuration file. If the file exists, it will load that file into it’s options array.

From here, I wanted to take what I liked from Kathryn’s solution, which was the concept of finding and accessing the active module bootstrap at the plugin level, after the route has been parsed.

 
class Offshoot_Controller_Plugin_ActiveModule 
    extends Zend_Controller_Plugin_Abstract
{
 
    public function routeShutdown(Zend_Controller_Request_Abstract $request)
    {
 
        $activeModuleName = $request->getModuleName();
        $activeBootstrap = $this->_getActiveBootstrap($activeModuleName);
 
    }
 
    /**
     * return the default bootstrap of the app
     * @return Zend_Application_Bootstrap_Bootstrap
     */
    protected function _getBootstrap()
    {
        $frontController = Zend_Controller_Front::getInstance();
        $bootstrap =  $frontController->getParam('bootstrap');
        return $bootstrap;
    }
 
    /**
     * return the bootstrap object for the active module
     * @return Offshoot_Application_Module_Bootstrap
     */
    public function _getActiveBootstrap($activeModuleName)
    {
 
        $moduleList = $this->_getBootstrap()->getResource('modules');
 
        if (isset($moduleList[$activeModuleName])) {
            return $moduleList[$activeModuleName];
        }
 
        return null;
 
    }
 
}

This plugin doesn’t do much right now (we’ll come back to it shortly), but what’s important here is that we have access to the module bootstrap class (which has loaded the module specific configuration file). My biggest problem with Kathryn’s solution was that the plugin was doing too much: I wanted to delegate some of the work to another class (or set of classes) and at the same time, regain the power that the ZF bootstrapping has to offer, without duplicating any code.

This is where things start to get interesting! If what I’m looking for is essentially a second round of bootstrapping, that’s specific to the active module, why not just extend the abstract bootstrapping functionality and create a class that takes care of the module specific bootstrapping. This class will need to be separate from the ZF bootstrapping procedure, so that it can be called/run in our ActiveModule plugin after the route has been parsed. I decided to go with the name “Initializer”, in the sense that the module bootstrap setups up the stage and the module Initializer puts it all into action. Maybe not the most intuitive or best fitting name, but I don’t have a lot of time to wrestle with semantics… so it’ll do for now!

 
abstract class Offshoot_Application_Module_Initializer
	extends Zend_Application_Bootstrap_BootstrapAbstract
{
 
    /** @var Offshoot_Application_Module_Bootstrap */
    protected $_bootstrap;
 
    /**
     *
     * initialize the intializer
     * @param Offshoot_Application_Module_Bootstrap $bootstrap
     * @throws Zend_Application_Bootstrap_Exception
     */
    public function __construct($bootstrap)
    {
 
        if (!$bootstrap instanceof Offshoot_Application_Module_Bootstrap) {
            throw new Zend_Application_Bootstrap_Exception(
                __CLASS__ 
                . '::__construct expects an instance of '
                . 'Offshoot_Application_Module_Bootstrap'
            );
        }
 
        $this->_bootstrap = $bootstrap;
 
    }
 
    /**
     *
     * not used but required by interface
     */
    public function run()
    {}
 
    /**
     * get the bootstrap object that is for the module being initialized
     * @return Offshoot_Application_Module_Bootstrap
     */
    public function getBootstrap()
    {
 
        return $this->_bootstrap;
 
    }
 
    /**
     * Bootstrap individual, all, or multiple resources
     *
     * @param  null|string|array $resource
     * @return Offshoot_Application_Module_Initializer
     * @throws Zend_Application_Bootstrap_Exception
     */
    final public function initialize($resource = null)
    {
        $this->_bootstrap($resource);
        return $this;
    }
 
}

The Initializer accepts the module bootstrap as an argument in the constructor, in case I need to use any of it’s methods at a later time. I don’t have a use case for it right now, but I can see the module specific initializer classes benefiting from easy access to the active module’s specific bootstrap class. I define the initialize() method as final, just following the standard set in the BootstrapAbstract class, which defines the bootstrap() method as final. I try to err on the side of consistency.

Let’s look at an example of a module specific Initializer. Let’s assume we have a module in our application called “accounts”

 
class Accounts_Bootstrap_Initializer 
    extends Offshoot_Application_Module_Initializer
{
 
    protected function _initSomething()
    {
        // initialize some resource, or run some module specific code
        print "INITIALIZING";
    }
 
}

You’ll see the similarity in structure to the module bootstrap class, but this class allows us to keep our module setup and our module initialization separate. The functionality remains the same! I think this is what was missing from some of the other solutions that I saw. We are now able to retain the power of the ZF bootstrapping and perform it for only the active module!

We’re getting close, but we’re not done yet. We need to go back and update some of the earlier classes. First of all, we’ll need to add a method to the Offshoot_Application_Module_Bootstrap, to add our Initializer class as a resource that can be autoloaded, as well as update the Offshoot_Application_Module_Bootstrap’s constructor to call this new method.

 
/** @see Offshoot_Application_Module_Bootstrap */
 
public function __construct($application)
{
    parent::__construct($application);
    $this->_loadModuleConfig();
    $this->_loadInitializer();
}
 
/**
 *
 * add the bootstrap intializer to the resource loader
 */
public function _loadInitializer()
{
    $this->getResourceLoader()->addResourceType(
        'Bootstrap_Initializer', 'bootstrap', 'Bootstrap'
    );
}

The last piece of the puzzle is to make our module initializer available in the active module plugin, so that we can call the initialize() method and subsequently initialize (i.e. boostrap) all of it’s module specific resources. Let’s go back and look at the routeShutdown() method of the Offshoot_Controller_Plugin_ActiveModule plugin.

 
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
 
    $activeModuleName = $request->getModuleName();
    $activeBootstrap = $this->_getActiveBootstrap($activeModuleName);
 
    if ($activeBootstrap instanceof Offshoot_Application_Module_Bootstrap) {
 
        $className = ucfirst($activeModuleName) . '_Bootstrap_Initializer';
 
        // don't assume that every module has an initializer...
        if (class_exists($className)) {
            $intializer = new $className($activeBootstrap);
            $intializer->initialize();
        }
 
    }
 
}

And there you have it folks! I would love to hear some feedback on this or any ideas for improvement. If people like it or are interested, I’ll see if we can get a full solution up on github in the near future.

15 Responses to Module Bootstrapping in Zend Framework

  1. Pingback: Tweets that mention Modular Bootstrapping in Zend Framework | Offshoot Blog -- Topsy.com

  2. NGUYEN Chien Cong says:

    Hi Chris,

    I really like your idea of determining and loading Module Bootstrap.
    But the point here is I can’t make your code work.
    I’m quite new at Zend so what I understand in your code is that :

    $moduleList = $this->_getBootstrap()->getResource(‘modules’);

    the bootstrap cannot find any resources for the Modules when I try to dispatch the plugin Offshoot_Controller_Plugin_ActiveModule.

    By any chance, do you have any idea?

    Cheers and thanks in advace

  3. NGUYEN Chien Cong says:

    Hi Chris,

    Don’t worry about the stupid question anymore.
    I managed to fix the problem:
    - by adding resources.modules = “” on application.ini to add modules as resources
    - or by putting ‘modules’ => array(), in the options resources in index.php

    Anyway thanks man for your resolution to the module bootstrapping.

    Cheers

  4. Marco says:

    Hi Chris,

    great job and very neat code. I appreciated the effort to go even further all the other proposed solution.
    However I need still some hits. Everything seems to work properly except the initialiser class looks it doesn’t exists. I was wondering if you can at least add/show where you pur your files to understand what I’m missing. I’m pretty sure it’s matter of path probably but I’m getting stuck in understand where is the problem

    Thank you very much for your time and consideration.

  5. the class names decompose into the paths, so a file like Accounts_Bootstrap_Initializer
    would be in modules/accounts/bootstrap/Initializer.php

    make sure you have the _loadInitializer() method in Offshoot_Application_Module_Bootstrap. that’s what will allow for autoloading of the Initializer class

  6. Marco says:

    My thought… indeed I followed it. Well I’ll try again step by step probably I was tired and I missed something.

    Thanks again.

  7. the only other thing that i can think of is to make sure that your include_path set properly and that there are no spelling mistakes in your class and/or file names

  8. Cong says:

    @Marco

    I think you should put var_dump a bit everywhere to see where you’re stuck at. Anyway, it’s my best way of debugging something in zf.

    cheers

  9. HB says:

    Chris,

    I am somewhat new to Zend but I am performancoholic. Can you tell me what other places I need to add code (application.ini, Application/Bootstrap.php) etc in order to get this running. A sample code would be helpful.

    Regards

    HB

  10. Your best bet for now would be to run through the quick start guide found here: http://framework.zend.com/manual/en/learning.quickstart.intro.html

    by the end of the quick start, you’ll have a fully functioning base ZF application that you can then apply this solution too. Really all you’ll need to add to your application.ini at that point is:

    resources.modules[] =
    resources.frontController.moduleDirectory = APPLICATION_PATH “/modules” ;this will replace resources.frontController.controllerDirectory

    that will ensure that ZF knows to load your modules. and as long as your include_path is set properly and your resource and plugin paths are correct it should all work. i’ll try to post a sample application.ini file shortly

  11. Pingback: Zend Framework: bootstrap e inizializzazione dell’applicazione | Gabriele Romanato

  12. Martin Adamec says:

    Chris,
    Very nice piece of work. I wold like to participate on getting it that one step further as you mentioned at the bottom – to get the full solution up on github.
    Please let me know how I can join your task-force for this one.
    Thanks
    Martin

  13. Japones says:

    Hi Chris,

    I can’t make your code work.
    I’m quite new at Zend so what I understand in your code is that :

    $moduleList = $this->_getBootstrap()->getResource(‘modules’);

    the bootstrap cannot find any resources for the Modules when I try to dispatch the plugin Offshoot_Controller_Plugin_ActiveModule.

    By any chance, do you have any idea?

    I have in my application.ini:

    resources.modules[] = “default”
    resources.modules[] = “admin”

    Thanks

  14. orry says:

    Nice work.
    I have had this up and running in a zf app and it works perfectly.

    I cannot seem to get it to work with phpunit though. I keep getting this error:

    Fatal error: Call to a member function getResource() on a non-object

    Has anyone tried this yet??

    Thanks

  15. Los420 says:

    Nice work!!! Still have a couple of doubts, when I get the options at the module bootstrap with $this->getOptions() I still get both of my modules config items (i have 2 modules i’m testing with) instead of just the active module config file (module.ini). Am i doing something wrong or is it the expected behaviour??

*
To prove that you're not a bot, enter this code
Anti-Spam Image

67 Mowat Suite 307 Toronto, Ontario M6K 3E3 Canada
p: 1.416.656.3362 / 1.866.656.3362 | f: 416.656.7236
© 2012 Offshoot Inc. All Rights Reserved