Wednesday, July 25, 2012

Throw your own - Nested exceptions

If you think that working with exceptions is hard to understand, exception nesting will look even harder (nested exceptions, chained exceptions, exception bubbling - it is all the same). Well, it is a step above, and it can be used if your application is structured in several layers, but at the end you'll see that it is a concept which is here to make your life easier.

The basic syntax
try
{
 // do something that can go wrong
}
catch (Exception $e)
{
 throw new Exception( 'Something really gone wrong', 0, $e);
}
Yes, you could see that kind of example at php.net, but it still does not make much sense. Why you caught it at first place, just to throw a new one? To be honest, it is a bad example (just like the examples on php.net). No wonder that you can not understand it.
But lets stick on it for one more moment.
First, you caught exception, like in any other try catch block, and than you passed it to an another one. It means that the second exception has reference to the first one, and who ever catch that second exception, will have access to the first exception too (the previous or the cause). It is hard to notice from this example, but you are probably guessing, it can be very helpful when debugging your application.

Adding useful information

function get_db_connection( )
{
 throw new Exception( 'Could not connect to database');
}

function update_email( $username, $email)
{
 try
 {
  $conn = get_db_connection();
  $conn->update( "UPDATE user SET email = '$email' WHERE username = '$username'");
 }
 catch (Exception $e)
 {
  throw new Exception( 'Failed to save email ['.$email.'] for user ['.$username.']', 0, $e);
 }
}

try
{
 update_email( 'myusername', 'newmail@foo.com');
}
catch (Exception $e)
{
 echo($e->getMessage().'
'.$e->getTraceAsString().'
'); while($e = $e->getPrevious()) echo('Caused by: '.$e->getMessage().'
'.$e->getTraceAsString().'
'); }
In this example we are outputting error information on screen, but in the real life situation, it should go to your log file. That way, when database error occurs, instead of only knowing that database connection was corrupted, you will know exactly which users encountered that problem too.

Pre Php 5.3 problem
Although passing cause exception as argument to a new one is a standard feature in languages which are supporting try/catch syntax, PHP architects included it in version 5.3. So if your application should be compatible with PHP versions before 5.3 you will have to do workaround to support nested exceptions. It can be done, it is not too hard, but I'll write about it in some other article.

Conclusion
The best motivation to use nested exceptions is when you are not satisfied with exceptions that your lower layers are throwing. During the development and debugging, when you encounter such situation, just catch it and add additional useful information. In most cases it will assume that you are using some external libraries (which original source you should not change) and that your application has multiple layers of functionality. Do not forget that your logging mechanism should be aware and capable of nested exceptions too.

6 comments:

  1. If you intend on creating a lot of custom exceptions, you may find this code useful. I've created an interface and an abstract exception class that ensures that all parts of the built-in Exception class are preserved in child classes. It also properly pushes all information back to the parent constructor ensuring that nothing is lost. This allows you to quickly create new exceptions on the fly. It also overrides the default __toString method with a more thorough one.

    message}' in {$this->file}({$this->line})\n"
    . "{$this->getTraceAsString()}";
    }
    }
    ?>

    Now you can create new exceptions in one line:



    Here's a test that shows that all information is properly preserved throughout the backtrace.

    getMessage()}')\n{$e}\n";
    }
    catch (Exception $e) {
    echo "Caught Exception ('{$e->getMessage()}')\n{$e}\n";
    }
    }

    echo '' . exceptionTest() . '';
    ?>

    Here's a sample output:

    Caught TestException ('Unknown TestException')
    TestException 'Unknown TestException' in C:\xampp\htdocs\CustomException\CustomException.php(31)
    #0 C:\xampp\htdocs\CustomException\ExceptionTest.php(19): CustomException->__construct()
    #1 C:\xampp\htdocs\CustomException\ExceptionTest.php(43): exceptionTest()
    #2 {main}

    ReplyDelete
  2. It seems that a google stripped out parts of your code.

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete
  4. This comment has been removed by a blog administrator.

    ReplyDelete
  5. This comment has been removed by a blog administrator.

    ReplyDelete
  6. This comment has been removed by a blog administrator.

    ReplyDelete