Yii Application Development Cookbook(Second Edition)
上QQ阅读APP看书,第一时间看更新

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

  1. Set up a new application using yiic webapp.
  2. Add .htaccess, shown in the official URL management guide to your webroot folder.
  3. Uncomment the urlManager section in protected/config/main.php and add 'showScriptName' => false.
  4. Generate the page module using Gii. You need to uncomment the gii section under modules and set a password. Then, open http://localhost/gii in your browser.
  5. 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:

Getting ready

How to do it...

  1. Create ModuleUrlManager.php in your protected/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;
      }
    }
  2. In your application configuration file, add the following line:
    'onBeginRequest' => array('ModuleUrlManager', 'collectRules'),
  3. 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',
    );
  4. 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:
    How to do it...

    This is the view content for action "index". The action belongs to the controller "DefaultController" in the "page" module.

  5. 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