Zend Framework: module config

Zend Framework supports modules, but in my opinion, not in a modular way. I have been trying to set up self-containing modules with their own configuration. The objective is to have an architecture where I can just drop a module in the modules directory, and it will work immediately without further configuration.

Now that there is Zend Application with the Modules Resource, that is almost possible. There is one problem. The module specific config still has to be put in application.ini. On the Zend Framework mailinglists, many people are clamoring for module specific configuration files. In this article I will explain how to set up modules with their own config files.


Of course as a good developer should, I first started searching for a solution by someone else. I found lots of complaints, but no solutions, until I happened upon Jeroen Keppens’ blog. He has been busy creating a solution to this problem as can be read in this post.

In a nutshell, his solution is this: he has extended Zend_Application_Module_Bootstrap to automatically register and execute a bootstrap resource called Moduleconfig, that searches for ini files in the configs directory of the module. These ini settings are then used as the options for the module bootstrap. This way, a module can set up its own resources.

This is a valid approach and it works, but I had a few problems with it.

  • I found it impossible to load a frontcontroller plugin in the module config. The frontcontroller resource would get called by the module bootstrap, but the frontcontroller bootstrap resource looks at the application options for plugins and ignores module options so it would do nothing.
  • A nitpick: the settings from the module ini files don’t become part of the main application options. This makes them harder to get at in your controllers. Instead of
    $this->getInvokeArg('bootstrap')->getOptions();

    you need to do this:

    $this->getInvokeArg('bootstrap')->getResource('modules')
            ->offsetGet('modulenamehere')->getOptions();

I have come up with a solution that is strongly based on Jeroen’s solution, but with an important difference. Instead of letting each module bootstrap get its own config when it is bootstrapped, I chose to create a resource for the main bootstrap that looks for config files in all module config directories and merges them with the application options. This way, the application options array looks the same as if you would have put the ini settings directly in the application.ini.

First, I will show you how to use this solution. Then I’ll share my code with you so you can use it for yourself.

Usage

In your application.ini, you set the resources to be loaded. Note that the resources are loaded in the order you provide them. The Bootstrap resource that is going to do the module setup for us is called Modulesetup. It needs to be loaded as the very first resource, so put it at the top of you [production] segment:

resources.modulesetup[] =

Directly after that I set the modules resource, so that all module-specific resources are loaded first:

resources.modules[] =

Then we create a module.ini in /path/to/application/modules/modulename/configs/module.ini. In that file you can set anything you want, just as if you set it in application.ini. There is one difference: in application.ini you would set it like so:

modulename.resources.frontcontroller.plugins.foo = bar

In your module.ini, you don’t have to set the module prefix, this is done for you:

resources.frontcontroller.plugins.foo = bar

You can now get at your module specific settings in the following ways:

$this->getInvokeArg('bootstrap')->getOption('modulenamehere');

the same but longer:

$this->getInvokeArg('bootstrap')->getResource('modules')
       ->offsetGet('modulenamehere')->getOptions();

And to get your module specific loaded resources:

$this->getInvokeArg('bootstrap')->getResource('modules')
        ->offsetGet('modulenamehere')->getResource('resourcenamehere');

Code

All you need is the following class:

