ZetCode

PHP Exceptions

last modified October 22, 2026

In this article, we show how to use exceptions in PHP. Exceptions are a mechanism for error handling in PHP, allowing developers to handle errors gracefully and maintain control over the program flow. Exceptions are thrown using the throw keyword and caught using the try-catch block.

Main Features of PHP Exceptions

Exceptions are instances of the Exception class or its subclasses. They are thrown using the throw keyword and caught using the try-catch block.

Basic Usage of Exceptions

The following example demonstrates how to throw and catch an exception in PHP.

main.php
<?php

declare(strict_types=1);

function divide(int $a, int $b): int
{
    if ($b === 0) {
        throw new Exception("Division by zero is not allowed.");
    }
    return intdiv($a, $b);
}

try {
    echo divide(10, 0);
} catch (Exception $e) {
    echo "Caught exception: " . $e->getMessage();
}

In this program, the divide function throws an exception if the second argument is zero. The exception is caught in the try-catch block, and the error message is displayed.

$ php main.php
Caught exception: Division by zero is not allowed.

Custom Exceptions

The following example demonstrates how to create and use a custom exception class in PHP.

main.php
<?php

declare(strict_types=1);

class DivisionByZeroException extends Exception
{
    public function __construct()
    {
        parent::__construct("Division by zero is not allowed.");
    }
}

function divide(int $a, int $b): int
{
    if ($b === 0) {
        throw new DivisionByZeroException();
    }
    return intdiv($a, $b);
}

try {
    echo divide(10, 0);
} catch (DivisionByZeroException $e) {
    echo "Caught custom exception: " . $e->getMessage();
}

In this program, the DivisionByZeroException class extends the Exception class. The divide function throws this custom exception if the second argument is zero. The exception is caught and handled in the try-catch block.

$ php main.php
Caught custom exception: Division by zero is not allowed.

Multiple Catch Blocks

The following example demonstrates how to use multiple catch blocks to handle different types of exceptions.

main.php
<?php

declare(strict_types=1);

class DivisionByZeroException extends Exception {}
class NegativeArgumentException extends Exception {}

function divide(int $a, int $b): int
{
    if ($b === 0) {
        throw new DivisionByZeroException("Division by zero is not allowed.");
    }
    if ($a < 0 || $b < 0) {
        throw new NegativeArgumentException("Arguments must be positive.");
    }
    return intdiv($a, $b);
}

try {
    echo divide(-10, 5);
} catch (DivisionByZeroException $e) {
    echo "Caught DivisionByZeroException: " . $e->getMessage();
} catch (NegativeArgumentException $e) {
    echo "Caught NegativeArgumentException: " . $e->getMessage();
}

In this program, the divide function throws different exceptions based on the input values. The exceptions are caught and handled in separate catch blocks.

$ php main.php
Caught NegativeArgumentException: Arguments must be positive.

Finally Block

The following example demonstrates how to use the finally block to execute code regardless of whether an exception is thrown.

main.php
<?php

declare(strict_types=1);

function divide(int $a, int $b): int
{
    if ($b === 0) {
        throw new Exception("Division by zero is not allowed.");
    }
    return intdiv($a, $b);
}

try {
    echo divide(10, 0);
} catch (Exception $e) {
    echo "Caught exception: " . $e->getMessage();
} finally {
    echo "\nFinally block executed.";
}

In this program, the finally block is executed after the try-catch block, regardless of whether an exception is thrown.

$ php main.php
Caught exception: Division by zero is not allowed.
Finally block executed.

Rethrowing Exceptions

The following example demonstrates how to rethrow an exception after catching it.

main.php
<?php

declare(strict_types=1);

function divide(int $a, int $b): int
{
    if ($b === 0) {
        throw new Exception("Division by zero is not allowed.");
    }
    return intdiv($a, $b);
}

try {
    try {
        echo divide(10, 0);
    } catch (Exception $e) {
        echo "Caught exception: " . $e->getMessage();
        throw $e; // Rethrow the exception
    }
} catch (Exception $e) {
    echo "\nRethrown exception: " . $e->getMessage();
}

In this program, the exception is caught, logged, and then rethrown to be handled by an outer catch block.

$ php main.php
Caught exception: Division by zero is not allowed.
Rethrown exception: Division by zero is not allowed.

Exception Hierarchy

The following example demonstrates how to catch exceptions based on their hierarchy.

main.php
<?php

declare(strict_types=1);

class CustomException extends Exception {}
class AnotherException extends CustomException {}

function testException()
{
    throw new AnotherException("Another exception occurred.");
}

