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

43 thoughts on “Zend Framework: module config

  1. 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. 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.

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

    Thanks in advance!
    Uli

  4. 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

  5. Pingback: Setting up a Zend Framework project on Mac OS X Leopard « MySQLTalk.com

  6. 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

  7. 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

  8. Pingback: 1.8.0 Bootstrapping Modules and Autoloaders - Page 2 - Zend Framework Forum

  9. 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.

  10. 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)

  11. 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)

    • 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.

    • 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.

  12. 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

    • What is hard to implement? I mean, if you have a bit of confidence with the ZF MVC pattern it shouldn’t be that hard, tell me and I’ll try to help you out.

      • Hi

        I have tried to follow the tutorial and now I have all set but I get this error:
        Fatal error: Uncaught exception ‘Zend_Loader_PluginLoader_Exception’ with message ‘Plugin by name ‘Profile’ was not found in the registry; used paths: Zend_View_Helper_

        I have an helper for the Admin module folder but If I call the Default module I see that error. How have I split the modules?

        Thanks

  13. 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.

    • 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

  14. Hi,
    Sorry, i’m newbie.
    Where can i put this line?
    $this->getInvokeArg(‘bootstrap’)->getOption(‘modulenamehere’);

    In Module Bootstrap, Module Controller or something else?
    Thanks.

  15. I like this approach, but it seems that the “modulesetup” resource is initialized for each module that has its own bootstrap. I think this is the normal behaviour of Zend_Application, that re-initializes resources that are defined before the “modules” resource.

    Anyway, I need Bootstrap classes in my modules, so the approach mentioned by Leonard Donkers is more suitable for me.

    thx for your contributions!

  16. nevermind my last comment, my module bootstrap was extending the Zend_Application_Bootstrap_Bootstrap instead of the appropriate Zend_Application_Module_Bootstrap class. That caused the duplicate initialisation of some resources…

  17. resources.modulesetup[] =
    I’ve already put this line at the first line of my [production] segment but it still takes the resources given in application.ini.

    this is my module.ini
    [production]
    phpSettings.display_startup_errors = 1
    phpSettings.display_errors = 1
    resources.layout.layoutPath = APPLICATION_PATH “/modules/testmodule/layouts/scripts/”
    resources.layout.layout = “reallayout”
    resources.frontController.controllerDirectory = APPLICATION_PATH “/modules/testmodule/controllers”
    resources.frontController.params.displayExceptions = 0

    and this is my application.ini
    [production]
    resources.modulesetup[] = “”
    resources.modules[] = “”
    pluginPaths.Cyberlab_Application_Resource = “Cyberlab/Application/Resource”
    resources.frontController.controllerDirectory = APPLICATION_PATH “/controllers”
    resources.frontController.params.displayExceptions = 0
    resources.frontController.moduleDirectory = APPLICATION_PATH “/modules”
    phpSettings.display_startup_errors = 1
    phpSettings.display_errors = 1
    includePaths.library = APPLICATION_PATH “/../library”
    bootstrap.path = APPLICATION_PATH “/Bootstrap.php”
    bootstrap.class = “Bootstrap”
    appnamespace = “Application”
    resources.view[] =
    resources.layout.layoutPath = APPLICATION_PATH “/layouts/scripts/”
    resources.layout.layout = “common”

    this makes my module’s layout stick to the one inside default layout path.
    does anyone know how to solve this?

  18. Pingback: Zend Framework: 模块(Module)特定的配置

  19. Hi,

    Am I missing something or wouldn’t it be better to add the module options to the Zend_Config object in Zend_Registry?

    Bazmo

  20. I have a working and simpler solution.
    First of all, let’s start from the main Bootstrap.php file:
    —-CUT—-
    protected function _initConfig()
    {
    $config = new Zend_Config_Ini(APPLICATION_PATH . ‘/configs/application.ini’, APPLICATION_ENV, true);
    Zend_Registry::set(‘Zend_Config’, $config);
    }
    —-CUT—-
    This will load the “main” config file and put it in the Registry for later use. Watch out for the “true” parameter of Zend_Config_Ini. That does the magic, because allows us to _modify_ the config object after populating it with the ini file.
    Now, let’s take our example module, i.e. “default”, and open up its Bootstrap.php file:
    —-CUT—-
    class Default_Bootstrap extends Zend_Application_Module_Bootstrap
    {
    protected function _initConfig()
    {
    $config = new Zend_Config_Ini(dirname(__FILE__) . ‘/configs/module.ini’, APPLICATION_ENV);
    $globalconfig = Zend_Registry::get(‘Zend_Config’);
    $globalconfig->merge($config);
    $globalconfig->setReadOnly();
    Zend_Registry::set(‘Zend_Config’, $globalconfig);
    }
    }
    —-CUT—-
    Here is the magic: we retrieve the “main” config file from the registry, merge it with the entries in our module-specific configfile, make it read-only and put it back in the registry.
    example module.ini
    —-CUT—-
    [production]
    default.auth.table.tablename = “users”
    default.auth.table.username = “email”
    default.auth.table.password = “password”
    default.auth.table.encryption =

    [staging : production]
    [testing : production]
    [development : production]
    —-CUT—–

    Now, if we load Zend_Config from our registry, it will contain both application- and module-specific configuration.
    NOTICE: the module.ini should contain the module prefix for every key, otherwise existing keys could be overwritten (maybe you want to…).

    That’s it. No need to add strange parameters to application.ini, just edit the Bootstrap.php.

    • This will not work with more than one module since Zend Framework is loading EACH module’s bootstrap not only the active one.

  21. i need to know how i can call action in another controller and another module , but taking in consedration that i have these folders structur:

    application
    ->administration
    -> Hrs
    ->model
    ->view
    ->controller
    ->HrsController

    -> Prl
    ->model
    ->view
    ->controller
    ->Production
    -> sal
    ->model
    ->view
    ->controller

    ->model
    -> view
    ->controller
    ->IndexController

    i need in the IndexController to call the action method (hrsmenuAction) which belongs to the HrsController
    from hrs module in the Administration folder .

    i tried the following :

    indexController.php
    $this->_forword(‘hrsmenu’,Hrs,Hrs);

    but the application says :

    Invalid controller specified (Hrs)

    please what can i do to solve this problem ?? :(

  22. sorry the structure is :

    application
    ->administration
    -> Hrs
    ->model
    ->view
    ->controller
    ->HrsController

    -> Prl
    ->model
    ->view
    ->controller
    ->Production
    -> sal
    ->model
    ->view
    ->controller

    ->model
    -> view
    ->controller
    ->IndexController

  23. Doesn’t load anything for me.
    I have in application.ini a

    resources.view.doctype = XHTML1_TRANSITIONAL

    In admin module, module.ini

    admin.resources.view.doctype = XHTML1_STRICT

    When in admin module the doctype is still XHTML1_TRANSITIONAL

  24. This post is a year old and STILL it proved to be excellent! I’m new to Zend Framework and yet I find it baffling how many of the negative commenters here failed to simply follow the instructions.

    Case in point – Argo right above me, missed the part where Matthijs says in the post that in your modules ini files, you reference module-specific resources without the normal preceding module qualifier e.g “resources.view.doctype” and NOT “admin.resources.view.doctype”.

    Aside from ZF throwing an error that required me to include a “development” sub-section in my module.ini, the implementation worked perfectly!

    Thanks for a very useful article Matthijs.

  25. Pingback: Zend Framework: module specific frontcontroller plugins | vandenbos.org

  26. Pingback: Zend Framework: module specific layout | vandenbos.org

  27. Pingback: Zend Framework: per module translation sources | vandenbos.org

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>