From aad6ef0621c2422a9f56f85b06d184d7dd1ccecf Mon Sep 17 00:00:00 2001 From: Alexandre Castelain Date: Mon, 26 Jan 2026 12:01:27 +0100 Subject: [PATCH 1/2] Add a way to define a default query for the DataTableType --- docs/src/docs/features/query.md | 115 ++++++++++++++++++ docs/src/docs/introduction.md | 1 + ...esolvedDataTableTypeDataCollectorProxy.php | 5 +- src/DataTableBuilder.php | 3 +- src/DataTableFactory.php | 16 ++- src/Type/ResolvedDataTableType.php | 5 +- src/Type/ResolvedDataTableTypeInterface.php | 3 +- tests/Unit/DataTableBuilderTest.php | 7 +- 8 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 docs/src/docs/features/query.md diff --git a/docs/src/docs/features/query.md b/docs/src/docs/features/query.md new file mode 100644 index 00000000..5e5802de --- /dev/null +++ b/docs/src/docs/features/query.md @@ -0,0 +1,115 @@ +# Query + +The data table requires a query to fetch the data. This query can be passed directly to the factory or defined as a default option in the data table type. + +[[toc]] + +## Ways to handle the query + +There are several ways to pass the query to a DataTable. + +### Passing the query to the factory + +The query can be passed directly to the factory in the controller: + +```php +use App\DataTable\Type\ProductDataTableType; +use Kreyu\Bundle\DataTableBundle\DataTableFactoryAwareTrait; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ProductController extends AbstractController +{ + use DataTableFactoryAwareTrait; + + public function index(Request $request): Response + { + // $query can be a QueryBuilder, an array, etc. + $dataTable = $this->createDataTable(ProductDataTableType::class, $query); + $dataTable->handleRequest($request); + + return $this->render('product/index.html.twig', [ + 'products' => $dataTable->createView(), + ]); + } +} +``` + +### Defining a default value + +You can provide a default value for the `query` option in your DataTable type. This avoids having to recreate the query builder every time you create the DataTable. + +Similar to how Symfony forms allow initializing `data` when no data is provided, you can initialize the `query` option. + +To do this, add the `query` option to your DataTable type: + +```php +use Kreyu\Bundle\DataTableBundle\Type\AbstractDataTableType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use App\Repository\ProductRepository; + +class ProductDataTableType extends AbstractDataTableType +{ + public function __construct( + private ProductRepository $productRepository, + ) { + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'query' => $this->productRepository->createQueryBuilder('p'), + ]); + } +} +``` + +This allows you to define a default configuration that is fully overridable. + +## Overriding the option + +When you define a default `query`, you can still override it when creating the data table in the controller: + +```php +$dataTable = $this->createDataTable(ProductDataTableType::class, $customQuery); +``` + +Or by passing it in the options: + +```php +$dataTable = $this->createDataTable(ProductDataTableType::class, null, [ + 'query' => $customQuery, +]); +``` + +### Extending the default query + +If you want to reuse the default query defined in the `ProductDataTableType` and add a condition from the controller, you can use the `OptionsResolver` normalizer: + +```php +use App\DataTable\Type\ProductDataTableType; +use Kreyu\Bundle\DataTableBundle\DataTableFactoryAwareTrait; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\OptionsResolver\Options; +use Doctrine\ORM\QueryBuilder; + +class ProductController extends AbstractController +{ + use DataTableFactoryAwareTrait; + + public function index() + { + $dataTable = $this->createDataTable(ProductDataTableType::class, null, [ + 'query' => function (Options $options, $query) { + if ($query instanceof QueryBuilder) { + $query->andWhere('p.active = :active') + ->setParameter('active', true); + } + + return $query; + }, + ]); + } +} +``` diff --git a/docs/src/docs/introduction.md b/docs/src/docs/introduction.md index e1f5700e..d8304193 100644 --- a/docs/src/docs/introduction.md +++ b/docs/src/docs/introduction.md @@ -31,6 +31,7 @@ If you want to include your application here, open an issue, create a pull reque - [Exporting](features/exporting.md) with or without applied pagination, filters and personalization - [Theming](features/theming.md) of every part of the bundle using Twig - [Data source agnostic](features/extensibility.md) with Doctrine ORM supported out of the box +- [Query](features/query.md) to fetch the data from any source - [Asynchronicity](features/asynchronicity.md) thanks to integration with Turbo (with prefetching enabled by default) ## Use cases diff --git a/src/DataCollector/Proxy/ResolvedDataTableTypeDataCollectorProxy.php b/src/DataCollector/Proxy/ResolvedDataTableTypeDataCollectorProxy.php index 1637f70c..39d9d88a 100644 --- a/src/DataCollector/Proxy/ResolvedDataTableTypeDataCollectorProxy.php +++ b/src/DataCollector/Proxy/ResolvedDataTableTypeDataCollectorProxy.php @@ -9,7 +9,6 @@ use Kreyu\Bundle\DataTableBundle\DataTableFactoryInterface; use Kreyu\Bundle\DataTableBundle\DataTableInterface; use Kreyu\Bundle\DataTableBundle\DataTableView; -use Kreyu\Bundle\DataTableBundle\Query\ProxyQueryInterface; use Kreyu\Bundle\DataTableBundle\Type\DataTableTypeInterface; use Kreyu\Bundle\DataTableBundle\Type\ResolvedDataTableTypeInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -42,9 +41,9 @@ public function getTypeExtensions(): array return $this->proxiedType->getTypeExtensions(); } - public function createBuilder(DataTableFactoryInterface $factory, string $name, ?ProxyQueryInterface $query = null, array $options = []): DataTableBuilderInterface + public function createBuilder(DataTableFactoryInterface $factory, string $name, array $options = []): DataTableBuilderInterface { - $builder = $this->proxiedType->createBuilder($factory, $name, $query, $options); + $builder = $this->proxiedType->createBuilder($factory, $name, $options); $builder->setAttribute('data_collector/passed_options', $options); $builder->setType($this); diff --git a/src/DataTableBuilder.php b/src/DataTableBuilder.php index 8f49c975..fe9db0f6 100755 --- a/src/DataTableBuilder.php +++ b/src/DataTableBuilder.php @@ -136,10 +136,11 @@ class DataTableBuilder extends DataTableConfigBuilder implements DataTableBuilde */ private array $unresolvedExporters = []; + private ?ProxyQueryInterface $query = null; + public function __construct( string $name, ResolvedDataTableTypeInterface $type, - private ?ProxyQueryInterface $query = null, EventDispatcherInterface $dispatcher = new EventDispatcher(), array $options = [], ) { diff --git a/src/DataTableFactory.php b/src/DataTableFactory.php index 54a4b5b3..9d1ce947 100755 --- a/src/DataTableFactory.php +++ b/src/DataTableFactory.php @@ -33,6 +33,16 @@ public function createNamedBuilder(string $name, string $type = DataTableType::c { $query = $data; + $type = $this->registry->getType($type); + + $builder = $type->createBuilder($this, $name, $options); + + $type->buildDataTable($builder, $builder->getOptions()); + + if (null === $data && $builder->hasOption('query')) { + $data = $builder->getOption('query'); + } + if (null !== $data && !$data instanceof ProxyQueryInterface) { foreach ($this->registry->getProxyQueryFactories() as $proxyQueryFactory) { if ($proxyQueryFactory->supports($data)) { @@ -42,11 +52,7 @@ public function createNamedBuilder(string $name, string $type = DataTableType::c } } - $type = $this->registry->getType($type); - - $builder = $type->createBuilder($this, $name, $query, $options); - - $type->buildDataTable($builder, $builder->getOptions()); + $builder->setQuery($query); return $builder; } diff --git a/src/Type/ResolvedDataTableType.php b/src/Type/ResolvedDataTableType.php index 8db12642..4f06be41 100755 --- a/src/Type/ResolvedDataTableType.php +++ b/src/Type/ResolvedDataTableType.php @@ -10,7 +10,6 @@ use Kreyu\Bundle\DataTableBundle\DataTableInterface; use Kreyu\Bundle\DataTableBundle\DataTableView; use Kreyu\Bundle\DataTableBundle\Extension\DataTableTypeExtensionInterface; -use Kreyu\Bundle\DataTableBundle\Query\ProxyQueryInterface; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\OptionsResolver\Exception\ExceptionInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -52,7 +51,7 @@ public function getTypeExtensions(): array /** * @throws ExceptionInterface */ - public function createBuilder(DataTableFactoryInterface $factory, string $name, ?ProxyQueryInterface $query = null, array $options = []): DataTableBuilderInterface + public function createBuilder(DataTableFactoryInterface $factory, string $name, array $options = []): DataTableBuilderInterface { try { $options = $this->getOptionsResolver()->resolve($options); @@ -60,7 +59,7 @@ public function createBuilder(DataTableFactoryInterface $factory, string $name, throw new $exception(sprintf('An error has occurred resolving the options of the data table "%s": ', get_debug_type($this->getInnerType())).$exception->getMessage(), $exception->getCode(), $exception); } - return new DataTableBuilder($name, $this, $query, new EventDispatcher(), $options); + return new DataTableBuilder($name, $this, new EventDispatcher(), $options); } public function createView(DataTableInterface $dataTable): DataTableView diff --git a/src/Type/ResolvedDataTableTypeInterface.php b/src/Type/ResolvedDataTableTypeInterface.php index 720cb8d8..ac87d3d4 100755 --- a/src/Type/ResolvedDataTableTypeInterface.php +++ b/src/Type/ResolvedDataTableTypeInterface.php @@ -9,7 +9,6 @@ use Kreyu\Bundle\DataTableBundle\DataTableInterface; use Kreyu\Bundle\DataTableBundle\DataTableView; use Kreyu\Bundle\DataTableBundle\Extension\DataTableTypeExtensionInterface; -use Kreyu\Bundle\DataTableBundle\Query\ProxyQueryInterface; use Symfony\Component\OptionsResolver\OptionsResolver; interface ResolvedDataTableTypeInterface @@ -28,7 +27,7 @@ public function getTypeExtensions(): array; /** * @param array $options */ - public function createBuilder(DataTableFactoryInterface $factory, string $name, ?ProxyQueryInterface $query = null, array $options = []): DataTableBuilderInterface; + public function createBuilder(DataTableFactoryInterface $factory, string $name, array $options = []): DataTableBuilderInterface; public function createView(DataTableInterface $dataTable): DataTableView; diff --git a/tests/Unit/DataTableBuilderTest.php b/tests/Unit/DataTableBuilderTest.php index d9837dfc..fe5748ff 100644 --- a/tests/Unit/DataTableBuilderTest.php +++ b/tests/Unit/DataTableBuilderTest.php @@ -735,13 +735,16 @@ public function testGetDataTableResolvesExporters() private function createBuilder(): DataTableBuilder { - return new DataTableBuilder( + $builder = new DataTableBuilder( name: 'foo', type: $this->createStub(ResolvedDataTableTypeInterface::class), - query: $this->createStub(ProxyQueryInterface::class), dispatcher: $this->createStub(EventDispatcherInterface::class), options: [], ); + + $builder->setQuery(query: $this->createStub(ProxyQueryInterface::class)); + + return $builder; } private function createColumnFactory(): ColumnFactory From 59fe5fbe0208f29e8154e613f940c83611385aad Mon Sep 17 00:00:00 2001 From: Alexandre Castelain Date: Mon, 26 Jan 2026 13:07:17 +0100 Subject: [PATCH 2/2] Add empty value reference --- docs/src/docs/features/query.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/src/docs/features/query.md b/docs/src/docs/features/query.md index 5e5802de..971e9728 100644 --- a/docs/src/docs/features/query.md +++ b/docs/src/docs/features/query.md @@ -67,6 +67,12 @@ class ProductDataTableType extends AbstractDataTableType This allows you to define a default configuration that is fully overridable. +When a default value is defined, it is no longer mandatory to pass the query as the second argument of the `createDataTable` method. + +```php +$dataTable = $this->createDataTable(ProductDataTableType::class); +``` + ## Overriding the option When you define a default `query`, you can still override it when creating the data table in the controller: