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

Создавал ли свои Exception?

1.0 Junior🔥 152 комментариев
#PHP Core#ООП

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

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

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

Мои практики создания пользовательских исключений (Custom Exceptions)

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

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

Пользовательские исключения решают несколько ключевых задач:

  • Семантическая ясность: Более точно описывают природу ошибки, чем стандартные RuntimeException или InvalidArgumentException
  • Логическая группировка: Позволяют создавать иерархии исключений для разных слоев приложения (Domain, Application, Infrastructure)
  • Упрощенная обработка: Можно ловить конкретные типы исключений, а не проверять сообщения или коды
  • Добавление контекста: Возможность включать в исключение специфичные для бизнес-логики данные
  • Согласованность: Унификация обработки ошибок во всей кодовой базе

Типичные примеры создания исключений

Базовый пример с наследованием от стандартных исключений

<?php

namespace App\Domain\User\Exception;

/**
 * Бизнес-логика: пользователь не найден в системе
 * Наследуем от DomainException, так как это нарушение доменных правил
 */
class UserNotFoundException extends \DomainException
{
    private ?int $userId;

    public function __construct(string $message = "User not found", int $code = 0, ?int $userId = null)
    {
        parent::__construct($message, $code);
        $this->userId = $userId;
    }

    public function getUserId(): ?int
    {
        return $this->userId;
    }
}

Иерархия исключений для слоеной архитектуры

В современных приложениях часто используют DDD (Domain-Driven Design) или гексагональную архитектуру, где исключения разделяются по слоям:

<?php

// Доменный слой (бизнес-правила)
namespace App\Domain\Order\Exception;

class OrderAlreadyPaidException extends \DomainException 
{
    // Нарушение инвариантов домена
}

// Прикладной слой (use cases)
namespace App\Application\Order\Exception;

class OrderProcessingFailedException extends \RuntimeException
{
    // Ошибки в сценариях использования
}

// Инфраструктурный слой (внешние сервисы)
namespace App\Infrastructure\Payment\Exception;

class PaymentGatewayTimeoutException extends \RuntimeException
{
    private \DateTimeImmutable $occurredAt;
    
    public function __construct(string $gatewayName, int $timeoutSeconds)
    {
        parent::__construct(
            sprintf('Payment gateway "%s" timeout after %d seconds', $gatewayName, $timeoutSeconds)
        );
        $this->occurredAt = new \DateTimeImmutable();
    }
    
    public function getOccurredAt(): \DateTimeImmutable
    {
        return $this->occurredAt;
    }
}

Исключения с дополнительными данными для логов и мониторинга

<?php

namespace App\Application\Validation\Exception;

/**
 * Исключение с детальной информацией о ошибках валидации
 * Полезно для API, чтобы возвращать структурированные ошибки
 */
class ValidationFailedException extends \InvalidArgumentException
{
    private array $errors;
    private string $validationContext;

    public function __construct(array $errors, string $validationContext = 'general')
    {
        $this->errors = $errors;
        $this->validationContext = $validationContext;
        
        $errorMessages = array_map(fn($error) => $error['message'], $errors);
        parent::__construct(
            sprintf('Validation failed in context "%s": %s', 
                   $validationContext, 
                   implode(', ', $errorMessages))
        );
    }

    public function getErrors(): array
    {
        return $this->errors;
    }
    
    public function getValidationContext(): string
    {
        return $this->validationContext;
    }
    
    /**
     * Преобразуем исключение в массив для API-ответа
     */
    public function toArray(): array
    {
        return [
            'type' => 'validation_error',
            'context' => $this->validationContext,
            'errors' => $this->errors,
            'timestamp' => time()
        ];
    }
}

Лучшие практики при создании исключений

1. Осмысленное наследование

  • LogicException и её потомки (InvalidArgumentException, DomainException) - для ошибок, которые можно обнаружить до выполнения кода
  • RuntimeException и её потомки - для ошибок, которые возникают во время выполнения

2. Четкое неймспейсирование

Исключения должны находиться в соответствующих неймспейсах, обычно:

  • App\Domain\{Entity}\Exception\
  • App\Application\{UseCase}\Exception\
  • App\Infrastructure\{Service}\Exception\

3. Добавление контекстной информации

Включайте в исключение данные, которые помогут:

  • Разработчику понять причину
  • Системе мониторинга классифицировать ошибку
  • Пользователю получить понятное сообщение (через перевод)

4. Создание базовых классов для унификации

Часто создаю базовые исключения для всего приложения:

<?php

namespace App\Shared\Exception;

abstract class ApplicationException extends \RuntimeException
{
    private string $errorCode;
    private array $context;

    public function __construct(
        string $message, 
        string $errorCode, 
        array $context = [], 
        int $code = 0, 
        \Throwable $previous = null
    ) {
        parent::__construct($message, $code, $previous);
        $this->errorCode = $errorCode;
        $this->context = $context;
    }
    
    public function getErrorCode(): string
    {
        return $this->errorCode;
    }
    
    public function getContext(): array
    {
        return $this->context;
    }
}

5. Интеграция с системами мониторинга

Пользовательские исключения могут автоматически обогащаться данными для Sentry, Rollbar или других систем:

<?php

try {
    $orderProcessor->process($order);
} catch (OrderProcessingFailedException $e) {
    // Обогащаем контекст для Sentry
    \Sentry\configureScope(function (\Sentry\State\Scope $scope) use ($e, $order) {
        $scope->setContext('order', [
            'id' => $order->getId(),
            'amount' => $order->getAmount(),
            'customer' => $order->getCustomerId()
        ]);
        $scope->setTag('exception_type', 'order_processing');
    });
    
    throw $e;
}

Заключение

Создание пользовательских исключений — это признак зрелой кодовой базы. Это позволяет:

  • Четко разделять ответственность между слоями приложения
  • Улучшать диагностику ошибок в production
  • Создавать более читаемый и поддерживаемый код
  • Реализовывать специфичные для бизнеса сценарии обработки ошибок

В современных PHP-

фреймворках (Laravel, Symfony) пользовательские исключения также интегрируются с механизмами обработки ошибок HTTP, преобразуясь в соответствующие статус -коды и структурированные JSON-ответы для API.

Создавал ли свои Exception? | PrepBro