class Rexus_Application_Resource_Modulesetup
    extends Zend_Application_Resource_ResourceAbstract
{
    public function init()
    {
        $this->_getModuleSetup();
    }
 
    /**
     * Load the module's ini files
     *
     * @return void
     */
    protected function _getModuleSetup()
    {
        $bootstrap = $this->getBootstrap();
 
        if (!($bootstrap instanceof Zend_Application_Bootstrap_Bootstrap)) {
            throw new Zend_Application_Exception('Invalid bootstrap class');
        }
 
        $bootstrap->bootstrap('frontcontroller');
        $front = $bootstrap->getResource('frontcontroller');
        $modules = $front->getControllerDirectory();
 
        foreach (array_keys($modules) as $module) {
            $configPath  = $front->getModuleDirectory($module) 
                         . DIRECTORY_SEPARATOR . 'configs';
            if (file_exists($configPath)) {
                $cfgdir = new DirectoryIterator($configPath);
                $appOptions = $this->getBootstrap()->getOptions();
 
                foreach ($cfgdir as $file) {
                    if ($file->isFile()) {
                        $filename = $file->getFilename();
                        $options = $this->_loadOptions($configPath 
                                 . DIRECTORY_SEPARATOR . $filename);
                        if (($len = strpos($filename, '.')) !== false) {
                            $cfgtype = substr($filename, 0, $len);
                        } else {
                            $cfgtype = $filename;
                        }
 
                        if (strtolower($cfgtype) == 'module') {
                            if (array_key_exists($module, $appOptions)) {
                                if (is_array($appOptions[$module])) {
                                    $appOptions[$module] =
                                        array_merge($appOptions[$module], $options);
                                } else {
                                    $appOptions[$module] = $options;
                                }
                            } else {
                                $appOptions[$module] = $options;
                            }
                        } else {
                            $appOptions[$module]['resources'][$cfgtype] = $options;
                        }
                    }
                }
                $this->getBootstrap()->setOptions($appOptions);
            } else {
                continue;
            }
        }
    }
 
    /**
     * Load the config file
     *
     * @param string $fullpath
     * @return array
     */
    protected function _loadOptions($fullpath)
    {
        if (file_exists($fullpath)) {
            switch(substr(trim(strtolower($fullpath)), -3))
            {
                case 'ini':
                    $cfg = new Zend_Config_Ini($fullpath, $this->getBootstrap()
                                                    ->getEnvironment());
                    break;
                case 'xml':
                    $cfg = new Zend_Config_Xml($fullpath, $this->getBootstrap()
                                                    ->getEnvironment());
                    break;
                default:
                    throw new Zend_Config_Exception('Invalid format for config file');
                    break;
            }
        } else {
            throw new Zend_Application_Resource_Exception('File does not exist');
        }
        return $cfg->toArray();
    }
}

Put it in your library directory and don’t forget to set the prefix for your own resources in your application.ini like so:

pluginPaths.Rexus_Application_Resource = "Rexus/Application/Resource"

I use Rexus as the prefix for all my own stuff, but you can change this of course.

I am curious what you think.

edit: comment from Michel: changed the way the module directory is determined from hardcoded to dynamic

