Один класс, что правит всеми или как улучшить респонс в Laravel

от автора

В фреймворке Laravel есть встроенный хелпер, который преобразует данные для вывода используя функцию json_encode и устанавливает в ответном заголовке Content-Type значение application/json. Например:

<?php  return response()->json([     'id' => 100,     'name' => 'Alexey Shatrov' ]);

Этот хелпер поддерживает HTTP коды, заголовки и JSON флаги, что делает его действительно полезным во многих случаях. Однако есть ситуации, когда такой подход становится неудобным.

Представьте, что Вам нужно вернуть 201-й код после создания ресурса. Звучит и выглядит легко. Просто передаём код вторым параметром:

<?php  return response()->json([     'id' => 100,     'name' => 'Alexey Shatrov' ], 201);

Теперь представьте, что, по мере роста API, Вам понадобится реализовать ещё одну модификацию ответа — например, добавить флаг JSON_UNESCAPED_UNICODE.

Опять же, нет проблем и такая модификация проста, но сам подход требует много времени, так как придётся вручную искать каждый вызов response()->json() в приложении и добавлять флаг параметр передачи флага:

<?php  return response()->json([     'id' => 100,     'name' => 'Alexey Shatrov' ], 201, options: JSON_UNESCAPED_UNICODE);

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

Преобразование

Давайте добавим немного магии создав класс с именем Response, который будет управлять всеми ответами. Данный класс будет реализовывать простую логику, основанную на переданном HTTP-коде: если код успешен, вернёт массив с ключом data, иначе — errors.

Итак, приступим. Создадим инвокабельный класс:

<?php  namespace App\Http\Responses;  use Illuminate\Http\JsonResponse;  class Response {     public function __invoke(mixed $data, int $statusCode): JsonResponse     {         return $this->payload($this->data($data, $statusCode), $statusCode);     }      protected function data(mixed $data, int $statusCode): array     {         return $statusCode < 400 ? compact('data') : ['errors' => $data];     }      protected function payload(array $data, int $statusCode): JsonResponse     {         return response()->json($data, $statusCode, options: JSON_UNESCAPED_UNICODE);     } }

Теперь мы можем заменить вызов функции response()->json() на наш класс:

<?php  return (new Response)([     'id' => 100,     'name' => 'Alexey Shatrov' ], 201);

Такой подход не сказать что выглядит элегантно, зато Вы сможете контролировать логику ответа в одном месте.

Однако каждый раз указывать коды ответов будет неудобно, как и не иметь контроля над передаваемыми параметрами не говоря уже о внешнем виде вызова. Вы легко можете допустить ошибку передав, например, 400-й код с «успешными» данными или наоборот. Чтобы исправить это недоразумение, добавим публичные методы:

<?php  class Response {     public static function ok(mixed $data): JsonResponse     {         return (new static)($data, 200);     }      public static function created(mixed $data): JsonResponse     {         return (new static)($data, 201);     }      public static function notFound(string $message = 'Item Not Found'): JsonResponse     {         return (new static)($message, 404);     }      public function __invoke(mixed $data, int $statusCode): JsonResponse     {         return $this->payload($this->data($data, $statusCode), $statusCode);     }      protected function data(mixed $data, int $statusCode): array     {         return $statusCode < 400 ? compact('data') : ['errors' => $data];     }      protected function payload(array $data, int $statusCode): JsonResponse     {         return response()->json($data, $statusCode, options: JSON_UNESCAPED_UNICODE);     } }

Пример использования:

<?php  public function someAction() {     // some logic      return $user ? Response::ok($user) : Response::notFound(); }

Такой код вполне понятен и удобен, а с классом Response легко работать. Его также можно расширить проверкой допустимых пределов HTTP кода (например, 200 <= x < 600), но это Вы можете сделать самостоятельно при необходимости, поэтому писать пример кода здесь не будем)

Где же Responsable интерфейс?

Я не рекомендую использовать интерфейс Responsable не только потому, что придётся реализовывать метод toResponse(Request $request), передавая ему объект реквеста, но ещё и потому, что:

  • метод toResponse возвращает инстанс Symfony\Component\HttpFoundation\Response в то время как мы ожидаем JsonResponse;

  • крайне редко возникает необходимость в передаче объекта реквеста внутрь обрабатываемого класса;

  • передача ненужной переменной в класс только для реализации метода toResponse захламляет код.

Таким образом, получаем метод ради метода (прим переводчика).

Всех благ!

От переводчика

Оригинальная статья имеет юмор с уклоном во вселенную «Властелин Колец», который я решил не переводить. Кроме того, помимо самого текста, были оптимизированы и блоки кода.


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


Комментарии

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

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