Как стать автором
Обновить

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

Время на прочтение3 мин
Количество просмотров4.3K

Статья рассчитана на самураев, кто находится в самом начале пути 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 канале.

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

Теги:
Хабы:
Всего голосов 7: ↑6 и ↓1+7
Комментарии5

Публикации

Истории

Работа

PHP программист
124 вакансии

Ближайшие события

Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область