flytachi/winter-mui-data-grid

Server-side MUI DataGrid adapter for Winter Framework (K2) — declarative schema: pagination, filtering, sorting and column whitelisting out of the box.

Maintainers

Package info

github.com/Flytachi/winter-mui-data-grid

Homepage

Documentation

pkg:composer/flytachi/winter-mui-data-grid

Statistics

Installs: 50

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v2.0.0 2026-05-31 00:44 UTC

This package is auto-updated.

Last update: 2026-05-31 01:00:42 UTC


README

Latest Version on Packagist Software License PHP

Server-side adapter that connects MUI X DataGrid to the Winter Framework (K2).

You declare a column schema — which fields may be filtered and sorted, and how they map to SQL. The library takes the grid's request (page, sort model, filter model), enforces that schema as a whitelist, builds a parameterized query on top of your repository, and returns a { rowCount, rows } payload the DataGrid understands. SQL-injection safe by construction.

$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)->toArray();

Requirements

Installation

composer require flytachi/winter-mui-data-grid

How it works (60 seconds)

A grid request has three moving parts: pagination, sorting and filtering. This library handles all three around a query you build yourself:

   ┌─────────────────────── your code ───────────────────────┐
   │ build repo: SELECT + JOINs + base WHERE                  │
   │ declare GridSchema: field → SQL, filter type, sortable   │
   └──────────────────────────┬──────────────────────────────┘
                              │  MuiGrid::wrap(repo, request, schema, mapper?)
   ┌──────────────────────────▼──────────────────────────────┐
   │ library:                                                 │
   │  • validate filters against the schema (whitelist)       │
   │  • append WHERE (filters) + ORDER BY (sort) to your repo  │
   │  • run COUNT + paginated SELECT                           │
   │  • map each row (optional) → MuiGridResponse{rowCount,rows}│
   └─────────────────────────────────────────────────────────┘

The schema is the contract: a field the frontend references that you did not declare is rejected (for filtering) or ignored (for sorting). Operators are gated by each column's FilterType, so a text operator can't be sent against a numeric column.

Quick start

A minimal "list articles" endpoint. Two tables: articles a joined to authors au.

1. Request

Extend MuiGridRequest. The grid envelope (page, pageSize, sortModel, filterModel) is hydrated for you by the K2 request layer — add only your own domain filters:

use Flytachi\Winter\K2\Http\Request\Validation\ListOf;
use Flytachi\Winter\K2\Http\Request\Validation\Valid;
use Flytachi\Winter\MuiDataGrid\Entity\MGFilterModel;
use Flytachi\Winter\MuiDataGrid\Entity\MGSortItem;
use Flytachi\Winter\MuiDataGrid\MuiGridRequest;

class ArticleGridRequest extends MuiGridRequest
{
    public function __construct(
        public ?int $authorId = null,            // a domain filter of your own

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

2. Service

You own the query; the library overlays the grid concerns:

use Flytachi\Winter\Cdo\Qb;
use Flytachi\Winter\MuiDataGrid\MuiGrid;
use Flytachi\Winter\MuiDataGrid\MuiGridResponse;
use Flytachi\Winter\MuiDataGrid\Schema\FilterType;
use Flytachi\Winter\MuiDataGrid\Schema\GridColumn;
use Flytachi\Winter\MuiDataGrid\Schema\GridSchema;

class ArticleService
{
    public function grid(ArticleGridRequest $request): MuiGridResponse
    {
        $repo = ArticleRepository::instance('a')
            ->select('a.id, a.title, a.views, a.created_at, au.name author_name')
            ->joinLeft(AuthorRepository::instance('au'), 'au.id = a.author_id')
            ->where(Qb::eq('a.is_published', true));

        if ($request->authorId) {
            $repo->andWhere(Qb::eq('a.author_id', $request->authorId));
        }

        $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(),
            GridColumn::for('createdAt',  'a.created_at')->filterable(FilterType::Date)->sortable(),
        )->defaultOrder('a.created_at DESC');

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

3. Controller

#[PostMapping]
public function grid(
    #[RequestJson, Valid] ArticleGridRequest $request
): ResponseEntity {
    return ResponseEntity::ok(
        $this->service->grid($request)->toArray()
    );
}

4. The request the frontend sends

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

5. The response

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

rowCount is the total matching the filter (for the grid's pager); rows is the current page.

Documentation

Guide What's inside
Getting started End-to-end walkthrough, request/response shape, frontend wiring.
Schema & columns GridSchema, GridColumn, mapping fields to SQL, whitelisting.
Filtering FilterType, operator gating, per-column overrides, AND/OR logic.
Sorting Sort model, custom ORDER BY expressions, default order.
Requests & responses Extending MuiGridRequest, the entity DTOs, MuiGridResponse.
Recipes Joins, subquery search, mappers, category-tree / EXISTS patterns.

At a glance

// Entry point
MuiGrid::wrap(
    RepositoryViewInterface $repo,     // your pre-built query (SELECT/JOIN/base WHERE)
    MuiGridRequest          $request,  // the grid request DTO
    GridSchema              $schema,   // the filter/sort whitelist  (required)
    ?callable               $mapper = null,  // optional fn(object $row): mixed
): MuiGridResponse;                    // { rowCount, rows }  (JsonSerializable)
// Schema building blocks
GridColumn::for('muiField', 'sql.column')
    ->filterable(FilterType::String)         // allow filtering, gate operators by type
    ->sortable()                             // allow sorting by sql.column
    ->sortable('lower(sql.column)')          // ...or by a custom expression
    ->filterUsing(fn(MGFilterItem $i): ?Qb => …);  // per-column operator override

GridSchema::make(GridColumn …)->defaultOrder('sql ASC');
FilterType Allowed operators
String contains, notContains, startsWith, endsWith, equals, is, not, =, !=, isAnyOf, isEmpty, isNotEmpty
Number equals, is, not, =, !=, >, >=, <, <=, isAnyOf, isEmpty, isNotEmpty
Boolean is, equals, =, isEmpty, isNotEmpty
Date is, not, equals, =, !=, after, onOrAfter, before, onOrBefore, isEmpty, isNotEmpty

License

MIT — Flytachi