Skip to content

Commit a1d24fc

Browse files
committed
ACP2E-4285: Aggregated search field "_search" is no longer used in the search query
1 parent a8cf637 commit a1d24fc

File tree

12 files changed

+360
-11
lines changed

12 files changed

+360
-11
lines changed

app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchProductByAttributeNotSearchableUsedInLayeredNavigationTest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@
7070
<argument name="useInLayeredNavigationValue" value="Filterable (no results)"/>
7171
</actionGroup>
7272
<actionGroup ref="AdminProductAttributeSaveActionGroup" stepKey="saveMultiSelectAttribute"/>
73+
<!-- Run reindex to index invalidated indices after changes to the attribute -->
74+
<actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex">
75+
<argument name="indices" value=""/>
76+
</actionGroup>
7377

7478
<!-- Perform search with attribute value -->
7579
<actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="goToFrontPage"/>

app/code/Magento/CatalogSearch/etc/search_request.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
<queryReference clause="must" ref="visibility"/>
2121
</query>
2222
<query xsi:type="matchQuery" value="$search_term$" name="search">
23-
<match field="name" matchCondition="match_phrase_prefix"/>
23+
<match field="*"/>
2424
</query>
2525
<query xsi:type="matchQuery" value="$search_term$" name="partial_search">
26+
<match field="*"/>
2627
<match field="name" matchCondition="match_phrase_prefix"/>
2728
<match field="sku" matchCondition="match_phrase_prefix"/>
2829
</query>