try {
    testException();
} catch (AnotherException $e) {
    echo "Caught AnotherException: " . $e->getMessage();
} catch (CustomException $e) {
    echo "Caught CustomException: " . $e->getMessage();
} catch (Exception $e) {
    echo "Caught Exception: " . $e->getMessage();
}

In this program, the AnotherException is caught first because it is more specific than CustomException and Exception.

$ php main.php
Caught AnotherException: Another exception occurred.

Exceptions and Errors

Since PHP 7, most engine failures are reported as Error objects instead of fatal errors. Both Exception and Error implement the Throwable interface, so catching Throwable handles both.

throwable.php
<?php

declare(strict_types=1);

function half(int $n): int
{
    return intdiv($n, 2);
}

$tests = [
    fn() => half(10),
    fn() => half("10"),
    fn() => intdiv(1, 0),
    fn() => 1 % 0,
];

foreach ($tests as $test) {
    try {
        echo $test(), "\n";
    } catch (TypeError $e) {
        echo "TypeError: ", $e->getMessage(), "\n";
    } catch (DivisionByZeroError $e) {
        echo "DivisionByZeroError: ", $e->getMessage(), "\n";
    }
}

The hierarchy is Throwable with the two branches Exception and Error. Passing a string to an int parameter under strict_types raises a TypeError; integer division by zero (intdiv and the modulo operator) raises a DivisionByZeroError. Both are Error subclasses, so a catch (Exception $e) block would not catch them.

$ php throwable.php
5
TypeError: half(): Argument #1 ($n) must be of type int, string given, called in /home/user/throwable.php on line 12
DivisionByZeroError: Division by zero
DivisionByZeroError: Modulo by zero

Catching multiple types

A single catch block can handle several exception types separated by the | character.

multicatch.php
<?php

function parse(string $json): array
{
    $data = json_decode($json, true, flags: JSON_THROW_ON_ERROR);

    if (!is_array($data)) {
        throw new UnexpectedValueException("JSON must contain an object.");
    }

    return $data;
}

foreach (['{"a": 1}', '{bad', '12'] as $input) {
    try {
        print_r(parse($input));
    } catch (JsonException | UnexpectedValueException $e) {
        echo get_class($e), ": ", $e->getMessage(), "\n";
    }
}

The parse function can throw a JsonException (thanks to the JSON_THROW_ON_ERROR flag) or an UnexpectedValueException. Both are handled in the same block. The caught object's class is printed with get_class.

$ php multicatch.php
Array
(
    [a] => 1
)
JsonException: Syntax error
UnexpectedValueException: JSON must contain an object.

Exception chaining

An exception can wrap the one that caused it. The previous exception is passed as the third argument of the constructor and is available through getPrevious.

chaining.php
<?php

class ConfigException extends RuntimeException {}

function loadConfig(string $path): array
{
    try {
        $content = file_get_contents($path)
            ?: throw new RuntimeException("Cannot read $path");
        return json_decode($content, true, flags: JSON_THROW_ON_ERROR);
    } catch (Throwable $e) {
        throw new ConfigException("Configuration failed", 1, $e);
    }
}

try {
    loadConfig('/no/such/file.json');
} catch (ConfigException $e) {
    echo $e->getMessage(), " (code ", $e->getCode(), ")\n";
    echo "Caused by: ", $e->getPrevious()->getMessage(), "\n";
}

The low-level failure is wrapped in a domain-specific ConfigException. The caller deals with one meaningful exception type, while the original cause stays available for logging. Note that throw is an expression since PHP 8, so it can be used on the right side of the ?: operator. (The file_get_contents call also emits a warning here, which is not shown.)

$ php chaining.php
Configuration failed (code 1)
Caused by: Cannot read /no/such/file.json

Global exception handler

The set_exception_handler function registers a callback for exceptions that are not caught anywhere.

handler.php
<?php

set_exception_handler(function (Throwable $e) {
    echo "Unhandled ", get_class($e), ": ", $e->getMessage(), "\n";
    echo "Thrown at line ", $e->getLine(), "\n";
});

echo "Start\n";
throw new LogicException("Something went wrong");
echo "Never reached\n";

The handler receives the uncaught exception and runs instead of PHP's default fatal error message. After it returns, the script terminates, so the last echo never executes. The getLine method returns the line where the exception was created.

$ php handler.php
Start
Unhandled LogicException: Something went wrong
Thrown at line 9

Source

PHP Exceptions - Documentation

In this article, we have shown how to use exceptions in PHP for error handling. Exceptions are a powerful tool for managing errors and maintaining control over the program flow.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all PHP tutorials.