21 Comments

  1. Michel says:

    Great,

    Tested and seem to work so far. I still have to develop a little module to test this further but I did receive an error when my test module ini had resources.frontcontroller.plugins.foo = bar in it so ZF does load the ini and try to setup the resource.

    Couple of things…

    First, I see in the code that the path to the files are hard coded. Could it be change to get these path from the main application.ini resources.frontController.moduleDirectory

    Secondly, Scanning for INI files in the whole modules directory tree could led to longer application loading times as modules grows in number. We must find a way to only load the needed module specific configurations.

    I don’t understand why is not automatically done by ZF.

    I’m fairly new to ZF so it’s hard for me to grasp every aspect of it. One thing is sure is that my whole site will be modular since it will be base on my own little module based CSM. I started the project a long time ago and I’m porting it to ZF cause it seem to me that I was reinventing the wheel on part of it when something was already created and did the same thing and was really more stable then my own implementation.

    Once I have the modular aspect set it wont take to long to had other things also set to place.

  2. Matthijs says:

    Hi Michel,

    Good point about the path to the module dir. I’ll fix it.

  3. Andrew says:

    Thanks Jeroen and Matthijs, this is a very helpful resource.

    One issue I had with your implementation is that it expects all config files found to be constructed with the application environment as section names. I have other config files in my configs directory so I had to add some code to skip certain files. The resulting bootstrap configuration changes from:

    resources.modulesetup[] =

    to the following:

    resources.modulesetup.skip.default[] = config1.ini
    resources.modulesetup.skip.default[] = config2.ini
    resources.modulesetup.skip.default[] = config3.ini

    Where default represents the module name for which you want to skip the file.

  4. Uli says:

    Very helpful, thanks! Could you post your complete application.ini for this example? Or perhaps the hole source-code?

    Thanks in advance!
    Uli

  5. neobeacon says:

    I’m new comer for zend framework and especially for modules in ZF.I’m little bit confuse with your modifications.Pleace, if you can give a link or email me to modified version of complete project.I’m confuse with what parts should be remove from the project of Jeroen Keppens’.Please help me,I’m at neobeacon@gmail.com.Thanks

  6. Joe Devon says:

    Nice addition to Jeroen’s work!

  7. Chris says:

    Thanks for sharing this!

  8. vatoer says:

    im newbie in ZEND , i have created project with scenario below

    project name : test

    module : admin , user

    controller for each module index

    i use :

    c:> zf create project c:\www\test

    c:\www\test>zf create module admin

    c:\www\test>zf create module user

    c:\www\test>zf create controller index index-action-included[=1] admin

    in apache configuration (httpd.conf) i use
    ..

    Order deny,allow
    Allow from all

    Alias /test “C:\www\test\public”

    AllowOverride All

    ………

    finally i browse my project

    http://localhost/test/
    –> result : display successfully

    http://localhost/test/admin/
    –> result :

    Not Found

    The requested URL /www/test/public/index.php was not found on this server.

    question :

    what i have to do to set up module.i have read some article but still i cant get it.
    do i have to configure my bootstrap.php or application.ini ,. i touch then yet at all.
    how ? i really confused thx for helping
    please email me for answer vatoer_ckplus@yahoo.com
    thx

  9. Romeo Manzur says:

    Hi vatoer, you should create a virtual domain host on your apache (ex: http://test.local) and put the public folder as the document root, here is an example

    ServerName test.local
    DocumentRoot “D:/webs/test/public”

    AllowOverride All

    greetings

  10. [...] other problems. Then I started implementing Module-level ini configurations following this post : Zend Framework: module config | vandenbos.org and discovered the solution to getting Module-level Bootstrap files to actually load. The answer [...]

  11. Nicklas says:

    Looks like a great solution, but I don’t get the tutorial totally. Could you please attach your total project source zipped in the blog?

    /N

  12. Onur says:

    Dear Author,

    Thank you very much for this post. It is really very usefull. Could you please send me the all structures codes over this post , module specific frontcontroller plugins post and per module translation sources post? I and my team debate over a moduler structre for our newest project.

    In my opionian you should write a tutorial serials over this subjects. And you can share your codes at SVN.

    Best Regards.

  13. Hemant says:

    Hi Matthijs,

    After going through following methods:

    http://blog.keppens.biz/2009/06/zend-framework-module-specific-config.html

    and

    http://blog.astrumfutura.com/archives/415-Self-Contained-Reusable-Zend-Framework-Modules-With-Standardised-Configurators.html

    I found your way is best because its clean and clear.

    But can I have separate bootstrap.php for each module? If Yes then kindly highlight.

    I want to add an view helper path in a module, how can I add it? (Actually, in non-modular structure I could add in Bootstrap.php but I am facing error)

  14. Hemant says:

    Hi again,

    I got my problem’s solution. In fact, Your code is superb but,

    According to me, it can be optimized further as follows:

    1. Put “$appOptions = $this->getBootstrap()->getOptions();” before all loops.

    2. In

    if (($len = strpos($filename, ‘.’)) !== false) {
    $cfgtype = substr($filename, 0, $len);
    } else {
    $cfgtype = $filename;
    }

    following code is not redundant:
    else {
    $cfgtype = $filename;
    }

    3. Similarly, last “else” is redundant.

    I am sorry if I hurt a programmer’s soul but I am just making a excellent code into perfect one.
    (If I am wrong then I’ll happily learn new concept)

    • -PK- says:

      According to point 1 I think it’s better to have it inside, in my opinion it’s pointless to set e variable is it’s not really needed. Especially because appOptions represent the specific module config file and it’s not said that each module will have specific ini.

      For the points 2 and 3 I agree with you so far… but maybe we are not seeing something I don’t know… I’d like to wait for a Matthiss’s reply.

    • -PK- says:

      For the points 1 I think it’s better to have it inside. Set a variable if it’s not needed it’s pointless in my opinion this because it’s not said that you will have a specific config file for each module. So the solution provided it’s ok for me.

      Viceversa for the pointd 2 and 3 I agree with you. They look strange to me too. I’d like to wait for a Matthijs’s reply.

  15. Michelangelo says:

    It’s a good job but it is so hard to implement it if we have not a complete project.
    Please share your zend project in this page.

    thanks

  16. Hi,

    I tried a similar approach but I think a module should be as its name suggests; modular. Which to me means it can be dropped into any ZF application without dependencies from the main application. And actually writing resource plug-ins defeats that purpose plus Zend_Application_Module_Bootstrap provides all the means necessary. It has its own resource container (Registry) plus is has methods such as set- and getOptions. What I also recognize is that accessing these specific module bootstrap options ore resources could be easier.

    Have a look at http://www.sreknord.net/blog/zend-framework/zend-framework-module-config-the-easy-way

    As to your solution: you could also use (in your module bootstrap class) $this->getApplication()->setOptions() to set the options globally it desired.

    Lastly, I tried your code: I also have routes defined for my modules, your resource class tries to parse those files too, as they have the .ini extension and reside in the same folder which then results into an error.

    • -PK- says:

      Thank you Len for sharing also your idea…. I’d like to know also Matthijs’s opinion regarding your approach in order to know if he has thought to it too or not. If yes I’d like to know why he has discarded it.

      Regards

Leave a Reply