Installation:
composer require omines/datatables-bundle
Ensure DataTablesBundle is registered in config/bundles.php (Symfony Flex handles this automatically).
Assets:
php bin/console assets:install
Include DataTables CSS/JS in your template (see README).
First Controller:
use Omines\DataTablesBundle\DataTableFactory;
use Omines\DataTablesBundle\Column\TextColumn;
public function index(Request $request, DataTableFactory $factory)
{
$table = $factory->create()
->add('name', TextColumn::class)
->createAdapter(\Omines\DataTablesBundle\Adapter\ArrayAdapter::class, [
['name' => 'Alice'], ['name' => 'Bob']
])
->handleRequest($request);
return $this->render('index.html.twig', ['table' => $table]);
}
Frontend:
<div id="users"></div>
<script>
$(function() {
$('#users').initDataTables({{ datatable_settings(table) }});
});
</script>
$table->add('name', TextColumn::class);
$table->add('fullName', TextColumn::class, [
'field' => 'firstName', // Override field name
'label' => 'Full Name', // Custom label
]);
$table->add('company', TextColumn::class, [
'field' => 'company.name', // Dot notation for relationships
]);
$table->createAdapter(ArrayAdapter::class, [['id' => 1, 'name' => 'Foo']]);
$table->createAdapter(\Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter::class, [
'entity' => User::class,
'queryBuilder' => $customQueryBuilder, // Optional
]);
$table->createAdapter(\Omines\DataTablesBundle\Adapter\ElasticaAdapter::class, [
'client' => $elasticaClient,
'index' => 'users',
]);
$table->handleRequest($request);
if ($table->isCallback()) {
return $table->getResponse(); // Return JSON for AJAX
}
$table->setCriteriaProvider(new CustomCriteriaProvider());
return $this->render('template.html.twig', ['table' => $table]);
# config/packages/datatables.yaml
datatables:
template: 'AppBundle:DataTables:custom_template.html.twig'
$table->addOrderBy('name', DataTable::SORT_DESCENDING);
initDataTables({ processing: true, serverSide: true });
Doctrine ORM Joins:
user.company.name), ensure the relationship is properly mapped in Doctrine. The AutomaticQueryBuilder may fail silently if joins are missing.QueryBuilder:
$qb = $entityManager->createQueryBuilder()
->leftJoin('u.company', 'c');
$table->setQueryBuilder($qb);
Case Sensitivity in Search:
SearchCriteriaProvider performs case-sensitive searches. Use LIKE with LOWER() for case-insensitive:
$table->setCriteriaProvider(new CustomCriteriaProvider([
'search' => function ($queryBuilder, $search) {
return $queryBuilder->andWhere("LOWER(u.name) LIKE LOWER(:search)")
->setParameter('search', "%$search%");
}
]));
Circular References in JSON:
ArrayAdapter with complex objects, ensure no circular references exist (e.g., bidirectional relationships). Use json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) or a custom serializer.Twig datatable_settings:
datatable_settings(table) fails, verify:
DataTable object is passed correctly to the template.Elastica Pagination:
from/size pagination differs from SQL. Ensure your ElasticaAdapter uses search() with from/size:
$results = $client->getIndex('users')->search([
'from' => $start,
'size' => $length,
'query' => $query,
]);
Log Raw Requests: Dump the DataTables request payload to debug issues:
$requestData = json_decode($request->getContent(), true);
file_put_contents('debug/datatables.json', json_encode($requestData));
QueryBuilder Debugging: For Doctrine ORM, log the generated SQL:
$qb = $entityManager->getConnection()->getWrappedConnection()->prepare('...');
$qb->execute();
var_dump($qb->getSQL());
Adapter-Specific Issues:
NotFoundException if the entity class is incorrect.Custom Adapters:
Implement Omines\DataTablesBundle\Adapter\AdapterInterface for new data sources (e.g., Redis, GraphQL):
class RedisAdapter implements AdapterInterface {
public function count($criteria) { ... }
public function fetch($criteria) { ... }
}
Criteria Providers:
Extend Omines\DataTablesBundle\Criteria\CriteriaProviderInterface to modify search/sort logic:
class CustomCriteriaProvider implements CriteriaProviderInterface {
public function getCriteria(DataTable $table, Request $request) {
$criteria = parent::getCriteria($table, $request);
$criteria['customFilter'] = $request->query->get('filter');
return $criteria;
}
}
Column Types:
Create reusable column classes (e.g., DateColumn, BooleanColumn):
class DateColumn extends TextColumn {
public function getValue($data) {
return (new \DateTime($data))->format('Y-m-d');
}
}
Twig Extensions:
Override datatable_settings in your bundle’s Twig extension:
$twig->addFunction(new \Twig\TwigFunction('custom_datatable_settings', function ($table) {
return json_encode($table->getOptions() + ['customOption' => true]);
}));
Doctrine ORM:
DISTINCT for duplicate rows:
$qb->distinct();
select() to avoid N+1 queries:
$qb->select('u.id', 'u.name', 'c.name AS company_name');
Elastica:
searchType: 'count' for large datasets to avoid fetching all results.Elastica\Scroll.ArrayAdapter:
$adapter->setData(array_slice($data, $start, $length));
How can I help you explore Laravel packages today?