I’ve recently taken to using Zend Framework for a project that I needed to bring up to date. I won’t go into the pros and cons of choosing a framework as there are many much more qualified people who have done a much better job of this subject than I would or could. So Instead I bring to you How I managed to get Active Module Based Configuration within Zend Framework.
The Problem
The Concept I wanted to achieve was to have unique Configuration based upon the module that was active. The Issue with this is that the Bootstrap files and the _init functions for ALL modules are called with no bias as to which module is active. Thus if you created a 3 modules wanted to make menu alterations in one, those alterations will be applied to all. I also wanted to have a a system where if i added extra modules i could just add extra functions to the bootstrap file and it would work in a similar way.
With this in mind, I set about trying to figure out the solution.
Solution 1 – Failure
With the Idea that no-body is perfect, including myself. My 1st attempt ended in failure. This attempt was to modify/extend the Bootstrap class to add extra functions to the resources list.. In the end I couldn’t determine if the Module bootstrap was the active one. ok So Attempt 1 was a failure, onto the next
Solution 2 – Success!
A quick conversation with Matthew Weier O’Phinney (@weierophinney) pointed me in the direction of Controller plugins and the routeShutdown method, as after the route had been finished which module was active would be able to be discerned.
At this point I must apologise to Pieter Kokx ( @kokxie ) who I had a small disagreement with in the #zftalk channel. Pieter had done his best to point me down this route to start with, though being a stubborn mule I am refused to see the quality and precision of his comments.
Thank you both for your help here.
The way that this works is that is scans the active modules bootstrap for functions starting with activeInit or modulenameInit just like the _init functions but these would only be called if the module is active.
I was successfully able to create the plugin and trigger the functions, unfortunately it was triggering/calling them in a static sense.. which meant that standard _init style code wouldn’t work. Luckily with a little digging in the source of the framework i found a storage of the modules and their initiated bootstrap classes. Lucky Me! So finally we call the methods within the right context.
So Here it is the Final Code, Please do comment, I learn from people as I hope that others can learn from me.
I’ve used the “Namespace” of BinaryKitten here. If you want to use a different “Namespace” Replace BinaryKitten with what you want.
The “Namespace” allows for the use of the BinaryKitten folder within the Libray Folder.
Remember the Controller Plugin should go in the right place for your application, if you are using the autoloader you can add the “Namespace” to be autoloaded via your application.ini
autoloadernamespaces[] = "BinaryKitten"
First off we have the Controller Plugin.
This should go into the “Namespace” folder within the Library Folder and should be called ModuleConfig.php
<?php
<?php
class AonL_ModuleConfig extends Zend_Controller_Plugin_Abstract
{
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
$frontController = Zend_Controller_Front::getInstance();
$bootstrap = $frontController->getParam('bootstrap');
$activeModuleName = $request->getModuleName();
$moduleList = $bootstrap->modules;
$moduleInitName = strtolower($activeModuleName)."Init";
$moduleInitNameLength = strlen($moduleInitName);
if (isset($moduleList[$activeModuleName])) {
$activeModule = $moduleList[$activeModuleName];
$bootstrapMethodNames = get_class_methods($bootstrap);
foreach ($bootstrapMethodNames as $key=>$method) {
$runMethod = false;
$methodNameLength = strlen($method);
if ($moduleInitNameLength < $methodNameLength &&
$moduleInitName == substr($method, 0, $moduleInitNameLength)) {
call_user_func(array($bootstrap,$method));
}
}
} else {
$activeModule = $bootstrap;
}
$methodNames = get_class_methods($activeModule);
foreach ($methodNames as $key=>$method) {
$runMethod = false;
$methodNameLength = strlen($method);
if (10 < $methodNameLength && 'activeInit' === substr($method, 0, 10)) {
$runMethod = true;
} elseif ($moduleInitNameLength < $methodNameLength &&
$moduleInitName == substr($method, 0, $moduleInitNameLength)) {
$runMethod = true;
}
if ($runMethod) {
call_user_func(array($activeModule,$method));
}
}
}
}
Next we need to make sure the Controller Plugin is loaded.
We can do this in one of two ways. Either in the Application Bootstrap via an _init function
public function _initControllerPlugins()
{
$plugin = Zend_Controller_Front::getInstance()->registerPlugin(
new BinaryKitten_ModuleConfig()
);
}
*– Or –*
We can add a line to the application.ini
resources.frontController.plugins.BKModuleConfig = "BinaryKitten_ModuleConfig"
Finally some example init code from the module bootstrap.
Please remember that the activeInit*() Functions need to be public for this to work properly
class Default_Bootstrap extends Zend_Application_Module_Bootstrap {
public function activeInitMenus() {
$layout = $this
->bootstrap('layout')
->getResource('layout');
$view = $layout->getView();
$config = new Zend_Config_Xml(APPLICATION_PATH.'/configs/navigation_default.xml',"menu");
$navigation = new Zend_Navigation($config);
$view->navigation($navigation);
}
public function activeInitDoSomethingElse() {
/* some other code */
}
public function defaultInitSomething() {
/* more code */
}
}
We can also add module inits to the application bootstrap like so:
public function modulenameInitFunction() {
/* place code here */
}
Where modulename is the lowercase version of the Modules name, eg if you have the Admin module, then you would use:
public function adminInitFunction() {
/* place code here */
}
Hopefully someone will find this code useful.
[ Edit January 5th 2010 ]
Thanks to:
Matthew Weier O’Phinney for pointing out places for update.
Rob Allen (@Akrabat) for the info that the plugin could be loaded via the application.ini
Elizabeth Marie Smith and Matthew Turland for questioning the use of the Reflection.
- Updated the Bootstrap code to properly define the functions as Public
- Removed the reflection as this wasn’t actually required any more.
- Updated Post to make clean up the order of things and to properly designate that the BinaryKitten is the “Namespace”
“Namespace” is used to reference that we’re not using PHP5.3 Namespaces, but the Namespaces within the Zend Framework.
[ Edit January 11th 2010 ]
Thanks to:
septem for pointing out a typo where i had $boostrap instead of $bootstrap
Gerard Roche for pointing out that by default, the default module doesn’t require a module bootstrap (in my code it has one)
- Updated to fix the typos
- Added in a quick check to see if the module exists in the modules list of bootstraps
- Removed the _ from the function name that it searches for, this should please the people who are adamant over the Zend Coding Standards
- Added in the functionality to have $modulenameInit() functions as well in both active module bootstrap and the application bootstrap
[ Edit February 13th 2010 ]
Ran the code through codesniffer against the Zend Standard supplied.. updated so no errors found. 3 Warnings are left .. they are as follows:
22 | WARNING | Line exceeds 80 characters; contains 83 characters
35 | WARNING | Line exceeds 80 characters; contains 84 characters
38 | WARNING | Line exceeds 80 characters; contains 83 characters
Don’t think it’s worth the change for 3/4 characters
Out of curiosity, in routeShutdown(), why use reflection for PHP versions < 5.2.6 and get_class_methods() for other versions when the latter is available in all version of PHP 5?
I copied/took that code from the Zend Framework Module Bootstrap itself. You’re not the 1st to question this..
It’s the code from the getClassResources Method of the Zend_Application_Bootstrap_BootstrapAbstract lines 208-240
Have removed it now. As its not really required.
Good and useful one, congratulations.
I am having some architecture issues in implementing a modular Zend based app and maybe we can share some knowledgment.
Thanks
head over to #ZFTalk on Freenode IRC. Great people there willing to help with a lot of things.
Why use underscore to name methods like this: ‘_activeInitDoSomethingElse’ ?
This does not correspond ZF coding standards, does it?
Just to be more similar to standard protected ‘_init’ methods? I don’t see problems with using public methods without underscore here, and I do see potential problems if we don’t follow coding standards.
I agree there is nothing wrong with using public methods without underscores here at all.
This is just there, as you say, to make the method/function name closer to the _init functions/method. I Wrote this for myself and as such, I don’t feel that it doesn’t really need to adhere to the Zend Framework coding standards 100%.
By all means, if you wish to convert this to be more standards compliant then feel free to do so. If you have anything further to ask about this, please also feel free, but please do not let the use of a _ where the coding standard says it shouldn’t from using the code if you want to.
Hi Bk,
I like your idea on how to check whether or not a module is active or not. But I would not go about the module config the way you do. Actually you have just moved setting your navigation up a level. There are methods in your your controllers that you can use that would do the job a lot easier. Plus you would not have dependencies between the module and the application like you have created now.
Lets say you want specific navigation in some of your controllers; either subclass Zend_Controller_Action and add the functionality in that class in maybe the predsipatch() method. which is called before executing the action methods or write the code desired in every controller class you want to use different navigation in. I think predispatch would be a good location for it.
This way you can easily get the view and set its navigation and locate your xml or ini file within your module directory. This would maybe be three lines of code. And will prevent all the overhead, in computing (processing every time) ,dependencies and writing classes like you have.
Just one thing, to sell my idea on module config which your blog entry suggests, have a look at http://www.sreknord.net/blog/zend-framework/zend-framework-module-config-the-easy-way
This though is not an answer to your problem, as I suggest it should be solved elsewhere.
Hi Leonard and Thanks for Commenting. I used setting navigation in the example as originally i wanted to do per module navigation in the app. For me I’ve used the modules for separating the sections of the site, rather than (as some have) for a drop in code collection for specific tasks. I agree that if you are wanting to to do the module navigation then the preDispatch method in a Action or even an action helper would work well. This code might also be used to bootstrap some resources that may be required in the predispatch step. What you place inside the _activeInit*() functions is, after all, down to you the user.
Whatever you use, whatever you do in which ever way you want, in the end I feel that this falls really down to personal preference.
Please recal $boostrap to $bootstrap.
forgot: in BinaryKitten_ModuleConfig line 8
If the default bootstrap is active you get a undefined index on $boostrap->modules[$request->getModuleName()]; as the module name will be “default” and that will be the bootstrap itself, instead try:
$moduleName = $request->getModuleName();
if ('default' == $moduleName) {
$activeModule = $bootstrap;
} else {
$activeModule = $bootstrap->modules[$moduleName];
}
Great plugin!
Fixed and credit given.
Thanks Septem
thanks for pointing that out.
Have made adjustments to the plugin to support that scenario as well as others.
Many thanks again Gerard
Hi BK,
I don’t want to be a pain, but just stumbled upon this by accident while I was working on something completely different. Thought i’d share it with you. A way to easily determine the active module.
/**
* This code here determines the requested module based on the
* getModuleDirectory() method return value. This method returns
* the configured module directory set in configs/application.ini
* plus (+) the requested module
*
* Four step process:
* 1) get front controller
* 2) set some replace values, based upon your configuration
* 3) replace values to get just the requested module name
* 4) set to default when empty
*
*/
$front = Zend_Controller_Front::getInstance();
$from = array (APPLICATION_PATH, ‘modules’, ‘/’, ‘\\’);
$name = str_replace($from,”,$front->getModuleDirectory());
$activeModuleName = ($name == ”) ? ‘default’ : $name;
thanks for that.. but really that is messy .. especially as the after the route is processed you can grab the request object and get the module name from that
$activeModuleName = $request->getModuleName(); the reason i have the front controller referenced is to get the bootstrap which then allows me to get the substantiated version of the class to be able to use as scope on which to call the code..
Hi BK,
I guess I misread. but your right getModuleName method gets you the active module name. I thought I’d found an easier way to do all that work, Just get the active module name and do some work based on it. I guess then my question to you is why do you need to specifically call methods in the ‘active’ module’s bootstrap? I guess the extra work is to centralize the ‘bootstrapping’ to one location?
Pretty much exactly that.. the idea was to have 1 plugin that would allow me to just create a function in the bootstrap to process certain things when that module was the active one.
Keep posting stuff like this i really like it
This is great, but why do you have both activeInit and modulenameInit, instead having just one of them, and have only one foreach for methods?
for example, you don’t need a bootstrap for the default module, so added the option to have the modulenameInit in the main application bootstrap..
The idea for both was to add more versatility with what the end user wanted to do
And I get error while trying to acces resources
$layout = $this
->bootstrap(‘layout’)
->getResource(‘layout’);
Zend_Application_Bootstrap_Exception: Resource matching “layout” not found
thrown in Zend\Application\Bootstrap\BootstrapAbstract.php on line 687
Fixed with
$view = $this->getApplication()->bootstrap(‘view’)->getResource(‘view’);
I’m not sure if that is the right way to do it, but it works.
ah yes, will not that. I use the Layout option in Zend Framework to provide a base design which then has the current view script injected. Which is why there is the layout there.. Please note that it is only demonstration code in that segment, and it’s up to you what you place in your init functions
Just Posted an update for those worried about code standards etc apart from the 3 warnings listed this is 100% code standard error free when placed against the code standards for Zend as supplied with PHP_CodeSniffer … hopefully this will help appease people