Catching, Throwing, and Passing Exceptions

June 25, 2017

There's a frequent oversight I see in code I read that involves exceptions. More specifically, this oversight occurs when an exception is caught and an exception of a different class is thrown as a result.

Here's an example of this scenario.

<?php

try {
    throw new \Exception('try');
} catch (\Exception $e) {
    throw new CustomException('catch');
}

Here's what this code outputs when executed.

Fatal error: Uncaught CustomException: catch in file.php:6
Stack trace:
#0 {main}
  thrown in file.php on line 6

The problem here is that this output provides no information on the first exception that was thrown. This can make debugging much more difficult than it has to be. This is especially the case when the code throwing the first exception involves a third-party library and you are not familiar with its internals.

Here's an example of how to fix this.

<?php

try {
    throw new \Exception('try');
} catch (\Exception $e) {
    throw new CustomException('catch', 0, $e);
}

Notice that two additional parameters are passed when instantiating the second exception. Let's assume that the CustomException class has a constructor signature identical to that of the core Exception class.

The second parameter passed is an integer representing an exception code. Its default value is 0. In this case, I'm passing that value because I don't have a code I need to specify. (In reality, it's generally good practice to specify a code, but doing so isn't really relevant to the topic at hand.)

The third parameter is an instance of a previously thrown and caught exception. This parameter is often forgotten and the source of the aforementioned oversight. When this parameter is included, the output includes a stack trace for the first exception as well as the second.

Fatal error: Uncaught Exception: try in file.php:4
Stack trace:
#0 {main}

Next CustomException: catch in file.php:6
Stack trace:
#0 {main}
  thrown in file.php on line 6

This example is admittedly contrived and has very short stack traces. This is because the code is being executed in the global namespace and doesn't have nearly the complexity as a real world codebase. As such, the additional stack trace may not look as useful here, but in practical applications, it can be invaluable in debugging the source of an issue.

So please, for the sake of your own sanity a few months from now and that of anyone else who has to read your code: if you have reason to catch an exception and throw a new one, specify the first exception as the new exception's $previous parameter so that you'll have more debugging information at your disposal.