internations/datagrid-bundle
Internations DataGrid Bundle is a Symfony bundle for defining and rendering reusable data grids, with configurable columns, sorting, filtering, and pagination for Doctrine-backed admin and listing pages.
This is a small, Symfony2-era bundle kept alive by InterNations. Treat the source as the documentation: read README.md, then DataGrid/DataGridFactory.php, DataGrid/DataGridBuilderInterface.php, DataSource/DoctrineORMDataSource.php, ColumnType/, and Resources/views/DataGrid/. The class namespace is DataGrid\… (not InterNations\…); confirm the bundle class name in vendor/internations/datagrid-bundle/DataGridBundle.php before registering it.
Version note (4.0.2). The only change in 4.0.2 is declared Symfony 5.4 compatibility. That is a constraint/metadata fix, not an API change — the grid API, service ids, and Twig theme are unchanged from 4.0.x. Practical reading: the supported target is Symfony 5.4 LTS. Nothing in the release claims Symfony 6.x or 7.x support, so treat 6+ as unverified and budget for the get()/public-service and return-type issues described in Gotchas before assuming it works.
Install and register
composer require internations/datagrid-bundle
Check composer.json constraints first: on 4.0.2 you should see symfony/* pinned in the ^5.4 range. If your app is on Symfony 6 or 7, either stay on 5.4 for this bundle, fork it, or pick a maintained alternative.
There is likely no Flex recipe, so register manually:
// config/bundles.php
return [
// ...
DataGrid\DataGridBundle::class => ['all' => true],
];
First use case: a Doctrine list page
Define a grid type exactly like a Symfony form type:
namespace App\DataGrid;
use DataGrid\DataGrid\AbstractDataGridType;
use DataGrid\DataGrid\DataGridBuilderInterface;
class ProductDataGridType extends AbstractDataGridType
{
public function buildDataGrid(DataGridBuilderInterface $builder, array $options): void
{
$builder
->addColumn('id', 'number')
->addColumn('name', 'text')
->addColumn('price', 'money', ['currency' => 'EUR'])
->addColumn('createdAt', 'datetime', ['format' => 'Y-m-d H:i'])
->addColumn('actions', 'actions', [
'actions' => [
'edit' => [
'route' => 'product_edit',
'route_parameters' => ['id' => 'id'],
],
],
]);
}
}
Create it in the controller from a Doctrine QueryBuilder:
$qb = $productRepository->createQueryBuilder('p');
// Service id in the bundle is typically data_grid.factory.
// In modern Symfony, alias/inject DataGrid\DataGrid\DataGridFactoryInterface if autowiring is set up.
$grid = $this->container->get('data_grid.factory')
->createDataGrid(ProductDataGridType::class, $qb);
return $this->render('product/index.html.twig', ['grid' => $grid]);
Render it with the Twig function:
{{ datagrid(grid) }}
That single call gives you a sortable, paginated Bootstrap table. If that is all you need, adoption is quick.
Grid types as reusable UI components
Mirror the form-type workflow: one class per list view, options passed through createDataGrid($type, $data, $options), defaults in configureOptions(OptionsResolver $resolver). This keeps controller actions thin and makes grids testable and reusable across admin and public controllers.
Choose or write a data source
The bundle centres on a DataSourceInterface; the Doctrine ORM source accepts a QueryBuilder, and there are collection/array sources for in-memory lists. For APIs, DBAL, or Elasticsearch, implement the interface:
use DataGrid\DataSource\DataSourceInterface;
use DataGrid\DataGrid\DataGridInterface;
final class ApiDataSource implements DataSourceInterface
{
public function getData(DataGridInterface $dataGrid): array
{
return $this->client->fetch([
'page' => $dataGrid->getPage(),
'limit' => $dataGrid->getMaxPerPage(),
'sort' => $dataGrid->getSortField(),
'order' => $dataGrid->getSortOrder(),
]);
}
public function getTotalCount(DataGridInterface $dataGrid): int
{
return $this->client->count();
}
}
Verify exact method names against the installed interface before writing this — this is the kind of package where signatures matter more than docs.
Sorting and pagination
Sorting is driven by request query parameters (sort, order, page, and a limit parameter) that the bundled template emits and the factory/grid reads. Mark columns sortable (or leave the default) and set sensible defaults in buildDataGrid if the builder exposes setDefaultSort() / setMaxPerPage():
$builder->setMaxPerPage(25);
$builder->setDefaultSort('createdAt', 'desc');
Because sorting is request-driven, the controller usually only needs to build the QueryBuilder; the grid applies orderBy and limit/offset. That is the main day-to-day win.
Actions and translations
The actions column type maps row properties to route parameters. The values in route_parameters are property names on the row, not literals — so ['id' => 'id'] means “take the row’s id getter”. The translation column type translates a value with the translator, with a domain option. Both types are good candidates for copying if your UI needs different markup.
Themes and custom rendering
The bundle ships Twig themes (typically Bootstrap 3 markup, no CSS). For Bootstrap 5 or Tailwind, copy the bundled template into your app, adjust classes, and point the bundle’s data_grid theme configuration at it — confirm the exact config key in DependencyInjection/Configuration.php. If the theme system fights you, bypass it: the grid object exposes columns, data, page, and sort state, so you can render your own table in a few lines of Twig.
Multiple grids and filters
For dashboards with several grids, check whether the grid supports distinct request parameter names. If not, put each grid on its own route or namespace the query parameters yourself. There is no built-in filter/search layer — filters belong in the QueryBuilder you pass in (read $request->query and add andWhere clauses). If you do this, make sure the theme’s sort/pagination links preserve your filter parameters, otherwise paging will silently drop them.
Testing
Boot a kernel test, fetch the factory, and build the grid with an ArrayDataSource:
$grid = $factory->createDataGrid(ProductDataGridType::class, new ArrayDataSource($fixtures));
$this->assertSame(['id', 'name', 'price'], array_keys($grid->getColumns()));
$this->assertStringContainsString('Widget', (string) $grid->render());
Unit-test custom column types and data sources directly; functional-test rendering with a request carrying page/sort parameters.
Pin the patch line
4.0.2 is a compatibility patch, so it is the safe floor for a Symfony 5.4 app. Pin ^4.0 (or 4.0.*) in composer.json and commit the lock file — with one maintainer-adjacent package and no SemVer guarantees documented in the README, an unpinned constraint is how you discover a behaviour change in production.
$this->get(), non-typed setters, possibly getMockBuilder-era test helpers. Do not assume it installs or boots on 6+. If you must, audit services.xml/services.yml for deprecated aliases and missing return types before upgrading the framework.data_grid.factory are old-style. On Symfony 5.4, $this->get() will fail unless the service is public; add an alias to DataGridFactoryInterface in services.yaml or configure the service as public. Do not assume autowiring aliases exist.getTotalCount() rewrites or clones the QueryBuilder. groupBy, having, distinct, fetch joins, and complex select() clauses can produce wrong counts or SQL errors. For large or complex queries, implement a custom data source with an explicit count query; COUNT(*) on big tables is also slow — cache or approximate where possible.orderBy, setFirstResult, and setMaxResults to the builder you hand it. Clone before reuse, or you will get paginated results in unrelated code paths.category.name or a computed field needs a sort-field mapping option or a custom data source; otherwise you get “unrecognized field” errors.page/sort/order unless parameter names are configurable. Check the grid/template source before designing a dashboard.array column type or a custom column for arrays.format, currency, locale behaviour). Verify timezone handling and whether money expects minor units. Date formats using PHP date() patterns differ from Intl patterns.route_parameters fails at render time. Keep action definitions close to the entity and test them.data_grid.column_type, aliased to a type name); custom data sources cover new backends; custom grid types cover shared configuration. Overriding the theme is the escape hatch for markup.How can I help you explore Laravel packages today?