krystal-sf/ux-datatables

DataTables for Krystal Symfony Projects

Maintainers

Package info

gitlab.com/krystal-sf/ux-datatables

Homepage

Type:symfony-bundle

pkg:composer/krystal-sf/ux-datatables

Transparency log

Statistics

Installs: 2 069

Dependents: 3

Suggesters: 2

Stars: 0

1.0.x-dev 2026-08-10 16:45 UTC

This package is auto-updated.

Last update: 2026-08-10 16:45:05 UTC


README

Server side DataTables.net tables as Symfony UX Live Components: reusable table types, secure ajax serving, Doctrine ORM / ODM adapters, searchable repositories, batch actions & server side exports.

PHP Version Symfony

The datatables core (src/Datatable/) is derived from omines/datatables-bundle (MIT), absorbed & reworked for Krystal: see LICENSE.omines.

Installation

composer require krystal-sf/ux-datatables

Enable the bundle in config/bundles.php:

return [
    // ...
    Ksf\Plugins\Datatables\KsfDatatablesBundle::class => ['all' => true],
];

Then import the bundle routes (ajax serve endpoint) in config/routes.yaml:

ksf_datatables:
    resource:   '@KsfDatatablesBundle/Resources/config/routes.yaml'
    prefix:     /datatable

Source Layout

src/
├── Datatable/              # THE CORE (derived from omines/datatables-bundle)
│   ├── DataTable.php       #   Server side table: columns, adapter, request → response
│   ├── DataTableState.php  #   Decoded request state (pagination, searches, orders)
│   ├── DataTableFactory.php#   Entry point: creates tables from types
│   ├── Instantiator.php    #   Lazy service locators for columns / adapters / types
│   ├── Adapter/            #   Data sources: ArrayAdapter + Doctrine/ (ORM, ODM)
│   ├── Column/             #   Column types: Text, Bool, Number, DateTime, Map,
│   │                       #     Twig, TwigString, Attr, Menu
│   ├── Exporter/           #   Server side exports: CSV (native), Excel (openspout)
│   └── Type/               #   Demo type (SampleDatatableType)
│
├── TwigComponent/          # The <twig:Datatable /> Live Component
├── Actions/Serve.php       # Generic ajax endpoint (alias + stored key)
├── Services/DatatableStore.php # Server side store of mounted tables
├── Attribute/AsDatatable.php   # Opt-in attribute for served types
├── Presets/                # Client config presets (default, compact, minimal,
│                           #   searchable, selectable, exportable...)
├── Dictionary/             # Events, routes & presets constants
├── Helpers/                # DatatableBatchArgs (batch menus glue)
├── Menus/Demo/, Actions/Demo/  # Demo pages (dev env only)
└── Resources/
    ├── public/controllers/datatables_controller.js  # Stimulus controller
    ├── public/datatables.js                         # DataTables.net imports
    └── views/Component/datatable.html.twig          # Component template

Data Flow

