[CodeIgniter] Dynamic Routes from Database

Discussion for various published PHP frameworks, including Zend Framework, CodeIgniter, Kohana, CakePHP, Yii, Symfony, and others.

Moderator: General Moderators

Post Reply
User avatar
twinedev
Forum Regular
Posts: 984
Joined: Tue Sep 28, 2010 11:41 am
Location: Columbus, Ohio

[CodeIgniter] Dynamic Routes from Database

Post by twinedev »

This is from a CMS I am working on. The idea is to allow the following types of URLS:

/ (index)
/page-name
/products/product-name
/news/article-name
/page-name/var_33/setting_off (anything with a _ gets turned into $_GET['var']='33' ; $_GET['setting']='off';

Also in the table for paths, I have:
path_id (unsigned auto increment int)
path (varchar, what is actually in url)
section (enum, pages/products/news/paths - paths basically lets you do two pagenames to same content)
section_id (unsigned it, this is the id for the specific section (table))
status (enum, active/disabled/linkable)

While the routes will easily allow handling of /products/product-name and /news/article-name I have all sections available as dynamic, as I have some uses for this where it is not desirable to prefix each section with its type.

So anyhow, here is how I accomplished it, and please note, this is my first major working with CI, and while I'm building it, yes it is just set for mysql until I can see if I can clean it up some and use built in DB calls...

First: Set up in the main index.php the variable that will hold what gets set via hooks, and what get used via routes:

Code: Select all

// THIS WILL HOLD THE DYNAMIC ROUTE POSSIBLY FOUND IN router_hook.php
$cms_dyn_route = NULL;
Second, in applications/hooks.php we set a pre_system hook:

Code: Select all

require_once (APPPATH."config/database.php");

$hook['pre_system'] = array('class'    => 'Router_Hook',
                            'function' => 'dynamic_route',
                            'filename' => 'router_hook.php',
                            'filepath' => 'hooks',
                            'params'   => array('server'=>$db['default']['hostname'],
                                                'user'=>$db['default']['username'],
                                                'password'=>$db['default']['password'],
                                                'database'=>$db['default']['database'],
                                                'driver'=>$db['default']['dbdriver']
                                               )
                           );
Now we create application/hooks/router_hook.php:
Note, I forget if DB_PREFIX which I have defined in the database.php file is part of CI or something I added...

Code: Select all

class Router_Hook
{
  /**
   * Loads routes from database.
   *
   * @access public
   * @params array : hostname, username, password, database, db_prefix
   * @return void
   */
  function dynamic_route($params) {
        global $cms_dyn_route;  // This is initialized in the main index.php. It will be used in

        if ($_SERVER['REQUEST_URI']=='/') {
            $strRequestURI = '/index/';
        }
        else {
            $strRequestURI = $_SERVER['REQUEST_URI'].'/';
        }
        if (preg_match('%^/(([a-z][-a-z0-9]*)/)?(([a-z][-a-z0-9]*)/)?([-a-z0-9]+_.*)?$%i', $strRequestURI, $aryPathParts)) {
            $aryLookup = array();
            if (!isset($aryPathParts[4]) || $aryPathParts[4]=='') { // Only singe page name
                $aryLookup['section'] = 'pages';
                $aryLookup['path'] = ($aryPathParts[2]!='') ? $aryPathParts[2] : 'index';
            }
            else {
                $aryLookup['section'] = $aryPathParts[2];
                $aryLookup['path'] = $aryPathParts[4];
            }
            if (isset($aryPathParts[5]) && trim($aryPathParts[5],'/')!='') {
                $aryGets = explode('/',trim($aryPathParts[5],'/'));
                foreach($aryGets as $strGet) {
                    if (preg_match('/^([a-z][-0-9a-z]*)_(.*)$/i', $strGet, $regs)) {
                        $_GET[$regs[1]] = (isset($regs[2])) ? $regs[2] : '';
                    }
                }
            }

            switch($params['driver']) {
                case 'mysql':
                            mysql_connect($params['server'],$params['user'],$params['password']);
                            mysql_select_db($params['database']);
                            $intLevels = 0;
                            $SQL = sprintf("SELECT * FROM `".DB_PREFIX."paths` WHERE (`section`='%s' OR `section`='paths') AND `path`='%s' AND `status`<>'disabled' LIMIT 1",
                                           mysql_real_escape_string($aryLookup['section']),
                                           mysql_real_escape_string($aryLookup['path'])
                                           );
                            $rsLookup = mysql_query($SQL);
                            if ($rsLookup && mysql_num_rows($rsLookup)>0) {
                                $aryLookup = mysql_fetch_assoc($rsLookup);
                                mysql_free_result($rsLookup);
                            }
                            else {
                                $aryLookup = FALSE;
                            }
                            while ($aryLookup && ++$intLevels<6 && $aryLookup['section']=='paths') {
                                $rsLookup = mysql_query("SELECT * FROM `".DB_PREFIX."paths` WHERE `path_id`=".(int)$aryLookup['section_id']." AND `status`<>'disabled' LIMIT 1");
                                if ($rsLookup && mysql_num_rows($rsLookup)>0) {
                                    $aryLookup = mysql_fetch_assoc($rsLookup);
                                    mysql_free_result($rsLookup);
                                }
                                else {
                                    $aryLookup = FALSE;
                                }
                            }
                            unset($rsLookup);
                        break;
                default:
            }

            if ($aryLookup) {
                if (!isset($_GET['action']) || !preg_match('/^[a-z]+$/',$_GET['action'])) {
                    $_GET['action'] = 'view'; // set default action
                }
                $cms_dyn_route = array('.*'=>$aryLookup['section'].'/'.$_GET['action'].'/'.$aryLookup['path'].'/'.$aryLookup['section_id'].'/'.$intLevels);
            }
            unset($aryLookup);
        }
    }
}
Now we will either have a good matching route, or not, so set this route up with normal route programming in application/config/routes.php (put it after all other routing code)
there really is no need for the foreach to loop, there will only be one, I just used it to easily get the key/value pair

Code: Select all

global $cms_dyn_route;
if (!is_null($cms_dyn_route) && is_array($cms_dyn_route)) {
    foreach($cms_dyn_route as $route_key=>$dynamic_route) {
        $route[$route_key] = $dynamic_route;
    }
}
At this point, you will have a route such as the following, assuming you are viewing a page called 'test', its page_id is 72 and it wasn't redirected:

Code: Select all

$route['.*'] = 'pages/view/test/72/1';
doing a /test2/action_edit would give you the following, assuming test2 points back to test:

Code: Select all

$route['.*'] = 'pages/edit/test/72/2';
So now, finally in the controller for pages, for the view (note, this has a little SQL specific to my needs, but you get the idea):

Code: Select all

function view($strPath='index',$intPageID=1,$intLevels=1) {

    // Load Up Page Data
    $SQL = 'SELECT p.*, t.* FROM '.DB_PREFIX.'pages AS p LEFT JOIN '.DB_PREFIX.'templates AS t on p.template_id=t.template_id WHERE page_id = '.(int)$intPageID.' LIMIT 1';
    $qPages = $this->db->query($SQL);
    if ($qPages->num_rows() < 1) {
        show_404($strPath);
        return;
    }
    $aryPage = $qPages->row_array();
    $qPages->free_result();
    unset($qPages);

    $aryData = array();
    $aryData['strCanonical'] = ($intLevels>1) ? '<link rel="canonical" href="http://'.$_SERVER['HTTP_HOST'].'/'.$strPath.'" />' : NULL;
   ... Your other assignments from the database ...
Anyhow, that was the way I found to do it, if anyone has suggestions on how to clean it up a bit, I'd welcome them.

-Greg
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: [CodeIgniter] Dynamic Routes from Database

Post by VladSun »

Have you thought of using the _remap() method instead?

http://codeigniter.com/user_guide/gener ... #remapping
There are 10 types of people in this world, those who understand binary and those who don't
Post Reply