Skip to main content

Концепция :meta эндпоинтов

Задача: В Административной панели требуется, чтобы администратор мог табличные списки гибко настраивать, выводить только интересующие столбцы, фильтры, менять сортировку. При этом добавление новых полей в сущность не должно требовать доработок табличного списка на фронте.

Для решения этой задачи реализуется следующее:

  1. На каждый табличный список бэк предоставляет эндпоинт :meta, который отдаёт информацию о каждом поле сущности, с детализацией как по этому полю фильтровать, доступна ли сортировка и т.д.
  2. Фронт использует эту мета-информацию о сущности, чтобы построить страницу. Для получения самих данных необходимо использовать эндпоинт :search

Таким образом можно настроить редактирование пользователем выводимых полей, а при добавлении нового поля, оно просто дополнительно регистрируется в :meta

Структура ответа эндпоинта :meta

{
"data": {
"fields": [ // Массив с описанием полей сущности
{
"code": "id", // Символьный код поля
"name": "ID", // Название поля
"list": true, // Доступно ли поле для вывода в таблицу
"type": "string", // Тип поля. Список актуальных доступных значений смотрите в документации админки, FieldTypeEnum
"enum_info": { // Заполняется, если поле типа `enum`. Доступно 2 варианта, либо статический список перечислений, либо догрузка списка через отдельный эндпоинт
"endpoint": "string", // Заполняется для динамических перечислений, тут будет относительный путь до эндпоинта от корня сервиса
"values": [ // Заполняется для статических перечислений
{
"id": "int/string/...",
"title": "string"
}
]
},
"sort": true, // Доступно ли поле для сортировки
"sort_key": "id", // Заполняется, если доступна сортировка. Этот ключ необходимо передавать в :search метод для сортировки по данному полю
"filter": "default", // Тип фильтра по данному полю. Список актуальных доступных значений смотрите в документации админки, FieldFilterTypeEnum
"filter_range_key_from": "id_from", // Если тип фильтра по диапазону - ключ, под которым необходимо передавать в :search значение ОТ
"filter_range_key_to": "id_to", // Если тип фильтра по диапазону - ключ, под которым необходимо передавать в :search значение ДО
"filter_key": "id" // Если фильтр обычный - ключ, под которым необходимо передавать в :search значение
}
],
"detail_link": "number", // Код поля, которое нужно использовать для вывода ссылки на детальную страницу. Такое поле пользователь не может скрыть
"default_sort": "id", // Код поля, по которому необходимо проводить сортировку по-умолчанию
"default_list": [ // Коды полей, которые по-умолчанию выводятся в таблицу
"id",
"created_at"
],
"default_filter": [ // Коды полей, которые по-умолчанию выводятся в фильтр
"id",
"created_at"
]
}
}

Эндпоинт для перечислений

Обычно структура этого эндпоинта одинаковая:

  • Фильтрация по 2 полям: id (для получения значений по id, например для вывода значения в таблицу) или query (для автодополнения, при введении в фильтр).
  • Массив объектов в ответе, каждый объект содержит id (int/string/...) + title (string)

Реализация на бэке

Со стороны бэкенда подготовлены хелперы для формирования списка полей:

  1. new \App\Http\ApiV1\Support\Resources\ModelMetaResource([...]) - ресурс для формирования ответа
  2. \App\Domain\Common\Data\Meta\Field::{type}() - получение объекта для поля нужного типа
  3. \App\Domain\Common\Data\Meta\Fields\AbstractField - базовый класс для работы описанием поля, содержит методы для конфигурации описания
  4. \App\Domain\Common\Data\Meta\Enum\AbstractEnumInfo - базовый класс для заполнения информации о Enum. Либо указываем name эндпоинта, либо загружаем статическое перечисление

В итоге стандартный :meta эндпоинт выглядит примерно так:

public function propertiesMeta(AsyncLoadAction $action, PropertyTypeEnumInfo $types): ModelMetaResource
{
$action->execute([$types]);

return new ModelMetaResource([
Field::id()->listDefault()->filterDefault()->detailLink(),
Field::text('name', 'Рабочее название')->listDefault()->filterDefault()->resetSort(),
Field::text('display_name', 'Публичное название')->listDefault()->filterDefault()->resetSort(),
Field::text('code', 'Код')->listDefault()->filterDefault()->resetSort(),
Field::enum('type', 'Тип данных', $types)->listDefault()->filterDefault()->resetSort(),
Field::boolean('is_active', 'Активный')->listDefault()->filterDefault()->resetSort(),
Field::boolean('is_public', 'Выводить на витрине')->listDefault()->filterDefault()->resetSort(),
Field::boolean('is_required', 'Обязательность')->listDefault()->filterDefault()->resetSort(),
Field::boolean('is_gluing', 'Параметр склеивания')->listDefault()->filterDefault()->resetSort(),
Field::datetime('created_at', 'Дата создания')->listDefault()->filterDefault(),
Field::datetime('updated_at', 'Дата обновления')->listDefault()->filterDefault(),
Field::boolean('is_filterable', 'Фильтр на витрине')->resetSort(),
Field::boolean('is_multiple', 'Множественный')->resetSort(),
Field::boolean('has_directory', 'Справочник')->resetSort(),
]);
}