Saturday, August 20, 2011

CakePHP trailing slash

Fancy trailing slashes?

Me too! That's why I desperately wanted to have them in my CakePHP applications as well.

There are three occasions, when a trailing slash needs to be added:
  1. When generating a link in a view using HtmlHelper
  2. When doing a redirect in a controller using the redirect() method
  3. When a visitor loads a URL without a trailing slash

Here's what needs to be done to solve all those cases.

Trailing slash in HtmlHelper


Open the file /cake/libs/view/helpers/app_helper.php. By default, it contains an empty AppHelper class. Add this code into that class:

function url($url = null, $full = false) {
  $routerUrl = parent::url($url, $full);
  if(!preg_match('/\.[a-z0-9]{1,5}$/', strtolower($routerUrl)) && substr($routerUrl, -1) != '/') {
    $routerUrl .= '/';
  }
  return $routerUrl;
}


This method will be inherited and used by all helpers in the application, including HtmlHelper. It checks whether a given URL contains a trailing slash or an extension, and if not, it adds a trailing slash.

Trailing slash in redirects


In order to have trailing slashes in redirects made by controllers, you need to add a redirect() function to the application level controller. Open the file /cake/libs/controller/app_controller.php (it contains an empty AppController class by default) and add the following code into it:

function redirect($url, $status = null, $exit = true) {
  $routerUrl = Router::url($url, true);
  if(!preg_match('/\.[a-z0-9]{1,5}$/', strtolower($routerUrl)) && substr($routerUrl, -1) != '/') {
    $routerUrl .= '/';
  }
  parent::redirect($routerUrl, $status, $exit);
}


Trailing slash in user-typed URLs


The two above functions will make sure that CakePHP always adds trailing slashes in URLs. Now, how do we deal with the case when someone types a URL without a trailing slash in the address bar? Here htaccess comes into play.

Open the file /app/webroot/.htaccess and change it the following way.

1. Add the line RewriteBase / just BELOW the line RewriteEngine On, which is near the beginning of the file. The resulting piece of code should look like this:

RewriteEngine On
RewriteBase /


2. Look further until you see these lines:

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]


Now, just ABOVE them, insert the following two lines of code:

RewriteCond %{REQUEST_URI} !(\.[a-zA-Z0-9]{1,5}|/)$
RewriteRule ^(.*)$ $1/ [R=301,L]


Done!

This htaccess code will do the same thing as in the two previous steps: it will add a trailing slash to any URL that has neither a trailing slash nor a file extension.

Note: The above htaccess modifications were tailored for default CakePHP installations where the application folder is located in the website root (e.g., /public_html/app/) and the Cake's /webroot/ folder is located in the application folder (e.g. /public_html/app/webroot/). If you have a different folder structure, you might have to set the correct paths for RewriteBase and RewriteRule.

2 comments:

  1. Thanks A LOT for that.
    Though, I think there's an issue if you want to use the "?" parameter when creating links.
    It's sometimes needed when for instance you create campaign with Google Analytics. Then the slash will be added after the last parameter :
    /controller/action/param1/param2?foo=bar/
    instead of :
    /controller/action/param1/param2/?foo=bar
    I think the good place to look is in Router::url()...

    Or add the ?foo=bar manually

    ReplyDelete