Creating and Managing Zend Framework Modules

In one of my previous blog posts, there was a question related to the modular structure of the Zend Framework. Later the comments moved to the email and I suppose it will be nice to share what we talked in this blog too, so it becomes common knowledge :)

The talk was about starting a ZF project from scratch with different modules which can be plugged seamlessly, making it work with simple copy pastes. Here I will talk about this. As always you can find the code examples in my github, here.

In the left you are seeing the folder structure for a simple ZF project. It contains tests, config, public and modules and models and data and library etc. What is important is the modules directory for the moment. Every module contains it’s own controllers and views directories by default. You can also add models or data or forms folder in order to create a beautiful module package. However I will not get into the details of that, you can make it out on your own according to my previous post, as it’s quite nearly the same.

Once you have created the folder structure, we should do two things before we get our hands dirty with our module files. First we should define in our Bootstrap.php where our modules exists:

    protected function _initSiteModules()
    {
        //Don't forget to bootstrap the front controller as the resource may not been created yet...
        $this->bootstrap("frontController");
        $front = $this->getResource("frontController");

        //Add modules dirs to the controllers for default routes...
        $front->addModuleDirectory(APPLICATION_PATH . '/modules');
    }

That’s all! Now our application knows where to look for modules. But we should also define some things in the application ini too. Put these lines anywhere in the right staging environment.

resources.modules = ""

; Lets also define some module specific configuration here. It should contain the module name as prefix ("custom" in our case)
custom.config.enabled = 1 ;Some module specific configuration can be like this, check the module's bootstrap on how to use it...

As we have handled everything else, let’s get down to business with the module bootstrap file:

/**
 * Module's bootstrap file.
 * Notice the bootstrap class' name is "Modulename_"Bootstrap.
 * When creating your own modules make sure that you are using the correct namespace
 */
class Custom_Bootstrap extends Zend_Application_Module_Bootstrap
{

    protected function _bootstrap()
    {
        //Now let's parse the module specific configuration
        //Path might change however this is probably the one you won't ever need to change...
        //And also don't forget to use the current staging environment by sending the APP_ENV parameter to the Zend_Config
        $_conf = new Zend_Config_Ini(APPLICATION_PATH . "/modules/" . $this->getModuleName() . "/config/application.ini", APPLICATION_ENV);
        $this->_options = array_merge($this->_options, $_conf->toArray()); //Let's merge the both arrays so that we can use them together...
        parent::_bootstrap(); //Well our custom bootstrap logic should end with the actual bootstrapping, now that we have merged both configs, we can go on...
    }
    
    protected function _initMyModule()
    {
        $_isEnabled = $this->_options['config']['enabled'];
        if ($_isEnabled != 1) {
            throw new Exception($this->_options[$this->_options["language"]["selected"]]["error_message"]);
        }
    }
    
    protected function _initModuleLangArray()
    {
        //Now let's define our language array to the registry so that we can use it...
        //Notice I have added "Custom_" prefix to the registry to avoid any conflicts with other modules...
        Zend_Registry::set("Custom_language", $this->_options[$this->_options["language"]["selected"]]);
    }
}

Pretty easy right? I have overwritten the my parent class’ _bootstrap() and merged the options coming from the global app config file with my local, module specific app config file. Let’s also look to the application.ini:

[production]
language.selected = tr
language.available[] = tr
language.available[] = en
config.enabled = 0 ;You can overwrite options defined in the global application.ini

;You should better use Zend_Translate for this. I'm a bit lazy for it :)
tr.hello_message = "Merhaba Dunya!"
tr.error_message = "Bu mod su anda kullanilabilir degildir."

en.hello_message = "Hello World!"
en.error_message = "This mod is not available for the moment."

[testing : production]

;We have to support the staging environment right?
[development : production]
language.available[] = fr

fr.hello_message = "Bonjour Monde!"
fr.error_message = "Ce mod n'est pas disponible pour le moment."

As you see it’s the same with the global application.ini file. However it only contains less information as it only requires module specific configurations. I have added an enabler conf and some multi-language support thing. It’s very simplistic and you should never implement multi-language like this however it helps as a proof of concept. As you notice we also haven’t forgotten the staging. It’s important because you will want to have different configurations for your production, testing and development environment. We ensure the consistency of the environment by using constant we had defined previously “APPLICATION_ENV”. Let’s finally look to our IndexController and view scripts:

/**
 * Custom module's index controller. You should notice the "Custom" Namespace
 */
class Custom_IndexController extends Zend_Controller_Action
{
    
    private $_lang = array();
    
    public function init()
    {
        $this->_lang = Zend_Registry::get("Custom_language");
    }
    
    /*
     * This actions is reached via url: http://www.example.com/custom/index/index
     * custom = module name
     * index = controller name
     * index = action name
     * 
     * This kind of route is defined in the application Bootstrap in the _initSiteRoutes()
     */
    public function indexAction()
    {
        $this->view->message = $this->_lang["hello_message"];
    }
}

and our view script index.phtml:

message?>

