Criteria::getApiAlias()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\Framework\DataAbstractionLayer\Search;
4
5
use Shopware\Core\Framework\DataAbstractionLayer\DataAbstractionLayerException;
6
use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
7
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Aggregation;
8
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
9
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\Filter;
10
use Shopware\Core\Framework\DataAbstractionLayer\Search\Grouping\FieldGrouping;
11
use Shopware\Core\Framework\DataAbstractionLayer\Search\Parser\AggregationParser;
12
use Shopware\Core\Framework\DataAbstractionLayer\Search\Query\ScoreQuery;
13
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
14
use Shopware\Core\Framework\Log\Package;
15
use Shopware\Core\Framework\Struct\StateAwareTrait;
16
use Shopware\Core\Framework\Struct\Struct;
17
use Shopware\Core\Framework\Util\Json;
18
19
/**
20
 * @final
21
 */
22
#[Package('core')]
23
class Criteria extends Struct implements \Stringable
24
{
25
    use StateAwareTrait;
26
    final public const STATE_ELASTICSEARCH_AWARE = 'elasticsearchAware';
27
28
    /**
29
     * no total count will be selected. Should be used if no pagination required (fastest)
30
     */
31
    final public const TOTAL_COUNT_MODE_NONE = 0;
32
33
    /**
34
     * exact total count will be selected. Should be used if an exact pagination is required (slow)
35
     */
36
    final public const TOTAL_COUNT_MODE_EXACT = 1;
37
38
    /**
39
     * fetches limit * 5 + 1. Should be used if pagination can work with "next page exists" (fast)
40
     */
41
    final public const TOTAL_COUNT_MODE_NEXT_PAGES = 2;
42
43
    /**
44
     * @var FieldSorting[]
45
     */
46
    protected $sorting = [];
47
48
    /**
49
     * @var Filter[]
50
     */
51
    protected $filters = [];
52
53
    /**
54
     * @var Filter[]
55
     */
56
    protected $postFilters = [];
57
58
    /**
59
     * @var Aggregation[]
60
     */
61
    protected $aggregations = [];
62
63
    /**
64
     * @var ScoreQuery[]
65
     */
66
    protected $queries = [];
67
68
    /**
69
     * @var FieldGrouping[]
70
     */
71
    protected $groupFields = [];
72
73
    /**
74
     * @var int|null
75
     */
76
    protected $offset;
77
78
    /**
79
     * @var int|null
80
     */
81
    protected $limit;
82
83
    /**
84
     * @var int
85
     */
86
    protected $totalCountMode = self::TOTAL_COUNT_MODE_NONE;
87
88
    /**
89
     * @var Criteria[]
90
     */
91
    protected $associations = [];
92
93
    /**
94
     * @var array<string>|array<int, array<string>>
95
     */
96
    protected $ids = [];
97
98
    /**
99
     * @var bool
100
     */
101
    protected $inherited = false;
102
103
    /**
104
     * @var string|null
105
     */
106
    protected $term;
107
108
    /**
109
     * @var array<string, array<string, string>>|null
110
     */
111
    protected $includes;
112
113
    /**
114
     * @var string|null
115
     */
116
    protected $title;
117
118
    /**
119
     * @var string[]
120
     */
121
    protected array $fields = [];
122
123
    /**
124
     * @param array<string>|array<array<string, string>>|null $ids
125
     */
126
    public function __construct(?array $ids = null)
127
    {
128
        if ($ids === null) {
129
            return;
130
        }
131
132
        $ids = array_filter($ids);
133
        if (empty($ids)) {
134
            throw DataAbstractionLayerException::invalidCriteriaIds($ids, 'Ids should not be empty');
135
        }
136
        $this->validateIds($ids);
137
138
        $this->ids = $ids;
139
    }
140
141
    public function __toString(): string
142
    {
143
        $parsed = (new CriteriaArrayConverter(new AggregationParser()))->convert($this);
144
145
        return Json::encode($parsed);
146
    }
147
148
    /**
149
     * @return array<string>|array<array<string, string>>
150
     */
151
    public function getIds(): array
152
    {
153
        return $this->ids;
154
    }
155
156
    public function getOffset(): ?int
157
    {
158
        return $this->offset;
159
    }
160
161
    public function getLimit(): ?int
162
    {
163
        return $this->limit;
164
    }
165
166
    public function getTotalCountMode(): int
167
    {
168
        return $this->totalCountMode;
169
    }
170
171
    /**
172
     * @return FieldSorting[]
173
     */
174
    public function getSorting(): array
175
    {
176
        return $this->sorting;
177
    }
178
179
    /**
180
     * @return Aggregation[]
181
     */
182
    public function getAggregations(): array
183
    {
184
        return $this->aggregations;
185
    }
186
187
    public function getAggregation(string $name): ?Aggregation
188
    {
189
        return $this->aggregations[$name] ?? null;
190
    }
191
192
    /**
193
     * @return Filter[]
194
     */
195
    public function getFilters(): array
196
    {
197
        return $this->filters;
198
    }
199
200
    /**
201
     * @param string $field
202
     */
203
    public function hasEqualsFilter($field): bool
204
    {
205
        return \count(array_filter($this->filters, static fn (Filter $filter) /* EqualsFilter $filter */ => $filter instanceof EqualsFilter && $filter->getField() === $field)) > 0;
206
    }
207
208
    /**
209
     * @return Filter[]
210
     */
211
    public function getPostFilters(): array
212
    {
213
        return $this->postFilters;
214
    }
215
216
    /**
217
     * @return ScoreQuery[]
218
     */
219
    public function getQueries(): array
220
    {
221
        return $this->queries;
222
    }
223
224
    /**
225
     * @return Criteria[]
226
     */
227
    public function getAssociations(): array
228
    {
229
        return $this->associations;
230
    }
231
232
    /**
233
     * Returns the criteria for the provided association path. Also supports nested paths
234
     *
235
     * e.g `$criteria->getAssociation('categories.media.thumbnails')`
236
     *
237
     * @throws InconsistentCriteriaIdsException
238
     */
239
    public function getAssociation(string $path): Criteria
240
    {
241
        $parts = explode('.', $path);
242
243
        $criteria = $this;
244
        foreach ($parts as $part) {
245
            if ($part === 'extensions') {
246
                continue;
247
            }
248
249
            if (!$criteria->hasAssociation($part)) {
250
                $criteria->associations[$part] = new Criteria();
251
            }
252
253
            $criteria = $criteria->associations[$part];
254
        }
255
256
        return $criteria;
257
    }
258
259
    public function addFilter(Filter ...$queries): self
260
    {
261
        foreach ($queries as $query) {
262
            $this->filters[] = $query;
263
        }
264
265
        return $this;
266
    }
267
268
    public function setFilter(string $key, Filter $filter): self
269
    {
270
        $this->filters[$key] = $filter;
271
272
        return $this;
273
    }
274
275
    public function addSorting(FieldSorting ...$sorting): self
276
    {
277
        foreach ($sorting as $sort) {
278
            $this->sorting[] = $sort;
279
        }
280
281
        return $this;
282
    }
283
284
    public function addAggregation(Aggregation ...$aggregations): self
285
    {
286
        foreach ($aggregations as $aggregation) {
287
            $this->aggregations[$aggregation->getName()] = $aggregation;
288
        }
289
290
        return $this;
291
    }
292
293
    public function addPostFilter(Filter ...$queries): self
294
    {
295
        foreach ($queries as $query) {
296
            $this->postFilters[] = $query;
297
        }
298
299
        return $this;
300
    }
301
302
    public function addQuery(ScoreQuery ...$queries): self
303
    {
304
        foreach ($queries as $query) {
305
            $this->queries[] = $query;
306
        }
307
308
        return $this;
309
    }
310
311
    /**
312
     * Add for each part of the provided path an association
313
     *
314
     * e.g
315
     *
316
     * $criteria->addAssociation('categories.media.thumbnails')
317
     *
318
     * @throws InconsistentCriteriaIdsException
319
     */
320
    public function addAssociation(string $path): self
321
    {
322
        $parts = explode('.', $path);
323
324
        $criteria = $this;
325
        foreach ($parts as $part) {
326
            if (mb_strtolower($part) === 'extensions') {
327
                continue;
328
            }
329
330
            $criteria = $criteria->getAssociation($part);
331
        }
332
333
        return $this;
334
    }
335
336
    /**
337
     * @param string[] $paths
338
     *
339
     * Allows to add multiple associations paths
340
     *
341
     * e.g.:
342
     *
343
     * $criteria->addAssociations([
344
     *      'prices',
345
     *      'cover.media',
346
     *      'categories.cover.media'
347
     * ]);
348
     *
349
     * @throws InconsistentCriteriaIdsException
350
     */
351
    public function addAssociations(array $paths): self
352
    {
353
        foreach ($paths as $path) {
354
            $this->addAssociation($path);
355
        }
356
357
        return $this;
358
    }
359
360
    public function hasAssociation(string $field): bool
361
    {
362
        return isset($this->associations[$field]);
363
    }
364
365
    public function resetSorting(): self
366
    {
367
        $this->sorting = [];
368
369
        return $this;
370
    }
371
372
    public function resetAssociations(): self
373
    {
374
        $this->associations = [];
375
376
        return $this;
377
    }
378
379
    public function resetQueries(): self
380
    {
381
        $this->queries = [];
382
383
        return $this;
384
    }
385
386
    public function resetFilters(): self
387
    {
388
        $this->filters = [];
389
390
        return $this;
391
    }
392
393
    public function resetPostFilters(): self
394
    {
395
        $this->postFilters = [];
396
397
        return $this;
398
    }
399
400
    public function resetAggregations(): self
401
    {
402
        $this->aggregations = [];
403
404
        return $this;
405
    }
406
407
    public function setTotalCountMode(int $totalCountMode): self
408
    {
409
        $this->totalCountMode = $totalCountMode;
410
411
        return $this;
412
    }
413
414
    public function setLimit(?int $limit): self
415
    {
416
        $this->limit = $limit;
417
418
        return $this;
419
    }
420
421
    public function setOffset(?int $offset): self
422
    {
423
        $this->offset = $offset;
424
425
        return $this;
426
    }
427
428
    /**
429
     * @return array<string>
430
     */
431
    public function getAggregationQueryFields(): array
432
    {
433
        return $this->collectFields([
434
            $this->filters,
435
            $this->queries,
436
        ]);
437
    }
438
439
    /**
440
     * @return array<string>
441
     */
442
    public function getSearchQueryFields(): array
443
    {
444
        return $this->collectFields([
445
            $this->filters,
446
            $this->postFilters,
447
            $this->sorting,
448
            $this->queries,
449
            $this->groupFields,
450
        ]);
451
    }
452
453
    /**
454
     * @return array<string>
455
     */
456
    public function getFilterFields(): array
457
    {
458
        return $this->collectFields([
459
            $this->filters,
460
            $this->postFilters,
461
        ]);
462
    }
463
464
    /**
465
     * @return array<string>
466
     */
467
    public function getAllFields(): array
468
    {
469
        return $this->collectFields([
470
            $this->filters,
471
            $this->postFilters,
472
            $this->sorting,
473
            $this->queries,
474
            $this->groupFields,
475
            $this->aggregations,
476
        ]);
477
    }
478
479
    /**
480
     * @param array<string>|array<array<string, string>> $ids
481
     */
482
    public function setIds(array $ids): self
483
    {
484
        $this->validateIds($ids);
485
        $this->ids = $ids;
486
487
        return $this;
488
    }
489
490
    public function getTerm(): ?string
491
    {
492
        return $this->term;
493
    }
494
495
    public function setTerm(?string $term): self
496
    {
497
        $this->term = $term;
498
499
        return $this;
500
    }
501
502
    /**
503
     * @param array<string>|array<int, array<string>> $ids
504
     */
505
    public function cloneForRead(array $ids = []): Criteria
506
    {
507
        $self = new self($ids);
508
        $self->setTitle($this->getTitle());
509
510
        $associations = [];
511
512
        foreach ($this->associations as $name => $association) {
513
            $associations[$name] = clone $association;
514
        }
515
516
        $self->associations = $associations;
517
        $self->fields = $this->fields;
518
519
        return $self;
520
    }
521
522
    public function addGroupField(FieldGrouping $grouping): self
523
    {
524
        $this->groupFields[] = $grouping;
525
526
        return $this;
527
    }
528
529
    /**
530
     * @return FieldGrouping[]
531
     */
532
    public function getGroupFields(): array
533
    {
534
        return $this->groupFields;
535
    }
536
537
    public function resetGroupFields(): self
538
    {
539
        $this->groupFields = [];
540
541
        return $this;
542
    }
543
544
    /**
545
     * @param array<string, array<string, string>>|null $includes
546
     */
547
    public function setIncludes(?array $includes): void
548
    {
549
        $this->includes = $includes;
550
    }
551
552
    /**
553
     * @return array<string, array<string, string>>|null
554
     */
555
    public function getIncludes()
556
    {
557
        return $this->includes;
558
    }
559
560
    public function getApiAlias(): string
561
    {
562
        return 'dal_criteria';
563
    }
564
565
    public function useIdSorting(): bool
566
    {
567
        if (empty($this->getIds())) {
568
            return false;
569
        }
570
571
        // manual sorting provided
572
        if (!empty($this->getSorting())) {
573
            return false;
574
        }
575
576
        // result will be sorted by interpreted search term and the calculated ranking
577
        if (!empty($this->getTerm())) {
578
            return false;
579
        }
580
581
        // result will be sorted by calculated ranking
582
        if (!empty($this->getQueries())) {
583
            return false;
584
        }
585
586
        return true;
587
    }
588
589
    public function removeAssociation(string $association): void
590
    {
591
        unset($this->associations[$association]);
592
    }
593
594
    public function getTitle(): ?string
595
    {
596
        return $this->title;
597
    }
598
599
    public function setTitle(?string $title): void
600
    {
601
        $this->title = $title;
602
    }
603
604
    /**
605
     * @param string[] $fields
606
     *
607
     * @internal
608
     */
609
    public function addFields(array $fields): self
610
    {
611
        $this->fields = array_merge($this->fields, $fields);
612
613
        return $this;
614
    }
615
616
    /**
617
     * @return string[]
618
     *
619
     * @internal
620
     */
621
    public function getFields(): array
622
    {
623
        return $this->fields;
624
    }
625
626
    /**
627
     * @param array<array<CriteriaPartInterface>> $parts
628
     *
629
     * @return array<string>
630
     */
631
    private function collectFields(array $parts): array
632
    {
633
        $fields = [];
634
635
        foreach ($parts as $part) {
636
            /** @var CriteriaPartInterface $item */
637
            foreach ($part as $item) {
638
                foreach ($item->getFields() as $field) {
639
                    $fields[] = $field;
640
                }
641
            }
642
        }
643
644
        return $fields;
645
    }
646
647
    /**
648
     * @param array<mixed> $ids
649
     */
650
    private function validateIds(array $ids): void
651
    {
652
        foreach ($ids as $id) {
653
            if (!\is_string($id) && !\is_array($id)) {
654
                throw DataAbstractionLayerException::invalidCriteriaIds($ids, 'Ids should be a list of strings or a list of key value pairs, for entities with combined primary keys');
655
            }
656
657
            if (!\is_array($id)) {
658
                continue;
659
            }
660
661
            foreach ($id as $key => $value) {
662
                if (!\is_string($key) || !\is_string($value)) {
663
                    throw DataAbstractionLayerException::invalidCriteriaIds($ids, 'Ids should be a list of strings or a list of key value pairs, for entities with combined primary keys');
664
                }
665
            }
666
        }
667
    }
668
}
669