Skip to content
39 changes: 21 additions & 18 deletions doc/04_Searching_For_Data_In_Index/05_Search_Modifiers/README.md

Large diffs are not rendered by default.

62 changes: 62 additions & 0 deletions src/Model/DefaultSearch/Query/MultiBoolQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);

/**
* This source file is available under the terms of the
* Pimcore Open Core License (POCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
* @license Pimcore Open Core License (POCL)
*/

namespace Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query;

use Pimcore\Bundle\GenericDataIndexBundle\Enum\SearchIndex\DefaultSearch\ConditionType;
use Pimcore\Bundle\GenericDataIndexBundle\Enum\SearchIndex\DefaultSearch\QueryType;

final class MultiBoolQuery extends BoolQuery implements AsSubQueryInterface
{
public function __construct(
private readonly string $field,
/** @var (bool)[] */
private readonly array $terms,
) {
parent::__construct([
ConditionType::FILTER->value => [
QueryType::BOOL->value => [
ConditionType::SHOULD->value => [
(new BoolExistsQuery($this->field))->toArrayAsSubQuery(),
(new TermsFilter($this->field, $this->terms))->toArrayAsSubQuery(),
],
'minimum_should_match' => 1,
],
],
]);
}

public function getField(): string
{
return $this->field;
}

/** @return (bool)[] */
public function getTerms(): array
{
return $this->terms;
}

public function toArrayAsSubQuery(): array
{
return [
QueryType::BOOL->value => [
ConditionType::SHOULD->value => [
(new BoolExistsQuery($this->field))->toArrayAsSubQuery(),
(new TermsFilter($this->field, $this->terms))->toArrayAsSubQuery(),
],
'minimum_should_match' => 1,
],
];
}
}
4 changes: 2 additions & 2 deletions src/Model/DefaultSearch/Query/TermsFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final class TermsFilter extends BoolQuery implements AsSubQueryInterface
{
public function __construct(
private readonly string $field,
/** @var (int|string)[] */
/** @var (int|string|bool)[] */
private readonly array $terms,
) {
parent::__construct([
Expand All @@ -36,7 +36,7 @@ public function getField(): string
return $this->field;
}

/** @return (int|string)[] */
/** @return (int|string|bool)[] */
public function getTerms(): array
{
return $this->terms;
Expand Down
20 changes: 20 additions & 0 deletions src/Model/Search/Modifier/Filter/Basic/ExcludeVariantsFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);

/**
* This source file is available under the terms of the
* Pimcore Open Core License (POCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
* @license Pimcore Open Core License (POCL)
*/

namespace Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic;

use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\SearchModifierInterface;

final readonly class ExcludeVariantsFilter implements SearchModifierInterface
{
}
41 changes: 41 additions & 0 deletions src/Model/Search/Modifier/Filter/Basic/NumberFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);

/**
* This source file is available under the terms of the
* Pimcore Open Core License (POCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
* @license Pimcore Open Core License (POCL)
*/

namespace Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic;

use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\SearchModifierInterface;

final readonly class NumberFilter implements SearchModifierInterface
{
public function __construct(
private string $fieldName,
private int|float $searchTerm,
private bool $enablePqlFieldNameResolution = true,
) {
}

public function getFieldName(): string
{
return $this->fieldName;
}

public function getSearchTerm(): int|float
{
return $this->searchTerm;
}

public function isPqlFieldNameResolutionEnabled(): bool
{
return $this->enablePqlFieldNameResolution;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);

/**
* This source file is available under the terms of the
* Pimcore Open Core License (POCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
* @license Pimcore Open Core License (POCL)
*/

namespace Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\FieldType;

use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\SearchModifierInterface;
use ValueError;

final readonly class BooleanMultiSelectFilter implements SearchModifierInterface
{
public function __construct(
private string $field,
private array $values,
private bool $enablePqlFieldNameResolution = true,
) {
$this->validate();
}

public function getField(): string
{
return $this->field;
}

public function getValues(): array
{
return $this->values;
}

public function isPqlFieldNameResolutionEnabled(): bool
{
return $this->enablePqlFieldNameResolution;
}

private function validate(): void
{
foreach ($this->values as $value) {
if (!is_bool($value) && !is_null($value)) {
throw new ValueError(
sprintf(
'Provided array must contain only boolean or null values. (%s given)',
gettype($value)
),
);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\BooleanFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\IntegerFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\NumberFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\FullTextSearch\FullTextSearch;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\FullTextSearch\WildcardSearch;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\SearchModifierInterface;
Expand All @@ -25,8 +26,8 @@
public function __construct(
private string $fieldName,
private string $group,
private BooleanFilter|DateFilter|FullTextSearch|IntegerFilter|MultiSelectFilter|NumberRangeFilter|
WildcardSearch $subModifier,
private BooleanFilter|DateFilter|FullTextSearch|IntegerFilter|MultiSelectFilter|BooleanMultiSelectFilter|
NumberFilter|NumberRangeFilter|WildcardSearch $subModifier,
private string $locale = MappingProperty::NOT_LOCALIZED_KEY,
) {
}
Expand All @@ -42,7 +43,7 @@ public function getGroup(): string
}

public function getSubModifier(): BooleanFilter|DateFilter|FullTextSearch|IntegerFilter|
MultiSelectFilter|NumberRangeFilter|WildcardSearch
MultiSelectFilter|BooleanMultiSelectFilter|NumberFilter|NumberRangeFilter|WildcardSearch
{
return $this->subModifier;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\DefaultSearch\Search\Modifier\Filter;

use Pimcore\Bundle\GenericDataIndexBundle\Attribute\Search\AsSearchModifierHandler;
use Pimcore\Bundle\GenericDataIndexBundle\Enum\SearchIndex\DefaultSearch\ConditionType;
use Pimcore\Bundle\GenericDataIndexBundle\Enum\SearchIndex\FieldCategory\SystemField;
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Modifier\SearchModifierContextInterface;
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query\BoolExistsQuery;
Expand All @@ -23,10 +24,13 @@
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Interfaces\SearchInterface;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\BooleanFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\ExcludeFoldersFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\ExcludeVariantsFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\IdFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\IdsFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\IntegerFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\NumberFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\SearchPqlFieldNameTransformationServiceInterface;
use Pimcore\Model\DataObject\AbstractObject;

/**
* @internal
Expand All @@ -53,26 +57,34 @@ public function handleIdFilter(IdFilter $idFilter, SearchModifierContextInterfac
public function handleIntegerFilter(IntegerFilter $integerFilter, SearchModifierContextInterface $context): void
{
$context->getSearch()->addQuery(
$this->getIntegerQuery($integerFilter, null, $context->getOriginalSearch())
$this->getNumberQuery($integerFilter, null, $context->getOriginalSearch())
);
}

public function getIntegerQuery(
IntegerFilter $integerFilter,
#[AsSearchModifierHandler]
public function handleNumberFilter(NumberFilter $numberFilter, SearchModifierContextInterface $context): void
{
$context->getSearch()->addQuery(
$this->getNumberQuery($numberFilter, null, $context->getOriginalSearch())
);
}

public function getNumberQuery(
IntegerFilter|NumberFilter $filter,
?string $prefix = null,
?SearchInterface $search = null
): TermFilter {
$fieldName = $integerFilter->getFieldName();
$fieldName = $filter->getFieldName();
if ($prefix) {
$fieldName = $prefix . '.' . $fieldName;
}
if ($search && $integerFilter->isPqlFieldNameResolutionEnabled()) {
if ($search && $filter->isPqlFieldNameResolutionEnabled()) {
$fieldName = $this->fieldNameTransformationService->transformFieldnameForSearch($search, $fieldName);
}

return new TermFilter(
field: $fieldName,
term: $integerFilter->getSearchTerm(),
term: $filter->getSearchTerm(),
);
}

Expand Down Expand Up @@ -125,13 +137,26 @@ public function handleExcludeFoldersFilter(
ExcludeFoldersFilter $excludeFoldersFilter,
SearchModifierContextInterface $context
): void {
$context->getSearch()->addQuery(new BoolQuery([
'must_not' => [
$context->getSearch()->addQuery($this->excludeTypeQuery(AbstractObject::OBJECT_TYPE_FOLDER));
}

#[AsSearchModifierHandler]
public function handleExcludeVariantsFilter(
ExcludeVariantsFilter $excludeVariantsFilter,
SearchModifierContextInterface $context
): void {
$context->getSearch()->addQuery($this->excludeTypeQuery(AbstractObject::OBJECT_TYPE_VARIANT));
}

private function excludeTypeQuery(string $type): BoolQuery
{
return new BoolQuery([
ConditionType::MUST_NOT->value => [
new TermFilter(
field: SystemField::TYPE->getPath(),
term: 'folder',
term: $type,
),
],
]));
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@

use Pimcore\Bundle\GenericDataIndexBundle\Attribute\Search\AsSearchModifierHandler;
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Modifier\SearchModifierContextInterface;
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query\BoolExistsQuery;
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query\DateFilter as DateFilterQuery;
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query\MultiBoolQuery;
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query\Query as QueryFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Model\DefaultSearch\Query\TermsFilter as TermsFilterQuery;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Interfaces\SearchInterface;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\FieldType\BooleanMultiSelectFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\FieldType\DateFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\FieldType\MultiSelectFilter;
use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\FieldType\NumberRangeFilter;
Expand Down Expand Up @@ -97,6 +100,44 @@ public function getMultiSelectQuery(
);
}

#[AsSearchModifierHandler]
public function handleBooleanMultiSelectFilter(
BooleanMultiSelectFilter $filter,
SearchModifierContextInterface $context
): void {
$context->getSearch()->addQuery(
$this->getBooleanMultiSelectFilter($filter, null, $context->getOriginalSearch())
);
}

public function getBooleanMultiSelectFilter(
BooleanMultiSelectFilter $filter,
?string $prefix = null,
?SearchInterface $search = null
): null|BoolExistsQuery|MultiBoolQuery|TermsFilterQuery {
if (count($filter->getValues()) === 0) {
return null;
}

$fieldName = $filter->getField();
if ($prefix) {
$fieldName = $prefix . '.' . $fieldName;
}

if ($search && $filter->isPqlFieldNameResolutionEnabled()) {
$fieldName = $this->fieldNameTransformationService->transformFieldnameForSearch($search, $fieldName);
}

$hasNull = in_array(null, $filter->getValues(), true);
$nonNullValues = array_filter($filter->getValues(), static fn ($v) => $v !== null);

return match (true) {
$hasNull && $nonNullValues !== [] => new MultiBoolQuery($fieldName, $nonNullValues),
$hasNull => new BoolExistsQuery($fieldName),
default => new TermsFilterQuery($fieldName, $nonNullValues),
};
}

#[AsSearchModifierHandler]
public function handleNumberRangeFilter(
NumberRangeFilter $numberRangeFilter,
Expand Down Expand Up @@ -125,8 +166,8 @@ public function getNumberRangeFilter(
'range',
[
$fieldName => [
'gte' => $numberRangeFilter->getMin(),
'lte' => $numberRangeFilter->getMax(),
'gt' => $numberRangeFilter->getMin(),
'lt' => $numberRangeFilter->getMax(),
],
]
);
Expand Down
Loading