Throwing and catching exceptions
As we all know, PHP and Symfony allow you to throw exceptions from your code. The code that calls the code throwing the exception can then handle it using a try-catch block. This is a very powerful method of programming, but to use it to its full potential requires some thought. I personally had some previous experience with exceptions from Java. Java exceptions are a lot more developed than PHP/Symfony exceptions out of the box.
For one thing, in Java you must catch all exceptions, or your code won’t even compile. So there will never be exceptions that are unhandled. PHP and Symfony do not enforce exception handling as such. (Well, the framework will just crash with a 500 server error when it encounters an unhandled exception.) Another big difference is that all the standard Java classes (and there are a lot of these) have built-in exceptions that are very specific. For instance, the File object can, amongst others, throw an IOException or a NullPointerException.
Now, we know two things:
- Java classes throw specific exceptions
- All exceptions must be caught
In Java, you are strongly advised to be very specific when throwing exceptions, and when checking your exceptions. What does that mean?
Say you are trying to open a file via a URL, and it fails.You could throw and catch an IOException, but that wouldn’t tell you too much. So it might be better to use a FileNotFoundException in case the file is not found, and an UnknownHostException if the url is not valid.
In the calling code, you can then catch these separate exceptions, and decide what you want to do with them. In this example, the FileNotFoundException may indicate a user entered the wrong data, whereas the UnknownHostException might indicate some configuration error. You will probably want to handle these differently.
Since you must catch all possible exceptions in Java, you could just catch the Exception object in your main method and not catch anything lower down. But this is frowned upon because you are killing all the exception handling logic (which is made of kittens).
So, how does this translate to Symfony?
You might write some code, and if something goes wrong throw a new sfException and put some info in the message or code. In the calling code, you catch sfException, examine the message/code and decide what to do. I believe this is not very good practice. For one thing, it means you catch all possible exceptions in a particular place. If something went wrong that you would want to handle higher up the call stack, you can’t, unless you throw another exception.
So in our example here, you might throw sfException, with the message being either ‘file not found’ or ‘unknown host’ and some arbitrary codes. You have look at the exception, and if you want to handle the error in code higher up the call stack, you have to throw a new exception. Even if you want to handle both errors in one caller, you’d still need to look at the code or message to decide what to show the user. You can’t change the code or message because your exception handling will break.
There is another issue in catching sfException: You don’t know what you will be catching. Say that in your code, you use an object written by a colleague. This code may well throw its own exceptions. If you don’t catch these in your called code, they will get propagated up the call stack and your calling code will catch it, probably in a place where you don’t want to handle it. If in your code you only catch the exceptions you are prepared to handle, unchecked exceptions will get propagated up the stack until they are caught or crash the app. But it’s quite feasible you want to catch and handle a configuration error higher up the call stack than a ‘file not found’.
If you throw specific exceptions, you can catch what you want, where you want. Any exceptions you want to check higher in the call stack, you just don’t catch lower down. Any exceptions you are not prepared to handle will be propagated up. Any logic on what to do with exceptions no longer relies on the message, but on the exception type.
Of course this means that you have to define all the exceptions for your objects by extending sfException. You could just autoload a single PHP file with all your exceptions as empty class definitions. It also means you have to give more thought to what could actually go wrong, and how you’d want to handle that, and that is never a bad thing.
In short: Throw specific exceptions, catch specific exceptions.