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.