Преобразование формата ошибок с помощью встроенных событий Symfony

от автора

Статья рассчитана на самураев, кто находится в самом начале пути Symfony и не способен самостоятельно постичь фреймворк силой одной лишь документации.

Зачем я пишу об этом?

Первая причина — это моя личная мотивация начать-таки наконец писать статьи на Хабре. Говорят, дорога возникает под шагами идущего. И вот я встал на путь менторства и уже веду Youtube канал, где публикую обучающие видео по фреймворку Symfony. Но еще никогда не писал технические статьи.

Вторая причина — этот материал кому-то окажется полезен.

Работая над проектом, у меня возникла задача: возвращать информацию об Exception в формате JSON если клиент указывает поддерживаемый им MIME тип application/json в запросе, используя заголовок Accept.

Простыми словами — если клиенту нужна ошибка в JSON, то дать ему JSON. В других фатальных запросах возвращать стандартную ошибку в формате HTML.

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

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

Есть 2 способа обработки встроенного события exception: через слушатели и подписчики.

Первый способ — EventListener

Создадим класс слушателя:

<?php declare(strict_types=1);  namespace App\Core\EventListener;  use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;  class ExceptionListener {     const MIME_JSON = 'application/json';      public function onKernelException(ExceptionEvent $event): void     {         // Получаем MIME тип из заголовка Accept         $acceptHeader = $event->getRequest()->headers->get('Accept');          if ($acceptHeader === self::MIME_JSON) {             $exception = $event->getThrowable();             $response = new JsonResponse();             $response->setContent($this->exceptionToJson($exception));              // HttpException содержит информацию о заголовках и статусе, испольузем это             if ($exception instanceof HttpExceptionInterface) {                 $response->setStatusCode($exception->getStatusCode());                 $response->headers->replace($exception->getHeaders());             } else {                 $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);             }              $event->setResponse($response);         }     }      public function exceptionToJson(\Throwable $exception): string     {         return json_encode(             [                 'message' => $exception->getMessage(),                 'code' => $exception->getCode(),                 'file' => $exception->getFile(),                 'line' => $exception->getLine(),                 'trace' => $exception->getTraceAsString(),             ]         );     } }

Далее нам необходимо зарегистрировать класс в файле services.yaml с указанием соответствующего тега.

App\Core\EventListener\ExceptionListener:     tags:         - { name: kernel.event_listener, event: kernel.exception }

Проверим, зарегистрировался ли слушатель в диспетчере событий, используя команду:

php bin/console debug:event-dispatcher kernel.exception

Результат:

Как видим, слушатель зарегистрировался успешно
Как видим, слушатель зарегистрировался успешно

Второй способ — Event Subscriber

Добавим класс подписчика

<?php declare(strict_types=1);  namespace App\Core\EventListener;  use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\KernelEvents;  class BuiltInEventsSubscriber {     public function onKernelException(ExceptionEvent $event)     {         // Код обработки ошибки можно взять из класса ExceptionListener     }      public static function getSubscribedEvents()     {         return [             KernelEvents::EXCEPTION => 'onKernelException',         ];     } } 

Если в проекте включена автоматическая регистрация сервисов autoconfigure: true, то дополнительно регистрировать подписчик не нужно. В противном случае пропишем сервис в файле services.yaml

App\Core\EventListener\BuiltInEventsSubscriber:     tags:         - { name: kernel.event_subscriber }

Проверим, зарегистрировался ли подписчик в диспетчере событий, используя команду:

php bin/console debug:event-dispatcher kernel.exception

Результат:

Тестирование

Проверим как выглядит ответ с ошибкой исключения при запросе в браузере:

Как выглядит ответ при запросе с указанием заголовка Accept.

Запрос:

curl http://127.0.0.1:888/health-check -H "Accept: application/json"

Ответ:

{    "message":"Division by zero",    "code":0,    "file":"\/var\/www\/src\/Shared\/Infrastructure\/Controller\/HealthCheckAction.php",    "line":16,    "trace":"#0 \/var\/www\/vendor\/symfony\/http-kernel\/HttpKernel.php(153): ....... require_once('\/var\/www\/vendor...')\n#6 {main}" }

Перечень данных возвращаемой ошибки можно изменить под нужды в методе onKernelException.

Рабочий пример можно посмотреть в репозитории, где есть еще много чего интересного 🙂

Другой обучающий материал по Symfony 6 представлен на моем Youtube канале.

Спасибо за внимание!


ссылка на оригинал статьи https://habr.com/ru/post/688202/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *