Pages

2008/10/14

Throwing and catching exceptions

I recently had to review a web application that was written in PHP and was using Zend Framework to facilitate in the site setup.

One thing that I noticed was that although the code was well written, implementing coding standards and best practices on many of the classes, I did notice a wrong usage of throwing exceptions (the try - catch statements). Most of the exceptions were captured in global class, but nothing was done with it. In this blog post I will try to explain the concepts of bubbling exceptions so you can manage it all in one place.

If you're new to the concept of throwing and catching exceptions, take a look at the description on php.net as it explains you clearly what it's all about.

In short, throwing and catching exceptions is the good way to make sure your application doesn't do stuff it isn't supposed to do and leaving your application broken.

Example: division by zero
function divide($value)
{
    return 1 / $value;
}


This example will return a fatal error when $value is 0 (zero).

A better way would be
function divide($value)
{
    if (!$value) {
        throw new Exception('Division by zero is not allowed');
    }
    return 1 / $value;
}


This will cause your application to be aware that an exception is thrown. Of course you need to handle this in a proper way, that's why you use a try - catch statement.

function mymath($value)
{
    $result = 0;
    try {
        $result = divide($value);
    } catch (Exception $e) {
        echo "An exception occured: " . $e->getMessage();
    }
    return $result;
}


But when you're working on a web application, this might not be your desired action because you don't want to let your visitor know your application is broken. Instead, you want to handle exceptions your own way, by bubbling exceptions upwards so you can manage exceptions in one place.

Bubbling exceptions means that you check in your code fragment for an exception (in your try statement) and throwing this upwards (in your catch statement).

function mymath($value)
{
    $result = 0;
    try {
        $result = divide($value);
    } catch (Exception $e) {
        throw new Exception($e->getMessage());
    }
    return $result;
}


When you create your own exceptions, you can perform better management on your exceptions and act differently upon them.
Creating your own exception is pretty simple, you only need to extend the Exception base class.

class DivideByZeroException extends Exception {}

That's all to have your own exception in place. Now you can bubble it upwards again.

function mymath($value)
{
    $result = 0;
    try {
        $result = divide($value);
    } catch (DivideByZeroException $e) {
        throw new DivideByZeroException($e->getMessage());
    }
    return $result;
}


Now you can create your exception handler method to handle each exception on it's own and perform a specific action. In this case you might want to log these exceptions, but when accessing databases, files or webservices you might want to send out an e-mail to the administrator or developers. I am sure you can think of useful ways to handle different kind of exceptions.

Another good read that's related to this topic is a recent blog post of Patrick Allaert titled Readable PHP code #1: Return ASAP.

