Total Complexity | 137 |
Total Lines | 1000 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like CriteriaParser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use CriteriaParser, and based on these observations, apply Extract Interface, too.
1 | <?php declare(strict_types=1); |
||
74 | #[Package('core')] |
||
75 | class CriteriaParser |
||
76 | { |
||
77 | /** |
||
78 | * @internal |
||
79 | */ |
||
80 | public function __construct( |
||
81 | private readonly EntityDefinitionQueryHelper $helper, |
||
82 | private readonly CustomFieldService $customFieldService, |
||
83 | private readonly AbstractKeyValueStorage $keyValueStorage |
||
84 | ) { |
||
85 | } |
||
86 | |||
87 | public function buildAccessor(EntityDefinition $definition, string $fieldName, Context $context): string |
||
88 | { |
||
89 | $root = $definition->getEntityName(); |
||
90 | |||
91 | $parts = explode('.', $fieldName); |
||
92 | if ($root === $parts[0]) { |
||
93 | array_shift($parts); |
||
94 | } |
||
95 | |||
96 | $field = $this->helper->getField($fieldName, $definition, $root, false); |
||
97 | if ($field instanceof TranslatedField) { |
||
98 | $ordered = []; |
||
99 | foreach ($parts as $part) { |
||
100 | $ordered[] = $part; |
||
101 | } |
||
102 | $parts = $ordered; |
||
103 | } |
||
104 | |||
105 | if (!$field instanceof PriceField) { |
||
106 | return implode('.', $parts); |
||
107 | } |
||
108 | |||
109 | if (\in_array(end($parts), ['net', 'gross'], true)) { |
||
110 | $taxState = end($parts); |
||
111 | array_pop($parts); |
||
112 | } elseif ($context->getTaxState() === CartPrice::TAX_STATE_GROSS) { |
||
113 | $taxState = 'gross'; |
||
114 | } else { |
||
115 | $taxState = 'net'; |
||
116 | } |
||
117 | |||
118 | $currencyId = $context->getCurrencyId(); |
||
119 | if (Uuid::isValid((string) end($parts))) { |
||
120 | $currencyId = end($parts); |
||
121 | array_pop($parts); |
||
122 | } |
||
123 | |||
124 | $parts[] = 'c_' . $currencyId; |
||
125 | $parts[] = $taxState; |
||
126 | |||
127 | return implode('.', $parts); |
||
128 | } |
||
129 | |||
130 | public function parseSorting(FieldSorting $sorting, EntityDefinition $definition, Context $context): FieldSort |
||
131 | { |
||
132 | if ($this->isCheapestPriceField($sorting->getField())) { |
||
133 | return new FieldSort('_script', $sorting->getDirection(), null, [ |
||
134 | 'type' => 'number', |
||
135 | 'script' => [ |
||
136 | 'id' => 'cheapest_price', |
||
137 | 'params' => $this->getCheapestPriceParameters($context), |
||
138 | ], |
||
139 | ]); |
||
140 | } |
||
141 | |||
142 | if ($this->isCheapestPriceField($sorting->getField(), true)) { |
||
143 | return new FieldSort('_script', $sorting->getDirection(), null, [ |
||
144 | 'type' => 'number', |
||
145 | 'script' => [ |
||
146 | 'id' => 'cheapest_price_percentage', |
||
147 | 'params' => ['accessors' => $this->getCheapestPriceAccessors($context, true)], |
||
148 | ], |
||
149 | ]); |
||
150 | } |
||
151 | |||
152 | if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) { |
||
|
|||
153 | $field = $this->helper->getField($sorting->getField(), $definition, $definition->getEntityName(), false); |
||
154 | |||
155 | if ($field instanceof TranslatedField) { |
||
156 | return $this->createTranslatedSorting($definition->getEntityName(), $sorting, $context); |
||
157 | } |
||
158 | } |
||
159 | |||
160 | $accessor = $this->buildAccessor($definition, $sorting->getField(), $context); |
||
161 | |||
162 | if ($sorting instanceof CountSorting) { |
||
163 | return new CountSort($accessor, $sorting->getDirection()); |
||
164 | } |
||
165 | |||
166 | return new FieldSort($accessor, $sorting->getDirection()); |
||
167 | } |
||
168 | |||
169 | public function parseAggregation(Aggregation $aggregation, EntityDefinition $definition, Context $context): ?AbstractAggregation |
||
190 | } |
||
191 | |||
192 | public function parseFilter(Filter $filter, EntityDefinition $definition, string $root, Context $context): BuilderInterface |
||
193 | { |
||
194 | return match (true) { |
||
195 | $filter instanceof NotFilter => $this->parseNotFilter($filter, $definition, $root, $context), |
||
196 | $filter instanceof MultiFilter => $this->parseMultiFilter($filter, $definition, $root, $context), |
||
197 | $filter instanceof EqualsFilter => $this->parseEqualsFilter($filter, $definition, $context), |
||
198 | $filter instanceof EqualsAnyFilter => $this->parseEqualsAnyFilter($filter, $definition, $context), |
||
199 | $filter instanceof ContainsFilter => $this->parseContainsFilter($filter, $definition, $context), |
||
200 | $filter instanceof PrefixFilter => $this->parsePrefixFilter($filter, $definition, $context), |
||
201 | $filter instanceof SuffixFilter => $this->parseSuffixFilter($filter, $definition, $context), |
||
202 | $filter instanceof RangeFilter => $this->parseRangeFilter($filter, $definition, $context), |
||
203 | default => throw new \RuntimeException(sprintf('Unsupported filter %s', $filter::class)), |
||
204 | }; |
||
205 | } |
||
206 | |||
207 | protected function parseFilterAggregation(FilterAggregation $aggregation, EntityDefinition $definition, Context $context): AbstractAggregation |
||
208 | { |
||
209 | if ($aggregation->getAggregation() === null) { |
||
210 | throw new \RuntimeException(sprintf('Filter aggregation %s contains no nested aggregation.', $aggregation->getName())); |
||
211 | } |
||
212 | |||
213 | $nested = $this->parseAggregation($aggregation->getAggregation(), $definition, $context); |
||
214 | if ($nested === null) { |
||
215 | throw new \RuntimeException(sprintf('Nested filter aggregation %s can not be parsed.', $aggregation->getName())); |
||
216 | } |
||
217 | |||
218 | // when aggregation inside the filter aggregation points to a nested object (e.g. product.properties.id) we have to add all filters |
||
219 | // which points to the same association to the same "nesting" level like the nested aggregation for this association |
||
220 | $path = $nested instanceof NestedAggregation ? $nested->getPath() : null; |
||
221 | $bool = new BoolQuery(); |
||
222 | |||
223 | $filters = []; |
||
224 | foreach ($aggregation->getFilter() as $filter) { |
||
225 | $query = $this->parseFilter($filter, $definition, $definition->getEntityName(), $context); |
||
226 | |||
227 | if (!$query instanceof NestedQuery) { |
||
228 | $filters[] = new Bucketing\FilterAggregation($aggregation->getName(), $query); |
||
229 | |||
230 | continue; |
||
231 | } |
||
232 | |||
233 | // same property path as the "real" aggregation |
||
234 | if ($query->getPath() === $path) { |
||
235 | $bool->add($query->getQuery()); |
||
236 | |||
237 | continue; |
||
238 | } |
||
239 | |||
240 | // query points to a nested document property - we have to define that the filter points to this field |
||
241 | $parsed = new NestedAggregation($aggregation->getName(), $query->getPath()); |
||
242 | |||
243 | // now we can defined a filter which points to the nested field (remove NestedQuery layer) |
||
244 | $filter = new Bucketing\FilterAggregation($aggregation->getName(), $query->getQuery()); |
||
245 | |||
246 | // afterwards we reset the nesting to allow following filters to point to another nested property |
||
247 | $reverse = new ReverseNestedAggregation($aggregation->getName()); |
||
248 | |||
249 | $filter->addAggregation($reverse); |
||
250 | |||
251 | $parsed->addAggregation($filter); |
||
252 | |||
253 | $filters[] = $parsed; |
||
254 | } |
||
255 | |||
256 | // nested aggregation should have filters - we have to remap the nesting |
||
257 | $mapped = $nested; |
||
258 | if (\count($bool->getQueries()) > 0 && $nested instanceof NestedAggregation) { |
||
259 | $real = $nested->getAggregation($nested->getName()); |
||
260 | if (!$real instanceof AbstractAggregation) { |
||
261 | throw new \RuntimeException(sprintf('Nested filter aggregation %s can not be parsed.', $aggregation->getName())); |
||
262 | } |
||
263 | |||
264 | $filter = new Bucketing\FilterAggregation($aggregation->getName(), $bool); |
||
265 | $filter->addAggregation($real); |
||
266 | |||
267 | $mapped = new NestedAggregation($aggregation->getName(), $nested->getPath()); |
||
268 | $mapped->addAggregation($filter); |
||
269 | } |
||
270 | |||
271 | // at this point we have to walk over all filters and create one nested filter for it |
||
272 | $parent = null; |
||
273 | $root = $mapped; |
||
274 | foreach ($filters as $filter) { |
||
275 | if ($parent === null) { |
||
276 | $parent = $filter; |
||
277 | $root = $filter; |
||
278 | |||
279 | continue; |
||
280 | } |
||
281 | |||
282 | $parent->addAggregation($filter); |
||
283 | |||
284 | if (!$filter instanceof NestedAggregation) { |
||
285 | $parent = $filter; |
||
286 | |||
287 | continue; |
||
288 | } |
||
289 | |||
290 | $filter = $filter->getAggregation($filter->getName()); |
||
291 | if (!$filter instanceof AbstractAggregation) { |
||
292 | throw new \RuntimeException('Expected nested+filter+reverse pattern for parsed filters to set next parent correctly'); |
||
293 | } |
||
294 | |||
295 | $parent = $filter->getAggregation($filter->getName()); |
||
296 | if (!$parent instanceof AbstractAggregation) { |
||
297 | throw new \RuntimeException('Expected nested+filter+reverse pattern for parsed filters to set next parent correctly'); |
||
298 | } |
||
299 | } |
||
300 | |||
301 | // it can happen, that $parent is not defined if the "real" aggregation is a nested and all filters points to the same property |
||
302 | // than we return the following structure: [nested-agg] + filter-agg + real-agg ( [] = optional ) |
||
303 | if ($parent === null) { |
||
304 | return $root; |
||
305 | } |
||
306 | |||
307 | // at this point we have some other filters which point to another nested object as the "real" aggregation |
||
308 | // than we return the following structure: [nested-agg] + filter-agg + [reverse-nested-agg] + [nested-agg] + real-agg ( [] = optional ) |
||
309 | $parent->addAggregation($mapped); |
||
310 | |||
311 | return $root; |
||
312 | } |
||
313 | |||
314 | protected function parseTermsAggregation(TermsAggregation $aggregation, string $fieldName, EntityDefinition $definition, Context $context): AbstractAggregation |
||
315 | { |
||
316 | if ($aggregation->getSorting() === null) { |
||
317 | $terms = new Bucketing\TermsAggregation($aggregation->getName(), $fieldName); |
||
318 | |||
319 | if ($nested = $aggregation->getAggregation()) { |
||
320 | $terms->addAggregation( |
||
321 | $this->parseNestedAggregation($nested, $definition, $context) |
||
322 | ); |
||
323 | } |
||
324 | |||
325 | // set default size to 10.000 => max for default configuration |
||
326 | $terms->addParameter('size', ElasticsearchHelper::MAX_SIZE_VALUE); |
||
327 | |||
328 | if ($aggregation->getLimit()) { |
||
329 | $terms->addParameter('size', (string) $aggregation->getLimit()); |
||
330 | } |
||
331 | |||
332 | return $terms; |
||
333 | } |
||
334 | |||
335 | $composite = new CompositeAggregation($aggregation->getName()); |
||
336 | |||
337 | $accessor = $this->buildAccessor($definition, $aggregation->getSorting()->getField(), $context); |
||
338 | |||
339 | $sorting = new Bucketing\TermsAggregation($aggregation->getName() . '.sorting', $accessor); |
||
340 | $sorting->addParameter('order', $aggregation->getSorting()->getDirection()); |
||
341 | $composite->addSource($sorting); |
||
342 | |||
343 | $terms = new Bucketing\TermsAggregation($aggregation->getName() . '.key', $fieldName); |
||
344 | $composite->addSource($terms); |
||
345 | |||
346 | if ($nested = $aggregation->getAggregation()) { |
||
347 | $composite->addAggregation( |
||
348 | $this->parseNestedAggregation($nested, $definition, $context) |
||
349 | ); |
||
350 | } |
||
351 | |||
352 | // set default size to 10.000 => max for default configuration |
||
353 | $composite->addParameter('size', ElasticsearchHelper::MAX_SIZE_VALUE); |
||
354 | |||
355 | if ($aggregation->getLimit()) { |
||
356 | $composite->addParameter('size', (string) $aggregation->getLimit()); |
||
357 | } |
||
358 | |||
359 | return $composite; |
||
360 | } |
||
361 | |||
362 | protected function parseStatsAggregation(StatsAggregation $aggregation, string $fieldName, Context $context): Metric\StatsAggregation |
||
363 | { |
||
364 | if ($this->isCheapestPriceField($aggregation->getField())) { |
||
365 | return new Metric\StatsAggregation($aggregation->getName(), null, [ |
||
366 | 'id' => 'cheapest_price', |
||
367 | 'params' => $this->getCheapestPriceParameters($context), |
||
368 | ]); |
||
369 | } |
||
370 | |||
371 | if ($this->isCheapestPriceField($aggregation->getField(), true)) { |
||
372 | return new Metric\StatsAggregation($aggregation->getName(), null, [ |
||
373 | 'id' => 'cheapest_price_percentage', |
||
374 | 'params' => ['accessors' => $this->getCheapestPriceAccessors($context, true)], |
||
375 | ]); |
||
376 | } |
||
377 | |||
378 | return new Metric\StatsAggregation($aggregation->getName(), $fieldName); |
||
379 | } |
||
380 | |||
381 | protected function parseEntityAggregation(EntityAggregation $aggregation, string $fieldName): Bucketing\TermsAggregation |
||
382 | { |
||
383 | $bucketingAggregation = new Bucketing\TermsAggregation($aggregation->getName(), $fieldName); |
||
384 | |||
385 | $bucketingAggregation->addParameter('size', ElasticsearchHelper::MAX_SIZE_VALUE); |
||
386 | |||
387 | return $bucketingAggregation; |
||
388 | } |
||
389 | |||
390 | protected function parseDateHistogramAggregation(DateHistogramAggregation $aggregation, string $fieldName, EntityDefinition $definition, Context $context): CompositeAggregation |
||
391 | { |
||
392 | $composite = new CompositeAggregation($aggregation->getName()); |
||
393 | |||
394 | if ($fieldSorting = $aggregation->getSorting()) { |
||
395 | $accessor = $this->buildAccessor($definition, $fieldSorting->getField(), $context); |
||
396 | |||
397 | $sorting = new Bucketing\TermsAggregation($aggregation->getName() . '.sorting', $accessor); |
||
398 | $sorting->addParameter('order', $fieldSorting->getDirection()); |
||
399 | |||
400 | $composite->addSource($sorting); |
||
401 | } |
||
402 | |||
403 | $histogram = new ElasticsearchDateHistogramAggregation( |
||
404 | $aggregation->getName() . '.key', |
||
405 | $fieldName, |
||
406 | $aggregation->getInterval(), |
||
407 | 'yyyy-MM-dd HH:mm:ss' |
||
408 | ); |
||
409 | |||
410 | if ($aggregation->getTimeZone()) { |
||
411 | $histogram->addParameter('time_zone', $aggregation->getTimeZone()); |
||
412 | } |
||
413 | |||
414 | $composite->addSource($histogram); |
||
415 | |||
416 | if ($nested = $aggregation->getAggregation()) { |
||
417 | $composite->addAggregation( |
||
418 | $this->parseNestedAggregation($nested, $definition, $context) |
||
419 | ); |
||
420 | } |
||
421 | |||
422 | return $composite; |
||
423 | } |
||
424 | |||
425 | protected function parseRangeAggregation(RangeAggregation $aggregation, string $fieldName): Bucketing\RangeAggregation |
||
426 | { |
||
427 | return new Bucketing\RangeAggregation( |
||
428 | $aggregation->getName(), |
||
429 | $fieldName, |
||
430 | $aggregation->getRanges() |
||
431 | ); |
||
432 | } |
||
433 | |||
434 | /** |
||
435 | * @return array<string, mixed> |
||
436 | */ |
||
437 | private function getCheapestPriceParameters(Context $context): array |
||
438 | { |
||
439 | return [ |
||
440 | 'accessors' => $this->getCheapestPriceAccessors($context), |
||
441 | 'decimals' => 10 ** $context->getRounding()->getDecimals(), |
||
442 | 'round' => $this->useCashRounding($context), |
||
443 | 'multiplier' => 100 / ($context->getRounding()->getInterval() * 100), |
||
444 | ]; |
||
445 | } |
||
446 | |||
447 | private function useCashRounding(Context $context): bool |
||
448 | { |
||
449 | if ($context->getRounding()->getDecimals() !== 2) { |
||
450 | return false; |
||
451 | } |
||
452 | |||
453 | if ($context->getTaxState() === CartPrice::TAX_STATE_GROSS) { |
||
454 | return true; |
||
455 | } |
||
456 | |||
457 | return $context->getRounding()->roundForNet(); |
||
458 | } |
||
459 | |||
460 | /** |
||
461 | * @return array<int, array<string, string|float>> |
||
462 | */ |
||
463 | private function getCheapestPriceAccessors(Context $context, bool $percentage = false): array |
||
464 | { |
||
465 | $accessors = []; |
||
466 | |||
467 | $tax = $context->getTaxState() === CartPrice::TAX_STATE_GROSS ? 'gross' : 'net'; |
||
468 | |||
469 | $ruleIds = array_merge($context->getRuleIds(), ['default']); |
||
470 | |||
471 | foreach ($ruleIds as $ruleId) { |
||
472 | $key = implode('_', [ |
||
473 | 'cheapest_price', |
||
474 | 'rule' . $ruleId, |
||
475 | 'currency' . $context->getCurrencyId(), |
||
476 | $tax, |
||
477 | ]); |
||
478 | |||
479 | if ($percentage) { |
||
480 | $key .= '_percentage'; |
||
481 | } |
||
482 | |||
483 | $accessors[] = ['key' => $key, 'factor' => 1]; |
||
484 | |||
485 | if ($context->getCurrencyId() === Defaults::CURRENCY) { |
||
486 | continue; |
||
487 | } |
||
488 | |||
489 | $key = implode('_', [ |
||
490 | 'cheapest_price', |
||
491 | 'rule' . $ruleId, |
||
492 | 'currency' . Defaults::CURRENCY, |
||
493 | $tax, |
||
494 | ]); |
||
495 | |||
496 | if ($percentage) { |
||
497 | $key .= '_percentage'; |
||
498 | } |
||
499 | |||
500 | $accessors[] = ['key' => $key, 'factor' => $context->getCurrencyFactor()]; |
||
501 | } |
||
502 | |||
503 | return $accessors; |
||
504 | } |
||
505 | |||
506 | private function parseNestedAggregation(Aggregation $aggregation, EntityDefinition $definition, Context $context): AbstractAggregation |
||
507 | { |
||
508 | $fieldName = $this->buildAccessor($definition, $aggregation->getField(), $context); |
||
509 | |||
510 | return $this->createAggregation($aggregation, $fieldName, $definition, $context); |
||
511 | } |
||
512 | |||
513 | private function createAggregation(Aggregation $aggregation, string $fieldName, EntityDefinition $definition, Context $context): AbstractAggregation |
||
514 | { |
||
515 | if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) { |
||
516 | $field = $this->getField($definition, $fieldName); |
||
517 | |||
518 | if ($field instanceof TranslatedField) { |
||
519 | $fieldName = $this->getTranslatedFieldName($fieldName, $context->getLanguageId()); |
||
520 | } |
||
521 | } |
||
522 | |||
523 | return match (true) { |
||
524 | $aggregation instanceof StatsAggregation => $this->parseStatsAggregation($aggregation, $fieldName, $context), |
||
525 | $aggregation instanceof AvgAggregation => new Metric\AvgAggregation($aggregation->getName(), $fieldName), |
||
526 | $aggregation instanceof EntityAggregation => $this->parseEntityAggregation($aggregation, $fieldName), |
||
527 | $aggregation instanceof MaxAggregation => new Metric\MaxAggregation($aggregation->getName(), $fieldName), |
||
528 | $aggregation instanceof MinAggregation => new Metric\MinAggregation($aggregation->getName(), $fieldName), |
||
529 | $aggregation instanceof SumAggregation => new Metric\SumAggregation($aggregation->getName(), $fieldName), |
||
530 | $aggregation instanceof CountAggregation => new ValueCountAggregation($aggregation->getName(), $fieldName), |
||
531 | $aggregation instanceof FilterAggregation => $this->parseFilterAggregation($aggregation, $definition, $context), |
||
532 | $aggregation instanceof TermsAggregation => $this->parseTermsAggregation($aggregation, $fieldName, $definition, $context), |
||
533 | $aggregation instanceof DateHistogramAggregation => $this->parseDateHistogramAggregation($aggregation, $fieldName, $definition, $context), |
||
534 | $aggregation instanceof RangeAggregation => $this->parseRangeAggregation($aggregation, $fieldName), |
||
535 | default => throw new \RuntimeException(sprintf('Provided aggregation of class %s not supported', $aggregation::class)), |
||
536 | }; |
||
537 | } |
||
538 | |||
539 | private function parseEqualsFilter(EqualsFilter $filter, EntityDefinition $definition, Context $context): BuilderInterface |
||
540 | { |
||
541 | if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) { |
||
542 | $fieldName = $this->buildAccessor($definition, $filter->getField(), $context); |
||
543 | |||
544 | $field = $this->getField($definition, $fieldName); |
||
545 | |||
546 | if ($filter->getValue() === null) { |
||
547 | $query = new BoolQuery(); |
||
548 | |||
549 | if ($field instanceof TranslatedField) { |
||
550 | foreach ($context->getLanguageIdChain() as $languageId) { |
||
551 | $query->add(new ExistsQuery(sprintf('%s.%s', $fieldName, $languageId)), BoolQuery::MUST_NOT); |
||
552 | } |
||
553 | } else { |
||
554 | $query->add(new ExistsQuery($fieldName), BoolQuery::MUST_NOT); |
||
555 | } |
||
556 | |||
557 | return $this->createNestedQuery($query, $definition, $filter->getField()); |
||
558 | } |
||
559 | |||
560 | $value = $this->parseValue($definition, $filter, $filter->getValue()); |
||
561 | $query = new TermQuery($fieldName, $value); |
||
562 | |||
563 | if ($field instanceof TranslatedField) { |
||
564 | $multiMatchFields = []; |
||
565 | |||
566 | foreach ($context->getLanguageIdChain() as $languageId) { |
||
567 | $multiMatchFields[] = $this->getTranslatedFieldName($fieldName, $languageId); |
||
568 | } |
||
569 | |||
570 | $query = new MultiMatchQuery($multiMatchFields, $value, [ |
||
571 | 'type' => 'best_fields', |
||
572 | ]); |
||
573 | } |
||
574 | |||
575 | return $this->createNestedQuery($query, $definition, $filter->getField()); |
||
576 | } |
||
577 | |||
578 | $fieldName = $this->buildAccessor($definition, $filter->getField(), $context); |
||
579 | |||
580 | if ($filter->getValue() === null) { |
||
581 | $query = new BoolQuery(); |
||
582 | $query->add(new ExistsQuery($fieldName), BoolQuery::MUST_NOT); |
||
583 | |||
584 | return $this->createNestedQuery($query, $definition, $filter->getField()); |
||
585 | } |
||
586 | |||
587 | $value = $this->parseValue($definition, $filter, $filter->getValue()); |
||
588 | |||
589 | $query = new TermQuery($fieldName, $value); |
||
590 | |||
591 | return $this->createNestedQuery($query, $definition, $filter->getField()); |
||
592 | } |
||
593 | |||
594 | private function parseEqualsAnyFilter(EqualsAnyFilter $filter, EntityDefinition $definition, Context $context): BuilderInterface |
||
626 | ); |
||
627 | } |
||
628 | |||
629 | private function parseContainsFilter(ContainsFilter $filter, EntityDefinition $definition, Context $context): BuilderInterface |
||
630 | { |
||
631 | $accessor = $this->buildAccessor($definition, $filter->getField(), $context); |
||
632 | |||
633 | /** @var string $value */ |
||
634 | $value = $filter->getValue(); |
||
635 | |||
636 | if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) { |
||
637 | $field = $this->getField($definition, $filter->getField()); |
||
638 | |||
639 | $query = new WildcardQuery($accessor, '*' . $value . '*'); |
||
640 | |||
641 | if ($field instanceof TranslatedField) { |
||
642 | $query = new DisMaxQuery(); |
||
643 | foreach ($context->getLanguageIdChain() as $languageId) { |
||
644 | $fieldName = $this->getTranslatedFieldName($accessor, $languageId); |
||
645 | $query->addQuery(new WildcardQuery($fieldName, '*' . $value . '*')); |
||
646 | } |
||
647 | } |
||
648 | |||
649 | return $this->createNestedQuery( |
||
650 | $query, |
||
651 | $definition, |
||
652 | $filter->getField() |
||
653 | ); |
||
654 | } |
||
655 | |||
656 | return $this->createNestedQuery( |
||
657 | new WildcardQuery($accessor, '*' . $value . '*'), |
||
658 | $definition, |
||
659 | $filter->getField() |
||
660 | ); |
||
661 | } |
||
662 | |||
663 | private function parsePrefixFilter(PrefixFilter $filter, EntityDefinition $definition, Context $context): BuilderInterface |
||
664 | { |
||
665 | $accessor = $this->buildAccessor($definition, $filter->getField(), $context); |
||
666 | |||
667 | $value = $filter->getValue(); |
||
668 | |||
669 | if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) { |
||
670 | $field = $this->getField($definition, $filter->getField()); |
||
671 | |||
672 | $query = new PrefixQuery($accessor, $value); |
||
673 | |||
674 | if ($field instanceof TranslatedField) { |
||
675 | $multiMatchFields = []; |
||
676 | |||
677 | foreach ($context->getLanguageIdChain() as $languageId) { |
||
678 | $multiMatchFields[] = $this->getTranslatedFieldName($accessor, $languageId) . '.search'; |
||
679 | } |
||
680 | |||
681 | $query = new MultiMatchQuery($multiMatchFields, $value, [ |
||
682 | 'type' => 'phrase_prefix', |
||
683 | 'slop' => 5, |
||
684 | ]); |
||
685 | } |
||
686 | |||
687 | return $this->createNestedQuery( |
||
688 | $query, |
||
689 | $definition, |
||
690 | $filter->getField() |
||
691 | ); |
||
692 | } |
||
693 | |||
694 | return $this->createNestedQuery( |
||
695 | new PrefixQuery($accessor, $value), |
||
696 | $definition, |
||
697 | $filter->getField() |
||
698 | ); |
||
699 | } |
||
700 | |||
701 | private function parseSuffixFilter(SuffixFilter $filter, EntityDefinition $definition, Context $context): BuilderInterface |
||
702 | { |
||
703 | $accessor = $this->buildAccessor($definition, $filter->getField(), $context); |
||
704 | |||
705 | $value = $filter->getValue(); |
||
706 | |||
707 | if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) { |
||
708 | $field = $this->getField($definition, $filter->getField()); |
||
709 | |||
710 | $query = new WildcardQuery($accessor, '*' . $value); |
||
711 | |||
712 | if ($field instanceof TranslatedField) { |
||
713 | $query = new DisMaxQuery(); |
||
714 | foreach ($context->getLanguageIdChain() as $languageId) { |
||
715 | $fieldName = $this->getTranslatedFieldName($accessor, $languageId); |
||
716 | $query->addQuery(new WildcardQuery($fieldName, '*' . $value)); |
||
717 | } |
||
718 | } |
||
719 | |||
720 | return $this->createNestedQuery( |
||
721 | $query, |
||
722 | $definition, |
||
723 | $filter->getField() |
||
724 | ); |
||
725 | } |
||
726 | |||
727 | return $this->createNestedQuery( |
||
728 | new WildcardQuery($accessor, '*' . $value), |
||
729 | $definition, |
||
730 | $filter->getField() |
||
731 | ); |
||
732 | } |
||
733 | |||
734 | private function parseRangeFilter(RangeFilter $filter, EntityDefinition $definition, Context $context): BuilderInterface |
||
781 | ); |
||
782 | } |
||
783 | |||
784 | private function isCheapestPriceField(string $field, bool $percentage = false): bool |
||
785 | { |
||
786 | if ($percentage) { |
||
787 | $haystack = ['product.cheapestPrice.percentage', 'cheapestPrice.percentage']; |
||
788 | } else { |
||
789 | $haystack = ['product.cheapestPrice', 'cheapestPrice']; |
||
790 | } |
||
791 | |||
792 | return \in_array($field, $haystack, true); |
||
793 | } |
||
794 | |||
795 | /** |
||
796 | * @return array<string, float> |
||
797 | */ |
||
798 | private function getRangeParameters(RangeFilter $filter): array |
||
799 | { |
||
800 | $params = []; |
||
801 | foreach ($filter->getParameters() as $key => $value) { |
||
802 | $params[$key] = (float) $value; |
||
803 | } |
||
804 | |||
805 | return $params; |
||
806 | } |
||
807 | |||
808 | private function parseNotFilter(NotFilter $filter, EntityDefinition $definition, string $root, Context $context): BuilderInterface |
||
809 | { |
||
810 | $bool = new BoolQuery(); |
||
811 | if (\count($filter->getQueries()) === 0) { |
||
812 | return $bool; |
||
813 | } |
||
814 | |||
815 | if (\count($filter->getQueries()) === 1) { |
||
816 | $bool->add( |
||
817 | $this->parseFilter($filter->getQueries()[0], $definition, $root, $context), |
||
818 | BoolQuery::MUST_NOT |
||
819 | ); |
||
820 | |||
821 | return $bool; |
||
822 | } |
||
823 | |||
824 | $multiFilter = match ($filter->getOperator()) { |
||
825 | MultiFilter::CONNECTION_OR => new OrFilter(), |
||
826 | MultiFilter::CONNECTION_XOR => new XOrFilter(), |
||
827 | default => new AndFilter(), |
||
828 | }; |
||
829 | |||
830 | foreach ($filter->getQueries() as $query) { |
||
831 | $multiFilter->addQuery($query); |
||
832 | } |
||
833 | |||
834 | $bool->add( |
||
835 | $this->parseFilter($multiFilter, $definition, $root, $context), |
||
836 | BoolQuery::MUST_NOT |
||
837 | ); |
||
838 | |||
839 | return $bool; |
||
840 | } |
||
841 | |||
842 | private function parseMultiFilter(MultiFilter $filter, EntityDefinition $definition, string $root, Context $context): BuilderInterface |
||
843 | { |
||
844 | return match ($filter->getOperator()) { |
||
845 | MultiFilter::CONNECTION_OR => $this->parseOrMultiFilter($filter, $definition, $root, $context), |
||
846 | MultiFilter::CONNECTION_AND => $this->parseAndMultiFilter($filter, $definition, $root, $context), |
||
847 | MultiFilter::CONNECTION_XOR => $this->parseXorMultiFilter($filter, $definition, $root, $context), |
||
848 | default => throw new \InvalidArgumentException('Operator ' . $filter->getOperator() . ' not allowed'), |
||
849 | }; |
||
850 | } |
||
851 | |||
852 | private function parseAndMultiFilter(MultiFilter $filter, EntityDefinition $definition, string $root, Context $context): BuilderInterface |
||
853 | { |
||
854 | $grouped = []; |
||
855 | $bool = new BoolQuery(); |
||
856 | |||
857 | foreach ($filter->getQueries() as $nested) { |
||
858 | $query = $this->parseFilter($nested, $definition, $root, $context); |
||
859 | |||
860 | if (!$query instanceof NestedQuery) { |
||
861 | $bool->add($query, BoolQuery::MUST); |
||
862 | |||
863 | continue; |
||
864 | } |
||
865 | |||
866 | if (!\array_key_exists($query->getPath(), $grouped)) { |
||
867 | $grouped[$query->getPath()] = new BoolQuery(); |
||
868 | $bool->add(new NestedQuery($query->getPath(), $grouped[$query->getPath()])); |
||
869 | } |
||
870 | |||
871 | $grouped[$query->getPath()]->add($query->getQuery()); |
||
872 | } |
||
873 | |||
874 | return $bool; |
||
875 | } |
||
876 | |||
877 | private function parseOrMultiFilter(MultiFilter $filter, EntityDefinition $definition, string $root, Context $context): BuilderInterface |
||
878 | { |
||
879 | $bool = new BoolQuery(); |
||
880 | |||
881 | foreach ($filter->getQueries() as $nested) { |
||
882 | $bool->add( |
||
883 | $this->parseFilter($nested, $definition, $root, $context), |
||
884 | BoolQuery::SHOULD |
||
885 | ); |
||
886 | } |
||
887 | |||
888 | return $bool; |
||
889 | } |
||
890 | |||
891 | private function parseXorMultiFilter(MultiFilter $filter, EntityDefinition $definition, string $root, Context $context): BuilderInterface |
||
892 | { |
||
893 | $bool = new BoolQuery(); |
||
894 | |||
895 | foreach ($filter->getQueries() as $nested) { |
||
896 | $xorQuery = new BoolQuery(); |
||
897 | foreach ($filter->getQueries() as $mustNot) { |
||
898 | if ($nested === $mustNot) { |
||
899 | $xorQuery->add($this->parseFilter($nested, $definition, $root, $context), BoolQuery::MUST); |
||
900 | |||
901 | continue; |
||
902 | } |
||
903 | |||
904 | $xorQuery->add($this->parseFilter($mustNot, $definition, $root, $context), BoolQuery::MUST_NOT); |
||
905 | } |
||
906 | |||
907 | $bool->add( |
||
908 | $xorQuery, |
||
909 | BoolQuery::SHOULD |
||
910 | ); |
||
911 | } |
||
912 | |||
913 | return $bool; |
||
914 | } |
||
915 | |||
916 | private function createNestedQuery(BuilderInterface $query, EntityDefinition $definition, string $field): BuilderInterface |
||
917 | { |
||
918 | $path = $this->getNestedPath($definition, $field); |
||
919 | |||
920 | if ($path) { |
||
921 | return new NestedQuery($path, $query); |
||
922 | } |
||
923 | |||
924 | return $query; |
||
925 | } |
||
926 | |||
927 | private function getField(EntityDefinition $definition, string $fieldName): ?Field |
||
937 | } |
||
938 | |||
939 | private function getNestedPath(EntityDefinition $definition, string $accessor): ?string |
||
940 | { |
||
941 | if (mb_strpos($accessor, $definition->getEntityName() . '.') === false) { |
||
942 | $accessor = $definition->getEntityName() . '.' . $accessor; |
||
961 | } |
||
962 | |||
963 | private function parseValue(EntityDefinition $definition, SingleFieldFilter $filter, mixed $value): mixed |
||
964 | { |
||
965 | $field = $this->getField($definition, $filter->getField()); |
||
966 | |||
967 | if ($field instanceof TranslatedField) { |
||
968 | $field = EntityDefinitionQueryHelper::getTranslatedField($definition, $field); |
||
969 | } |
||
970 | |||
971 | if ($field instanceof CustomFields) { |
||
972 | $accessor = \explode('.', $filter->getField()); |
||
1013 | } |
||
1014 | |||
1015 | private function createTranslatedSorting(string $root, FieldSorting $sorting, Context $context): FieldSort |
||
1016 | { |
||
1017 | $parts = explode('.', $sorting->getField()); |
||
1018 | if ($root === $parts[0]) { |
||
1019 | array_shift($parts); |
||
1020 | } |
||
1021 | |||
1022 | if ($parts[0] === 'customFields') { |
||
1023 | $customField = $this->customFieldService->getCustomField($parts[1]); |
||
1024 | |||
1025 | if ($customField instanceof IntField || $customField instanceof FloatField) { |
||
1026 | return new FieldSort('_script', $sorting->getDirection(), null, [ |
||
1027 | 'type' => 'number', |
||
1028 | 'script' => [ |
||
1029 | 'id' => 'numeric_translated_field_sorting', |
||
1030 | 'params' => [ |
||
1031 | 'field' => 'customFields', |
||
1032 | 'languages' => $context->getLanguageIdChain(), |
||
1033 | 'suffix' => $parts[1] ?? '', |
||
1034 | 'order' => strtolower($sorting->getDirection()), |
||
1035 | ], |
||
1036 | ], |
||
1037 | ]); |
||
1038 | } |
||
1039 | |||
1040 | return new FieldSort('_script', $sorting->getDirection(), null, [ |
||
1041 | 'type' => 'string', |
||
1042 | 'script' => [ |
||
1043 | 'id' => 'translated_field_sorting', |
||
1044 | 'params' => [ |
||
1045 | 'field' => 'customFields', |
||
1046 | 'languages' => $context->getLanguageIdChain(), |
||
1047 | 'suffix' => $parts[1] ?? '', |
||
1048 | ], |
||
1049 | ], |
||
1050 | ]); |
||
1051 | } |
||
1052 | |||
1053 | return new FieldSort('_script', $sorting->getDirection(), null, [ |
||
1054 | 'type' => 'string', |
||
1055 | 'script' => [ |
||
1056 | 'id' => 'translated_field_sorting', |
||
1057 | 'params' => [ |
||
1058 | 'field' => implode('.', $parts), |
||
1059 | 'languages' => $context->getLanguageIdChain(), |
||
1060 | ], |
||
1061 | ], |
||
1062 | ]); |
||
1063 | } |
||
1064 | |||
1065 | private function getTranslatedFieldName(string $accessor, string $languageId): string |
||
1074 | } |
||
1075 | } |
||
1076 |
This class constant has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.