Welcome Guest, Not a member yet? Register   Sign In
Routing to specific controller in case controller/method is not found.
#1

Hello there,

I'm trying to archieve to create custom url for specific content.

For example, i have blog and page tables in my database.

URL structure for the blog posts is "example.com/blog/view/post-slug-here" and it's working as intended.

Exclusively for page content, i want to create a URL structure like "example.com/page-slug-here".

Since this route taking priority over all rules:
PHP Code:
$route['(:any)'] = 'page/view/$1'
I couldn't managed to archieve it so far.

In fact, i can archieve this this 404_override. But i have doubt that i'll have trouble when things getting complex.

For example, when i need to create sub site as follows:
Code:
"example.com/sub-site"
"example.com/sub-site/blog/view/post-slug-here"
"example.com/sub-site/controller/method"

Probably i need to route sub-site's "example.com/sub-site/page-slug-here" to a controller other than 404_override.

What is the best way to archieve this?
Reply
#2

(This post was last modified: 09-25-2015, 10:42 AM by mwhitney.)

I'm not sure I completely understand your issue, but any route which is specifically defined in the routes config will be matched before any RegEx rules are evaluated (':any' or ':num' is converted to a RegEx). So, if you have:
Code:
$route['sub-site'] = 'sub_site';

Then example.com/sub-site will work. Of course, that still creates an issue for the routes beneath it (example.com/sub-site/blog, etc.). For that, you would need to define another RegEx route for your sub-site, which means the router will loop through the list of RegEx routes until it finds a match.

This means that you need to define your RegEx routes in order from the most specific to the least specific in order to correctly match a route. So, your sub-site RegEx must be defined before your '(:any)' route. Once you do this, the only issue you should run into is that you won't be able to use 'sub-site' as a slug unless you use the URL example.com/page/view/sub-site.
Reply
#3

(This post was last modified: 09-25-2015, 10:53 AM by Muzikant.)

Try something like this:

PHP Code:
$route['controller-1/method-1/(:any)'] = 'controller-1/method-1/$1';
$route['controller-2/(:any)'] = 'controller-2/method-2/$1';
$route['(:any)'] = 'controller-3/method-3/$1'

You can also use:

PHP Code:
$route['something-1/controller-1/method-1/(:any)'] = 'controller-1/method-1/$1';
$route['something-2/controller-2/(:any)'] = 'controller-2/method-2/$1';
$route['something-3/(:any)'] = 'controller-3/method-3/$1'

If you did not, read the URI Routing in the documentation, where you can find more tips.
Reply
#4

I think you are looking for a catch all controller. When a /db-page-slug does not match any “physical” controller the system falls back on this catch all controller. With this controller you can then perfrom a DB search for the given slug. If still nothing is found you can revert to your 404 controller
Reply
#5

Thank you for your interest @mwhitney.

Let me be more specific on the issue.

Let's say,

i'm showing list of blog posts on example.com/blog which points to controller/Blog.php->index()

and, i'm showing single blog post on example.com/blog/view/post-slug-here which points to controller/Blog.php->view($slug)

and maybe my contact page on example.com/contact which points to controller/Contact.php->index()

these are expected behaviour of CI and should work perfecty.

in addition to this scenario, (blog and contact page) there is another content (eg: page table on database) which i want to serve from url pattern like this: "example.com/page-title"

there maybe tens of dynamic content. so i cannot create controller for each of these content.

Exaple Page Table
Code:
ID   TITLE           CONTENT
1    brown-fox       The quick brown fox jumps over the lazy dog
2    fox-origin      The quick brown fox jumps over the lazy dog
..
99   where-is-lazy   The quick brown fox jumps over the lazy dog

To archieve this behaviour i tried following route:
PHP Code:
$route['(:any)'] = 'page/view/$1'

Since this approach archieves my needs, it's also breaking other blogs/pages etc.
Because any string just after example.com/anystring routing to [b]controller/Page.php->view($title)[/b]

So, is there a way to tell to CI that,

1) If requested controller/method exists, execute it. eg: example.com/blog => controller/Blog.php->index() )
2) And in case if it's not exists , execute a specified controller/method.
Reply
#6

(09-25-2015, 11:21 AM)Martin7483 Wrote: I think you are looking for a catch all controller. When a /db-page-slug does not match any “physical” controller the system falls back on this catch all controller. With this controller you can then perfrom a DB search for the given slug. If still nothing is found you can revert to your 404 controller

Yes, this is one of the things that i want to archieve.

Could you please help me on "catch all controller" because i couldn't find any info about it.
Reply
#7

I don't have a working example for CI 3. Only a working version for CI 2.x

The Router has changed quite a bit between versions.

What i have seen is people use the $router['404_override'] as a catchall_controller.
Reply
#8

Hello again;

Thanks for everyone who tried to help me in this thread. Smile I appreciate your interest.

I've solved my issue with following approach. (I know it's a little bit dirty but i couldn't find an elegant solution to this.)

PHP Code:
$route['(:any)'] = function($slug)
{
    
// find out which class(controller)/method to execute.
    
$validated $this->_validate_requestarray_values($this->uri->segments) );
    
    
// cleanup. (which set by _validate_request())
    
$directory $this->directory;
    
$this->directory NULL;

    
// extract class(controller) & method name.
    
if(array_key_exists(0,$validated))
    {
        
// class(controller) name found.
        
$class_name ucfirst($validated[0]);
    }
    else
    {
        
// class(controller) name not found. route to desired controller.
        
return 'page/view/'.$slug;
    }
    
$method_name array_key_exists(1,$validated) ? $validated[1] : 'index';
    
    
// class(controller) file to load.
    
$class_path APPPATH.'controllers/'$directory $class_name '.php';
    
    
// cleanup. (which set by _validate_request())
    
$this->directory NULL;
    
    
// class(controller) exists?
    
if(file_exists($class_path))
    {
        
// load CI_Controller core class and our class(controller).
        
include_once(FCPATH.'system/core/Controller.php');
        include_once(
$class_path);
        
        
// does method exists?
        
if(method_exists($class_name,$method_name))
        {
            
// if class(controller) and method exists, we should execute it.
            
return implode('/',$this->uri->segments);
        }
        else
        {
            
// well, if not, it's time to route to desired controller.
            
return 'page/view/'.$slug;
        }
    }
    else
    {
        
// if class(controller) doesnt exists, route to desired controller.
        
return 'page/view/'.$slug;
    }
}; 

This code works as follows:

Scenario: Physically Exists
example.com/controller/method => controllers/Controller.php->method();
example.com/controller => controllers/Controller.php->index();

Scenario: Not Exists
example.com/non-controller => controllers/page.php->view($slug);


Sub-Site Example
PHP Code:
$route['subsite/(:any)'] = /* same code as above */ 

Scenario: Physically Exists
example.com/subsite/controller/method => controllers/subsite/Controller.php->method();
example.com/subsite/controller => controllers/subsite/Controller.php->index();

Scenario: Not Exists
example.com/subsite/non-controller => controllers/page.php->view($slug);
Reply
#9

Hi,

I see you have found a solution. Should you still be interested in using a catch all controller I have been able to extend the core Router class to make use of this option.

Add the following line to your routes.php config file in ./application/config
PHP Code:
$route['catchall_controller'] = 'your_catch_all'

Copy MY_Router.php and add it to your ./application/core directory
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;
 }
 

Reply
#10

The (:any) has to be the last route in your routes.php file for it to work correct.
What did you Try? What did you Get? What did you Expect?

Joined CodeIgniter Community 2009.  ( Skype: insitfx )
Reply




Theme © iAndrew 2016 - Forum software by © MyBB