From 1e4130e921ace603e1bbe96ade0845d8a33960d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Wr=C3=B3blewski?= Date: Fri, 15 Nov 2024 11:02:57 +0100 Subject: [PATCH] wip --- src/DataTable.php | 82 ++++++++++--------- src/DataTableInterface.php | 25 ++++-- ...EnsureValidSortingColumnsPropertyPaths.php | 31 +++++++ src/EventListener/RemoveRedundantFilters.php | 31 +++++++ .../RemoveRedundantPersonalizationColumns.php | 41 ++++++++++ .../RemoveRedundantSortingColumns.php | 39 +++++++++ src/Filter/FiltrationData.php | 3 + src/Sorting/SortingData.php | 6 ++ src/Type/DataTableType.php | 19 +++++ ...reValidSortingColumnsPropertyPathsTest.php | 69 ++++++++++++++++ .../RemoveRedundantFiltersTest.php | 44 ++++++++++ ...oveRedundantPersonalizationColumnsTest.php | 71 ++++++++++++++++ .../RemoveRedundantSortingColumnsTest.php | 72 ++++++++++++++++ 13 files changed, 491 insertions(+), 42 deletions(-) create mode 100644 src/EventListener/EnsureValidSortingColumnsPropertyPaths.php create mode 100644 src/EventListener/RemoveRedundantFilters.php create mode 100644 src/EventListener/RemoveRedundantPersonalizationColumns.php create mode 100644 src/EventListener/RemoveRedundantSortingColumns.php create mode 100644 tests/Unit/EventListener/EnsureValidSortingColumnsPropertyPathsTest.php create mode 100644 tests/Unit/EventListener/RemoveRedundantFiltersTest.php create mode 100644 tests/Unit/EventListener/RemoveRedundantPersonalizationColumnsTest.php create mode 100644 tests/Unit/EventListener/RemoveRedundantSortingColumnsTest.php diff --git a/src/DataTable.php b/src/DataTable.php index da8bc558..9acf557c 100755 --- a/src/DataTable.php +++ b/src/DataTable.php @@ -107,7 +107,7 @@ class DataTable implements DataTableInterface /** * The copy of a query used to retrieve the data, without any filters applied. */ - private ProxyQueryInterface $originalQuery; + private ProxyQueryInterface $nonFilteredQuery; private ?ResultSetInterface $resultSet = null; @@ -117,13 +117,14 @@ public function __construct( private ProxyQueryInterface $query, private /* readonly */ DataTableConfigInterface $config, ) { - $this->originalQuery = clone $this->query; + $this->nonFilteredQuery = clone $this->query; } public function __clone(): void { $this->config = clone $this->config; $this->query = clone $this->query; + $this->nonFilteredQuery = clone $this->nonFilteredQuery; } public function initialize(): void @@ -464,13 +465,14 @@ public function paginate(PaginationData $data, bool $persistence = true): void $this->query->paginate($data); - $this->originalQuery = $this->query; + $this->nonFilteredQuery = $this->query; if ($persistence && $this->config->isPaginationPersistenceEnabled()) { $this->setPersistenceData(PersistenceContext::Pagination, $data); } - $this->setPaginationData($data); + $this->paginationData = $data; + $this->resetPagination(); $this->dispatch(DataTableEvents::POST_PAGINATE, new DataTablePaginationEvent($this, $data)); @@ -486,20 +488,16 @@ public function sort(SortingData $data, bool $persistence = true): void $data = $event->getSortingData(); - $columns = $this->getColumns(); - - $data->removeRedundantColumns($columns); - $data->ensureValidPropertyPaths($columns); - $this->query->sort($data); - $this->originalQuery = $this->query; + $this->nonFilteredQuery = $this->query; if ($persistence && $this->config->isSortingPersistenceEnabled()) { $this->setPersistenceData(PersistenceContext::Sorting, $data); } - $this->setSortingData($data); + $this->sortingData = $data; + $this->resetPagination(); $this->dispatch(DataTableEvents::POST_SORT, new DataTableSortingEvent($this, $data)); @@ -511,18 +509,13 @@ public function filter(FiltrationData $data, bool $persistence = true): void return; } - $this->query = clone $this->originalQuery; + $this->query = clone $this->nonFilteredQuery; $this->dispatch(DataTableEvents::PRE_FILTER, $event = new DataTableFiltrationEvent($this, $data)); $data = $event->getFiltrationData(); - $filters = $this->getFilters(); - - $data->appendMissingFilters($filters); - $data->removeRedundantFilters($filters); - - foreach ($filters as $filter) { + foreach ($this->getFilters() as $filter) { $filterData = $data->getFilterData($filter->getName()); if ($filterData && $filterData->hasValue()) { @@ -534,7 +527,8 @@ public function filter(FiltrationData $data, bool $persistence = true): void $this->setPersistenceData(PersistenceContext::Filtration, $data); } - $this->setFiltrationData($data); + $this->filtrationData = $data; + $this->resetPagination(); $this->dispatch(DataTableEvents::POST_FILTER, new DataTableFiltrationEvent($this, $data)); @@ -553,13 +547,12 @@ public function personalize(PersonalizationData $data, bool $persistence = true) $columns = $this->getColumns(); $data->addMissingColumns($columns); - $data->removeRedundantColumns($columns); if ($persistence && $this->config->isPersonalizationPersistenceEnabled()) { $this->setPersistenceData(PersistenceContext::Personalization, $data); } - $this->setPersonalizationData($data); + $this->personalizationData = $data; $data->apply($this->getColumns()); @@ -647,6 +640,9 @@ public function getSortingData(): ?SortingData return $this->sortingData; } + /** + * @deprecated use {@see sort()} method instead + */ public function setSortingData(?SortingData $sortingData): static { $this->sortingData = $sortingData; @@ -659,6 +655,9 @@ public function getPaginationData(): ?PaginationData return $this->paginationData; } + /** + * @deprecated use {@see paginate()} method instead + */ public function setPaginationData(?PaginationData $paginationData): static { $this->paginationData = $paginationData; @@ -671,6 +670,9 @@ public function getFiltrationData(): ?FiltrationData return $this->filtrationData; } + /** + * @deprecated use {@see filter()} method instead + */ public function setFiltrationData(?FiltrationData $filtrationData): static { $this->filtrationData = $filtrationData; @@ -683,6 +685,9 @@ public function getPersonalizationData(): ?PersonalizationData return $this->personalizationData; } + /** + * @deprecated use {@see personalize()} method instead + */ public function setPersonalizationData(?PersonalizationData $personalizationData): static { $this->personalizationData = $personalizationData; @@ -695,6 +700,9 @@ public function getExportData(): ?ExportData return $this->exportData; } + /** + * @deprecated use {@see export()} method instead + */ public function setExportData(?ExportData $exportData): static { $this->exportData = $exportData; @@ -814,21 +822,24 @@ public function createExportView(): DataTableView return $view; } - private function dispatch(string $eventName, DataTableEvent $event): void - { - $dispatcher = $this->config->getEventDispatcher(); - - if ($dispatcher->hasListeners($eventName)) { - $dispatcher->dispatch($event, $eventName); - } - } - private function resetPagination(): void { $this->pagination = null; $this->resultSet = null; } + private function resetPersonalization(): void + { + $this->personalizationData = null; + + foreach ($this->columns as $column) { + $column + ->setPriority($column->getConfig()->getOption('priority')) + ->setVisible($column->getConfig()->getOption('visible')) + ; + } + } + private function getInitialPaginationData(): ?PaginationData { if (!$this->config->isPaginationEnabled()) { @@ -958,15 +969,12 @@ private function getPersistenceSubject(PersistenceContext $context): Persistence return $provider->provide(); } - private function resetPersonalization(): void + private function dispatch(string $eventName, DataTableEvent $event): void { - $this->personalizationData = null; + $dispatcher = $this->config->getEventDispatcher(); - foreach ($this->columns as $column) { - $column - ->setPriority($column->getConfig()->getOption('priority')) - ->setVisible($column->getConfig()->getOption('visible')) - ; + if ($dispatcher->hasListeners($eventName)) { + $dispatcher->dispatch($event, $eventName); } } } diff --git a/src/DataTableInterface.php b/src/DataTableInterface.php index 0a5d7464..000057ad 100755 --- a/src/DataTableInterface.php +++ b/src/DataTableInterface.php @@ -182,24 +182,39 @@ public function getPagination(): PaginationInterface; public function getSortingData(): ?SortingData; + /** + * @deprecated since 0.26, use {@see sort()} instead + */ public function setSortingData(?SortingData $sortingData): static; - public function setPaginationData(?PaginationData $paginationData): static; - public function getPaginationData(): ?PaginationData; - public function setFiltrationData(?FiltrationData $filtrationData): static; + /** + * @deprecated since 0.26, use {@see paginate()} instead + */ + public function setPaginationData(?PaginationData $paginationData): static; public function getFiltrationData(): ?FiltrationData; - public function setPersonalizationData(?PersonalizationData $personalizationData): static; + /** + * @deprecated since 0.26, use {@see filter()} instead + */ + public function setFiltrationData(?FiltrationData $filtrationData): static; public function getPersonalizationData(): ?PersonalizationData; - public function setExportData(?ExportData $exportData): static; + /** + * @deprecated since 0.26, use {@see personalize()} instead + */ + public function setPersonalizationData(?PersonalizationData $personalizationData): static; public function getExportData(): ?ExportData; + /** + * @deprecated since 0.26, use {@see export()} instead + */ + public function setExportData(?ExportData $exportData): static; + public function createFiltrationFormBuilder(?DataTableView $view = null): FormBuilderInterface; public function createPersonalizationFormBuilder(?DataTableView $view = null): FormBuilderInterface; diff --git a/src/EventListener/EnsureValidSortingColumnsPropertyPaths.php b/src/EventListener/EnsureValidSortingColumnsPropertyPaths.php new file mode 100644 index 00000000..ac3315eb --- /dev/null +++ b/src/EventListener/EnsureValidSortingColumnsPropertyPaths.php @@ -0,0 +1,31 @@ + 'ensureValidSortingColumnsPropertyPaths', + ]; + } + + public function ensureValidSortingColumnsPropertyPaths(DataTableSortingEvent $event): void + { + $dataTable = $event->getDataTable(); + $data = $event->getSortingData(); + + foreach ($data->getColumns() as $column) { + if ($dataTable->hasColumn($column->getName())) { + $column->setPropertyPath($dataTable->getColumn($column->getName())->getSortPropertyPath()); + } + } + } +} diff --git a/src/EventListener/RemoveRedundantFilters.php b/src/EventListener/RemoveRedundantFilters.php new file mode 100644 index 00000000..f9b45451 --- /dev/null +++ b/src/EventListener/RemoveRedundantFilters.php @@ -0,0 +1,31 @@ + 'removeRedundantFilters', + ]; + } + + public function removeRedundantFilters(DataTableFiltrationEvent $event): void + { + $dataTable = $event->getDataTable(); + $data = $event->getFiltrationData(); + + foreach ($data->getFilters() as $name => $filter) { + if (!$dataTable->hasFilter($name)) { + $data->removeFilter($name); + } + } + } +} diff --git a/src/EventListener/RemoveRedundantPersonalizationColumns.php b/src/EventListener/RemoveRedundantPersonalizationColumns.php new file mode 100644 index 00000000..6c1978fe --- /dev/null +++ b/src/EventListener/RemoveRedundantPersonalizationColumns.php @@ -0,0 +1,41 @@ + 'removeRedundantPersonalizationColumns', + ]; + } + + public function removeRedundantPersonalizationColumns(DataTablePersonalizationEvent $event): void + { + $dataTable = $event->getDataTable(); + $data = $event->getPersonalizationData(); + + foreach ($data->getColumns() as $column) { + if ($this->isColumnRedundant($dataTable, $column)) { + $data->removeColumn($column); + } + } + } + + private function isColumnRedundant(DataTableInterface $dataTable, PersonalizationColumnData $column): bool + { + return !$dataTable->hasColumn($column->getName()) + || !$dataTable->getColumn($column->getName())->getConfig()->isPersonalizable(); + } +} diff --git a/src/EventListener/RemoveRedundantSortingColumns.php b/src/EventListener/RemoveRedundantSortingColumns.php new file mode 100644 index 00000000..922b7bd6 --- /dev/null +++ b/src/EventListener/RemoveRedundantSortingColumns.php @@ -0,0 +1,39 @@ + 'removeRedundantSortingColumns', + ]; + } + + public function removeRedundantSortingColumns(DataTableSortingEvent $event): void + { + $dataTable = $event->getDataTable(); + $data = $event->getSortingData(); + + foreach ($data->getColumns() as $column) { + if ($this->isColumnRedundant($dataTable, $column)) { + $data->removeColumn($column); + } + } + } + + private function isColumnRedundant(DataTableInterface $dataTable, SortingColumnData $column): bool + { + return !$dataTable->hasColumn($column->getName()) + || !$dataTable->getColumn($column->getName())->getConfig()->isSortable(); + } +} diff --git a/src/Filter/FiltrationData.php b/src/Filter/FiltrationData.php index f9e98b6b..599960a8 100755 --- a/src/Filter/FiltrationData.php +++ b/src/Filter/FiltrationData.php @@ -5,6 +5,7 @@ namespace Kreyu\Bundle\DataTableBundle\Filter; use Kreyu\Bundle\DataTableBundle\DataTableInterface; +use Kreyu\Bundle\DataTableBundle\EventListener\RemoveRedundantFilters; class FiltrationData implements \ArrayAccess { @@ -126,6 +127,8 @@ public function appendMissingFilters(array $filters): void /** * @param array $filters + * + * @deprecated since 0.26, use {@see RemoveRedundantFilters} event listener instead */ public function removeRedundantFilters(array $filters): void { diff --git a/src/Sorting/SortingData.php b/src/Sorting/SortingData.php index bf92afa2..be5a9e02 100755 --- a/src/Sorting/SortingData.php +++ b/src/Sorting/SortingData.php @@ -5,6 +5,8 @@ namespace Kreyu\Bundle\DataTableBundle\Sorting; use Kreyu\Bundle\DataTableBundle\Column\ColumnInterface; +use Kreyu\Bundle\DataTableBundle\EventListener\EnsureValidSortingColumnsPropertyPaths; +use Kreyu\Bundle\DataTableBundle\EventListener\RemoveRedundantSortingColumns; use Kreyu\Bundle\DataTableBundle\Exception\UnexpectedTypeException; class SortingData @@ -47,6 +49,8 @@ public static function fromArray(array $data): self /** * @param array $columns + * + * @deprecated since 0.26, use {@see RemoveRedundantSortingColumns} event listener instead */ public function removeRedundantColumns(array $columns): void { @@ -74,6 +78,8 @@ public function removeRedundantColumns(array $columns): void /** * @param array $columns + * + * @deprecated since 0.26, use {@see EnsureValidSortingColumnsPropertyPaths} event listener instead */ public function ensureValidPropertyPaths(array $columns): void { diff --git a/src/Type/DataTableType.php b/src/Type/DataTableType.php index 109671d2..f7169abe 100755 --- a/src/Type/DataTableType.php +++ b/src/Type/DataTableType.php @@ -12,6 +12,11 @@ use Kreyu\Bundle\DataTableBundle\DataTableBuilderInterface; use Kreyu\Bundle\DataTableBundle\DataTableInterface; use Kreyu\Bundle\DataTableBundle\DataTableView; +use Kreyu\Bundle\DataTableBundle\Event\DataTableEvents; +use Kreyu\Bundle\DataTableBundle\EventListener\EnsureValidSortingColumnsPropertyPaths; +use Kreyu\Bundle\DataTableBundle\EventListener\RemoveRedundantFilters; +use Kreyu\Bundle\DataTableBundle\EventListener\RemoveRedundantPersonalizationColumns; +use Kreyu\Bundle\DataTableBundle\EventListener\RemoveRedundantSortingColumns; use Kreyu\Bundle\DataTableBundle\Exporter\ExporterFactoryInterface; use Kreyu\Bundle\DataTableBundle\Filter\FilterData; use Kreyu\Bundle\DataTableBundle\Filter\FilterFactoryInterface; @@ -73,6 +78,20 @@ public function buildDataTable(DataTableBuilderInterface $builder, array $option $setter($value); } } + + if ($builder->isSortingEnabled()) { + $builder + ->addEventSubscriber(new RemoveRedundantSortingColumns()) + ->addEventSubscriber(new EnsureValidSortingColumnsPropertyPaths()); + } + + if ($builder->isFiltrationEnabled()) { + $builder->addEventSubscriber(new RemoveRedundantFilters()); + } + + if ($builder->isPersonalizationEnabled()) { + $builder->addEventSubscriber(new RemoveRedundantPersonalizationColumns()); + } } public function buildView(DataTableView $view, DataTableInterface $dataTable, array $options): void diff --git a/tests/Unit/EventListener/EnsureValidSortingColumnsPropertyPathsTest.php b/tests/Unit/EventListener/EnsureValidSortingColumnsPropertyPathsTest.php new file mode 100644 index 00000000..cac036d6 --- /dev/null +++ b/tests/Unit/EventListener/EnsureValidSortingColumnsPropertyPathsTest.php @@ -0,0 +1,69 @@ +createMock(DataTableInterface::class); + + $dataTable->expects($this->exactly(3)) + ->method('hasColumn') + ->willReturnCallback(fn (string $name) => match ($name) { + 'title', 'description' => true, + 'category' => false, + }) + ; + + $dataTable->expects($this->exactly(2)) + ->method('getColumn') + ->willReturnCallback(function (string $name) { + $column = $this->createMock(ColumnInterface::class); + + $column->expects($this->once()) + ->method('getSortPropertyPath') + ->willReturnCallback(fn () => match ($name) { + 'title' => new PropertyPath('title'), + 'description' => new PropertyPath('shortDescription'), + }) + ; + + return $column; + }) + ; + + $sortingData = SortingData::fromArray([ + new SortingColumnData('title', 'desc', 'title'), + new SortingColumnData('description', 'desc', 'description'), + new SortingColumnData('category', 'desc', 'category'), + ]); + + $event = new DataTableSortingEvent($dataTable, $sortingData); + + $eventListener = new EnsureValidSortingColumnsPropertyPaths(); + $eventListener->ensureValidSortingColumnsPropertyPaths($event); + + $sortingData = $event->getSortingData(); + + $this->assertEquals('title', (string) $sortingData->getColumn('title')->getPropertyPath()); + $this->assertEquals('shortDescription', (string) $sortingData->getColumn('description')->getPropertyPath()); + $this->assertEquals('category', (string) $sortingData->getColumn('category')->getPropertyPath()); + } +} \ No newline at end of file diff --git a/tests/Unit/EventListener/RemoveRedundantFiltersTest.php b/tests/Unit/EventListener/RemoveRedundantFiltersTest.php new file mode 100644 index 00000000..3a7335c4 --- /dev/null +++ b/tests/Unit/EventListener/RemoveRedundantFiltersTest.php @@ -0,0 +1,44 @@ +createMock(DataTableInterface::class); + + $dataTable->expects($this->exactly(3)) + ->method('hasFilter') + ->willReturnCallback(fn (string $name) => match ($name) { + 'title', 'description' => true, + 'category' => false, + }) + ; + + $filtrationData = FiltrationData::fromArray([ + 'title' => 'foo', + 'description' => 'bar', + 'category' => 'baz', + ]); + + $event = new DataTableFiltrationEvent($dataTable, $filtrationData); + + $eventListener = new RemoveRedundantFilters(); + $eventListener->removeRedundantFilters($event); + + $filters = $event->getFiltrationData()->getFilters(); + + $this->assertArrayHasKey('title', $filters); + $this->assertArrayHasKey('description', $filters); + $this->assertArrayNotHasKey('category', $filters); + } +} \ No newline at end of file diff --git a/tests/Unit/EventListener/RemoveRedundantPersonalizationColumnsTest.php b/tests/Unit/EventListener/RemoveRedundantPersonalizationColumnsTest.php new file mode 100644 index 00000000..7dba2442 --- /dev/null +++ b/tests/Unit/EventListener/RemoveRedundantPersonalizationColumnsTest.php @@ -0,0 +1,71 @@ +createMock(DataTableInterface::class); + + $dataTable->expects($this->exactly(3)) + ->method('hasColumn') + ->willReturnCallback(fn (string $name) => match ($name) { + 'title', 'description' => true, + 'category' => false, + }) + ; + + $dataTable->expects($this->exactly(2)) + ->method('getColumn') + ->willReturnCallback(function (string $name) { + $column = $this->createMock(ColumnInterface::class); + $columnConfig = $this->createMock(ColumnConfigInterface::class); + + $column->expects($this->once()) + ->method('getConfig') + ->willReturn($columnConfig) + ; + + $columnConfig->expects($this->once()) + ->method('isPersonalizable') + ->willReturnCallback(fn () => match ($name) { + 'title' => true, + 'description' => false, + }) + ; + + return $column; + }) + ; + + $personalizationData = PersonalizationData::fromArray([ + 'columns' => [ + 'title' => ['visible' => true], + 'description' => ['visible' => true], + 'category' => ['visible' => true], + ], + ]); + + $event = new DataTablePersonalizationEvent($dataTable, $personalizationData); + + $eventListener = new RemoveRedundantPersonalizationColumns(); + $eventListener->removeRedundantPersonalizationColumns($event); + + $columns = $event->getPersonalizationData()->getColumns(); + + $this->assertArrayHasKey('title', $columns); + $this->assertArrayNotHasKey('description', $columns); + $this->assertArrayNotHasKey('category', $columns); + } +} \ No newline at end of file diff --git a/tests/Unit/EventListener/RemoveRedundantSortingColumnsTest.php b/tests/Unit/EventListener/RemoveRedundantSortingColumnsTest.php new file mode 100644 index 00000000..136378ba --- /dev/null +++ b/tests/Unit/EventListener/RemoveRedundantSortingColumnsTest.php @@ -0,0 +1,72 @@ +createMock(DataTableInterface::class); + + $dataTable->expects($this->exactly(3)) + ->method('hasColumn') + ->willReturnCallback(fn (string $name) => match ($name) { + 'title', 'description' => true, + 'category' => false, + }) + ; + + $dataTable->expects($this->exactly(2)) + ->method('getColumn') + ->willReturnCallback(function (string $name) { + $column = $this->createMock(ColumnInterface::class); + $columnConfig = $this->createMock(ColumnConfigInterface::class); + + $column->expects($this->once()) + ->method('getConfig') + ->willReturn($columnConfig) + ; + + $columnConfig->expects($this->once()) + ->method('isSortable') + ->willReturnCallback(fn () => match ($name) { + 'title' => true, + 'description' => false, + }) + ; + + return $column; + }) + ; + + $sortingData = SortingData::fromArray([ + 'title' => 'desc', + 'description' => 'desc', + 'category' => 'desc', + ]); + + $event = new DataTableSortingEvent($dataTable, $sortingData); + + $eventListener = new RemoveRedundantSortingColumns(); + $eventListener->removeRedundantSortingColumns($event); + + $columns = $event->getSortingData()->getColumns(); + + $this->assertArrayHasKey('title', $columns); + $this->assertArrayNotHasKey('description', $columns); + $this->assertArrayNotHasKey('category', $columns); + } +} \ No newline at end of file