PHP Exceptions

Su Subhalakshmi Chandran Updated 17 Oct 2026
8 min read ·Lesson 88 of 109
In this post

What Is an Exception? In simple terms: An exception is a way for code to say "something went wrong, and I cannot continue normally". Instead of returning a special value that the caller might forget t

What Is an Exception?

In simple terms: An exception is a way for code to say "something went wrong, and I cannot continue normally". Instead of returning a special value that the caller might forget to check, the code throws an exception, and the nearest code prepared to deal with it catches it.

Technically: An exception is an object of a class that implements the Throwable interface. throw stops the current execution and transfers control to the first matching catch block up the call stack. If no catch matches, the script ends with a fatal "Uncaught exception" error.

Why Exceptions Instead of Return Values?

$user = findUser($id);   // returns false on error... or null... or an empty array?

With return values, every caller must remember to check for every failure value, and errors are easily ignored. Exceptions:

  • cannot be silently ignored — an unhandled exception stops the script with a clear message,
  • separate normal code from error handling,
  • travel up through several function calls to the place that can actually handle the problem,
  • carry information: a message, a code, the location, a stack trace, and any custom data.

Throwing and Catching

Syntax

try {
    // code that may throw
} catch (SomeException $e) {
    // handle the problem
} finally {
    // always runs (optional)
}

Example

<?php
function divide(int $a, int $b): float
{
    if ($b === 0) {
        throw new InvalidArgumentException('Cannot divide by zero.');
    }

    return $a / $b;
}

