From 098e1cc5265f2f9c9760a6042bfebfbeb9cf9e6a Mon Sep 17 00:00:00 2001 From: Nicholas Ruunu Date: Wed, 19 Jun 2024 22:47:53 +0200 Subject: [PATCH 01/10] Update to PHP 8.3, Doctrine ORM 3 and Collections 2 --- .coveralls.yml | 1 - .gitignore | 2 +- .php-cs-fixer.php | 17 ++++ .php_cs | 26 ------ README.md | 24 ++--- composer.json | 24 ++--- docker-compose.yaml | 6 ++ phpspec.yml.dist | 7 +- rector.php | 24 +++++ spec/Condition/BetweenSpec.php | 30 +++--- spec/Condition/ComparisonSpec.php | 26 +++--- spec/Condition/EqualsPropertySpec.php | 16 ++-- spec/Condition/EqualsSpec.php | 20 ++-- spec/Condition/GreaterThanOrEqualsSpec.php | 20 ++-- spec/Condition/GreaterThanSpec.php | 20 ++-- spec/Condition/InSpec.php | 32 +++---- spec/Condition/IsInstanceOfSpec.php | 31 ++++--- spec/Condition/IsMemberOfSpec.php | 30 +++--- spec/Condition/IsNotNullSpec.php | 24 +++-- spec/Condition/IsNullSpec.php | 23 ++--- spec/Condition/LessThanOrEqualsSpec.php | 20 ++-- spec/Condition/LessThanSpec.php | 20 ++-- spec/Condition/LikeSpec.php | 32 +++---- spec/Condition/NotEqualsSpec.php | 20 ++-- spec/Condition/NotInSpec.php | 40 ++++---- spec/Logic/AndXSpec.php | 8 +- spec/Logic/CompositeSpec.php | 47 +++++----- spec/Logic/NotSpec.php | 25 ++--- spec/Logic/OrXSpec.php | 8 +- spec/Query/GroupBySpec.php | 23 ++--- spec/Query/HavingSpec.php | 34 +++---- spec/Query/IndexBySpec.php | 13 +-- spec/Query/InnerJoinSpec.php | 16 ++-- spec/Query/JoinSpec.php | 40 ++++---- spec/Query/LeftJoinSpec.php | 16 ++-- spec/Query/OrderBySpec.php | 22 +++-- spec/Query/SelectSpec.php | 24 ++--- spec/Result/AsArraySpec.php | 10 +- spec/Result/ModifierCollectionSpec.php | 12 +-- spec/SpecificationRepositorySpec.php | 57 +++++------- spec/SpecificationSpec.php | 35 +++---- src/AbstractSpecification.php | 43 ++------- src/Condition/Between.php | 45 ++------- src/Condition/Comparison.php | 71 +++++--------- src/Condition/Equals.php | 10 +- src/Condition/EqualsProperty.php | 18 ++-- src/Condition/GreaterThan.php | 10 +- src/Condition/GreaterThanOrEquals.php | 10 +- src/Condition/In.php | 33 ++----- src/Condition/IsInstanceOf.php | 26 ++---- src/Condition/IsMemberOf.php | 26 ++---- src/Condition/IsNotNull.php | 12 +-- src/Condition/IsNull.php | 14 ++- src/Condition/LessThan.php | 10 +- src/Condition/LessThanOrEquals.php | 10 +- src/Condition/Like.php | 27 ++---- src/Condition/NotEquals.php | 10 +- src/Condition/NotIn.php | 15 ++- src/Exception/InvalidArgumentException.php | 2 +- src/Exception/LogicException.php | 2 +- src/Logic/AndX.php | 11 +-- src/Logic/Composite.php | 20 ++-- src/Logic/Not.php | 34 ++----- src/Logic/OrX.php | 14 +-- src/Query/GroupBy.php | 54 ++++------- src/Query/Having.php | 55 +++++------ src/Query/IndexBy.php | 18 ++-- src/Query/InnerJoin.php | 13 +-- src/Query/Join.php | 102 ++++++--------------- src/Query/LeftJoin.php | 13 +-- src/Query/OrderBy.php | 41 ++++----- src/Query/Select.php | 57 ++++-------- src/Result/AsArray.php | 10 +- src/Result/ModifierCollection.php | 32 +++---- src/Result/ModifierInterface.php | 6 +- src/Specification.php | 73 +++++---------- src/SpecificationAwareInterface.php | 13 +-- src/SpecificationInterface.php | 12 +-- src/SpecificationRepository.php | 2 +- src/SpecificationRepositoryTrait.php | 46 +++------- 80 files changed, 808 insertions(+), 1137 deletions(-) delete mode 100644 .coveralls.yml create mode 100644 .php-cs-fixer.php delete mode 100644 .php_cs create mode 100644 docker-compose.yaml create mode 100644 rector.php diff --git a/.coveralls.yml b/.coveralls.yml deleted file mode 100644 index 9160059..0000000 --- a/.coveralls.yml +++ /dev/null @@ -1 +0,0 @@ -service_name: travis-ci diff --git a/.gitignore b/.gitignore index a6ac1df..5a2f8fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ build/ vendor/ -.php_cs.cache +.php-cs-fixer.cache composer.lock diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..2070fa1 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,17 @@ +exclude(['vendor', 'build']) + ->in(__DIR__); + +return (new PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@Symfony' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'ordered_class_elements' => true, + 'strict_comparison' => true, + 'strict_param' => true, + ]) + ->setFinder($finder); diff --git a/.php_cs b/.php_cs deleted file mode 100644 index 475f713..0000000 --- a/.php_cs +++ /dev/null @@ -1,26 +0,0 @@ -exclude(['vendor', 'build']) - ->in(__DIR__); - -return PhpCsFixer\Config::create() - ->setRiskyAllowed(true) - ->setRules([ - '@Symfony' => true, - 'binary_operator_spaces' => [ - 'align_double_arrow' => true, - 'align_equals' => true, - ], - 'concat_space' => ['spacing' => 'one'], - 'not_operator_with_successor_space' => true, - 'no_multiline_whitespace_before_semicolons' => true, - 'no_useless_else' => true, - 'no_useless_return' => true, - 'ordered_class_elements' => true, - 'ordered_imports' => true, - 'phpdoc_order' => true, - 'strict_comparison' => true, - 'strict_param' => true, - ]) - ->setFinder($finder); diff --git a/README.md b/README.md index c151fa6..9a6bd2b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Doctrine Specification +# Doctrine Specification [![Build Status](https://travis-ci.org/rikbruil/Doctrine-Specification.svg)](https://travis-ci.org/rikbruil/Doctrine-Specification) [![Coverage Status](https://coveralls.io/repos/rikbruil/Doctrine-Specification/badge.svg?branch=master)](https://coveralls.io/r/rikbruil/Doctrine-Specification?branch=master) [![Latest Stable Version](https://poser.pugx.org/rikbruil/doctrine-specification/v/stable.svg)](https://packagist.org/packages/rikbruil/doctrine-specification) @@ -43,12 +43,12 @@ return $qb->where('r.ended = 0') ``` ```php -use Rb\Specification\Doctrine\Condition\Equals; -use Rb\Specification\Doctrine\Condition\IsNull; -use Rb\Specification\Doctrine\Condition\LessThan; -use Rb\Specification\Doctrine\Logic\AndX; -use Rb\Specification\Doctrine\Logic\OrX; -use Rb\Specification\Doctrine\Specification; +use Purist\Specification\Doctrine\Condition\Equals; +use Purist\Specification\Doctrine\Condition\IsNull; +use Purist\Specification\Doctrine\Condition\LessThan; +use Purist\Specification\Doctrine\Logic\AndX; +use Purist\Specification\Doctrine\Logic\OrX; +use Purist\Specification\Doctrine\Specification; // Using the lib $spec = new Specification([ @@ -89,7 +89,7 @@ class ExpiredAds extends Specification ]; parent::__construct($specs); } - + public function isSatisfiedBy($value) { return $value === Advertisement::class; @@ -107,7 +107,7 @@ class AdsByUser extends Specification new Join('user', 'u'), new Equals('id', $user->getId(), 'u'), ]; - + parent::__construct($specs); } @@ -131,7 +131,7 @@ class SomeService return $this->em->getRepository('Advertisement')->match($spec)->execute(); } - + /** * Fetch adverts paginated by Doctrine Paginator with joins intact. * A paginator can be iterated over like a normal array or Doctrine Collection @@ -142,7 +142,7 @@ class SomeService new ExpiredAds(), new AdsByUser($user), ]); - + $query = $this->em->getRepository('Advertisement')->match($spec); $query->setFirstResult(($page - 1) * $size)) ->setMaxResults($size); @@ -155,7 +155,7 @@ class SomeService Doctrine-Specification requires: -- PHP 5.5+ +- PHP 8.3+ - Doctrine 2.2 ## License diff --git a/composer.json b/composer.json index 719b208..bd07ffa 100644 --- a/composer.json +++ b/composer.json @@ -12,22 +12,21 @@ ], "minimum-stability": "stable", "autoload": { - "psr-4": {"Rb\\Specification\\Doctrine\\": "src/"} + "psr-4": {"Purist\\Specification\\Doctrine\\": "src/"} }, "autoload-dev": { - "psr-4": {"spec\\Rb\\Specification\\Doctrine\\": "spec/"} + "psr-4": {"spec\\Purist\\Specification\\Doctrine\\": "spec/"} }, "require": { - "php": ">=5.5", - "doctrine/orm": "^2.2", - "doctrine/collections": "^1.2", - "rikbruil/specification": "0.9.*" + "php": "^8.3", + "doctrine/orm": "^2.2|^3.0", + "doctrine/collections": "^2.0", + "purist/specification": "dev-master" }, "require-dev": { - "phpspec/phpspec": "^2.0", - "henrikbjorn/phpspec-code-coverage": "^2.0", - "friendsofphp/php-cs-fixer": "^2.0", - "satooshi/php-coveralls": "^1.0" + "phpspec/phpspec": "^7.5", + "rector/rector": "^1.1", + "friendsofphp/php-cs-fixer": "^3.59" }, "extra": { "branch-alias": { @@ -37,5 +36,8 @@ "scripts": { "test": "vendor/bin/phpspec run", "fix-style": "vendor/bin/php-cs-fixer fix" - } + }, + "repositories": [ + { "type": "git", "url": "https://github.com/PuristPHP/specification.git" } + ] } diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..693871b --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,6 @@ +services: + php: + image: php:8.3-cli-alpine + volumes: + - .:/app + working_dir: /app diff --git a/phpspec.yml.dist b/phpspec.yml.dist index 639b21a..8f16157 100644 --- a/phpspec.yml.dist +++ b/phpspec.yml.dist @@ -1,6 +1,3 @@ -extensions: - - PhpSpec\Extension\CodeCoverageExtension - code_coverage: format: - clover @@ -11,5 +8,5 @@ code_coverage: suites: main: - namespace: Rb\Specification\Doctrine - psr4_prefix: Rb\Specification\Doctrine + namespace: Purist\Specification\Doctrine + psr4_prefix: Purist\Specification\Doctrine diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..12037ce --- /dev/null +++ b/rector.php @@ -0,0 +1,24 @@ +withPaths([ + __DIR__.'/src', + __DIR__.'/spec', + ]) + ->withPhpSets(php83: true) + ->withAttributesSets( + symfony: true, + doctrine: true, + ) + // here we can define, what prepared sets of rules will be applied + ->withPreparedSets( + deadCode: true, + codeQuality: true, + typeDeclarations: true, + privatization: true, + instanceOf: true, + earlyReturn: true, + strictBooleans: true, + ); diff --git a/spec/Condition/BetweenSpec.php b/spec/Condition/BetweenSpec.php index 6fa882a..ff4b3f4 100644 --- a/spec/Condition/BetweenSpec.php +++ b/spec/Condition/BetweenSpec.php @@ -1,34 +1,34 @@ beConstructedWith($this->field, $this->from, $this->to); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_returns_an_expression_func_object(QueryBuilder $queryBuilder, ArrayCollection $parameters, Expr $expr) + public function it_returns_an_expression_func_object(QueryBuilder $queryBuilder, ArrayCollection $parameters, Expr $expr): void { - $dqlAlias = 'a'; + $dqlAlias = 'a'; $expression = 'a.foo between(:from_10, :to_10)'; $queryBuilder->expr()->willReturn($expr); @@ -37,16 +37,16 @@ public function it_returns_an_expression_func_object(QueryBuilder $queryBuilder, $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('from_10', $this->from)->shouldBeCalled(); - $queryBuilder->setParameter('to_10', $this->to)->shouldBeCalled(); + $queryBuilder->setParameter('from_10', $this->from)->shouldBeCalled()->willReturn($queryBuilder); + $queryBuilder->setParameter('to_10', $this->to)->shouldBeCalled()->willReturn($queryBuilder); $this->isSatisfiedBy('foo')->shouldReturn(true); $this->modify($queryBuilder, $dqlAlias)->shouldReturn($expression); } - public function it_should_use_dql_alias_if_set(QueryBuilder $queryBuilder, ArrayCollection $parameters, Expr $expr) + public function it_should_use_dql_alias_if_set(QueryBuilder $queryBuilder, ArrayCollection $parameters, Expr $expr): void { - $dqlAlias = 'x'; + $dqlAlias = 'x'; $expression = 'x.foo between(:from_10, :to_10)'; $this->beConstructedWith($this->field, $this->from, $this->to, $dqlAlias); @@ -57,8 +57,8 @@ public function it_should_use_dql_alias_if_set(QueryBuilder $queryBuilder, Array $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('from_10', $this->from)->shouldBeCalled(); - $queryBuilder->setParameter('to_10', $this->to)->shouldBeCalled(); + $queryBuilder->setParameter('from_10', $this->from)->shouldBeCalled()->willReturn($queryBuilder); + $queryBuilder->setParameter('to_10', $this->to)->shouldBeCalled()->willReturn($queryBuilder); $this->isSatisfiedBy('foo')->shouldReturn(true); $this->modify($queryBuilder, $dqlAlias)->shouldReturn($expression); diff --git a/spec/Condition/ComparisonSpec.php b/spec/Condition/ComparisonSpec.php index ed27442..d9b6263 100644 --- a/spec/Condition/ComparisonSpec.php +++ b/spec/Condition/ComparisonSpec.php @@ -1,54 +1,54 @@ beConstructedWith(Comparison::GT, 'age', 18, 'a'); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters) + public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters): void { $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); $this->isSatisfiedBy('foo')->shouldReturn(true); - $this->modify($queryBuilder, null) + $this->modify($queryBuilder, 'x') ->shouldReturn('a.age > :comparison_10'); } public function it_uses_comparison_specific_dql_alias_if_passed( QueryBuilder $queryBuilder, - ArrayCollection $parameters - ) { + ArrayCollection $parameters, + ): void { $this->beConstructedWith(Comparison::GT, 'age', 18, null); $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); $this->isSatisfiedBy('foo')->shouldReturn(true); $this->modify($queryBuilder, 'x')->shouldReturn('x.age > :comparison_10'); } - public function it_validates_operator() + public function it_validates_operator(): void { $this->shouldThrow(InvalidArgumentException::class) ->during('__construct', ['==', 'age', 18, null]); diff --git a/spec/Condition/EqualsPropertySpec.php b/spec/Condition/EqualsPropertySpec.php index 44198c4..0ff435e 100644 --- a/spec/Condition/EqualsPropertySpec.php +++ b/spec/Condition/EqualsPropertySpec.php @@ -1,37 +1,37 @@ beConstructedWith('age', 'address', 'a'); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters) + public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters): void { $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $condition = $this->modify($queryBuilder, null) + $this->modify($queryBuilder, null) ->shouldReturn('a.age = a.address'); } public function it_uses_comparison_specific_dql_alias_if_passed( QueryBuilder $queryBuilder, - ArrayCollection $parameters - ) { + ArrayCollection $parameters, + ): void { $this->beConstructedWith('age', 'address', null); $queryBuilder->getParameters()->willReturn($parameters); diff --git a/spec/Condition/EqualsSpec.php b/spec/Condition/EqualsSpec.php index e229486..bdf0bc6 100644 --- a/spec/Condition/EqualsSpec.php +++ b/spec/Condition/EqualsSpec.php @@ -1,45 +1,45 @@ beConstructedWith('age', 18, 'a'); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters) + public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters): void { $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); - $condition = $this->modify($queryBuilder, null) + $this->modify($queryBuilder, null) ->shouldReturn('a.age = :comparison_10'); } public function it_uses_comparison_specific_dql_alias_if_passed( QueryBuilder $queryBuilder, - ArrayCollection $parameters - ) { + ArrayCollection $parameters, + ): void { $this->beConstructedWith('age', 18, null); $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, 'x')->shouldReturn('x.age = :comparison_10'); } diff --git a/spec/Condition/GreaterThanOrEqualsSpec.php b/spec/Condition/GreaterThanOrEqualsSpec.php index caac0bd..c7ffedd 100644 --- a/spec/Condition/GreaterThanOrEqualsSpec.php +++ b/spec/Condition/GreaterThanOrEqualsSpec.php @@ -1,45 +1,45 @@ beConstructedWith('age', 18, 'a'); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters) + public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters): void { $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); - $condition = $this->modify($queryBuilder, null) + $this->modify($queryBuilder, null) ->shouldReturn('a.age >= :comparison_10'); } public function it_uses_comparison_specific_dql_alias_if_passed( QueryBuilder $queryBuilder, - ArrayCollection $parameters - ) { + ArrayCollection $parameters, + ): void { $this->beConstructedWith('age', 18, null); $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, 'x')->shouldReturn('x.age >= :comparison_10'); } diff --git a/spec/Condition/GreaterThanSpec.php b/spec/Condition/GreaterThanSpec.php index 023d5b4..4971cc5 100644 --- a/spec/Condition/GreaterThanSpec.php +++ b/spec/Condition/GreaterThanSpec.php @@ -1,45 +1,45 @@ beConstructedWith('age', 18, 'a'); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters) + public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters): void { $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); - $condition = $this->modify($queryBuilder, null) + $this->modify($queryBuilder, null) ->shouldReturn('a.age > :comparison_10'); } public function it_uses_comparison_specific_dql_alias_if_passed( QueryBuilder $queryBuilder, - ArrayCollection $parameters - ) { + ArrayCollection $parameters, + ): void { $this->beConstructedWith('age', 18, null); $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, 'x')->shouldReturn('x.age > :comparison_10'); } diff --git a/spec/Condition/InSpec.php b/spec/Condition/InSpec.php index 4deaf9a..6e8bb05 100644 --- a/spec/Condition/InSpec.php +++ b/spec/Condition/InSpec.php @@ -1,33 +1,33 @@ beConstructedWith($this->field, $this->value); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_returns_an_expression_func_object(QueryBuilder $queryBuilder, ArrayCollection $parameters, Expr $expr) + public function it_returns_an_expression_func_object(QueryBuilder $queryBuilder, ArrayCollection $parameters, Expr $expr): void { - $dqlAlias = 'a'; - $expression = 'a.foo in(:in_10)'; + $dqlAlias = 'a'; + $expression = new Expr\Func('a.foo in', ':in_10'); $queryBuilder->expr()->willReturn($expr); $expr->in(sprintf('%s.%s', $dqlAlias, $this->field), ':in_10')->willReturn($expression); @@ -35,15 +35,15 @@ public function it_returns_an_expression_func_object(QueryBuilder $queryBuilder, $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('in_10', $this->value)->shouldBeCalled(); + $queryBuilder->setParameter('in_10', $this->value)->shouldBeCalled()->willReturn($queryBuilder); $this->isSatisfiedBy('foo')->shouldReturn(true); - $this->modify($queryBuilder, $dqlAlias)->shouldReturn($expression); + $this->modify($queryBuilder, $dqlAlias)->shouldReturn((string) $expression); } - public function it_should_use_dql_alias_if_set(QueryBuilder $queryBuilder, ArrayCollection $parameters, Expr $expr) + public function it_should_use_dql_alias_if_set(QueryBuilder $queryBuilder, ArrayCollection $parameters, Expr $expr): void { - $dqlAlias = 'x'; - $expression = 'x.foo in(:in_10)'; + $dqlAlias = 'x'; + $expression = new Expr\Func('x.foo in', ':in_10'); $this->beConstructedWith($this->field, $this->value, $dqlAlias); @@ -53,8 +53,8 @@ public function it_should_use_dql_alias_if_set(QueryBuilder $queryBuilder, Array $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('in_10', $this->value)->shouldBeCalled(); + $queryBuilder->setParameter('in_10', $this->value)->shouldBeCalled()->willReturn($queryBuilder); $this->isSatisfiedBy('foo')->shouldReturn(true); - $this->modify($queryBuilder, $dqlAlias)->shouldReturn($expression); + $this->modify($queryBuilder, $dqlAlias)->shouldReturn((string) $expression); } } diff --git a/spec/Condition/IsInstanceOfSpec.php b/spec/Condition/IsInstanceOfSpec.php index 74593ff..8b3fbc8 100644 --- a/spec/Condition/IsInstanceOfSpec.php +++ b/spec/Condition/IsInstanceOfSpec.php @@ -1,49 +1,52 @@ beConstructedWith($this->field, $this->className, $this->dqlAlias); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_calls_is_instance_of(QueryBuilder $queryBuilder, Expr $expr) + public function it_calls_is_instance_of(QueryBuilder $queryBuilder, Expr $expr): void { - $expression = 'a.foo instance of ' . $this->className; + $expression = new Expr\Comparison('a.foo', 'instance of', $this->className); $queryBuilder->expr()->willReturn($expr); - $expr->isInstanceOf(sprintf('%s.%s', $this->dqlAlias, $this->field), $this->className)->willReturn($expression); + $expr->isInstanceOf(sprintf('%s.%s', $this->dqlAlias, $this->field), $this->className)->shouldBeCalled()->willReturn($expression); $this->isSatisfiedBy('foo')->shouldReturn(true); - $this->modify($queryBuilder, 'b')->shouldReturn($expression); + $this->modify($queryBuilder, 'b')->shouldReturn((string) $expression); } - public function it_uses_dql_alias_if_passed(QueryBuilder $queryBuilder, Expr $expr) + public function it_uses_dql_alias_if_passed(QueryBuilder $queryBuilder, Expr $expr): void { $dqlAlias = 'x'; + $expression = new Expr\Comparison('x.foo', 'instance of', $this->className); + $this->beConstructedWith($this->field, $this->className, null); $queryBuilder->expr()->willReturn($expr); - $expr->isInstanceOf(sprintf('%s.%s', $dqlAlias, $this->field), $this->className)->shouldBeCalled(); + $expr->isInstanceOf(sprintf('%s.%s', $dqlAlias, $this->field), $this->className)->shouldBeCalled()->willReturn($expression); $this->isSatisfiedBy('foo')->shouldReturn(true); - $this->modify($queryBuilder, $dqlAlias); + $this->modify($queryBuilder, $dqlAlias)->shouldReturn((string) $expression); } } diff --git a/spec/Condition/IsMemberOfSpec.php b/spec/Condition/IsMemberOfSpec.php index bc2b4a3..a3902e3 100644 --- a/spec/Condition/IsMemberOfSpec.php +++ b/spec/Condition/IsMemberOfSpec.php @@ -1,49 +1,51 @@ beConstructedWith($this->field, $this->className, $this->dqlAlias); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_calls_is_instance_of(QueryBuilder $queryBuilder, Expr $expr) + public function it_calls_is_instance_of(QueryBuilder $queryBuilder, Expr $expr): void { - $expression = 'a.foo member of ' . $this->className; + $expression = new Expr\Comparison($this->field, 'member of', $this->className); $queryBuilder->expr()->willReturn($expr); $expr->isMemberOf(sprintf('%s.%s', $this->dqlAlias, $this->field), $this->className)->willReturn($expression); $this->isSatisfiedBy('foo')->shouldReturn(true); - $this->modify($queryBuilder, 'b')->shouldReturn($expression); + $this->modify($queryBuilder, 'b')->shouldReturn((string) $expression); } - public function it_uses_dql_alias_if_passed(QueryBuilder $queryBuilder, Expr $expr) + public function it_uses_dql_alias_if_passed(QueryBuilder $queryBuilder, Expr $expr): void { $dqlAlias = 'x'; + $aliasedField = sprintf('%s.%s', $dqlAlias, $this->field); + $expression = new Expr\Comparison($aliasedField, 'member of', $this->className); + $this->beConstructedWith($this->field, $this->className, null); $queryBuilder->expr()->willReturn($expr); - $expr->isMemberOf(sprintf('%s.%s', $dqlAlias, $this->field), $this->className)->shouldBeCalled(); + $expr->isMemberOf($aliasedField, $this->className)->shouldBeCalled()->willReturn($expression); $this->isSatisfiedBy('foo')->shouldReturn(true); - $this->modify($queryBuilder, $dqlAlias); + $this->modify($queryBuilder, $dqlAlias)->shouldReturn((string) $expression); } } diff --git a/spec/Condition/IsNotNullSpec.php b/spec/Condition/IsNotNullSpec.php index 0bcade7..21f9e9d 100644 --- a/spec/Condition/IsNotNullSpec.php +++ b/spec/Condition/IsNotNullSpec.php @@ -1,28 +1,29 @@ beConstructedWith($this->field, $this->dqlAlias); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_calls_not_null(QueryBuilder $queryBuilder, Expr $expr) + public function it_calls_not_null(QueryBuilder $queryBuilder, Expr $expr): void { $expression = 'a.foo is not null'; @@ -32,13 +33,16 @@ public function it_calls_not_null(QueryBuilder $queryBuilder, Expr $expr) $this->modify($queryBuilder, null)->shouldReturn($expression); } - public function it_uses_dql_alias_if_passed(QueryBuilder $queryBuilder, Expr $expr) + public function it_uses_dql_alias_if_passed(QueryBuilder $queryBuilder, Expr $expr): void { $dqlAlias = 'x'; + $expression = 'x.foo is not null'; + $this->beConstructedWith($this->field, null); + $queryBuilder->expr()->willReturn($expr); + $expr->isNotNull(sprintf('%s.%s', $dqlAlias, $this->field))->willReturn($expression); - $expr->isNotNull(sprintf('%s.%s', $dqlAlias, $this->field))->shouldBeCalled(); - $this->modify($queryBuilder, $dqlAlias); + $this->modify($queryBuilder, $dqlAlias)->shouldReturn($expression); } } diff --git a/spec/Condition/IsNullSpec.php b/spec/Condition/IsNullSpec.php index 275155e..70eca69 100644 --- a/spec/Condition/IsNullSpec.php +++ b/spec/Condition/IsNullSpec.php @@ -1,29 +1,29 @@ beConstructedWith($this->field, $this->dqlAlias); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_calls_null(QueryBuilder $queryBuilder, Expr $expr) + public function it_calls_null(QueryBuilder $queryBuilder, Expr $expr): void { $expression = 'a.foo is null'; @@ -34,15 +34,16 @@ public function it_calls_null(QueryBuilder $queryBuilder, Expr $expr) $this->modify($queryBuilder, 'b')->shouldReturn($expression); } - public function it_uses_dql_alias_if_passed(QueryBuilder $queryBuilder, Expr $expr) + public function it_uses_dql_alias_if_passed(QueryBuilder $queryBuilder, Expr $expr): void { $dqlAlias = 'x'; + $expression = 'x.foo is null'; + $this->beConstructedWith($this->field, null); $queryBuilder->expr()->willReturn($expr); - - $expr->isNull(sprintf('%s.%s', $dqlAlias, $this->field))->shouldBeCalled(); + $expr->isNull(sprintf('%s.%s', $dqlAlias, $this->field))->willReturn($expression); $this->isSatisfiedBy('foo')->shouldReturn(true); - $this->modify($queryBuilder, $dqlAlias); + $this->modify($queryBuilder, $dqlAlias)->shouldReturn($expression); } } diff --git a/spec/Condition/LessThanOrEqualsSpec.php b/spec/Condition/LessThanOrEqualsSpec.php index 06eba13..e237e19 100644 --- a/spec/Condition/LessThanOrEqualsSpec.php +++ b/spec/Condition/LessThanOrEqualsSpec.php @@ -1,45 +1,45 @@ beConstructedWith('age', 18, 'a'); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters) + public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters): void { $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); - $condition = $this->modify($queryBuilder, null) + $this->modify($queryBuilder, null) ->shouldReturn('a.age <= :comparison_10'); } public function it_uses_comparison_specific_dql_alias_if_passed( QueryBuilder $queryBuilder, - ArrayCollection $parameters - ) { + ArrayCollection $parameters, + ): void { $this->beConstructedWith('age', 18, null); $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, 'x')->shouldReturn('x.age <= :comparison_10'); } diff --git a/spec/Condition/LessThanSpec.php b/spec/Condition/LessThanSpec.php index 6d6bf7c..6f05541 100644 --- a/spec/Condition/LessThanSpec.php +++ b/spec/Condition/LessThanSpec.php @@ -1,45 +1,45 @@ beConstructedWith('age', 18, 'a'); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters) + public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters): void { $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); - $condition = $this->modify($queryBuilder, null) + $this->modify($queryBuilder, null) ->shouldReturn('a.age < :comparison_10'); } public function it_uses_comparison_specific_dql_alias_if_passed( QueryBuilder $queryBuilder, - ArrayCollection $parameters - ) { + ArrayCollection $parameters, + ): void { $this->beConstructedWith('age', 18, null); $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, 'x')->shouldReturn('x.age < :comparison_10'); } diff --git a/spec/Condition/LikeSpec.php b/spec/Condition/LikeSpec.php index 677086c..38f696f 100644 --- a/spec/Condition/LikeSpec.php +++ b/spec/Condition/LikeSpec.php @@ -1,64 +1,64 @@ beConstructedWith($this->field, $this->value, Like::CONTAINS, 'dqlAlias'); } - public function it_is_a_specification() + public function it_is_a_specification(): void { $this->shouldHaveType(SpecificationInterface::class); } public function it_surrounds_with_wildcards_when_using_contains( QueryBuilder $queryBuilder, - ArrayCollection $parameters - ) { + ArrayCollection $parameters, + ): void { $this->beConstructedWith($this->field, $this->value, Like::CONTAINS, 'dqlAlias'); $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(1); - $queryBuilder->setParameter('comparison_1', '%bar%')->shouldBeCalled(); + $queryBuilder->setParameter('comparison_1', '%bar%')->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, null); } public function it_starts_with_wildcard_when_using_ends_with( QueryBuilder $queryBuilder, - ArrayCollection $parameters - ) { + ArrayCollection $parameters, + ): void { $this->beConstructedWith($this->field, $this->value, Like::ENDS_WITH, 'dqlAlias'); $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(1); - $queryBuilder->setParameter('comparison_1', '%bar')->shouldBeCalled(); + $queryBuilder->setParameter('comparison_1', '%bar')->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, null); } public function it_ends_with_wildcard_when_using_starts_with( QueryBuilder $queryBuilder, - ArrayCollection $parameters - ) { + ArrayCollection $parameters, + ): void { $this->beConstructedWith($this->field, $this->value, Like::STARTS_WITH, 'dqlAlias'); $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(1); - $queryBuilder->setParameter('comparison_1', 'bar%')->shouldBeCalled(); + $queryBuilder->setParameter('comparison_1', 'bar%')->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, null); } diff --git a/spec/Condition/NotEqualsSpec.php b/spec/Condition/NotEqualsSpec.php index 38b857c..5a76ef7 100644 --- a/spec/Condition/NotEqualsSpec.php +++ b/spec/Condition/NotEqualsSpec.php @@ -1,45 +1,45 @@ beConstructedWith('age', 18, 'a'); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); } - public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters) + public function it_returns_comparison_object(QueryBuilder $queryBuilder, ArrayCollection $parameters): void { $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); - $condition = $this->modify($queryBuilder, null) + $this->modify($queryBuilder, null) ->shouldReturn('a.age <> :comparison_10'); } public function it_uses_comparison_specific_dql_alias_if_passed( QueryBuilder $queryBuilder, - ArrayCollection $parameters - ) { + ArrayCollection $parameters, + ): void { $this->beConstructedWith('age', 18, null); $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled(); + $queryBuilder->setParameter('comparison_10', 18)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, 'x')->shouldReturn('x.age <> :comparison_10'); } diff --git a/spec/Condition/NotInSpec.php b/spec/Condition/NotInSpec.php index 0bca1e1..ee65a3a 100644 --- a/spec/Condition/NotInSpec.php +++ b/spec/Condition/NotInSpec.php @@ -1,62 +1,64 @@ beConstructedWith($this->field, $this->value); } - public function it_is_an_expression() + public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); $this->shouldBeAnInstanceOf(In::class); } - public function it_returns_an_expression_func_object(QueryBuilder $queryBuilder, ArrayCollection $parameters, Expr $expr) + public function it_returns_an_expression_func_object(QueryBuilder $queryBuilder, ArrayCollection $parameters, Expr $expr): void { - $dqlAlias = 'a'; - $expression = 'a.foo not in(:not_in_10)'; + $dqlAlias = 'a'; + $aliasedField = sprintf('%s.%s', $dqlAlias, $this->field); + $expression = new Expr\Func(sprintf('%s not in', $aliasedField), ':not_in_10'); $queryBuilder->expr()->willReturn($expr); - $expr->notIn(sprintf('%s.%s', $dqlAlias, $this->field), ':not_in_10')->willReturn($expression); + $expr->notIn($aliasedField, ':not_in_10')->willReturn($expression); $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('not_in_10', $this->value)->shouldBeCalled(); + $queryBuilder->setParameter('not_in_10', $this->value)->shouldBeCalled()->willReturn($queryBuilder); $this->isSatisfiedBy('foo')->shouldReturn(true); - $this->modify($queryBuilder, $dqlAlias)->shouldReturn($expression); + $this->modify($queryBuilder, $dqlAlias)->shouldReturn((string) $expression); } - public function it_should_use_dql_alias_if_set(QueryBuilder $queryBuilder, ArrayCollection $parameters, Expr $expr) + public function it_should_use_dql_alias_if_set(QueryBuilder $queryBuilder, ArrayCollection $parameters, Expr $expr): void { - $dqlAlias = 'x'; - $expression = 'x.foo not in(:not_in_10)'; + $dqlAlias = 'x'; + $aliasedField = sprintf('%s.%s', $dqlAlias, $this->field); + $expression = new Expr\Func(sprintf('%s not in', $aliasedField), ':not_in_10'); $this->beConstructedWith($this->field, $this->value, $dqlAlias); $queryBuilder->expr()->willReturn($expr); - $expr->notIn(sprintf('%s.%s', $dqlAlias, $this->field), ':not_in_10')->willReturn($expression); + $expr->notIn($aliasedField, ':not_in_10')->willReturn($expression); $queryBuilder->getParameters()->willReturn($parameters); $parameters->count()->willReturn(10); - $queryBuilder->setParameter('not_in_10', $this->value)->shouldBeCalled(); + $queryBuilder->setParameter('not_in_10', $this->value)->shouldBeCalled()->willReturn($queryBuilder); $this->isSatisfiedBy('foo')->shouldReturn(true); - $this->modify($queryBuilder, $dqlAlias)->shouldReturn($expression); + $this->modify($queryBuilder, $dqlAlias)->shouldReturn((string) $expression); } } diff --git a/spec/Logic/AndXSpec.php b/spec/Logic/AndXSpec.php index d27c905..24579a2 100644 --- a/spec/Logic/AndXSpec.php +++ b/spec/Logic/AndXSpec.php @@ -1,15 +1,15 @@ shouldHaveType(SpecificationInterface::class); $this->shouldHaveType(Composite::class); diff --git a/spec/Logic/CompositeSpec.php b/spec/Logic/CompositeSpec.php index 6bd55d8..8774f7f 100644 --- a/spec/Logic/CompositeSpec.php +++ b/spec/Logic/CompositeSpec.php @@ -1,57 +1,58 @@ beConstructedWith(self::EXPRESSION, [$specificationA, $specificationB]); } - public function it_is_a_specification() + public function it_is_a_specification(): void { $this->shouldHaveType(Composite::class); } public function it_supports_conditions( QueryBuilder $queryBuilder, - Expr $expression, + Expr $expr, SpecificationInterface $conditionA, SpecificationInterface $conditionB, - $x, - $y - ) { + ): void { $this->beConstructedWith(self::EXPRESSION, [$conditionA, $conditionB]); $dqlAlias = 'a'; + $x = 'x'; + $y = 'y'; + $expression = new Expr\Andx([$x, $y]); $conditionA->isSatisfiedBy('foo')->willReturn(true); $conditionB->isSatisfiedBy('foo')->willReturn(true); - $conditionA->modify($queryBuilder, $dqlAlias)->willReturn($x); - $conditionB->modify($queryBuilder, $dqlAlias)->willReturn($y); - $queryBuilder->expr()->willReturn($expression); + $conditionA->modify($queryBuilder, $dqlAlias)->shouldBeCalled()->willReturn($x); + $conditionB->modify($queryBuilder, $dqlAlias)->shouldBeCalled()->willReturn($y); + $queryBuilder->expr()->willReturn($expr); - $expression->{self::EXPRESSION}($x, $y)->shouldBeCalled(); + $expr->{self::EXPRESSION}($x, $y)->shouldBeCalled()->willReturn($expression); $this->isSatisfiedBy('foo')->shouldReturn(true); - $this->modify($queryBuilder, $dqlAlias); + $this->modify($queryBuilder, $dqlAlias)->shouldReturn((string) $expression); } public function it_should_fail_satisfaction_if_child_fails( SpecificationInterface $specificationA, - SpecificationInterface $specificationB - ) { + SpecificationInterface $specificationB, + ): void { $this->beConstructedWith(self::EXPRESSION, [$specificationA, $specificationB]); $specificationA->isSatisfiedBy('foo')->willReturn(true); @@ -64,8 +65,8 @@ public function it_should_return_null_for_specifications_without_conditions( QueryBuilder $queryBuilder, Expr $expression, SpecificationInterface $specificationA, - SpecificationInterface $specificationB - ) { + SpecificationInterface $specificationB, + ): void { $this->beConstructedWith(self::EXPRESSION, [$specificationA, $specificationB]); $dqlAlias = 'a'; @@ -79,8 +80,8 @@ public function it_should_return_null_for_specifications_without_conditions( public function it_should_throw_exception_on_invalid_type( SpecificationInterface $specificationA, - SpecificationInterface $specificationB - ) { + SpecificationInterface $specificationB, + ): void { $type = 'foo'; $this->beConstructedWith($type, [$specificationA, $specificationB]); @@ -89,7 +90,7 @@ public function it_should_throw_exception_on_invalid_type( ->during('__construct', [$type, [$specificationA, $specificationB]]); } - public function it_should_throw_exception_on_invalid_child() + public function it_should_throw_exception_on_invalid_child(): void { $child = 'bar'; diff --git a/spec/Logic/NotSpec.php b/spec/Logic/NotSpec.php index 1db2ab8..985845c 100644 --- a/spec/Logic/NotSpec.php +++ b/spec/Logic/NotSpec.php @@ -1,34 +1,35 @@ beConstructedWith($condition, null); } - public function it_calls_parent_match(QueryBuilder $queryBuilder, Expr $expr, SpecificationInterface $condition) + public function it_calls_parent_match(QueryBuilder $queryBuilder, Expr $expr, SpecificationInterface $condition): void { - $dqlAlias = 'a'; - $expression = 'expression'; - $parentExpression = 'foo'; + $dqlAlias = 'a'; + $aliasedField = sprintf('%s.%s', $dqlAlias, 'foo'); + $parentExpression = new Expr\Func($aliasedField, ':bar'); + $expression = new Expr\Func('NOT', [$parentExpression]); $queryBuilder->expr()->willReturn($expr); - $condition->modify($queryBuilder, $dqlAlias)->willReturn($parentExpression); + $condition->modify($queryBuilder, $dqlAlias)->willReturn((string) $parentExpression); $expr->not($parentExpression)->willReturn($expression); - $this->modify($queryBuilder, $dqlAlias)->shouldReturn($expression); + $this->modify($queryBuilder, $dqlAlias)->shouldReturn((string) $expression); } - public function it_modifies_parent_query(QueryBuilder $queryBuilder, SpecificationInterface $specification) + public function it_modifies_parent_query(QueryBuilder $queryBuilder, SpecificationInterface $specification): void { $dqlAlias = 'a'; $this->beConstructedWith($specification, null); @@ -37,7 +38,7 @@ public function it_modifies_parent_query(QueryBuilder $queryBuilder, Specificati $this->modify($queryBuilder, $dqlAlias); } - public function it_should_call_supports_on_parent(SpecificationInterface $specification) + public function it_should_call_supports_on_parent(SpecificationInterface $specification): void { $className = 'foo'; $this->beConstructedWith($specification, null); @@ -47,7 +48,7 @@ public function it_should_call_supports_on_parent(SpecificationInterface $specif $this->isSatisfiedBy($className); } - public function it_does_not_modify_parent_query(QueryBuilder $queryBuilder) + public function it_does_not_modify_parent_query(QueryBuilder $queryBuilder): void { $this->modify($queryBuilder, 'a'); } diff --git a/spec/Logic/OrXSpec.php b/spec/Logic/OrXSpec.php index e282397..e55a451 100644 --- a/spec/Logic/OrXSpec.php +++ b/spec/Logic/OrXSpec.php @@ -1,15 +1,15 @@ shouldHaveType(SpecificationInterface::class); $this->shouldHaveType(Composite::class); diff --git a/spec/Query/GroupBySpec.php b/spec/Query/GroupBySpec.php index fb809bd..90cc740 100644 --- a/spec/Query/GroupBySpec.php +++ b/spec/Query/GroupBySpec.php @@ -1,45 +1,46 @@ beConstructedWith($this->field, GroupBy::ADD_GROUP_BY, $this->alias); $this->isSatisfiedBy('foo')->shouldReturn(true); } - public function it_calls_groupBy_on_query_builder(QueryBuilder $queryBuilder) + public function it_calls_groupBy_on_query_builder(QueryBuilder $queryBuilder): void { $this->beConstructedWith($this->field, GroupBy::GROUP_BY, $this->alias); $arg = sprintf('%s.%s', $this->alias, $this->field); - $queryBuilder->groupBy($arg)->shouldBeCalled(); + $queryBuilder->groupBy($arg)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, $this->alias); } - public function it_calls_addGroupBy_on_query_builder(QueryBuilder $queryBuilder) + public function it_calls_addGroupBy_on_query_builder(QueryBuilder $queryBuilder): void { $this->beConstructedWith($this->field, GroupBy::ADD_GROUP_BY, $this->alias); $arg = sprintf('%s.%s', $this->alias, $this->field); - $queryBuilder->addGroupBy($arg)->shouldBeCalled(); + $queryBuilder->addGroupBy($arg)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, $this->alias); } - public function it_throws_exception_when_setting_illegal_type() + public function it_throws_exception_when_setting_illegal_type(): void { $this->beConstructedWith($this->field, 'foo', $this->alias); $this->shouldThrow(InvalidArgumentException::class)->duringInstantiation(); diff --git a/spec/Query/HavingSpec.php b/spec/Query/HavingSpec.php index 7b1e76d..064c6d1 100644 --- a/spec/Query/HavingSpec.php +++ b/spec/Query/HavingSpec.php @@ -1,71 +1,73 @@ */ class HavingSpec extends ObjectBehavior { - private $dqlAlias = 'a'; + private string $dqlAlias = 'a'; - public function let(SpecificationInterface $specification) + public function let(SpecificationInterface $specification): void { $this->beConstructedWith($specification); } - public function it_should_support_anything() + public function it_should_support_anything(): void { $this->isSatisfiedBy('foo')->shouldReturn(true); } - public function it_calls_modify_on_child_specification(QueryBuilder $queryBuilder, SpecificationInterface $specification) + public function it_calls_modify_on_child_specification(QueryBuilder $queryBuilder, SpecificationInterface $specification): void { - $specification->modify($queryBuilder, $this->dqlAlias)->shouldBeCalled(); + $condition = 'foo'; + $specification->modify($queryBuilder, $this->dqlAlias)->shouldBeCalled()->willReturn($condition); + $queryBuilder->andHaving($condition)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, $this->dqlAlias); } - public function it_calls_having_on_query_builder(QueryBuilder $queryBuilder, SpecificationInterface $specification) + public function it_calls_having_on_query_builder(QueryBuilder $queryBuilder, SpecificationInterface $specification): void { $condition = 'foo'; $specification->modify($queryBuilder, $this->dqlAlias)->willReturn($condition); $this->setType(Having::HAVING); - $queryBuilder->having($condition)->shouldBeCalled(); + $queryBuilder->having($condition)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, $this->dqlAlias); } - public function it_calls_andHaving_on_query_builder(QueryBuilder $queryBuilder, SpecificationInterface $specification) + public function it_calls_andHaving_on_query_builder(QueryBuilder $queryBuilder, SpecificationInterface $specification): void { $condition = 'foo'; $specification->modify($queryBuilder, $this->dqlAlias)->willReturn($condition); $this->setType(Having::AND_HAVING); - $queryBuilder->andHaving($condition)->shouldBeCalled(); + $queryBuilder->andHaving($condition)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, $this->dqlAlias); } - public function it_calls_orHaving_on_query_builder(QueryBuilder $queryBuilder, SpecificationInterface $specification) + public function it_calls_orHaving_on_query_builder(QueryBuilder $queryBuilder, SpecificationInterface $specification): void { $condition = 'foo'; $specification->modify($queryBuilder, $this->dqlAlias)->willReturn($condition); $this->setType(Having::OR_HAVING); - $queryBuilder->orHaving($condition)->shouldBeCalled(); + $queryBuilder->orHaving($condition)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, $this->dqlAlias); } - public function it_throws_exception_when_setting_illegal_type() + public function it_throws_exception_when_setting_illegal_type(): void { $this->shouldThrow(InvalidArgumentException::class) ->during('setType', ['foo']); diff --git a/spec/Query/IndexBySpec.php b/spec/Query/IndexBySpec.php index bce1ff7..849bf5e 100644 --- a/spec/Query/IndexBySpec.php +++ b/spec/Query/IndexBySpec.php @@ -1,25 +1,26 @@ beConstructedWith($this->field, $this->alias); } - public function it_should_modify_query_builder(QueryBuilder $queryBuilder) + public function it_should_modify_query_builder(QueryBuilder $queryBuilder): void { $property = sprintf('%s.%s', $this->alias, $this->field); - $queryBuilder->indexBy($this->alias, $property)->shouldBeCalled(); + $queryBuilder->indexBy($this->alias, $property)->shouldBeCalled()->willReturn($queryBuilder); $this->isSatisfiedBy('foo')->shouldReturn(true); $this->modify($queryBuilder, $this->alias); diff --git a/spec/Query/InnerJoinSpec.php b/spec/Query/InnerJoinSpec.php index d6da001..e162162 100644 --- a/spec/Query/InnerJoinSpec.php +++ b/spec/Query/InnerJoinSpec.php @@ -1,33 +1,33 @@ beConstructedWith('user', 'authUser', 'a'); } - public function it_is_a_specification() + public function it_is_a_specification(): void { $this->shouldHaveType(SpecificationInterface::class); } - public function it_joins_with_default_dql_alias(QueryBuilder $queryBuilder) + public function it_joins_with_default_dql_alias(QueryBuilder $queryBuilder): void { - $queryBuilder->innerJoin('a.user', 'authUser', null, null, null)->shouldBeCalled(); + $queryBuilder->innerJoin('a.user', 'authUser', null, null, null)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, 'a'); } - public function it_uses_local_alias_if_global_was_not_set(QueryBuilder $queryBuilder) + public function it_uses_local_alias_if_global_was_not_set(QueryBuilder $queryBuilder): void { $this->beConstructedWith('user', 'authUser'); - $queryBuilder->innerJoin('b.user', 'authUser', null, null, null)->shouldBeCalled(); + $queryBuilder->innerJoin('b.user', 'authUser', null, null, null)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, 'b'); } } diff --git a/spec/Query/JoinSpec.php b/spec/Query/JoinSpec.php index cda6b76..89fe300 100644 --- a/spec/Query/JoinSpec.php +++ b/spec/Query/JoinSpec.php @@ -1,47 +1,47 @@ beConstructedWith('user', 'authUser', 'a'); } - public function it_is_a_specification() + public function it_is_a_specification(): void { $this->shouldHaveType(SpecificationInterface::class); } - public function it_should_support_anything() + public function it_should_support_anything(): void { $this->isSatisfiedBy('foo')->shouldReturn(true); } - public function it_joins_with_default_dql_alias(QueryBuilder $queryBuilder) + public function it_joins_with_default_dql_alias(QueryBuilder $queryBuilder): void { - $queryBuilder->join('a.user', 'authUser', null, null, null)->shouldBeCalled(); + $queryBuilder->join('a.user', 'authUser', null, null, null)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, 'a'); } - public function it_uses_local_alias_if_global_was_not_set(QueryBuilder $queryBuilder) + public function it_uses_local_alias_if_global_was_not_set(QueryBuilder $queryBuilder): void { $this->beConstructedWith('user', 'authUser'); - $queryBuilder->join('b.user', 'authUser', null, null, null)->shouldBeCalled(); + $queryBuilder->join('b.user', 'authUser', null, null, null)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, 'b'); } - public function it_should_use_be_able_to_use_join_conditions(QueryBuilder $queryBuilder) + public function it_should_use_be_able_to_use_join_conditions(QueryBuilder $queryBuilder): void { - $joinType = DoctrineJoin::ON; + $joinType = DoctrineJoin::ON; $joinCondition = 'join condition'; $this->beConstructedWith('user', 'authUser'); @@ -49,41 +49,41 @@ public function it_should_use_be_able_to_use_join_conditions(QueryBuilder $query $this->setConditionType($joinType)->shouldReturn($this); $this->setCondition($joinCondition)->shouldReturn($this); - $queryBuilder->join('a.user', 'authUser', $joinType, $joinCondition, null)->shouldBeCalled(); + $queryBuilder->join('a.user', 'authUser', $joinType, $joinCondition, null)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, 'a'); } - public function it_should_be_able_to_set_index_by_for_join(QueryBuilder $queryBuilder) + public function it_should_be_able_to_set_index_by_for_join(QueryBuilder $queryBuilder): void { $indexedBy = 'index_by'; $this->beConstructedWith('user', 'authUser'); - $queryBuilder->join('a.user', 'authUser', null, null, $indexedBy)->shouldBeCalled(); + $queryBuilder->join('a.user', 'authUser', null, null, $indexedBy)->shouldBeCalled()->willReturn($queryBuilder); $this->setIndexedBy($indexedBy)->shouldReturn($this); $this->modify($queryBuilder, 'a'); } - public function it_should_accept_specifications_as_condition(QueryBuilder $queryBuilder, SpecificationInterface $specification) + public function it_should_accept_specifications_as_condition(QueryBuilder $queryBuilder, SpecificationInterface $specification): void { - $type = DoctrineJoin::ON; + $type = DoctrineJoin::ON; $condition = 'condition'; $this->beConstructedWith('user', 'authUser'); $specification->modify($queryBuilder, 'a')->willReturn($condition); - $queryBuilder->join('a.user', 'authUser', $type, $condition, null)->shouldBeCalled(); + $queryBuilder->join('a.user', 'authUser', $type, $condition, null)->shouldBeCalled()->willReturn($queryBuilder); $this->setConditionType($type)->shouldReturn($this); $this->setCondition($specification)->shouldReturn($this); $this->modify($queryBuilder, 'a'); } - public function it_throws_an_exception_when_setting_illegal_type() + public function it_throws_an_exception_when_setting_illegal_type(): void { $this->setType(Join::LEFT_JOIN); diff --git a/spec/Query/LeftJoinSpec.php b/spec/Query/LeftJoinSpec.php index 1f56ab7..b8efaf0 100644 --- a/spec/Query/LeftJoinSpec.php +++ b/spec/Query/LeftJoinSpec.php @@ -1,33 +1,33 @@ beConstructedWith('user', 'authUser', 'a'); } - public function it_is_a_specification() + public function it_is_a_specification(): void { $this->shouldHaveType(SpecificationInterface::class); } - public function it_joins_with_default_dql_alias(QueryBuilder $queryBuilder) + public function it_joins_with_default_dql_alias(QueryBuilder $queryBuilder): void { - $queryBuilder->leftJoin('a.user', 'authUser', null, null, null)->shouldBeCalled(); + $queryBuilder->leftJoin('a.user', 'authUser', null, null, null)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, 'a'); } - public function it_uses_local_alias_if_global_was_not_set(QueryBuilder $queryBuilder) + public function it_uses_local_alias_if_global_was_not_set(QueryBuilder $queryBuilder): void { $this->beConstructedWith('user', 'authUser'); - $queryBuilder->leftJoin('b.user', 'authUser', null, null, null)->shouldBeCalled(); + $queryBuilder->leftJoin('b.user', 'authUser', null, null, null)->shouldBeCalled()->willReturn($queryBuilder); $this->modify($queryBuilder, 'b'); } } diff --git a/spec/Query/OrderBySpec.php b/spec/Query/OrderBySpec.php index 95afadc..3f6b59b 100644 --- a/spec/Query/OrderBySpec.php +++ b/spec/Query/OrderBySpec.php @@ -1,34 +1,36 @@ beConstructedWith($this->field, $this->order, $this->alias); } - public function it_should_throw_exception_when_given_invalid_order() + public function it_should_throw_exception_when_given_invalid_order(): void { $this->shouldThrow(InvalidArgumentException::class) ->during('__construct', ['foo', 'bar']); } - public function it_should_modify_query_builder(QueryBuilder $queryBuilder) + public function it_should_modify_query_builder(QueryBuilder $queryBuilder): void { $sort = sprintf('%s.%s', $this->alias, $this->field); - $queryBuilder->addOrderBy($sort, $this->order)->shouldBeCalled(); + $queryBuilder->addOrderBy($sort, $this->order)->shouldBeCalled()->willReturn($queryBuilder); $this->isSatisfiedBy('foo')->shouldReturn(true); $this->modify($queryBuilder, $this->alias); diff --git a/spec/Query/SelectSpec.php b/spec/Query/SelectSpec.php index a52cb94..90d8f31 100644 --- a/spec/Query/SelectSpec.php +++ b/spec/Query/SelectSpec.php @@ -1,41 +1,41 @@ beConstructedWith($entity, $type); - $queryBuilder->addSelect($entity)->shouldBeCalled(); + $queryBuilder->addSelect($entity)->shouldBeCalled()->willReturn($queryBuilder); $this->isSatisfiedBy('foo')->shouldReturn(true); $this->modify($queryBuilder, $alias); } - public function it_should_replace_selects_in_query_builder(QueryBuilder $queryBuilder) + public function it_should_replace_selects_in_query_builder(QueryBuilder $queryBuilder): void { - $alias = 'a'; + $alias = 'a'; $entity = 'foo'; - $type = Select::SELECT; + $type = Select::SELECT; $this->beConstructedWith($entity, $type); - $queryBuilder->select($entity)->shouldBeCalled(); + $queryBuilder->select($entity)->shouldBeCalled()->willReturn($queryBuilder); $this->isSatisfiedBy('foo')->shouldReturn(true); $this->modify($queryBuilder, $alias); } - public function it_throws_an_exception_when_setting_illegal_type() + public function it_throws_an_exception_when_setting_illegal_type(): void { $entity = 'foo'; $this->beConstructedWith($entity); diff --git a/spec/Result/AsArraySpec.php b/spec/Result/AsArraySpec.php index 54a8bbc..e26df51 100644 --- a/spec/Result/AsArraySpec.php +++ b/spec/Result/AsArraySpec.php @@ -1,22 +1,22 @@ shouldHaveType(ModifierInterface::class); } - public function it_sets_hydration_mode_to_array(AbstractQuery $query) + public function it_sets_hydration_mode_to_array(AbstractQuery $query): void { - $query->setHydrationMode(Query::HYDRATE_ARRAY)->shouldBeCalled(); + $query->setHydrationMode(Query::HYDRATE_ARRAY)->shouldBeCalled()->willReturn($query); $this->modify($query); } diff --git a/spec/Result/ModifierCollectionSpec.php b/spec/Result/ModifierCollectionSpec.php index 54117f2..b70db11 100644 --- a/spec/Result/ModifierCollectionSpec.php +++ b/spec/Result/ModifierCollectionSpec.php @@ -1,19 +1,19 @@ beConstructedWith($modifierA, $modifierB); $modifierA->modify($query)->shouldBeCalled(); @@ -22,7 +22,7 @@ public function it_should_call_modify_on_child_modifiers( $this->modify($query); } - public function it_should_throw_exception_when_adding_incorrect_children() + public function it_should_throw_exception_when_adding_incorrect_children(): void { $this->shouldThrow(InvalidArgumentException::class) ->during('add', ['test']); diff --git a/spec/SpecificationRepositorySpec.php b/spec/SpecificationRepositorySpec.php index 6863f67..c34729a 100644 --- a/spec/SpecificationRepositorySpec.php +++ b/spec/SpecificationRepositorySpec.php @@ -1,28 +1,29 @@ name = 'foo'; $this->beAnInstanceOf(SpecificationRepository::class); $this->beConstructedWith($entityManager, $classMetadata); } @@ -31,17 +32,14 @@ public function it_matches_a_specification_without_result_modifier( SpecificationInterface $specification, EntityManager $entityManager, QueryBuilder $queryBuilder, - AbstractQuery $query - ) { + Query $query, + ): void { $this->prepare($specification, $entityManager, $queryBuilder, $query); - $specification->isSatisfiedBy(Argument::any()) - ->willReturn(true); - $specification->modify($queryBuilder, $this->dqlAlias) - ->shouldBeCalled(); + $specification->isSatisfiedBy(Argument::any())->willReturn(true); + $specification->modify($queryBuilder, $this->dqlAlias)->shouldBeCalled(); - $this->match($specification) - ->shouldReturn($query); + $this->match($specification)->shouldReturn($query); $query->execute(); } @@ -50,9 +48,9 @@ public function it_matches_a_specification_with_result_modifier( SpecificationInterface $specification, EntityManager $entityManager, QueryBuilder $queryBuilder, - AbstractQuery $query, - ModifierInterface $modifier - ) { + Query $query, + ModifierInterface $modifier, + ): void { $this->prepare($specification, $entityManager, $queryBuilder, $query); $specification->isSatisfiedBy(Argument::any()) ->willReturn(true); @@ -72,8 +70,8 @@ public function it_should_throw_logic_exception_if_spec_not_supported( SpecificationInterface $specification, EntityManager $entityManager, QueryBuilder $queryBuilder, - AbstractQuery $query - ) { + Query $query, + ): void { $this->prepare($specification, $entityManager, $queryBuilder, $query); $specification->isSatisfiedBy(Argument::any()) ->willReturn(false); @@ -86,8 +84,8 @@ public function it_should_accept_specification_with_only_query_modifiers( SpecificationInterface $specification, EntityManager $entityManager, QueryBuilder $queryBuilder, - AbstractQuery $query - ) { + Query $query, + ): void { $entityManager->createQueryBuilder()->willReturn($queryBuilder); $queryBuilder->select($this->dqlAlias)->willReturn($queryBuilder); @@ -104,18 +102,13 @@ public function it_should_accept_specification_with_only_query_modifiers( /** * Prepare mocks. - * - * @param SpecificationInterface $specification - * @param EntityManager $entityManager - * @param QueryBuilder $queryBuilder - * @param AbstractQuery $query */ private function prepare( SpecificationInterface $specification, EntityManager $entityManager, QueryBuilder $queryBuilder, - AbstractQuery $query - ) { + Query $query, + ): void { $entityManager->createQueryBuilder()->willReturn($queryBuilder); $specification->modify($queryBuilder, $this->dqlAlias)->willReturn($this->expression); diff --git a/spec/SpecificationSpec.php b/spec/SpecificationSpec.php index ed63f64..4ad09c1 100644 --- a/spec/SpecificationSpec.php +++ b/spec/SpecificationSpec.php @@ -1,16 +1,16 @@ shouldHaveType(SpecificationInterface::class); } @@ -18,8 +18,8 @@ public function it_is_a_specification() public function it_modifies_all_child_queries( QueryBuilder $queryBuilder, SpecificationInterface $specificationA, - SpecificationInterface $specificationB - ) { + SpecificationInterface $specificationB, + ): void { $this->beConstructedWith([$specificationA, $specificationB]); $dqlAlias = 'a'; @@ -31,13 +31,14 @@ public function it_modifies_all_child_queries( public function it_supports_conditions( QueryBuilder $queryBuilder, - Expr $expression, + Expr $expr, SpecificationInterface $conditionA, SpecificationInterface $conditionB, - $x, - $y - ) { + ): void { $dqlAlias = 'a'; + $x = 'x'; + $y = 'y'; + $expression = new Expr\Andx([$x, $y]); $this[] = $conditionA; $this[] = $conditionB; @@ -47,9 +48,9 @@ public function it_supports_conditions( $conditionA->modify($queryBuilder, $dqlAlias)->willReturn($x); $conditionB->modify($queryBuilder, $dqlAlias)->willReturn($y); - $queryBuilder->expr()->willReturn($expression); + $queryBuilder->expr()->willReturn($expr); - $expression->andX($x, $y)->shouldBeCalled(); + $expr->andX($x, $y)->shouldBeCalled()->willReturn($expression); $this->isSatisfiedBy('foo')->shouldReturn(true); $this->modify($queryBuilder, $dqlAlias); @@ -58,8 +59,8 @@ public function it_supports_conditions( public function it_supports_query_modifiers( QueryBuilder $queryBuilder, SpecificationInterface $modifierA, - SpecificationInterface $modifierB - ) { + SpecificationInterface $modifierB, + ): void { $this->beConstructedWith([$modifierA, $modifierB]); $dqlAlias = 'a'; @@ -76,8 +77,8 @@ public function it_supports_query_modifiers( public function it_should_throw_exception_when_child_does_not_support_class( SpecificationInterface $specificationA, - SpecificationInterface $specificationB - ) { + SpecificationInterface $specificationB, + ): void { $className = 'foo'; $this->beConstructedWith([$specificationA, $specificationB]); @@ -87,7 +88,7 @@ public function it_should_throw_exception_when_child_does_not_support_class( $this->isSatisfiedBy($className)->shouldReturn(false); } - public function it_should_throw_exception_on_invalid_child() + public function it_should_throw_exception_on_invalid_child(): void { $this->shouldThrow(InvalidArgumentException::class) ->during('add', ['bar']); diff --git a/src/AbstractSpecification.php b/src/AbstractSpecification.php index 59c04cb..6420a05 100644 --- a/src/AbstractSpecification.php +++ b/src/AbstractSpecification.php @@ -1,64 +1,37 @@ field = $field; - $this->dqlAlias = $dqlAlias; } - /** - * {@inheritdoc} - */ - public function isSatisfiedBy($value) + #[\Override] + public function isSatisfiedBy(mixed $value): bool { return true; } /** * Create a formatted string for the given property prefixed with the DQL alias. - * - * @param string $dqlAlias - * - * @return string */ - protected function createPropertyWithAlias($dqlAlias) + protected function createPropertyWithAlias(?string $dqlAlias): string { return $this->createAliasedName($this->field, $dqlAlias); } /** * Create a formatted string where the value will be prefixed with DQL alias (if not already present). - * - * @param string $value - * @param string $dqlAlias - * - * @return string */ - protected function createAliasedName($value, $dqlAlias) + protected function createAliasedName(string $value, ?string $dqlAlias): string { - if (strpos($value, '.') !== false) { + if (str_contains($value, '.')) { return $value; } - if (! empty($this->dqlAlias)) { + if (null !== $this->dqlAlias && '' !== $this->dqlAlias && '0' !== $this->dqlAlias) { $dqlAlias = $this->dqlAlias; } diff --git a/src/Condition/Between.php b/src/Condition/Between.php index eca0c45..3a71312 100644 --- a/src/Condition/Between.php +++ b/src/Condition/Between.php @@ -1,61 +1,34 @@ from = $from; - $this->to = $to; - parent::__construct($field, $dqlAlias); } - /** - * {@inheritdoc} - */ - public function modify(QueryBuilder $queryBuilder, $dqlAlias) + #[\Override] + public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): string { $fromParam = $this->generateParameterName('from', $queryBuilder); - $toParam = $this->generateParameterName('to', $queryBuilder); + $toParam = $this->generateParameterName('to', $queryBuilder); $queryBuilder->setParameter($fromParam, $this->from); $queryBuilder->setParameter($toParam, $this->to); - return (string) $queryBuilder->expr()->between( + return $queryBuilder->expr()->between( $this->createPropertyWithAlias($dqlAlias), sprintf(':%s', $fromParam), - sprintf(':%s', $toParam) + sprintf(':%s', $toParam), ); } - /** - * @param string $type - * @param QueryBuilder $queryBuilder - * - * @return string - */ - private function generateParameterName($type, QueryBuilder $queryBuilder) + private function generateParameterName(string $type, QueryBuilder $queryBuilder): string { return sprintf('%s_%d', $type, count($queryBuilder->getParameters())); } diff --git a/src/Condition/Comparison.php b/src/Condition/Comparison.php index 4b0b525..48b84b2 100644 --- a/src/Condition/Comparison.php +++ b/src/Condition/Comparison.php @@ -1,72 +1,53 @@ '; - const LT = '<'; - const LTE = '<='; - const GT = '>'; - const GTE = '>='; - const LIKE = 'LIKE'; + public const string EQ = '='; - /** - * @var string[] - */ - protected static $operators = [self::EQ, self::NEQ, self::LT, self::LTE, self::GT, self::GTE, self::LIKE]; + public const string NEQ = '<>'; - /** - * @var string - */ - protected $value; + public const string LT = '<'; + + public const string LTE = '<='; + + public const string GT = '>'; + + public const string GTE = '>='; + + public const string LIKE = 'LIKE'; /** - * @var string + * @var string[] */ - protected $operator; + protected static array $operators = [self::EQ, self::NEQ, self::LT, self::LTE, self::GT, self::GTE, self::LIKE]; + protected string $operator; /** - * @param string $operator - * @param string $field - * @param string $value - * @param string|null $dqlAlias - * * @throws InvalidArgumentException */ - public function __construct($operator, $field, $value, $dqlAlias = null) + public function __construct(string $operator, string $field, protected string $value, ?string $dqlAlias = null) { - if (! in_array($operator, self::$operators, true)) { - throw new InvalidArgumentException( - sprintf( - '"%s" is not a valid operator. Valid operators: %s', - $operator, - implode(', ', self::$operators) - ) - ); + if (!in_array($operator, self::$operators, true)) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid operator. Valid operators: %s', $operator, implode(', ', self::$operators))); } $this->operator = $operator; - $this->value = $value; parent::__construct($field, $dqlAlias); } /** * Return a string expression which can be used as condition (in WHERE-clause). - * - * @param QueryBuilder $queryBuilder - * @param string $dqlAlias - * - * @return string */ - public function modify(QueryBuilder $queryBuilder, $dqlAlias) + #[\Override] + public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): string { $paramName = $this->generateParameterName($queryBuilder); $queryBuilder->setParameter($paramName, $this->value); @@ -74,18 +55,14 @@ public function modify(QueryBuilder $queryBuilder, $dqlAlias) return (string) new DoctrineComparison( $this->createPropertyWithAlias($dqlAlias), $this->operator, - sprintf(':%s', $paramName) + sprintf(':%s', $paramName), ); } /** * Return automatically generated parameter name. - * - * @param QueryBuilder $queryBuilder - * - * @return string */ - protected function generateParameterName(QueryBuilder $queryBuilder) + protected function generateParameterName(QueryBuilder $queryBuilder): string { return sprintf('comparison_%d', count($queryBuilder->getParameters())); } diff --git a/src/Condition/Equals.php b/src/Condition/Equals.php index 0e5b212..13b9008 100644 --- a/src/Condition/Equals.php +++ b/src/Condition/Equals.php @@ -1,19 +1,15 @@ createPropertyWithAlias($dqlAlias), $this->operator, - $this->createAliasedName($this->value, $dqlAlias) + $this->createAliasedName($this->value, $dqlAlias), ); } } diff --git a/src/Condition/GreaterThan.php b/src/Condition/GreaterThan.php index 7989bad..0bfc153 100644 --- a/src/Condition/GreaterThan.php +++ b/src/Condition/GreaterThan.php @@ -1,19 +1,15 @@ value = $value; - parent::__construct($field, $dqlAlias); } - /** - * {@inheritdoc} - */ - public function modify(QueryBuilder $queryBuilder, $dqlAlias) + #[\Override] + public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): string { $paramName = $this->generateParameterName($queryBuilder); $queryBuilder->setParameter($paramName, $this->value); return (string) $queryBuilder->expr()->in( $this->createPropertyWithAlias($dqlAlias), - sprintf(':%s', $paramName) + sprintf(':%s', $paramName), ); } - /** - * @param QueryBuilder $queryBuilder - * - * @return string - */ - protected function generateParameterName(QueryBuilder $queryBuilder) + protected function generateParameterName(QueryBuilder $queryBuilder): string { return sprintf('in_%d', count($queryBuilder->getParameters())); } diff --git a/src/Condition/IsInstanceOf.php b/src/Condition/IsInstanceOf.php index 04f377d..066d939 100644 --- a/src/Condition/IsInstanceOf.php +++ b/src/Condition/IsInstanceOf.php @@ -1,37 +1,23 @@ className = $className; - parent::__construct($field, $dqlAlias); } - /** - * {@inheritdoc} - */ - public function modify(QueryBuilder $queryBuilder, $dqlAlias) + #[\Override] + public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): string { return (string) $queryBuilder->expr()->isInstanceOf( $this->createPropertyWithAlias($dqlAlias), - $this->className + $this->className, ); } } diff --git a/src/Condition/IsMemberOf.php b/src/Condition/IsMemberOf.php index de7b684..fa33a60 100644 --- a/src/Condition/IsMemberOf.php +++ b/src/Condition/IsMemberOf.php @@ -1,37 +1,23 @@ className = $className; - parent::__construct($field, $dqlAlias); } - /** - * {@inheritdoc} - */ - public function modify(QueryBuilder $queryBuilder, $dqlAlias) + #[\Override] + public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): string { return (string) $queryBuilder->expr()->isMemberOf( $this->createPropertyWithAlias($dqlAlias), - $this->className + $this->className, ); } } diff --git a/src/Condition/IsNotNull.php b/src/Condition/IsNotNull.php index 0d446d8..8351e1b 100644 --- a/src/Condition/IsNotNull.php +++ b/src/Condition/IsNotNull.php @@ -1,18 +1,16 @@ expr()->isNotNull( - $this->createPropertyWithAlias($dqlAlias) + return $queryBuilder->expr()->isNotNull( + $this->createPropertyWithAlias($dqlAlias), ); } } diff --git a/src/Condition/IsNull.php b/src/Condition/IsNull.php index 84e106d..99a7268 100644 --- a/src/Condition/IsNull.php +++ b/src/Condition/IsNull.php @@ -1,19 +1,17 @@ expr()->isNull( - $this->createPropertyWithAlias($dqlAlias) + return $queryBuilder->expr()->isNull( + $this->createPropertyWithAlias($dqlAlias), ); } } diff --git a/src/Condition/LessThan.php b/src/Condition/LessThan.php index 04ad304..7e572ec 100644 --- a/src/Condition/LessThan.php +++ b/src/Condition/LessThan.php @@ -1,19 +1,15 @@ formatValue($format, $value); parent::__construct(self::LIKE, $field, $formattedValue, $dqlAlias); } - /** - * @param string $format - * @param string $value - * - * @return string - */ - private function formatValue($format, $value) + private function formatValue(string $format, string $value): string { return sprintf($format, $value); } diff --git a/src/Condition/NotEquals.php b/src/Condition/NotEquals.php index 024979d..fc2898a 100644 --- a/src/Condition/NotEquals.php +++ b/src/Condition/NotEquals.php @@ -1,19 +1,15 @@ generateParameterName($queryBuilder); $queryBuilder->setParameter($paramName, $this->value); return (string) $queryBuilder->expr()->notIn( $this->createPropertyWithAlias($dqlAlias), - sprintf(':%s', $paramName) + sprintf(':%s', $paramName), ); } - /** - * @param QueryBuilder $queryBuilder - * - * @return string - */ - protected function generateParameterName(QueryBuilder $queryBuilder) + #[\Override] + protected function generateParameterName(QueryBuilder $queryBuilder): string { return sprintf('not_in_%d', count($queryBuilder->getParameters())); } diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index fc4b461..b99587e 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -1,6 +1,6 @@ setType($type) - ->setChildren($children); + parent::__construct($children); + + $this->setType($type); } } diff --git a/src/Logic/Not.php b/src/Logic/Not.php index f3f38ab..d410bc2 100644 --- a/src/Logic/Not.php +++ b/src/Logic/Not.php @@ -1,48 +1,32 @@ parent = $expr; } - /** - * @param QueryBuilder $queryBuilder - * @param string $dqlAlias - * - * @return string - */ - public function modify(QueryBuilder $queryBuilder, $dqlAlias) + #[\Override] + public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): ?string { $filter = $this->parent->modify($queryBuilder, $dqlAlias); - if (empty($filter)) { + if (null === $filter || '' === $filter || '0' === $filter) { return ''; } return (string) $queryBuilder->expr()->not($filter); } - /** - * {@inheritdoc} - */ - public function isSatisfiedBy($value) + #[\Override] + public function isSatisfiedBy(mixed $value): bool { return $this->parent->isSatisfiedBy($value); } diff --git a/src/Logic/OrX.php b/src/Logic/OrX.php index aef60c0..3ed66bc 100644 --- a/src/Logic/OrX.php +++ b/src/Logic/OrX.php @@ -1,19 +1,13 @@ */ class GroupBy extends AbstractSpecification { - const GROUP_BY = 'groupBy'; - const ADD_GROUP_BY = 'addGroupBy'; + public const string GROUP_BY = 'groupBy'; + public const string ADD_GROUP_BY = 'addGroupBy'; /** @var string[] */ - protected static $types = [self::GROUP_BY, self::ADD_GROUP_BY]; - - /** @var string */ - protected $type; + protected static array $types = [self::GROUP_BY, self::ADD_GROUP_BY]; + protected string $type; /** - * Constructor. - * - * @param string $field - * @param string $type - * @param string|null $dqlAlias + * @throws InvalidArgumentException */ - public function __construct($field, $type = self::ADD_GROUP_BY, $dqlAlias = null) + public function __construct(string $field, string $type = self::ADD_GROUP_BY, ?string $dqlAlias = null) { - $this->setType($type); parent::__construct($field, $dqlAlias); + + $this->setType($type); } - /** - * {@inheritdoc} - */ - public function modify(QueryBuilder $queryBuilder, $dqlAlias) + #[\Override] + public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): ?string { - call_user_func_array( - [$queryBuilder, $this->type], - [ - $this->createPropertyWithAlias($dqlAlias), - ] - ); + $queryBuilder->{$this->type}($this->createPropertyWithAlias($dqlAlias)); + + return null; } /** - * @param string $type - * * @throws InvalidArgumentException */ - public function setType($type) + public function setType(string $type): void { - if (! in_array($type, self::$types, true)) { - throw new InvalidArgumentException(sprintf( - '"%s" is not a valid type! Valid types: %s', - $type, - implode(', ', self::$types) - )); + if (!in_array($type, self::$types, true)) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::$types))); } $this->type = $type; diff --git a/src/Query/Having.php b/src/Query/Having.php index a4ef385..12aba31 100644 --- a/src/Query/Having.php +++ b/src/Query/Having.php @@ -1,57 +1,52 @@ */ class Having implements SpecificationInterface { - const HAVING = 'having'; - const AND_HAVING = 'andHaving'; - const OR_HAVING = 'orHaving'; + public const string HAVING = 'having'; + public const string AND_HAVING = 'andHaving'; + public const string OR_HAVING = 'orHaving'; - protected static $types = [self::HAVING, self::AND_HAVING, self::OR_HAVING]; + protected static array $types = [self::HAVING, self::AND_HAVING, self::OR_HAVING]; + protected string $type; - /** @var string */ - protected $type; - - /** @var SpecificationInterface */ - protected $specification; - - public function __construct(SpecificationInterface $specification, $type = self::AND_HAVING) + /** + * @throws InvalidArgumentException + */ + public function __construct(protected SpecificationInterface $specification, string $type = self::AND_HAVING) { - $this->specification = $specification; $this->setType($type); } - public function modify(QueryBuilder $queryBuilder, $dqlAlias) + #[\Override] + public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): ?string { - call_user_func_array( - [$queryBuilder, $this->type], - [ - $this->specification->modify($queryBuilder, $dqlAlias), - ] - ); + $queryBuilder->{$this->type}($this->specification->modify($queryBuilder, $dqlAlias)); + + return null; } - public function isSatisfiedBy($value) + #[\Override] + public function isSatisfiedBy(mixed $value): bool { return true; } - public function setType($type) + /** + * @throws InvalidArgumentException + */ + public function setType(string $type): void { - if (! in_array($type, self::$types, true)) { - throw new InvalidArgumentException(sprintf( - '"%s" is not a valid type! Valid types: %s', - $type, - implode(', ', self::$types) - )); + if (!in_array($type, self::$types, true)) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::$types))); } $this->type = $type; diff --git a/src/Query/IndexBy.php b/src/Query/IndexBy.php index 367a2da..3f3bbef 100644 --- a/src/Query/IndexBy.php +++ b/src/Query/IndexBy.php @@ -1,23 +1,27 @@ indexBy( - $this->dqlAlias ?: $dqlAlias, - $this->createPropertyWithAlias($dqlAlias) + $this->dqlAlias ?? $dqlAlias, + $this->createPropertyWithAlias($dqlAlias), ); + + return null; } } diff --git a/src/Query/InnerJoin.php b/src/Query/InnerJoin.php index 03c56eb..18711d0 100644 --- a/src/Query/InnerJoin.php +++ b/src/Query/InnerJoin.php @@ -1,21 +1,18 @@ setType(self::INNER_JOIN); parent::__construct($field, $newAlias, $dqlAlias); + + $this->setType(self::INNER_JOIN); } } diff --git a/src/Query/Join.php b/src/Query/Join.php index a0c2ed7..e1cbd6b 100644 --- a/src/Query/Join.php +++ b/src/Query/Join.php @@ -1,77 +1,42 @@ newAlias = $newAlias; + private string $type = self::JOIN; + public function __construct(string $field, private readonly string $newAlias, ?string $dqlAlias = null) + { parent::__construct($field, $dqlAlias); } /** - * @param string $type - * * @throws InvalidArgumentException - * - * @return $this */ - public function setType($type) + public function setType(string $type): static { - if (! in_array($type, self::$types, true)) { - throw new InvalidArgumentException(sprintf( - '"%s" is not a valid type! Valid types: %s', - $type, - implode(', ', self::$types) - )); + if (!in_array($type, self::$types, true)) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::$types))); } $this->type = $type; @@ -79,12 +44,10 @@ public function setType($type) return $this; } - /** - * {@inheritdoc} - */ - public function modify(QueryBuilder $queryBuilder, $dqlAlias) + #[\Override] + public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): ?string { - if (! is_null($this->dqlAlias)) { + if (!is_null($this->dqlAlias)) { $dqlAlias = $this->dqlAlias; } @@ -95,20 +58,15 @@ public function modify(QueryBuilder $queryBuilder, $dqlAlias) $condition = $condition->modify($queryBuilder, $dqlAlias); } - call_user_func_array( - [$queryBuilder, $this->type], - [$property, $this->newAlias, $this->conditionType, $condition, $this->indexedBy] - ); + $queryBuilder->{$this->type}($property, $this->newAlias, $this->conditionType, $condition, $this->indexedBy); + + return null; } /** * Set the condition type to be used on the join (WITH/ON). - * - * @param string $conditionType - * - * @return $this */ - public function setConditionType($conditionType) + public function setConditionType(?string $conditionType): static { $this->conditionType = $conditionType; @@ -117,12 +75,8 @@ public function setConditionType($conditionType) /** * Set the condition to be used for the join statement. - * - * @param string|SpecificationInterface $condition - * - * @return $this */ - public function setCondition($condition) + public function setCondition(SpecificationInterface|string|null $condition): static { $this->condition = $condition; @@ -131,12 +85,8 @@ public function setCondition($condition) /** * Set the property which will be used as index for the returned collection. - * - * @param mixed $indexedBy - * - * @return $this */ - public function setIndexedBy($indexedBy) + public function setIndexedBy(?string $indexedBy): static { $this->indexedBy = $indexedBy; diff --git a/src/Query/LeftJoin.php b/src/Query/LeftJoin.php index 6380363..c562b71 100644 --- a/src/Query/LeftJoin.php +++ b/src/Query/LeftJoin.php @@ -1,21 +1,18 @@ setType(self::LEFT_JOIN); parent::__construct($field, $newAlias, $dqlAlias); + + $this->setType(self::LEFT_JOIN); } } diff --git a/src/Query/OrderBy.php b/src/Query/OrderBy.php index eaf488e..ae7a111 100644 --- a/src/Query/OrderBy.php +++ b/src/Query/OrderBy.php @@ -1,38 +1,29 @@ addOrderBy( $this->createPropertyWithAlias($dqlAlias), - $this->order + $this->order, ); + + return null; } } diff --git a/src/Query/Select.php b/src/Query/Select.php index 449c74c..a4c5c79 100644 --- a/src/Query/Select.php +++ b/src/Query/Select.php @@ -1,71 +1,54 @@ setType($type); - $this->select = $select; } - /** - * {@inheritdoc} - */ - public function modify(QueryBuilder $queryBuilder, $dqlAlias) + #[\Override] + public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): ?string { - call_user_func_array([$queryBuilder, $this->type], [$this->select]); + $queryBuilder->{$this->type}($this->select); + + return null; } /** - * @param string $type - * * @throws InvalidArgumentException */ - public function setType($type) + public function setType(string $type): void { - if (! in_array($type, self::$types, true)) { - throw new InvalidArgumentException(sprintf( - '"%s" is not a valid type! Valid types: %s', - $type, - implode(', ', self::$types) - )); + if (!in_array($type, self::$types, true)) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::$types))); } $this->type = $type; } - /** - * {@inheritdoc} - */ - public function isSatisfiedBy($value) + #[\Override] + public function isSatisfiedBy(mixed $value): bool { return true; } diff --git a/src/Result/AsArray.php b/src/Result/AsArray.php index 849e53a..f045802 100644 --- a/src/Result/AsArray.php +++ b/src/Result/AsArray.php @@ -1,9 +1,8 @@ setHydrationMode(Query::HYDRATE_ARRAY); + $query->setHydrationMode(AbstractQuery::HYDRATE_ARRAY); } } diff --git a/src/Result/ModifierCollection.php b/src/Result/ModifierCollection.php index c442d67..35edd42 100644 --- a/src/Result/ModifierCollection.php +++ b/src/Result/ModifierCollection.php @@ -1,10 +1,10 @@ add(...), $modifiers); } /** * @param ModifierInterface $value * * @throws InvalidArgumentException - * - * @return bool */ - public function add($value) + #[\Override] + public function add(mixed $value): void { - if (! $value instanceof ModifierInterface) { - throw new InvalidArgumentException(sprintf( - '"%s" does not implement "%s"!', - (is_object($value)) ? get_class($value) : $value, - ModifierInterface::class - )); + if (!$value instanceof ModifierInterface) { + throw new InvalidArgumentException(sprintf('"%s" does not implement "%s"!', (is_object($value)) ? $value::class : $value, ModifierInterface::class)); } - return parent::add($value); + parent::add($value); } /** * Modify the query (e.g. select more fields/relations). * - * @param AbstractQuery $query - * * @throws InvalidArgumentException */ - public function modify(AbstractQuery $query) + #[\Override] + public function modify(AbstractQuery $query): void { foreach ($this as $child) { $child->modify($query); diff --git a/src/Result/ModifierInterface.php b/src/Result/ModifierInterface.php index dbcba07..b43adfb 100644 --- a/src/Result/ModifierInterface.php +++ b/src/Result/ModifierInterface.php @@ -1,6 +1,6 @@ modify($queryBuilder, $dqlAlias); - }; + $match = static fn (SpecificationInterface $specification): ?string => $specification->modify($queryBuilder, $dqlAlias); $result = array_filter(array_map($match, $this->toArray())); - if (empty($result)) { - return; + if ([] === $result) { + return null; } - return call_user_func_array( - [$queryBuilder->expr(), $this->type], - $result - ); + return $queryBuilder->expr()->{$this->type}(...$result); } - /** - * {@inheritdoc} - */ - public function isSatisfiedBy($value) + #[\Override] + public function isSatisfiedBy($value): bool { /** @var SpecificationInterface $child */ foreach ($this as $child) { @@ -91,13 +72,11 @@ public function isSatisfiedBy($value) /** * @param SpecificationInterface[] $children - * - * @return $this */ - protected function setChildren(array $children) + protected function setChildren(array $children): static { $this->clear(); - array_map([$this, 'add'], $children); + array_map($this->add(...), $children); return $this; } @@ -105,15 +84,11 @@ protected function setChildren(array $children) /** * Set the type of comparison. * - * @param string $type - * * @throws InvalidArgumentException - * - * @return $this */ - protected function setType($type) + protected function setType(string $type): static { - if (! in_array($type, self::$types, true)) { + if (!in_array($type, self::$types, true)) { $message = sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::$types)); throw new InvalidArgumentException($message); } diff --git a/src/SpecificationAwareInterface.php b/src/SpecificationAwareInterface.php index 72803b8..26f6a32 100644 --- a/src/SpecificationAwareInterface.php +++ b/src/SpecificationAwareInterface.php @@ -1,9 +1,9 @@ isSatisfiedBy($this->getEntityName())) { - throw new LogicException(sprintf( - 'Specification "%s" not supported by this repository!', - get_class($specification) - )); + if (!$specification->isSatisfiedBy($this->getEntityName())) { + throw new LogicException(sprintf('Specification "%s" not supported by this repository!', $specification::class)); } $queryBuilder = $this->createQueryBuilder($this->dqlAlias); @@ -46,16 +32,13 @@ public function match(SpecificationInterface $specification, ModifierInterface $ * Modifies the QueryBuilder according to the passed Specification. * Will also set the condition for this query if needed. * - * @param QueryBuilder $queryBuilder - * @param SpecificationInterface $specification - * * @internal param string $dqlAlias */ - private function modifyQueryBuilder(QueryBuilder $queryBuilder, SpecificationInterface $specification) + private function modifyQueryBuilder(QueryBuilder $queryBuilder, SpecificationInterface $specification): void { $condition = $specification->modify($queryBuilder, $this->dqlAlias); - if (empty($condition)) { + if (null === $condition || '' === $condition || '0' === $condition) { return; } @@ -64,17 +47,12 @@ private function modifyQueryBuilder(QueryBuilder $queryBuilder, SpecificationInt /** * Modifies and returns a Query object according to the (optional) result modifier. - * - * @param QueryBuilder $queryBuilder - * @param ModifierInterface|null $modifier - * - * @return Query */ - private function modifyQuery(QueryBuilder $queryBuilder, ModifierInterface $modifier = null) + private function modifyQuery(QueryBuilder $queryBuilder, ?ModifierInterface $modifier = null): AbstractQuery { $query = $queryBuilder->getQuery(); - if ($modifier) { + if ($modifier instanceof ModifierInterface) { $modifier->modify($query); } From 78011f17782bb8063386eda50e7dcdf46cc09614 Mon Sep 17 00:00:00 2001 From: Nicholas Ruunu Date: Thu, 20 Jun 2024 00:07:57 +0200 Subject: [PATCH 02/10] Add phpstan and fix errors --- composer.json | 3 ++- phpstan-baseline.neon | 11 +++++++++++ phpstan.neon | 7 +++++++ src/Condition/Between.php | 2 +- src/Query/Having.php | 3 +++ src/Query/IndexBy.php | 2 +- src/Query/Join.php | 8 +++----- src/Query/OrderBy.php | 10 +++++----- src/Query/Select.php | 8 +++++--- src/Result/ModifierCollection.php | 12 +++++++++--- src/Specification.php | 7 ++++++- src/SpecificationRepository.php | 2 ++ 12 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 phpstan-baseline.neon create mode 100644 phpstan.neon diff --git a/composer.json b/composer.json index bd07ffa..1771baf 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "require-dev": { "phpspec/phpspec": "^7.5", "rector/rector": "^1.1", - "friendsofphp/php-cs-fixer": "^3.59" + "friendsofphp/php-cs-fixer": "^3.59", + "phpstan/phpstan": "^1.11" }, "extra": { "branch-alias": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..e657b02 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,11 @@ +parameters: + ignoreErrors: + - + message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" + count: 1 + path: src/Result/ModifierCollection.php + + - + message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" + count: 1 + path: src/Specification.php diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..3d3952c --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,7 @@ +parameters: + level: max + paths: + - src + +includes: + - phpstan-baseline.neon diff --git a/src/Condition/Between.php b/src/Condition/Between.php index 3a71312..e62ac56 100644 --- a/src/Condition/Between.php +++ b/src/Condition/Between.php @@ -7,7 +7,7 @@ class Between extends AbstractSpecification { - public function __construct(string $field, protected $from, protected $to, ?string $dqlAlias = null) + public function __construct(string $field, protected mixed $from, protected mixed $to, ?string $dqlAlias = null) { parent::__construct($field, $dqlAlias); } diff --git a/src/Query/Having.php b/src/Query/Having.php index 12aba31..d78e343 100644 --- a/src/Query/Having.php +++ b/src/Query/Having.php @@ -15,6 +15,9 @@ class Having implements SpecificationInterface public const string AND_HAVING = 'andHaving'; public const string OR_HAVING = 'orHaving'; + /** + * @var array + */ protected static array $types = [self::HAVING, self::AND_HAVING, self::OR_HAVING]; protected string $type; diff --git a/src/Query/IndexBy.php b/src/Query/IndexBy.php index 3f3bbef..9380ca8 100644 --- a/src/Query/IndexBy.php +++ b/src/Query/IndexBy.php @@ -18,7 +18,7 @@ class IndexBy extends AbstractSpecification public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): ?string { $queryBuilder->indexBy( - $this->dqlAlias ?? $dqlAlias, + $this->dqlAlias ?? $dqlAlias ?? '', $this->createPropertyWithAlias($dqlAlias), ); diff --git a/src/Query/Join.php b/src/Query/Join.php index e1cbd6b..de28d25 100644 --- a/src/Query/Join.php +++ b/src/Query/Join.php @@ -10,19 +10,17 @@ class Join extends AbstractSpecification { public const string JOIN = 'join'; - public const string LEFT_JOIN = 'leftJoin'; - public const string INNER_JOIN = 'innerJoin'; + /** + * @var array + */ protected static array $types = [self::JOIN, self::LEFT_JOIN, self::INNER_JOIN]; private ?string $conditionType = null; - private string|SpecificationInterface|null $condition = null; - private ?string $indexedBy = null; - private string $type = self::JOIN; public function __construct(string $field, private readonly string $newAlias, ?string $dqlAlias = null) diff --git a/src/Query/OrderBy.php b/src/Query/OrderBy.php index ae7a111..8ea5918 100644 --- a/src/Query/OrderBy.php +++ b/src/Query/OrderBy.php @@ -8,12 +8,12 @@ class OrderBy extends AbstractSpecification { - public const ASC = 'ASC'; - - public const DESC = 'DESC'; - + public const string ASC = 'ASC'; + public const string DESC = 'DESC'; protected ?string $order; - + /** + * @var array + */ private static array $validOrder = [self::ASC, self::DESC]; /** diff --git a/src/Query/Select.php b/src/Query/Select.php index a4c5c79..5a998ee 100644 --- a/src/Query/Select.php +++ b/src/Query/Select.php @@ -12,14 +12,16 @@ class Select implements SpecificationInterface { public const string SELECT = 'select'; - public const string ADD_SELECT = 'addSelect'; - + /** + * @var array + */ protected static array $types = [self::SELECT, self::ADD_SELECT]; - protected string $type; /** + * @param string|array $select + * * @throws InvalidArgumentException */ public function __construct(protected string|array $select, string $type = self::ADD_SELECT) diff --git a/src/Result/ModifierCollection.php b/src/Result/ModifierCollection.php index 35edd42..42bc877 100644 --- a/src/Result/ModifierCollection.php +++ b/src/Result/ModifierCollection.php @@ -8,17 +8,25 @@ /** * CollectionResultModifierInterface allows to compose one/more ResultModifier classes. + * + * @extends ArrayCollection */ class ModifierCollection extends ArrayCollection implements ModifierInterface { /** * Compose one or more ResultModifier and evaluate as a single modifier. + * + * @param ModifierInterface ...$modifiers + * + * @throws InvalidArgumentException */ public function __construct(mixed ...$modifiers) { parent::__construct(); - array_map($this->add(...), $modifiers); + foreach ($modifiers as $modifier) { + $this->add($modifier); + } } /** @@ -38,8 +46,6 @@ public function add(mixed $value): void /** * Modify the query (e.g. select more fields/relations). - * - * @throws InvalidArgumentException */ #[\Override] public function modify(AbstractQuery $query): void diff --git a/src/Specification.php b/src/Specification.php index a2789c6..f839995 100644 --- a/src/Specification.php +++ b/src/Specification.php @@ -9,11 +9,16 @@ /** * Specification can be used as a quick-start to writing your own specifications. * It extends Doctrines ArrayCollection class, so you can compose specifications. + * + * @extends ArrayCollection */ class Specification extends ArrayCollection implements SpecificationInterface { public const string AND_X = 'andX'; public const string OR_X = 'orX'; + /** + * @var array + */ protected static array $types = [self::OR_X, self::AND_X]; private string $type = self::AND_X; @@ -56,7 +61,7 @@ public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): ?s } #[\Override] - public function isSatisfiedBy($value): bool + public function isSatisfiedBy(mixed $value): bool { /** @var SpecificationInterface $child */ foreach ($this as $child) { diff --git a/src/SpecificationRepository.php b/src/SpecificationRepository.php index d7bd8be..c241823 100644 --- a/src/SpecificationRepository.php +++ b/src/SpecificationRepository.php @@ -14,6 +14,8 @@ * * @see SpecificationRepositoryTrait * @see SpecificationAwareInterface + * + * @extends EntityRepository */ class SpecificationRepository extends EntityRepository implements SpecificationAwareInterface { From 28050fd966331386f5b5d520724c41e0c23ee736 Mon Sep 17 00:00:00 2001 From: Nicholas Ruunu Date: Thu, 20 Jun 2024 00:08:58 +0200 Subject: [PATCH 03/10] Add phpspec, phpstan, php-cs-fixer and composer to docker --- docker-compose.yaml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 693871b..b569b94 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,6 +1,25 @@ services: - php: + php: &php image: php:8.3-cli-alpine volumes: - .:/app working_dir: /app + + phpspec: + <<: *php + entrypoint: vendor/bin/phpspec + command: run + + phpstan: + <<: *php + entrypoint: vendor/bin/phpstan + + php-cs-fixer: + <<: *php + entrypoint: vendor/bin/php-cs-fixer fix + + composer: + image: composer:latest + volumes: + - .:/app + working_dir: /app From cbe5892a10414cb9d32c14b9b1c593204b405ca7 Mon Sep 17 00:00:00 2001 From: Nicholas Ruunu Date: Thu, 20 Jun 2024 00:53:27 +0200 Subject: [PATCH 04/10] Remove SpecificationRepository and old phpspec settings --- phpspec.yml | 4 ++++ phpspec.yml.dist | 12 ------------ spec/SpecificationRepositoryDouble.php | 20 ++++++++++++++++++++ spec/SpecificationRepositorySpec.php | 5 +---- src/SpecificationRepository.php | 23 ----------------------- 5 files changed, 25 insertions(+), 39 deletions(-) create mode 100644 phpspec.yml delete mode 100644 phpspec.yml.dist create mode 100644 spec/SpecificationRepositoryDouble.php delete mode 100644 src/SpecificationRepository.php diff --git a/phpspec.yml b/phpspec.yml new file mode 100644 index 0000000..572f1fe --- /dev/null +++ b/phpspec.yml @@ -0,0 +1,4 @@ +suites: + main: + namespace: Purist\Specification\Doctrine + psr4_prefix: Purist\Specification\Doctrine diff --git a/phpspec.yml.dist b/phpspec.yml.dist deleted file mode 100644 index 8f16157..0000000 --- a/phpspec.yml.dist +++ /dev/null @@ -1,12 +0,0 @@ -code_coverage: - format: - - clover - - html - output: - html: build/logs/html - clover: build/logs/clover.xml - -suites: - main: - namespace: Purist\Specification\Doctrine - psr4_prefix: Purist\Specification\Doctrine diff --git a/spec/SpecificationRepositoryDouble.php b/spec/SpecificationRepositoryDouble.php new file mode 100644 index 0000000..9bbbd65 --- /dev/null +++ b/spec/SpecificationRepositoryDouble.php @@ -0,0 +1,20 @@ +name = 'foo'; - $this->beAnInstanceOf(SpecificationRepository::class); + $this->beAnInstanceOf(SpecificationRepositoryDouble::class); $this->beConstructedWith($entityManager, $classMetadata); } diff --git a/src/SpecificationRepository.php b/src/SpecificationRepository.php deleted file mode 100644 index c241823..0000000 --- a/src/SpecificationRepository.php +++ /dev/null @@ -1,23 +0,0 @@ - - */ -class SpecificationRepository extends EntityRepository implements SpecificationAwareInterface -{ - use SpecificationRepositoryTrait; -} From b2725abb27339c8b72eb6d7be0ee224a7e850ed1 Mon Sep 17 00:00:00 2001 From: Nicholas Ruunu Date: Thu, 20 Jun 2024 01:03:59 +0200 Subject: [PATCH 05/10] Fix up authors, license, vendor name, gitattributes --- .gitattributes | 9 +++++---- .travis.yml | 28 ---------------------------- LICENSE | 2 +- README.md | 26 ++++++++++++++------------ composer.json | 4 ++-- 5 files changed, 22 insertions(+), 47 deletions(-) delete mode 100644 .travis.yml diff --git a/.gitattributes b/.gitattributes index 3cf489b..95ec386 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,9 @@ /spec export-ignore -.coveralls.yml export-ignore .editorconfig export-ignore .gitattributes export-ignore .gitignore export-ignore -.travis.yml export-ignore -.php_cs export-ignore -phpspec.yml.dist export-ignore +.php-cs-fixer.php export-ignore +phpspec.yml export-ignore +rector.php export-ignore +phpstan.neon export-ignore +phpstan-baseline.neon export-ignore diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 471417a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -language: php - -matrix: - include: - - php: 5.5 - - php: 5.6 - - php: 7.0 - -sudo: false - -cache: - directories: - - $HOME/.composer/cache - -before_install: - - travis_retry composer self-update - -install: - - travis_retry composer install --no-interaction - -before_script: - - mkdir -p build/logs - -script: - - composer test - -after_success: - - php vendor/bin/coveralls -v diff --git a/LICENSE b/LICENSE index 3c98b61..75abca4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ The MIT License (MIT) Copyright (c) 2015 Rik Bruil +Copyright (c) 2024 Nicholas Ruunu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,4 +20,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/README.md b/README.md index 9a6bd2b..f9bee03 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,23 @@ # Doctrine Specification -[![Build Status](https://travis-ci.org/rikbruil/Doctrine-Specification.svg)](https://travis-ci.org/rikbruil/Doctrine-Specification) -[![Coverage Status](https://coveralls.io/repos/rikbruil/Doctrine-Specification/badge.svg?branch=master)](https://coveralls.io/r/rikbruil/Doctrine-Specification?branch=master) -[![Latest Stable Version](https://poser.pugx.org/rikbruil/doctrine-specification/v/stable.svg)](https://packagist.org/packages/rikbruil/doctrine-specification) -[![License](https://poser.pugx.org/rikbruil/doctrine-specification/license.svg)](https://packagist.org/packages/rikbruil/doctrine-specification) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/rikbruil/Doctrine-Specification/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/rikbruil/Doctrine-Specification/?branch=master) -[![SensioLabsInsight](https://insight.sensiolabs.com/projects/bb221821-1ba6-4613-b39f-f43718aaa92d/mini.png)](https://insight.sensiolabs.com/projects/bb221821-1ba6-4613-b39f-f43718aaa92d) -Doctrine [Specification pattern][specification_pattern] for building queries dynamically and with re-usable classes for composition. +Doctrine [Specification pattern][specification_pattern] for building queries dynamically and with re-usable classes for +composition. -This library started out as an adaptation of Benjamin Eberlei's [blog post][blog_post]. I was also inspired by the [Happyr Doctrine-Specification][happyr_spec] code, however this library has some small differences. -The main one is that SpecificationRepository->match() does not return the results directly, but returns the query object. +This library started out as an adaptation of Benjamin Eberlei's [blog post][blog_post]. I was also inspired by +the [Happyr Doctrine-Specification][happyr_spec] code, however this library has some small differences. +The main one is that SpecificationRepository->match() does not return the results directly, but returns the query +object. Since I like Doctrine's Paginator object, I wanted to be able to use that in combination with the Specification pattern. -__Note:__ In versions prior to 1.2 it was required to extend the SpecificationRepository class. This is no longer needed since we provide a SpecificationRepositoryTrait that you can use instead. -The class is still provided for backwards compatibility reasons. There is also the SpecificationAwareInterface that you can use if you need it. +__Note:__ In versions prior to 1.2 it was required to extend the SpecificationRepository class. This is no longer needed +since we provide a SpecificationRepositoryTrait that you can use instead. +The class is still provided for backwards compatibility reasons. There is also the SpecificationAwareInterface that you +can use if you need it. ## Usage -Install the latest version with `composer require rikbruil/doctrine-specification` +Install the latest version with `composer require purist/doctrine-specification` ```php // Not using the lib @@ -67,6 +66,7 @@ return $this->em->getRepository('Advertisement')->match($spec)->execute(); ``` ## Composition + A bonus of this pattern is composition, which makes specifications very reusable: ```php @@ -168,5 +168,7 @@ This library is heavily inspired by Benjamin Eberlei's [blog post][blog_post] and [Happyr's Doctrine-Specification library][happyr_spec]. [specification_pattern]: http://en.wikipedia.org/wiki/Specification_pattern + [happyr_spec]: https://github.com/Happyr/Doctrine-Specification + [blog_post]: http://www.whitewashing.de/2013/03/04/doctrine_repositories.html diff --git a/composer.json b/composer.json index 1771baf..45fc6b4 100644 --- a/composer.json +++ b/composer.json @@ -6,8 +6,8 @@ "license": "MIT", "authors": [ { - "name": "Rik Bruil", - "email": "rik.bruil@gmail.com" + "name": "Nicholas Ruunu", + "email": "nicholas@ruu.nu" } ], "minimum-stability": "stable", From 5ea96955acc4ae440d4fd4e5ef09f5ac498bd818 Mon Sep 17 00:00:00 2001 From: Nicholas Ruunu Date: Thu, 20 Jun 2024 01:14:51 +0200 Subject: [PATCH 06/10] Add test workflow --- .github/workflows/php.yml | 43 +++++++++++++++++++++++++++++++++++++++ composer.json | 2 ++ 2 files changed, 45 insertions(+) create mode 100644 .github/workflows/php.yml diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 0000000..cef2c14 --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,43 @@ +name: PHP Composer + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Validate composer.json and composer.lock + run: docker compose run composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.json') }}-${{ github.ref_name }} + restore-keys: | + ${{ runner.os }}-php-${{ hashFiles('**/composer.json') }} + ${{ runner.os }}-php- + + - name: Install packages + run: docker compose run composer install + + - name: Run test suite + run: docker compose run phpspec + + - name: Run static analyzer + run: docker compose run phpstan + + - name: Run code style checker + run: docker compose run php-cs-fixer --dry-run diff --git a/composer.json b/composer.json index 45fc6b4..833cc06 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,8 @@ }, "scripts": { "test": "vendor/bin/phpspec run", + "static-analyzer": "vendor/bin/phpstan", + "check-style": "vendor/bin/php-cs-fixer fix --dry-run", "fix-style": "vendor/bin/php-cs-fixer fix" }, "repositories": [ From a5178bfe36c8714aa716668d300faa78ac62f56e Mon Sep 17 00:00:00 2001 From: Nicholas Ruunu Date: Tue, 2 Jul 2024 03:06:28 +0200 Subject: [PATCH 07/10] Add readonly where possible --- README.md | 2 - spec/Condition/NotInSpec.php | 4 +- spec/Logic/NotSpec.php | 8 +-- spec/Query/HavingSpec.php | 15 ++--- spec/Query/JoinSpec.php | 14 ++-- src/AbstractSpecification.php | 4 +- src/Condition/Between.php | 2 +- src/Condition/Comparison.php | 20 ++---- src/Condition/Equals.php | 2 +- src/Condition/EqualsProperty.php | 2 +- src/Condition/GreaterThan.php | 2 +- src/Condition/GreaterThanOrEquals.php | 2 +- src/Condition/In.php | 2 +- src/Condition/IsInstanceOf.php | 2 +- src/Condition/IsMemberOf.php | 2 +- src/Condition/IsNotNull.php | 3 +- src/Condition/IsNull.php | 2 +- src/Condition/LessThan.php | 2 +- src/Condition/LessThanOrEquals.php | 2 +- src/Condition/Like.php | 4 +- src/Condition/NotEquals.php | 2 +- src/Condition/NotIn.php | 9 ++- src/Logic/Not.php | 3 +- src/Query/GroupBy.php | 36 +++++------ src/Query/Having.php | 22 +++---- src/Query/IndexBy.php | 2 +- src/Query/InnerJoin.php | 6 +- src/Query/Join.php | 92 +++++++++++++++++---------- src/Query/LeftJoin.php | 6 +- src/Query/OrderBy.php | 11 ++-- src/Query/Select.php | 19 +++--- src/Result/AsArray.php | 2 +- src/Result/ModifierCollection.php | 10 +-- src/Specification.php | 32 ++++++---- 34 files changed, 179 insertions(+), 169 deletions(-) diff --git a/README.md b/README.md index f9bee03..413e247 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,5 @@ This library is heavily inspired by Benjamin Eberlei's [blog post][blog_post] and [Happyr's Doctrine-Specification library][happyr_spec]. [specification_pattern]: http://en.wikipedia.org/wiki/Specification_pattern - [happyr_spec]: https://github.com/Happyr/Doctrine-Specification - [blog_post]: http://www.whitewashing.de/2013/03/04/doctrine_repositories.html diff --git a/spec/Condition/NotInSpec.php b/spec/Condition/NotInSpec.php index ee65a3a..1840142 100644 --- a/spec/Condition/NotInSpec.php +++ b/spec/Condition/NotInSpec.php @@ -6,7 +6,7 @@ use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; use PhpSpec\ObjectBehavior; -use Purist\Specification\Doctrine\Condition\In; +use Purist\Specification\Doctrine\Condition\NotIn; use Purist\Specification\Doctrine\SpecificationInterface; class NotInSpec extends ObjectBehavior @@ -23,7 +23,7 @@ public function let(): void public function it_is_an_expression(): void { $this->shouldBeAnInstanceOf(SpecificationInterface::class); - $this->shouldBeAnInstanceOf(In::class); + $this->shouldBeAnInstanceOf(NotIn::class); } public function it_returns_an_expression_func_object(QueryBuilder $queryBuilder, ArrayCollection $parameters, Expr $expr): void diff --git a/spec/Logic/NotSpec.php b/spec/Logic/NotSpec.php index 985845c..7c456a6 100644 --- a/spec/Logic/NotSpec.php +++ b/spec/Logic/NotSpec.php @@ -11,7 +11,7 @@ class NotSpec extends ObjectBehavior { public function let(SpecificationInterface $condition): void { - $this->beConstructedWith($condition, null); + $this->beConstructedWith($condition); } public function it_calls_parent_match(QueryBuilder $queryBuilder, Expr $expr, SpecificationInterface $condition): void @@ -32,16 +32,16 @@ public function it_calls_parent_match(QueryBuilder $queryBuilder, Expr $expr, Sp public function it_modifies_parent_query(QueryBuilder $queryBuilder, SpecificationInterface $specification): void { $dqlAlias = 'a'; - $this->beConstructedWith($specification, null); + $this->beConstructedWith($specification); - $specification->modify($queryBuilder, $dqlAlias)->shouldBeCalled(); + $specification->modify($queryBuilder, $dqlAlias)->shouldBeCalled()->willReturn(null); $this->modify($queryBuilder, $dqlAlias); } public function it_should_call_supports_on_parent(SpecificationInterface $specification): void { $className = 'foo'; - $this->beConstructedWith($specification, null); + $this->beConstructedWith($specification); $specification->isSatisfiedBy($className)->shouldBeCalled(); diff --git a/spec/Query/HavingSpec.php b/spec/Query/HavingSpec.php index 064c6d1..6c991b4 100644 --- a/spec/Query/HavingSpec.php +++ b/spec/Query/HavingSpec.php @@ -38,33 +38,30 @@ public function it_calls_having_on_query_builder(QueryBuilder $queryBuilder, Spe { $condition = 'foo'; $specification->modify($queryBuilder, $this->dqlAlias)->willReturn($condition); - - $this->setType(Having::HAVING); $queryBuilder->having($condition)->shouldBeCalled()->willReturn($queryBuilder); - $this->modify($queryBuilder, $this->dqlAlias); + $having = $this->setType(Having::HAVING); + $having->modify($queryBuilder, $this->dqlAlias); } public function it_calls_andHaving_on_query_builder(QueryBuilder $queryBuilder, SpecificationInterface $specification): void { $condition = 'foo'; $specification->modify($queryBuilder, $this->dqlAlias)->willReturn($condition); - - $this->setType(Having::AND_HAVING); $queryBuilder->andHaving($condition)->shouldBeCalled()->willReturn($queryBuilder); - $this->modify($queryBuilder, $this->dqlAlias); + $having = $this->setType(Having::AND_HAVING); + $having->modify($queryBuilder, $this->dqlAlias); } public function it_calls_orHaving_on_query_builder(QueryBuilder $queryBuilder, SpecificationInterface $specification): void { $condition = 'foo'; $specification->modify($queryBuilder, $this->dqlAlias)->willReturn($condition); - - $this->setType(Having::OR_HAVING); $queryBuilder->orHaving($condition)->shouldBeCalled()->willReturn($queryBuilder); - $this->modify($queryBuilder, $this->dqlAlias); + $having = $this->setType(Having::OR_HAVING); + $having->modify($queryBuilder, $this->dqlAlias); } public function it_throws_exception_when_setting_illegal_type(): void diff --git a/spec/Query/JoinSpec.php b/spec/Query/JoinSpec.php index 89fe300..4b1fa63 100644 --- a/spec/Query/JoinSpec.php +++ b/spec/Query/JoinSpec.php @@ -46,12 +46,11 @@ public function it_should_use_be_able_to_use_join_conditions(QueryBuilder $query $this->beConstructedWith('user', 'authUser'); - $this->setConditionType($joinType)->shouldReturn($this); - $this->setCondition($joinCondition)->shouldReturn($this); + $join = $this->setConditionType($joinType)->setCondition($joinCondition); $queryBuilder->join('a.user', 'authUser', $joinType, $joinCondition, null)->shouldBeCalled()->willReturn($queryBuilder); - $this->modify($queryBuilder, 'a'); + $join->modify($queryBuilder, 'a'); } public function it_should_be_able_to_set_index_by_for_join(QueryBuilder $queryBuilder): void @@ -62,9 +61,9 @@ public function it_should_be_able_to_set_index_by_for_join(QueryBuilder $queryBu $queryBuilder->join('a.user', 'authUser', null, null, $indexedBy)->shouldBeCalled()->willReturn($queryBuilder); - $this->setIndexedBy($indexedBy)->shouldReturn($this); + $join = $this->setIndexedBy($indexedBy); - $this->modify($queryBuilder, 'a'); + $join->modify($queryBuilder, 'a'); } public function it_should_accept_specifications_as_condition(QueryBuilder $queryBuilder, SpecificationInterface $specification): void @@ -78,9 +77,8 @@ public function it_should_accept_specifications_as_condition(QueryBuilder $query $queryBuilder->join('a.user', 'authUser', $type, $condition, null)->shouldBeCalled()->willReturn($queryBuilder); - $this->setConditionType($type)->shouldReturn($this); - $this->setCondition($specification)->shouldReturn($this); - $this->modify($queryBuilder, 'a'); + $join = $this->setConditionType($type)->setCondition($specification); + $join->modify($queryBuilder, 'a'); } public function it_throws_an_exception_when_setting_illegal_type(): void diff --git a/src/AbstractSpecification.php b/src/AbstractSpecification.php index 6420a05..3fa85a7 100644 --- a/src/AbstractSpecification.php +++ b/src/AbstractSpecification.php @@ -2,7 +2,7 @@ namespace Purist\Specification\Doctrine; -abstract class AbstractSpecification implements SpecificationInterface +abstract readonly class AbstractSpecification implements SpecificationInterface { public function __construct(protected string $field, protected ?string $dqlAlias = null) { @@ -31,7 +31,7 @@ protected function createAliasedName(string $value, ?string $dqlAlias): string return $value; } - if (null !== $this->dqlAlias && '' !== $this->dqlAlias && '0' !== $this->dqlAlias) { + if (null !== $this->dqlAlias) { $dqlAlias = $this->dqlAlias; } diff --git a/src/Condition/Between.php b/src/Condition/Between.php index e62ac56..4b5e005 100644 --- a/src/Condition/Between.php +++ b/src/Condition/Between.php @@ -5,7 +5,7 @@ use Doctrine\ORM\QueryBuilder; use Purist\Specification\Doctrine\AbstractSpecification; -class Between extends AbstractSpecification +readonly class Between extends AbstractSpecification { public function __construct(string $field, protected mixed $from, protected mixed $to, ?string $dqlAlias = null) { diff --git a/src/Condition/Comparison.php b/src/Condition/Comparison.php index 48b84b2..7ff4578 100644 --- a/src/Condition/Comparison.php +++ b/src/Condition/Comparison.php @@ -7,39 +7,29 @@ use Purist\Specification\Doctrine\AbstractSpecification; use Purist\Specification\Doctrine\Exception\InvalidArgumentException; -class Comparison extends AbstractSpecification +readonly class Comparison extends AbstractSpecification { public const string EQ = '='; - public const string NEQ = '<>'; - public const string LT = '<'; - public const string LTE = '<='; - public const string GT = '>'; - public const string GTE = '>='; - public const string LIKE = 'LIKE'; - /** * @var string[] */ - protected static array $operators = [self::EQ, self::NEQ, self::LT, self::LTE, self::GT, self::GTE, self::LIKE]; - protected string $operator; + protected const array OPERATORS = [self::EQ, self::NEQ, self::LT, self::LTE, self::GT, self::GTE, self::LIKE]; /** * @throws InvalidArgumentException */ - public function __construct(string $operator, string $field, protected string $value, ?string $dqlAlias = null) + public function __construct(protected string $operator, string $field, protected string $value, ?string $dqlAlias = null) { - if (!in_array($operator, self::$operators, true)) { - throw new InvalidArgumentException(sprintf('"%s" is not a valid operator. Valid operators: %s', $operator, implode(', ', self::$operators))); + if (!in_array($operator, self::OPERATORS, true)) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid operator. Valid operators: %s', $operator, implode(', ', self::OPERATORS))); } - $this->operator = $operator; - parent::__construct($field, $dqlAlias); } diff --git a/src/Condition/Equals.php b/src/Condition/Equals.php index 13b9008..56f7ed1 100644 --- a/src/Condition/Equals.php +++ b/src/Condition/Equals.php @@ -4,7 +4,7 @@ use Purist\Specification\Doctrine\Exception\InvalidArgumentException; -class Equals extends Comparison +readonly class Equals extends Comparison { /** * @throws InvalidArgumentException diff --git a/src/Condition/EqualsProperty.php b/src/Condition/EqualsProperty.php index 5eebf0e..3559dbd 100644 --- a/src/Condition/EqualsProperty.php +++ b/src/Condition/EqualsProperty.php @@ -6,7 +6,7 @@ use Doctrine\ORM\QueryBuilder; use Purist\Specification\Doctrine\Exception\InvalidArgumentException; -class EqualsProperty extends Comparison +readonly class EqualsProperty extends Comparison { /** * @throws InvalidArgumentException diff --git a/src/Condition/GreaterThan.php b/src/Condition/GreaterThan.php index 0bfc153..32ebba1 100644 --- a/src/Condition/GreaterThan.php +++ b/src/Condition/GreaterThan.php @@ -4,7 +4,7 @@ use Purist\Specification\Doctrine\Exception\InvalidArgumentException; -class GreaterThan extends Comparison +readonly class GreaterThan extends Comparison { /** * @throws InvalidArgumentException diff --git a/src/Condition/GreaterThanOrEquals.php b/src/Condition/GreaterThanOrEquals.php index a237aa0..1a6cbd0 100644 --- a/src/Condition/GreaterThanOrEquals.php +++ b/src/Condition/GreaterThanOrEquals.php @@ -4,7 +4,7 @@ use Purist\Specification\Doctrine\Exception\InvalidArgumentException; -class GreaterThanOrEquals extends Comparison +readonly class GreaterThanOrEquals extends Comparison { /** * @throws InvalidArgumentException diff --git a/src/Condition/In.php b/src/Condition/In.php index 17bcb4f..ec087d6 100644 --- a/src/Condition/In.php +++ b/src/Condition/In.php @@ -5,7 +5,7 @@ use Doctrine\ORM\QueryBuilder; use Purist\Specification\Doctrine\AbstractSpecification; -class In extends AbstractSpecification +readonly class In extends AbstractSpecification { public function __construct(string $field, protected mixed $value, ?string $dqlAlias = null) { diff --git a/src/Condition/IsInstanceOf.php b/src/Condition/IsInstanceOf.php index 066d939..5c1053b 100644 --- a/src/Condition/IsInstanceOf.php +++ b/src/Condition/IsInstanceOf.php @@ -5,7 +5,7 @@ use Doctrine\ORM\QueryBuilder; use Purist\Specification\Doctrine\AbstractSpecification; -class IsInstanceOf extends AbstractSpecification +readonly class IsInstanceOf extends AbstractSpecification { public function __construct(string $field, private string $className, ?string $dqlAlias = null) { diff --git a/src/Condition/IsMemberOf.php b/src/Condition/IsMemberOf.php index fa33a60..357462e 100644 --- a/src/Condition/IsMemberOf.php +++ b/src/Condition/IsMemberOf.php @@ -5,7 +5,7 @@ use Doctrine\ORM\QueryBuilder; use Purist\Specification\Doctrine\AbstractSpecification; -class IsMemberOf extends AbstractSpecification +readonly class IsMemberOf extends AbstractSpecification { public function __construct(string $field, private string $className, ?string $dqlAlias = null) { diff --git a/src/Condition/IsNotNull.php b/src/Condition/IsNotNull.php index 8351e1b..c3912b5 100644 --- a/src/Condition/IsNotNull.php +++ b/src/Condition/IsNotNull.php @@ -3,8 +3,9 @@ namespace Purist\Specification\Doctrine\Condition; use Doctrine\ORM\QueryBuilder; +use Purist\Specification\Doctrine\AbstractSpecification; -class IsNotNull extends IsNull +readonly class IsNotNull extends AbstractSpecification { #[\Override] public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): string diff --git a/src/Condition/IsNull.php b/src/Condition/IsNull.php index 99a7268..0fa1cec 100644 --- a/src/Condition/IsNull.php +++ b/src/Condition/IsNull.php @@ -5,7 +5,7 @@ use Doctrine\ORM\QueryBuilder; use Purist\Specification\Doctrine\AbstractSpecification; -class IsNull extends AbstractSpecification +readonly class IsNull extends AbstractSpecification { #[\Override] public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): string diff --git a/src/Condition/LessThan.php b/src/Condition/LessThan.php index 7e572ec..57f5f8e 100644 --- a/src/Condition/LessThan.php +++ b/src/Condition/LessThan.php @@ -4,7 +4,7 @@ use Purist\Specification\Doctrine\Exception\InvalidArgumentException; -class LessThan extends Comparison +readonly class LessThan extends Comparison { /** * @throws InvalidArgumentException diff --git a/src/Condition/LessThanOrEquals.php b/src/Condition/LessThanOrEquals.php index 7c7c1e8..0795d12 100644 --- a/src/Condition/LessThanOrEquals.php +++ b/src/Condition/LessThanOrEquals.php @@ -4,7 +4,7 @@ use Purist\Specification\Doctrine\Exception\InvalidArgumentException; -class LessThanOrEquals extends Comparison +readonly class LessThanOrEquals extends Comparison { /** * @throws InvalidArgumentException diff --git a/src/Condition/Like.php b/src/Condition/Like.php index 41bce67..30e7ce0 100644 --- a/src/Condition/Like.php +++ b/src/Condition/Like.php @@ -4,12 +4,10 @@ use Purist\Specification\Doctrine\Exception\InvalidArgumentException; -class Like extends Comparison +readonly class Like extends Comparison { public const string CONTAINS = '%%%s%%'; - public const string ENDS_WITH = '%%%s'; - public const string STARTS_WITH = '%s%%'; /** diff --git a/src/Condition/NotEquals.php b/src/Condition/NotEquals.php index fc2898a..f0d1be5 100644 --- a/src/Condition/NotEquals.php +++ b/src/Condition/NotEquals.php @@ -4,7 +4,7 @@ use Purist\Specification\Doctrine\Exception\InvalidArgumentException; -class NotEquals extends Comparison +readonly class NotEquals extends Comparison { /** * @throws InvalidArgumentException diff --git a/src/Condition/NotIn.php b/src/Condition/NotIn.php index cfb7631..554b551 100644 --- a/src/Condition/NotIn.php +++ b/src/Condition/NotIn.php @@ -3,9 +3,15 @@ namespace Purist\Specification\Doctrine\Condition; use Doctrine\ORM\QueryBuilder; +use Purist\Specification\Doctrine\AbstractSpecification; -class NotIn extends In +readonly class NotIn extends AbstractSpecification { + public function __construct(string $field, protected mixed $value, ?string $dqlAlias = null) + { + parent::__construct($field, $dqlAlias); + } + #[\Override] public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): string { @@ -18,7 +24,6 @@ public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): st ); } - #[\Override] protected function generateParameterName(QueryBuilder $queryBuilder): string { return sprintf('not_in_%d', count($queryBuilder->getParameters())); diff --git a/src/Logic/Not.php b/src/Logic/Not.php index d410bc2..a269cb8 100644 --- a/src/Logic/Not.php +++ b/src/Logic/Not.php @@ -18,7 +18,8 @@ public function __construct(private SpecificationInterface $parent) public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): ?string { $filter = $this->parent->modify($queryBuilder, $dqlAlias); - if (null === $filter || '' === $filter || '0' === $filter) { + + if (null === $filter) { return ''; } diff --git a/src/Query/GroupBy.php b/src/Query/GroupBy.php index b533b35..04014b3 100644 --- a/src/Query/GroupBy.php +++ b/src/Query/GroupBy.php @@ -9,23 +9,33 @@ /** * @author Kyle Tucker */ -class GroupBy extends AbstractSpecification +readonly class GroupBy extends AbstractSpecification { public const string GROUP_BY = 'groupBy'; public const string ADD_GROUP_BY = 'addGroupBy'; - - /** @var string[] */ - protected static array $types = [self::GROUP_BY, self::ADD_GROUP_BY]; - protected string $type; + /** + * @var array + */ + protected const array TYPES = [self::GROUP_BY, self::ADD_GROUP_BY]; /** * @throws InvalidArgumentException */ - public function __construct(string $field, string $type = self::ADD_GROUP_BY, ?string $dqlAlias = null) + public function __construct(string $field, protected string $type = self::ADD_GROUP_BY, ?string $dqlAlias = null) { parent::__construct($field, $dqlAlias); - $this->setType($type); + if (!in_array($type, self::TYPES, true)) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::TYPES))); + } + } + + /** + * @throws InvalidArgumentException + */ + public function setType(string $type): self + { + return new self($this->field, $type, $this->dqlAlias); } #[\Override] @@ -35,16 +45,4 @@ public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): ?s return null; } - - /** - * @throws InvalidArgumentException - */ - public function setType(string $type): void - { - if (!in_array($type, self::$types, true)) { - throw new InvalidArgumentException(sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::$types))); - } - - $this->type = $type; - } } diff --git a/src/Query/Having.php b/src/Query/Having.php index d78e343..aeb4d47 100644 --- a/src/Query/Having.php +++ b/src/Query/Having.php @@ -7,26 +7,26 @@ use Purist\Specification\Doctrine\SpecificationInterface; /** - * @author Kyle Tucker + * @author Kyle Tucker */ -class Having implements SpecificationInterface +readonly class Having implements SpecificationInterface { public const string HAVING = 'having'; public const string AND_HAVING = 'andHaving'; public const string OR_HAVING = 'orHaving'; - /** * @var array */ - protected static array $types = [self::HAVING, self::AND_HAVING, self::OR_HAVING]; - protected string $type; + protected const array TYPES = [self::HAVING, self::AND_HAVING, self::OR_HAVING]; /** * @throws InvalidArgumentException */ - public function __construct(protected SpecificationInterface $specification, string $type = self::AND_HAVING) + public function __construct(protected SpecificationInterface $specification, protected string $type = self::AND_HAVING) { - $this->setType($type); + if (!in_array($type, self::TYPES, true)) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::TYPES))); + } } #[\Override] @@ -46,12 +46,8 @@ public function isSatisfiedBy(mixed $value): bool /** * @throws InvalidArgumentException */ - public function setType(string $type): void + public function setType(string $type): self { - if (!in_array($type, self::$types, true)) { - throw new InvalidArgumentException(sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::$types))); - } - - $this->type = $type; + return new self($this->specification, $type); } } diff --git a/src/Query/IndexBy.php b/src/Query/IndexBy.php index 9380ca8..ed5039a 100644 --- a/src/Query/IndexBy.php +++ b/src/Query/IndexBy.php @@ -9,7 +9,7 @@ /** * IndexBy will modify the query-builder, so you can specify INDEX BY-statements. */ -class IndexBy extends AbstractSpecification +readonly class IndexBy extends AbstractSpecification { /** * @throws QueryException diff --git a/src/Query/InnerJoin.php b/src/Query/InnerJoin.php index 18711d0..229e745 100644 --- a/src/Query/InnerJoin.php +++ b/src/Query/InnerJoin.php @@ -4,15 +4,13 @@ use Purist\Specification\Doctrine\Exception\InvalidArgumentException; -class InnerJoin extends Join +readonly class InnerJoin extends Join { /** * @throws InvalidArgumentException */ public function __construct(string $field, string $newAlias, ?string $dqlAlias = null) { - parent::__construct($field, $newAlias, $dqlAlias); - - $this->setType(self::INNER_JOIN); + parent::__construct($field, $newAlias, $dqlAlias, self::INNER_JOIN); } } diff --git a/src/Query/Join.php b/src/Query/Join.php index de28d25..1f5f16a 100644 --- a/src/Query/Join.php +++ b/src/Query/Join.php @@ -7,39 +7,33 @@ use Purist\Specification\Doctrine\Exception\InvalidArgumentException; use Purist\Specification\Doctrine\SpecificationInterface; -class Join extends AbstractSpecification +readonly class Join extends AbstractSpecification { public const string JOIN = 'join'; public const string LEFT_JOIN = 'leftJoin'; public const string INNER_JOIN = 'innerJoin'; - /** * @var array */ - protected static array $types = [self::JOIN, self::LEFT_JOIN, self::INNER_JOIN]; - - private ?string $conditionType = null; - private string|SpecificationInterface|null $condition = null; - private ?string $indexedBy = null; - private string $type = self::JOIN; - - public function __construct(string $field, private readonly string $newAlias, ?string $dqlAlias = null) - { - parent::__construct($field, $dqlAlias); - } + protected const array TYPES = [self::JOIN, self::LEFT_JOIN, self::INNER_JOIN]; /** * @throws InvalidArgumentException */ - public function setType(string $type): static - { - if (!in_array($type, self::$types, true)) { - throw new InvalidArgumentException(sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::$types))); + public function __construct( + string $field, + private string $newAlias, + ?string $dqlAlias = null, + private string $type = self::JOIN, + private string|SpecificationInterface|null $condition = null, + private ?string $conditionType = null, + private ?string $indexedBy = null, + ) { + if (!in_array($type, self::TYPES, true)) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::TYPES))); } - $this->type = $type; - - return $this; + parent::__construct($field, $dqlAlias); } #[\Override] @@ -62,32 +56,66 @@ public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): ?s } /** - * Set the condition type to be used on the join (WITH/ON). + * @throws InvalidArgumentException */ - public function setConditionType(?string $conditionType): static + public function setType(string $type): self { - $this->conditionType = $conditionType; + return new self( + $this->field, + $this->newAlias, + $this->dqlAlias, + $type, + $this->condition, + $this->conditionType, + $this->indexedBy, + ); + } - return $this; + /** + * Set the condition type to be used on the join (WITH/ON). + */ + public function setConditionType(?string $conditionType): self + { + return new self( + $this->field, + $this->newAlias, + $this->dqlAlias, + $this->type, + $this->condition, + $conditionType, + $this->indexedBy, + ); } /** * Set the condition to be used for the join statement. */ - public function setCondition(SpecificationInterface|string|null $condition): static + public function setCondition(SpecificationInterface|string|null $condition): self { - $this->condition = $condition; - - return $this; + return new self( + $this->field, + $this->newAlias, + $this->dqlAlias, + $this->type, + $condition, + $this->conditionType, + $this->indexedBy, + ); } /** * Set the property which will be used as index for the returned collection. */ - public function setIndexedBy(?string $indexedBy): static + public function setIndexedBy(?string $indexedBy): self { - $this->indexedBy = $indexedBy; - - return $this; + return new self( + $this->field, + $this->newAlias, + $this->dqlAlias, + $this->type, + $this->condition, + $this->conditionType, + $indexedBy, + ); } } diff --git a/src/Query/LeftJoin.php b/src/Query/LeftJoin.php index c562b71..029aba5 100644 --- a/src/Query/LeftJoin.php +++ b/src/Query/LeftJoin.php @@ -4,15 +4,13 @@ use Purist\Specification\Doctrine\Exception\InvalidArgumentException; -class LeftJoin extends Join +readonly class LeftJoin extends Join { /** * @throws InvalidArgumentException */ public function __construct(string $field, string $newAlias, ?string $dqlAlias = null) { - parent::__construct($field, $newAlias, $dqlAlias); - - $this->setType(self::LEFT_JOIN); + parent::__construct($field, $newAlias, $dqlAlias, self::LEFT_JOIN); } } diff --git a/src/Query/OrderBy.php b/src/Query/OrderBy.php index 8ea5918..ae87a58 100644 --- a/src/Query/OrderBy.php +++ b/src/Query/OrderBy.php @@ -6,29 +6,26 @@ use Purist\Specification\Doctrine\AbstractSpecification; use Purist\Specification\Doctrine\Exception\InvalidArgumentException; -class OrderBy extends AbstractSpecification +readonly class OrderBy extends AbstractSpecification { public const string ASC = 'ASC'; public const string DESC = 'DESC'; - protected ?string $order; /** * @var array */ - private static array $validOrder = [self::ASC, self::DESC]; + private const array VALID_ORDERS = [self::ASC, self::DESC]; /** * @throws InvalidArgumentException */ - public function __construct(string $field, ?string $order = null, ?string $dqlAlias = null) + public function __construct(string $field, private ?string $order = null, ?string $dqlAlias = null) { $order = null !== $order && '' !== $order && '0' !== $order ? strtoupper($order) : self::ASC; - if (!in_array($order, self::$validOrder, true)) { + if (!in_array($order, self::VALID_ORDERS, true)) { throw new InvalidArgumentException(); } - $this->order = $order; - parent::__construct($field, $dqlAlias); } diff --git a/src/Query/Select.php b/src/Query/Select.php index 5a998ee..c3f5546 100644 --- a/src/Query/Select.php +++ b/src/Query/Select.php @@ -9,24 +9,25 @@ /** * Select will modify the query-builder, so you can specify SELECT-statements. */ -class Select implements SpecificationInterface +readonly class Select implements SpecificationInterface { public const string SELECT = 'select'; public const string ADD_SELECT = 'addSelect'; /** * @var array */ - protected static array $types = [self::SELECT, self::ADD_SELECT]; - protected string $type; + protected const array TYPES = [self::SELECT, self::ADD_SELECT]; /** * @param string|array $select * * @throws InvalidArgumentException */ - public function __construct(protected string|array $select, string $type = self::ADD_SELECT) + public function __construct(protected string|array $select, private string $type = self::ADD_SELECT) { - $this->setType($type); + if (!in_array($type, self::TYPES, true)) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::TYPES))); + } } #[\Override] @@ -40,13 +41,9 @@ public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): ?s /** * @throws InvalidArgumentException */ - public function setType(string $type): void + public function setType(string $type): self { - if (!in_array($type, self::$types, true)) { - throw new InvalidArgumentException(sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::$types))); - } - - $this->type = $type; + return new self($this->select, $type); } #[\Override] diff --git a/src/Result/AsArray.php b/src/Result/AsArray.php index f045802..e1690b5 100644 --- a/src/Result/AsArray.php +++ b/src/Result/AsArray.php @@ -7,7 +7,7 @@ /** * Hydrate results as array instead of objects. */ -class AsArray implements ModifierInterface +readonly class AsArray implements ModifierInterface { /** * Modify the query (e.g. select more fields/relations). diff --git a/src/Result/ModifierCollection.php b/src/Result/ModifierCollection.php index 42bc877..b774166 100644 --- a/src/Result/ModifierCollection.php +++ b/src/Result/ModifierCollection.php @@ -30,18 +30,18 @@ public function __construct(mixed ...$modifiers) } /** - * @param ModifierInterface $value + * @param ModifierInterface $element * * @throws InvalidArgumentException */ #[\Override] - public function add(mixed $value): void + public function add(mixed $element): void { - if (!$value instanceof ModifierInterface) { - throw new InvalidArgumentException(sprintf('"%s" does not implement "%s"!', (is_object($value)) ? $value::class : $value, ModifierInterface::class)); + if (!$element instanceof ModifierInterface) { + throw new InvalidArgumentException(sprintf('"%s" does not implement "%s"!', (is_object($element)) ? $element::class : $element, ModifierInterface::class)); } - parent::add($value); + parent::add($element); } /** diff --git a/src/Specification.php b/src/Specification.php index f839995..61fbf0b 100644 --- a/src/Specification.php +++ b/src/Specification.php @@ -19,11 +19,13 @@ class Specification extends ArrayCollection implements SpecificationInterface /** * @var array */ - protected static array $types = [self::OR_X, self::AND_X]; + protected const array TYPES = [self::OR_X, self::AND_X]; private string $type = self::AND_X; /** * @param SpecificationInterface[] $elements + * + * @throws InvalidArgumentException */ public function __construct(array $elements = []) { @@ -33,18 +35,18 @@ public function __construct(array $elements = []) } /** - * @param SpecificationInterface $value + * @param SpecificationInterface $element * * @throws InvalidArgumentException */ #[\Override] - public function add(mixed $value): void + public function add(mixed $element): void { - if (!$value instanceof SpecificationInterface) { - throw new InvalidArgumentException(sprintf('"%s" does not implement "%s"!', (is_object($value)) ? $value::class : $value, SpecificationInterface::class)); + if (!$element instanceof SpecificationInterface) { + throw new InvalidArgumentException(sprintf('"%s" does not implement "%s"!', (is_object($element)) ? $element::class : $element, SpecificationInterface::class)); } - parent::add($value); + parent::add($element); } #[\Override] @@ -60,11 +62,14 @@ public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): ?s return $queryBuilder->expr()->{$this->type}(...$result); } + /** + * @throws \Exception + */ #[\Override] public function isSatisfiedBy(mixed $value): bool { /** @var SpecificationInterface $child */ - foreach ($this as $child) { + foreach ($this->getIterator() as $child) { if ($child->isSatisfiedBy($value)) { continue; } @@ -76,12 +81,17 @@ public function isSatisfiedBy(mixed $value): bool } /** - * @param SpecificationInterface[] $children + * @param array $children + * + * @throws InvalidArgumentException */ protected function setChildren(array $children): static { $this->clear(); - array_map($this->add(...), $children); + + foreach ($children as $child) { + $this->add($child); + } return $this; } @@ -93,8 +103,8 @@ protected function setChildren(array $children): static */ protected function setType(string $type): static { - if (!in_array($type, self::$types, true)) { - $message = sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::$types)); + if (!in_array($type, self::TYPES, true)) { + $message = sprintf('"%s" is not a valid type! Valid types: %s', $type, implode(', ', self::TYPES)); throw new InvalidArgumentException($message); } From 849fe4ee1b9ea7126aaed8b56e956feb409ee22d Mon Sep 17 00:00:00 2001 From: Nicholas Ruunu Date: Tue, 2 Jul 2024 03:39:54 +0200 Subject: [PATCH 08/10] Add declare strict --- src/AbstractSpecification.php | 2 ++ src/Condition/Between.php | 2 ++ src/Condition/Comparison.php | 2 ++ src/Condition/Equals.php | 2 ++ src/Condition/EqualsProperty.php | 2 ++ src/Condition/GreaterThan.php | 2 ++ src/Condition/GreaterThanOrEquals.php | 2 ++ src/Condition/In.php | 2 ++ src/Condition/IsInstanceOf.php | 2 ++ src/Condition/IsMemberOf.php | 2 ++ src/Condition/IsNotNull.php | 2 ++ src/Condition/IsNull.php | 2 ++ src/Condition/LessThan.php | 2 ++ src/Condition/LessThanOrEquals.php | 2 ++ src/Condition/Like.php | 2 ++ src/Condition/NotEquals.php | 2 ++ src/Condition/NotIn.php | 2 ++ src/Exception/InvalidArgumentException.php | 2 ++ src/Exception/LogicException.php | 2 ++ src/Logic/AndX.php | 2 ++ src/Logic/Composite.php | 2 ++ src/Logic/Not.php | 2 ++ src/Logic/OrX.php | 2 ++ src/Query/GroupBy.php | 2 ++ src/Query/Having.php | 2 ++ src/Query/IndexBy.php | 2 ++ src/Query/InnerJoin.php | 2 ++ src/Query/Join.php | 2 ++ src/Query/LeftJoin.php | 2 ++ src/Query/OrderBy.php | 2 ++ src/Query/Select.php | 2 ++ src/Result/AsArray.php | 2 ++ src/Result/ModifierCollection.php | 2 ++ src/Result/ModifierInterface.php | 2 ++ src/Specification.php | 4 +++- src/SpecificationAwareInterface.php | 2 ++ src/SpecificationInterface.php | 2 ++ src/SpecificationRepositoryTrait.php | 2 ++ 38 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/AbstractSpecification.php b/src/AbstractSpecification.php index 3fa85a7..c68db28 100644 --- a/src/AbstractSpecification.php +++ b/src/AbstractSpecification.php @@ -1,5 +1,7 @@ expr()->{$this->type}(...$result); + return (string) $queryBuilder->expr()->{$this->type}(...$result); } /** diff --git a/src/SpecificationAwareInterface.php b/src/SpecificationAwareInterface.php index 26f6a32..4cebdc8 100644 --- a/src/SpecificationAwareInterface.php +++ b/src/SpecificationAwareInterface.php @@ -1,5 +1,7 @@ Date: Wed, 3 Jul 2024 10:51:12 +0200 Subject: [PATCH 09/10] Add phpspec coverage --- .gitignore | 2 +- Dockerfile | 6 ++++++ composer.json | 5 +++-- docker-compose.yaml | 2 +- phpspec.yml | 9 +++++++++ 5 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 Dockerfile diff --git a/.gitignore b/.gitignore index 5a2f8fd..bcf29c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -build/ +coverage/ vendor/ .php-cs-fixer.cache composer.lock diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..744a37c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM php:8.3-cli-alpine +RUN apk add --no-cache --update --virtual .build-deps $PHPIZE_DEPS linux-headers \ + && pecl install xdebug-3.3.2 \ + && docker-php-ext-enable xdebug \ + && apk del -f .build-deps \ + && echo "xdebug.mode=coverage" >> $PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini diff --git a/composer.json b/composer.json index 833cc06..6656720 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "rikbruil/doctrine-specification", + "name": "purist/doctrine-specification", "description": "Doctrine Specification pattern for building queries dynamically and with re-usable classes for composition.", "type": "library", "keywords": ["doctrine", "repository", "specification"], @@ -27,7 +27,8 @@ "phpspec/phpspec": "^7.5", "rector/rector": "^1.1", "friendsofphp/php-cs-fixer": "^3.59", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.11", + "friends-of-phpspec/phpspec-code-coverage": "^6.3" }, "extra": { "branch-alias": { diff --git a/docker-compose.yaml b/docker-compose.yaml index b569b94..d992c97 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,6 +1,6 @@ services: php: &php - image: php:8.3-cli-alpine + build: . volumes: - .:/app working_dir: /app diff --git a/phpspec.yml b/phpspec.yml index 572f1fe..dc0a833 100644 --- a/phpspec.yml +++ b/phpspec.yml @@ -2,3 +2,12 @@ suites: main: namespace: Purist\Specification\Doctrine psr4_prefix: Purist\Specification\Doctrine +extensions: + FriendsOfPhpSpec\PhpSpec\CodeCoverage\CodeCoverageExtension: + format: + - text + - clover + - html + output: + html: coverage/html + clover: coverage/clover.xml From a8649c94ccfce668d8b3ae15ec1c26b6cdea67ef Mon Sep 17 00:00:00 2001 From: Nicholas Ruunu Date: Wed, 3 Jul 2024 10:51:32 +0200 Subject: [PATCH 10/10] Fix 100% coverage --- spec/Query/GroupBySpec.php | 22 +++++++++------------- spec/SpecificationRepositorySpec.php | 2 +- src/Query/GroupBy.php | 8 -------- src/SpecificationRepositoryTrait.php | 2 +- 4 files changed, 11 insertions(+), 23 deletions(-) diff --git a/spec/Query/GroupBySpec.php b/spec/Query/GroupBySpec.php index 90cc740..c2c80cf 100644 --- a/spec/Query/GroupBySpec.php +++ b/spec/Query/GroupBySpec.php @@ -9,40 +9,36 @@ class GroupBySpec extends ObjectBehavior { - private string $alias = 'a'; - - private string $field = 'foo'; + private string $field = 'a.foo'; public function it_should_support_anything(): void { - $this->beConstructedWith($this->field, GroupBy::ADD_GROUP_BY, $this->alias); + $this->beConstructedWith($this->field, GroupBy::ADD_GROUP_BY); $this->isSatisfiedBy('foo')->shouldReturn(true); } public function it_calls_groupBy_on_query_builder(QueryBuilder $queryBuilder): void { - $this->beConstructedWith($this->field, GroupBy::GROUP_BY, $this->alias); + $this->beConstructedWith($this->field, GroupBy::GROUP_BY); - $arg = sprintf('%s.%s', $this->alias, $this->field); - $queryBuilder->groupBy($arg)->shouldBeCalled()->willReturn($queryBuilder); + $queryBuilder->groupBy($this->field)->shouldBeCalled()->willReturn($queryBuilder); - $this->modify($queryBuilder, $this->alias); + $this->modify($queryBuilder); } public function it_calls_addGroupBy_on_query_builder(QueryBuilder $queryBuilder): void { - $this->beConstructedWith($this->field, GroupBy::ADD_GROUP_BY, $this->alias); + $this->beConstructedWith($this->field, GroupBy::ADD_GROUP_BY); - $arg = sprintf('%s.%s', $this->alias, $this->field); - $queryBuilder->addGroupBy($arg)->shouldBeCalled()->willReturn($queryBuilder); + $queryBuilder->addGroupBy($this->field)->shouldBeCalled()->willReturn($queryBuilder); - $this->modify($queryBuilder, $this->alias); + $this->modify($queryBuilder); } public function it_throws_exception_when_setting_illegal_type(): void { - $this->beConstructedWith($this->field, 'foo', $this->alias); + $this->beConstructedWith($this->field, 'foo'); $this->shouldThrow(InvalidArgumentException::class)->duringInstantiation(); } } diff --git a/spec/SpecificationRepositorySpec.php b/spec/SpecificationRepositorySpec.php index 37a4c52..536a90b 100644 --- a/spec/SpecificationRepositorySpec.php +++ b/spec/SpecificationRepositorySpec.php @@ -92,7 +92,7 @@ public function it_should_accept_specification_with_only_query_modifiers( $specification->modify($queryBuilder, $this->dqlAlias, Argument::any())->shouldBeCalled(); $specification->isSatisfiedBy(Argument::any())->willReturn(true); - $specification->modify($queryBuilder, $this->dqlAlias, Argument::any())->willReturn(''); + $specification->modify($queryBuilder, $this->dqlAlias, Argument::any())->willReturn(null); $this->match($specification); } diff --git a/src/Query/GroupBy.php b/src/Query/GroupBy.php index e434e0a..3732706 100644 --- a/src/Query/GroupBy.php +++ b/src/Query/GroupBy.php @@ -32,14 +32,6 @@ public function __construct(string $field, protected string $type = self::ADD_GR } } - /** - * @throws InvalidArgumentException - */ - public function setType(string $type): self - { - return new self($this->field, $type, $this->dqlAlias); - } - #[\Override] public function modify(QueryBuilder $queryBuilder, ?string $dqlAlias = null): ?string { diff --git a/src/SpecificationRepositoryTrait.php b/src/SpecificationRepositoryTrait.php index 4ffe5f6..faa97d5 100644 --- a/src/SpecificationRepositoryTrait.php +++ b/src/SpecificationRepositoryTrait.php @@ -40,7 +40,7 @@ private function modifyQueryBuilder(QueryBuilder $queryBuilder, SpecificationInt { $condition = $specification->modify($queryBuilder, $this->dqlAlias); - if (null === $condition || '' === $condition || '0' === $condition) { + if (null === $condition) { return; }