Exceptional Exceptions

Share this article

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

Frequently Asked Questions about Exception Handling in PHP

What is the purpose of exception handling in PHP?

Exception handling in PHP is a powerful mechanism that allows developers to manage errors and unusual situations that may occur during the execution of a program. It provides a way to transfer control from one part of a program to another. PHP exception handling is used to change the normal flow of the code execution if a specified error occurs. This can make the code more readable and manageable, as it separates the error handling code from the main program logic.

How does the try-catch block work in PHP?

In PHP, the try-catch block is used to handle exceptions. The try block contains the code that may potentially throw an exception, while the catch block contains the code that will be executed if an exception is thrown in the try block. If an exception is thrown within the try block, the script stops running and control is passed to the first catch block that matches the type of exception thrown.

What is the role of the ‘finally’ block in PHP exception handling?

The ‘finally’ block in PHP exception handling is used to ensure that a section of code is always executed, regardless of whether an exception is thrown or not. This can be useful for cleanup activities, such as closing a file or a database connection, which should be performed whether an operation succeeded or failed.

How can I create custom exceptions in PHP?

In PHP, you can create custom exceptions by extending the built-in Exception class. This allows you to add custom functionality to your exceptions, or to create exceptions that are specific to your application’s domain. To create a custom exception, you define a new class that extends Exception, and then add any custom methods or properties that you need.

What is the difference between exceptions and errors in PHP?

In PHP, an error is a serious problem that prevents the script from running, while an exception is a condition that changes the normal flow of execution. Errors are typically caused by things like syntax errors, or calls to undefined functions. Exceptions, on the other hand, are typically used to handle conditions that are not necessarily fatal to the program, but that require some special handling.

How can I handle multiple exceptions in PHP?

In PHP, you can handle multiple exceptions by using multiple catch blocks. Each catch block handles a specific type of exception. When an exception is thrown, the catch blocks are checked in the order they appear in the code. The first catch block that can handle the type of exception thrown is executed.

Can I rethrow exceptions in PHP?

Yes, you can rethrow exceptions in PHP. This can be useful if you want to handle an exception in some way, but also want to allow it to be caught by a higher level exception handler. To rethrow an exception, you simply use the ‘throw’ statement within a catch block.

How can I log exceptions in PHP?

In PHP, you can log exceptions by using the error_log function within a catch block. This allows you to record information about the exception, including its message and stack trace, to a specified log file.

What is the PDOException in PHP?

The PDOException is a type of exception that is thrown when an error occurs in a PDO operation. PDO (PHP Data Objects) is a database abstraction layer that provides a consistent interface for accessing databases in PHP. The PDOException provides information about the error, including the SQLSTATE error code and the error message from the database driver.

How can I handle uncaught exceptions in PHP?

In PHP, you can handle uncaught exceptions by defining a custom exception handler function, and then setting it as the default exception handler using the set_exception_handler function. This function will be called whenever an exception is thrown that is not caught by a try-catch block.

Remi WolerRemi Woler
View Author

Remi is from The Netherlands and spends most of his time developing web applications and socializing. When he's not busy doing either of those, he likes watching NFL games, (ice)speed-skating, playing a game from the Half-Life(2) series, or watching a multi-layered movie.

Intermediate
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week
Loading form