CodeIgniter Forums
Routing to specific controller in case controller/method is not found. - Printable Version

+- CodeIgniter Forums (https://forum.codeigniter.com)
+-- Forum: Using CodeIgniter (https://forum.codeigniter.com/forum-5.html)
+--- Forum: Model-View-Controller (https://forum.codeigniter.com/forum-10.html)
+--- Thread: Routing to specific controller in case controller/method is not found. (/thread-63092.html)

Pages: 1 2


Routing to specific controller in case controller/method is not found. - W140 - 09-25-2015

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?


RE: Routing to specific controller in case controller/method is not found. - mwhitney - 09-25-2015

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.


RE: Routing to specific controller in case controller/method is not found. - Muzikant - 09-25-2015

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.


RE: Routing to specific controller in case controller/method is not found. - Martin7483 - 09-25-2015

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


RE: Routing to specific controller in case controller/method is not found. - W140 - 09-25-2015

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.


RE: Routing to specific controller in case controller/method is not found. - W140 - 09-25-2015

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


RE: Routing to specific controller in case controller/method is not found. - Martin7483 - 09-25-2015

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.


RE: Routing to specific controller in case controller/method is not found. - W140 - 09-25-2015

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);


RE: Routing to specific controller in case controller/method is not found. - Martin7483 - 09-28-2015

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;
 }
 




RE: Routing to specific controller in case controller/method is not found. - InsiteFX - 10-03-2015

The (:any) has to be the last route in your routes.php file for it to work correct.