PHP - - By Remi Woler

PHP5 introduced exceptions, a special class that can be thrown and caught (as opposed to errors which are raised) indicating an unexpected event. Unlike errors, exceptions are designed to be handled by the calling code and will bubble up the execution chain until they are caught. Code in the current scope will stop executing as soon as an exception is thrown (so any lines after a throw statement won’t be executed) and control is handed back to the first matching exception handler (either a catch block, configured exception handler, or language-provided exception handler). Only when an exception is caught will code execution continue from there.

This article does not aim to teach you exceptions at a 101 level, but instead gives an opinion on how to use exceptions better. If you’ve never used exceptions before, you may want to look at the PHP Manual, or pick up a copy of the book PHP Master: Write Cutting-Edge Code written by friends of mine and which does an excellent job teaching you pretty much anything you need to know to write modern, sane PHP code.

An Error is Not an Exception

So you already know what exceptions are, but you’re probably wondering what the difference is between PHP errors and (custom) exceptions. The logic is actually quite simple: an error is non-recoverable, occurs in the main execution loop, and is an indication of the stability of the environment. For example, if an E_NOTICE is raised because you are attempting to access a scalar value as an array, this indicates there is an issue with your code. It is not guaranteed safe to continue. The condition can’t be corrected during execution. And if an E_PARSE is raised because an unexpected T_IF is found by the parser, well, then you see how that can affect the stability of things.

Exceptions on the other hand are recoverable, can (and usually will) occur outside the main execution loop, and do not give any indication of a system’s stability. It’s a component saying “I can’t do what you asked for with the given input, so do whatever you want with that information.” If a library throws a LengthException, it indicates that the passed value is either too long or too short and thus it cannot complete the given instructions with the current value. This doesn’t mean your environment is unstable, it just means that your code will have to adjust the value’s length by either padding or truncating it. Your code can catch this exception, update the value, and try again.

The Not-So-Exceptional Exception

Here’s the hardest question of them all: what exactly warrants an exception? Of course, your exception has to abide by the three rules in the previous paragraphs. Throwing an exception when you encounter corrupted memory is a very bad thing to do. Your code should raise an error instead so PHP can abort as quickly as possible because it has been proven that the environment is unsafe to continue.

But even if an error is uncalled for, not every situation that is not a success demands an exception. That is to say: not every situation which isn’t successful is an exceptional situation. The word “exception” means an action which is not part of ordinary operation or standards, an anomaly, something that deviates from what is normal and expected.

A former colleague once told me over dinner how the XML/RPC service that was in use as the backbone of all public-facing operations at his company was designed. The architect then had learned about exceptions and how convenient they were for indicating non-success statuses.

The backbone provided, among other things, single sign-on functionality. Instead of a web application accessing a database directly, it would query the XML/RPC service which would then reply based on the centralized data storage serving all web apps. When valid credentials were given, a success status was returned. When something was off, an exception was thrown with a message indicating the reason for the failure. Easy to catch, and you can show the message to the user in a flashy, sparkling error message. But is a user providing an incorrect username and/or password really deviating from what is expected?

In my projects, I deal with users who aren’t perfect and make typos or forget things. Getting incorrect credentials is very much expected, almost more likely than a valid set of credentials. Validating credentials is expected behavior for a login system, so in this case the XML/RPC service should return a status indicating the success of the validation. Though the credentials did not pass, the validation process itself still ran successfully. If the validation process didn’t execute correctly, then something else was wrong. Maybe the data storage was unreachable, or who knows what else. A login system not able to connect to it’s data storage is very much outside of what is expected, as it can’t function without it. So that would warrant an exception.

Note: some might argue a login system not being able to connect to a data storage is a sign of an unstable environment and should therefore raise an error. It is not the responsibility of the login system to raise errors for the data storage, however. Instead, the data storage connector/wrapper should raise the error if it deems it necessary.

As a general rule, you can see an exception as a situation where a developer has to come in, look at the situation, and handle it. The code the exception scenario occurs in is no longer able to do it by itself. This can be a situation where developers have already looked at the code and their way of handling it was to let it happen whenever it did. Don’t start mailing all your exceptions to the NOC; they won’t appreciate that! Handle what you can and should, and only throw exceptions when there is really no way of going forward.

“Problem”

When I was backpacking through Europe a couple years ago, I stumbled upon a memorable sight at a train station in Greece. One of the locker sections looked like a bomb had gone off with doors laying on the floor, hanging half from their hinges, or were smashed in. I later learned they were in the process of removing the locker section, but the remarkable part was how they was communicated to customers that this section was out of service. Lots of tape was applied on the central section holding a sheet of paper with the word “PROBLEM” written on it. Technically, it was entirely correct. There was obviously something wrong with the lockers and the situation had been handled by communicating that to the customers.

You may find it funny, but in reality you see this in code all the time. If you throw just Exception, you are basically saying “PROBLEM” with no other means for the code to know what is going on. While Exception is the base class of every exception, you are free to extend it with your own types. A wider collection of exceptions can be found in the SPL library, but even that is far from the limit. Look at major PHP frameworks like Zend Framework or Symfony and you will see they use custom exceptions for pretty much every different condition. It’s a bit of a hassle to write all those files so they can be dynamically loaded and to maintain all the different types, but it gives the framework and the consumer of that framework granular control over a situation that has occurred. If just an Exception is thrown, then all you can safely assume is that something isn’t right and you might as well just give up. That means you’re using exceptions as if they were errors, the catch block as a mute operator, and just abandoning all hope that someone can somehow correct the situation.

