rollerworks/datagrid
Rollerworks Datagrid is a PHP library for building configurable data grids with columns, sorting, filtering, and pagination. It helps developers present tabular data cleanly in web applications, with a flexible API for defining grids and integrating them into existing PHP projects.
Install (with caution):
composer require rollerworks/datagrid
Before writing a single line, check composer.json in the repo — the last release is from March 2017, targets the Symfony 2.7/3.x era, and pins symfony/options-resolver, symfony/event-dispatcher, symfony/form, symfony/property-access, and pagerfanta/pagerfanta at old majors. On a modern Laravel app (PHP 8.x, Symfony 6/7 components) you will almost certainly need --ignore-platform-reqs and a compatibility fork, or you should treat this as reference material rather than a dependency.
Look here first:
src/Column/Type/ — the shipped column types (TextType, NumberType, DateTimeType, BooleanType, ActionType, etc.).src/Column/Extension/Core/ — CoreExtension registers those types and the default Symfony\Component\Form-style options resolvers.src/Data/ (data sources) and src/Extension/ (grid extensions / events).tests/ — the fastest way to see the intended wiring end-to-end.src/Twig/ — the DatagridExtension and default theme templates; this is what rendering expects.First use case — the smoke test:
use Rollerworks\Component\Datagrid\DatagridFactory;
use Rollerworks\Component\Datagrid\Column\Extension\CoreExtension;
use Rollerworks\Component\Datagrid\Column\Type;
$factory = DatagridFactory::create();
$factory->addColumnExtension(new CoreExtension());
$grid = $factory->createDatagrid('users');
$grid->addColumn('id', Type\NumberType::class);
$grid->addColumn('name', Type\TextType::class);
$grid->setDataSource($arraySource); // ArraySource wrapping your rows
$grid->handleRequest($request); // Symfony HttpFoundation Request
$view = $grid->createView(); // pass to Twig's datagrid() function
Wire this into a Laravel service provider as a singleton (see below) so the factory is bootstrapped once per request.
1. Factory as a container singleton. The DatagridFactory is expensive to build (extension registry, type extensions, option resolvers). Register it once:
// app/Providers/DatagridServiceProvider.php
$this->app->singleton(DatagridFactory::class, function () {
$factory = DatagridFactory::create();
$factory->addColumnExtension(new CoreExtension());
// register your own types here
return $factory;
});
Then inject it into controllers/actions. Treat the factory as immutable — never mutate the registry mid-request.
2. Column types mirror Symfony form types. addColumn('name', TextType::class, ['label' => 'Full name']) — the third argument is an options array resolved by an OptionsResolver. Define custom column types by extending AbstractType (or AbstractColumnType) and implementing buildView, buildHeaderView, getParent, and configureOptions. This is the primary extension point: if you need a currency column, a status badge, or a link column, you write a type, not a template.
3. Data sources decouple the grid from the ORM. ArraySource for in-memory arrays, Doctrine\ORM\Source (or your own DataSourceInterface) for entity queries. The grid asks the source to apply sorting/filtering/pagination via DataSourceInterface::getData(). Implement DataSourceInterface yourself to bridge into Eloquent/Query Builder — the grid only needs getData(int $offset, int $limit) plus sort/filter metadata.
4. Request handling + Pagerfanta. $grid->handleRequest($request) reads page, sort, direction, and per-column filter params off the request. Pagination is delegated to Pagerfanta adapters, so supply a Pagerfanta\Adapter\DoctrineORMAdapter (or array adapter) through the data source. In Laravel, hand it the Illuminate\Http\Request wrapped in a Symfony Request (Laravel's request is a Symfony request, so this works).
5. Grid builder pattern for reuse. Instead of instantiating datagrids in each controller, build a UserGrid class with a build(DatagridInterface $grid) method that adds columns and options; call $builder->build($factory->createDatagrid('users')). This is the closest thing the package has to a "grid definition" and keeps controllers thin.
6. Events for cross-cutting concerns. The grid dispatches DatagridEvents::VIEW_CREATE (and column-level events) with a DatagridViewEvent. Use these for adding bulk-action columns, injecting totals, or per-row action URLs without touching every grid definition.
7. Rendering. The shipped DatagridExtension provides Twig functions (datagrid, datagrid_css, datagrid_js, etc.) and expects a specific view object. In a Blade app you either (a) add the Twig bridge and render only the grid partial from a Blade @include, or (b) write a small Blade renderer that iterates $view->columns and $view->rows — the ColumnView/RowView API is straightforward, and this is usually the cleaner choice for Laravel.
It is archived and unmaintained. No security fixes, no PHP 8 support in released tags, no Symfony 5+ compatibility. Do not introduce it into a new production codebase without a fork; treat it as a design reference. The 2017 date on the release means every transitive dependency is a CVE liability.
Unregistered type = runtime exception. If addColumn('foo', MyType::class) throws "unknown column type", either the type wasn't registered via addColumnExtension()/addType() or you're passing a string alias that doesn't match getName(). Register custom types in the factory singleton, not per-request.
The name is the identifier, the option label is the display. Two columns cannot share a name. This bites when you add created_at twice (one formatted, one raw) — use distinct names or two separate grids.
setData vs. dataSource are different paths. Passing raw rows to setData() bypasses the data source's sorting/filtering. If you expect handleRequest sorting to work, you must register a data source that understands the sort directions — otherwise the grid silently ignores ?sort=name&direction=desc. A very common "sorting does nothing" bug.
Filtering is opt-in per column. Adding a column does not make it filterable unless the type declares filter options (or you enable them). Check the type's OptionsResolver defaults; many types default filterable => false.
Pagerfanta version lock-in. The released constraint targets Pagerfanta 1.x (Pagerfanta\Adapter\...), not the namespaced Pagerfanta\Pagerfanta API introduced in 2.x. If you pull a newer pagerfanta you will get class-not-found errors at pagination time — usually the first crash you hit, not at construction.
Symfony form dependency is invisible until it isn't. Column types are built on Symfony\Component\Form\AbstractType semantics, so they drag in symfony/property-access and symfony/options-resolver. In a Laravel app you'll be maintaining two view layers (Blade + Symfony forms for column config). Consider implementing the ColumnTypeInterface directly instead of extending the Symfony form base if you only need view data.
Debugging tip: dump the view, not the grid. Most "the column doesn't render" issues are in DatagridView/ColumnView (isVisible, attr, value), not in the grid definition. dump($grid->createView()->columns['name']) will show you the resolved options, header attrs, and cell value — infinitely more useful than stepping through the data source.
Extension points, in order of usefulness: (1) custom column types for domain-specific cells; (2) a custom DataSourceInterface to bridge Eloquent; (3) event listeners on VIEW_CREATE for bulk actions; (4) custom Twig/Blade templates for layout. Avoid monkey-patching the factory or subclassing Datagrid — the internals are not designed for it and there's no upstream to fix regressions.
How can I help you explore Laravel packages today?