PHP Exception Handling - ErrorException

PHP Exception Handling – InvalidArgumentException

Making our way through our in-depth PHP Exception Handling series, today we’ll go over the InvalidArgumentException in PHP. The InvalidArgumentException should be thrown when an inappropriate argument is passed to a method or function. This can be either due to the actual object data type, or because the data itself is invalid in some way.

Throughout this article we’ll explore the InvalidArgumentException in more detail, starting with where it sits in the PHP Exception Hierarchy. We’ll also go over a few functional sample code examples that aim to show how InvalidArgumentExceptions might be used, and where they differ from TypeErrors which we saw explored in our PHP Exception Handling - TypeError article last month. Let’s get to it!

The Technical Rundown

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?

The ability to include type declarations was introduced with the release of PHP 5. This allows code to explicitly define when parameters are expected to be of a certain data type. In PHP 5, the effect of passing an invalid object type is a recoverable fatal error. Nowadays, with PHP 7, the result of an invalid argument is a TypeError. Generally speaking, it’s considered good practice to explicitly define your type declarations wherever possible, since this tends to produce tighter, less error-prone code. However, the existence of built-in declaration type-checking raises an interesting question in the context of an article about InvalidArgumentExceptions: What is the purpose of explicitly throwing an InvalidArgumentException, when the language itself includes built-in means for detecting improper argument types?

There are two scenarios where using InvalidArgumentExceptions might be appropriate. The first is when explicitly changing the strict_types flag, which disables the default coercion from one data type argument to that of the declared type, when possible. That is, if passing a string to an int declared type argument, PHP 7 will attempt to convert the value to an int automatically. With strict_types enabled, this coercion is disabled.

The other scenario in which explicitly throwing an InvalidArgumentException can be useful is when the data type is correct, but the data value is considered invalid. Be careful here though, since PHP 7 also includes other Exception types that may be more appropriate depending on the invalidity of the data, such as LengthException and OutOfRangeException.

To examine how this all works we have setup a testing ground in our code in which we’ll be creating a few new Book instances, which each have an associated Publisher instance assigned to them:

Of particular importance is the setPageCount(int $pageCount) method:

As you can see, we have an explicit type declaration of int for the $pageCount parameter, but we also go a step further and directly confirm if the passed value is an integer type. If not, we throw a new InvalidArgumentException.

To test this out, here we have a couple functions that create a new Book instance and attempt to change the pageCount property by passing non-integer values of float and string, respectively:

Calling both these test functions doesn’t produce any problems and the output shows our initial Book instances both have their page count updated to the correct value:

What we’re seeing here is the previously discussed automatic coercion from the compatible types of float and string into the declared parameter type of int. PHP knows how to convert the string value "835" and the float value of 328.0 to int automatically, so it does so without any trouble.

However, now let’s enable strict_types at the top of the file and run these functions again:

As we can see, suddenly we’re producing unexpected TypeErrors because PHP is detecting that the explicitly declared type of int is not being passed.

That’s all well and good, but let’s look at what happens when we use a class object instead of a primitive type in our type declaration. Here we have the Book->setPublisher(Publisher $publisher) method:

To test this out we’ll try passing a string as the only parameter:

Calling this function produces an unsurprising TypeError once again:

In fact, since we’re dealing with object instances here, PHP doesn’t know how to implicitly coerce a string value into a complex Publisher class instance. Therefore, no matter whether strict_types is enabled or not, the above function always throws a TypeError.

Does this mean that InvalidArgumentExceptions are never appropriate or can never be used for methods with explicit type declarations of class object types? No, it just means that an InvalidArgumentException should be used based on the data value itself, rather than the data type. For example, let’s look at our last test function:

Here we’re trying to pass a new Publisher instance with the correct name of the publisher, except it’s all lowercase. We only want to accept Pascal Case values as valid publisher names, so the Publisher->setName(string $name) method performs a simple bit of logic to check if the $name parameter is in Pascal Case or not:

Note: We could obviously just change the passed value to the result of the ucwords() function, prior to assignment to the name property, but we’re purposely neglecting to do so in this example. Either way, execution of the passInvalidPublisherToPublisher() function throws the InvalidArgumentException we were expecting:

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.