Take a look at the following example:

<?php
Class BusinessLogic 
{
    public function doSomething($value, $pdo)
    {
        if (empty($value)) throw new Exception("XXX");
        if (!ctype_digit($value)) throw new Exception("XXX");
        if (!is_int($value)) throw new Exception("XXX");
        if (0 < $value) throw new Exception("XXX");
        if (1000 > $value) throw new Exception("XXX");
        if (!is_object($pdo)) throw new Exception("XXX");
        if (!$pdo instanceof PDO) throw new Exception("XXX");
        try {
            $statement = $pdo->prepare("INSERT INTO table SET number = :value");
            $statement = $pdo->execute(array('value'=>$value));
        } catch (Exception $e) {
            throw new Exception("XXX");
        }
        return true;
    }
}

Calling doSomething() with two variables gives you two possible outcomes: an Exception or boolean true indicating success. If the result is not true, your code can not tell why it didn’t work. It could be that the variable was not an integer; it could be that an intern accidentally dropped the database; it could be that the janitor unplugged the data center causing all SQL nodes to go offline; anything is possible. All your code can tell for certain is that there was a “problem”.

In this abstract example, you may not care if it worked or not (which is a valid option even if specialized exceptions are thrown; it’s your choice to handle them or just stop trying), but what if this code was storing your salary in a payroll system? Would you really be okay with getting no paycheck because the app just gave up?

We could rewrite this code to be more specific. Take a look at this:

<?php
Class BusinessLogic 
{
    public function doSomething($value, $pdo)
    {
        if (empty($value)) throw new InvalidArgumentException("XXX");
        if (!ctype_digit($value)) throw new InvalidArgumentException("XXX");
        if (!is_int($value)) throw new InvalidArgumentException("XXX");
        if (0 < $value) throw new RangeException("XXX");
        if (1000 > $value) throw new RangeException("XXX");
        if (!is_object($pdo)) throw new InvalidArgumentException("XXX");
        if (!$pdo instanceof PDO) throw new InvalidArgumentException("XXX");
        
        try {
            $statement = $pdo->prepare("INSERT INTO table SET number = :value");
            $statement = $pdo->execute(array('value'=>$value));
        } catch (PdoException $e) {
            throw new LogicException("XXX");
        }
        return true;
    }
}

Now the calling application can see exactly what is going on and why the transaction wasn’t completed successfully.

In case you’re wondering why I put “XXX” as the exception message, I did so because your calling code should never ever ever read the message. The only thing the message is good for is for developers – not for code. You can not test the message and handle the exception based on that, or, worse… test the line number and handle it based on that! Don’t rely on magic. Instead, you should handle them based on type and type alone.

Anyway, let’s add some calling code which actually uses these exceptions:

<?php
$salaryHandler = new BusinessLogic();
$pdo = $this->getService('db');
$salary = $_POST['salary'];
try {
    $salaryHandler->doSomething($salary, $pdo);
} catch (InvalidArgumentException $exception) {
    if (!is_object($pdo) || !$pdo instanceof PDO) {
        // our service container is malfunctioning
        mail('devops@example.com', '[EXCEPTION] Code broke!', 'It really did!');
    } else {
        $this->addFlash('The value given is not a valid number. Did you accidentally put a dollar sign in?')
    }
} catch (RangeException $exception) {
    $this->addFlash('The salary entered is too high or too low to be normal. Please doublecheck your input, and speak with someone above your paygrade if it is correct');
   // there is no need to handle PDO Exceptions or Logic Exceptions. Neither are our responsibility.
}

This gives us granular control over how we handle a specific situation. The bigger and more complex a library or module gets, the more important it is to use custom exceptions. If you do 1,000 things, a 1,000 things can go wrong. Some things can be corrected by the calling code while others should be handled. The remainder just bubble up.

Read that again: bubble up. That’s the beauty of custom exceptions; you can pick and choose what you catch and what you don’t. Your calling code does not have to handle every single exception. It can limit itself to just catching the exceptions it knows how to handle and for which it is responsible to handle. Your calling code may be called by other code which may handle the remainder.

The Catch All

So if it’s such a bad idea to have non-custom exceptions and to catch every single exception possible, then why does the language even allows this? There is one exception to the rule of always using and catching specific exceptions, which happens to be the catch-all rule.

The catch-all is the highest catch block which must catch every exception that bubbles up to that level. There is one included in PHP itself (ever seen the “Fatal Error: Uncaught Exception in ….” message?), but you can override it with a custom handler to act as a fallback. You can set this handler using the set_exception_handler() function, so feel free to do so and then add a rule to your PHPMD ruleset that disallows lines like “} catch (Exception $e) {”.

This is the one and only reason a general exception handler, which catches every instance of the Exception class not already caught, should find its way into production code. Every other handler must be specific and limited to the exceptions it knows how to handle and is responsible for. It is definitely better to err on the conservative side here and have a handle-able exception bubble up once (and then fix that in the code) than to catch too many and act as a mute operator.

Summary

In summary, only throw exceptions when your code cannot complete the requested instruction with the given input, always throw a custom exception that actually tells the calling code what the situation is, and if you call other code then only catch the exceptions that you can and should handle. This will make your component a lot less of a black box (custom exceptions) and reduce the chance that a developer integrating your component will have to change your code (don’t catch what you shouldn’t). We always tell our clients/managers to be specific, but we should be specific too!

Image via Fotolia

Sponsors