kelvinluck.com

a stroke of luck

Custom 404 error messages with CodeIgniter

CodeIgniter is an “open source PHP web application framework that helps you write kick ass PHP programs”. It’s a nice MVC framework which makes writing PHP bearable by enforcing some organisation and structure on your project. I’ve used it a few times, most recently when I built the Sharify website.

CodeIgniter provides helpful templates for certain error states of your application including a 404 Page not found. And you can edit these templates to suit your needs. Unfortunately these templates are static view code and in an error situation you can’t easily query a database or access most of the functionality of CodeIgniter. This is quite limiting – in my case I couldn’t render my error view into the standard template view I use on the rest of the site. And so I came up with a bit of a hack to make a 404 error behave like any other page request.

I started by creating a method on my default controller (home) called “page_not_found”. Inside this method I generate my error page (by inserting the relevant view into my main template view as I do throughout the rest of the site). Since this is a normal controller method I can interact with any models and views I desire (for example if I needed to grab stuff from the database to build my navigation I could) to produce my output page.

Then I need to override the CI_Exceptions class. So I create the file MY_Exceptions.php inside CI_DIR/application/libraries (where CI_DIR is the path to your codeigniter system folder). Inside that file I override the show_404 method and instead of simply outputting a view I use cURL to request the page I previously set up. I also POST the originally requested URI along with this request which allows me to output it on the generated page if desired.

The code looks like this:

class MY_Exceptions extends CI_Exceptions {
   
   function show_404($page = '')
   {
      $code = '404';
      $text = 'Page not found';
     
      $server_protocol = (isset($_SERVER['SERVER_PROTOCOL'])) ? $_SERVER['SERVER_PROTOCOL'] : FALSE;
   
      if (substr(php_sapi_name(), 0, 3) == 'cgi')
      {
         header("Status: {$code} {$text}", TRUE);
      }
      elseif ($server_protocol == 'HTTP/1.1' OR $server_protocol == 'HTTP/1.0')
      {
         header($server_protocol." {$code} {$text}", TRUE, $code);
      }
      else
      {
         header("HTTP/1.1 {$code} {$text}", TRUE, $code);
      }
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, 'http://' . $_SERVER['HTTP_HOST'] .'/home/page_not_found/');
      curl_setopt($ch, CURLOPT_HEADER, 0);
      curl_setopt($ch, CURLOPT_POST, 1);
      curl_setopt($ch, CURLOPT_POSTFIELDS, 'originalURL=' . urlencode($_SERVER['REQUEST_URI']));
      curl_ex ec($ch); // Wordpress won't let me post the word e x e c - remove the space in the previous code to make it run...
      curl_close($ch);
   }
}

As you can see, I make sure that the correct HTTP header is outputted and then I pass through the page loaded from my “page_not_found” method on my default “home” controller. This means if you visit a non existent page on the Sharify site (like this one) then you get a pretty page which looks like the rest of the site and tries to help you find what you are looking for.

In a perfect world there would be a cleaner way to do this within the CodeIgniter framework but I did some searching when I started building the site and I couldn’t find any clean solutions. There were a couple of other suggestions on the forums but they seemed to involve editing more files which shouldn’t be related to error handling. While I don’t like the hacky nature of the extra curl request I think this solution is relatively simple and clean.

9 Comments, Comment or Ping

  1. uk

    Hej.

    Very nice script. Near to what I was searching for. Thanks! :)

    August 10th, 2009

  2. Kyle

    This is the simplest way to handle 404s in CI I have come across. It's much smarter than extending the Router class. One suggestion though. Change:

    curl_setopt($ch, CURLOPT_URL, 'http://' . $_SERVER['HTTP_HOST'] .'/home/page_not_found/');

    To:

    // add this line to get the base URL
    $this->config =& get_config();
    $base_url = $this->config['base_url'];

    // change this line in the curl code
    curl_setopt($ch, CURLOPT_URL, $base_url . "/home/page_not_found/");

    That should avoid any problems with moving between servers (e.g. local development and production) as well as using subdirectories.

    Thanks!

    August 17th, 2009

  3. What exactly is the purpose of cURL in this script? It seems like a really convoluted way of dealing with errors... There has to be something better!

    (sorry, I'm wracking my brain trying to find the best method)

    August 18th, 2009

  4. Hi,

    @Kyle - thanks for the suggestion - my CI install is in the root directory and I didn't consider that it could be anywhere else but your code looks good for that :)

    @Thomas - yeah, it's not pretty! In fact it's hacky like I mention above... There is actually a nicer solution which I've been meaning to write up for a while - see this thread:

    http://codeigniter.com/forums/viewthread/109892/P15/#576973

    (the link is the final piece of the puzzle - you'll have to work backwards through the thread to find the first piece!

    Hope it helps,

    Kelvin :)

    August 19th, 2009

  5. Kyle

    Thanks for the update, Kelvin.

    I agree this method is hacky, although I actually prefer it over the new one. Having to define an "any" rule for every controller seems more painful. I like that I can just drop MY_Exceptions into the library folder and not have to worry about much else.

    I suppose another way to approach the new solution would be writing a function that loops through the controllers directory and makes all the routes for you. However, there's still room for errors when you take into account controllers that use the index method. I really think CI should ship with a more acceptable solution.

    Anyway, keep the CI posts coming!

    August 19th, 2009

  6. Laurence Wilks

    Hi

    Am I being dim? How did you echo out the 'not found' URL in your view file in the example i.e.:

    http://www.sharify.it/how-it-works1

    October 21st, 2009

  7. I just use $_SERVER['HTTP_HOST'] and $_SERVER['REQUEST_URI']...

    October 22nd, 2009

  8. ONe

    Hi Kelvin. I've been reading the codeigniter forums trying to figure out the best solution to handle 404 errors and I found your approarch very interesting. Im using it now in my website, but I dont fully understand one thing about your implementation. You use this route to handle 404's:

    $route['(:any)'] = "home/$1";

    But it doesnt work for me adding the last "/$1". Actually, how could it possibly work with "$1"? Using just :

    $route['(:any)'] = "errorController";

    makes sense to me. If you add the last "/$1", how does it know which function of the controller should it use? It would be different every time.

    I hope you understand my question :P
    Thanks for your idea and your time!

    November 13th, 2009

  9. Nice bit of code Kelvin... works like a charm!

    February 17th, 2010

Reply to “Custom 404 error messages with CodeIgniter”