A Crash Course of Changes to Exception Handling in PHP 7

Ahmed Khan
Share

This article was peer reviewed by Thomas Punt, Niklas Keller, and Younes Rafie. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

Exception handling saves your code in the most unusual circumstances. PHP 7 has introduced two new classes that assist a developer in handling errors with ease, and that’s what we’ll look at in this post. Before the introduction of these classes in PHP 7, exception error classes were written to handle the different types of errors.

Illustration of female leg stepping on rake with the word Oops in big letters, indicating pending accident

Throwable Class

Throwable is the interface from which Exception and Error classes branch out. This particular class helps you catch any throwable errors, irrespective of whether they are an exception or an error. For example:

<?php
try {
    throw new Exception("This is an exception");
}
catch (Throwable $e) {
    echo $e->getMessage();
}

Or the newly defined ParseError:

<?php
try {
    $result = eval("2*'7'");
}
catch (Throwable $e) {
    echo $e->getMessage();
}

After executing this code, you will get a ParseError because “;” is missing inside eval().

User defined classes cannot implement Throwable directly, and must instead extend Exception which implements Throwable.

Error Class

The Error class in PHP 7 is a new type of class that handles the different errors – they are either fatal errors or type errors, and this class is only for internal PHP errors. Error is divided into four subclasses:

  1. ArithmeticError
  2. TypeError
  3. ParseError
  4. AssertionError

A thing to keep in mind before upgrading to PHP 7 is that if you have defined a custom Error class, you have to make sure you changed the name before upgrading. If you don’t, you will get a fatal error.

Let’s discuss the above four classes one by one.

ArithmeticError

This error shows up when performing mathematical operations. For example, when you are using intdiv():

<?php

try {
    var_dump(intdiv(PHP_INT_MIN, -1));
}
catch (ArithmeticError $e) {
    echo $e->getMessage();
}

You will get “Division of PHP_INT_MIN by -1” because we have shifted it a bit by a negative amount.

Another class, DivisionByZeroError, also extends from ArithmeticError. This error is thrown under two different conditions:

Note: You will get -1 only in combination with PHP_INT_MIN.

First, if you do a mod of a number by 0:

<?php
try {
    $result = 5 % 0;
    echo $result;
}
catch (DivisionByZeroError $e) {
    echo $e->getMessage();
}

If you use the same method as above and change the % to /, you will get a warning instead and the result can be any one of these: +INF, -INF, or NAN. If you want this to result in an exception, better use an error handler that transforms the warning into a thrown exception.

However, you will have the DivisionByZeroError exception if you execute the following code:

<?php
try {
    $result = is_finite(1.0 / 0);
    if (in_array($result, [INF, NAN,-INF])) {
        throw new DivisionByZeroError('Division by zero error');
    }
}
catch (DivisionByZeroError $e) {
    echo $e->getMessage();
}

The other method that will get you the DivisionByZeroError is by using intdiv() again.

Note: A bug report for this issue has been reported on PHP.net.

TypeError

This error is mostly used with the Scalar Type declarations in PHP 7. The error will be shown when you have created a function or variable of a specific data type and you are trying to save a value of a different data type. For example:

<?php
declare (strict_types = 1);
function add(int $a, int $b)
{
    return $a + $b;
}
try {
    echo add("3", "4");
}
catch (TypeError $e) {
    echo $e->getMessage();
}

If you run the above code, a TypeError will be thrown and you will get must be of the type integer, string was given error. If you run the above code without declare(strict_types=1); you won’t get any problems and the result will be 7, unless you change the number to a non-numerical string.

ParseError

This error is thrown when using eval() to insert a new line of code or using an external PHP file which contains a syntax error. Before ParseError, when you had a syntax error in your external PHP file or in eval(), your code was broken and a fatal error was shown. For example, let’s assume we have a PHP file with the following code:

<?php
$a = 4
$result = $a * 5;

And, we are calling it in another PHP file:

<?php
try {
    require "index3.php";
}
catch (ParseError $e) {
    echo $e->getMessage();
}

When this code is executed, syntax error, unexpected end of file is shown instead of a fatal error. Before this class was introduced, it was almost impossible to handle syntax and fatal errors with ease.

AssertionError

Before the introduction of the AssertionError class, we had to create our own functions to handle assertion exceptions when binding a custom function using assert_options(). This error will only be shown when an assertion made via assert() fails. To work with it, you first need to configure the assert directives in PHP.ini:

  1. Assert.exception: By default its value is 0 and it only generates a warning for the object rather than showing an error. However, when the value is changed to 1, then it will throw an exception or an Assertion Error which can be caught.

  2. Zend.assertions: By default, it’s value is -1 which is for production mode, i.e., the assertion code will not be generated. When it is set to 1 it will be in development mode in which assertion code will be generated and executed. When it is set to 0, assertion code will be generated but won’t be executed during runtime.

For example, let’s make an assert which will fail.

<?php
try {
    assert(2 < 1, "Two is less than one");
}
catch (AssertionError $ex) {
    echo $ex->getMessage();
}

When the above code is executed, you will get only a warning: "assert(): Two is less than one failed" and your exception will not be caught because assert.exception is 0. In order to make AssertionError catch the assert exception, we need to change assert.exception to 1. So when you run the following code:

<?php
ini_set('assert.exception', 1);
try {
    assert(2 < 1, "Two is not less than one");
}
catch (AssertionError $ex) {
    echo $ex->getMessage();
}

Instead of the warning, you will see that an error is caught and only an error message will be shown, i.e. “Two is less than one.”

Since the introduction of the new classes, many of the fatal and recoverable fatal errors have been inherited from the Error class. It is not guaranteed that your custom handler, which you have set by using set_exception_handler(), will catch those errors. So, if you want to throw some custom exceptions in your code, you don’t have to reset your custom handler as it can now catch the errors by just using Throwable.

Summary

If you are using a PHP version older than 7, you should keep these things in mind before transitioning. To be safe, you can also look at the php 7 upgrade guide.

Have the new error and extension classes caused you any grief? Do you like or dislike their introduction in PHP 7? What do you hope to see changed about them, if anything?