Menu
Coddy logo textTech

Custom Exception Hierarchy

Part of the Object Oriented Programming section of Coddy's PHP journey — lesson 67 of 91.

While PHP's built-in exception classes cover common scenarios, real applications often need more specific error types. Creating a custom exception hierarchy lets you define exceptions that match your domain and catch them at different levels of specificity.

To create a custom exception, simply extend the Exception class or one of its subclasses:

<?php
class PaymentException extends Exception {}

class InsufficientFundsException extends PaymentException {
    public function __construct(
        private float $required,
        private float $available
    ) {
        parent::__construct("Need $required but only $available available");
    }
    
    public function getShortfall(): float {
        return $this->required - $this->available;
    }
}

Notice how InsufficientFundsException extends PaymentException, not Exception directly. This creates a hierarchy where you can catch all payment-related exceptions with one catch block, or handle specific ones individually.

Custom exceptions can include additional properties and methods relevant to the error. Here, getShortfall() provides useful information that a generic exception couldn't offer:

<?php
class Wallet {
    public function __construct(private float $balance) {}
    
    public function pay(float $amount): void {
        if ($amount > $this->balance) {
            throw new InsufficientFundsException($amount, $this->balance);
        }
        $this->balance -= $amount;
    }
}

A well-designed exception hierarchy makes your code more maintainable. When catching exceptions, you can be as broad or specific as needed, and the exception objects themselves carry meaningful context about what went wrong.

challenge icon

Challenge

Easy

Let's build an order processing system with a custom exception hierarchy that handles different types of order failures. You'll create exceptions that carry meaningful context about what went wrong, making error handling more precise and informative.

You'll organize your code across four files:

  • OrderException.php — Create a base OrderException class that extends Exception. This serves as the parent for all order-related exceptions, allowing you to catch any order problem with a single catch block when needed.
  • OutOfStockException.php — Create an OutOfStockException class that extends OrderException. This exception should accept a product name and the requested quantity in its constructor. Use constructor promotion for these private properties. Call the parent constructor with a message in the format "[productName] is out of stock (requested: [quantity])". Add a method getProductName(): string that returns the product name.
  • InvalidQuantityException.php — Create an InvalidQuantityException class that extends OrderException. This exception should accept a quantity value in its constructor using constructor promotion as a private property. Call the parent constructor with the message "Invalid quantity: [quantity]". Add a method getQuantity(): int that returns the quantity value.
  • main.php — Include all three exception files. You'll receive two inputs: a product name and a quantity (as a string that needs to be converted to an integer).

    Based on the quantity value, throw the appropriate exception:

    • If the quantity is less than 1, throw an InvalidQuantityException
    • If the quantity is greater than 100, throw an OutOfStockException (simulating insufficient stock)
    • Otherwise, print "Order placed: [quantity]x [productName]"

    Catch exceptions using the hierarchy. First catch InvalidQuantityException and print "Quantity error: [message] (value: [quantity])" using the exception's message and getQuantity() method. Then catch OutOfStockException and print "Stock error: [message] (product: [productName])" using the exception's message and getProductName() method.

This hierarchy lets you handle specific exceptions individually while still having the option to catch all order-related problems through the base OrderException class. The custom methods on each exception provide context that a generic exception couldn't offer.

Cheat sheet

Create custom exceptions by extending the Exception class or its subclasses:

<?php
class PaymentException extends Exception {}

class InsufficientFundsException extends PaymentException {
    public function __construct(
        private float $required,
        private float $available
    ) {
        parent::__construct("Need $required but only $available available");
    }
    
    public function getShortfall(): float {
        return $this->required - $this->available;
    }
}

Custom exceptions can form a hierarchy by extending other custom exceptions. This allows catching exceptions at different levels of specificity—catch all payment-related exceptions with PaymentException, or handle specific ones like InsufficientFundsException individually.

Custom exceptions can include additional properties and methods to provide context about the error:

<?php
class Wallet {
    public function __construct(private float $balance) {}
    
    public function pay(float $amount): void {
        if ($amount > $this->balance) {
            throw new InsufficientFundsException($amount, $this->balance);
        }
        $this->balance -= $amount;
    }
}

Use constructor promotion to define properties directly in the constructor parameters. Call parent::__construct() to set the exception message.

Try it yourself

<?php
// Include all exception files
require_once 'OrderException.php';
require_once 'OutOfStockException.php';
require_once 'InvalidQuantityException.php';

// Read input
$productName = trim(fgets(STDIN));
$quantity = intval(trim(fgets(STDIN)));

// TODO: Use try-catch to handle the order processing
// Inside try block:
//   - If quantity < 1, throw InvalidQuantityException
//   - If quantity > 100, throw OutOfStockException
//   - Otherwise, print "Order placed: [quantity]x [productName]"
//
// Catch InvalidQuantityException first:
//   - Print "Quantity error: [message] (value: [quantity])" using getMessage() and getQuantity()
//
// Catch OutOfStockException:
//   - Print "Stock error: [message] (product: [productName])" using getMessage() and getProductName()

?>
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming