Обновить

Комментарии 5

С годами я понял одну вещь. Чем больше я смотрю на ООП, на все эти потуги с DDD, отловом исключений и т.п, тем больше понимаю, что через ООП нужно пройти. Сначала осознать, потом обуздать, потом стать евангелистом ООП, потом начать сомневаться что бы потом в итоге осознать - что тебе это не нужно.

Выглядит интересно, но первая же мысль при взгляде на Packagist: почему вы разрешаете только Symfony 7.4? А как же 8.0?

Также, вам точно не помешает ознакомиться с тем, как оформляется bundle в Symfony. Сейчас то, что ставится через Composer - это не bundle, а ваш тестовый проект для его разработки.

Обратите внимание на то, что в устанавливаемом коде много лишнего: bin, config, public, tests - всё это вообще не имеет смысла тащить во внешние проекты, их нужно убирать через export-ignore в .gitattributes

Типовую конфигурацию стоит создавать через Symfony recipes.

Спасибо за конкретику - всё по делу. По.gitattributes, export-ignore и recipes принято, это очевидный пропуск. По поводу версий - это ошибка, не намерение. Я случайно запинил 7.4.* вместо ^7.4|^8.0. Бандл не использует deprecated API, поэтому Symfony 8.0 поддерживается без изменений кода.

Без всяких бандлов код типичного контроллера на симфони выглядит так:

public function create(
    #[MapRequestPayload] CreateUserRequest $userRequest,
): JsonResponse {
    $user = $this->userService->createService($userRequest);
    return new JsonResponse($this->serializer->serialize($user , 'json'), Response::HTTP_CREATED, json: true);
}

MapRequestPayload самостоятельно вызывает валидацию. По умолчанию сериалайзер отвечает ошибками в формате RFC 7807. Однако и код ответа и формат легко меняются.

Перехват исключений в контроллере моветон с версии 2.0.0-BETA. Для централизованной обработки исключений существует событие kernel.exception. Если вы не хотите кидаться из сервисов http-исключениями, можно держать всю обработку исключений в одном сабскрайбере/листенере. Например:

final readonly class ExceptionResponseSubscriber implements EventSubscriberInterface
{
    private const EXCEPTION_MAP = [
        OrderNotFound::class      => Response::HTTP_NOT_FOUND,
        InvalidOrderData::class   => Response::HTTP_UNPROCESSABLE_ENTITY,
        AccessDenied::class       => Response::HTTP_FORBIDDEN,
    ];

    public static function getSubscribedEvents(): array
    {
        return [KernelEvents::EXCEPTION => 'onKernelException'];
    }

    public function onKernelException(ExceptionEvent $event): void
    {
        $exception = $event->getThrowable();
        $class = $exception::class;

        if (!array_key_exists($class, self::EXCEPTION_MAP)) {
            return; // Пусть стандартный ErrorListener обработает
        }

        $event->setResponse(new JsonResponse(
            ['error' => $exception->getMessage()],
            self::EXCEPTION_MAP[$class]
        ));
    }
}

Совершенно верно - и #[MapRequestPayload] с автовалидацией, и централизованная обработка через kernel.exception - это стандартные инструменты Symfony. Ваш пример хорошо показывает, что в рамках одного проекта можно обойтись без дополнительного бандла и держать всё под контролем. Собственно, я и сам делал именно так - через subscriber с маппингом исключений и единым JsonResponse.
Бандл решает немного другую задачу. Он не добавляет новую механику поверх Symfony, а упаковывает уже существующие практики в переиспользуемый слой. Когда проектов становится много (микросервисы, разные команды, разные подрядчики), важна не просто централизованная обработка ошибок, а гарантированная консистентность формата ответов между сервисами - без копирования listener’ов из проекта в проект и без постепенного "дрейфа" структуры.
В случае с ExceptionResponseSubscriber его действительно несложно написать. Сложнее - поддерживать синхронность изменений между несколькими репозиториями, когда формат ответа эволюционирует.
Кстати, по RFC 7807 - идея хорошая! Если кому-то нужен полноценный problem+json технически это реализуемо через ResponseFactoryInterface который уже есть в бандле. Можно добавить ProblemDetailsResponseFactory как альтернативную реализацию и переключать через конфиг (format: problem+json). Единственный нюанс - RFC 7807 стандартизирует только формат ошибок, для успешных ответов общего стандарта нет, поэтому это скорее опциональный режим, а не замена дефолтному формату. Если будет запрос - реализовать несложно.
В общем, ваш подход полностью корректен для проекта, где всё находится под контролем одной команды. А бандл может быть полезен там, где нужна стандартизация между несколькими проектами "из коробки".

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации