Пакет · mui-data-grid

Справочник API

Все публичные классы и методы пакета — сигнатуры, параметры, возвращаемые значения и ошибки. Перечисления FilterType, MGOperator и TextMatch вынесены на отдельные страницы: Операторы и Режимы совпадения.

Пространство имён Flytachi\Winter\MuiDataGridПакет flytachi/winter-mui-data-grid

MuiGrid

Точка входа библиотеки. Финальный класс без состояния — единственный публичный метод статический.

MuiGrid::wrap()

Накладывает фильтры, сортировку и пагинацию запроса на готовый репозиторий.

Метод выполняет четыре шага. Сначала разрешается режим текстового совпадения: если схема оставлена в режиме TextMatch::Auto, он определяется по драйверу базы, с которой работает репозиторий. Затем схема строит условие WHERE из модели фильтров и добавляет его к репозиторию через andWhere() — базовый запрос при этом не затрагивается. Дальше из модели сортировки строится ORDER BY и выставляется методом orderBy(). Наконец, страница и общее число строк запрашиваются у пагинатора фреймворка, который выполняет два запроса: SELECT … LIMIT … OFFSET … и отдельный COUNT.

Синтаксис

php
public static function wrap(
  RepositoryViewInterface $repo,
  MuiGridRequest $request,
  GridSchema $schema,
  ?callable $mapper = null,
): MuiGridResponse

Параметры

$repo — репозиторий с уже применёнными SELECT, JOIN и базовым WHERE. Метод изменяет этот объект: добавляет условия, выставляет порядок и границы страницы. Если репозиторий нужен после вызова, клонируйте его заранее.

$request — разобранный запрос таблицы: страница, размер страницы, модель сортировки и модель фильтров. Обычно это наследник MuiGridRequest с вашими дополнительными полями.

$schema — белый список колонок и запасной порядок. Определяет, что из присланного браузером имеет право попасть в запрос.

$mapper — необязательный преобразователь строк вида fn (object $row): mixed. Применяется к строкам текущей страницы после выполнения запроса.

Возвращает

MuiGridResponse — объект с полями rowCount (всего строк под фильтром) и rows (текущая страница).

Ошибки

MuiGridException (HTTP 400) — поле отсутствует в схеме, не включено для фильтрации или оператор не разрешён для типа колонки.

Пример

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

$response = MuiGrid::wrap($repo, $request, $schema, fn ($r) => ArticleRow::from($r));

return ResponseEntity::ok($response->toArray());

Результат

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

GridSchema

Набор колонок и запасной ORDER BY. Объект без состояния запроса — постройте один раз и переиспользуйте.

GridSchema::make()

Создаёт схему из перечня колонок.

Колонки индексируются по имени поля. Если одно имя объявлено дважды, побеждает последнее вхождение.

Синтаксис

php
public static function make(GridColumn ...$columns): self

Параметры

$columns — переменное число колонок. Объявляйте только те, которыми клиенту действительно позволено пользоваться: колонка, не включённая ни для фильтрации, ни для сортировки, не делает ничего.

Возвращает

GridSchema — новую схему.

Пример

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

GridSchema::defaultOrder()

Задаёт ORDER BY, применяемый, когда в запросе нет пригодной сортировки.

Используется в двух случаях: модель сортировки пуста или в ней остались только неизвестные и несортируемые поля. Без этого вызова запрос без сортировки уйдёт в базу вообще без ORDER BY, и порядок строк станет неопределённым — а значит, страницы перестанут быть стабильными.

Синтаксис

php
public function defaultOrder(string $order): self

Параметры

$order — готовое выражение ORDER BY без ключевого слова. Это ваш SQL, он не проверяется и не экранируется. Включайте в него уникальный «хвост» (обычно первичный ключ), иначе строки с одинаковыми значениями сортировки будут переставляться между страницами.

Возвращает

GridSchema — ту же схему, для цепочки вызовов.

Пример

php
GridSchema::make(/* … */)->defaultOrder('a.created_at DESC, a.id DESC');

GridSchema::textMatch()

Фиксирует, как для всей схемы пишется регистронезависимое совпадение.