try {
    echo divide(10, 4) . "\n";
    echo divide(5, 0) . "\n";
    echo "This line is never reached\n";
} catch (InvalidArgumentException $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

echo "The program continues\n";

Output

2.5
Error: Cannot divide by zero.
The program continues

How It Works

  1. divide(10, 4) returns normally.
  2. divide(5, 0) executes throw. The function stops immediately; nothing after throw runs.
  3. PHP looks for a matching catch block. The remaining lines in the try block are skipped.
  4. The catch block receives the exception object in $e and handles it.
  5. Execution continues after the try/catch statement.

The Exception Hierarchy

Throwable (interface)
├── Error                         ← problems detected by the PHP engine
│   ├── TypeError
│   │   └── ArgumentCountError
│   ├── ValueError
│   ├── ArithmeticError
│   │   └── DivisionByZeroError
│   ├── UnhandledMatchError
│   └── ...
└── Exception                     ← problems in application logic
    ├── LogicException
    │   ├── InvalidArgumentException
    │   ├── DomainException
    │   ├── LengthException
    │   └── OutOfRangeException
    ├── RuntimeException
    │   ├── OutOfBoundsException
    │   ├── UnexpectedValueException
    │   ├── RangeException
    │   └── ...
    ├── JsonException
    └── ...
BranchMeaningExamples
ErrorProgramming errors and engine failuresWrong argument types, division by zero, calling undefined functions
LogicExceptionA bug in the code that should be fixedInvalid arguments passed by a programmer
RuntimeExceptionProblems that occur only at runtimeMissing files, network failures, unavailable services

Catching Engine Errors

<?php
$operations = [
    'intdiv' => fn() => intdiv(10, 0),
    'modulo' => fn() => 10 % 0,
    'type' => fn() => str_repeat('x', 'three'),
    'value' => fn() => str_repeat('x', -1),
];

foreach ($operations as $name => $operation) {
    try {
        $operation();
    } catch (Throwable $e) {
        echo str_pad($name, 7) . get_class($e) . ": " . $e->getMessage() . "\n";
    }
}

Output

intdiv DivisionByZeroError: Division by zero
modulo DivisionByZeroError: Modulo by zero
type   TypeError: str_repeat(): Argument #2 ($times) must be of type int, string given
value  ValueError: str_repeat(): Argument #2 ($times) must be greater than or equal to 0

catch (Throwable $e) catches everything — both Error and Exception. Use it at the top level of an application (to log and show an error page), not around ordinary code, where it could hide bugs.


Multiple and Combined catch Blocks

<?php
function loadSettings(string $json): array
{
    if ($json === '') {
        throw new LengthException('Settings are empty.');
    }

    return json_decode($json, true, flags: JSON_THROW_ON_ERROR);
}

foreach (['{"theme":"dark"}', '', '{bad json}'] as $input) {
    try {
        $settings = loadSettings($input);
        echo "Theme: {$settings['theme']}\n";
    } catch (LengthException | JsonException $e) {
        echo "Invalid settings (" . get_class($e) . "): " . $e->getMessage() . "\n";
    } catch (Exception $e) {
        echo "Unexpected problem: " . $e->getMessage() . "\n";
    }
}

Output

Theme: dark
Invalid settings (LengthException): Settings are empty.
Invalid settings (JsonException): Syntax error
  • catch (A | B $e) (PHP 7.1+) handles several exception types with the same code.
  • catch blocks are checked in order, and the first matching one runs. Put specific types before general ones such as Exception; otherwise the general block catches everything first.
  • Since PHP 8.0, the variable can be omitted when it is not needed: catch (JsonException) { ... }.

finally: Code That Always Runs

<?php
function processFile(string $path): string
{
    $handle = fopen('php://memory', 'r+');
    echo "File opened\n";

    try {
        if ($path === 'bad') {
            throw new RuntimeException('Processing failed');
        }
        return 'Processed';
    } finally {
        fclose($handle);
        echo "File closed\n";
    }
}

echo processFile('good') . "\n";

try {
    processFile('bad');
} catch (RuntimeException $e) {
    echo "Caught: " . $e->getMessage() . "\n";
}

Output

File opened
File closed
Processed
File opened
File closed
Caught: Processing failed

The finally block runs whether the try block finishes normally, returns, or throws. It is the right place for cleanup: closing files, releasing locks, or resetting state. Note that in the first call, finally runs before the returned value reaches the caller.


Custom Exceptions

Creating your own exception classes lets callers catch specific problems and carry extra information:

<?php
final class InsufficientFundsException extends RuntimeException
{
    public function __construct(
        public readonly float $balance,
        public readonly float $requested,
    ) {
        parent::__construct(sprintf(
            'Cannot withdraw ₹%s: balance is ₹%s.',
            number_format($requested, 2),
            number_format($balance, 2)
        ));
    }

    public function shortfall(): float
    {
        return $this->requested - $this->balance;
    }
}

function withdraw(float $balance, float $amount): float
{
    if ($amount > $balance) {
        throw new InsufficientFundsException($balance, $amount);
    }

    return $balance - $amount;
}

try {
    withdraw(1000, 1500);
} catch (InsufficientFundsException $e) {
    echo $e->getMessage() . "\n";
    echo "Please add ₹" . number_format($e->shortfall(), 2) . " to continue.\n";
}

Output

Cannot withdraw ₹1,500.00: balance is ₹1,000.00.
Please add ₹500.00 to continue.

Guidelines for Custom Exceptions

  • Extend the most fitting built-in class (RuntimeException, InvalidArgumentException, DomainException).
  • Name them after the problem: InsufficientFundsException, OrderNotFoundException.
  • Add properties or methods for data that handlers need (here, the balance and the shortfall).
  • Libraries often define a common base exception (for example, PaymentException), so users can catch all of the library's exceptions at once.

Exception Chaining: Keeping the Original Cause

When converting a low-level exception into a higher-level one, pass the original as the previous exception:

<?php
final class SettingsUnavailableException extends RuntimeException {}

function loadUserSettings(string $json): array
{
    try {
        return json_decode($json, true, flags: JSON_THROW_ON_ERROR);
    } catch (JsonException $e) {
        throw new SettingsUnavailableException('User settings could not be loaded.', 0, $e);
    }
}

try {
    loadUserSettings('{broken');
} catch (SettingsUnavailableException $e) {
    echo $e->getMessage() . "\n";
    echo "Caused by: " . get_class($e->getPrevious()) . ": " . $e->getPrevious()->getMessage() . "\n";
}

Output

User settings could not be loaded.
Caused by: JsonException: Syntax error

Callers deal with a meaningful, domain-level exception, while logs still contain the technical root cause. Logging libraries and error pages display the whole chain.


Useful Exception Methods

MethodReturns
getMessage()The message
getCode()The numeric code (default 0)
getPrevious()The previous exception, or null
getFile() / getLine()Where the exception was created
getTrace() / getTraceAsString()The call stack at that point

throw as an Expression (PHP 8.0+)

throw can be used where an expression is expected, for example with ??, ?:, and arrow functions:

<?php
$users = [7 => 'Asha', 9 => 'Bala'];

function userName(array $users, int $id): string
{
    return $users[$id] ?? throw new OutOfBoundsException("User $id not found.");
}

echo userName($users, 7) . "\n";

try {
    echo userName($users, 42) . "\n";
} catch (OutOfBoundsException $e) {
    echo $e->getMessage() . "\n";
}

Output

Asha
User 42 not found.

Where to Catch Exceptions

  • Throw where a problem is detected.
  • Catch only where you can do something useful: retry, use a fallback, show a helpful message, or convert the exception into a more meaningful one.
  • Let other exceptions propagate to a global handler that logs them and shows a generic error page (see Error Handling and Custom Error Handler).

An empty catch block — catch (Exception $e) {} — hides problems and makes debugging very difficult. If an exception is truly ignorable, add a comment explaining why.


Common Mistakes

Catching Too Broadly

Effect: catch (Throwable) around ordinary code hides programming errors.

Fix: Catch specific types; handle Throwable only at the top level.

Wrong Order of catch Blocks

Effect: A general catch (Exception) placed first prevents specific blocks from running.

Fix: Order catch blocks from specific to general.

Swallowing Exceptions

Effect: Errors disappear silently.

Fix: Handle, log, or rethrow — never ignore without a reason.

Losing the Original Exception

Effect: Logs show only the high-level message, not the root cause.

Fix: Pass the original as $previous.

Using Exceptions for Normal Control Flow

Effect: Hard-to-follow code and unnecessary overhead.

Fix: Use exceptions for exceptional situations; use return values (for example, null for "not found" where that is a normal outcome).


Best Practices

  • Throw exceptions for situations a function cannot handle itself.
  • Create meaningful custom exception classes that carry useful data.
  • Catch specific exceptions where you can handle them; let others propagate.
  • Use finally for cleanup and exception chaining to preserve root causes.
  • Install a global handler that logs uncaught exceptions and shows a safe error page.

Official reference: https://www.php.net/manual/en/language.exceptions.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators
Ask about this post
AI Ask about this post

Ask questions about PHP Exceptions and get answers drawn from it.

Signed-in readers only.