Пользовательская иерархия исключений
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy. Урок 67 из 91.
Хотя встроенные классы исключений PHP охватывают распространённые сценарии, реальным приложениям часто требуются более специфичные типы ошибок. Создание иерархии пользовательских исключений позволяет определять исключения, соответствующие вашей предметной области, и выполнять catch на разных уровнях специфичности.
Чтобы создать пользовательское исключение, просто расширьте класс 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;
}
}
Хорошо спроектированная иерархия exceptions делает ваш код более удобным для сопровождения. При перехвате exceptions вы можете выбрать настолько общий или конкретный подход, насколько это необходимо, а сами объекты exceptions содержат значимый контекст о том, что пошло не так.
Задание
ЛегкоДавайте создадим систему обработки заказов с иерархией пользовательских исключений, которая обрабатывает различные типы сбоев заказа. Вы создадите исключения, содержащие содержательный контекст о том, что пошло не так, благодаря чему обработка ошибок станет более точной и информативной.
Вы организуете код в four файлах:
OrderException.php: Создайте базовый классOrderException, который extendsException. Он служит parent для всех исключений, связанных с заказами, позволяя при необходимости перехватывать любую проблему заказа с помощью одного блокаcatch.OutOfStockException.php: Создайте классOutOfStockException, который extendsOrderException. Это исключение должно принимать в своём constructor название продукта и запрошенное количество. Используйте продвижение свойств constructor для этих private-свойств. Вызовите parent constructor с сообщением в формате"[productName] is out of stock (requested: [quantity])". Добавьте methodgetProductName(): string, который возвращает название продукта.InvalidQuantityException.php: Создайте классInvalidQuantityException, который extendsOrderException. Это исключение должно принимать значение количества в своём constructor, используя продвижение constructor как private-свойство. Вызовите parent constructor с сообщением"Invalid quantity: [quantity]". Добавьте methodgetQuantity(): int, который возвращает значение количества.main.php: Подключите all three файла исключений. Вы получите два входных значения: название продукта и количество (в виде строки, которую необходимо преобразовать в целое число).В зависимости от значения количества выбросьте соответствующее исключение:
- Если количество меньше 1, выбросьте
InvalidQuantityException - Если количество больше 100, выбросьте
OutOfStockException(имитируя недостаток товара на складе) - Otherwise, выведите
"Order placed: [quantity]x [productName]"
Перехватывайте исключения с использованием иерархии. Сначала перехватите
InvalidQuantityExceptionи выведите"Quantity error: [message] (value: [quantity])", используя message исключения и его methodgetQuantity(). Затем перехватитеOutOfStockExceptionи выведите"Stock error: [message] (product: [productName])", используя message исключения и его methodgetProductName().- Если количество меньше 1, выбросьте
Эта иерархия позволяет обрабатывать конкретные исключения по отдельности, сохраняя при этом возможность перехватывать все проблемы, связанные с заказами, через базовый класс OrderException. Пользовательские методы каждого исключения предоставляют контекст, который не могло бы предложить общее исключение.
Попробуйте сами
<?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, throw InvalidQuantityException
// - Если quantity > 100, throw OutOfStockException
// - Иначе, print "Order placed: [quantity]x [productName]"
//
// Сначала catch InvalidQuantityException:
// - Print "Quantity error: [message] (value: [quantity])" используя getMessage() и getQuantity()
//
// Catch OutOfStockException:
// - Print "Stock error: [message] (product: [productName])" используя getMessage() и getProductName()
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Повторение — простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыПовторение — иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateМодификаторы доступа подробноГеттеры и сеттерыСокрытие информацииПродвижение конструктора (8.0)Повторение — система учёта студентов2Пространства имён и автозагрузка
Введение в пространства имёнКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerПовторение — структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс и абстрактный классАннотация типов с интерфейсамиПовторение — калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeПовторение — пользовательская коллекция11Система типов и обработка ошибок
Объявления типовNullable-типыОбъединение и пересечение типовКлассы исключенийПользовательская иерархия исключенийTry, Catch, FinallyПовторение — валидатор формы3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищённые свойстваСвойства только для чтения (PHP 8.1)Повторение — менеджер банковского счёта6Полиморфизм
Повторение переопределения методовПолиморфизм через интерфейсыАннотации типов и объединённые типыПозднее статическое связываниеПовторение — обработчик платежей9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования, часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн StrategyПотренируйтесь самостоятельно: Онлайн-компилятор PHP