diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..a3351c1 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,21 @@ +parameters: + ignoreErrors: + - + message: "#^Class Ibexa\\\\ProductCatalog\\\\Local\\\\Persistence\\\\Legacy\\\\Common\\\\CriterionMapper\\\\AbstractCompositeCriterionMapper not found\\.$#" + count: 1 + path: src/contracts/Persistence/CriterionMapper/AbstractCompositeCriterionMapper.php + + - + message: "#^Class Ibexa\\\\ProductCatalog\\\\Local\\\\Persistence\\\\Legacy\\\\Common\\\\CriterionMapper\\\\AbstractFieldCriterionMapper not found\\.$#" + count: 1 + path: src/contracts/Persistence/CriterionMapper/AbstractFieldCriterionMapper.php + + - + message: "#^Class Ibexa\\\\Contracts\\\\ProductCatalog\\\\Values\\\\Common\\\\Query\\\\Criterion\\\\CriterionInterface not found\\.$#" + count: 1 + path: src/contracts/Values/Query/Criterion/CriterionInterface.php + + - + message: "#^Class Ibexa\\\\Contracts\\\\ProductCatalog\\\\Values\\\\Common\\\\Query\\\\Criterion\\\\FieldValueCriterion not found\\.$#" + count: 1 + path: src/contracts/Values/Query/Criterion/FieldValueCriterion.php diff --git a/phpstan.neon b/phpstan.neon index 3756f56..e85bd74 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,7 @@ includes: - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-symfony/extension.neon + - phpstan-baseline.neon parameters: level: 8 diff --git a/src/contracts/Persistence/CriterionMapper/AbstractCompositeCriterionMapper.php b/src/contracts/Persistence/CriterionMapper/AbstractCompositeCriterionMapper.php new file mode 100644 index 0000000..3c768b1 --- /dev/null +++ b/src/contracts/Persistence/CriterionMapper/AbstractCompositeCriterionMapper.php @@ -0,0 +1,63 @@ + + */ +abstract class AbstractCompositeCriterionMapper implements CriterionMapperInterface +{ + /** + * @return class-string<\Ibexa\Contracts\CoreSearch\Values\Query\Criterion\AbstractCompositeCriterion> + */ + abstract protected function getHandledClass(): string; + + /** + * @phpstan-return \Doctrine\Common\Collections\Expr\CompositeExpression::TYPE_* + */ + abstract protected function getType(): string; + + final public function canHandle(CriterionInterface $criterion): bool + { + $handledClass = $this->getHandledClass(); + + return $criterion instanceof $handledClass; + } + + final public function handle(CriterionInterface $criterion, CriterionMapper $mapper): CompositeExpression + { + $expressions = $this->getExpressions($criterion->getCriteria(), $mapper); + + return new CompositeExpression($this->getType(), $expressions); + } + + /** + * @param iterable<\Ibexa\Contracts\CoreSearch\Values\Query\Criterion\CriterionInterface> $criteria + * + * @return array<\Doctrine\Common\Collections\Expr\Expression> + */ + private function getExpressions(iterable $criteria, CriterionMapper $mapper): array + { + $expressions = []; + foreach ($criteria as $criterion) { + $expressions[] = $mapper->handle($criterion); + } + + return $expressions; + } +} + +class_alias(AbstractCompositeCriterionMapper::class, \Ibexa\ProductCatalog\Local\Persistence\Legacy\Common\CriterionMapper\AbstractCompositeCriterionMapper::class); diff --git a/src/contracts/Persistence/CriterionMapper/AbstractFieldCriterionMapper.php b/src/contracts/Persistence/CriterionMapper/AbstractFieldCriterionMapper.php new file mode 100644 index 0000000..0401209 --- /dev/null +++ b/src/contracts/Persistence/CriterionMapper/AbstractFieldCriterionMapper.php @@ -0,0 +1,91 @@ + + */ +abstract class AbstractFieldCriterionMapper implements CriterionMapperInterface +{ + /** + * @var array< + * \Ibexa\Contracts\CoreSearch\Values\Query\Criterion\FieldValueCriterion::COMPARISON_*, + * \Doctrine\Common\Collections\Expr\Comparison::* + * > + */ + private static array $comparisonMap = [ + FieldValueCriterion::COMPARISON_EQ => Comparison::EQ, + FieldValueCriterion::COMPARISON_NEQ => Comparison::NEQ, + FieldValueCriterion::COMPARISON_LT => Comparison::LT, + FieldValueCriterion::COMPARISON_LTE => Comparison::LTE, + FieldValueCriterion::COMPARISON_GT => Comparison::GT, + FieldValueCriterion::COMPARISON_GTE => Comparison::GTE, + FieldValueCriterion::COMPARISON_IN => Comparison::IN, + FieldValueCriterion::COMPARISON_NIN => Comparison::NIN, + FieldValueCriterion::COMPARISON_CONTAINS => Comparison::CONTAINS, + FieldValueCriterion::COMPARISON_MEMBER_OF => Comparison::MEMBER_OF, + FieldValueCriterion::COMPARISON_STARTS_WITH => Comparison::STARTS_WITH, + FieldValueCriterion::COMPARISON_ENDS_WITH => Comparison::ENDS_WITH, + ]; + + /** + * @phpstan-param T $criterion + */ + final public function handle(CriterionInterface $criterion, CriterionMapper $mapper): Comparison + { + assert($criterion instanceof FieldValueCriterion); + + return new Comparison( + $this->getComparisonField($criterion), + $this->getComparisonOperator($criterion), + $this->getComparisonValue($criterion) + ); + } + + protected function getComparisonField(FieldValueCriterion $criterion): string + { + return $criterion->getField(); + } + + /** + * @phpstan-return \Doctrine\Common\Collections\Expr\Comparison::* + */ + protected function getComparisonOperator(FieldValueCriterion $criterion): string + { + $operator = $criterion->getOperator(); + if (isset(self::$comparisonMap[$operator])) { + return self::$comparisonMap[$operator]; + } + + throw new LogicException(sprintf( + 'Unable to map %s operator %s to a valid DBAL operator', + get_class($criterion), + $operator, + )); + } + + /** + * @return mixed + */ + protected function getComparisonValue(FieldValueCriterion $criterion) + { + return $criterion->getValue(); + } +} + +class_alias(AbstractFieldCriterionMapper::class, \Ibexa\ProductCatalog\Local\Persistence\Legacy\Common\CriterionMapper\AbstractFieldCriterionMapper::class); diff --git a/src/contracts/Values/Query/Criterion/CriterionInterface.php b/src/contracts/Values/Query/Criterion/CriterionInterface.php index 9b57485..19771b7 100644 --- a/src/contracts/Values/Query/Criterion/CriterionInterface.php +++ b/src/contracts/Values/Query/Criterion/CriterionInterface.php @@ -11,3 +11,5 @@ interface CriterionInterface { } + +class_alias(CriterionInterface::class, \Ibexa\Contracts\ProductCatalog\Values\Common\Query\Criterion\CriterionInterface::class); diff --git a/src/contracts/Values/Query/Criterion/FieldValueCriterion.php b/src/contracts/Values/Query/Criterion/FieldValueCriterion.php index 812396c..2d27c7f 100644 --- a/src/contracts/Values/Query/Criterion/FieldValueCriterion.php +++ b/src/contracts/Values/Query/Criterion/FieldValueCriterion.php @@ -83,3 +83,5 @@ public function getOperator(): string return $this->operator; } } + +class_alias(FieldValueCriterion::class, \Ibexa\Contracts\ProductCatalog\Values\Common\Query\Criterion\FieldValueCriterion::class); diff --git a/src/contracts/Values/Query/CriterionMapper.php b/src/contracts/Values/Query/CriterionMapper.php index 46cadba..90b22dd 100644 --- a/src/contracts/Values/Query/CriterionMapper.php +++ b/src/contracts/Values/Query/CriterionMapper.php @@ -14,8 +14,10 @@ /** * Converts Criterion instances into objects that underlying Handler can understand. + * + * @final */ -final class CriterionMapper +class CriterionMapper { /** * @var iterable<\Ibexa\Contracts\CoreSearch\Values\Query\CriterionMapperInterface< diff --git a/src/contracts/Values/Query/SortDirection.php b/src/contracts/Values/Query/SortDirection.php index 90f9c6c..20eb5bc 100644 --- a/src/contracts/Values/Query/SortDirection.php +++ b/src/contracts/Values/Query/SortDirection.php @@ -8,7 +8,10 @@ namespace Ibexa\Contracts\CoreSearch\Values\Query; -final class SortDirection +/** + * @final + */ +class SortDirection { public const ASC = 'ascending'; public const DESC = 'descending';