Menu
Coddy logo textTech

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

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

Хорошо спроектированная иерархия исключений делает ваш код более удобным в сопровождении. При перехвате исключений вы можете действовать настолько широко или специфично, насколько это необходимо, а сами объекты исключений несут в себе значимый контекст о том, что пошло не так.

challenge icon

Задание

Легко

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

Вы распределите свой код по четырем файлам:

  • 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().

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

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

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

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