Exception Handling in PHP 8

PHP is the language used to build websites on the internet for over ten years. Although, there are a lot of people who think that it's time to move into something else, PHP is a dynamic programming language, which means that it can be adapted to the current needs. And the PHP Core team has been excellent in bringing out new features that make PHP an attractive language in this time and age.

The flexibility in the PHP language makes it easy to handle things like exceptions in code, which are the out of the ordinary scenarios that can occur. They can be caused by some unexpected input, a bug, or some other problem. PHP 8 is a new version of this language that was released on 26 November 2020. The new version has been adapted to be more secure and handle exceptions better than the previous versions.

Potential exceptions/errors are enclosed inside a try block if exception is encountered, will be thrown to catch or finally block. PHP usually handles exceptions in a separate catch block for each different type of exception.

In this post, you can gain knowledge about what exactly is exception handling, and how it works.

Below are the topics that shall be covered in this blog:

  1. When, Where, and How to use Exceptions and Errors in PHP?
  2. Error Class
  3. Exception Class
  4. Custom Exception
  5. Multiple Exception
  6. Global Exception Handler
  7. Non-Capturing Catches

#1 When, Where, and How to use Exceptions and Errors in PHP?

PHP 7 introduced the new Throwable interface to unite the exception branches Exception and Error. The entire PHP exception hierarchy is as follows:

interface Throwable
    |- Error implements Throwable
        |- CompileError extends Error
            |- ParseError extends CompileError
        |- TypeError extends Error
            |- ArgumentCountError extends TypeError
        |- ArithmeticError extends Error
            |- DivisionByZeroError extends ArithmeticError
        |- AssertionError extends Error
    |- Exception implements Throwable
        |- ClosedGeneratorException
        |- DOMException
        |- ErrorException
        |- IntlException
        |- LogicException
            |- BadFunctionCallException
                |- BadMethodCallException
            |- DomainException
            |- InvalidArgumentException
            |- LengthException
            |- OutOfRangeException
        |- PharExceptionaddition
        |- ReflectionException
        |- RuntimeException
            |- mysqli_sql_exception
            |- OutOfBoundsException
            |- OverflowException
            |- PDOException
            |- RangeException
            |- UnderflowException
            |- UnexpectedValueException
        |- Custom Exception

To catch both exceptions and errors in PHP 8, add a catch block for Exception after catching Throwable first.

try 
{ 
    // Code that may throw an Exception or Error. 
} catch (Throwable $t) 
{ 
   // Executed only in PHP 7 and more
}

#2 Error Class

Error Class is the base class for all internal PHP errors. Errors can be caught in  try/catch block as explained above. Few errors will throw a specific subclass of Error such as Parse Error, Type Error, and so on.

Here are the list of various types of errors (we have covered only the most common ones):

  1. Parse/Syntax Error
  2. Type Error
  3. Arithmetic Error
  4. Assertion Error
  5. Value Error

a. Parse/Syntax Error

A syntax/parse error in the code while compilation, a Parse error is thrown. If a code contains an error, the PHP parser cannot interpret the code and it stops working.

Let's look into a simple example for understanding Parse error.

Code:

<?php
    $x = "Exception";
    y = "Handling";
    echo $x . ' ' . y;
?>

Output:

syntax error, unexpected '=' in line 3

b. Type Error

When data type mismatch happens in PHP while doing an operation, a Type error is thrown. There are three scenarios where this type of error is thrown:

  • Invalid number of arguments passed to a built-in function.
  • Value returned from a function doesn't match the declared function return type.
  • Argument type passed to a function doesn't match the declared parameter type.

Let's look into a simple example for understanding Type error.

Code:

<?php
function add(int $x, int $y)
{
    return $x + $y;
}
try {
    $value = add('Type', 10);
}
catch (TypeError $e) {
    echo $e->getMessage(), "\n";
}
?>

Output:

Argument 1 passed to add() must be of the type integer, string given.

c. Arithmetic Error

Occurrence of error while performing a mathematical operation, bit shifting by a negative number or calling an intdiv() function, the Arithmetic error is thrown.

Example With Division Operator:

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

Output:

Division of PHP_INT_MIN by -1 is not an integer

Example With Modulo Operator:

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

Output:

Modulo by zero error

Example With Division Operator Which Returns INF:

<?php
try {
    $x      = 4;
    $y      = 0;
    $result = $x / $y;
}
catch (DivisionByZeroError $e) {
    echo $e->getMessage();
}
?>

Output:

INF

Explanation:

There is a very minute difference in the above two examples. The first one contains the Modulo operator and the second one has the  Division operator. If any variable divided by zero will return an error, Division by zero error. When any variable divided by zero with modulo operator returns Modulo by zero error and the variable divided by zero with the division operator also returns anyone the following- INF, NAN, -INF

d. Assertion Error

When an assert() call fails or let's say when the condition inside the assert() call doesn't meet, the Assertion error is thrown. String description emits E_DEPRECATED message from PHP 7.2 version. The Assertion Error thrown by assert() will be sent to catch block only if assert.exception=on is enabled in php.ini.

Let's look into a simple example for understanding assertion error.

Code:

<?php
try {
    $x      = 1;
    $y      = 2;
    $result = assert($x === $y);
    if (!$result) {
        throw new DivisionByZeroError('Assertion error');
    }
}
catch (AssertionError $e) {
    echo $e->getMessage();
}
?>

Output:

Assertion error

e. Value Error

When the type of the argument is correct and the value of it is incorrect, a value error is thrown. These type of errors occurs when:

  • Passing a negative value when the function expects a positive value.
  • Passing an empty string or array when function expects a non-empty string/array.

Let's look into a simple examples for understanding value error.

Code:

<?php
    $x = strpos("u", "austin", 24);
    var_dump($x);
?>

Output:

[Mon Feb 22 20:59:04 2021] 
PHP Warning:  strpos(): Offset not contained in string in /home/ubuntu/value_error.php on line 2

Code:

<?php
    $x = array_rand(array(), 0);
    var_dump($x);
?>
[Mon Feb 22 21:04:14 2021] PHP Warning:  array_rand(): Array is empty in /home/ubuntu/index.php on line 2

#3 Exception Class

Exception Class occurs when a specified/exceptional error condition changes the normal flow of the code execution.

Exception handling comprises five components i.e, try block, exception, throw, catch block, and finally block.

Let's look into a simple example for understanding the above-mentioned components

Code:

<?php
function add($x,$y) {
    if (is_numeric($x) == False) {
        throw new Exception('Num1  is not a number');
    }
    if (is_numeric($y) == False) {
        throw new RuntimeException('Num2 is not a number');
    }
    return $x + $y;
}

try {
    echo add(5,10). "\n";
    echo add(5,k). "\n";
} 

catch (Exception $e) {
    echo 'Exception caught: ', $e->getMessage(), "\n";
} 

finally {
    echo "Finally Block.\n";
}

// Continue execution
echo "Hello World\n";
?>

Output:

15
Exception caught: Num2 is not a number
Finally Block.
Hello World

Explanation:

Our example is about adding two numbers and we assumed that we might get non-numeric value as input which would raise an error.

  • We created a function called addition with Exception for non-numeric values and If encountered with the exception, will throw it with the exception message.

  • We called the addition function inside a Try block so that non-numeric value error won't affect/stop the whole execution. All potential exceptions should be enclosed inside a try block.

  • The Catch block will receive any exceptions thrown from the try block and execute the code inside the block. In our case will print the error message 'Caught exception: Num2 is not a number'.

  • The Finally block will be executed irrespective of whether we received exception or not.

#4 Custom Exception

We use custom exception to make it clear what is being caught in the catch block and to understand the exception in a better way. The custom exception class inherits properties from the PHP exception's class where you can add your custom functions too. To easily understand the exceptions we can use custom exceptions and can log it for the future use.

If you just want to capture a message, you can do it at follows:

try {
    throw new Exception("This is an error message");
}
catch(Exception $e) {
    print $e->getMessage();
}

If you want to capture specific error messages which could be easy to understand you can use:

try {
    throw new MyException("Error message");
}
catch(MyException $e) {
    print "Exception caught: ".$e->getMessage();
}
catch(Exception $e) {
    print "Error: ".$e->getMessage();
}

Code:

<?php

class customStringException extends Exception
{
    public function myerrorMessage()
    {
        //error message
        $errorMsg = 'Error on line ' . $this->getLine() . ': <b>' . $this->getMessage() . '</b> is not a String';
        return $errorMsg;
    }
}

class customNumericException extends Exception
{
    public function myerrorMessage()
    {
        //error message
        $errorMsg = 'Error on line ' . $this->getLine() . ': <b>' . $this->getMessage() . '</b> is not an Integer';
        return $errorMsg;
    }
}

function typeCheck($name, $age)
{
    if (!is_string($name)) {
        throw new customStringException($name);
    }
    if (!is_numeric($age)) {
        throw new customNumericException($age);
    } else {
        echo $name . " is of age " . $age;
    }
}

try {
    echo typeCheck("Sara", 25) . "\n";
    echo typeCheck(5, 10) . "\n";
}
catch (customStringException $e) {
    echo $e->myerrorMessage();
}
catch (customNumericException $e) {
    echo $e->myerrorMessage();
}

?>

Output:

Sara is of age 25
Error on line 21: 5 is not a String

Explanation:

The above example is on a type check, we have two variables name and age . Let's assume $name is of type string and $age is of type integer and we assumed that we might get any type of value as input which would raise an error.

  • We created a function called typeCheck to check the type of the variable with exception. If the condition fails it will throw an exception with an Exception message that we have customized.

  • We created a class customStringException to create a custom exception handler with a function called errorMessage which would be called when an exception occurs.

  • Here we called the typeCheck function inside a try block so that if any error is encountered it could be caught in the catch block.

#5 Multiple Exception

You can also handle multiple exception in a single catch block using the pipe '|' symbol like this:

try {
    $error = "Foo / Bar / Baz Exception"; throw new MyBazException($error); 
} 
catch(MyFooException | MyBarException | MyBazException $e) { 
    //Do something here 
}

#6 Global Exception Handler

In Global Exception Handler, it sets the default exception handler if an exception is not caught within a try/catch block. If no other block is invoked the set_exception_handler function can set a function which will be called in the place of catch. Execution will stop after the exception_handler is called.

set_exception_handler Syntax:

set_exception_handler ( callable $exception_handler ) : callable

<?php

function exception_handler($exception)
{
    echo "Uncaught exception: ", $exception->getMessage(), "\n";
}

set_exception_handler('exception_handler');

throw new Exception('Uncaught Exception');

echo "Not Executed\n";
?>

#7 Non-Capturing Catches

Before PHP version 8, if you wanna catch an exception you will need to store it in a variable irrespective of its usage. You usually must specify the type whenever you use a catch exception. With this Non-Capturing Catch exception, you can ignore the variable.

Example:

try {
    // Something goes wrong
} 
catch (MyException $exception) {
    Log::error("Something went wrong");
}

You can put it this way in PHP 8:

try {
    // Something goes wrong
} 
catch (MyException) {
    Log::error("Something went wrong");
}

Summary:

Here we have explained the basic usage of exceptions and how to implement it in detail.You can quickly track the errors and fix the exceptions that have been thrown in your code. I hope this blog might be useful for you to learn what exception is and the correct usage of it.

If you would like to monitor your PHP code, you can try Atatus here.