PHP Exception Handling - ErrorException

PHP Exception Handling – UnexpectedValueException

Traversing the depths of our detailed PHP Exception Handling series, today we’ll dive into the UnexpectedValueException. Not to be confused with the InvalidArgumentException that we covered in a previous article, the UnexpectedValueException is typically thrown when a value is not within an expected set of values. For example, if a function is returning a date value, but the runtime execution of this function produces a malformed date, you may opt to throw an UnexpectedValueException, so further execution using this value isn’t corrupted by it.

In this article we’ll examine the UnexpectedValueException by first looking at where it resides in the overall PHP Exception Hierarchy. We’ll also look through some fully-functional sample code that illustrates how you might use UnexpectedValueExceptions in your own code, along with how this exception subtly differs from the InvalidArgumentException, so let’s get exploring!

The Technical Rundown

All PHP errors implement the Throwable interface, or are extended from another inherited class therein. The full exception hierarchy of this error is:

Full Code Sample

Below is the full code sample we’ll be using in this article. Feel free to use any or all of the code if you wish to follow along.

When Should You Use It?

As mentioned in the introduction, there is a bit of debate and uncertainty about the difference between the InvalidArgumentException and the UnexpectedValueException. Specifically, in what scenarios should one exception type be used over the other?

To attempt to answer this we’ll start with the InvalidArgumentException which we’ve previously explored. The key to knowing when to use this exception type is in the name — in particular, the Argument keyword. In most situations, the InvalidArgumentException should be used when an argument is passed that is considered improper for one reason or another. For example, the following Book::setPublisher(Publisher $publisher) method confirms that the passed argument is of the proper class/type, and if not, it throws an InvalidArgumentException indicating as much:

Since InvalidArgumentException is a child class of LogicException, another way to think of this exception type is that its validity should be checked by the logic of the code itself. This is why it makes sense to perform the above type check, since we can confidently state that any passed argument that is not a Publisher class instance is invalid and won’t work with our code.

On the other hand, the UnexpectedValueException is a child class of RuntimeException, indicating that it should be used in response to something that can only be known at runtime. This might be checking the actual data of an argument, or often, it is used when the return value of a function is improper or unexpected in some way.

To illustrate, consider this modified version of our Book class:

Specifically, we’ve added some additional logic to the setPageCount(int $pageCount) method. While we explicitly confirm that the passed argument is an integer (throwing an InvalidArgumentException if not), we also perform a check on the validity of the data itself by confirming it falls without the bounds set by MAX_PAGE_COUNT. If the page count exceeds this maximum, we throw a new UnexpectedValueException, since this is an “unexpected value” that we’re seeing at runtime.

We can test this out and see the difference between these two exceptions at runtime with a few example functions. We’ll start with createValidBook() for a functional baseline:

This creates a Book instance, as expected, and outputs it to the log:

Now, let’s try creating another Book with an excessive page count argument:

Sure enough, this throws an UnexpectedValueException, indicating that the page count is invalid:

Finally, some people argue that UnexpectedValueExceptions should only be thrown within functions that call other functions within their code block, which could result in unexpected values from other called functions. Let’s see how we might create such a function with getInvalidBook():

Running this function produces the following output:

As you can see here, our Book class itself considers this instance valid given all the arguments passed. However, notice that we’ve accidentally passed a negative page count argument of -848. Since the Book class doesn’t currently have any checks for a negative value (which it obviously should, but this is just for illustration), our getInvalidBook() function checks the created $book instance for validity, and if it finds that the $book->getPageCount() method returns a negative number, it throws an UnexpectedValueException.

Check out the Airbrake-PHP library, designed to quickly and easily integrate into any PHP project, giving you and your team access to real-time error monitoring and reporting throughout your application’s entire life cycle. With automatic, instantaneous error and exception notifications at your fingertips, you’ll be constantly aware of your application’s health, including any issues that may arise. Best of all, with Airbrake’s robust web dashboard cataloging every error that occurs, you and your team can immediately dive into the exact details of what went wrong, making it easy to quickly recognize and resolve problems.