PHP Exceptions & Errors

An exception signals "this cannot continue" and unwinds the stack until something catches it.

PHP
<?php
function divide(float $a, float $b): float
{
    if ($b === 0.0) {
        throw new InvalidArgumentException("Cannot divide by zero");
    }
    return $a / $b;
}

try {
    echo divide(10, 0);
} catch (InvalidArgumentException $e) {
    echo "Error: " . $e->getMessage();
} finally {
    echo "This always runs";
}

Catching several types

PHP
<?php
try {
    riskyOperation();
} catch (PDOException $e) {
    // database problem
} catch (JsonException | RuntimeException $e) {
    // either of these
} catch (Throwable $e) {
    // absolutely anything else, including fatal Errors
}

Throwable is the root of both Exception (things you can anticipate) and Error (engine-level problems like TypeError). Catch Throwable only at the outermost layer, where you log and show a friendly page.

Useful methods

PHP
<?php
  $e->getMessage();
  $e->getCode();
  $e->getFile();
  $e->getLine();
  $e->getTraceAsString();
  $e->getPrevious();   // the exception this one wrapped

Custom exceptions

Give distinct failures distinct types so callers can react precisely:

PHP
<?php
class OutOfStockException extends RuntimeException {}

if ($product['stock'] < $qty) {
    throw new OutOfStockException("Only {$product['stock']} left");
}

Never leak details to visitors

Stack traces and file paths help attackers. Log them; show a generic message.

PHP
<?php
try {
    processOrder();
} catch (Throwable $e) {
    error_log($e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
    http_response_code(500);
    echo "Something went wrong. Please try again.";
}

Production settings

PHP
<?php
  ini_set('display_errors', '0');   // never show errors to visitors
  ini_set('log_errors', '1');
  error_reporting(E_ALL);            // but log everything

On your own machine, flip display_errors to 1 — seeing the error immediately is worth far more than a tidy page while you're developing.