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

Зачем нужно создавать свои классы ошибок?

2.0 Middle🔥 161 комментариев
#Архитектура и паттерны#ООП

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

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

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

Зачем создавать собственные классы ошибок в PHP?

Создание собственных классов ошибок (custom exception classes) — это важная практика проектирования качественного PHP-приложения, которая выходит за рамки базового использования встроенных исключений Exception и Error. Вот ключевые причины, почему это необходимо.

1. Семантическая ясность и детализация ошибок

Стандартный класс Exception предоставляет лишь общую информацию. Собственные классы превращают ошибки в доменно-ориентированные сущности, делая код самодокументируемым.

// Вместо неясного:
throw new Exception('Ошибка валидации: email');

// Используем семантически понятный класс:
throw new InvalidEmailException('Адрес "user@domain" не прошел проверку MX-записей');

Это позволяет сразу понять тип ошибки, не анализируя текст сообщения.

2. Дифференцированная обработка исключений

Это главное преимущество. Мы можем ловить конкретные типы ошибок и обрабатывать их по-разному, не прибегая к анализу строковых сообщений или кодов.

try {
    $user = $this->userService->register($data);
} catch (ValidationException $e) {
    // Возвращаем 400 Bad Request с деталями ошибок
    return $response->json($e->getErrors(), 400);
} catch (EmailSendingException $e) {
    // Логируем, но регистрацию считаем успешной
    $this->logger->warning($e);
    return $response->json(['warning' => 'Account created, but confirmation email delayed']);
} catch (DatabaseException $e) {
    // Критическая ошибка БД - возвращаем 500
    $this->logger->critical($e);
    return $response->json(['error' => 'Server error'], 500);
}
// Все остальные исключения не перехватываются здесь и попадают в глобальный обработчик

3. Добавление специализированных данных и поведения

Собственный класс может содержать дополнительную контекстную информацию, недоступную в базовом Exception.

class PaymentProcessingException extends \RuntimeException {
    private string $transactionId;
    private float $amount;
    private string $gatewayResponse;

    public function __construct(string $message, string $transactionId, float $amount, string $gatewayResponse, int $code = 0, \Throwable $previous = null) {
        $this->transactionId = $transactionId;
        $this->amount = $amount;
        $this->gatewayResponse = $gatewayResponse;
        parent::__construct($message, $code, $previous);
    }

    public function getTransactionId(): string { return $this->transactionId; }
    public function getAmount(): float { return $this->amount; }
    public function getGatewayResponse(): string { return $this->gatewayResponse; }

    // Специализированный метод для логирования
    public function toLogContext(): array {
        return [
            'transaction_id' => $this->transactionId,
            'amount' => $this->amount,
            'gateway_response' => $this->gatewayResponse,
            'error_message' => $this->getMessage()
        ];
    }
}

// Использование
throw new PaymentProcessingException(
    'Payment gateway declined transaction',
    'txn_12345',
    99.99,
    '{"code": "insufficient_funds"}'
);

4. Структурирование иерархии ошибок

Можно создать логическую иерархию исключений, отражающую структуру вашего домена и уровни абстракции.

namespace App\Exception;

// Базовый класс для всех исключений приложения
abstract class AppException extends \RuntimeException {}

// Исключения бизнес-логики
class BusinessLogicException extends AppException {}
    class ValidationException extends BusinessLogicException {}
        class InvalidEmailException extends ValidationException {}
        class EntityNotFoundException extends ValidationException {}
    class PaymentException extends BusinessLogicException {}
        class InsufficientFundsException extends PaymentException {}
        class PaymentGatewayTimeoutException extends PaymentException {}

// Исключения инфраструктуры
class InfrastructureException extends AppException {}
    class DatabaseException extends InfrastructureException {}
    class ExternalServiceException extends InfrastructureException {}

Такая иерархия позволяет:

  • Ловить ошибки на нужном уровне детализации (catch (ValidationException $e) или catch (InvalidEmailException $e))
  • Уверенно различать фатальные ошибки инфраструктуры от обрабатываемых бизнес-исключений

5. Интеграция с фреймворками и инструментами

Современные PHP-фреймворки (Laravel, Symfony) активно используют собственные иерархии исключений для:

  • Автоматического преобразования в HTTP-ответы (NotFoundException → 404, AccessDeniedException → 403)
  • Интеграции с системами мониторинга (Sentry, Rollbar) – каждая группа исключений может настраиваться отдельно
  • Подключения специализированных обработчиков через Event Dispatcher

6. Улучшение тестируемости

Custom exceptions упрощают написание тестов, позволяя явно проверять тип выброшенного исключения.

public function testRegistrationWithInvalidEmail(): void
{
    $this->expectException(InvalidEmailException::class);
    // Можно также проверить свойства исключения
    $this->expectExceptionMessage('не прошел проверку MX-записей');
    
    $service = new UserService();
    $service->register(['email' => 'invalid-email']);
}

7. Консистентность и стандартизация

Создание общего базового класса для всех исключений приложения позволяет:

  • Единообразно добавлять методы для логирования
  • Реализовать глобальный обработчик с централизованным логированием и преобразованием в ответы API
  • Гарантировать, что все исключения содержат необходимый контекст

Практические рекомендации

  1. Начинайте с базового класса AppException для всего приложения
  2. Создавайте исключения для значимых бизнес-сценариев, а не для каждой возможной ошибки
  3. Добавляйте конструкторы, которые требуют передачи обязательного контекста
  4. Используйте неймспейсы для логической группировки (App\Exception\Payment\)
  5. Наследуйтесь от наиболее релевантных SPL-исключений (LogicException, RuntimeException, InvalidArgumentException), когда это уместно

Итог: Собственные классы ошибок — это не "over-engineering", а необходимый инструмент для создания поддерживаемого, надежного и понятного кода. Они превращают обработку ошибок из хаотичного набора if-else проверок в стройную систему доменных событий, что критически важно для сложных back-end приложений.

Зачем нужно создавать свои классы ошибок? | PrepBro