По умолчанию схема находится в режиме TextMatch::Auto, и MuiGrid::wrap() определяет написание по драйверу репозитория. Явно заданный режим отменяет определение.

Синтаксис

php
public function textMatch(TextMatch $mode): self

Параметры

$mode — один из режимов TextMatch: Auto, ILike, Like или Lower.

Возвращает

GridSchema — ту же схему, для цепочки вызовов.

Пример

php
GridSchema::make(/* … */)->textMatch(TextMatch::Lower);

GridSchema::textMatchMode()

Возвращает режим, заданный схемой, — возможно, всё ещё Auto.

Нужен, когда режим требуется узнать до того, как он разрешён: например, чтобы применить тот же диалект к доменному фильтру.

Синтаксис

php
public function textMatchMode(): TextMatch

Возвращает

TextMatch — режим схемы без разрешения Auto.

GridSchema::buildWhere()

Строит условие WHERE из модели фильтров.

Каждый элемент модели ищется в схеме по имени поля; неизвестное или незаявленное для фильтрации поле останавливает обработку исключением. Элементы, давшие пустое условие (например, isAnyOf с пустым набором), пропускаются. Итог объединяется через AND или, если logicOperator равен or, через OR в скобках.

Обычно этот метод вызывает MuiGrid::wrap(); напрямую он нужен, когда условие требуется отдельно от пагинации.

Синтаксис

php
public function buildWhere(MGFilterModel $model, ?TextMatch $textMatch = null): Qb

Параметры

$model — модель фильтров из запроса.

$textMatch — разрешённый режим совпадения. null означает «взять режим схемы»; если тот всё ещё Auto — репозитория для определения здесь нет, поэтому применяется портируемый TextMatch::Lower.

Возвращает

Qb — условие, либо Qb::empty(), если ничего действующего не осталось.

Ошибки

MuiGridException (HTTP 400) — неизвестное поле, поле без разрешения на фильтрацию или оператор вне набора FilterType.

Пример

php
$where = $schema->buildWhere($request->filterModel, TextMatch::ILike);

echo $where->getQuery();

Результат

sql
(a.title ILIKE :iqb0 AND a.views >= :iqb1)

GridSchema::buildOrder()

Строит выражение ORDER BY из модели сортировки.

Поля обрабатываются в том порядке, в котором их прислал браузер. Неизвестное или несортируемое поле молча пропускается — таблица регулярно присылает переходное состояние сортировки, и падать на нём было бы враждебно к интерфейсу. Если пригодных полей не осталось, возвращается defaultOrder().

Синтаксис

php
public function buildOrder(array $sortModel): string

Параметры

$sortModel — массив MGSortItem из запроса.

Возвращает

string — выражение ORDER BY без ключевого слова, либо пустую строку, если нет ни пригодной сортировки, ни запасного порядка.

Пример

php
echo $schema->buildOrder([new MGSortItem('title', 'desc')]);

Результат

sql
lower(a.title) DESC

GridColumn

Декларация одной колонки: имя поля в браузере, SQL-выражение и разрешённые операции. Строится текучим интерфейсом.

GridColumn::for()

Создаёт колонку, сопоставляя имя поля SQL-выражению.

Свежая колонка не умеет ничего — ни фильтроваться, ни сортироваться. Возможности включаются явно, отдельными вызовами.

Синтаксис

php
public static function for(string $field, string $sql): self

Параметры

$field — имя поля, которое присылает браузер. Должно совпадать со свойством field у колонки MUI DataGrid.

$sql — SQL-выражение, которому это поле соответствует. Подставляется в запрос как есть, поэтому обязано приходить из вашего кода: колонка (a.title), колонка присоединённой таблицы (au.name) или любое выражение (coalesce(a.nick, a.name)).

Возвращает

GridColumn — новую колонку.

GridColumn::filterable()

Разрешает фильтрацию по колонке и задаёт тип её значений.

Тип работает шлюзом: оператор, не входящий в набор этого FilterType, отклоняется до того, как дойдёт до SQL. Наборы перечислены в Операторах.

Синтаксис

php
public function filterable(FilterType $type): self

Параметры

$typeFilterType::String, Number, Boolean или Date. Выбирайте по тому, как значение ведёт себя в SQL, а не по типу в PHP: колонка со статусом, сравниваемая только на равенство, — это String, а метка времени — Date.

Возвращает

GridColumn — ту же колонку, для цепочки вызовов.

Пример

php
GridColumn::for('views', 'a.views')->filterable(FilterType::Number);
// принимает =, !=, >, >=, <, <=, isAnyOf, isEmpty, isNotEmpty
// отклоняет contains, startsWith, … → HTTP 400

GridColumn::sortable()

Разрешает сортировку по колонке, необязательно — по другому выражению.

Выражение сортировки независимо от выражения фильтрации: колонка может искаться по a.title, а упорядочиваться по lower(a.title).

Синтаксис

php
public function sortable(?string $sqlExpr = null): self

Параметры

$sqlExpr — необязательное выражение для ORDER BY. null означает «сортировать по основному выражению колонки». Как и оно, это доверенный SQL: он не проверяется и не экранируется, поэтому не собирайте его из данных запроса.

Возвращает

GridColumn — ту же колонку, для цепочки вызовов.

Пример

php
GridColumn::for('title', 'a.title')->filterable(FilterType::String)->sortable('lower(a.title)');

GridColumn::textMatch()

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

Нужен, когда одной колонке требуется другое поведение: например, свёртка регистра через lower() для не-ASCII имени, тогда как остальной таблице хватает режима соединения. Значение TextMatch::Auto означает «наследовать от схемы» — то же самое, что не вызывать этот метод вовсе.

Синтаксис

php
public function textMatch(TextMatch $mode): self

Параметры

$mode — режим для этой колонки.

Возвращает

GridColumn — ту же колонку, для цепочки вызовов.

GridColumn::filterUsing()

Переопределяет сопоставление «оператор → SQL» для этой колонки.

Резолвер вызывается после проверки типа и до стандартного сопоставления. Возврат Qb перехватывает обработку; возврат null возвращает управление стандартному поведению для этого оператора — так пишутся только исключения.

Синтаксис

php
public function filterUsing(callable $resolver): self

Параметры

$resolver — вызываемое значение вида fn (MGFilterItem $item, TextMatch $mode): ?Qb. Второй аргумент — уже разрешённый режим совпадения; объявлять его необязательно, резолверы с одним параметром продолжают работать.

Возвращает

GridColumn — ту же колонку, для цепочки вызовов.

Пример

php
GridColumn::for('authorName', 'au.name')
  ->filterable(FilterType::String)
  ->filterUsing(fn (MGFilterItem $i): ?Qb => match ($i->operator) {
      MGOperator::IS_EMPTY     => Qb::isNull('a.author_id'),
      MGOperator::IS_NOT_EMPTY => Qb::isNotNull('a.author_id'),
      default                  => null,
  });

Прочие методы GridColumn

Метод Возвращает Что делает
isFilterable() bool объявлен ли filterable()
isSortable() bool объявлен ли sortable()
orderExpr() string выражение для ORDER BY — своё или основное
resolveFilter(MGFilterItem $item, TextMatch $mode) ?Qb строит условие для элемента фильтра; вызывается схемой

Публичные свойства: $field и $sql — доступны только для чтения.


MuiGridRequest

Конверт запроса таблицы. Гидрируется слоем запросов Winter из тела JSON; парсить массивы вручную не нужно.

Синтаксис

php
class MuiGridRequest
{
  public function __construct(
      #[Min(0)]             public readonly int $page = 0,
      #[Min(1), Max(10000)] public readonly int $pageSize = 20,
      #[ListOf(MGSortItem::class)] public readonly array $sortModel = [],
      #[Valid] public readonly MGFilterModel $filterModel = new MGFilterModel(),
  ) {
  }

  public function offset(): int;   // $page * $pageSize
}

Свойства

$page — индекс страницы с нуля. Отрицательное значение отклоняется валидацией.

$pageSize — число строк на странице. Допустимо от 1 до 10 000; верхняя граница защищает от запроса «отдай всё» под видом пагинации.

