Saturday, January 5, 2013

CodeIgniter: SEO-friendly localized URLs


After having seen how to internationalize CodeIgniter by using language files, we will see in this post how to create localized SEO-friendly URLs, i.e. understandable URLs for both search engines and users.

Cleaning the default URLs


By default, the URLs include a call to the file index.php: example.com/index.php/controller/method/parameter

To get a more readable URL than example.com/controller/method/parameter, we are going to use the following Apache URL rewriting rules (in the .htaccess file).
    .htaccess
    RewriteEngine on
    RewriteCond $1 !^(index\.php|images|robots\.txt)
    RewriteRule ^(.*)$ index.php/$1 [L]
The configuration file (application/config/config.php) shall also be modified to indicate the default index page (empty).
    $config['index_page'] = '';

Internationalization: localized controllers and views


By default, the URLs generated by CodeIgniter (cleaned up by rewriting) are looking like example.com/controller/method/parameter

An internationalized site has to pass in its parameters the language of consultation (we will use here a two-letter language code).

The routing is defined in the file application/config/routes.php
    // English
    $route['en/my-test/my-view'] = 'test/view/en';

    // French
    $route['fr/essai/vue'] = 'test/view/fr';
The following URLs are well supported:
  • example.com/en/my-test/my-view/
  • example.com/fr/essai/vue/
As well as those:
  • example.com/en/my-test/my-view
  • example.com/fr/essai/vue
So only the listed routes (the others sending the 404 error code), but with two possible URLs for each page, one with an ending slash, the other without. We encounter here the problem of duplicate content where several addresses access the same content.

Remark: if you change a URL during the life of the site, the 301 redirections can not be set up in the routing configuration file but at the server level (for instance in the .htaccess file).

Retrieving parameters


To accept any additional parameter without knowing beforehand its value, you need to duplicate each row by adding (:any) that will match by regular expression the rest of the URL with the second variable of the controller. As can be seen in the class CI_Router (defined in the file system/core/Router.php), (:any) is a greedy regular expression (replaced by .+ that matches any character, including the slash bar). It is therefore preferable to replace it with ([^/]*) (any character, except the slash bar).
    $route['en/my-test/my-view'] = 'test/view/en/';
    $route['en/my-test/my-view/([^/]*)'] = 'test/view/en/$1';
Besides, we would like to pass any number of parameters to the controllers methods, without having to define for each route the number of fixed parameters accepted. The previous solution only retrieves the first parameter, as a string (where we would prefer an array of all parameters).

To do this, we will recover the full URL in the controller, then explode it into an array via the method segment_array.
    class Test extends CI_Controller {
        public function view() {
            $params = $this->uri->segment_array();
The URL example.com/en/my-test/my-view/param1/param2 thus creates the array ( [1] => en [2] => my-test [3] => my-view [4] => param1 [5] => param2 )

Its first element is the language code, then come the localized controller and view (very useful if you want to do redirects), and finally the parameters.

Furthermore, as the recovered parameters are independent of the routing, you can revert to a greedy regular expression.
    // English
    $route['en/my-test/my-view'] = 'test/view/en/';
    $route['en/my-test/my-view/(.*)'] = 'test/view/en/$1';

    // French
    $route['fr/essai/vue'] = 'test/view/fr';
    $route['fr/essai/vue/(.*)'] = 'test/view/fr/$1';

Let's sum it up


We have separated the internal names of the controllers and their methods from their external names appearing in the URLs, which is useful even if the site supports only one language. We are talking here of internationalization, since everything is ready for the site to be localized in different languages/cultures. The language code comes before the localized name of the controller in the URL, giving the illusion of a specific subdirectory for each supported language. Finally, the number of parameters supported by the methods of the controllers is left free in the routing file, as handled directly in the methods themselves.

Some reading


CodeIgniter for Rapid PHP Application Development


If you want to go a bit further, I recommend you this book: CodeIgniter for Rapid PHP Application Development, by David Upton (2007, 220 pages).


CodeIgniter : URLs SEO-friendly et localisées (in French)
CodeIgniter: URLs SEO-friendly y localizadas (in Spanish)
CodeIgniter: URLs SEO-friendly e localizados (in Portuguese)

No comments:

Post a Comment