How to make “truly” SEO friendly URLs in CodeIgniter (without “duplicate content” on underscore URLs)

I promised myself I won’t do anymore tutorials regarding CodeIgniter until a new version is about to appear, but I can’t help myself. And this subject seems more than appropriate for a new tutorial.

Let us start with the beginning. In routes.php (application/config/routes.php), we have a configuration parameter called “translate_uri_dashes“. If we set this parameter to TRUE, the CodeIgniter framework will translate the dashes (“-“) in our urls into underscores (“_”) when calling a controller or a method. This, of course is what we want in order to have “SEO friendly” URLs (although I think it’s strange that up until this moment, search engines can’t interpret an url).

That means that when the CodeIgniter sees an address like http://yourdomain.com/test-controller/a-url-method/ will know to call the Test_controller method and the a_url_method() method.

But, hey!… This won’t remove the possibility of accessing the same controller and method by using underscores in the url. So, that means that you get same content at http://yourdomain.com/test-controller/a-url-method/ and at http://yourdomain.com/test_controller/a_url_method/ and that is a BIIIG “NO-NO” from our beloved search engines. Why? The famous “DUPLICATE CONTENT“.

Then how do save ourselves from this SEO Armageddon? Well… Let us take a look at the Router.php inside the system/core. Looking at that we will see that the method that deals with “translating” the URL into a controller and its method is _set_request():

// --------------------------------------------------------------------

/**
 * 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';
	}

	array_unshift($segments, NULL);
	unset($segments[0]);
	$this->uri->rsegments = $segments;
}

So, this is the culprit… But what now? Should we simply modify it? No… NEVER MODIFY THE SYSTEM FILES IN CODEIGNITER!

Let’s go into our application/core and create a file named MY_Router.php in it. As you may, or may not know, CodeIgniter automatically extends anything that starts with MY_… This class will simply extend the core Router.php and we will only modify the method in question:

 

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class MY_Router extends CI_Router {
    function __construct()
    {
        parent::__construct();
    }

    // --------------------------------------------------------------------

    /**
     * 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)
        {
            if(strpos($segments[0],'_')!==FALSE)
            {
                $wrong_controller = TRUE;
            }
            $segments[0] = str_replace('-', '_', $segments[0]);

            if(isset($segments[1]) && strpos($segments[1],'_')!==FALSE)
            {
                $wrong_method = TRUE;
            }
            if (isset($segments[1]))
            {
                $segments[1] = str_replace('-', '_', $segments[1]);
            }
        }

        if(isset($wrong_controller) || isset($wrong_method))
        {
            $url_segments = $segments;
            $url = $this->config->config['base_url'];
            $controller = array_shift($url_segments);
            $url .= str_replace('_','-',$controller);
            if(isset($url_segments) && sizeof($url_segments)>0) {
                $method = array_shift($url_segments);
                $url .= '/' . str_replace('_', '-', $method);
            }
            if(!empty($url_segments))
            {
                $url .= '/'.implode('/',$url_segments);
            }
            header('Location: '.$url, TRUE, 301);
        }

        $this->set_class($segments[0]);
        if (isset($segments[1]))
        {
            $this->set_method($segments[1]);
        }
        else
        {
            $segments[1] = 'index';
        }

        array_unshift($segments, NULL);
        unset($segments[0]);
        $this->uri->rsegments = $segments;
    }
}

As you can see in lines 35-38 and 41-44 we look to see if what we get from URL contains any underscores (“_”). If it does, then that means that the URL is no good and we need to do a redirect to the url that only contains dashes “-“. Now in lines 51-57 we simply do a redirect with a 301 server code (moved permanently), if either the controller’ or the method’s URL contain underscore “_”.

And that’s it. Of course, this is not an universal solution and is prone to errors if you have a more “exotic” setup of urls. Do you have another solution? I would be more than happy to put it here. Can someone, please, buy me a coffee (hint-hint)?

7 comments

    1. I am not a “Codeignitor” expert. Anyway… why use my time creating a CodeIgniter application when I can use it to write tutorials (not only CodeIgniter tutorials…)?

Leave a Reply

Your email address will not be published. Required fields are marked *

No spam? * Time limit is exhausted. Please reload CAPTCHA.