How to use built-in SPL exception classes for better error handling

May 6, 2011 – 8:56 pm Tags:

Since PHP 5, there has been a bundle of built-in exceptions – the “SPL exceptions” – in PHP. However, the documentation for these classes is quite lacking in examples, and it can be difficult to understand when you should be using them.

The short answer is always. You’ll find a longer answer if you continue reading :)

There is also a french translation of this post, by Frédéric Blanc.

What are the SPL exceptions

The very base PHP exception class is the Exception.

The SPL exceptions are quite similar to this base class, but they are more specialized – as in, they should be used to report more specific error conditions. They are used exactly the same as the base exception – just throw.

You should be using these more specific exception classes most of the time, as they fit the more specific conditions you typically have quite well.

You can find the list of SPL exceptions in the PHP manual. Next, let’s look at each of them and an example or two of how to use them.

BadFunctionCallException

Going in the same order as they are in the manual, the first is BadFunctionCallException.

This exception isn’t very useful in my opinion. Generally this may get thrown by PHP if you’re calling some code incorrectly, but since the main use for this is if a function (not a method in a class) is called without all the parameters or if the function doesn’t exist, custom code rarely needs this.

BadMethodCallException

This one is a bit more useful. Similar to BadFunctionCallException, this should be thrown when a class method either does not exist or does not have all the required parameters.

The main use for this exception is when you implement the __call magic method:

public function __call($name, $arg) {
    //Let's say here's a condition which determines what to do
    if(...) {
        //This is the succesful condition and perhaps we return a value or something
    }
 
    //Here things went wrong - The method doesn't exist!
    throw new BadMethodCallException("The method '$name' does not exist"); 
}

This is in fact a best practice: Always throw a BadMethodCallException when you create a __call method! Otherwise your code could be calling functions which indeed don’t exist, and you never know about it (for example if you have typed the name wrong).

DomainException

The DomainException is a bit trickier case to explain.

Basically, this is what you would throw if your code messes up and for example a sanity-check finds a value is “outside the domain”.

For example, if you have a method which performs weekday calculations, and for some reason a result of a calculation is outside the 1-7 range (for days in a week), you could throw a DomainException. This is because the value is outside the “domain” for day numbers in a week.

InvalidArgumentException

This one is pretty self-explanatory: Throw an InvalidArgumentException when your functions or methods receive arguments that are invalid.

For example, if your function requires a number but is instead given a string, throw a InvalidArgumentException stating that the function requires a number:

public function numberRobot($number) {
    if(!is_numeric($number)) {
        throw new InvalidArgumentException('The number robot demands a numeric sacrifice!');
    }
}

Additionally, you can use this when the application receives invalid POST or GET arguments. For example, depending on how you want to handle your errors, you could throw an InvalidArgumntException in a controller which expects specific GET/POST arguments but gets wrong types of them.

LengthException

A LengthException can be used when the length of something is too short or too long – For example, a file name’s length could be too long.

This can also be applied if an array’s length is incorrect.

LogicException

The LogicException is again a bit trickier one because it has no obvious uses, as most cases are covered by many of the other exception classes.

The main use for LogicException is a bit similar to DomainException – it should be used if your code (for example a calculation) produces a value that it shouldn’t produce.

Errors which cause a LogicException to be thrown would generally be caused by bugs in your code.

OutOfBoundsException

An OutOfBoundsException should be thrown if code is attempting to access an invalid key.

Typically this could be used in code that attempts to access an associative array, but performs a check for the key.

Also, another use for this can be when you implement ArrayAccess in your class.

public function offsetGet($offset) {
    if(!isset($this->objects[$offset])) {
        throw new OutOfBoundsException("The offset '$offset' is out of bounds");
    }
 
    return $this->objects[$offset];
}

Note: This should be used for keys, not indexes (as in strings, not numbers). You may wish to check when implementing whether or not the offset being read is a number or not, and throw a OutOfRangeException instead.

OutOfRangeException

This is the same as OutOfBoundsException, but this should be used for normal arrays which are indexed by number, not by key.

OverflowException

If your class acts like a container, you can use OverflowException when the object is full, but someone is trying to add more items into it.

RangeException

This is an exception that should be thrown when a value is out of some specific range. It’s similar to DomainException in its intended purpose, but it should be used in cases where the error is going to be caused in a runtime scenario.

RuntimeException

The purpose of the RuntimeException is somewhat similar to as its namesake’s purpose is in Java.

In Java-world, you have checked and runtime exceptions. Checked exceptions must always be caught – The Java compiler will not compile code which does not have catch-blocks for any code that throws checked exceptions.

Runtime exceptions in Java do not require a catch-block in the calling code.

Since PHP does not have support for checked exceptions, the divide between runtime and other exceptions is less strict. However, the purpose of a RuntimeException is still similar: It should be throw in cases where the calling code does not necessarily have the capacity to handle it.

This also applies to subclasses of RuntimeException: OutOfBoundsException, OverflowException, RangeException, UnderflowException and UnexpectedValueException.

UnderflowException

This is the opposite of OverflowException – If your class is a container and it’s empty, but someone is trying to remove elements from it, you can throw a UnderflowException.

UnexpectedValueException

A UnexpectedValueException should be thrown if a value is outside a set of values.

For example, if you have a list of const‘s, and a value must be one of them:

const TYPE_FOO = 'foo';
const TYPE_BAR = 'bar';
 
