vendor/shopware/core/Framework/DataAbstractionLayer/Search/Criteria.php line 21

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