Extending CodeIgniter’s Exceptions Class
CodeIgniter, my favorite open-source PHP framework, provides functions (full-on, globally available functions) for dealing with errors. It’s not such a bad thing, because you don’t have to worry too much about variable scope or any other troublesome programming concepts. The downside of having such a simple approach to error handling, at least in CodeIgniter’s default implementation, is that there isn’t much information about your application available once an error is thrown. For example, if you’re in a controller and decide that http://example.com/foo/bar should only be accessible by users with certain permissions (according to some authentication system you’ve set up on your own since CodeIgniter, unfortunately, doesn’t come with a built-in authentication library), you can do something like the following in the bar() method of your Foo controller (assuming you’re routing your requests using the default setup):
if (!$this->auth->is_allowed()) {
show_error('Not for you, jack!', 401);
}According to the definitions of the HTTP status codes, a 401 should be sent when “the request requires user authentication”. The above bit of code will send a 401 status code and display the “Not for you, jack!” message in a fairly vanilla error page. What that message will NOT do, however, is:
- Log the error. You have to explicitly call the logging function yourself. Crazy, right?!?!
- Provide any useful information about the error. Again, no logging.
- Display a page that looks like the rest of your site. You can, of course, style the error views any way you want, but you can’t include common headers or footers used elsewhere in your application because the variables used in those view files won’t be in scope.
So I did some digging around. As it turns out, those error handling functions instantiate and call methods in an exception class named CI_Exceptions (note that it’s “CI_Exceptions” and not “CI_Exception”). CodeIgniter let’s you extend and/or replace native libraries, so long as you tell CI what prefix you’re gonna use on your versions of those libraries. So, if you want your own version of the exception class, and your prefix is OOR_, you can create a file in the libraries folder under your application folder (or wherever your application logic lives) named OOR_Exceptions.php. In that file, you can include the following:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class OOR_Exceptions extends CI_Exceptions
{
}You, my friend, are banging on all cylinders. Let’s add some functionality.
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class OOR_Exceptions extends CI_Exceptions
{
public function show_error($heading, $message, $template = '', $status_code = 500)
{
$ci =& get_instance();
if (!$page = $ci->uri->uri_string()) {
$page = 'home';
}
switch($status_code) {
case 403: $heading = 'Access Forbidden'; break;
case 404: $heading = 'Page Not Found'; break;
case 503: $heading = 'Undergoing Maintenance'; break;
}
log_message('error', $status_code . ' ' . $heading . ' --> '. $page);
return parent::show_error($heading, $message, 'error_general', $status_code);
}
}CI_Exceptions::show_error() takes the following parameters: $heading, which determines what heading message will shown on your error page; $message, which determines the message displayed under the heading; $template, which determines which error view to use; and $status_code, which determines which HTTP status code to send in the header.
The following line of code is important:
$ci =& get_instance();
The get_instance() function returns an instance of the CI super object (more about that in the libraries section of the CI user guide). With that super object, you have access to just about everything you need to grab, information normally available in the rest of your application through the controllers. Now, there is something to be said about making your error pages blind to whatever is going on in the rest of your application (for security reasons), so I advise you to consider the implications of what you could potentially be doing and whether or not this sort of code-wrangling is right for you. If you’re OK with doing this, then notice of much more awesome the show_error() function just got. You now have access to CodeIgniter’s URI library, which provides information about the request using a straightforward API; so you can now log a useful error message that, in subsequent log audits, will yield useful information about that or any other error, giving you a better idea of how people are actually using your application.
There’s so much you can now do. The above example uses the error_general.php view for all errors, so you don’t have to worry about styling or maintaining more than one error view. You can default to a different status code (although I’m not sure why you’d really want to do that). You can redirect people to some random external page. The possibilities are endless.
Hope this helps someone. Tootles.
I’ve just started working with CodeIgniter to get started with a more structured Framework. We had created our own, but with bring on new talent, we need something that others have worked with before. Anyway, in my previous code I used try/catch for exceptions a lot. Is this available in CI? With the Framework, is there anything to catch? I know that I could throw my own exceptions, but is the CI_Exceptions a traditional try/catch exception handler? I guess I’m a little thrown by the wording of an Exception that doesn’t use try/catch, or does it? Any additional information would be helpful. Thanks in advance.
@Philip The naming is a bit misleading. CodeIgniter — at least, as it exists today — doesn’t throw exceptions. And
CI_Exceptionshandles errors, not exceptions. You can’t throw an instance ofCI_Exceptions(it doesn’t extend PHP’s nativeExceptionclass, and the constructor doesn’t take any arguments). If you throw an uncaught exception,CI_Exceptionswon’t handle it. You can probably useCI_Exceptionsas your exception handler, although I’m not sure of the nuance involved in doing that (I should try it and do a post, probably).If you look in CodeIgniter.php, you’ll find
set_error_handler('_exception_handler');, which begs the question, “Why aren’t they usingset_exception_handler()instead since the name of the callback is, um,_exception_handler?”. For some reason, they use the terms “exception” and “error” as if the two are interchangeable when they really aren’t in PHP._exception_handler()loadsCI_Exceptionsand uses it’s methods to show and log the error. My guess is that since CI has been in development since around 2006 — back when lots of people were still using PHP4 (set_exception_handler()has only been available since PHP5) — that’s how they decided to introduce the concept of exception handling.