← Назад к вопросам

Можно ли делать пользовательские исключения?

2.0 Middle🔥 131 комментариев
#PHP Core

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Можно и нужно: Пользовательские исключения в PHP

Да, не только можно, но и крайне рекомендуется создавать собственные (пользовательские) исключения в PHP. Это фундаментальный принцип хорошей архитектуры и один из ключевых паттернов объектно-ориентированного программирования. Встроенных исключений (RuntimeException, InvalidArgumentException и т.д.) часто недостаточно для точного отражения специфичных для вашего домена (бизнес-логики) ошибок.

Зачем создавать пользовательские исключения?

  1. Семантическая ясность и читаемость кода. Исключение с именем PaymentFailedException или UserNotFoundException понятнее и информативнее, чем общее RuntimeException("Error in payment processing").
  2. Точный контроль обработки. Вы можете поймать (catch) конкретный тип исключения и отреагировать на него уникальным образом, не затрагивая другие ошибки.
  3. Расширение функциональности. В пользовательском классе исключения вы можете добавить дополнительные свойства и методы для хранения контекстной информации (ID заказа, код ошибки API, данные валидации).
  4. Структурирование логики приложения. Исключения становятся частью предметной области, четко разделяя типы сбоев: сетевые, бизнес-логики, валидации, доступа.

Как создать пользовательское исключение?

Все пользовательские исключения должны наследоваться от базового класса Exception или одного из его стандартных потомков (RuntimeException, LogicException). Согласно стандарту PSR-4 и лучшим практикам, их следует размещать в отдельном пространстве имен (например, App\Exception\).

Базовый пример

<?php

namespace App\Exception;

use RuntimeException;

// Наследуемся от RuntimeException, так как эта ошибка, как правило,
// возникает во время выполнения и не может быть обнаружена на этапе компиляции.
class InsufficientFundsException extends RuntimeException
{
    // Можно добавить свойства для хранения специфичных данных
    private float $currentBalance;
    private float $requiredAmount;

    public function __construct(float $currentBalance, float $requiredAmount, string $message = "", int $code = 0, ?Throwable $previous = null)
    {
        $this->currentBalance = $currentBalance;
        $this->requiredAmount = $requiredAmount;

        // Формируем информативное сообщение по умолчанию
        $defaultMessage = sprintf(
            'Insufficient funds. Current balance: %.2f, required amount: %.2f',
            $currentBalance,
            $requiredAmount
        );

        parent::__construct($message ?: $defaultMessage, $code, $previous);
    }

    // Геттеры для доступа к контексту ошибки
    public function getCurrentBalance(): float
    {
        return $this->currentBalance;
    }

    public function getRequiredAmount(): float
    {
        return $this->requiredAmount;
    }
}

Использование и обработка

<?php

// В сервисе оплаты
class PaymentService
{
    public function chargeAccount(Account $account, float $amount): void
    {
        if ($account->getBalance() < $amount) {
            // Бросаем наше специфичное исключение
            throw new InsufficientFundsException($account->getBalance(), $amount);
        }
        // ... логика списания ...
    }
}

// В месте вызова (например, контроллере)
try {
    $paymentService->chargeAccount($userAccount, 1000);
    echo "Payment successful!";
} catch (InsufficientFundsException $e) {
    // Обрабатываем именно ошибку недостатка средств
    log_error('Payment failed due to funds', [
        'balance' => $e->getCurrentBalance(),
        'required' => $e->getRequiredAmount()
    ]);
    echo "Error: " . $e->getMessage();
    // Можем предложить пополнить счет
} catch (NetworkException $e) {
    // Другие типы исключений обрабатываем иначе
    echo "Network error. Please try again later.";
} catch (Exception $e) {
    // Общий fallback для непредвиденных ошибок
    echo "An unexpected error occurred.";
}

Иерархия исключений

Для сложных приложений рекомендуется строить целую иерархию:

Exception (базовый от PHP)
├── LogicException (стандартный)
│   └── App\Exception\InvalidOrderStateException
├── RuntimeException (стандартный)
│   ├── App\Exception\ServiceUnavailableException
│   ├── App\Exception\Payment\PaymentFailedException
│   │   ├── App\Exception\Payment\InsufficientFundsException
│   │   ├── App\Exception\Payment\CardDeclinedException
│   │   └── App\Exception\Payment\GatewayTimeoutException
│   └── App\Exception\Validation\ValidationException
│       ├── App\Exception\Validation\EmailValidationException
│       └── App\Exception\Validation\LengthValidationException

Важное правило: Всегда ловите исключения от наиболее специфичных к наиболее общим. Это гарантирует, что обработчик InsufficientFundsException не будет "перехвачен" более общим PaymentFailedException, если это не требуется по логике.

Вывод

Создание пользовательских исключений — это не просто "можно", а обязательная практика для создания поддерживаемого, надежного и понятного кода. Они превращают обработку ошибок из хаотичного набора условий в стройную, типобезопасную систему, интегрированную в доменную модель приложения.

Можно ли делать пользовательские исключения? | PrepBro