15 comments:

  1. Why not just re-throw $e? You're doing this (poor Blogger formatting below):

    try {
    // foo
    } catch (Exception $e) {
    throw new Exception($e->getMessage());
    }

    ...when you can just do:

    throw $e;

    By throwing a new exception here, you're also neglecting to carry any exception code value along with the new exception. Just carrying the message isn't enough!

    You should also mention that, if you're catching a Foo_Exception from some method, but the end user is calling some method in your Bar class, you should be throwing a Bar_Exception.

    Exceptions should always be thrown at the same level of abstraction as the call. So, never throw Foo_Exception from the Bar::createWidget() method.

    ReplyDelete
  2. Even I can learn a bit. I didn't knew this yet. I'm not going to modify the blog post here since all comments are visible, but I'm surely going to implement this in my applications.

    ReplyDelete
  3. This is the worst advice on exceptions ever. You just lose part of the stack trace and add few completely unnecessary lines of code. The point of exceptions is to separate error handling code from where the error happens. You let it bubble up where it can be handled or logged and application terminated. Re throwing it without any action is absolutely retarded and renders the whole concept of exceptions useless. It's also a great way to make others scratch their heads.

    ReplyDelete
  4. OMFG. I just hope nubies do not read this post. This is very good example of BAD practice.

    If you log all stack traces from your code, I can imagine your error logs are simply spammed and useless.

    My good advice is to learn about OO concepts and exception handling best practices.

    As a ZCE you should be more careful about your posts

    ReplyDelete
  5. @karol,

    You're right on the missing stack trace. As brian deshong already pointed out, there's indeed a better way to bubble up exceptions.

    Bubbling up to the highest level is still regarded the best way to handle exceptions in complex and large applications.

    Handling exceptions when they occur should be done, if no further dependencies rely on the object that created the exception (e.g. a database connection or an external web service).

    ReplyDelete
  6. @Daniel,


    The purpose of this post is to show how to throw exceptions upwards so it can be handled in a proper way.

    My mentioning of logging is more in regards to enterprise environments where a separate department handles logging and provides monitoring. This situation is different when working on smaller projects or when there's less separation of tasks.

    ReplyDelete
  7. I'm very sorry, but I cannot agree with your approach or at least the way you did present it.

    > The purpose of this post is to show how > to throw exceptions upwards so it can be > handled in a proper way.

    What is proper way then? For me it is try-to-recover-first operation, then log error and then inform customer that something went wrong (without details of course)

    I still do not get what is the point of having:

    try {
    method_that_throws_MyExceptionClass();
    } catch (MyExceptionClass $e) {
    throw $e;
    }

    ReplyDelete
  8. DragonBe,
    I think you are missing the whole point, you don't need to "bubble" them at all, they will go up the stack trace on their own. This allows you to separate error handling code from business logic and make the code more maintainable. As opposite to return codes, exceptions allow you to defer action and handle the error in the right place. In case of a web application this usually means catching them in a central point, logging an error and displaying 500 error page. In rare occasions you can catch it early and try to recover e.g. connect to a backup database/webservice etc. if primary fails and retry.
    The bottom line is: don't re-throw them, there's no point, you're just adding 4 useless lines of code.

    ReplyDelete
  9. I agree with Karol if we talk about recovery process. For myself it could work more less like this

    try {
    ... Do some nasty stuff that can throw an exception of type ExceptionA in minor failure and general CriticalException instance in case of critical failure...
    } catch (ExceptionA $ea) {

    ... Try to recover ...
    if (!$did_we_recover) {
    ... log $ea stack trace to error log ...
    throw new CriticalException("We could not recover from exception ".get_class($ea)." with message ".$ea->getMessage());
    }

    }

    As example above shows, we do not even try to catch CriticalException since we would not be able to recover from it anyway. When code re-thorws it changes exception class to let system know that this exception is unrecoverable and code let it go up the call-stack until it's handled in main namespace (usually means error_log write and some general message to the customer).

    But usually I do not throw any exceptions on errors that I can recover from.

    ReplyDelete
  10. This comment has been removed by the author.

    ReplyDelete
  11. Bubbling up doesn't make much sense unless you need to recover locally because exceptions are bubbled up any way. I'd rather like to handle them at once place unless I need extreme control over the code. Also the biggest problem of this method is as shown by others is you loose your data. But interesting article anyway.

    ReplyDelete
  12. I've been doing some further research on the matter and I have to agree my approach is not preferred.

    Thanks to @daniel, @karol and @lucky for their comments, they've made me a wiser person.

    ReplyDelete
  13. I learnt alot from this post, does anyone have a better resource for furthering exception knowledge?

    thanks DragonBe!

    ReplyDelete
  14. Thanks for this, great info! Would be nice if you could write a little bit about how to catch different types of exceptions generated from a single try block. :)

    ReplyDelete
  15. @Matt Stevens,


    What you could do is to
    try {
    ...
    } catch (FirstException $e) {
    ...
    } catch (SecondException $e) {
    ...
    } catch (FinalException $e) {
    ...
    }

    This way you can catch each exception, handle them individually for that exception specifically.

    ReplyDelete