On modules
by Naneau
Modules are a way of separating your software into re-usable blocks. For those new to the Zend Framework, it is often a source of confusion. I’m going to try to explain the philosophy behind them briefly, and give you a few tips on how to implement them.
The example given in the documentation is that of a site that consists of both a forum and a blog section. While they are both part of a single application, a blog is different from a forum and therefore separated. You may also find yourself in the situation that you want to create a new application later, which exists of a blog and a shop. If you have a blog module ready to go it will save you half of the development.
Deciding which parts of your application are not strongly linked, and could therefore be put in a module is really your own choice. You may find that you don’t have a need for modules at all. That’s fine, but just make to double check that, because nothing is more frustrating than having to rewrite code.
When writing modules, you should always try to keep in mind that if you play your cards right, you may be able to reuse them later on. That’s why all module-related code should be put in a single directory:
1 2 3 4 5 6 7 8 9 10 11 12 | ModuleName/ controllers/ IndexController.php [...] models/ views/ scripts/ index/ index.phtml [...] helpers/ filters/ |
You should be able to grab that directory later on, and put it into a different application. As you can see the ‘views’ directory has three subdirectories. There’s the ’scripts’ directory, in which you should put your actual views. The “helpers” and “filters” directories are, like you may expect, for view helpers and filters respectively.
As for the controllers, it is indicated in the manual that you can subclass Zend_Controller_Action and have all your actual controllers extend from that subclass. While this is of course a good way of avoiding code repetition, you may find that it will stand in the way of module portability. While I can’t give you a definitive answer on what is better (it really depends on the situation), if you can avoid using a custom base class, and use Zend_Controller_Action directly, do so.
One thing you should remember is that the naming convention for modules follow the coding standard. This means that if you were create a file called IndexController.php, for an index controller, the name of the actual class should be ModuleName_IndexController. Always prepend the name of the module, with an underscore, to the name. This should help you to distinguish controllers from different modules.
Setting up a module is relatively easy. In your bootstrap file (index.php) you can add it’s controller path to the front controller like so:
1 2 | $frontController->addControllerDirectory('application/blog/controllers', 'blog'); //add a module called 'blog' |
But if you use the conventional layout for modules (the one described in this post), and put them all under a single subdirectory of your application directory, you can just add all modules with one line of code:
1 | $frontController->addModuleDirectory('application/modules'); |
As one last thing, please remember that even if you don’t use modules, you are using modules. The front controller will always look for a ‘default’ module. You don’t have to explicitly specify it in your controllers and directory structure, though. You can change the default module the front controller uses like this:
1 | $frontController->setDefaultModule('moduleName'); |
Update
Andries, who is a nice guy that hangs out a lot on #zftalk, knows a lot about the zend framework, being on the team. He has put together two sample files for you to download with a default/conventional or modular layout for any application. Have a look at his blog. Just add water!
Comments
I have some weird behavior when using the conventional layout for modules.
I have the following in my bootstrap:
$LfrontController_obj = Zend_Controller_Front::getInstance();
$LfrontController_obj->setControllerDirectory(array(
‘default’ => ‘../application/default/controllers’,
‘acv’ => ‘../application/acv/controllers’
));
$LfrontController_obj->throwExceptions(false);
$LfrontController_obj->registerPlugin(new Zend_Controller_Plugin_ErrorHandler(array(
‘module’ => ‘default’,
‘controller’ => ‘index’,
‘action’ => ‘error’
)));
So i defined 1 module apart from the default one. I am also using the plugin errorhandler because i find it a nice feature to use during development.
In my index controller i have the following actions:
indexAction => does nothing specific just renders a tpl file
listAction => does nothing specific just renders a tpl file
The following occures when i enter these in the url:
http://site/ => works, display the index tpl
http://site/index => works, display the index tpl
http://site/list => error Invalid controller specified (list)
http://site/index/list => works, displays the list.tpl
I thought these errors occured because i use the Plugin_ErrorHandler but when i disable it i still get the same results.
I thought the framework searches first for a controller and if it can’t find a controller with the name specified it searches for an action with the same name in the indexcontroller. Is this a bug or is this normal ? I can’t find anything more in the zf manual.
Update:
I found this in the zf manual:
7.11.4. Module or Global Default Controller
In the default router, if a controller was not specified in the URL, a default controller is used (IndexController, unless otherwise requested). With modular controllers, if a module has been specified but no controller, the dispatcher first looks for this default controller in the module path, and then falls back on the default controller found in the ‘default’, global, namespace.
If you wish to always default to the global namespace, set the useGlobalDefault parameter in the front controller:
setParam(‘useGlobalDefault’, true);
?>
In my case there is no module and no controller specified but it knows i am using a modular layout so then it should check when no module and no controller are provided it should default to the indexcontroller in ‘default’ and call the action specified in url
If you specify something after the ‘base url’, the router will look for either a controller or a module. If you want to go to a specific action (not ‘index’), you will have to specify both the controller AND the action name (and module, if needed).
A lot of people find this behavior weird but that’s just how it works. You must specify index/list if you want the list action of the indexcontroller. If you really can’t live with that, you can add a custom route to your router. Have a look at:
http://framework.zend.com/manual/en/zend.controller.router.html
Hi Maurice,
first of all, i didn’t take the time yet to thank you for your blog. I enjoy reading it.
Now, I have a question about modules. Every single example about it, shows the model directory under the module one. Does it mean that if two modules need the same model classes, I shouldn’t have my code split into thi two modules ? I mean, is the model access a criteria to consider whether an application should be split into several modules ?
Hum…don’t know if I’m clear enough…
Regards,
fred
You are clear enough, it’s a good question. Sometimes it makes sense to let a module have it’s own models, from an architectural point of view. Other times, there are models that should work application-wide. The decision to split an application into modules can be based on many factors, logical grouping of models is one of them.
As for application-wide models, there is no convention yet on where to put them. Personally, I’d go for a “global” directory under your application’s directory, and have the models there. Others would say that global models should be under the default module. Either way, the important part is that they are in you include path.
Spotsec has a solution for adding the models subdirectories of your modules to your include path transparently. Check out: http://www.spotsec.com/blogs/archive/zend-model-loading-modelloader.html?Itemid=125
Thank you Maurice,
the Spotsec solution is interesting, but it doesn’t fit my need. I keep it in mind though. I’d go for a “global” directory and have the models there too. Got to try it, I will let you know the problems encountered (there are always problems…:)).
regards,
fred
[...] Naneau explain a modular setup in more details here. [...]
[...] original: http://andries.systray.be metodología de funcionamiento: http://naneau.nl « ¿Quieres saber quién te tiene no admitido/eliminado en el MSN? Pues no des tu [...]
I just want to say that your post saved my ass. As a newbie I would never check the ModuleName_IndexController catch.
Thank you!
[...] part out, but most of that was me trying to get it to do variations. There are some pretty good articles about how to do this in addition to the [...]
After reading all this alot more makes sense to me now but I still have one problem…
My bootstrap is as shown:
$controller = Zend_Controller_Front::getInstance();
$controller->addModuleDirectory(‘/home/digi/include/modules’);
$controller->setDefaultModule(‘/home/digi/include/modules/default’);
$controller->setParam(‘useDefaultControllerAlways’, true);
I have tried this with and without both setDefaultModule and also setParam too which both keep giving me “No default module defined for this application” althought I have checked and checked again and the settings seem right.
If I try doing it this way:
$controller->setControllerDirectory(array(
‘default’ => $config->paths->base . ‘include/default/Controllers’,
‘zone’ => $config->paths->base . ‘include/zone/Controllers’,
‘admin’ => $config->paths->base . ‘include/admin/Controllers’
));
This all works OK. I seem to be getting very confused here somewhere?!….
My directory structure is:
include/
modules/
default/
Controller/
IndexController.php
and so forth…..
Can anyone shed any light on this for me please?
I merely want to repeate ‘Programmer’, so I will:
I just want to say that your post saved my ass. As a newbie I would never check the ModuleName_IndexController catch.
Thank you!
Browsing the web for modular-setup tutorials whole of the afternoon, and nowhere is stated that the controllername within a module should be prepended with the module name. Ok, I overlooked this perhaps, but finally …
Thanks again.
[...] different. I have mentioned this on my blog before, but it keeps popping up. If you plan on writing modules for an application, it pays to design them in such a way that they can be re-used in another [...]