Serving (type mode) — the recommended flow:

  1. The <twig:Datatable :type="..." /> component mounts: the table is built server side, its columns & language are resolved into DataTables.net options, and the type + options are saved in the DatatableStore (cache, random key, 24h TTL).
  2. The Stimulus controller initializes DataTables.net synchronously on the rendered <table> skeleton - no extra "init" round trip.
  3. DataTables.net calls /datatable/serve/{alias}/{key}: the Serve action resolves the stored type (checked against its #[AsDatatable] alias), replays the request through the adapter and returns the standard protocol payload (draw, recordsTotal, recordsFiltered, data).

No class name nor options ever travel through client urls.

Direct mode — pass a prebuilt datatable object to the component and handle the callback in your own controller action (see Actions/Demo/Direct): ajax then posts back to the current page url.

Quick Start

Define a reusable table type:

use Ksf\Plugins\Datatables\Attribute\AsDatatable;
use Ksf\Plugins\Datatables\Datatable\Adapter\Doctrine\ORMAdapter;
use Ksf\Plugins\Datatables\Datatable\Column\TextColumn;
use Ksf\Plugins\Datatables\Datatable\Column\DateTimeColumn;
use Ksf\Plugins\Datatables\Datatable\DataTable;
use Ksf\Plugins\Datatables\Datatable\DataTableTypeInterface;

#[AsDatatable("users")]
class UsersTableType implements DataTableTypeInterface
{
    public function configure(DataTable $dataTable, array $options): void
    {
        $dataTable
            ->setName("users")
            ->add('email', TextColumn::class, ['label' => 'Email'])
            ->add('createdAt', DateTimeColumn::class, ['format' => 'd/m/Y'])
            ->createAdapter(ORMAdapter::class, ['entity' => User::class])
        ;
    }
}

Render it anywhere:

<twig:Datatable type="{{ 'App\\Table\\UsersTableType' }}" :presets="['dt-default']" />

The #[AsDatatable("users")] attribute is REQUIRED for ajax serving: it is the opt-in that makes the type resolvable by the serve endpoint.

Adapters (3 Modes)

Raw mode - ArrayAdapter

In-memory arrays: full dataset given to the adapter, sorting / global search (on raw values) / pagination done in PHP. Best for small cached datasets.

$dataTable->createAdapter(ArrayAdapter::class, $rows);

Entity mode - Doctrine ORM

Two adapters, by increasing control:

  • ORMAdapter - generic: give an entity class, the query is built automatically from the column field options (associations joined on the fly). Options: entity (required), hydrate, query & criteria processors. TextColumn searches default to a case insensitive "contains" (LOWER(field) LIKE %term%), whatever the database collation - override per column via operator / leftExpr / rightExpr.

  • FetchJoinORMAdapter - same as ORMAdapter but counts & paginates through the Doctrine Paginator: REQUIRED when the query fetch-joins to-many collections. Extra simple_total_query option for a faster total count when the base query has no criteria.

  • DoctrineOrmAdapter - repository-driven: your repository implements SearchableEntityRepositoryInterface (via SearchableEntityRepositoryTrait), the adapter delegates filtering to it. Options:

$dataTable->createAdapter(DoctrineOrmAdapter::class, [
    'repository'    => $this->usersRepository,
    'filters'       => ['status' => 'active'],   // filtered rows & counts
    'staticFilters' => ['deleted' => false],     // applied to totals too
]);

The global search input is forwarded to the repository as the conventional "query" filter key.

Document mode - Doctrine MongoDB ODM

Symmetric to the repository-driven ORM mode: DoctrineOdmAdapter + SearchableDocumentRepositoryInterface (via SearchableDocumentRepositoryTrait). Requires doctrine/mongodb-odm.

For collections WITHOUT the ODM, the MongoDBAdapter serves a raw MongoDB\Collection (requires mongodb/mongodb only): plain documents as rows, base filters document, case insensitive regex global search.

$dataTable->createAdapter(MongoDBAdapter::class, [
    'collection' => $client->mydb->users,
    'filters'    => ['deleted' => false],
]);

Searchable Repositories & Tagged Filters

The searchable repositories system (filter any repository with a plain [key => value] array, filters as #[AsOrmFilter] / #[AsOdmFilter] tagged services) lives in the standalone krystal-sf/doctrine bundle - pulled automatically as a dependency of this package, and fully documented in packages/core/doctrine/README.md. It is usable from any application code, with or without datatables.

Presets

Client side configuration is applied through Krystal presets on the component (DatatablePresets dictionary): DEFAULT, COMPACT, MINIMAL, SIMPLE (pagination), SEARCHABLE, SELECTABLE, EXPORTABLE. Presets merge dtConfig (native DataTables.net options) & component options - see src/Presets/ for the reference implementations.

<twig:Datatable :type="type" :presets="[DatatablePresets.SELECTABLE, DatatablePresets.SEARCHABLE]" />

Batch Actions

With the SELECTABLE preset, rows become selectable & the component renders a ux-menus context menu (subject + ActionContext::DT_BATCH). Batch buttons are standard menu providers using DatatableBatchArgs to target a Live controller action executed once per selected row, with bootbox progress & per-row status colors. See src/Menus/Demo/Datatable/ for complete examples (plain, confirmed & dropdown variants).

Exports

Server side exporters stream the FULL filtered dataset (pagination lifted) as a file download when the ajax request carries _exporter={name}. On large tables, cap it with the maxExportRows table option ($dataTable = new DataTable($dispatcher, ['maxExportRows' => 10000]) or via the factory options) - the export endpoint is reachable by any client knowing a serve key:

  • csv - native, no dependency
  • excel - xlsx, requires openspout/openspout

Client side, the EXPORTABLE preset adds DataTables.net html5 buttons (CSV / print of the visible page). Custom exporters implement DataTableExporterInterface (auto-tagged ksf.datatable.exporter).

Demo Pages (dev only)

/datatable/ (default), /simple, /compact, /searchable, /selectable, /exportable, /direct - one page per preset / mode, backed by SampleDatatableType.

Testing

make quality    # lint + style + stan
make phpunit    # test suites