Best place to handle url slugs

Hi guys!

I’m in the painful process to convert a Drupal site on Yii. Here’s the scenario i need help with at the moment:

I need to handle invalid URLs such as http://www.xyz.com/unknownpage.html, http://www.xyz.com/aboutus.html without throwing 404 error. I need to take the request uri from this, lookup into a DB table url_alias, and depending upon that need to forward it either one of some specified controllers such as nodes, terms, users etc…

Below is the Controller::nodeRouter() method for decision logic. Question is what’s the best place to place this conditional logic ? This nodeRouter shall only be triggered if the url/route is invalid but without throwing the 404 exception.




    public static function nodeRouter($urlpath=null) {

     $urlpath = $urlpath ? $urlpath : Yii::app()->request->pathInfo; 


      // Handle Root/Home Page

	    if (empty($urlpath)) {

			 return;

		  }


      $sqlAlias = "SELECT * FROM ".TBL_URL_ALIASES." WHERE alias='$urlpath'";

      $modelAlias = Yii::app()->db->createCommand($sqlAlias)->queryAll();

      $modelAlias = isset($modelAlias[0]) ? $modelAlias[0] : array();


      if (isset($modelAlias['id'])) {

    $route_controller_array = explode("/",$modelAlias['path']);

    $route_controller = $route_controller_array[0];    returns one of these:   nodes,  terms,  users   

    $route_id = $route_controller_array[1];            returns records pk id i.e.  44 or something

    

    switch ($route_controller) {

      case 'nodes':

        $_GET['id'] = $route_id;

        Yii::app()->runController('/cms/nodes/view');

      break;

      

      case 'terms':

        $_GET['id'] = $route_id;

        Yii::app()->runController('/cms/terms/view');

      break;


      case 'users':

      break;

      

      default:

      break;

    }

  }




      // If not routed any where means the Request is invalid

      return false;

    }

    



I tried placing Controller::nodeRouter() in the following places:

Placement 1:

============

In Controller::init() - Here i get an infinite loop as Controller::init() triggers each time after Controller::forward() is fired.

Placement 2:

============

In SiteController::actionError() - All works fine here except the issue that the desired pages are rendered but with a 404 Exception. I understand this exception comes from CWebApplication::runController(). This ugly solution will create major SEO issues for the site and i can’t go with that. Neither do i want to hack the core.

How exactly shall i get around with this issue. Appreciate if i can get some help.

Thanks,

Raheel

Friends, welcome your suggestions on this. In the meanwhile i’ve hacked the core a little bit to move forward. If this helps someone.

We have a method available in Yii named CController::missingAction() to do something with the request if the controller is valid but the requested action in URL is invalid.

I’ve wrote a similar method named missingController() which catches all the invalid requests and give you a chance to do something with them before throwing the 404 Exception.

Here’s how i did this:

Step1:

  • Open protected/components/Controller.php

  • Add the following method to this file:




    public static function missingController($route) {

      // My additional routing logic is this, you may add your own logic here

      $valid = Controller::nodeRouter();  // See above for the contents of this method.


      if (!$valid) {

        return true;    // Returning true here means you want to throw 404 error

      }

    }



Step2:

  • Open yii/framework/web/CWebApplication.php

  • Change the method runController($route) as follows:




public function runController($route)

{

if(($ca=$this->createController($route))!==null)

{

	list($controller,$actionID)=$ca;

        $oldController=$this->_controller;

	$this->_controller=$controller;

	$controller->init();

	$controller->run($actionID);

	$this->_controller=$oldController;

}

else {

  // Raised an Event to run our own logic before throwing 404. 

	$missingController = Controller::missingController($route);


  if ($missingController) {

    throw new CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".',

			array('{route}'=>$route===''?$this->defaultController:$route)));        

  }		  

}

}



modifying framework files is highly discouraged… instead you should create your own class that is extending CWebApplication and create instance of this class in index.php instead of default CWebApplication: http://www.yiiframework.com/forum/index.php/topic/6959-extend-cwebapplication/

I agree we should not modify the core and extending CWebApplication is the correct way. Thanks!