netteam/datatable-bundle
Netteam Datatable Bundle is a PHP package for building sortable, paginated, filterable data tables. Configure columns, actions, and server-side data sources to render reusable grids with minimal setup in your application.
This is a zero-star, zero-dependent bundle with no community footprint, so treat the repository itself as the only documentation. Budget an hour to read src/ before writing any application code.
composer.json and check the symfony/* constraints and the last commit date. A bundle of this vintage is likely written for Symfony 2/3/4-era APIs (KernelInterface::registerBundles, Resources/config/services.xml, maybe @Route annotations). If you are on Symfony 6/7, expect to fork or vendor it rather than composer require it cleanly.composer require netteam/datatable-bundle
Then register the bundle in config/bundles.php (or AppKernel::registerBundles() on legacy apps). The class name will be NetTeam\DataTableBundle\NetTeamDataTableBundle — verify the exact namespace in src/ before assuming.README.md — usually a stub for packages like this; skim only.src/ — the core DataTable/Column/Type abstractions. This is your real API reference.DependencyInjection/Configuration.php + *Extension.php — tells you the config key (often differs from the bundle name) and any tunables like default page size or template.Resources/config/ — service definitions and any tagged services (look for a data_table.type-style tag; that is your extension point).Resources/views/ — the Twig template you will be overriding.One table class per listing. The idiomatic shape is a dedicated class (or service definition) per table, configured declaratively rather than built inline in the controller. Expect something like:
// Shape of the API — confirm exact method names against src/
class UserTable extends AbstractDataTable
{
public function build(DataTableBuilder $builder): void
{
$builder->add('id', 'integer', ['sortable' => true]);
$builder->add('email', 'text');
$builder->add('createdAt', 'datetime', ['format' => 'Y-m-d']);
}
}
Controller as thin glue. The controller should only: obtain the table from a factory/registry, bind the request (page, sort, search), and hand the result to Twig. Keep query construction inside the table class so the same table can be reused across an index page and an export or API endpoint.
$table = $this->get('netteam_datatable.factory')->create(UserTable::class, $qb);
$table->handleRequest($request);
return $this->render('admin/users.html.twig', ['table' => $table]);
Base class for boilerplate. Because you'll repeat the same id / createdAt / actions columns everywhere, create your own App\DataTable\AbstractAdminTable extending the bundle's base and add shared columns plus a default query-builder factory. This also gives you a single seam to swap the package out later.
Formatter/renderer for cells. Use the bundle's column formatter hook for anything non-scalar (relations, enums, status badges) instead of branching in Twig. If no formatter abstraction exists, wrap the column value in a small value object and render that in an overridden template.
Template override, not template edits. Copy the bundle's Twig file into templates/bundles/NetTeamDataTableBundle/ (path segment must match the bundle name exactly) and customize there. Never edit vendor/.
Wrap it behind your own interface. Given the maintenance risk, expose the table to your app through App\DataTable\TableFactoryInterface rather than injecting the bundle's factory directly. When the bundle inevitably stops working on a future Symfony, you rewrite one adapter class instead of every controller.
src/ or a private VCS repo from day one.getContainerExtension(), which is frequently something like net_team_data_table while the bundle class is NetTeamDataTableBundle. If config/packages/*.yaml is rejected, run debug:config against the guessed aliases or read the extension directly.sort parameter, whitelist it against the configured column names. Naive bundles pass the column straight into ORDER BY, which is an injection vector or at minimum an error on unknown columns.$user->getCompany()->getName() fires a query per row. Join and select the relation in the query builder you hand the table, or use a custom formatter that reads a preloaded field.bin/console debug:container --tag=<tag_from_Resources/config> is the fastest way to confirm your table class is actually registered and autowired.composer.json (no ^ range) the moment you have it working. An unmaintained dependency drifting to a new patch is a broken deploy waiting to happen.How can I help you explore Laravel packages today?