diff --git a/src/bundle/Resources/config/services/query.yaml b/src/bundle/Resources/config/services/query.yaml new file mode 100644 index 0000000..f4f5313 --- /dev/null +++ b/src/bundle/Resources/config/services/query.yaml @@ -0,0 +1,9 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + Ibexa\Contracts\CoreSearch\Values\Query\CriterionMapper: + arguments: + $mappers: !tagged_iterator ibexa.core_search.criterion_mapper diff --git a/src/contracts/Values/Query/AbstractCriterionQuery.php b/src/contracts/Values/Query/AbstractCriterionQuery.php new file mode 100644 index 0000000..34c24dc --- /dev/null +++ b/src/contracts/Values/Query/AbstractCriterionQuery.php @@ -0,0 +1,111 @@ +query = $query; + $this->sortClauses = $sortClauses ?? []; + $this->offset = $offset; + $this->limit = $limit; + } + + /** + * @param TCriterion|null $criterion + */ + final public function setQuery(?CriterionInterface $criterion): void + { + $this->query = $criterion; + } + + /** + * @return TCriterion|null + */ + final public function getQuery(): ?CriterionInterface + { + return $this->query; + } + + final public function hasQuery(): bool + { + return $this->query !== null; + } + + final public function getOffset(): int + { + return $this->offset; + } + + final public function setOffset(int $offset): void + { + $this->offset = $offset; + } + + final public function getLimit(): ?int + { + return $this->limit; + } + + final public function setLimit(?int $limit): void + { + $this->limit = $limit; + } + + /** + * @return TSortClause[] + */ + final public function getSortClauses(): array + { + return $this->sortClauses; + } + + /** + * @phpstan-param TSortClause $sortClause + */ + final public function addSortClause(AbstractSortClause $sortClause): void + { + $this->sortClauses[] = $sortClause; + } + + /** + * @phpstan-param TSortClause[] $sortClauses + */ + final public function setSortClauses(array $sortClauses): void + { + $this->sortClauses = $sortClauses; + } +} diff --git a/src/contracts/Values/Query/AbstractSortClause.php b/src/contracts/Values/Query/AbstractSortClause.php new file mode 100644 index 0000000..2728e93 --- /dev/null +++ b/src/contracts/Values/Query/AbstractSortClause.php @@ -0,0 +1,59 @@ +setDirection($sortDirection); + } + + final public function getDirection(): string + { + return $this->direction; + } + + /** + * @throws \InvalidArgumentException if the given sort direction is invalid + */ + final public function setDirection(string $direction): void + { + if (!SortDirection::isValid($direction)) { + throw new InvalidArgumentException(sprintf( + 'Sort direction must be one of %1$s::ASC or %1$s::DESC', + SortDirection::class, + )); + } + + /** @var SortDirection::* $direction */ + $this->direction = $direction; + } +} diff --git a/src/contracts/Values/Query/Criterion/AbstractCompositeCriterion.php b/src/contracts/Values/Query/Criterion/AbstractCompositeCriterion.php new file mode 100644 index 0000000..bd20578 --- /dev/null +++ b/src/contracts/Values/Query/Criterion/AbstractCompositeCriterion.php @@ -0,0 +1,51 @@ + */ + private array $criteria; + + public function __construct(CriterionInterface ...$criteria) + { + $this->criteria = $criteria; + } + + public function add(CriterionInterface ...$criteria): void + { + $this->setCriteria( + ...$this->criteria, + ...$criteria, + ); + } + + public function remove(CriterionInterface ...$criteria): void + { + $this->setCriteria(...array_filter( + $this->criteria, + static function (CriterionInterface $criterion) use ($criteria): bool { + return !in_array($criterion, $criteria, true); + }, + )); + } + + public function setCriteria(CriterionInterface ...$criteria): void + { + $this->criteria = $criteria; + } + + /** + * @return array<\Ibexa\Contracts\CoreSearch\Values\Query\Criterion\CriterionInterface> + */ + final public function getCriteria(): array + { + return $this->criteria; + } +} diff --git a/src/contracts/Values/Query/Criterion/CriterionInterface.php b/src/contracts/Values/Query/Criterion/CriterionInterface.php new file mode 100644 index 0000000..9b57485 --- /dev/null +++ b/src/contracts/Values/Query/Criterion/CriterionInterface.php @@ -0,0 +1,13 @@ +'; + /** @final */ + public const COMPARISON_LT = '<'; + /** @final */ + public const COMPARISON_LTE = '<='; + /** @final */ + public const COMPARISON_GT = '>'; + /** @final */ + public const COMPARISON_GTE = '>='; + /** @final */ + public const COMPARISON_IN = 'IN'; + /** @final */ + public const COMPARISON_NIN = 'NIN'; + /** @final */ + public const COMPARISON_CONTAINS = 'CONTAINS'; + /** @final */ + public const COMPARISON_MEMBER_OF = 'MEMBER_OF'; + /** @final */ + public const COMPARISON_STARTS_WITH = 'STARTS_WITH'; + /** @final */ + public const COMPARISON_ENDS_WITH = 'ENDS_WITH'; + + private string $field; + + /** @var mixed */ + private $value; + + private string $operator; + + /** + * @param mixed $value + */ + public function __construct(string $field, $value, ?string $operator = null) + { + $this->field = $field; + $this->value = $value; + $this->operator = $operator ?? (is_array($value) ? self::COMPARISON_IN : self::COMPARISON_EQ); + } + + public function getField(): string + { + return $this->field; + } + + /** + * @param mixed $value + */ + public function setValue($value): void + { + $this->value = $value; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + public function setOperator(string $operator): void + { + $this->operator = $operator; + } + + public function getOperator(): string + { + return $this->operator; + } +} diff --git a/src/contracts/Values/Query/Criterion/LogicalAnd.php b/src/contracts/Values/Query/Criterion/LogicalAnd.php new file mode 100644 index 0000000..3dd6644 --- /dev/null +++ b/src/contracts/Values/Query/Criterion/LogicalAnd.php @@ -0,0 +1,13 @@ +> + */ + private iterable $mappers; + + /** + * @phpstan-param iterable<\Ibexa\Contracts\CoreSearch\Values\Query\CriterionMapperInterface< + * \Ibexa\Contracts\CoreSearch\Values\Query\Criterion\CriterionInterface, + * >> $mappers + */ + public function __construct(iterable $mappers) + { + $this->mappers = $mappers; + } + + public function handle(CriterionInterface $criterion): Expression + { + foreach ($this->mappers as $mapper) { + if ($mapper->canHandle($criterion)) { + return $mapper->handle($criterion, $this); + } + } + + throw new LogicException(sprintf( + 'Unable to handle "%s" criterion.', + get_class($criterion), + )); + } +} diff --git a/src/contracts/Values/Query/CriterionMapperInterface.php b/src/contracts/Values/Query/CriterionMapperInterface.php new file mode 100644 index 0000000..e828956 --- /dev/null +++ b/src/contracts/Values/Query/CriterionMapperInterface.php @@ -0,0 +1,25 @@ +field = $field; + } + + public function getField(): string + { + return $this->field; + } +} diff --git a/src/contracts/Values/Query/SortDirection.php b/src/contracts/Values/Query/SortDirection.php new file mode 100644 index 0000000..90f9c6c --- /dev/null +++ b/src/contracts/Values/Query/SortDirection.php @@ -0,0 +1,23 @@ +