CodeIgniter Forums
Routing DB pages - Printable Version

+- CodeIgniter Forums (https://forum.codeigniter.com)
+-- Forum: Using CodeIgniter (https://forum.codeigniter.com/forum-5.html)
+--- Forum: Best Practices (https://forum.codeigniter.com/forum-12.html)
+--- Thread: Routing DB pages (/thread-63113.html)

Pages: 1 2


Routing DB pages - Martin7483 - 09-28-2015

Hi,

I am upgrading my CMS from CI 2 to CI 3. In the current CMS the system uses a catch all controller to route DB pages. The catch all controller also handles the 404 page. So I don't need to set a 404_override in the routes.php config file. 99% of the front-end pages will be DB driven pages. Sometimes it could be a dedicated controller. The homepage (default controller) must also be the catch all controller as the homepage is also DB driven. I think I have three options.

  1. Use the 404_override as a catch all and set the same value for default_controller
  2. Extend the core Router to use the default controller as a catch all controller
  3. Extend the core Router to use a catch all controller. This way I could still have a seperate controller for home if ever required. See code below

In CI3 the core Router is quite different from the CI2 version. I have been able to extend the core Router with a catch all controller and would like to know if this has been done correctly. And from the three options what is the best solution for routing DB pages?

Here is MY_Router.php

PHP Code:
<?php  if (! defined('BASEPATH')) exit('No direct script access allowed');

/**
 * My Router
 *
 * This class extends the Crossfire core Router. It adds a catch all
 * controller for routing DB pages
 *
 * @author  Martin Langenberg
 */

 
class MY_Router extends CI_Router {
 
 
/**
  * Catchall controller
  *
  * @var string
  */
 
public $catchall_controller;
 
 
    public function __construct() {
 
        log_message('debug'"Class ".get_class($this)." Initialized.");
 
 
        parent::__construct();
 }
 
 
/**
  * Set route mapping
  *
  * Determines what should be served based on the URI request,
  * as well as any "routes" that have been set in the routing config file.
  *
  * @return void
  */
 
protected function _set_routing()
 {
 
    // Are query strings enabled in the config file? Normally CI doesn't utilize query strings
 
    // since URI segments are more search-engine friendly, but they can optionally be used.
 
    // If this feature is enabled, we will gather the directory/class/method a little differently
 
    if ($this->enable_query_strings)
 
    {
 
        // If the directory is set at this time, it means an override exists, so skip the checks
 
        if ( ! isset($this->directory))
 
        {
 
            $_d $this->config->item('directory_trigger');
 
            $_d = isset($_GET[$_d]) ? trim($_GET[$_d], " \t\n\r\0\x0B/") : '';
 
 
            if ($_d !== '')
 
            {
 
                $this->uri->filter_uri($_d);
 
                $this->set_directory($_d);
 
            }
 
        }
 
 
        $_c trim($this->config->item('controller_trigger'));
 
        if ( ! empty($_GET[$_c]))
 
        {
 
            $this->uri->filter_uri($_GET[$_c]);
 
            $this->set_class($_GET[$_c]);
 
 
            $_f trim($this->config->item('function_trigger'));
 
            if ( ! empty($_GET[$_f]))
 
            {
 
                $this->uri->filter_uri($_GET[$_f]);
 
                $this->set_method($_GET[$_f]);
 
            }
 
 
            $this->uri->rsegments = array(
 
                1 => $this->class,
 
                2 => $this->method
                 
);
 
        }
 
        else
 
        {
 
            $this->_set_default_controller();
 
        }
 
 
        // Routing rules don't apply to query strings and we don't need to detect
 
        // directories, so we're done here
 
        return;
 
    }
 
 
    // Load the routes.php file.
 
    if (file_exists(APPPATH.'config/routes.php'))
 
    {
 
        include(APPPATH.'config/routes.php');
 
    }
 
 
    if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/routes.php'))
 
    {
 
        include(APPPATH.'config/'.ENVIRONMENT.'/routes.php');
 
    }
 
 
    // Validate & get reserved routes
 
    if (isset($route) && is_array($route))
 
    {
 
        isset($route['default_controller']) && $this->default_controller $route['default_controller'];
 
        // Set the catch all controller
 
        isset($route['catchall_controller']) && $this->catchall_controller $route['catchall_controller'];
 
        isset($route['translate_uri_dashes']) && $this->translate_uri_dashes $route['translate_uri_dashes'];
 
        unset($route['default_controller'], $route['translate_uri_dashes'], $route['catchall_controller']);
 
        $this->routes $route;
 
    }
 
 
    // Is there anything to parse?
 
    if ($this->uri->uri_string !== '')
 
    {
 
        $this->_parse_routes();
 
    }
 
    else
 
    {
 
        $this->_set_default_controller();
 
    }
 }
 
 
/**
  * Set request route
  *
  * Takes an array of URI segments as input and sets the class/method
  * to be called.
  *
  * @used-by CI_Router::_parse_routes()
  * @param array $segments URI segments
  * @return void
  */
 
protected function _set_request($segments = array())
 {
 
    $segments $this->_validate_request($segments);
 
    // If we don't have any segments left - try the default controller;
 
    // WARNING: Directories get shifted out of the segments array!
 
    if (empty($segments))
 
    {
 
        $this->_set_default_controller();
 
        return;
 
    }

 
    if ($this->translate_uri_dashes === TRUE)
 
    {
 
        $segments[0] = str_replace('-''_'$segments[0]);
 
        if (isset($segments[1]))
 
        {
 
            $segments[1] = str_replace('-''_'$segments[1]);
 
        }
 
    }

 
    $this->set_class($segments[0]);
 
    if (isset($segments[1]))
 
    {
 
        $this->set_method($segments[1]);
 
    }
 
    else
 
    {
 
        $segments[1] = 'index';
 
    }
 
 
    // Check if a valid controller is set, else call the catchall
 
    if( ! $this->_is_controller($segments[0]) ) {
 
        $this->_set_catchall_controller();
 
        return;
 
    }

 
    array_unshift($segmentsNULL);
 
    unset($segments[0]);
 
    $this->uri->rsegments $segments;
 }
 
 
/**
  * Set default controller
  *
  * @return void
  */
 
protected function _set_catchall_controller()
 {
 
    // Catch all controller empty? Set the default controller
 
    if (empty($this->catchall_controller))
 
    {
 
        $this->_set_default_controller();
 
    }
 
 
    // Is the method being specified?
 
    if (sscanf($this->catchall_controller'%[^/]/%s'$class$method) !== 2)
 
    {
 
        $method 'index';
 
    }
 
 
    if ( ! file_exists(APPPATH.'controllers/'.$this->directory.ucfirst($class).'.php'))
 
    {
 
        // No file found for the catch all controller
 
        $this->_set_default_controller();
 
    }
 
 
    $this->set_class($class);
 
    $this->set_method($method);
 
 
    // Assign routed segments, index starting from 1
 
    $this->uri->rsegments = array(
 
        1 => $class,
 
        2 => $method,
 
        3 => implode('/'$this->uri->segments)
 
        );
 
 
    log_message('debug'"No matching controller. Catch all controller {$this->catchall_controller} set.");
 }
 
 protected function 
_is_controller($controller) {
 
    $dir realpath(APPPATH.'controllers/'.$this->directory);
 
 
    if( ! file_exists(realpath($dir.'/'.$controller.'.php')) ) {
 
        // Clear, or set the directory for the use of the catchall controller
 
        $this->set_directory(NULL);
 
        return FALSE;
 
    }
 
 
    return TRUE;
 }
 




RE: Routing DB pages - slax0r - 09-28-2015

We extended the core router and overridden the "_set_routing" method of CI_Router to retrieve additional routes from the database, and simply merged them into the "routes" property of CI_Router. We used PDO to connect to the database there, because CI's db lib is not yet available.

You can also do this directly in the routes config file, but in my eyes logic does not belong in config files.

But I wonder, do you even need this for a CMS? Do you have predefined controllers for each page the user creates? Why not use a "PageBuilder" controller which gets the required content from the DB and builds the page? Or does your CMS require the user to create a controller for his custom page?


RE: Routing DB pages - Martin7483 - 09-28-2015

The catch all controller is the "page builder" controller in my case. But sometimes pages are needed that are to complex to build with a CMS and may need some special views that don't fit the rest of the website and it's main layout view.

And in some cases the default controller is not the DB home page, but a splash page or something like that.

What would you suggest?


RE: Routing DB pages - slax0r - 09-28-2015

Well, I would do as suggested already before, inject those special routes in the extended router, and for everything else, just use "(:any)" route to route requests to your catch-all.

Edit: Hope only I understood your issue correctly.


RE: Routing DB pages - Martin7483 - 09-28-2015

Well as i said, in some cases we do need a real controller for some pages. So using $route['(:any)'] is not an option.

When you inject the DB routes, do they all point to the same controller?


RE: Routing DB pages - slax0r - 09-28-2015

Using (:any) does not remove option of using other controllers. It just has to be the last route in the list. Our routes are completely custom in the DB, you can route them to any controller your wish.


RE: Routing DB pages - Martin7483 - 09-28-2015

But then I also have to define all other controllers in the routes.php. I don't want to edit my routes.php for every little change that may occur.
Maybe my situation is not clear. If wished, I can try to explain it again.

For now; Did I extend the Router in the correct manner for using a catch all controller?


RE: Routing DB pages - slax0r - 09-28-2015

Hm, I am not sure then if CIs uri matching then still works once (:any) is defined, would have to try. Your code should be alright, but you know, try it and see.


RE: Routing DB pages - Martin7483 - 09-30-2015

Hi!

I have been testing my catch all setup and all is good. Haven't run into any problems.
Need this in your project? Just grab the code from the original post Smile

- Martin


RE: Routing DB pages - weblogics - 10-05-2015

What I've been using in CI2 which would work in CI3 if a routing bug wasn't fixed is creating a cached version of the routes stored in the DB.

I'm using Jamie Rumbelow's MY_Model and using the triggers to run a save method against my database everytime a page is added/updated or removed to update the routes accordingly.

PHP Code:
/**
     * Get Slug By ID
     *
     * Returns a recursive slug based on a page ID
     *
     * @param $id
     * @return bool|string
     */
 
   public function get_slug_by_id($id)
 
   {
 
       $route $this->get($id);

 
       if( ! $route ) return FALSE;

 
       if$route->parent_id != )
 
           $prefix $this->get_slug_by_id$route->parent_id ). "/" $route->slug;
 
       else
            $prefix 
$route->slug;

 
       return $prefix;
 
   }

/**
     * Cache
     *
     * Rebuilds the routes cache file located in "application/cache/routes.php"
     *
     * @return bool
     */
public function cache()
 
   {
 
       $pages      $this->with('template')->get_many_by(array('status' => 'published'));

 
       if( ! empty( $pages) )
 
       {
 
           foreach$pages as $page )
 
           {
 
               $controller       $page->template->name;
 
               $method           ( ! $page->template->method ) ? 'index' $page->template->method;
 
               $id               $page->id;

 
               $controller       $controller DIRECTORY_SEPARATOR $method DIRECTORY_SEPARATOR $id;
 
               $route            $this->get_slug_by_id($id);

 
               //  Make sure index page is always set as the default controller
 
               if$route === 'index'    $route    'default_controller';
 
               if$route    $data[] = '$route["' $route '"] = "' $controller '";';
 
           }

 
           $output implode("\n"$data);

 
           $this->load->helper('file');

 
           $file    write_file(APPPATH "cache/routes.php""<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');\n // THIS FILE IS AUTOMATICALLY GENERATED BY THE CMS, DO NOT TRY TO EDIT THIS FILE AS IT WILL BE OVERWRITTEN!!\n\r" $output);

 
           if$file )
 
               log_message('debug''Page routing cache written successfully');
 
           else
                log_message
('error'"Page routing cache could't be written, check " APPPATH "cache/routes.php write permissions");

 
           return $file;
 
       }
 
   

The cache method is then triggered by the following triggers:

PHP Code:
   public $after_create      array('cache');
 
   public $after_update      array('cache');
 
   public $after_delete      array('cache'); 

This then spites out a file called routes.php in your cache directory (don't forget write permissions!), then in your config/routes.php add the following code:

PHP Code:
//  Cached Routes Generated from database
if(file_exists(APPPATH "cache/routes.php"))   include_once APPPATH "cache/routes.php"

The Generated routes file looks like this:

PHP Code:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 
// THIS FILE IS AUTOMATICALLY GENERATED BY THE CMS, DO NOT TRY TO EDIT THIS FILE AS IT WILL BE OVERWRITTEN!!

$route["default_controller"] = "cms/index/1";
$route["about"] = "cms/index/2";
$route["products"] = "cms/index/3";
$route["products/ford"] = "cms/index/4";
$route["products/ford/fiesta-mk6-acr-rs-wide-arch-body-kit"] = "cms/index/5";
$route["products/ford/fiesta-mk6-acr-rs-spoiler"] = "cms/index/6"

This method means that you aren't hitting the database on every single page request, but only performing the grunt work when an admin updates the pages table (or where ever your content is stored).

The issue with this in CI3 is that the default_controller route has now been modified (or fixed in their view) to only accept a controller/method and due to this the default_controller route in the cache/routes.php file no longer runs, but the rest of the functionality works. If someone could resolve this side of it, this would be an ideal solution since its scalable and fits the existing architecture of CI.

Just to clarify this method works, but brakes only on the default route.

Assumptions
  • Your content / pages table has a field with the slug (uri) in.
  • Your content / pages table has a parent_id field in (this is so the get_slug_by_id($id) method can build the full slug (uri) route.
  • Your content / pages table has a field with the template_id in (see table below).

Other things you may notice

The cache method has a template method in, each page has a template and the template table consists of:
  • Id
  • Name
  • Controller
  • Method (can be null, cache will default this to index)

This way you can map a page to a particular template, I have extended this to go further for modules. If anyone's interested then I'm happy to share, but I just wanted to keep this example as lightweight as possible to try and give / get some input.