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