Go on have a try! What?! Exceptions!!?? No way, but we had enabled it in the global config file… Yes, that’s what you guessed. The options in your module specific configuration file overwrites the ones defined in the global file! How? Look here, don’t expect everything from me :)

By following similar ways you have used when defining your global conf, you can also define module specific options and add module specific database configurations, connections or forms etc. You can create a zip file, give it to your friend and tell him to unpack in the modules directory and he is good to go. Your module is ready to call!

  • Pingback: Zend Framework News » Blog Archive » Erstellen und Einsetzen von Modulen mit dem Zend Framework()

  • Pingback: How to Create Custom Doctrine Models for Pluggable ZF Modules | Kingdom of Roi()

  • Miresnare

    Hi. I’m just starting out on Zend and this looks to be what I need to create and admin module with the overall aim of creating a CMS.

    I’m downloaded the github code and, got it set up, but all I get is a 500 internal server error.

    Can you shed some loight on what I need to start exploring the code?

    Thanks in advance.

  • Where do you get the 500 and what do you get as an error. It should contain a stack trace or a similar thing.

    I might be more helpful if you describe the errors you see. Because I didn’t get any error when i fetched the code from github.
    I found a typo in the userManager controller. Seems like instead of findAll I had used fetchAll. which was causing a problem. I updated the code. Maybe this solves your problem too.

  • swe

    what is flow of executing project in zendfrmework?

  • I didn’t get it. Do you mean the execution order of a request? Routing of modules?

    Well perhaps what you are looking for the is front controller pattern which is used by MVCs. This might give you and idea of dispatching a request. If you think of routes, ZF first checks if there is a predefined route for that link, then it checks for the module names (if it founds anything it looks for the controllers), then it looks for the default module’s controllers (if no controller is specified it looks for the IndexController), then it looks the folders in the public folder and finally generates a 404 error.

    Was that what you were asking or something completely different?

  • it’s much easier and less verbose (if you’re using Zend_Application) to bootstrap the module directory with a config entry:

    resources.frontController.moduleDirectory = APPLICATION_PATH “/modules”

  • it’s much easier and less verbose (if you’re using Zend_Application) to bootstrap the module directory with a config entry:

    resources.frontController.moduleDirectory = APPLICATION_PATH “/modules”

  • Hey,

    Did you try to merge your module config.ini with main application.ini? Thats what I see in your example:

    //And also don’t forget to use the current staging environment by sending the APP_ENV parameter to the Zend_Config
    $_conf = new Zend_Config_Ini(APPLICATION_PATH . “/modules/” . $this->getModuleName() . “/config/application.ini”, APPLICATION_ENV);
    $this->_options = array_merge($this->_options, $_conf->toArray()); //Let’s merge the both arrays so that we can use them together…
    parent::_bootstrap(); //Well our custom bootstrap logic should end with the actual bootstrapping, now that we have merged both configs, we can go on…
    }

    but $this->_options is empty when your module bootstrap is running.
    Maybe I didn’t understand something?

    BTW, your article is very good, thank you very much.

  • Pingback: Zend Framework – Working with Modules « Shaked Klein Orbach()

  • On the module bootstrap, there aren’t options related to the whole application, only the options defined specific to your own module. If there isn’t any option related to your module in the main app.ini, then the options will be empty. I hope this answers your question.

  • Roy Simkes

    Actually no, It’s perfectly valid to keep your old structure and it get
    used as default. I don’t get what you mean by “ignore”. Do you mean that
    when you write localhost/myapp/index it redirects to the custom’s index
    instead of default’s? It’s interesting because even the
    project-from-scratch does not have the default module directory. I
    already have another app which does not have such a module directory too.

  • Kieron

    Unfortunately following your tutorial my application ignores the default module and just uses the custom module. I’m assuming this is because I should now use the conventional modular structure where the default module is inside the modules folder?

  • naphstor

    when i tried to run your example project, i just got the welcome page which we used to get while running the url “localhost//public” . can you please tell me what is the actual output of your application.

  • Try localhost/public/index.php/userManager/index and I suppose you will see the user manager related things. However there is nothing exceptional about the project. It’s just a base project containing some infrastructure related code. It contains some simple examples about Doctrine too and that’s all.

  • Hbyangzhi0206

    you saved me. just one sentence.

  • Pingback: Zend Framework 采用 modules 式文件结构 — Shamrocker()

  • Pingback: Zend Framework « Blog Ramon RDM()

  • Pingback: Modular Zend Framework | Kerek egy ég alatt()

  • Pingback: 生如夏花 » 自定义的 Zend Framework 开发环境构建()

  • Rahulbanga10

    Tell me wat will be the URL for default module and for Custom module….
    if our application contains 1. “default” module in “Module” dir 2.”Custom” module in “Module “dir

  • roysimkes

    http://localhost/index redirects to the default module’s index controller’s index action

    http://localhost/custom/index redirects to the custome module’s index controller’s index action.

    The name of the module, with all lowercase characters is the prefix to call that module’s controllers and actions.

  • Pingback: Zend Framework | Ramon RDM()