Пакет · mui-data-grid

MUI Data Grid

Winter MUI Data Grid — серверный адаптер для таблицы MUI X DataGrid. Вы описываете схему колонок, а библиотека принимает запрос таблицы (страница, модель сортировки, модель фильтров), проверяет его по этой схеме как по белому списку, накладывает условия на ваш репозиторий и возвращает { rowCount, rows } — ровно то, что таблица ждёт от сервера.

Проблема

MUI X DataGrid умеет работать в серверном режиме: вместо того чтобы держать всю таблицу в браузере, она отправляет на сервер своё состояние — какую страницу показать, по каким колонкам сортировать, какие фильтры применил пользователь. Выглядит это так:

json
{
"page": 0,
"pageSize": 25,
"sortModel": [{ "field": "views", "sort": "desc" }],
"filterModel": {
  "logicOperator": "and",
  "items": [{ "field": "title", "operator": "contains", "value": "winter" }]
}
}

Дальше начинается ручная работа, которая в каждом проекте пишется заново и одинаково неудачно: разобрать этот JSON, сопоставить field из браузера с колонкой в базе, перевести operator в SQL, не забыть про LIMIT/OFFSET, посчитать общее число строк отдельным COUNT. И главное — не дать клиенту отсортировать по колонке, которую он не должен видеть, или подставить в фильтр то, что попадёт в запрос как есть.

Наивная реализация («возьмём field и подставим в ORDER BY») открывает SQL-инъекцию через имя колонки. Осторожная — превращается в сотню строк ветвлений на каждый эндпоинт.

Решение

Библиотека делает эту работу один раз, а от вас требует одну декларацию — схему:

php
$schema = GridSchema::make(
  GridColumn::for('title',      'a.title')->filterable(FilterType::String)->sortable(),
  GridColumn::for('views',      'a.views')->filterable(FilterType::Number)->sortable(),
  GridColumn::for('authorName', 'au.name')->filterable(FilterType::String)->sortable(),
)->defaultOrder('a.created_at DESC');

return MuiGrid::wrap($repo, $request, $schema);

Схема — это контракт между таблицей в браузере и базой. В ней сказано: какие имена полей существуют, какому SQL-выражению каждое соответствует, что с ним разрешено делать (фильтровать, сортировать) и какого типа его значения. Всё, чего в схеме нет, до базы не доходит.

Философия

  • Наложение, а не генерация. Библиотека не строит ваш запрос. SELECT, JOIN и бизнесовый WHERE пишете вы; она только добавляет andWhere() для фильтров, выставляет orderBy() и отдаёт пагинацию штатному Paginator. Поэтому всё, что выразимо в SQL — CTE, подзапросы, вычисляемые колонки — работает без оговорок.
  • Схема — белый список. Не «фильтруем всё, кроме запрещённого», а «фильтруем только то, что объявлено». Забыть закрыть колонку невозможно: колонка, которой нет в схеме, закрыта по умолчанию.
  • Имена — из вашего кода, значения — из запроса. SQL-выражение колонки берётся из схемы и в шаблон запроса подставляется как есть; значение пользователя всегда уходит связанным параметром. Инъекция невозможна по построению.
  • Диалект — свойство соединения, а не грида. Регистронезависимый поиск пишется по-разному в PostgreSQL, MySQL и SQLite. Библиотека определяет нужное написание по драйверу репозитория, так что одна и та же схема работает на любой из трёх баз.

Ключевые понятия

  • GridSchema — набор колонок плюс запасной ORDER BY. Единственный источник правды о том, что клиенту позволено.
  • GridColumn — одна колонка: имя поля в браузере → SQL-выражение, плюс флаги «можно фильтровать» и «можно сортировать».
  • FilterType — тип значения колонки (String, Number, Boolean, Date). Определяет, какие операторы колонка принимает: числовая колонка откажет contains, строковая — >.
  • MuiGridRequest — разобранный запрос таблицы. Гидрируется слоем запросов Winter, вручную ничего парсить не нужно.
  • MuiGrid::wrap() — точка входа: берёт репозиторий, запрос и схему, возвращает MuiGridResponse.
  • TextMatch — как пишется регистронезависимое совпадение (ILIKE / LIKE / lower()). По умолчанию определяется автоматически.

Возможности

  • Пагинация с общим счётчикомLIMIT/OFFSET и COUNT одним вызовом, через штатный Paginator фреймворка.
  • Фильтры со шлюзом по типу — двадцать три оператора MUI, каждый разрешён только для подходящего FilterType.
  • AND / OR — как в filterModel.logicOperator.
  • Мультиколоночная сортировка — по модели сортировки, с запасным порядком, когда сортировки нет.
  • Своё выражение сортировки — колонка может фильтроваться по a.title, а сортироваться по lower(a.title).
  • Переопределение фильтра на колонкуfilterUsing() для случаев, где нужен свой SQL.
  • Маппер строк — превратить плоские строки в ресурсы, не выходя из вызова.
  • Портируемость — PostgreSQL, MySQL, SQLite без изменений в коде.

Кому это подходит

Любому админ-интерфейсу или списку, где данных больше, чем разумно отдавать в браузер: таблица остаётся отзывчивой, потому что на клиент едет одна страница, а не весь набор.

Требования

  • PHP ≥ 8.4
  • flytachi/winter-kernel ^4.0 — слой запросов, который гидрирует и валидирует запрос таблицы
  • flytachi/winter-ppa ^1.0 — слой данных: репозитории, конструктор запросов и пагинатор

Оба пакета Composer поставит сам. Подробности — в Установке.

Установка

bash
composer require flytachi/winter-mui-data-grid

Как это выглядит

Схема объявлена, запрос пришёл — остаётся отдать ответ:

php
$repo = ArticleRepository::instance('a')
  ->select('a.id, a.title, a.views')
  ->where(Qb::eq('a.is_published', true));

return ResponseEntity::ok(
  MuiGrid::wrap($repo, $request, $schema)->toArray()
);

Ответ:

json
{ "rowCount": 134, "rows": [ { "id": 12, "title": "Winter internals", "views": 9001 } ] }

Полный путь — от установки до работающей таблицы в браузере — в Быстром старте.

Исходники и ссылки

Продолжите с Установки, Быстрого старта и Ментальной модели.