PHP Exception Handling - ErrorException

PHP Exception Handling – IntlException

As we approach the end of our detailed PHP Exception Handling series, today we’ll take a closer look at the IntlException. The IntlException is thrown by the wide variety of classes, functions, and methods found within the Internationalization Functions API set. These functions are are a PHP-based wrapper for the International Components for Unicode (ICU) library set, allowing code to easily work with strings, numbers, and dates across a variety of locales and formats.

Throughout this article we’ll examine the IntlException by first looking at where it resides in the overall PHP Exception Hierarchy. We’ll also explore some fully functional code samples that will illustrate how many of the common Intl classes can be used, and how doing so may cause IntlExceptions to be thrown, so let’s get started!

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, the purpose of the Intl class in PHP is to make it easy to work with values that are typically troublesome to convert to the appropriate locale-based format. As a simple example, the United States uses a period (.) as a decimal separator (e.g. 123.456), while France uses a comma (,) decimal separator (e.g. 123,456). It would be a monumental task to code localization handling for every project, so the Intl class, using the ICU library, standardizes localization techniques and makes it relatively easy to format number, date, and string values into the multitude of localized contexts you might need.

However, as with most API functions, the Intl class can sometimes run into problems, which can manifest themselves into thrown IntlExceptions. Since the Intl class is a built-in PHP extension, the first thing you’ll need to do is make sure the extension is enabled in your own php.ini file. Typically, this will involve opening your php.ini file, searching for php_intl, and uncommenting the appropriate line, like so:

Now, to test these classes out we’re going to manipulate some common types of values, including currencies, dates, numbers, and strings. To accomplish this we’re using the built-in formatter classes including IntlDateFormatter, NumberFormatter, and MessageFormatter. Let’s begin with a date via our formatDate(...) function:

As you can see, this function primarily acts as a small wrapper for the IntlDateFormatter class constructor method, accepting a number of arguments and passing those parameters to the IntlDateFormatter constructor. We then call the $formatter->format($value) method with our passed $value parameter as an argument, which actually attempts to perform the format based on all the arguments used during construction. We log the result to the console.

Now, you’ll notice a call to the throwFormatterException($exception) function at the end of the try block, which is particularly important. Since the Intl classes are from an extension, if these methods or classes have an issue they default to issuing a basic E_WARNING message, but otherwise try to use the default settings and locale to process the formatted value, so execution can continue. To force these classes to actually throw a catchable IntlException you’ll need to explicitly enable the intl.use_exceptions setting in the php.ini file. If this setting is disabled (which is the default value), only warnings will be generated. However, the Intl class provides a number of helper functions to check if a given formatter instance caused an error, even if it wasn’t an explicitly thrown/caught IntlException instance. To illustrate, let’s finally take a look at the aforementioned throwFormatterException($exception) method:

The purpose of this function is to determine if the passed $formatter object is actually a proper Formatter class type and, if so, determine if the intl.use_exceptions php.ini setting is disabled (the default setting). If intl.use_exceptions is disabled, we then check if the passed $formatter actually produced an error of some sort, which is determined by passing the $formatter->getErrorCode() method result to the intl_is_failure() function. This function returns a boolean indicating if the passed error code indicates an error or not. If a failure is detected, we output the error code to the log and then manually throw our own IntlException with the appropriate message and error code values passed in.

With this knowledge in hand, we can test out our formatDate(...):

Here we’re first calling formatDate(...) with the current date and time, then calling it a second time with a null date value specified. Here we see the output of these calls:

Unsurprisingly, the first call works fine and outputs our date and time. Meanwhile, the second call shows that execution called the format(...) method, but didn’t throw an error since intl.use_exceptions is disabled by default. However, we passed the formatter to throwFormatterException($formatter) and this determined that the formatter did actually run into trouble, so we output the error code and manually threw an IntlException with the actual error message. In this case, we can see that the Intl library calls the datefmt_format(...) function behind the scenes, which received an invalid data type for the date since we passed null in this second call. Cool!

Next up, let’s look at our formatNumber(...) function:

The format and execution of this function is the same as we saw in formatDate(...), so we won’t explain anything. Instead, let’s test it out:

Here’s the output we produce from these four calls:

As previously mentioned, by using the ICU library we’re able to automatically convert our value of 1234.567 to the appropriate formats based on the locale value we pass to each call. France uses the SI format, while Switzerland uses apostrophes as thousands separators and periods for a decimal separator. In our last call we passed an invalid $style argument value to the underlying NumberFormatter constructor, so a legit IntlException is thrown and caught.

Now we have the formatCurrency(...) method, which does just what formatNumber(...) did, except ideally for currencies:

Let’s test it out:

Here’s the output:

Again, the first call works as expected, while the second produces an uncaught error, so we manually throw an IntlException.

Finally, let’s look at formatting messages via formatMessage(...):

And our test code:

Produces this output:

Since our $pattern argument was set to null, we again throw an IntlException in the second call.

As mentioned, the php.ini setting of intl.use_exceptions is disabled by default, but let’s try enabling it and executing these tests again to see how things change:

Our expectation is, now that intl.use_exceptions is enabled, any problems that a Formatter instance experiences during constructor or when calling format(...) should result in an explicit IntlException being thrown. Therefore, subsequent calls to our backup throwFormatterException($exception) function will be skipped entirely, since these invocations appear after the formatter does its work.

Since all the valid calls we made during our tests will still perform as expected, we’ll skip over those and only execute the “INVALID” tests a second time:

Performing these same invalid calls produces the following output now, confirming that each problematic call explicitly created and threw IntlException, as expected:

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.