app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchField.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ class CopySearchableFieldsToSearchField implements FieldsMappingPreprocessorInte
2020
* List of field types to copy
2121
*/
2222
private const FIELD_TYPES = ['text', 'keyword'];
23+
24+
/**
25+
* @var array
26+
*/
27+
private array $exclude = [];
2328
/**
2429
* Add "copy_to" parameter for default search field to index fields.
2530
*
@@ -31,24 +36,40 @@ class CopySearchableFieldsToSearchField implements FieldsMappingPreprocessorInte
3136
public function process(array $mapping): array
3237
{
3338
foreach ($mapping as $field => $definition) {
34-
if ($this->isSearchable($definition)) {
39+
if ($this->isSearchable((string) $field, $definition)) {
3540
$definition['copy_to'][] = '_search';
3641
$mapping[$field] = $definition;
3742
}
3843
}
44+
// Reset exclude list after processing
45+
$this->exclude = [];
3946
return $mapping;
4047
}
4148

49+
/**
50+
* Add fields to exclude from copying to search field
51+
*
52+
* @param array $fields
53+
* @return void
54+
*/
55+
public function addExclude(array $fields): void
56+
{
57+
$this->exclude += array_fill_keys($fields, true);
58+
}
59+
4260
/**
4361
* Determine if the field is searchable by mapping
4462
*
4563
* The field is searchable if it's indexed and its mapping type is either "text" or "keyword"
4664
*
65+
* @param string $field
4766
* @param array $mapping
4867
* @return bool
4968
*/
50-
private function isSearchable(array $mapping): bool
69+
private function isSearchable(string $field, array $mapping): bool
5170
{
52-
return in_array($mapping['type'] ?? null, self::FIELD_TYPES) && (($mapping['index'] ?? true) !== false);
71+
return in_array($mapping['type'] ?? null, self::FIELD_TYPES)
72+
&& (($mapping['index'] ?? true) !== false)
73+
&& !isset($this->exclude[$field]);
5374
}
5475
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product;
10+
11+
interface AttributeFieldsMappingProcessorInterface
12+
{
13+
/**
14+
* Process attribute fields mapping
15+
*
16+
* @param string $attributeCode
17+
* @param array $mapping
18+
* @param array $context
19+
* @return array
20+
*/
21+
public function process(string $attributeCode, array $mapping, array $context = []): array;
22+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product;
10+
11+
/**
12+
* Composite processor for attribute fields mapping
13+
*/
14+
class CompositeAttributeFieldsMappingProcessor implements AttributeFieldsMappingProcessorInterface
15+
{
16+
/**
17+
* @param AttributeFieldsMappingProcessorInterface[] $processors
18+
*/
19+
public function __construct(
20+
private readonly array $processors = []
21+
) {
22+
foreach ($processors as $processor) {
23+
if (!$processor instanceof AttributeFieldsMappingProcessorInterface) {
24+
throw new \InvalidArgumentException(
25+
sprintf(
26+
'Instance of %s is expected, got %s instead.',
27+
AttributeFieldsMappingProcessorInterface::class,
28+
get_class($processor)
29+
)
30+
);
31+
}
32+
}
33+
}
34+
35+
/**
36+
* @inheritDoc
37+
*/
38+
public function process(string $attributeCode, array $mapping, array $context = []): array
39+
{
40+
foreach ($this->processors as $processor) {
41+
$mapping = $processor->process($attributeCode, $mapping, $context);
42+
}
43+
return $mapping;
44+
}
45+
}

app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Magento\Catalog\Api\Data\ProductAttributeInterface;
1111
use Magento\Eav\Model\Config;
1212
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
13+
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeFieldsMappingProcessorInterface;
1314
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider;
1415
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\ConverterInterface
1516
as IndexTypeConverterInterface;
@@ -21,11 +22,13 @@
2122
as FieldTypeResolver;
2223
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface;
2324
use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface;
25+
use Magento\Framework\App\ObjectManager;
2426

2527
/**
2628
* Provide static fields for mapping of product.
2729
* @deprecated Elasticsearch is no longer supported by Adobe
2830
* @see this class will be responsible for ES only
31+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2932
*/
3033
class StaticField implements FieldProviderInterface
3134
{
@@ -69,6 +72,11 @@ class StaticField implements FieldProviderInterface
6972
*/
7073
private $excludedAttributes;
7174

75+
/**
76+
* @var AttributeFieldsMappingProcessorInterface
77+
*/
78+
private $attributeFieldsMappingProcessor;
79+
7280
/**
7381
* @param Config $eavConfig
7482
* @param FieldTypeConverterInterface $fieldTypeConverter
@@ -78,6 +86,7 @@ class StaticField implements FieldProviderInterface
7886
* @param AttributeProvider $attributeAdapterProvider
7987
* @param FieldName\ResolverInterface $fieldNameResolver
8088
* @param array $excludedAttributes
89+
* @param AttributeFieldsMappingProcessorInterface|null $attributeFieldsMappingProcessor
8190
*/
8291
public function __construct(
8392
Config $eavConfig,
@@ -87,7 +96,8 @@ public function __construct(
8796
FieldIndexResolver $fieldIndexResolver,
8897
AttributeProvider $attributeAdapterProvider,
8998
FieldName\ResolverInterface $fieldNameResolver,
90-
array $excludedAttributes = []
99+
array $excludedAttributes = [],
100+
?AttributeFieldsMappingProcessorInterface $attributeFieldsMappingProcessor = null
91101
) {
92102
$this->eavConfig = $eavConfig;
93103
$this->fieldTypeConverter = $fieldTypeConverter;
@@ -97,6 +107,8 @@ public function __construct(
97107
$this->attributeAdapterProvider = $attributeAdapterProvider;
98108
$this->fieldNameResolver = $fieldNameResolver;
99109
$this->excludedAttributes = $excludedAttributes;
110+
$this->attributeFieldsMappingProcessor = $attributeFieldsMappingProcessor
111+
?? ObjectManager::getInstance()->get(AttributeFieldsMappingProcessorInterface::class);
100112
}
101113

102114
/**
@@ -211,7 +223,10 @@ public function getField(AbstractAttribute $attribute): array
211223
}
212224
}
213225

214-
return $fieldMapping;
226+
return $this->attributeFieldsMappingProcessor->process(
227+
$attribute->getAttributeCode(),
228+
$fieldMapping
229+
);
215230
}
216231

217232
/**
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product;
10+
11+
use Magento\Catalog\Api\Data\ProductAttributeInterface;
12+
use Magento\Eav\Model\Config;
13+
use Magento\Elasticsearch\Model\Adapter\FieldMapper\CopySearchableFieldsToSearchField;
14+
15+
/**
16+
* Processes attribute fields mapping for searchable attributes.
17+
*
18+
* This processor could eventually replace CopySearchableFieldsToSearchField by adding directly the "copy_to" mapping
19+
* for search fields here.
20+
* For backward compatibility, we will keep CopySearchableFieldsToSearchField and instruct it to exclude
21+
* non-searchable fields.
22+
* CopySearchableFieldsToSearchField cannot determine if a field is searchable because it lacks attribute metadata.
23+
*/
24+
class SearchableAttributeFieldsMappingProcessor implements AttributeFieldsMappingProcessorInterface
25+
{
26+
/**
27+
* @param Config $eavConfig
28+
* @param CopySearchableFieldsToSearchField $copySearchableFieldsToSearchField
29+
*/
30+
public function __construct(
31+
private readonly Config $eavConfig,
32+
private readonly CopySearchableFieldsToSearchField $copySearchableFieldsToSearchField
33+
) {
34+
}
35+
36+
/**
37+
* @inheritDoc
38+
*/
39+
public function process(string $attributeCode, array $mapping, array $context = []): array
40+
{
41+
$attribute = $this->eavConfig->getAttribute(
42+
ProductAttributeInterface::ENTITY_TYPE_CODE,
43+
$attributeCode
44+
);
45+
46+
if ($attribute && !$attribute->getIsSearchable()) {
47+
$this->copySearchableFieldsToSearchField->addExclude(array_keys($mapping));
48+
}
49+
50+
return $mapping;
51+
}
52+
}

app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchFieldTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,37 @@ public function testProcess(array $mappingBefore, array $mappingAfter)
3030
$this->assertEquals($mappingAfter, $model->process($mappingBefore));
3131
}
3232

33+
/**
34+
* Test excluded fields should not get "copy_to" parameter.
35+
*
36+
* @return void
37+
*/
38+
public function testProcessWithExcludes(): void
39+
{
40+
$model = new CopySearchableFieldsToSearchField();
41+
$mappingBefore = [
42+
'sku' => [
43+
'type' => 'text'
44+
],
45+
'name' => [
46+
'type' => 'text'
47+
]
48+
];
49+
$mappingAfter = [
50+
'sku' => [
51+
'type' => 'text',
52+
'copy_to' => [
53+
'_search'
54+
]
55+
],
56+
'name' => [
57+
'type' => 'text'
58+
]
59+
];
60+
$model->addExclude(['name']);
61+
$this->assertEquals($mappingAfter, $model->process($mappingBefore));
62+
}
63+
3364
/**
3465
* @return array
3566
*/

app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Magento\Eav\Model\Config;
1111
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
1212
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter;
13+
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeFieldsMappingProcessorInterface;
1314
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider;
1415
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\ConverterInterface
1516
as IndexTypeConverterInterface;
@@ -71,6 +72,11 @@ class StaticFieldTest extends TestCase
7172
*/
7273
private $fieldNameResolver;
7374

75+
/**
76+
* @var AttributeFieldsMappingProcessorInterface|MockObject
77+
*/
78+
private $attributeFieldsMappingProcessorMock;
79+
7480
/**
7581
* @inheritdoc
7682
*/
@@ -102,6 +108,10 @@ protected function setUp(): void
102108
->disableOriginalConstructor()
103109
->onlyMethods(['getFieldName'])
104110
->getMock();
111+
112+
$this->attributeFieldsMappingProcessorMock = $this->createMock(
113+
AttributeFieldsMappingProcessorInterface::class
114+
);
105115

106116
$objectManager = new ObjectManagerHelper($this);
107117

@@ -116,6 +126,7 @@ protected function setUp(): void
116126
'fieldTypeResolver' => $this->fieldTypeResolver,
117127
'fieldNameResolver' => $this->fieldNameResolver,
118128
'excludedAttributes' => ['price'],
129+
'attributeFieldsMappingProcessor' => $this->attributeFieldsMappingProcessorMock
119130
]
120131
);
121132
}
@@ -132,6 +143,7 @@ protected function setUp(): void
132143
* @param string $compositeFieldName
133144
* @param string $sortFieldName
134145
* @param array $expected
146+
* @param bool $isProcessed
135147
* @return void
136148
* @dataProvider attributeProvider
137149
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
@@ -149,8 +161,12 @@ public function testGetAllAttributesTypes(
149161
string $fieldName,
150162
string $compositeFieldName,
151163
string $sortFieldName,
152-
array $expected
164+
array $expected,
165+
bool $isProcessed = true
153166
): void {
167+
$this->attributeFieldsMappingProcessorMock->expects($isProcessed ? $this->once() : $this->never())
168+
->method('process')
169+
->willReturnArgument(1);
154170
$this->fieldTypeResolver->expects($this->any())
155171
->method('getFieldType')
156172
->willReturn($inputType);
@@ -403,6 +419,7 @@ public static function attributeProvider(): array
403419
'index' => 'no',
404420
],
405421
],
422+
false
406423
],
407424
];
408425
}

0 commit comments

Comments
 (0)