PHP Exceptions and Error Handling

Handle problems gracefully with try, catch and finally, throw your own exceptions and configure error reporting safely.

Lesson 20 of 86Advanced17 min read

Things go wrong on every website sooner or later: the database is unreachable, a file is missing, an API sends back nonsense, or a visitor submits something your code didn't expect. How a site behaves at that moment matters. A raw error message full of file paths looks unprofessional and can reveal information to attackers; a blank page leaves visitors confused.

Modern PHP reports most serious problems as exceptions: objects that are thrown when something fails and can be caught by code that knows how to respond. With try and catch you can show a friendly message, try a fallback, log the details for yourself, or undo a half-finished database change.

In this lesson you'll catch exceptions, run clean-up code with finally, throw exceptions of your own, create custom exception classes for your application, and set up error reporting so that you see every problem during development while visitors never see technical details.

try, catch and finally

PHP
try {
    $data = json_decode($input, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    echo "Sorry, that data couldn't be read.";
} finally {
    echo "Done.";
}
  • Code in try runs normally. If an exception is thrown, PHP jumps to the first catch whose type matches.
  • $e->getMessage() gives the exception's message; $e->getCode(), getFile() and getLine() give more detail.
  • Catch several types in one block with a pipe: catch (JsonException | ValueError $e).
  • finally runs whether or not an exception happened, which makes it right for clean-up.

Throwing exceptions

When your own function receives something it can't work with, throw an exception instead of returning a strange value:

PHP
function setQuantity(int $qty): void {
    if ($qty < 1) {
        throw new InvalidArgumentException('Quantity must be at least 1');
    }
}

PHP has built-in exception classes for common situations: InvalidArgumentException for bad arguments, RuntimeException for problems found while running, LogicException for programming mistakes, and DomainException and OutOfRangeException for values outside what makes sense. Choosing a specific class lets callers catch exactly the problems they can handle.

Custom exceptions and Throwable

For situations specific to your application, create your own exception class:

PHP
class OutOfStockException extends RuntimeException {}

Callers can then catch (OutOfStockException $e) without accidentally catching unrelated errors.

PHP has two families of throwable objects: Exception, for problems your code expects and might handle, and Error, for serious problems such as calling an undefined function or a TypeError. Both implement the Throwable interface. A top-level handler that catches Throwable can log anything that escapes, but everyday code should catch specific exceptions.

Warnings, error reporting and logging

Not every problem is an exception. Older functions emit warnings and notices, such as reading an undefined array key. Control what PHP shows with these settings, usually in php.ini:

SettingDevelopmentLive site
error_reportingE_ALLE_ALL
display_errorsOn, so you see problemsOff, so visitors never do
log_errorsOnOn, with error_log pointing to a file

On a live site, log the technical details with error_log($e->getMessage()) and show visitors a friendly page, often with http_response_code(500).

PHP Exceptions Syntax

Exception syntax

try {
    riskyOperation();
} catch (InvalidArgumentException | DomainException $e) {
    echo $e->getMessage();
} catch (Throwable $e) {
    error_log($e->getMessage());
} finally {
    cleanUp();
}

throw new RuntimeException('Something went wrong');
class MyException extends RuntimeException {}

PHP Exceptions Summary

  • Exceptions are thrown when something fails and caught with try / catch; finally always runs.
  • Use specific built-in classes like InvalidArgumentException, or extend them for custom exceptions.
  • Exception and Error both implement Throwable; catch specific types in everyday code.
  • Show errors during development, hide them from visitors on a live site, and always log them.
  • Catch exceptions only where you can respond meaningfully.

PHP Exceptions Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Validating with exceptions

PHP
<?php
function applyDiscount(float $price, int $percent): float {
    if ($percent < 0 || $percent > 90) {
        throw new InvalidArgumentException("Discount must be 0-90%, got $percent%");
    }
    return round($price * (100 - $percent) / 100, 2);
}

foreach ([10, 95, 25] as $percent) {
    try {
        echo "£" . applyDiscount(40, $percent) . "\n";
    } catch (InvalidArgumentException $e) {
        echo "Skipped: " . $e->getMessage() . "\n";
    }
}
Output
£36
Skipped: Discount must be 0-90%, got 95%
£30
Line-by-line explanation
  1. Lines 3 to 5Throw as soon as the input is invalid, with a message that explains the rule.
  2. Lines 9 to 14The loop handles the problem for one value and carries on with the next.
  3. Line 1140 with 10% off is 36, and with 25% off is 30.
Example 2

A custom exception in a shop

PHP
<?php
class OutOfStockException extends RuntimeException {}

$stock = ['pen' => 300, 'lunch box' => 0];

function reserve(array $stock, string $item, int $qty): string {
    $available = $stock[$item] ?? throw new OutOfRangeException("Unknown item: $item");
    if ($available < $qty) {
        throw new OutOfStockException("Only $available $item(s) left");
    }
    return "Reserved $qty $item(s)";
}

foreach ([['pen', 2], ['lunch box', 1], ['sofa', 1]] as [$item, $qty]) {
    try {
        echo reserve($stock, $item, $qty), "\n";
    } catch (OutOfStockException $e) {
        echo "Sold out: ", $e->getMessage(), "\n";
    } catch (OutOfRangeException $e) {
        echo "Error: ", $e->getMessage(), "\n";
    }
}
Output
Reserved 2 pen(s)
Sold out: Only 0 lunch box(s) left
Error: Unknown item: sofa
Line-by-line explanation
  1. Line 2A custom exception for a business situation, based on RuntimeException.
  2. Line 7Since PHP 8, throw can be used as an expression, here after ??.
  3. Lines 17 to 21Each kind of problem gets its own response.
Example 3

finally and a top-level handler

PHP
<?php
set_exception_handler(function (Throwable $e) {
    error_log($e->getMessage());
    http_response_code(500);
    echo "Sorry, something went wrong. Please try again later.\n";
});

function export(): void {
    echo "Opening export file\n";
    try {
        throw new RuntimeException('Disk full');
    } finally {
        echo "Closing export file\n";
    }
}

export();
echo "This line never runs\n";
Output
Opening export file
Closing export file
Sorry, something went wrong. Please try again later.
Line-by-line explanation
  1. Lines 2 to 6A last-resort handler logs the real problem and shows visitors a friendly message.
  2. Lines 12 to 14finally runs before the exception leaves the function, so the file is always closed.
  3. Lines 17 to 18The uncaught exception goes to the handler, and the script stops.

Common PHP Exceptions Mistakes

Showing errors to visitors

Don't
ini_set('display_errors', '1');   // on the live site
Do
ini_set('display_errors', '0');
ini_set('log_errors', '1');

Error messages can reveal file paths, database names and code to attackers.

An empty catch block

Don't
try {
    saveOrder($order);
} catch (Exception $e) { }
Do
try {
    saveOrder($order);
} catch (PDOException $e) {
    error_log($e->getMessage());
    $errors[] = 'We couldn\'t save your order. Please try again.';
}

Returning false instead of throwing

Don't
function loadUser(int $id) {
    // ...
    return false;   // callers forget to check
}
Do
function loadUser(int $id): array {
    // ...
    throw new RuntimeException("User $id not found");
}

An exception can't be ignored by accident, so problems are found sooner.

PHP Exceptions in Real Projects

Error handling in production PHP

Frameworks such as Laravel and Symfony turn uncaught exceptions into tidy error pages and send the details to log files or services like Sentry, which alert developers immediately. Database code throws PDOException, and validation layers throw exceptions that are converted into friendly form messages, so the same patterns you've learned here run on very large sites.

PHP Exceptions FAQ

What is the difference between Exception and Error in PHP?

Both implement the Throwable interface. Exception covers problems your application expects and may handle, such as invalid input or a failed API call. Error covers serious programming problems, such as calling an undefined function or passing the wrong type, including TypeError and DivisionByZeroError.

How do I catch every error at the top level?

Catch Throwable in your front controller or entry script, log the details and show a friendly error page. set_exception_handler() registers a function for anything that isn't caught elsewhere. Frameworks provide their own central handler, so you rarely write this yourself there.

What is exception chaining?

Passing the original exception as the third constructor argument when you throw a new one: throw new ImportException('Could not import products', 0, $e);. The new exception describes the problem in your application's terms, and getPrevious() still gives access to the original cause for logging.

Should validation errors be exceptions?

It depends on the code. For a form, collecting expected mistakes in an errors array is usually simpler, because you want to show them all at once. Inside a domain class or function, throwing InvalidArgumentException for a value that should never arrive keeps invalid data out of the system.

Do warnings count as exceptions?

No. Warnings and notices are reported through PHP's error system and the script continues. You can convert them into exceptions with set_error_handler() that throws an ErrorException, which many frameworks do so that problems such as undefined array keys are never silently ignored.

PHP Exceptions Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the code so a bad argument is reported.

if ($age < 0) {
     new InvalidArgumentException('Age cannot be negative');
}
Predict the outputeasy

What does this output?

try {
    echo "A";
    throw new Exception("B");
    echo "C";
} catch (Exception $e) {
    echo $e->getMessage();
}
Predict the outputmedium

What does this output?

try {
    echo "1";
} finally {
    echo "2";
}
echo "3";
Multiple choicemedium

What should display_errors be on a live website?

Multiple choicehard

Which interface do both Exception and Error implement?

PHP Exceptions Quiz

5 questions to check what you learned.

  1. Which block always runs after try, whether or not an exception happened?

    • catch
    • else
    • finally
    • always
  2. How do you create a custom exception?

    • function MyException() {}
    • new Exception('MyException') only
    • interface MyException {}
    • class MyException extends RuntimeException {}
  3. Which built-in exception fits a function that receives an invalid argument?

    • RuntimeException
    • InvalidArgumentException
    • ErrorException
    • JsonException
  4. What does $e->getMessage() return?

    • The line number
    • The message passed when the exception was created
    • The exception's class name
    • The stack trace
  5. Where is the best place to catch an exception?

    • Around every single line
    • Never; let the site crash
    • Only inside constructors
    • Where the code can respond meaningfully, with a top-level handler for everything else