$sortModel — список MGSortItem, гидрируется через #[ListOf].

$filterModel — вложенный MGFilterModel, проверяется через #[Valid]. Свойство не допускает null и имеет пустое значение по умолчанию: отсутствующий в теле filterModel станет пустой моделью, а явный "filterModel": null — ошибкой 400.

Наследование

Параметры конструктора в PHP не наследуются, поэтому в дочернем классе конверт объявляется заново и передаётся в parent::__construct(). Атрибуты #[ListOf] и #[Valid] на переобъявленных параметрах обязательны — без них гидратор не построит вложенные типы. Свойство filterModel объявляйте как MGFilterModel $filterModel = new MGFilterModel(), а не ?MGFilterModel = null: передача null в неnullable-родителя даст TypeError.

Базовый класс помечает readonly каждое свойство по отдельности, а не весь класс — именно чтобы наследник мог нормализовать свои поля в теле конструктора.

Пример

php
class ArticleGridRequest extends MuiGridRequest
{
  public function __construct(
      #[Positive] public ?int $authorId = null,

      int $page = 0,
      int $pageSize = 20,
      #[ListOf(MGSortItem::class)] array $sortModel = [],
      #[Valid] MGFilterModel $filterModel = new MGFilterModel(),
  ) {
      parent::__construct($page, $pageSize, $sortModel, $filterModel);
  }
}

MuiGridResponse

Ответ, который возвращает MuiGrid::wrap(). Финальный, readonly, реализует JsonSerializable.

Синтаксис

php
final readonly class MuiGridResponse implements JsonSerializable
{
  public int $rowCount;   // всего строк под фильтром, без учёта страницы
  public array $rows;     // текущая страница, уже после маппера

  public function toArray(): array;
}

Свойства

$rowCount — общее число строк, подходящих под фильтр. Именно это значение нужно пагинатору таблицы; размер страницы здесь ни при чём.

$rows — строки текущей страницы. Если в wrap() передавался маппер, они уже преобразованы.

Пример

php
return ResponseEntity::ok($response->toArray());
// или, поскольку класс JsonSerializable:
return ResponseEntity::ok($response);

Результат

json
{ "rowCount": 134, "rows": [ /* … */ ] }

DTO запроса

Три небольших readonly-класса, из которых состоит запрос. Обычно вы встречаете их только внутри резолвера filterUsing().

MGSortItem

Одна инструкция сортировки — { field, sort }.

php
readonly class MGSortItem
{
  public string $field;   // имя поля из браузера
  public string $sort;    // 'asc' | 'desc' — проверяется через #[In]

  public function isDesc(): bool;
}

Направление проверяется на этапе гидрации и принимается в любом регистре; отсутствующее направление трактуется как по возрастанию.

MGFilterItem

Одна инструкция фильтра — { field, operator, value }.

php
readonly class MGFilterItem
{
  public string     $field;     // имя поля из браузера
  public MGOperator $operator;  // enum, приводится из строки запроса
  public mixed      $value;     // значение как прислал клиент
}

operator приводится к перечислению прямо при гидрации: неизвестная строка оператора отклоняется ещё до того, как запрос доберётся до схемы.

MGFilterModel

Модель фильтров — { logicOperator, items }.

php
readonly class MGFilterModel
{
  /** @var MGFilterItem[] */
  public array  $items;          // #[ListOf(MGFilterItem::class)]
  public string $logicOperator;  // 'and' | 'or' — проверяется через #[In], по умолчанию 'and'

  public function isOr(): bool;
}

Пустой items не добавляет к запросу ничего — ваш базовый WHERE остаётся нетронутым.


MuiGridException

Исключение библиотеки. Наследует RequestException ядра, поэтому роутер отвечает 400 и отдаёт сообщение клиенту.

php
class MuiGridException extends RequestException {}

Бросается в трёх случаях: поля нет в схеме, поле объявлено, но не включено для фильтрации, оператор не входит в набор FilterType колонки. Его же удобно бросать из собственного резолвера, когда значение фильтра не годится.

Пример

json
{ "message": "Operator 'contains' is not allowed for field 'views'" }

Что дальше