Магический API Resource в Laravel

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

В Laravel есть удобные API ресурсы, с которыми легко и приятно работать в области трансформации данных для ответа на запрос. Но что делать когда возникает необходимость изменить их структуру в соответствии с бизнес-потребностями? Разберёмся вместе!

Вводная

Laravel предоставляет возможность транформации данных для ответа на запрос при помощи API ресурсов. По-умолчанию ответ будет иметь примерно следующий вид для возврата одного инстанса:

{
  "data": {
    "id": 123,
    "title": "Some title"
  }
}

Для коллекций:

{
  "data": [
    {
      "id": 123,
      "title": "Some title"
    }
  ]
}

И для пагинатора:

{
  "data": [
    {
      "id": 123,
      "title": "Some title"
    }
  ],
  "links": {
    "first": "http://localhost:8000/?page=1",
    "last": "http://localhost:8000/?page=2",
    "prev": null,
    "next": "http://localhost:8000/?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 2,
    "links": [
      {
        "url": null,
        "label": "« Previous",
        "active": false
      },
      {
        "url": "http://localhost:8000/?page=1",
        "label": "1",
        "active": true
      },
      {
        "url": "http://localhost:8000/?page=2",
        "label": 2,
        "active": false
      },
      {
        "url": "http://localhost:8000/?page=2",
        "label": "Next »",
        "active": false
      }
    ],
    "path": "http://localhost:8000/",
    "per_page": 2,
    "to": 2,
    "total": 3
  }
}

И данный ответ выглядит уже устрашающе в случая, когда Laravel работает в режиме API и фронтенд к нему разрабатывается снаружи другой командой.

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

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

В случае с данными всё просто - при создании ресурсов пишем имена ключей в нужном стиле и радуемся. Но как быть с пагинатором?

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

Костыльный способ

Пример костыльного метода, который я видел, некоторых людей приводит в ужас. Просто посмотрите на это:

<?php

public function index(Request $request)
{
    $items = Some::paginate();

    $resource = SomeResource::collection($items);

    $result = $this->convertToArray($resource->toArray($request));

    return response()->json($result);
}

protected function convertToArray(array $items): array
{
    $result = [];

    foreach ($items as $key => $value) {
        $newKey = Str::camel($key);

        $result[$newKey] = is_array($value) ? $this->convertToArray($value) : $value;
    }

    return $result;
}

Здесь даже говорить ничего не надо - все проблемы на лицо. Поэтому перейдём к неудобному способу.

Условно неудобный

Laravel позволяет легко изменять ключи пагинатора путём объявления метода paginationInformation в классе ресурса. Для удобства можно вынести его в общий абстрактный класс и наслаждаться.

<?php

public function paginationInformation($request, $paginated, $default)
{
    $default['links']['custom'] = 'https://example.com';
 
    return $default;
}

Такой подход довольно простой в реализации, полностью соответствует логике фреймворка и не требует дополнительных затрат по сопровождению. Но почему он неудобный?

Всё дело в том, что если придёт задача на изменение структуры ресурсов, этот метод придётся обрабатывать вручную и переписывать. Именно в этом случае в одном из проектов появился третий способ - мудрёный.

Мудрёный способ

Мудрёный он потому, что помимо превращения имён ключей в camelCase возникла бизнес-потребность дополнительно оборачивать ключи массивов в дополнительный ключ.

Так, например, если обычная коллекция имеет следующую структуру:

{
  "data": [
    {
      "id": 123,
      "title": "Some title"
    }
  ]
}

То по новым условиям нужно возвращать в такой:

{
  "data": {
   "somes": [
      {
        "id": 123,
        "title": "Some title"
      }
    ]
  }
}

Где somes - это множественная форма имени ключа и у каждого ресурса она своя.

Спросите зачем? Ответ прост - мета-данные. Периодически возникают потребности отдать какие-либо жёстко связанные именно с этим ответом данные, когда проще объединить их в один ответ нежели запрашивать из двух мест. В данном случае имена ключей не будут конфликтовать, т.к. внутри data у каждого будет, скажем так, своя область видимости.

Вдобавок, можно расширять ответы, чем мы ниже и займёмся.

Бизнес-потребность

Итак, нам прилетела необходимость выполнения следующих преобразований в респонсах:

  1. В случае возврата одного инстанса тело размещать внутри объекта data;

  2. В случае возврата коллекции, в том числе пагинации:

    1. тело размещать во вложенном в объект data ключе, имя которого имеет множественное значение (например, users, posts);

    2. выводить объект пагинатора в определённом формате;

  3. Приводить все даны на выходе к формату Y-m-d\TH:i.

Конечный вид объектов должен соответствовать следующему виду:

{
  "data": {
    "id": 123,
    "title": "Some title",
    "createdAt": "2024-03-16T18:54"
  },
  "status": "OK"
}
{
  "data": {
   "somes": [
      {
        "id": 123,
        "title": "Some title",
        "createdAt": "2024-03-16T18:54",
        "category": {
          "id": 1,
          "title": "Category name"
        }
      },
      {
        "id": 456,
        "title": "Another title",
        "createdAt": "2024-03-16T18:55",
        "category": null
      }
    ]
  },
  "pagination": {
    "total": 2,
    "perPage": 2,
    "currentPage": 1,
    "lastPage": 2
  },
  "status": "OK"
}

Выглядит просто? Это просто так выглядит

Источник: https://habr.com/ru/articles/800715/


Интересные статьи

Интересные статьи

Всем привет! Краткий обзор новостей из мира PHP и Laravel за январь 2024 года от Сергея Пантелеева и Данила Щуцкого (CutCode).
Здравствуй, Хабр!RAII (Resource Acquisition Is Initialization) - это важная концепция в C++. Она представляет собой парадигму управления ресурсами, которая способствует безопасности и эффективности ко...
Привет Хабр.Как и все разработчики на php, я тоже иногда сталкивался с NodeJs .На днях просмотривая фреймворки Node столкнулся на AdonisJs . И после нескольких минут прочтения кодоа я почувствовал себ...
В прошлом выпуске мы рассматривали различные способы формирования аудитории. Когда есть аудитория — есть потенциальные покупатели. Теперь осталось придумать, что им продавать. Откуда брать ид...
Считанные дни остаются до старта нового курса от OTUS — «Framework Laravel». В преддверии старта курса делимся заключительной частью авторской публикации о основных понятиях в Laravel. Важно: дан...