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!