Menu
Coddy logo textTech

Пользовательская иерархия исключений

Часть раздела Объектно-ориентированное программирование путешествия по 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 содержат значимый контекст о том, что пошло не так.

challenge icon

Задание

Легко

Давайте создадим систему обработки заказов с иерархией пользовательских исключений, которая обрабатывает различные типы сбоев заказа. Вы создадите исключения, содержащие содержательный контекст о том, что пошло не так, благодаря чему обработка ошибок станет более точной и информативной.

Вы организуете код в four файлах:

  • OrderException.php: Создайте базовый класс OrderException, который extends Exception. Он служит parent для всех исключений, связанных с заказами, позволяя при необходимости перехватывать любую проблему заказа с помощью одного блока catch.
  • OutOfStockException.php: Создайте класс OutOfStockException, который extends OrderException. Это исключение должно принимать в своём constructor название продукта и запрошенное количество. Используйте продвижение свойств constructor для этих private-свойств. Вызовите parent constructor с сообщением в формате "[productName] is out of stock (requested: [quantity])". Добавьте method getProductName(): string, который возвращает название продукта.
  • InvalidQuantityException.php: Создайте класс InvalidQuantityException, который extends OrderException. Это исключение должно принимать значение количества в своём constructor, используя продвижение constructor как private-свойство. Вызовите parent constructor с сообщением "Invalid quantity: [quantity]". Добавьте method getQuantity(): int, который возвращает значение количества.
  • main.php: Подключите all three файла исключений. Вы получите два входных значения: название продукта и количество (в виде строки, которую необходимо преобразовать в целое число).

    В зависимости от значения количества выбросьте соответствующее исключение:

    • Если количество меньше 1, выбросьте InvalidQuantityException
    • Если количество больше 100, выбросьте OutOfStockException (имитируя недостаток товара на складе)
    • Otherwise, выведите "Order placed: [quantity]x [productName]"

    Перехватывайте исключения с использованием иерархии. Сначала перехватите InvalidQuantityException и выведите "Quantity error: [message] (value: [quantity])", используя message исключения и его method getQuantity(). Затем перехватите OutOfStockException и выведите "Stock error: [message] (product: [productName])", используя message исключения и его method getProductName().

Эта иерархия позволяет обрабатывать конкретные исключения по отдельности, сохраняя при этом возможность перехватывать все проблемы, связанные с заказами, через базовый класс 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()

?>
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор PHP