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
EasyLet'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 baseOrderExceptionclass that extendsException. 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 anOutOfStockExceptionclass that extendsOrderException. 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 methodgetProductName(): stringthat returns the product name.InvalidQuantityException.php— Create anInvalidQuantityExceptionclass that extendsOrderException. 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 methodgetQuantity(): intthat 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
InvalidQuantityExceptionand print"Quantity error: [message] (value: [quantity])"using the exception's message andgetQuantity()method. Then catchOutOfStockExceptionand print"Stock error: [message] (product: [productName])"using the exception's message andgetProductName()method.- If the quantity is less than 1, throw an
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()
?>This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe $this KeywordMethodsPropertiesConstructor (__construct)Destructor (__destruct)Recap - Simple Calculator4Inheritance
Basic InheritanceThe parent:: KeywordMethod OverridingThe final KeywordAbstract ClassesRecap - Employee Hierarchy7Encapsulation
Public, Protected, PrivateAccess Modifiers in DepthGetters and SettersInformation HidingConstructor Promotion (8.0)Recap - Student Records System2Namespaces & Autoloading
Introduction to NamespacesThe use KeywordPSR-4 Autoloading StandardComposer AutoloaderRecap - Organized Project5Interfaces & Contracts
Introduction to InterfacesImplementing InterfacesMultiple Interface ImplementInterface vs Abstract ClassType Hinting with InterfacesRecap - Shape Calculator8Magic Methods
Magic Methods Introduction__toString & __debugInfo__get, __set, __isset, __unset__call & __callStatic__clone & Object Cloning__serialize & __unserializeRecap - Custom Collection11Type System & Error Handling
Type DeclarationsNullable TypesUnion & Intersection TypesException ClassesCustom Exception HierarchyTry, Catch, FinallyRecap - Form Validator14Project: Library Management
Project OverviewBook and User Classes3Class Properties
Instance vs Static PropertiesConstants in ClassesStatic Methods & PropertiesPrivate & Protected PropertiesReadonly Properties (PHP 8.1)Recap - Bank Account Manager6Polymorphism
Method Overriding RevisitedPolymorphism via InterfacesType Hinting & Union TypesLate Static BindingRecap - Payment Processor9Traits
Introduction to TraitsUsing Multiple TraitsTrait Conflict ResolutionAbstract Methods in TraitsTraits vs Inheritance12Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern