Skip to content

Commit 97c47a0

Browse files
authored
[Modifiers]: Add filters for grid (#350)
* add nested type filter * Apply php-cs-fixer changes * fix: remove unused parameters * remove unused parameters * add test cases * Apply php-cs-fixer changes * update docs * add missing filters required for grid and tree * update tests * code style * fix: unit tests * Apply php-cs-fixer changes --------- Co-authored-by: lukmzig <[email protected]>
1 parent ced77fc commit 97c47a0

File tree

14 files changed

+688
-41
lines changed

14 files changed

+688
-41
lines changed

doc/04_Searching_For_Data_In_Index/05_Search_Modifiers/README.md

Lines changed: 21 additions & 18 deletions
Large diffs are not rendered by default.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query;
15+
16+
use Pimcore\Bundle\GenericDataIndexBundle\Enum\SearchIndex\DefaultSearch\ConditionType;
17+
use Pimcore\Bundle\GenericDataIndexBundle\Enum\SearchIndex\DefaultSearch\QueryType;
18+
19+
final class MultiBoolQuery extends BoolQuery implements AsSubQueryInterface
20+
{
21+
public function __construct(
22+
private readonly string $field,
23+
/** @var (bool)[] */
24+
private readonly array $terms,
25+
) {
26+
parent::__construct([
27+
ConditionType::FILTER->value => [
28+
QueryType::BOOL->value => [
29+
ConditionType::SHOULD->value => [
30+
(new BoolExistsQuery($this->field))->toArrayAsSubQuery(),
31+
(new TermsFilter($this->field, $this->terms))->toArrayAsSubQuery(),
32+
],
33+
'minimum_should_match' => 1,
34+
],
35+
],
36+
]);
37+
}
38+
39+
public function getField(): string
40+
{
41+
return $this->field;
42+
}
43+
44+
/** @return (bool)[] */
45+
public function getTerms(): array
46+
{
47+
return $this->terms;
48+
}
49+
50+
public function toArrayAsSubQuery(): array
51+
{
52+
return [
53+
QueryType::BOOL->value => [
54+
ConditionType::SHOULD->value => [
55+
(new BoolExistsQuery($this->field))->toArrayAsSubQuery(),
56+
(new TermsFilter($this->field, $this->terms))->toArrayAsSubQuery(),
57+
],
58+
'minimum_should_match' => 1,
59+
],
60+
];
61+
}
62+
}

src/Model/DefaultSearch/Query/TermsFilter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ final class TermsFilter extends BoolQuery implements AsSubQueryInterface
1919
{
2020
public function __construct(
2121
private readonly string $field,
22-
/** @var (int|string)[] */
22+
/** @var (int|string|bool)[] */
2323
private readonly array $terms,
2424
) {
2525
parent::__construct([
@@ -36,7 +36,7 @@ public function getField(): string
3636
return $this->field;
3737
}
3838

39-
/** @return (int|string)[] */
39+
/** @return (int|string|bool)[] */
4040
public function getTerms(): array
4141
{
4242
return $this->terms;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic;
15+
16+
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\SearchModifierInterface;
17+
18+
final readonly class ExcludeVariantsFilter implements SearchModifierInterface
19+
{
20+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic;
15+
16+
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\SearchModifierInterface;
17+
18+
final readonly class NumberFilter implements SearchModifierInterface
19+
{
20+
public function __construct(
21+
private string $fieldName,
22+
private int|float $searchTerm,
23+
private bool $enablePqlFieldNameResolution = true,
24+
) {
25+
}
26+
27+
public function getFieldName(): string
28+
{
29+
return $this->fieldName;
30+
}
31+
32+
public function getSearchTerm(): int|float
33+
{
34+
return $this->searchTerm;
35+
}
36+
37+
public function isPqlFieldNameResolutionEnabled(): bool
38+
{
39+
return $this->enablePqlFieldNameResolution;
40+
}
41+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\FieldType;
15+
16+
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\SearchModifierInterface;
17+
use ValueError;
18+
19+
final readonly class BooleanMultiSelectFilter implements SearchModifierInterface
20+
{
21+
public function __construct(
22+
private string $field,
23+
private array $values,
24+
private bool $enablePqlFieldNameResolution = true,
25+
) {
26+
$this->validate();
27+
}
28+
29+
public function getField(): string
30+
{
31+
return $this->field;
32+
}
33+
34+
public function getValues(): array
35+
{
36+
return $this->values;
37+
}
38+
39+
public function isPqlFieldNameResolutionEnabled(): bool
40+
{
41+
return $this->enablePqlFieldNameResolution;
42+
}
43+
44+
private function validate(): void
45+
{
46+
foreach ($this->values as $value) {
47+
if (!is_bool($value) && !is_null($value)) {
48+
throw new ValueError(
49+
sprintf(
50+
'Provided array must contain only boolean or null values. (%s given)',
51+
gettype($value)
52+
),
53+
);
54+
}
55+
}
56+
}
57+
}

src/Model/Search/Modifier/Filter/FieldType/ClassificationStoreFilter.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\BooleanFilter;
1717
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\IntegerFilter;
18+
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\NumberFilter;
1819
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\FullTextSearch\FullTextSearch;
1920
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\FullTextSearch\WildcardSearch;
2021
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\SearchModifierInterface;
@@ -25,8 +26,8 @@
2526
public function __construct(
2627
private string $fieldName,
2728
private string $group,
28-
private BooleanFilter|DateFilter|FullTextSearch|IntegerFilter|MultiSelectFilter|NumberRangeFilter|
29-
WildcardSearch $subModifier,
29+
private BooleanFilter|DateFilter|FullTextSearch|IntegerFilter|MultiSelectFilter|BooleanMultiSelectFilter|
30+
NumberFilter|NumberRangeFilter|WildcardSearch $subModifier,
3031
private string $locale = MappingProperty::NOT_LOCALIZED_KEY,
3132
) {
3233
}
@@ -42,7 +43,7 @@ public function getGroup(): string
4243
}
4344

4445
public function getSubModifier(): BooleanFilter|DateFilter|FullTextSearch|IntegerFilter|
45-
MultiSelectFilter|NumberRangeFilter|WildcardSearch
46+
MultiSelectFilter|BooleanMultiSelectFilter|NumberFilter|NumberRangeFilter|WildcardSearch
4647
{
4748
return $this->subModifier;
4849
}

src/SearchIndexAdapter/DefaultSearch/Search/Modifier/Filter/BasicFilters.php

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\DefaultSearch\Search\Modifier\Filter;
1515

1616
use Pimcore\Bundle\GenericDataIndexBundle\Attribute\Search\AsSearchModifierHandler;
17+
use Pimcore\Bundle\GenericDataIndexBundle\Enum\SearchIndex\DefaultSearch\ConditionType;
1718
use Pimcore\Bundle\GenericDataIndexBundle\Enum\SearchIndex\FieldCategory\SystemField;
1819
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Modifier\SearchModifierContextInterface;
1920
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query\BoolExistsQuery;
@@ -23,10 +24,13 @@
2324
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Interfaces\SearchInterface;
2425
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\BooleanFilter;
2526
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\ExcludeFoldersFilter;
27+
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\ExcludeVariantsFilter;
2628
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\IdFilter;
2729
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\IdsFilter;
2830
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\IntegerFilter;
31+
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\NumberFilter;
2932
use Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\SearchPqlFieldNameTransformationServiceInterface;
33+
use Pimcore\Model\DataObject\AbstractObject;
3034

3135
/**
3236
* @internal
@@ -53,26 +57,34 @@ public function handleIdFilter(IdFilter $idFilter, SearchModifierContextInterfac
5357
public function handleIntegerFilter(IntegerFilter $integerFilter, SearchModifierContextInterface $context): void
5458
{
5559
$context->getSearch()->addQuery(
56-
$this->getIntegerQuery($integerFilter, null, $context->getOriginalSearch())
60+
$this->getNumberQuery($integerFilter, null, $context->getOriginalSearch())
5761
);
5862
}
5963

60-
public function getIntegerQuery(
61-
IntegerFilter $integerFilter,
64+
#[AsSearchModifierHandler]
65+
public function handleNumberFilter(NumberFilter $numberFilter, SearchModifierContextInterface $context): void
66+
{
67+
$context->getSearch()->addQuery(
68+
$this->getNumberQuery($numberFilter, null, $context->getOriginalSearch())
69+
);
70+
}
71+
72+
public function getNumberQuery(
73+
IntegerFilter|NumberFilter $filter,
6274
?string $prefix = null,
6375
?SearchInterface $search = null
6476
): TermFilter {
65-
$fieldName = $integerFilter->getFieldName();
77+
$fieldName = $filter->getFieldName();
6678
if ($prefix) {
6779
$fieldName = $prefix . '.' . $fieldName;
6880
}
69-
if ($search && $integerFilter->isPqlFieldNameResolutionEnabled()) {
81+
if ($search && $filter->isPqlFieldNameResolutionEnabled()) {
7082
$fieldName = $this->fieldNameTransformationService->transformFieldnameForSearch($search, $fieldName);
7183
}
7284

7385
return new TermFilter(
7486
field: $fieldName,
75-
term: $integerFilter->getSearchTerm(),
87+
term: $filter->getSearchTerm(),
7688
);
7789
}
7890

@@ -125,13 +137,26 @@ public function handleExcludeFoldersFilter(
125137
ExcludeFoldersFilter $excludeFoldersFilter,
126138
SearchModifierContextInterface $context
127139
): void {
128-
$context->getSearch()->addQuery(new BoolQuery([
129-
'must_not' => [
140+
$context->getSearch()->addQuery($this->excludeTypeQuery(AbstractObject::OBJECT_TYPE_FOLDER));
141+
}
142+
143+
#[AsSearchModifierHandler]
144+
public function handleExcludeVariantsFilter(
145+
ExcludeVariantsFilter $excludeVariantsFilter,
146+
SearchModifierContextInterface $context
147+
): void {
148+
$context->getSearch()->addQuery($this->excludeTypeQuery(AbstractObject::OBJECT_TYPE_VARIANT));
149+
}
150+
151+
private function excludeTypeQuery(string $type): BoolQuery
152+
{
153+
return new BoolQuery([
154+
ConditionType::MUST_NOT->value => [
130155
new TermFilter(
131156
field: SystemField::TYPE->getPath(),
132-
term: 'folder',
157+
term: $type,
133158
),
134159
],
135-
]));
160+
]);
136161
}
137162
}

src/SearchIndexAdapter/DefaultSearch/Search/Modifier/Filter/FieldTypeFilters.php

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515

1616
use Pimcore\Bundle\GenericDataIndexBundle\Attribute\Search\AsSearchModifierHandler;
1717
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Modifier\SearchModifierContextInterface;
18+
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query\BoolExistsQuery;
1819
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query\DateFilter as DateFilterQuery;
20+
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query\MultiBoolQuery;
1921
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query\Query as QueryFilter;
2022
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query\TermsFilter as TermsFilterQuery;
2123
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Interfaces\SearchInterface;
24+
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\FieldType\BooleanMultiSelectFilter;
2225
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\FieldType\DateFilter;
2326
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\FieldType\MultiSelectFilter;
2427
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\FieldType\NumberRangeFilter;
@@ -97,6 +100,44 @@ public function getMultiSelectQuery(
97100
);
98101
}
99102

103+
#[AsSearchModifierHandler]
104+
public function handleBooleanMultiSelectFilter(
105+
BooleanMultiSelectFilter $filter,
106+
SearchModifierContextInterface $context
107+
): void {
108+
$context->getSearch()->addQuery(
109+
$this->getBooleanMultiSelectFilter($filter, null, $context->getOriginalSearch())
110+
);
111+
}
112+
113+
public function getBooleanMultiSelectFilter(
114+
BooleanMultiSelectFilter $filter,
115+
?string $prefix = null,
116+
?SearchInterface $search = null
117+
): null|BoolExistsQuery|MultiBoolQuery|TermsFilterQuery {
118+
if (count($filter->getValues()) === 0) {
119+
return null;
120+
}
121+
122+
$fieldName = $filter->getField();
123+
if ($prefix) {
124+
$fieldName = $prefix . '.' . $fieldName;
125+
}
126+
127+
if ($search && $filter->isPqlFieldNameResolutionEnabled()) {
128+
$fieldName = $this->fieldNameTransformationService->transformFieldnameForSearch($search, $fieldName);
129+
}
130+
131+
$hasNull = in_array(null, $filter->getValues(), true);
132+
$nonNullValues = array_filter($filter->getValues(), static fn ($v) => $v !== null);
133+
134+
return match (true) {
135+
$hasNull && $nonNullValues !== [] => new MultiBoolQuery($fieldName, $nonNullValues),
136+
$hasNull => new BoolExistsQuery($fieldName),
137+
default => new TermsFilterQuery($fieldName, $nonNullValues),
138+
};
139+
}
140+
100141
#[AsSearchModifierHandler]
101142
public function handleNumberRangeFilter(
102143
NumberRangeFilter $numberRangeFilter,
@@ -125,8 +166,8 @@ public function getNumberRangeFilter(
125166
'range',
126167
[
127168
$fieldName => [
128-
'gte' => $numberRangeFilter->getMin(),
129-
'lte' => $numberRangeFilter->getMax(),
169+
'gt' => $numberRangeFilter->getMin(),
170+
'lt' => $numberRangeFilter->getMax(),
130171
],
131172
]
132173
);

0 commit comments

Comments
 (0)