Много раз при создании api нам нужно возвращать клиентам разные ответы в зависимости от возникшего исключения.
В этой небольшой статье я хотел бы показать вам, как добиться этого с помощью события Symfony Kernel Exception.
Представим, что мы создаем json api, который выбрасывает (среди прочих) следующие исключения:
И предположим, что мы хотим:
Начнем с создания сервиса, который управляет поведением каждого исключения.
class ExceptionResponseBuilder { public function __construct( private readonly ValidationFailedErrorsBuilder $validationFailedErrorsBuilder ) { } public function getExceptionResponse(mixed $exception): ?JsonResponse { return match (get_class($exception)) { ValidationFailedException::class => $this->getResponseForValidationFailedException($exception), TransportExceptionInterface::class => $this->getResponseForTransportException($exception), default => null }; } private function getResponseForValidationFailedException(ValidationFailedException $exception): JsonResponse { $errors = $this->validationFailedErrorsBuilder->build($exception->getViolations()); return new JsonResponse(['error' => 'INPUT_DATA_ERRORS', 'data' => $errors], Response::HTTP_BAD_REQUEST); } private function getResponseForTransportException(TransportExceptionInterface $exception): JsonResponse { return new JsonResponse(['error' => 'EXTERNAL_RESOURCE_UNAVAILABLE', 'data' => []], Response::HTTP_BAD_REQUEST); } }
Метод getExceptionResponse получает брошенное исключение в качестве параметра, сопоставляет имя его класса с исключениями, которыми мы хотим управлять, и возвращает JsonResponse для соответствующего исключения или null, если совпадений не найдено.
Метод getResponseForValidationFailedException, полагается на сервис ValidationFailedErrorsBuilder для построения массива ошибок из класса Symfony validation ConstraintViolationList. Давайте посмотрим, как это выглядит:
class ValidationFailedErrorsBuilder { public function build(ConstraintViolationListInterface $list): array { $errors = []; foreach ($list as $violation){ $errors[$violation->getPropertyPath()] = $violation->getMessage(); } return $errors; } }
Как мы видим, он перебирает нарушения и строит ассоциативный массив, где ключами являются пути ошибок (в форме это будут имена полей), а значениями - сообщения об ошибках.
Теперь давайте создадим подписчика событий, который будет слушать событие KernelEvents::EXCEPTION.
class KernelSubscriber implements EventSubscriberInterface { public function __construct( private readonly ExceptionResponseBuilder $exceptionResponseBuilder ) { } public static function getSubscribedEvents(): array { return [ KernelEvents::EXCEPTION => ['onKernelException'] ]; } public function onKernelException(ExceptionEvent $event): void { $exception = $event->getThrowable(); $response = $this->exceptionResponseBuilder->getExceptionResponse($exception); if ($response){ $event->setResponse($response); } } }
Когда возникает исключение и выполняется метод onKernelException, он использует наш сервис ExceptionResponseBuilder для получения JsonResponse в соответствии с классом исключения. Если JsonResponse будет возвращен, он будет помещен в событие
Теперь давайте протестируем наш конструктор ответов на исключения, чтобы убедиться, что он ведет себя так, как ожидается.
Для создания и выполнения тестов я использовал пакет symfony phpunit. Подробнее о нем вы можете узнать здесь: https://symfony.com/doc/current/testing.html
class ExceptionResponseBuilderTest extends KernelTestCase { public function testTransportExceptionResponse(): void { $exceptionResponseBuilder = static::getContainer()->get('App\Exception\ExceptionResponseBuilder'); $response = $exceptionResponseBuilder->getExceptionResponse(new TransportException('Resource unavailable')); $this->assertInstanceOf(JsonResponse::class, $response); $content = json_decode($response->getContent(), true); $this->assertEquals('EXTERNAL_RESOURCE_UNAVAILABLE', $content['error']); } public function testValidationExceptionResponse(): void { $exceptionResponseBuilder = static::getContainer()->get('App\Exception\ExceptionResponseBuilder'); $validationFailedException = new ValidationFailedException( null, new ConstraintViolationList( [ new ConstraintViolation('Name invalid', null, [], null, 'name', '658v') ] ) ); $response = $exceptionResponseBuilder->getExceptionResponse($validationFailedException); $this->assertInstanceOf(JsonResponse::class, $response); $content = json_decode($response->getContent(), true); $this->assertEquals('INPUT_DATA_ERRORS', $content['error']); $this->assertNotEmpty($content['data']); } public function testNoMappedException(): void { $exceptionResponseBuilder = static::getContainer()->get('App\Exception\ExceptionResponseBuilder'); $response = $exceptionResponseBuilder->getExceptionResponse(new \Exception('error')); $this->assertNull($response); } }
Как мы можем видеть в классе, мы протестировали каждый случай соответствия ExceptionResponseBuilder, чтобы убедиться, что все случаи работают так, как ожидалось. После выполнения тестов мы видим успешный результат
20.08.2023 18:21
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в 2023-2024 годах? Или это полная лажа?".
20.08.2023 17:46
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
19.08.2023 18:39
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в частности, магию поплавков и гибкость flexbox.
19.08.2023 17:22
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для чтения благодаря своей простоте. Кроме того, мы всегда хотим проверить самые последние возможности в наших проектах!
18.08.2023 20:33
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий их языку и культуре.
14.08.2023 14:49
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип предназначен для представления неделимого значения.