public function doSomething($x) {
    if($x != self::TYPE_FOO || $x != self::TYPE_BAR) {
        throw new UnexpectedValueException('Parameter must be one of the TYPE_* constants');
    }
}

Other tips

Sometimes more than one exception will look like it suits the error. In this case, you should always try to use the exception which most accurately explains what went wrong – The goal should be to make debugging the code easy.

This is also why you should always try to make the exception’s message be helpful. It doesn’t need to be understandable by whoever is using your application, but it should be understandable by whoever is going to be developing or maintaining it.

If you’re writing a library to be used by other programmers, the exceptions should also try to be as helpful as possible to the user of the library. It’s highly frustrating when you’re trying to figure out why some code doesn’t work, when it keeps producing confusing error messages.

Conclusion

You should always try to use the exception which best describes the error scenario your code is in. This does not only make handling the error easier, it also makes it easier to find and understand the cause of the error.

If you have any other uses for the SPL exception classes than what I have mentioned here, feel free to leave a comment – The official documentation for them is poor and I’m sure there are more ways to use them.

Further reading:
Should a failed function return a value or throw an exception? in this blog.
Exceptions and abstraction in this blog.
Brandon Savage has also written some good exceptions related articles on his blog.

Share this:

RSS feed Subscribe to my RSS feed

About the author

Jani is a 15 year veteran of the software industry. He's currently available for consulting

  1. 11 Responses to “How to use built-in SPL exception classes for better error handling”

  2. Thanks for posting info on when to actually use the SPL exceptions.

    I actually am finding it more helpful to use my own custom exceptions since the SPL exceptions are still somewhat ambiguously named in my opinion.

    By James Fuller on May 7, 2011

  3. Yeah the naming vs. the description is in some cases somewhat confusing. I have no idea why they haven’t improved the docs, they have been around for quite a while already afterall.

    By Jani Hartikainen on May 7, 2011

  4. The LogicException is pretty useful for things like calculations. Consider some calculation that was trying to calculate some percentage based on a set of data. If the divisor is zero after some maths were performed, a LogicException can be thrown.

    By Herman J. Radtke III on May 7, 2011

  5. Personally, I’m still trying to see the point (other than standardization).

    Usually my exceptions are a lot more specific. Exceptions should be as specific as possible, so they can be specifically caught.

    These exceptions, and the examples you’re giving are almost all ‘fatal errors’ to me. Meaning: not something I’d want to try to recover from.

    So why would I care to differentiate between a ‘LogicException’ and a regular exception in that case. Statistics?

    By Evert on May 9, 2011

  6. @Evert,

    Specific Exceptions are great, but since we already have the SPL Exceptions it would be beneficial if the community could agree upon the semantics of their use.

    Also, fatal errors are very often things you want to recover from, such as an atomic database operation.

    By James on May 9, 2011

  7. @James: I just can’t think of a reason why I’d specifically want to catch a LogicException or RangeException.

    A PDOException or a hypothetical HTTPException or UserNotFoundException I totally would though.

    If I had a complex database system and I do indeed need to fix non-transactional state after an error, an InvalidArgumentException is way too broad; This exception could theoretically be thrown by any method in the stack and the current state of my application is completely unknown.

    With a UserNotFoundException I’d know exactly whats wrong, and I can try to fix this situation.

    So yea, my earlier point: I can’t think of a reason why I’d want to ever specifically catch an exception with such a generalized definition.

    By Evert on May 9, 2011

  8. @Evert
    So you are arguing to catch your specific exceptions which I 100% agree with. I also prefer to do that, but let’s assume you are working with third party code that throws SPL exceptions. I think it would be beneficial agree on the meaning of those exception types, and I think this article is a good starting point.

    By James Fuller on May 10, 2011

  9. @Evert I do somewhat agree with what you’re saying – I think the primary use for these kinds of exceptions is to help out the programmer, and in some cases it is a bit hard to think of valid uses.

    There are all sorts of typical error cases – the InvalidArgumentException is perhaps the best example – and you would want to handle those. It’s convenient when the language gives you a standard way to go about it.

    Then, when you mis-use the code later, you hopefully get a more useful error to help you figure out what’s wrong.

    By Jani Hartikainen on May 10, 2011

  10. To those trying to understand when to use SPL exceptions and when to use a custom exception:

    1 – Using SPL exceptions is a good practice since each has intrinsic semantics.

    2 – Using custom exceptions is necessary as well because you want to be throwing granular and specific exceptions — this helps with debugging and takes away the ambiguity when trying to track down an issue. When you take the time to nail down specific exceptions, you’ll find your debugging sessions don’t last as long and aren’t as complex.

    3 – When creating your custom exceptions, have them “extend” the SPL exceptions when possible. For example:

    $eventManager->call( function() { … } );

    In the above code sample, a callback was given but no event selector was given. This would generate an “UndefinedSelectorException”. This exception extends “InvalidArgumentException”.

    https://github.com/wilmoore/reactq/blob/master/src/lib/ReactQueue/Exception/UndefinedSelectorException.php#L17

    By Wil Moore III on May 10, 2011

  1. 2 Trackback(s)

  2. May 11, 2011: Twitter, SPL and Developer Wushu | muddylemon
  3. Jan 30, 2013: Programowanie w PHP » Blog Archive » Jani Hartikainen’s Blog: How to use built-in SPL exception classes for better error handling

Post a Comment

You can use some HTML (a, em, strong, etc.). If you want to post code, use <pre lang="PHP">code here</pre> (you can replace PHP with the language you are posting)