krystal-sf / ux-datatables
DataTables for Krystal Symfony Projects
Package info
gitlab.com/krystal-sf/ux-datatables
Type:symfony-bundle
pkg:composer/krystal-sf/ux-datatables
Requires
- php: ^8.1
- krystal-sf/doctrine: ^1.0
- krystal-sf/ux-common: ^1.0
- krystal-sf/ux-menus: ^1.0
- symfony/property-access: ^6.4|^7.4|^8.0
- webmozart/assert: ^1.0
Requires (Dev)
- badpixxel/php-sdk: ^3.0
- doctrine/mongodb-odm: ^2.6
- doctrine/orm: ^2.15|^3.0
- krystal-sf/ux-bootstrap-5: ^1.0
- krystal-sf/ux-metadata: ^1.0
- krystal-sf/ux-prism: ^1.0
- openspout/openspout: ^4.0
- phpunit/phpunit: ^10.0|^11.0
- symfony/debug-bundle: ^6.4|^7.4|^8.0
- symfony/monolog-bundle: ^3.0|^4.0
- symfony/stopwatch: ^6.4|^7.4|^8.0
- symfony/web-profiler-bundle: ^6.4|^7.4|^8.0
Suggests
- doctrine/mongodb-odm: Documents mode: Doctrine MongoDB ODM adapter & searchable repositories
- doctrine/orm: Entities mode: Doctrine ORM adapter & searchable repositories
- krystal-sf/ux-bootbox: Confirmation & progress dialogs for batch actions
- mongodb/mongodb: Raw MongoDB collections adapter (without the ODM)
- openspout/openspout: Excel (xlsx) server side exporter
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.
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:
- 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 theDatatableStore(cache, random key, 24h TTL). - The Stimulus controller initializes DataTables.net synchronously on the
rendered
<table>skeleton - no extra "init" round trip. - DataTables.net calls
/datatable/serve/{alias}/{key}: theServeaction 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 columnfieldoptions (associations joined on the fly). Options:entity(required),hydrate,query&criteriaprocessors.TextColumnsearches default to a case insensitive "contains" (LOWER(field) LIKE %term%), whatever the database collation - override per column viaoperator/leftExpr/rightExpr.FetchJoinORMAdapter- same asORMAdapterbut counts & paginates through the Doctrine Paginator: REQUIRED when the query fetch-joins to-many collections. Extrasimple_total_queryoption for a faster total count when the base query has no criteria.DoctrineOrmAdapter- repository-driven: your repository implementsSearchableEntityRepositoryInterface(viaSearchableEntityRepositoryTrait), 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 dependencyexcel- xlsx, requiresopenspout/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