From 688f57f484be45a406c983af7152660c2eb62489 Mon Sep 17 00:00:00 2001 From: Martin Brettschneider Date: Mon, 14 Oct 2024 10:59:49 +0200 Subject: [PATCH] feat: add conjunction search if text filter --- .docs/filters.md | 7 +++++++ src/DataSource/ArrayDataSource.php | 12 +++++++++++- src/DataSource/DibiFluentDataSource.php | 2 +- src/DataSource/DibiFluentMssqlDataSource.php | 2 +- .../DibiFluentPostgreDataSource.php | 2 +- .../DoctrineCollectionDataSource.php | 2 +- src/DataSource/DoctrineDataSource.php | 2 +- .../NetteDatabaseTableDataSource.php | 12 +++++++----- src/DataSource/NextrasDataSource.php | 9 +++++---- src/Filter/FilterText.php | 18 ++++++++++++++++++ .../Cases/DataSources/BaseDataSourceTest.phpt | 12 ++++++++++++ .../DataSources/NextrasDataSourceTest.phpt | 19 ------------------- 12 files changed, 65 insertions(+), 34 deletions(-) diff --git a/.docs/filters.md b/.docs/filters.md index 5cb530f43..387b04015 100644 --- a/.docs/filters.md +++ b/.docs/filters.md @@ -158,6 +158,13 @@ $grid->addFilterText('name', 'Name') ->setSplitWordsSearch(false); ``` +If you need to find rows, that contains "foo" and "bar" (not just one of them), you can use `setConjunctionSearch()`. + +```php +$grid->addFilterText('name', 'Name') + ->setConjunctionSearch(); +``` + ## FilterSelect `FilterSelect` has one more parameter - options: diff --git a/src/DataSource/ArrayDataSource.php b/src/DataSource/ArrayDataSource.php index aecc8c17a..5d9c59bc3 100644 --- a/src/DataSource/ArrayDataSource.php +++ b/src/DataSource/ArrayDataSource.php @@ -209,11 +209,21 @@ protected function applyFilter($row, Filter $filter) $row_value = strtolower(Strings::toAscii((string) $row[$column])); + $found = []; + foreach ($words as $word) { if (strpos($row_value, strtolower(Strings::toAscii($word))) !== false) { - return $row; + if ($filter instanceof FilterText && !$filter->hasConjunctionSearch()) { + return $row; + } else { + $found[] = true; + } } } + + if (count($found) === count($words)) { + return $row; + } } } diff --git a/src/DataSource/DibiFluentDataSource.php b/src/DataSource/DibiFluentDataSource.php index 81336b20a..09d20d81c 100755 --- a/src/DataSource/DibiFluentDataSource.php +++ b/src/DataSource/DibiFluentDataSource.php @@ -215,7 +215,7 @@ protected function applyFilterText(FilterText $filter): void } if (sizeof($or) > 1) { - $this->dataSource->where('(%or)', $or); + $this->dataSource->where($filter->hasConjunctionSearch() ? '(%and)' : '(%or)', $or); } else { $this->dataSource->where($or); } diff --git a/src/DataSource/DibiFluentMssqlDataSource.php b/src/DataSource/DibiFluentMssqlDataSource.php index fe6b76145..9f7a3341b 100755 --- a/src/DataSource/DibiFluentMssqlDataSource.php +++ b/src/DataSource/DibiFluentMssqlDataSource.php @@ -136,7 +136,7 @@ protected function applyFilterText(FilterText $filter): void } if (sizeof($or) > 1) { - $this->dataSource->where('(%or)', $or); + $this->dataSource->where($filter->hasConjunctionSearch() ? '(%and)' : '(%or)', $or); } else { $this->dataSource->where($or); } diff --git a/src/DataSource/DibiFluentPostgreDataSource.php b/src/DataSource/DibiFluentPostgreDataSource.php index 3c9f08074..e798e223f 100755 --- a/src/DataSource/DibiFluentPostgreDataSource.php +++ b/src/DataSource/DibiFluentPostgreDataSource.php @@ -33,7 +33,7 @@ protected function applyFilterText(FilterText $filter): void } if (sizeof($or) > 1) { - $this->dataSource->where('(%or)', $or); + $this->dataSource->where($filter->hasConjunctionSearch() ? '(%and)' : '(%or)', $or); } else { $this->dataSource->where($or); } diff --git a/src/DataSource/DoctrineCollectionDataSource.php b/src/DataSource/DoctrineCollectionDataSource.php index 9f4806c19..38580f5a1 100755 --- a/src/DataSource/DoctrineCollectionDataSource.php +++ b/src/DataSource/DoctrineCollectionDataSource.php @@ -226,7 +226,7 @@ protected function applyFilterText(FilterText $filter): void } } - $expr = call_user_func_array([Criteria::expr(), 'orX'], $exprs); + $expr = call_user_func_array([Criteria::expr(), $filter->hasConjunctionSearch() ? 'andX' : 'orX'], $exprs); $this->criteria->andWhere($expr); } diff --git a/src/DataSource/DoctrineDataSource.php b/src/DataSource/DoctrineDataSource.php index 7d01d222e..65d3f6d77 100755 --- a/src/DataSource/DoctrineDataSource.php +++ b/src/DataSource/DoctrineDataSource.php @@ -319,7 +319,7 @@ protected function applyFilterText(FilterText $filter): void } } - $or = call_user_func_array([$this->dataSource->expr(), 'orX'], $exprs); + $or = call_user_func_array([$this->dataSource->expr(), $filter->hasConjunctionSearch() ? 'andX' : 'orX'], $exprs); $this->dataSource->andWhere($or); } diff --git a/src/DataSource/NetteDatabaseTableDataSource.php b/src/DataSource/NetteDatabaseTableDataSource.php index 67be9bd66..0de2f73f9 100755 --- a/src/DataSource/NetteDatabaseTableDataSource.php +++ b/src/DataSource/NetteDatabaseTableDataSource.php @@ -224,31 +224,33 @@ protected function applyFilterText(FilterText $filter): void $bigOrArgs = []; $condition = $filter->getCondition(); + $operator = $filter->hasConjunctionSearch() ? 'AND' : 'OR'; + foreach ($condition as $column => $value) { $like = '('; $args = []; if ($filter->isExactSearch()) { - $like .= "$column = ? OR "; + $like .= "$column = ? $operator "; $args[] = "$value"; } else { $words = $filter->hasSplitWordsSearch() === false ? [$value] : explode(' ', $value); foreach ($words as $word) { - $like .= "$column LIKE ? OR "; + $like .= "$column LIKE ? $operator "; $args[] = "%$word%"; } } - $like = substr($like, 0, strlen($like) - 4) . ')'; + $like = substr($like, 0, strlen($like) - (strlen($operator) + 2)) . ')'; $or[] = $like; - $bigOr .= "$like OR "; + $bigOr .= "$like $operator "; $bigOrArgs = array_merge($bigOrArgs, $args); } if (sizeof($or) > 1) { - $bigOr = substr($bigOr, 0, strlen($bigOr) - 4) . ')'; + $bigOr = substr($bigOr, 0, strlen($bigOr) - (strlen($operator) + 2)) . ')'; $query = array_merge([$bigOr], $bigOrArgs); diff --git a/src/DataSource/NextrasDataSource.php b/src/DataSource/NextrasDataSource.php index c78127c7a..275fed226 100755 --- a/src/DataSource/NextrasDataSource.php +++ b/src/DataSource/NextrasDataSource.php @@ -243,7 +243,7 @@ protected function applyFilterText(FilterText $filter): void // native handling with LikeFunction in v4 if (class_exists(LikeExpression::class)) { $conditions = [ - ICollection::OR, + $filter->hasConjunctionSearch() ? ICollection::AND : ICollection::OR, ]; foreach ($filter->getCondition() as $column => $value) { @@ -272,10 +272,11 @@ protected function applyFilterText(FilterText $filter): void $condition = $filter->getCondition(); $expr = '('; $params = []; + $operator = $filter->hasConjunctionSearch() ? 'AND' : 'OR'; foreach ($condition as $column => $value) { if ($filter->isExactSearch()) { - $expr .= '%column = %s OR '; + $expr .= "%column = %s $operator "; $params[] = $column; $params[] = "$value"; @@ -285,13 +286,13 @@ protected function applyFilterText(FilterText $filter): void $words = $filter->hasSplitWordsSearch() === false ? [$value] : explode(' ', $value); foreach ($words as $word) { - $expr .= '%column LIKE %s OR '; + $expr .= "%column LIKE %s $operator "; $params[] = $column; $params[] = "%$word%"; } } - $expr = preg_replace('/ OR $/', ')', $expr); + $expr = preg_replace("/ $operator $/", ')', $expr); array_unshift($params, $expr); diff --git a/src/Filter/FilterText.php b/src/Filter/FilterText.php index 3860d7984..6d083f684 100644 --- a/src/Filter/FilterText.php +++ b/src/Filter/FilterText.php @@ -30,6 +30,11 @@ class FilterText extends Filter */ protected $splitWordsSearch = true; + /** + * @var bool + */ + protected $conjunctionSearch = false; + /** * @var array|string[] */ @@ -110,4 +115,17 @@ public function hasSplitWordsSearch(): bool { return $this->splitWordsSearch; } + + + public function setConjunctionSearch(bool $conjunctionSearch = true): void + { + $this->conjunctionSearch = $conjunctionSearch; + } + + + public function hasConjunctionSearch(): bool + { + return $this->conjunctionSearch; + } + } diff --git a/tests/Cases/DataSources/BaseDataSourceTest.phpt b/tests/Cases/DataSources/BaseDataSourceTest.phpt index d3adbe79c..109103943 100644 --- a/tests/Cases/DataSources/BaseDataSourceTest.phpt +++ b/tests/Cases/DataSources/BaseDataSourceTest.phpt @@ -47,6 +47,11 @@ abstract class BaseDataSourceTest extends TestCase $this->ds->filter([$filter]); Assert::same(2, $this->ds->getCount()); + + $filter->setConjunctionSearch(); + + $this->ds->filter([$filter]); + Assert::same(1, $this->ds->getCount()); } public function testGetData(): void @@ -65,6 +70,13 @@ abstract class BaseDataSourceTest extends TestCase $this->data[0], $this->data[5], ], $this->getActualResultAsArray()); + + $filter->setConjunctionSearch(); + + $this->ds->filter([$filter]); + Assert::equal([ + $this->data[5], + ], $this->getActualResultAsArray()); } public function testFilterMultipleColumns(): void diff --git a/tests/Cases/DataSources/NextrasDataSourceTest.phpt b/tests/Cases/DataSources/NextrasDataSourceTest.phpt index fd143fbdd..5247b1a21 100644 --- a/tests/Cases/DataSources/NextrasDataSourceTest.phpt +++ b/tests/Cases/DataSources/NextrasDataSourceTest.phpt @@ -8,17 +8,14 @@ use Nette\Caching\Cache; use Nette\Caching\Storages\DevNullStorage; use Nette\Utils\Arrays; use Nextras\Dbal\Connection; -use Nextras\Orm\Collection\Expression\LikeExpression; use Nextras\Orm\Entity\Entity; use Nextras\Orm\Mapper\Dbal\DbalMapperCoordinator; use Nextras\Orm\Mapper\Mapper; use Nextras\Orm\Model\Model; use Nextras\Orm\Model\SimpleModelFactory; use Nextras\Orm\Repository\Repository; -use Tester\Assert; use Tester\Environment; use Ublaboo\DataGrid\DataSource\NextrasDataSource; -use Ublaboo\DataGrid\Filter\FilterText; use Ublaboo\DataGrid\Tests\Files\TestingDataGridFactory; require __DIR__ . '/BaseDataSourceTest.phpt'; @@ -38,22 +35,6 @@ final class NextrasDataSourceTest extends BaseDataSourceTest */ private $model; - public function testFilterOnJoinedTable(): void - { - // skip this test for v3.1 - if (!class_exists(LikeExpression::class)) { - return; - } - - $this->ds = new NextrasDataSource($this->model->books->findAll(), 'id'); - - $filter = new FilterText($this->grid, 'a', 'b', ['author.name']); - $filter->setValue('John Red'); - - $this->ds->filter([$filter]); - Assert::same(2, $this->ds->getCount()); - } - protected function setUp(): void { $this->setUpDatabase();