pedro-teixeira/grid-bundle
A Symfony bundle for building data grids from Doctrine entities with sortable columns, filters, pagination, bulk actions, and CSV/Excel export. Configure columns, search, and row actions quickly in PHP or annotations for admin and reporting screens.
Reality check first: pedro-teixeira/grid-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/grid.php, and its renderer is Twig-based. Last tagged release was 2015-03-31, so it targets Symfony 2.x and PHP 5.x-era components. For a Laravel developer, "leveraging" this package almost always means mining it for its grid architecture and re-implementing that structure in Laravel idioms — not composer require-ing it into a Laravel app.
Steps:
composer require pedro-teixeira/grid-bundle will almost certainly fail dependency resolution against Laravel 10/11 (which require symfony/* v6/v7) because the bundle pins old Symfony constraints. Don't burn time on --ignore-platform-reqs gymnastics in a real app.app/Support/Grid namespace. One class per table, columns declared in code, data source injected — that's the whole idea, and it translates cleanly to Laravel.UsersGrid class, render it in Blade, and compare line count against your previous hand-rolled <table>.If you genuinely need a maintained grid/table package in Laravel today, evaluate Filament Tables, Orchid, or a Livewire table component instead — and treat grid-bundle as an architectural reference (MIT licensed, so copying patterns or even classes with attribution is fine).
Grid-as-a-class (the core pattern worth stealing). The bundle's value is that column definitions are objects, not template conditionals. Mirror it:
// app/Support/Grid/UsersGrid.php
class UsersGrid extends Grid
{
public function columns(): array
{
return [
TextColumn::make('id')->sortable(),
TextColumn::make('name')->searchable(),
TextColumn::make('email'),
DateColumn::make('created_at')->format('Y-m-d'),
ActionColumn::make()->actions([
'edit' => fn ($row) => route('admin.users.edit', $row),
'delete' => fn ($row) => route('admin.users.destroy', $row),
]),
];
}
public function query(): Builder
{
return User::query()->with('roles'); // eager load here, not in the view
}
}
Value resolution via data_get(). The Symfony original resolves column values by property/accessor. In Laravel use closures plus data_get($row, $path) so nested paths (roles.0.name) and array rows both work without special-casing Eloquent vs. arrays.
Rendering as a Blade component. The bundle ships Twig templates; replace them with <x-grid :grid="$grid" /> that loops $grid->columns() and calls $column->render($row). Keep escaping in the component, not in the column classes — same separation the bundle gives you.
Actions column with authorization. The bundle's "extensible columns and actions" maps to closures that return a URL, plus a canSee closure checked against Gate::allows(). Doing the auth check here (server-side, once) beats scattering @can in Blade — and it makes the action set testable.
Filters and sorting from the request. Keep a whitelist derived from column metadata (->sortable(), ->searchable()) and apply it to the query builder. Never trust ?sort= directly — the whole point of declaring columns is that the column list is your allowlist.
Data source swap: Doctrine → Eloquent. The bundle expects a Doctrine-backed collection. Your port should accept Illuminate\Contracts\Pagination\LengthAwarePaginator so the grid can render its own pagination links and withQueryString(). If you go Inertia or Livewire, keep the grid class as the single source of truth and serialize columns to an array for the frontend.
Extension points. Copy the abstract-column-per-type structure: one base Column with render()/header()/sortKey(), subclasses for Text, Date, Badge, Action, and a ColumnFactory if you want config-driven columns later. This is the piece that pays off — new column types without touching the grid templates.
services.yml, tagged services, and GridBuilder autowiring. Laravel's container has no tag system; bind your grids explicitly ($this->app->bind(UsersGrid::class)) or instantiate them in controllers. If you copy classes wholesale, strip the ContainerAware/AbstractController inheritance first.OptionsResolver is a Symfony dependency. If you want option validation with defaults in your ported columns, use a small hand-rolled $defaults + $options merge or Laravel's Validator; don't drag symfony/options-resolver in just for this (you already have it, but the 2015 API differs from current).{{ }} escapes, {!! !!} doesn't. Give HTML-rendering columns an explicit ->html() flag so reviewers can see where raw output is intentional — this is exactly where XSS creeps into ported grid code.query(). Enable Model::preventLazyLoading() in local to catch it.withQueryString() and preserve filter params in action URLs, or users lose context after an edit redirect — the bundle's Twig templates handle this implicitly, so it's easy to drop when porting.canSee closures; one HTTP test to assert the grid renders. Asserting on table markup makes every CSS tweak a test failure.app/Support/Grid namespace is cheap and avoids awkwardness later.How can I help you explore Laravel packages today?