« Exceptional PHP: Extending The Base Exception Class | Why Every Developer Should Write Their Own Framework » |
A great feature of PHP is the ability to throw and catch exceptions. This feature was introduced in PHP 5, and has been around for years in other languages like Python.
Exceptions make it easy to interrupt program flow in the event that something goes wrong. They allow you to customize how a program handles errors, and gracefully degrades an application. This week, we will discuss various exception handling techniques, and today we will discuss the basic dos and don’ts for exceptions.
First, what is an exception? An exception is an object that is “thrown” by your application. When an exception is thrown, it halts processing until the exception is either caught, or left unhandled. To throw an exception, you use the following syntax:
<?php throw new Exception('my exception message'); [/sourcecode] There are a couple of things at work here: first, we are using the "new Exception" syntax to instantiate a new instance of the built-in Exception class. Second, we are using a special keyword in PHP called "throw" which allows for an exception to be placed onto the stack. If left like this, the exception thrown above will bubble up and cause processing to halt at the point when the exception is raised. This is typical error behavior, but what makes exceptions special (and useful) is the ability to "catch" them. [sourcecode language="php"] <?php try { throw new Exception('my exception message'); } catch (Exception $e) { // do some sort of error handling here } [/sourcecode] Catching exceptions allows us to try and recover from the error, or allow our application to degrade gracefully. In production, unhandled exceptions will cause the page to stop loading (or not load at all), but handled exceptions allow you the ability to redirect a user to an error page or do other error handling. That is the basic syntax for using exceptions. But when should you use them and under what conditions? Here are some tips for making proper use of exceptions: <strong>Exceptions are a part of object-oriented programming.</strong> This may well be the most controversial point of this blog entry, but objects are really best used and should mostly be used with <a href="http://en.wikipedia.org/wiki/Object-oriented_programming">object-oriented programming</a>. The exception itself is an object. PHP offers a number of <a href="http://php.net/manual/en/function.trigger-error.php">error raising options</a> that I recommend for use in procedural code, but exceptions should mostly be used with objects. <strong>Extending exceptions is cool and encouraged.</strong> As a developer you are allowed and encouraged to <a href="http://www.php.net/manual/en/language.exceptions.extending.php">extend the base exception class</a> on your own to create custom exceptions. These custom exceptions need not implement any custom methods; instead, you can use them to raise exceptions in different parts of your application. For example, you can raise a custom DatabaseException in the database class while raising a custom ActionException when actions are performed. Exceptions can be extended like any other class: class CustomException extends Exception {}
We can then throw CustomException. You can even further extend CustomException (for example if you want to implement certain custom methods and then have other exceptions use those methods). Note that in order to throw something it must extend the base Exception class; otherwise PHP will not allow it to be thrown.
Be sure that your exceptions honor layer abstraction.
One of the more complicated things about handling exceptions is that you want to honor layer abstraction when throwing and catching exceptions.
For example, let’s say that PDO raises an exception due to a unique key constraint in the database. Unhandled, it will bubble up to the top. If the PDO exception was caused by something in your Controller, allowing the PDO exception to bubble up would be a violation of layer abstraction.
A better choice would be to catch the PDO exception and wrap it in a Controller exception. For example:
<?php try { // some PDO action here } catch(PDOException $pdoE) { throw new ControllerException('There was an error: ' . $pdoE->getMessage() ); }
When the exception bubbles up, the PDO exception will have been handled, but the message will be included in a ControllerException. This is an acceptable way to handle exceptions that honors the principle of layer abstraction.
Don’t use exceptions to manage normal program flow.
There’s a temptation to use exceptions to manage program flow. Take the following source code for example:
<?php try { if($var == $condition) { throw new TypeA(); } else { throw new TypeB(); } } catch (TypeA $e) { } catch (TypeB $e) { } [/sourcecode] <a href="http://blueparabola.com/blog/exceptional-situations-require-exceptional-measures">Exceptions are designed to be used for exceptional situations.</a> Exceptional situations are situations in which the normal flow would result in an error or other detrimental behavior; exceptions are designed to reduce the detrimental result. In this case, the exceptions are being thrown purely to control what information is executed. There is clearly no normal operation of this code without the exceptions, as an exception is going to be raised no matter the value you pass to the if-else block. A good rule of thumb is that if you cannot remove the thrown exceptions from your application and still have it complete successfully (given the right information), you've incorrectly used exceptions. <strong>Exceptions are meant to be handled.</strong> The very existence of the try-catch loop indicates that exceptions are meant to be handled. They are meant to be resolved, even if that resolution is to rethrow the exception after doing some sort of error handling. With errors, fatal or otherwise, there's the possibility that something was completed half-way. Exceptions help eliminate this possibility by halting processing long enough for you to clean up before terminating the application. For example, with exceptions you can effectively utilize <a href="http://en.wikipedia.org/wiki/Database_transaction">transactions</a> - if an exception is raised, it can be caught, a rollback can be performed and the exception can be rethrown. It's a best practice to try and handle as many exceptions as possible, rather than leaving them to bubble up, as it enhances user experience and reduces the likelihood that they will break your application in some way (by half writing a file, for example). <strong>Exceptions are not meant to be silenced.</strong> Occasionally I run across code that does this: <?php function myFunction() { try { // do something that raises an exception } catch (Exception $e) {} } [/sourcecode] The end result here will be that the exception is silenced. The application will not be halted, and there is no error handling in the catch block; the exception simply disappears. This means there is no reason for the exception to be raised in the first place since it won't be heeded by the developer! Most of the time that I see this is when PHP itself raises some sort of exception (meaning the developer can't remove the exception from possibility, and thus just mitigates it). Struggle against the urge to do this. PHP's classes throw exceptions because there is a problem, and frameworks do the same. Handle those exceptions; don't just silence them. The only time it is appropriate to silence an exception is if it has been completely handled and there are no further issues related to that exception. For example, if an exception is raised by a file writing class because the file doesn't exist and in your catch block you create the file, silence the exception. But don't silence exceptions simply because they're inconvenient. <strong>Go from more specific to less when trapping exceptions</strong> What happens if you have the following code? class ExceptionA extends Exception {} class ExceptionB extends ExceptionA {} try { throw new ExceptionB(); } catch (ExceptionB $e) { } catch (ExceptionA $e) { } catch (Exception) { }
The correct answer is that the very first catch block (the block that catches ExceptionB) will handle the exception. This will happen despite the fact that ExceptionB is a child of ExceptionA and Exception. This is because PHP will execute the first catch block that meets the requirements. This is useful for situations in which you might have multiple different types of exceptions raised, and want to handle them differently.
Use exception codes to differentiate between exceptions
All exceptions in PHP take an optional second argument of an exception code. This code can be used to determine what kind of exception you have.
$randomException = new Exception('message'); $specificException = new Exception('message', 123);
The first exception looks like every other exception raised. However, the second exception has a code – which you can use to differentiate it from other types of exceptions of the same type. This is useful in the event that you have database exceptions, one of which is for a failed connection and another is for a unique key violation; you can therefore tell the user what exactly went wrong.
Summary
For those who have not used exceptions, this should provide a fairly basic introduction to their use and management. On Wednesday, we’ll talk about writing your own exceptions.
Brandon Savage is the author of Mastering Object Oriented PHP and Practical Design Patterns in PHP
Posted on 11/9/2009 at 1:00 am
David Coallier (@davidcoallier) wrote at 11/9/2009 9:38 am:
Good article, however I think you should dive into the spl exceptions in order to get people interested in various types of exceptions (Exceptions we are encouraging in the upcoming PHP Recommended Standards for framework (or whichever the form that it’s being given))
Andy Thompson (@andytson) wrote at 11/9/2009 4:52 pm:
I’d disagree on the use of the error code to differentiate between exceptions. It would be better to define clear subclassing of Exception as much as is feasible before involving error codes.
Also, following that, throwing/catching the base Exception should probably be avoided, as all exceptions are based on it, so making it hard to differentiate between them.
Brandon Savage (@brandonsavage) wrote at 11/9/2009 7:55 pm:
I think it would be really rather silly to have an exception for each and every file in a library or action. For example, under your model, for a Twitter library we’d have:
* CurlException
* BadRequestException
* MessagePostFailedException
* ReadStreamFailedException
* and so on…It’d be much better to define a TwitterException and define the various types as codes.
Matt McKeon wrote at 11/9/2009 11:20 pm:
I agree with Andy, extending the base Exception class seems more intuitive and strait forward then adding an error code. Isn’t that some of what Exceptions try and replace, ambiguous error codes?
I somewhat agree with you Brandon, in your example there are to many exceptions defined. But you don’t really need them all anyway.
Some of those can be lumped under something like NetworkException, ApiException, etc; I’d think most of them would be handled similarly anyway. So why burden yourself with extra Exception classes *or* confusing error codes.
Andy Thompson (@andytson) wrote at 11/10/2009 2:45 am:
Yes, sorry that’s what I meant by being feasible, I just thought from your examples that you were suggesting using the base Exception class and error codes.
Hans wrote at 11/10/2009 7:38 am:
Hi! Interesting post, would really like to learn more on the subject.
Is it bad coding practise to try and collect non-existant data from the database? Was contemplating the removal of a dbException which is thrown at the return of empty result sets. If yes – why?
Brandon Savage (@brandonsavage) wrote at 11/10/2009 7:54 am:
An empty results set is a perfectly valid results set. I would probably allow for an empty results set to be returned, rather than triggering an exception.
You might consider triggering an exception later on in the event that an empty results set should have been impossible. For example:
if(!$id) {
$user = new User();
} else {
$user = UserPeer::retrieveByPK($id);
}if(!$user)
{
throw new ActionException(‘User object not loaded’);
}Hope that helps!
Hans wrote at 11/10/2009 8:24 am:
Yes thank you. Makes sense to put the result set validation in the application logic (at least in my case).
Thank you.
Jory Geerts wrote at 11/10/2009 1:54 pm:
Brandon, I don’t really think its “much better to define a TwitterException and define the various types as codes”. If I recall correctly, you had some wrapping code around the cURL library, to abstract away from it and make things a bit more re-usable. (I might be wrong, obviously – can’t be bothered to go dig around and find that article.)
If you are then throwing TwitterExceptions in the cURL wrapper, the whole louse coupling idea kinda fails, doesn’t it?But, nice article. Exceptions are a great way to make your programs more understandable and manageable. (And the rule of thumb you mention is dead-on.)
« Exceptional PHP: Extending The Base Exception Class | Why Every Developer Should Write Their Own Framework » |