Иерархия пользовательских исключений
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 67 из 91.
Хотя встроенные классы исключений PHP охватывают стандартные сценарии, реальным приложениям часто требуются более специфические типы ошибок. Создание пользовательской иерархии исключений позволяет определять исключения, которые соответствуют вашей предметной области, и перехватывать их на разных уровнях детализации.
Чтобы создать пользовательское исключение, просто расширьте класс Exception или один из его подклассов:
<?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;
}
}
Обратите внимание, как InsufficientFundsException наследует PaymentException, а не Exception напрямую. Это создает иерархию, в которой вы можете перехватывать все исключения, связанные с платежами, одним блоком catch или обрабатывать конкретные исключения по отдельности.
Пользовательские исключения могут включать дополнительные свойства и методы, относящиеся к ошибке. Здесь getShortfall() предоставляет полезную информацию, которую не могло бы предложить стандартное исключение:
<?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;
}
}
Хорошо спроектированная иерархия исключений делает ваш код более удобным в сопровождении. При перехвате исключений вы можете действовать настолько широко или специфично, насколько это необходимо, а сами объекты исключений несут в себе значимый контекст о том, что пошло не так.
Задание
ЛегкоДавайте создадим систему обработки заказов с иерархией пользовательских исключений, которая обрабатывает различные типы сбоев при заказе. Вы создадите исключения, которые несут значимый контекст о том, что пошло не так, делая обработку ошибок более точной и информативной.
Вы распределите свой код по четырем файлам:
OrderException.php— Создайте базовый классOrderException, который расширяетException. Он служит родителем для всех исключений, связанных с заказами, позволяя при необходимости перехватывать любую проблему с заказом одним блоком catch.OutOfStockException.php— Создайте классOutOfStockException, который расширяетOrderException. Это исключение должно принимать название продукта и запрошенное количество в своем конструкторе. Используйте продвижение свойств в конструкторе (constructor promotion) для этих private свойств. Вызовите родительский конструктор с сообщением в формате"[productName] is out of stock (requested: [quantity])". Добавьте методgetProductName(): string, который возвращает название продукта.InvalidQuantityException.php— Создайте классInvalidQuantityException, который расширяетOrderException. Это исключение должно принимать значение количества в своем конструкторе, используя продвижение свойств в конструкторе как private свойство. Вызовите родительский конструктор с сообщением"Invalid quantity: [quantity]". Добавьте методgetQuantity(): int, который возвращает значение количества.main.php— Включите все три файла исключений. Вы получите два входных значения: название продукта и количество (в виде строки, которую необходимо преобразовать в целое число).В зависимости от значения количества, выбросьте соответствующее исключение:
- Если количество меньше 1, выбросьте
InvalidQuantityException - Если количество больше 100, выбросьте
OutOfStockException(имитируя нехватку товара на складе) - В противном случае выведите
"Order placed: [quantity]x [productName]"
Перехватывайте исключения, используя иерархию. Сначала перехватите
InvalidQuantityExceptionи выведите"Quantity error: [message] (value: [quantity])", используя сообщение исключения и методgetQuantity(). Затем перехватитеOutOfStockExceptionи выведите"Stock error: [message] (product: [productName])", используя сообщение исключения и методgetProductName().- Если количество меньше 1, выбросьте
Эта иерархия позволяет вам обрабатывать конкретные исключения индивидуально, сохраняя при этом возможность перехватывать все проблемы, связанные с заказами, через базовый класс OrderException. Пользовательские методы в каждом исключении предоставляют контекст, который не могло бы предложить обычное исключение.
Шпаргалка
Создавайте пользовательские исключения, расширяя класс Exception или его подклассы:
<?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;
}
}
Пользовательские исключения могут образовывать иерархию, расширяя другие пользовательские исключения. Это позволяет перехватывать исключения на разных уровнях специфичности — перехватывать все исключения, связанные с оплатой, с помощью PaymentException или обрабатывать конкретные, такие как InsufficientFundsException, по отдельности.
Пользовательские исключения могут включать дополнительные свойства и методы для предоставления контекста ошибки:
<?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;
}
}
Используйте продвижение параметров конструктора (constructor promotion) для определения свойств непосредственно в параметрах конструктора. Вызывайте parent::__construct(), чтобы установить сообщение об исключении.
Попробуйте сами
<?php
// Подключить все файлы исключений
require_once 'OrderException.php';
require_once 'OutOfStockException.php';
require_once 'InvalidQuantityException.php';
// Прочитать ввод
$productName = trim(fgets(STDIN));
$quantity = intval(trim(fgets(STDIN)));
// TODO: Использовать try-catch для обработки заказа
// Внутри блока try:
// - Если quantity < 1, выбросить InvalidQuantityException
// - Если quantity > 100, выбросить OutOfStockException
// - В противном случае вывести "Order placed: [quantity]x [productName]"
//
// Сначала поймать InvalidQuantityException:
// - Вывести "Quantity error: [message] (value: [quantity])", используя getMessage() и getQuantity()
//
// Поймать OutOfStockException:
// - Вывести "Stock error: [message] (product: [productName])", используя getMessage() и getProductName()
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Итоги — Простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыИтоги — Иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateПодробный разбор модификаторов доступаГеттеры и сеттерыСокрытие данныхConstructor Promotion (8.0)Итоги — Система учета студентов10Продвинутые концепции ООП
Композиция против наследованияВнедрение зависимостейАнонимные классыПеречисления (PHP 8.1)Файберы (PHP 8.1)Глубокое погружение в клонирование объектовГенераторы и итераторы13Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий2Пространства имен и автозагрузка
Введение в пространства именКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerИтоги: структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс против абстрактного классаType Hinting с интерфейсамиИтоги — Калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeИтоги — Кастомная коллекция11Система типов и обработка ошибок
Объявление типовNullable-типыТипы объединения и пересеченияКлассы исключенийИерархия пользовательских исключенийTry, Catch, FinallyИтоги — Валидатор форм14Проект: Управление библиотекой
Обзор проектаКлассы Book и User3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищенные свойстваСвойства Readonly (PHP 8.1)Итоги — Менеджер банковского счета6Полиморфизм
Снова о переопределении методовПолиморфизм через интерфейсыType Hinting и Union TypesПозднее статическое связываниеИтоги — Payment Processor9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy