Providing your own URL rules at runtime
When you are developing an application with a pluggable module architecture, you most likely need to somehow inject your module-specific rules into an existing application.
Getting ready
- Set up a new application using
yiic webapp
. - Add
.htaccess
, shown in the official URL management guide to yourwebroot
folder. - Uncomment the
urlManager
section inprotected/config/main.php
and add'showScriptName' => false
. - Generate the
page
module using Gii. You need to uncomment thegii
section undermodules
and set a password. Then, openhttp://localhost/gii
in your browser. - Don't forget to add your new module to the
modules
list in your application configuration.
The Yii code generator is shown in the following screenshot:
How to do it...
- Create
ModuleUrlManager.php
in yourprotected/components
directory with the following code inside it:<?php class ModuleUrlManager { static function collectRules() { if(!empty(Yii::app()->modules)) { foreach(Yii::app()->modules as $moduleName => $config) { $module = Yii::app()->getModule($moduleName); if(!empty($module->urlRules)) { Yii::app()->getUrlManager()->addRules($module->urlRules); } } } return true; } }
- In your application configuration file, add the following line:
'onBeginRequest' => array('ModuleUrlManager', 'collectRules'),
- Now, in your page module, you can add custom rules. To do so, open
PageModule.php
and add:public $urlRules = array( 'test' => 'page/default/index', );
- To test if it works, open your browser and go to
http://example.com/test
. This page should look like the one shown in the following screenshot: - You can still override URL rules from your main application configuration file. So, what you specify in module's
urlRules
is used only when the main application rules don't match.
How it works...
Let's review the ModuleUrlManager::collectRules
method.
If there are modules defined in our application, then we are checking if the urlRules
public property exists. If it does, then there are some rules defined in the module and they are added using CUrlManager::addRules
.
The CUrlManager::addRules
description says, "In order to make the new rules effective, this method must be called before CWebApplication::processRequest
."
Now, let's check how our application works. In our index.php
file, we have the following line:
Yii::createWebApplication($config)->run();
After being initialized with the configuration, we call CWebApplication::run()
:
public function run() { if($this->hasEventHandler('onBeginRequest')) $this->onBeginRequest(new CEvent($this)); $this->processRequest(); if($this->hasEventHandler('onEndRequest')) $this->onEndRequest(new CEvent($this)); }
As we can see, there is an onBeginRequest
event raised just before calling processRequest
. That is why we are attaching our class method to it.
There's more...
As instantiating all application modules on every request is not good for performance, it is good to cache module rules. Caching strategy can vary depending on your application. Let's implement a simple one:
<?php class ModuleUrlManager { static function collectRules() { if(!empty(Yii::app()->modules)) { $cache = Yii::app()->getCache(); foreach(Yii::app()->modules as $moduleName => $config) { $urlRules = false; if($cache) $urlRules = $cache->get('module.urls.'.$moduleName); if($urlRules===false){ $urlRules = array(); $module = Yii::app()->getModule($moduleName); if(isset($module->urlRules)) $urlRules = $module->urlRules; if($cache) $cache->set('module.urls.'.$moduleName, $urlRules); } if(!empty($urlRules)) Yii::app()->getUrlManager()->addRules($urlRules); } } return true; } }
This implementation caches URL rules per module. So, adding new modules is not a problem but changing existing ones requires you to flush the cache manually using Yii::app()->cache->flush()
.
In order to see it in action, you need to define the cache under the components
section of your protected/config/main.php
file, as follows:
'cache' => array( 'class' => 'CFileCache', ),
See also
- The Configuring URL rules recipe