├── .php-cs-fixer.dist.php ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── UPGRADE.md ├── composer.json ├── docs ├── 0-0-symfony.md ├── 0-1-zend1.md ├── 0-2-zend2.md ├── 0-3-laravel.md ├── 0-usage.md └── 1-creatingSpecs.md ├── phpstan.neon.dist ├── src ├── DBALTypesResolver.php ├── DQLContextResolver.php ├── Exception │ ├── InvalidArgumentException.php │ ├── LogicException.php │ ├── NoResultException.php │ ├── NonUniqueResultException.php │ ├── OperandNotExecuteException.php │ ├── PlatformFunctionExecutorException.php │ └── UnexpectedResultException.php ├── Filter │ ├── Comparison.php │ ├── Equals.php │ ├── Filter.php │ ├── GreaterOrEqualThan.php │ ├── GreaterThan.php │ ├── In.php │ ├── InstanceOfX.php │ ├── IsEmpty.php │ ├── IsNotNull.php │ ├── IsNull.php │ ├── LessOrEqualThan.php │ ├── LessThan.php │ ├── Like.php │ ├── MemberOfX.php │ ├── NotEquals.php │ └── Satisfiable.php ├── Logic │ ├── AndX.php │ ├── LogicX.php │ ├── Not.php │ └── OrX.php ├── Operand │ ├── Addition.php │ ├── Alias.php │ ├── ArgumentToOperandConverter.php │ ├── Arithmetic.php │ ├── Division.php │ ├── Field.php │ ├── LikePattern.php │ ├── Multiplication.php │ ├── Operand.php │ ├── PlatformFunction.php │ ├── PlatformFunction │ │ ├── Avg.php │ │ ├── Count.php │ │ ├── DateAdd.php │ │ ├── DateSub.php │ │ ├── Executor │ │ │ ├── AbsExecutor.php │ │ │ ├── BitAndExecutor.php │ │ │ ├── BitOrExecutor.php │ │ │ ├── ConcatExecutor.php │ │ │ ├── CurrentDateExecutor.php │ │ │ ├── CurrentTimeExecutor.php │ │ │ ├── CurrentTimestampExecutor.php │ │ │ ├── DateDiffExecutor.php │ │ │ ├── IdentityExecutor.php │ │ │ ├── LengthExecutor.php │ │ │ ├── LocateExecutor.php │ │ │ ├── LowerExecutor.php │ │ │ ├── ModExecutor.php │ │ │ ├── PlatformFunctionExecutorRegistry.php │ │ │ ├── SizeExecutor.php │ │ │ ├── SqrtExecutor.php │ │ │ ├── SubstringExecutor.php │ │ │ └── UpperExecutor.php │ │ ├── Max.php │ │ ├── Min.php │ │ ├── Sum.php │ │ └── Trim.php │ ├── Subtraction.php │ ├── Value.php │ └── Values.php ├── Query │ ├── AbstractJoin.php │ ├── AbstractSelect.php │ ├── AddSelect.php │ ├── Distinct.php │ ├── GroupBy.php │ ├── Having.php │ ├── IndexBy.php │ ├── InnerJoin.php │ ├── Join.php │ ├── LeftJoin.php │ ├── Limit.php │ ├── Offset.php │ ├── OrderBy.php │ ├── QueryModifier.php │ ├── QueryModifierCollection.php │ ├── Select.php │ ├── SelectNew.php │ ├── Selection │ │ ├── AbstractSelectAs.php │ │ ├── ArgumentToSelectionConverter.php │ │ ├── SelectAs.php │ │ ├── SelectEntity.php │ │ ├── SelectHiddenAs.php │ │ └── Selection.php │ └── Slice.php ├── Repository │ ├── EntitySpecificationRepository.php │ ├── EntitySpecificationRepositoryInterface.php │ ├── EntitySpecificationRepositoryTrait.php │ └── RepositoryFactory.php ├── Result │ ├── AsArray.php │ ├── AsScalar.php │ ├── AsSingleScalar.php │ ├── Cache.php │ ├── ResultModifier.php │ ├── ResultModifierCollection.php │ └── RoundDateTime.php ├── Spec.php ├── Specification │ ├── BaseSpecification.php │ ├── CountOf.php │ └── Specification.php └── ValueConverter.php └── tests ├── DQLContextResolverSpec.php ├── Filter ├── EqualsSpec.php ├── GreaterOrEqualThanSpec.php ├── GreaterThanSpec.php ├── InSpec.php ├── InstanceOfXSpec.php ├── IsEmptySpec.php ├── IsNotNullSpec.php ├── IsNullSpec.php ├── LessOrEqualThanSpec.php ├── LessThanSpec.php ├── LikeSpec.php ├── MemberOfXSpec.php └── NotEqualsSpec.php ├── Game.php ├── Logic ├── AndXSpec.php ├── NotSpec.php └── OrXSpec.php ├── Operand ├── AdditionSpec.php ├── AliasSpec.php ├── ArgumentToOperandConverterSpec.php ├── DivisionSpec.php ├── FieldSpec.php ├── LikePatternSpec.php ├── MultiplicationSpec.php ├── PlatformFunction │ ├── AvgSpec.php │ ├── CountSpec.php │ ├── DateAddSpec.php │ ├── DateSubSpec.php │ ├── Executor │ │ ├── AbsExecutorSpec.php │ │ ├── BitAndExecutorSpec.php │ │ ├── BitOrExecutorSpec.php │ │ ├── ConcatExecutorSpec.php │ │ ├── CurrentDateExecutorSpec.php │ │ ├── CurrentTimeExecutorSpec.php │ │ ├── CurrentTimestampExecutorSpec.php │ │ ├── DateDiffExecutorSpec.php │ │ ├── IdentityExecutorSpec.php │ │ ├── LengthExecutorSpec.php │ │ ├── LocateExecutorSpec.php │ │ ├── LowerExecutorSpec.php │ │ ├── ModExecutorSpec.php │ │ ├── PlatformFunctionExecutorRegistrySpec.php │ │ ├── SizeExecutorSpec.php │ │ ├── SqrtExecutorSpec.php │ │ ├── SubstringExecutorSpec.php │ │ └── UpperExecutorSpec.php │ ├── MaxSpec.php │ ├── MinSpec.php │ ├── SumSpec.php │ └── TrimSpec.php ├── PlatformFunctionSpec.php ├── SubtractionSpec.php ├── ValueSpec.php └── ValuesSpec.php ├── Player.php ├── Query ├── AddSelectSpec.php ├── DistinctSpec.php ├── HavingSpec.php ├── IndexBySpec.php ├── InnerJoinSpec.php ├── JoinSpec.php ├── LeftJoinSpec.php ├── SelectNewSpec.php ├── SelectSpec.php ├── Selection │ ├── ArgumentToSelectionConverterSpec.php │ ├── SelectAsSpec.php │ ├── SelectEntitySpec.php │ └── SelectHiddenAsSpec.php └── SliceSpec.php ├── Repository └── EntitySpecificationRepositorySpec.php ├── Result ├── AsArraySpec.php ├── AsScalarSpec.php ├── AsSingleScalarSpec.php ├── CacheSpec.php └── RoundDateTimeSpec.php ├── SpecSpec.php └── Specification └── CountOfSpec.php /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | 8 | Kacper Gunia 9 | Peter Gribanov 10 | 11 | For the full copyright and license information, please view the LICENSE 12 | file that was distributed with this source code. 13 | EOF; 14 | 15 | $rules = [ 16 | '@PSR2' => true, 17 | '@Symfony' => true, 18 | '@Symfony:risky' => true, 19 | '@PhpCsFixer' => true, 20 | '@PhpCsFixer:risky' => true, 21 | '@PHP70Migration' => true, 22 | '@PHP70Migration:risky' => true, 23 | 'header_comment' => [ 24 | 'comment_type' => 'PHPDoc', 25 | 'header' => $header, 26 | ], 27 | 'array_syntax' => ['syntax' => 'short'], 28 | 'no_superfluous_phpdoc_tags' => false, 29 | 'single_line_throw' => false, 30 | 'blank_line_after_opening_tag' => false, 31 | 'ordered_imports' => [ 32 | 'sort_algorithm' => 'alpha', 33 | ], 34 | 'phpdoc_align' => [ 35 | 'tags' => ['param', 'return', 'throws', 'type', 'var'], 36 | ], 37 | 'phpdoc_types_order' => [ 38 | 'null_adjustment' => 'always_last', 39 | 'sort_algorithm' => 'none', 40 | ], 41 | 'native_constant_invocation' => false, 42 | 'native_function_invocation' => false, 43 | 'ordered_class_elements' => false, 44 | 'operator_linebreak' => [ 45 | 'position' => 'end', 46 | ], 47 | ]; 48 | 49 | $finder = PhpCsFixer\Finder::create() 50 | ->in(__DIR__.'/src') 51 | ->in(__DIR__.'/tests') 52 | ->notPath('bootstrap.php') 53 | ; 54 | 55 | return (new PhpCsFixer\Config()) 56 | ->setRules($rules) 57 | ->setFinder($finder) 58 | ->setRiskyAllowed(true) 59 | ->setUsingCache(true) 60 | ; 61 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributions are welcome! 2 | 3 | ## Quick guide: 4 | 5 | * Fork the repo. 6 | * Create branch, e.g. feature-foo or bugfix-bar. 7 | * Make changes. 8 | * If you are adding functionality or fixing a bug - add a spec! 9 | * Check if specs pass. 10 | 11 | ## Creating a new Specification 12 | 13 | If you have a new specification that you think should be a part of this repository, submit a PR and we will happily 14 | evaluate it. There are some things that you should include in your PR. 15 | 16 | * Your specification class under the appropriate namespace 17 | * A PHPSpec spec (Try to write one, we will help you if you have trouble) 18 | * A factory function 19 | 20 | ## Project's standards: 21 | 22 | * [PSR-0: Autoloading Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) 23 | * [PSR-1: Basic Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md) 24 | * [PSR-2: Coding Style Guide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) 25 | * [Symfony Coding Standards](http://symfony.com/doc/current/contributing/code/standards.html) 26 | * Keep the order of class elements: static properties, instance properties, constructor, destructor, static methods, instance methods, magic static methods, magic instance methods. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Tobias Nyholm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "happyr/doctrine-specification", 3 | "type": "library", 4 | "description": "Specification Pattern for your Doctrine repositories", 5 | "keywords": ["Doctrine", "Repository", "Specification"], 6 | "homepage": "http://developer.happyr.com/", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Tobias Nyholm", 11 | "email": "tobias@happyr.com" 12 | }, 13 | { 14 | "name": "Kacper Gunia", 15 | "email": "kacper@gunia.me" 16 | }, 17 | { 18 | "name": "Peter Gribanov", 19 | "email": "info@peter-gribanov.ru" 20 | } 21 | ], 22 | "require": { 23 | "php": ">=7.2", 24 | "doctrine/orm": "~2.5", 25 | "symfony/property-access": "^4.0 || ^5.2 || ^6.0 || ^7.0", 26 | "symfony/polyfill-php80": "^1.20" 27 | }, 28 | "require-dev": { 29 | "phpspec/phpspec": "~6.3 || ^7.0" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Happyr\\DoctrineSpecification\\": "src/" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "tests\\Happyr\\DoctrineSpecification\\": "tests/" 39 | } 40 | }, 41 | "extra": { 42 | "branch-alias": { 43 | "dev-master": "2.0-dev" 44 | } 45 | }, 46 | "scripts": { 47 | "test": "vendor/bin/phpspec run" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /docs/0-0-symfony.md: -------------------------------------------------------------------------------- 1 | # Integrating with frameworks - Symfony 2 | 3 | ## Replacing the default repository type 4 | 5 | Replacing Doctrine's default repository type with `Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository` is easy 6 | in Symfony. The Doctrine bundle provides a place in configuration to specify the new type. 7 | 8 | ```yml 9 | # app/config/config.yml 10 | doctrine: 11 | orm: 12 | default_repository_class: 'Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository' 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/0-1-zend1.md: -------------------------------------------------------------------------------- 1 | # Integrating with frameworks - Zend Framework 1 2 | 3 | ## Replacing the default repository type 4 | 5 | Doctrine integration with Zend Framework 1 is done manually. However, replacing the default repository type is still 6 | simple. In the bootstrap (or wherever Doctrine is configured in the system in question), use Doctrine configurations 7 | `setDefaultRepositoryClassName` method and provide an implementation of `Doctrine\Common\Persistence\ObjectRepository`. 8 | A basic implementation is provided with `Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository`. 9 | 10 | ```php 11 | // During Doctrine configuration 12 | $config->setDefaultRepositoryClassName(\Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository::class); 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/0-2-zend2.md: -------------------------------------------------------------------------------- 1 | # Integrating with frameworks - Zend Framework 2 and 3 2 | 3 | ## Replacing the default repository type 4 | 5 | Doctrine integration with Zend Framework 2 can be achieved using the `DoctrineORM` bundle. This bundle contains 6 | configuration options for the repository. To replace the default repository type, provide a class name of 7 | `Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository`. 8 | 9 | ```php 10 | // Application configuration 11 | use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository; 12 | 13 | return [ 14 | 'doctrine' => [ 15 | 'configuration' => [ 16 | 'orm_default' => [ 17 | 'default_repository_class' => EntitySpecificationRepository::class, 18 | ], 19 | ], 20 | ], 21 | ]; 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/0-3-laravel.md: -------------------------------------------------------------------------------- 1 | # Integrating with frameworks - Laravel 2 | 3 | ## Replacing the default repository type 4 | 5 | Replacing Doctrine's default repository type with `Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository` 6 | is easy in Laravel. The Doctrine bundle provides a place in configuration to specify the new type. 7 | 8 | ```php 9 | // doctrine.php 10 | use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository; 11 | 12 | return [ 13 | 'managers' => [ 14 | 'default' => [ 15 | 'repository' => EntitySpecificationRepository::class, 16 | ], 17 | ], 18 | ]; 19 | ``` 20 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 8 3 | paths: 4 | - src 5 | -------------------------------------------------------------------------------- /src/DBALTypesResolver.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification; 16 | 17 | use Doctrine\DBAL\Types\Type; 18 | 19 | final class DBALTypesResolver 20 | { 21 | /** 22 | * The map of supported Doctrine mapping types. 23 | * 24 | * @var array 25 | */ 26 | private static $typesMap = []; 27 | 28 | /** 29 | * Try get type for value. 30 | * 31 | * If type is not found return NULL. 32 | * 33 | * @param mixed $value 34 | * 35 | * @return Type|null 36 | */ 37 | public static function tryGetTypeForValue($value): ?Type 38 | { 39 | if (!is_object($value)) { 40 | return null; 41 | } 42 | 43 | // maybe it's a ValueObject 44 | 45 | // try get type name from types map 46 | $className = get_class($value); 47 | if (isset(self::$typesMap[$className])) { 48 | return Type::getType(self::$typesMap[$className]); 49 | } 50 | 51 | // use class name as type name 52 | $classNameParts = explode('\\', str_replace('_', '\\', $className)); 53 | $typeName = array_pop($classNameParts); 54 | 55 | if (null !== $typeName && array_key_exists($typeName, Type::getTypesMap())) { 56 | return Type::getType($typeName); 57 | } 58 | 59 | return null; 60 | } 61 | 62 | /** 63 | * Adds a custom type to the type map for resolve Value Object. 64 | * 65 | * @param string $name the name of the type 66 | * @param string $className the class name of the Value Object 67 | */ 68 | public static function addType(string $name, string $className): void 69 | { 70 | self::$typesMap[$className] = $name; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Exception; 16 | 17 | final class InvalidArgumentException extends \InvalidArgumentException 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Exception/LogicException.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Exception; 16 | 17 | final class LogicException extends \LogicException 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Exception/NoResultException.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Exception; 16 | 17 | final class NoResultException extends UnexpectedResultException 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Exception/NonUniqueResultException.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Exception; 16 | 17 | final class NonUniqueResultException extends UnexpectedResultException 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Exception/OperandNotExecuteException.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Exception; 16 | 17 | final class OperandNotExecuteException extends \RuntimeException 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Exception/PlatformFunctionExecutorException.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Exception; 16 | 17 | final class PlatformFunctionExecutorException extends \InvalidArgumentException 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Exception/UnexpectedResultException.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Exception; 16 | 17 | abstract class UnexpectedResultException extends \RuntimeException 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Filter/Equals.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Filter; 16 | 17 | use Happyr\DoctrineSpecification\Operand\Operand; 18 | 19 | final class Equals extends Comparison 20 | { 21 | /** 22 | * @param Operand|string $field 23 | * @param Operand|mixed $value 24 | * @param string|null $context 25 | */ 26 | public function __construct($field, $value, ?string $context = null) 27 | { 28 | parent::__construct(self::EQ, $field, $value, $context); 29 | } 30 | 31 | /** 32 | * @param mixed $field 33 | * @param mixed $value 34 | * 35 | * @return bool 36 | */ 37 | protected function compare($field, $value): bool 38 | { 39 | return $field === $value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Filter/Filter.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Filter; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | interface Filter 20 | { 21 | /** 22 | * @param QueryBuilder $qb 23 | * @param string $context 24 | * 25 | * @return string 26 | */ 27 | public function getFilter(QueryBuilder $qb, string $context): string; 28 | } 29 | -------------------------------------------------------------------------------- /src/Filter/GreaterOrEqualThan.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Filter; 16 | 17 | use Happyr\DoctrineSpecification\Operand\Operand; 18 | 19 | final class GreaterOrEqualThan extends Comparison 20 | { 21 | /** 22 | * @param Operand|string $field 23 | * @param Operand|mixed $value 24 | * @param string|null $context 25 | */ 26 | public function __construct($field, $value, ?string $context = null) 27 | { 28 | parent::__construct(self::GTE, $field, $value, $context); 29 | } 30 | 31 | /** 32 | * @param mixed $field 33 | * @param mixed $value 34 | * 35 | * @return bool 36 | */ 37 | protected function compare($field, $value): bool 38 | { 39 | return $field >= $value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Filter/GreaterThan.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Filter; 16 | 17 | use Happyr\DoctrineSpecification\Operand\Operand; 18 | 19 | final class GreaterThan extends Comparison 20 | { 21 | /** 22 | * @param Operand|string $field 23 | * @param Operand|mixed $value 24 | * @param string|null $context 25 | */ 26 | public function __construct($field, $value, ?string $context = null) 27 | { 28 | parent::__construct(self::GT, $field, $value, $context); 29 | } 30 | 31 | /** 32 | * @param mixed $field 33 | * @param mixed $value 34 | * 35 | * @return bool 36 | */ 37 | protected function compare($field, $value): bool 38 | { 39 | return $field > $value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Filter/InstanceOfX.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Filter; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\DQLContextResolver; 19 | 20 | final class InstanceOfX implements Filter, Satisfiable 21 | { 22 | /** 23 | * @var string 24 | */ 25 | private $value; 26 | 27 | /** 28 | * @var string|null 29 | */ 30 | private $context; 31 | 32 | /** 33 | * @param string $value 34 | * @param string|null $context 35 | */ 36 | public function __construct(string $value, ?string $context = null) 37 | { 38 | $this->value = $value; 39 | $this->context = $context; 40 | } 41 | 42 | /** 43 | * @param QueryBuilder $qb 44 | * @param string $context 45 | * 46 | * @return string 47 | */ 48 | public function getFilter(QueryBuilder $qb, string $context): string 49 | { 50 | if (null !== $this->context) { 51 | $context = sprintf('%s.%s', $context, $this->context); 52 | } 53 | 54 | $dqlAlias = DQLContextResolver::resolveAlias($qb, $context); 55 | 56 | return (string) $qb->expr()->isInstanceOf($dqlAlias, $this->value); 57 | } 58 | 59 | /** 60 | * {@inheritdoc} 61 | */ 62 | public function filterCollection(iterable $collection, ?string $context = null): iterable 63 | { 64 | foreach ($collection as $candidate) { 65 | if ($candidate instanceof $this->value) { 66 | yield $candidate; 67 | } 68 | } 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function isSatisfiedBy($candidate, ?string $context = null): bool 75 | { 76 | return $candidate instanceof $this->value; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Filter/IsEmpty.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Filter; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Operand\ArgumentToOperandConverter; 19 | use Happyr\DoctrineSpecification\Operand\Operand; 20 | 21 | final class IsEmpty implements Filter, Satisfiable 22 | { 23 | /** 24 | * @var Operand|string 25 | */ 26 | private $field; 27 | 28 | /** 29 | * @var string|null 30 | */ 31 | private $context; 32 | 33 | /** 34 | * @param Operand|string $field 35 | * @param string|null $context 36 | */ 37 | public function __construct($field, ?string $context = null) 38 | { 39 | $this->field = $field; 40 | $this->context = $context; 41 | } 42 | 43 | /** 44 | * @param QueryBuilder $qb 45 | * @param string $context 46 | * 47 | * @return string 48 | */ 49 | public function getFilter(QueryBuilder $qb, string $context): string 50 | { 51 | if (null !== $this->context) { 52 | $context = sprintf('%s.%s', $context, $this->context); 53 | } 54 | 55 | $field = ArgumentToOperandConverter::toField($this->field); 56 | 57 | return sprintf('%s IS EMPTY', $field->transform($qb, $context)); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function filterCollection(iterable $collection, ?string $context = null): iterable 64 | { 65 | $context = $this->resolveContext($context); 66 | $field = ArgumentToOperandConverter::toField($this->field); 67 | 68 | foreach ($collection as $candidate) { 69 | if (empty($field->execute($candidate, $context))) { 70 | yield $candidate; 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function isSatisfiedBy($candidate, ?string $context = null): bool 79 | { 80 | $context = $this->resolveContext($context); 81 | $field = ArgumentToOperandConverter::toField($this->field); 82 | 83 | return empty($field->execute($candidate, $context)); 84 | } 85 | 86 | /** 87 | * @param string|null $context 88 | * 89 | * @return string|null 90 | */ 91 | private function resolveContext(?string $context): ?string 92 | { 93 | if (null !== $this->context && null !== $context) { 94 | return sprintf('%s.%s', $context, $this->context); 95 | } 96 | 97 | if (null !== $this->context) { 98 | return $this->context; 99 | } 100 | 101 | return $context; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Filter/IsNotNull.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Filter; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Operand\ArgumentToOperandConverter; 19 | use Happyr\DoctrineSpecification\Operand\Operand; 20 | 21 | final class IsNotNull implements Filter, Satisfiable 22 | { 23 | /** 24 | * @var Operand|string 25 | */ 26 | private $field; 27 | 28 | /** 29 | * @var string|null 30 | */ 31 | private $context; 32 | 33 | /** 34 | * @param Operand|string $field 35 | * @param string|null $context 36 | */ 37 | public function __construct($field, ?string $context = null) 38 | { 39 | $this->field = $field; 40 | $this->context = $context; 41 | } 42 | 43 | /** 44 | * @param QueryBuilder $qb 45 | * @param string $context 46 | * 47 | * @return string 48 | */ 49 | public function getFilter(QueryBuilder $qb, string $context): string 50 | { 51 | if (null !== $this->context) { 52 | $context = sprintf('%s.%s', $context, $this->context); 53 | } 54 | 55 | $field = ArgumentToOperandConverter::toField($this->field); 56 | 57 | return (string) $qb->expr()->isNotNull($field->transform($qb, $context)); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function filterCollection(iterable $collection, ?string $context = null): iterable 64 | { 65 | $context = $this->resolveContext($context); 66 | $field = ArgumentToOperandConverter::toField($this->field); 67 | 68 | foreach ($collection as $candidate) { 69 | if (null !== $field->execute($candidate, $context)) { 70 | yield $candidate; 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function isSatisfiedBy($candidate, ?string $context = null): bool 79 | { 80 | $context = $this->resolveContext($context); 81 | $field = ArgumentToOperandConverter::toField($this->field); 82 | 83 | return null !== $field->execute($candidate, $context); 84 | } 85 | 86 | /** 87 | * @param string|null $context 88 | * 89 | * @return string|null 90 | */ 91 | private function resolveContext(?string $context): ?string 92 | { 93 | if (null !== $this->context && null !== $context) { 94 | return sprintf('%s.%s', $context, $this->context); 95 | } 96 | 97 | if (null !== $this->context) { 98 | return $this->context; 99 | } 100 | 101 | return $context; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Filter/IsNull.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Filter; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Operand\ArgumentToOperandConverter; 19 | use Happyr\DoctrineSpecification\Operand\Operand; 20 | 21 | final class IsNull implements Filter, Satisfiable 22 | { 23 | /** 24 | * @var Operand|string 25 | */ 26 | private $field; 27 | 28 | /** 29 | * @var string|null 30 | */ 31 | private $context; 32 | 33 | /** 34 | * @param Operand|string $field 35 | * @param string|null $context 36 | */ 37 | public function __construct($field, ?string $context = null) 38 | { 39 | $this->field = $field; 40 | $this->context = $context; 41 | } 42 | 43 | /** 44 | * @param QueryBuilder $qb 45 | * @param string $context 46 | * 47 | * @return string 48 | */ 49 | public function getFilter(QueryBuilder $qb, string $context): string 50 | { 51 | if (null !== $this->context) { 52 | $context = sprintf('%s.%s', $context, $this->context); 53 | } 54 | 55 | $field = ArgumentToOperandConverter::toField($this->field); 56 | 57 | return (string) $qb->expr()->isNull($field->transform($qb, $context)); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function filterCollection(iterable $collection, ?string $context = null): iterable 64 | { 65 | $context = $this->resolveContext($context); 66 | $field = ArgumentToOperandConverter::toField($this->field); 67 | 68 | foreach ($collection as $candidate) { 69 | if (null === $field->execute($candidate, $context)) { 70 | yield $candidate; 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function isSatisfiedBy($candidate, ?string $context = null): bool 79 | { 80 | $context = $this->resolveContext($context); 81 | $field = ArgumentToOperandConverter::toField($this->field); 82 | 83 | return null === $field->execute($candidate, $context); 84 | } 85 | 86 | /** 87 | * @param string|null $context 88 | * 89 | * @return string|null 90 | */ 91 | private function resolveContext(?string $context): ?string 92 | { 93 | if (null !== $this->context && null !== $context) { 94 | return sprintf('%s.%s', $context, $this->context); 95 | } 96 | 97 | if (null !== $this->context) { 98 | return $this->context; 99 | } 100 | 101 | return $context; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Filter/LessOrEqualThan.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Filter; 16 | 17 | use Happyr\DoctrineSpecification\Operand\Operand; 18 | 19 | final class LessOrEqualThan extends Comparison 20 | { 21 | /** 22 | * @param Operand|string $field 23 | * @param Operand|mixed $value 24 | * @param string|null $context 25 | */ 26 | public function __construct($field, $value, ?string $context = null) 27 | { 28 | parent::__construct(self::LTE, $field, $value, $context); 29 | } 30 | 31 | /** 32 | * @param mixed $field 33 | * @param mixed $value 34 | * 35 | * @return bool 36 | */ 37 | protected function compare($field, $value): bool 38 | { 39 | return $field <= $value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Filter/LessThan.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Filter; 16 | 17 | use Happyr\DoctrineSpecification\Operand\Operand; 18 | 19 | final class LessThan extends Comparison 20 | { 21 | /** 22 | * @param Operand|string $field 23 | * @param Operand|mixed $value 24 | * @param string|null $context 25 | */ 26 | public function __construct($field, $value, ?string $context = null) 27 | { 28 | parent::__construct(self::LT, $field, $value, $context); 29 | } 30 | 31 | /** 32 | * @param mixed $field 33 | * @param mixed $value 34 | * 35 | * @return bool 36 | */ 37 | protected function compare($field, $value): bool 38 | { 39 | return $field < $value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Filter/MemberOfX.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Filter; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Operand\ArgumentToOperandConverter; 19 | use Happyr\DoctrineSpecification\Operand\Operand; 20 | 21 | final class MemberOfX implements Filter 22 | { 23 | /** 24 | * @var Operand|string 25 | */ 26 | private $field; 27 | 28 | /** 29 | * @var Operand|string 30 | */ 31 | private $value; 32 | 33 | /** 34 | * @var string|null 35 | */ 36 | private $context; 37 | 38 | /** 39 | * @param Operand|mixed $value 40 | * @param Operand|string $field 41 | * @param string|null $context 42 | */ 43 | public function __construct($value, $field, ?string $context = null) 44 | { 45 | $this->value = $value; 46 | $this->field = $field; 47 | $this->context = $context; 48 | } 49 | 50 | /** 51 | * @param QueryBuilder $qb 52 | * @param string $context 53 | * 54 | * @return string 55 | */ 56 | public function getFilter(QueryBuilder $qb, string $context): string 57 | { 58 | if (null !== $this->context) { 59 | $context = sprintf('%s.%s', $context, $this->context); 60 | } 61 | 62 | $field = ArgumentToOperandConverter::toField($this->field); 63 | $value = ArgumentToOperandConverter::toValue($this->value); 64 | 65 | return (string) $qb->expr()->isMemberOf( 66 | $value->transform($qb, $context), 67 | $field->transform($qb, $context) 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Filter/NotEquals.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Filter; 16 | 17 | use Happyr\DoctrineSpecification\Operand\Operand; 18 | 19 | final class NotEquals extends Comparison 20 | { 21 | /** 22 | * @param Operand|string $field 23 | * @param Operand|mixed $value 24 | * @param string|null $context 25 | */ 26 | public function __construct($field, $value, ?string $context = null) 27 | { 28 | parent::__construct(self::NEQ, $field, $value, $context); 29 | } 30 | 31 | /** 32 | * @param mixed $field 33 | * @param mixed $value 34 | * 35 | * @return bool 36 | */ 37 | protected function compare($field, $value): bool 38 | { 39 | return $field !== $value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Filter/Satisfiable.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Filter; 16 | 17 | interface Satisfiable 18 | { 19 | /** 20 | * @param iterable $collection 21 | * @param string|null $context 22 | * 23 | * @return iterable 24 | * 25 | * @phpstan-template T 26 | * @phpstan-param iterable $collection 27 | * @phpstan-return iterable 28 | */ 29 | public function filterCollection(iterable $collection, ?string $context = null): iterable; 30 | 31 | /** 32 | * @param mixed[]|object $candidate 33 | * @param string|null $context 34 | * 35 | * @return bool 36 | */ 37 | public function isSatisfiedBy($candidate, ?string $context = null): bool; 38 | } 39 | -------------------------------------------------------------------------------- /src/Logic/AndX.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Logic; 16 | 17 | use Happyr\DoctrineSpecification\Filter\Filter; 18 | use Happyr\DoctrineSpecification\Query\QueryModifier; 19 | 20 | final class AndX extends LogicX 21 | { 22 | /** 23 | * @param Filter|QueryModifier ...$children 24 | */ 25 | public function __construct(...$children) 26 | { 27 | parent::__construct(self::AND_X, ...$children); 28 | } 29 | 30 | /** 31 | * Append an other specification with a logic AND. 32 | * 33 | * 34 | * $spec = Spec::andX(A, B); 35 | * $spec->andX(C); 36 | * 37 | * // We be the same as 38 | * $spec = Spec::andX(A, B, C); 39 | * 40 | * 41 | * @param Filter|QueryModifier $child 42 | */ 43 | public function andX($child): void 44 | { 45 | $this->append($child); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Logic/Not.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Logic; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Filter\Filter; 19 | use Happyr\DoctrineSpecification\Filter\Satisfiable; 20 | use Happyr\DoctrineSpecification\Query\QueryModifier; 21 | use Happyr\DoctrineSpecification\Specification\Specification; 22 | 23 | final class Not implements Specification 24 | { 25 | /** 26 | * @var Filter 27 | */ 28 | private $child; 29 | 30 | /** 31 | * @param Filter $expr 32 | */ 33 | public function __construct(Filter $expr) 34 | { 35 | $this->child = $expr; 36 | } 37 | 38 | /** 39 | * @param QueryBuilder $qb 40 | * @param string $context 41 | * 42 | * @return string 43 | */ 44 | public function getFilter(QueryBuilder $qb, string $context): string 45 | { 46 | return (string) $qb->expr()->not($this->child->getFilter($qb, $context)); 47 | } 48 | 49 | /** 50 | * @param QueryBuilder $qb 51 | * @param string $context 52 | */ 53 | public function modify(QueryBuilder $qb, string $context): void 54 | { 55 | if ($this->child instanceof QueryModifier) { 56 | $this->child->modify($qb, $context); 57 | } 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function filterCollection(iterable $collection, ?string $context = null): iterable 64 | { 65 | foreach ($collection as $candidate) { 66 | if (!$this->child instanceof Satisfiable || !$this->child->isSatisfiedBy($candidate, $context)) { 67 | yield $candidate; 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function isSatisfiedBy($candidate, ?string $context = null): bool 76 | { 77 | return !$this->child instanceof Satisfiable || !$this->child->isSatisfiedBy($candidate, $context); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Logic/OrX.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Logic; 16 | 17 | use Happyr\DoctrineSpecification\Filter\Filter; 18 | use Happyr\DoctrineSpecification\Query\QueryModifier; 19 | 20 | final class OrX extends LogicX 21 | { 22 | /** 23 | * @param Filter|QueryModifier ...$children 24 | */ 25 | public function __construct(...$children) 26 | { 27 | parent::__construct(self::OR_X, ...$children); 28 | } 29 | 30 | /** 31 | * Append an other specification with a logic OR. 32 | * 33 | * 34 | * $spec = Spec::orX(A, B); 35 | * $spec->orX(C); 36 | * 37 | * // We be the same as 38 | * $spec = Spec::orX(A, B, C); 39 | * 40 | * 41 | * @param Filter|QueryModifier $child 42 | */ 43 | public function orX($child): void 44 | { 45 | $this->append($child); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Operand/Addition.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand; 16 | 17 | final class Addition extends Arithmetic 18 | { 19 | /** 20 | * @param Operand|string $field 21 | * @param Operand|mixed $value 22 | */ 23 | public function __construct($field, $value) 24 | { 25 | parent::__construct(self::ADD, $field, $value); 26 | } 27 | 28 | /** 29 | * @param mixed $field 30 | * @param mixed $value 31 | * 32 | * @return mixed 33 | */ 34 | protected function doExecute($field, $value) 35 | { 36 | return $field + $value; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Operand/Alias.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\DQLContextResolver; 19 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 20 | 21 | final class Alias implements Operand 22 | { 23 | /** 24 | * @var string 25 | */ 26 | private $alias; 27 | 28 | /** 29 | * @param string $alias 30 | */ 31 | public function __construct(string $alias) 32 | { 33 | $this->alias = $alias; 34 | } 35 | 36 | /** 37 | * @param QueryBuilder $qb 38 | * @param string $context 39 | * 40 | * @return string 41 | */ 42 | public function transform(QueryBuilder $qb, string $context): string 43 | { 44 | return DQLContextResolver::resolveAlias($qb, $this->alias); 45 | } 46 | 47 | /** 48 | * @param mixed[]|object $candidate 49 | * @param string|null $context 50 | */ 51 | public function execute($candidate, ?string $context = null): void 52 | { 53 | throw new OperandNotExecuteException('The aliasing is not supported for execution.'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Operand/ArgumentToOperandConverter.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand; 16 | 17 | /** 18 | * This service is intended for backward compatibility and may be removed in the future. 19 | */ 20 | final class ArgumentToOperandConverter 21 | { 22 | /** 23 | * Convert the argument into the field operand if it is a string and not an operand or into the value operand. 24 | * 25 | * @param Operand|string|mixed $argument 26 | * 27 | * @return Operand 28 | */ 29 | public static function toField($argument): Operand 30 | { 31 | if (is_string($argument)) { 32 | return new Field($argument); 33 | } 34 | 35 | return self::toValue($argument); 36 | } 37 | 38 | /** 39 | * Convert the argument into the value operand if it is not an operand. 40 | * 41 | * @param Operand|mixed $argument 42 | * 43 | * @return Operand 44 | */ 45 | public static function toValue($argument): Operand 46 | { 47 | if ($argument instanceof Operand) { 48 | return $argument; 49 | } 50 | 51 | return new Value($argument); 52 | } 53 | 54 | /** 55 | * Are all arguments is a operands? 56 | * 57 | * @param Operand[]|mixed[] $arguments 58 | * 59 | * @return bool 60 | */ 61 | public static function isAllOperands(array $arguments): bool 62 | { 63 | foreach ($arguments as $argument) { 64 | if (!($argument instanceof Operand)) { 65 | return false; 66 | } 67 | } 68 | 69 | return true; 70 | } 71 | 72 | /** 73 | * Convert all arguments to operands. 74 | * 75 | * @param Operand[]|mixed[] $arguments 76 | * 77 | * @return Operand[] 78 | */ 79 | public static function convert(array $arguments): array 80 | { 81 | $operands = []; 82 | foreach (array_values($arguments) as $i => $argument) { 83 | // always try convert the first argument to the field operand 84 | if (0 === $i) { 85 | $argument = self::toField($argument); 86 | } else { 87 | $argument = self::toValue($argument); 88 | } 89 | 90 | $operands[] = $argument; 91 | } 92 | 93 | return $operands; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Operand/Division.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand; 16 | 17 | final class Division extends Arithmetic 18 | { 19 | /** 20 | * @param Operand|string $field 21 | * @param Operand|mixed $value 22 | */ 23 | public function __construct($field, $value) 24 | { 25 | parent::__construct(self::DIV, $field, $value); 26 | } 27 | 28 | /** 29 | * @param mixed $field 30 | * @param mixed $value 31 | * 32 | * @return mixed 33 | */ 34 | protected function doExecute($field, $value) 35 | { 36 | return $field / $value; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Operand/Field.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\DQLContextResolver; 19 | use Happyr\DoctrineSpecification\Query\Selection\Selection; 20 | use Symfony\Component\PropertyAccess\PropertyAccess; 21 | 22 | final class Field implements Operand, Selection 23 | { 24 | /** 25 | * @var string 26 | */ 27 | private $fieldName; 28 | 29 | /** 30 | * @var string|null 31 | */ 32 | private $context; 33 | 34 | /** 35 | * @param string $fieldName 36 | * @param string|null $context 37 | */ 38 | public function __construct(string $fieldName, ?string $context = null) 39 | { 40 | $this->fieldName = $fieldName; 41 | $this->context = $context; 42 | } 43 | 44 | /** 45 | * @param QueryBuilder $qb 46 | * @param string $context 47 | * 48 | * @return string 49 | */ 50 | public function transform(QueryBuilder $qb, string $context): string 51 | { 52 | if (null !== $this->context) { 53 | $context = sprintf('%s.%s', $context, $this->context); 54 | } 55 | 56 | $dqlAlias = DQLContextResolver::resolveAlias($qb, $context); 57 | 58 | return sprintf('%s.%s', $dqlAlias, $this->fieldName); 59 | } 60 | 61 | /** 62 | * @param mixed[]|object $candidate 63 | * @param string|null $context 64 | * 65 | * @return mixed 66 | */ 67 | public function execute($candidate, ?string $context = null) 68 | { 69 | $propertyPath = $this->fieldName; 70 | 71 | if (null !== $this->context) { 72 | $propertyPath = sprintf('%s.%s', $this->context, $propertyPath); 73 | } 74 | 75 | if (null !== $context) { 76 | $propertyPath = sprintf('%s.%s', $context, $propertyPath); 77 | } 78 | 79 | // If the candidate is a array, then we assume that all nested elements are also arrays. 80 | // The candidate cannot combine arrays and objects since Property Accessor expects different syntax for 81 | // accessing array and object elements. 82 | if (is_array($candidate)) { 83 | $propertyPath = sprintf('[%s]', str_replace('.', '][', $propertyPath)); 84 | } 85 | 86 | return PropertyAccess::createPropertyAccessor()->getValue($candidate, $propertyPath); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Operand/LikePattern.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\ValueConverter; 19 | 20 | final class LikePattern implements Operand 21 | { 22 | public const CONTAINS = '%%%s%%'; 23 | 24 | public const ENDS_WITH = '%%%s'; 25 | 26 | public const STARTS_WITH = '%s%%'; 27 | 28 | /** 29 | * @var string 30 | */ 31 | private $value; 32 | 33 | /** 34 | * @var string 35 | */ 36 | private $format; 37 | 38 | /** 39 | * @param string $value 40 | * @param string $format 41 | */ 42 | public function __construct(string $value, string $format = self::CONTAINS) 43 | { 44 | $this->value = $value; 45 | $this->format = $format; 46 | } 47 | 48 | /** 49 | * @param QueryBuilder $qb 50 | * @param string $context 51 | * 52 | * @return string 53 | */ 54 | public function transform(QueryBuilder $qb, string $context): string 55 | { 56 | $paramName = sprintf('comparison_%d', $qb->getParameters()->count()); 57 | $value = ValueConverter::convertToDatabaseValue($this->value, $qb); 58 | $value = $this->formatValue($this->format, $value); 59 | $qb->setParameter($paramName, $value); 60 | 61 | return sprintf(':%s', $paramName); 62 | } 63 | 64 | /** 65 | * @param mixed[]|object $candidate 66 | * @param string|null $context 67 | * 68 | * @return string 69 | */ 70 | public function execute($candidate, ?string $context = null): string 71 | { 72 | return $this->formatValue($this->format, $this->value); 73 | } 74 | 75 | /** 76 | * @return string 77 | */ 78 | public function getValue(): string 79 | { 80 | return $this->value; 81 | } 82 | 83 | /** 84 | * @return string 85 | */ 86 | public function getFormat(): string 87 | { 88 | return $this->format; 89 | } 90 | 91 | /** 92 | * @param string $format 93 | * @param string $value 94 | * 95 | * @return string 96 | */ 97 | private function formatValue(string $format, string $value): string 98 | { 99 | return sprintf($format, $value); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Operand/Multiplication.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand; 16 | 17 | final class Multiplication extends Arithmetic 18 | { 19 | /** 20 | * @param Operand|string $field 21 | * @param Operand|mixed $value 22 | */ 23 | public function __construct($field, $value) 24 | { 25 | parent::__construct(self::MUL, $field, $value); 26 | } 27 | 28 | /** 29 | * @param mixed $field 30 | * @param mixed $value 31 | * 32 | * @return mixed 33 | */ 34 | protected function doExecute($field, $value) 35 | { 36 | return $field * $value; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Operand/Operand.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | interface Operand 20 | { 21 | /** 22 | * @param QueryBuilder $qb 23 | * @param string $context 24 | * 25 | * @return string 26 | */ 27 | public function transform(QueryBuilder $qb, string $context): string; 28 | 29 | /** 30 | * @param mixed[]|object $candidate 31 | * @param string|null $context 32 | * 33 | * @return mixed 34 | */ 35 | public function execute($candidate, ?string $context = null); 36 | } 37 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Avg.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 19 | use Happyr\DoctrineSpecification\Operand\ArgumentToOperandConverter; 20 | use Happyr\DoctrineSpecification\Operand\Operand; 21 | 22 | final class Avg implements Operand 23 | { 24 | /** 25 | * @var Operand|string 26 | */ 27 | private $field; 28 | 29 | /** 30 | * @var bool 31 | */ 32 | private $distinct; 33 | 34 | /** 35 | * @param Operand|string $field 36 | * @param bool $distinct 37 | */ 38 | public function __construct($field, bool $distinct = false) 39 | { 40 | $this->field = $field; 41 | $this->distinct = $distinct; 42 | } 43 | 44 | /** 45 | * @param QueryBuilder $qb 46 | * @param string $context 47 | * 48 | * @return string 49 | */ 50 | public function transform(QueryBuilder $qb, string $context): string 51 | { 52 | $field = ArgumentToOperandConverter::toField($this->field); 53 | $field = $field->transform($qb, $context); 54 | 55 | $expression = ''; 56 | 57 | if ($this->distinct) { 58 | $expression = 'DISTINCT '; 59 | } 60 | 61 | return sprintf('AVG(%s%s)', $expression, $field); 62 | } 63 | 64 | /** 65 | * @param mixed[]|object $candidate 66 | * @param string|null $context 67 | */ 68 | public function execute($candidate, ?string $context = null): void 69 | { 70 | throw new OperandNotExecuteException( 71 | sprintf('The operand "%s" cannot be executed for a single candidate.', self::class) 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Count.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 19 | use Happyr\DoctrineSpecification\Operand\ArgumentToOperandConverter; 20 | use Happyr\DoctrineSpecification\Operand\Operand; 21 | 22 | final class Count implements Operand 23 | { 24 | /** 25 | * @var Operand|string 26 | */ 27 | private $field; 28 | 29 | /** 30 | * @var bool 31 | */ 32 | private $distinct; 33 | 34 | /** 35 | * @param Operand|string $field 36 | * @param bool $distinct 37 | */ 38 | public function __construct($field, bool $distinct = false) 39 | { 40 | $this->field = $field; 41 | $this->distinct = $distinct; 42 | } 43 | 44 | /** 45 | * @param QueryBuilder $qb 46 | * @param string $context 47 | * 48 | * @return string 49 | */ 50 | public function transform(QueryBuilder $qb, string $context): string 51 | { 52 | $field = ArgumentToOperandConverter::toField($this->field); 53 | $field = $field->transform($qb, $context); 54 | 55 | $expression = ''; 56 | 57 | if ($this->distinct) { 58 | $expression = 'DISTINCT '; 59 | } 60 | 61 | return sprintf('COUNT(%s%s)', $expression, $field); 62 | } 63 | 64 | /** 65 | * @param mixed[]|object $candidate 66 | * @param string|null $context 67 | */ 68 | public function execute($candidate, ?string $context = null): void 69 | { 70 | throw new OperandNotExecuteException( 71 | sprintf('The operand "%s" cannot be executed for a single candidate.', self::class) 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/AbsExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class AbsExecutor 18 | { 19 | /** 20 | * @param mixed $arithmetic_expression 21 | * 22 | * @return float|int 23 | */ 24 | public function __invoke($arithmetic_expression) 25 | { 26 | return abs($arithmetic_expression); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/BitAndExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class BitAndExecutor 18 | { 19 | /** 20 | * @param int $a 21 | * @param int $b 22 | * 23 | * @return int 24 | */ 25 | public function __invoke(int $a, int $b): int 26 | { 27 | return $a & $b; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/BitOrExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class BitOrExecutor 18 | { 19 | /** 20 | * @param int $a 21 | * @param int $b 22 | * 23 | * @return int 24 | */ 25 | public function __invoke(int $a, int $b): int 26 | { 27 | return $a | $b; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/ConcatExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class ConcatExecutor 18 | { 19 | /** 20 | * @param string $string1 21 | * @param string $string2 22 | * 23 | * @return string 24 | */ 25 | public function __invoke(string $string1, string $string2): string 26 | { 27 | return $string1.$string2; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/CurrentDateExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class CurrentDateExecutor 18 | { 19 | /** 20 | * @return \DateTimeImmutable 21 | */ 22 | public function __invoke(): \DateTimeImmutable 23 | { 24 | return (new \DateTimeImmutable())->setTime(0, 0); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/CurrentTimeExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class CurrentTimeExecutor 18 | { 19 | /** 20 | * @return \DateTimeImmutable 21 | */ 22 | public function __invoke(): \DateTimeImmutable 23 | { 24 | return (new \DateTimeImmutable())->setDate(1, 1, 1); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/CurrentTimestampExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class CurrentTimestampExecutor 18 | { 19 | /** 20 | * @return \DateTimeImmutable 21 | */ 22 | public function __invoke(): \DateTimeImmutable 23 | { 24 | return new \DateTimeImmutable(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/DateDiffExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class DateDiffExecutor 18 | { 19 | /** 20 | * @param \DateTimeInterface $date1 21 | * @param \DateTimeInterface $date2 22 | * 23 | * @return \DateInterval 24 | */ 25 | public function __invoke(\DateTimeInterface $date1, \DateTimeInterface $date2): \DateInterval 26 | { 27 | return $date1->diff($date2); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/IdentityExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 18 | 19 | final class IdentityExecutor 20 | { 21 | /** 22 | * @throw OperandNotExecuteException 23 | */ 24 | public function __invoke(): void 25 | { 26 | throw new OperandNotExecuteException( 27 | sprintf('Platform function "%s" cannot be executed for a single candidate.', __CLASS__) 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/LengthExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class LengthExecutor 18 | { 19 | /** 20 | * @param string $str 21 | * 22 | * @return int 23 | */ 24 | public function __invoke(string $str): int 25 | { 26 | return strlen($str); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/LocateExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class LocateExecutor 18 | { 19 | /** 20 | * @param mixed $needle 21 | * @param string $haystack 22 | * @param int $offset 23 | * 24 | * @return int 25 | */ 26 | public function __invoke($needle, string $haystack, int $offset = 0): int 27 | { 28 | $position = strpos($haystack, $needle, $offset); 29 | 30 | // in DQL position is shifted 31 | return false === $position ? 0 : $position + 1; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/LowerExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class LowerExecutor 18 | { 19 | /** 20 | * @param string $string 21 | * 22 | * @return string 23 | */ 24 | public function __invoke(string $string): string 25 | { 26 | return strtolower($string); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/ModExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class ModExecutor 18 | { 19 | /** 20 | * @param float $a 21 | * @param float $b 22 | * 23 | * @return float 24 | */ 25 | public function __invoke(float $a, float $b): float 26 | { 27 | return fmod($a, $b); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/SizeExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class SizeExecutor 18 | { 19 | /** 20 | * @param mixed[]|\Countable $value 21 | * 22 | * @return int 23 | */ 24 | public function __invoke($value): int 25 | { 26 | return count($value); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/SqrtExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class SqrtExecutor 18 | { 19 | /** 20 | * @param float $num 21 | * 22 | * @return float 23 | */ 24 | public function __invoke(float $num): float 25 | { 26 | return sqrt($num); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/SubstringExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class SubstringExecutor 18 | { 19 | /** 20 | * @param string $string 21 | * @param int $offset 22 | * @param int|null $length 23 | * 24 | * @return false|string 25 | */ 26 | public function __invoke(string $string, int $offset, ?int $length = null) 27 | { 28 | if (null === $length) { 29 | return substr($string, $offset); 30 | } 31 | 32 | return substr($string, $offset, $length); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Executor/UpperExecutor.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | final class UpperExecutor 18 | { 19 | /** 20 | * @param string $string 21 | * 22 | * @return string 23 | */ 24 | public function __invoke(string $string): string 25 | { 26 | return strtoupper($string); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Max.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 19 | use Happyr\DoctrineSpecification\Operand\ArgumentToOperandConverter; 20 | use Happyr\DoctrineSpecification\Operand\Operand; 21 | 22 | final class Max implements Operand 23 | { 24 | /** 25 | * @var Operand|string 26 | */ 27 | private $field; 28 | 29 | /** 30 | * @var bool 31 | */ 32 | private $distinct; 33 | 34 | /** 35 | * @param Operand|string $field 36 | * @param bool $distinct 37 | */ 38 | public function __construct($field, bool $distinct = false) 39 | { 40 | $this->field = $field; 41 | $this->distinct = $distinct; 42 | } 43 | 44 | /** 45 | * @param QueryBuilder $qb 46 | * @param string $context 47 | * 48 | * @return string 49 | */ 50 | public function transform(QueryBuilder $qb, string $context): string 51 | { 52 | $field = ArgumentToOperandConverter::toField($this->field); 53 | $field = $field->transform($qb, $context); 54 | 55 | $expression = ''; 56 | 57 | if ($this->distinct) { 58 | $expression = 'DISTINCT '; 59 | } 60 | 61 | return sprintf('MAX(%s%s)', $expression, $field); 62 | } 63 | 64 | /** 65 | * @param mixed[]|object $candidate 66 | * @param string|null $context 67 | */ 68 | public function execute($candidate, ?string $context = null): void 69 | { 70 | throw new OperandNotExecuteException( 71 | sprintf('The operand "%s" cannot be executed for a single candidate.', self::class) 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Min.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 19 | use Happyr\DoctrineSpecification\Operand\ArgumentToOperandConverter; 20 | use Happyr\DoctrineSpecification\Operand\Operand; 21 | 22 | final class Min implements Operand 23 | { 24 | /** 25 | * @var Operand|string 26 | */ 27 | private $field; 28 | 29 | /** 30 | * @var bool 31 | */ 32 | private $distinct; 33 | 34 | /** 35 | * @param Operand|string $field 36 | * @param bool $distinct 37 | */ 38 | public function __construct($field, bool $distinct = false) 39 | { 40 | $this->field = $field; 41 | $this->distinct = $distinct; 42 | } 43 | 44 | /** 45 | * @param QueryBuilder $qb 46 | * @param string $context 47 | * 48 | * @return string 49 | */ 50 | public function transform(QueryBuilder $qb, string $context): string 51 | { 52 | $field = ArgumentToOperandConverter::toField($this->field); 53 | $field = $field->transform($qb, $context); 54 | 55 | $expression = ''; 56 | 57 | if ($this->distinct) { 58 | $expression = 'DISTINCT '; 59 | } 60 | 61 | return sprintf('MIN(%s%s)', $expression, $field); 62 | } 63 | 64 | /** 65 | * @param mixed[]|object $candidate 66 | * @param string|null $context 67 | */ 68 | public function execute($candidate, ?string $context = null): void 69 | { 70 | throw new OperandNotExecuteException( 71 | sprintf('The operand "%s" cannot be executed for a single candidate.', self::class) 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Operand/PlatformFunction/Sum.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand\PlatformFunction; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 19 | use Happyr\DoctrineSpecification\Operand\ArgumentToOperandConverter; 20 | use Happyr\DoctrineSpecification\Operand\Operand; 21 | 22 | final class Sum implements Operand 23 | { 24 | /** 25 | * @var Operand|string 26 | */ 27 | private $field; 28 | 29 | /** 30 | * @var bool 31 | */ 32 | private $distinct; 33 | 34 | /** 35 | * @param Operand|string $field 36 | * @param bool $distinct 37 | */ 38 | public function __construct($field, bool $distinct = false) 39 | { 40 | $this->field = $field; 41 | $this->distinct = $distinct; 42 | } 43 | 44 | /** 45 | * @param QueryBuilder $qb 46 | * @param string $context 47 | * 48 | * @return string 49 | */ 50 | public function transform(QueryBuilder $qb, string $context): string 51 | { 52 | $field = ArgumentToOperandConverter::toField($this->field); 53 | $field = $field->transform($qb, $context); 54 | 55 | $expression = ''; 56 | 57 | if ($this->distinct) { 58 | $expression = 'DISTINCT '; 59 | } 60 | 61 | return sprintf('SUM(%s%s)', $expression, $field); 62 | } 63 | 64 | /** 65 | * @param mixed[]|object $candidate 66 | * @param string|null $context 67 | */ 68 | public function execute($candidate, ?string $context = null): void 69 | { 70 | throw new OperandNotExecuteException( 71 | sprintf('The operand "%s" cannot be executed for a single candidate.', self::class) 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Operand/Subtraction.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand; 16 | 17 | final class Subtraction extends Arithmetic 18 | { 19 | /** 20 | * @param Operand|string $field 21 | * @param Operand|mixed $value 22 | */ 23 | public function __construct($field, $value) 24 | { 25 | parent::__construct(self::SUB, $field, $value); 26 | } 27 | 28 | /** 29 | * @param mixed $field 30 | * @param mixed $value 31 | * 32 | * @return mixed 33 | */ 34 | protected function doExecute($field, $value) 35 | { 36 | return $field - $value; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Operand/Value.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\ValueConverter; 19 | 20 | final class Value implements Operand 21 | { 22 | /** 23 | * @var mixed 24 | */ 25 | private $value; 26 | 27 | /** 28 | * @var int|string|null 29 | */ 30 | private $valueType; 31 | 32 | /** 33 | * @param mixed $value 34 | * @param int|string|null $valueType \PDO::PARAM_* or \Doctrine\DBAL\Types\Type::* constant 35 | */ 36 | public function __construct($value, $valueType = null) 37 | { 38 | $this->value = $value; 39 | $this->valueType = $valueType; 40 | } 41 | 42 | /** 43 | * @param QueryBuilder $qb 44 | * @param string $context 45 | * 46 | * @return string 47 | */ 48 | public function transform(QueryBuilder $qb, string $context): string 49 | { 50 | $paramName = sprintf('comparison_%d', $qb->getParameters()->count()); 51 | $value = ValueConverter::convertToDatabaseValue($this->value, $qb); 52 | $qb->setParameter($paramName, $value, $this->valueType); 53 | 54 | return sprintf(':%s', $paramName); 55 | } 56 | 57 | /** 58 | * @param mixed[]|object $candidate 59 | * @param string|null $context 60 | * 61 | * @return mixed 62 | */ 63 | public function execute($candidate, ?string $context = null) 64 | { 65 | return $this->value; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Operand/Values.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Operand; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\ValueConverter; 19 | 20 | final class Values implements Operand 21 | { 22 | /** 23 | * @var mixed[] 24 | */ 25 | private $values; 26 | 27 | /** 28 | * @var int|string|null 29 | */ 30 | private $valueType; 31 | 32 | /** 33 | * @param mixed[] $values 34 | * @param int|string|null $valueType PDO::PARAM_* or \Doctrine\DBAL\Types\Type::* constant 35 | */ 36 | public function __construct(array $values, $valueType = null) 37 | { 38 | $this->values = $values; 39 | $this->valueType = $valueType; 40 | } 41 | 42 | /** 43 | * @param QueryBuilder $qb 44 | * @param string $context 45 | * 46 | * @return string 47 | */ 48 | public function transform(QueryBuilder $qb, string $context): string 49 | { 50 | $values = $this->values; 51 | foreach ($values as $k => $v) { 52 | $values[$k] = ValueConverter::convertToDatabaseValue($v, $qb); 53 | } 54 | 55 | $paramName = sprintf('comparison_%d', $qb->getParameters()->count()); 56 | $qb->setParameter($paramName, $values, $this->valueType); 57 | 58 | return sprintf(':%s', $paramName); 59 | } 60 | 61 | /** 62 | * @param mixed[]|object $candidate 63 | * @param string|null $context 64 | * 65 | * @return mixed[] 66 | */ 67 | public function execute($candidate, ?string $context = null): array 68 | { 69 | return $this->values; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Query/AbstractJoin.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\DQLContextResolver; 19 | 20 | abstract class AbstractJoin implements QueryModifier 21 | { 22 | /** 23 | * @var string 24 | */ 25 | private $field; 26 | 27 | /** 28 | * @var string 29 | */ 30 | private $newAlias; 31 | 32 | /** 33 | * @var string|null 34 | */ 35 | private $context; 36 | 37 | /** 38 | * @param string $field 39 | * @param string|null $newAlias 40 | * @param string|null $context 41 | */ 42 | public function __construct(string $field, ?string $newAlias = null, ?string $context = null) 43 | { 44 | $this->field = $field; 45 | $this->newAlias = null !== $newAlias ? $newAlias : $field; 46 | $this->context = $context; 47 | } 48 | 49 | /** 50 | * @param QueryBuilder $qb 51 | * @param string $context 52 | */ 53 | public function modify(QueryBuilder $qb, string $context): void 54 | { 55 | if (null !== $this->context) { 56 | $context = sprintf('%s.%s', $context, $this->context); 57 | } 58 | 59 | $dqlAlias = DQLContextResolver::resolveAlias($qb, $context); 60 | 61 | $this->modifyJoin($qb, sprintf('%s.%s', $dqlAlias, $this->field), $this->newAlias); 62 | } 63 | 64 | /** 65 | * @param QueryBuilder $qb 66 | * @param string $join 67 | * @param string $alias 68 | */ 69 | abstract protected function modifyJoin(QueryBuilder $qb, string $join, string $alias): void; 70 | } 71 | -------------------------------------------------------------------------------- /src/Query/AbstractSelect.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Query\Selection\ArgumentToSelectionConverter; 19 | use Happyr\DoctrineSpecification\Query\Selection\Selection; 20 | 21 | abstract class AbstractSelect implements QueryModifier 22 | { 23 | /** 24 | * @var Selection[]|string[] 25 | */ 26 | private $selections; 27 | 28 | /** 29 | * @param Selection|string ...$selections 30 | */ 31 | public function __construct(...$selections) 32 | { 33 | $this->selections = $selections; 34 | } 35 | 36 | /** 37 | * @param QueryBuilder $qb 38 | * @param string $context 39 | */ 40 | public function modify(QueryBuilder $qb, string $context): void 41 | { 42 | $selections = []; 43 | foreach ($this->selections as $selection) { 44 | $selection = ArgumentToSelectionConverter::toSelection($selection); 45 | $selections[] = $selection->transform($qb, $context); 46 | } 47 | 48 | $this->modifySelection($qb, $selections); 49 | } 50 | 51 | /** 52 | * @param QueryBuilder $qb 53 | * @param string[] $selections 54 | */ 55 | abstract protected function modifySelection(QueryBuilder $qb, array $selections): void; 56 | } 57 | -------------------------------------------------------------------------------- /src/Query/AddSelect.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | final class AddSelect extends AbstractSelect 20 | { 21 | /** 22 | * @param QueryBuilder $qb 23 | * @param string[] $selections 24 | */ 25 | protected function modifySelection(QueryBuilder $qb, array $selections): void 26 | { 27 | $qb->addSelect($selections); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Query/Distinct.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | final class Distinct implements QueryModifier 20 | { 21 | /** 22 | * @param QueryBuilder $qb 23 | * @param string $context 24 | */ 25 | public function modify(QueryBuilder $qb, string $context): void 26 | { 27 | $qb->distinct(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Query/GroupBy.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Operand\Alias; 19 | use Happyr\DoctrineSpecification\Operand\Field; 20 | 21 | final class GroupBy implements QueryModifier 22 | { 23 | /** 24 | * @var Field|Alias 25 | */ 26 | private $field; 27 | 28 | /** 29 | * @var string|null 30 | */ 31 | private $context; 32 | 33 | /** 34 | * @param Field|Alias|string $field 35 | * @param string|null $context 36 | */ 37 | public function __construct($field, ?string $context = null) 38 | { 39 | if (!($field instanceof Field) && !($field instanceof Alias)) { 40 | $field = new Field($field); 41 | } 42 | 43 | $this->field = $field; 44 | $this->context = $context; 45 | } 46 | 47 | /** 48 | * @param QueryBuilder $qb 49 | * @param string $context 50 | */ 51 | public function modify(QueryBuilder $qb, string $context): void 52 | { 53 | if (null !== $this->context) { 54 | $context = sprintf('%s.%s', $context, $this->context); 55 | } 56 | 57 | $qb->addGroupBy($this->field->transform($qb, $context)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Query/Having.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Filter\Filter; 19 | 20 | final class Having implements QueryModifier 21 | { 22 | /** 23 | * @var Filter 24 | */ 25 | private $filter; 26 | 27 | /** 28 | * @param Filter $filter 29 | */ 30 | public function __construct(Filter $filter) 31 | { 32 | $this->filter = $filter; 33 | } 34 | 35 | /** 36 | * @param QueryBuilder $qb 37 | * @param string $context 38 | */ 39 | public function modify(QueryBuilder $qb, string $context): void 40 | { 41 | $qb->having($this->filter->getFilter($qb, $context)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Query/IndexBy.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\Query\QueryException; 18 | use Doctrine\ORM\QueryBuilder; 19 | use Happyr\DoctrineSpecification\DQLContextResolver; 20 | use Happyr\DoctrineSpecification\Operand\Field; 21 | 22 | /** 23 | * Class IndexBy. 24 | */ 25 | final class IndexBy implements QueryModifier 26 | { 27 | /** 28 | * @var Field 29 | */ 30 | private $field; 31 | 32 | /** 33 | * @var string|null 34 | */ 35 | private $context; 36 | 37 | /** 38 | * @param Field|string $field Field name for indexing 39 | * @param string|null $context DQL alias of field 40 | */ 41 | public function __construct($field, ?string $context = null) 42 | { 43 | if (!($field instanceof Field)) { 44 | $field = new Field($field); 45 | } 46 | 47 | $this->field = $field; 48 | $this->context = $context; 49 | } 50 | 51 | /** 52 | * @param QueryBuilder $qb 53 | * @param string $context 54 | * 55 | * @throws QueryException 56 | */ 57 | public function modify(QueryBuilder $qb, string $context): void 58 | { 59 | if (null !== $this->context) { 60 | $context = sprintf('%s.%s', $context, $this->context); 61 | } 62 | 63 | $dqlAlias = DQLContextResolver::resolveAlias($qb, $context); 64 | 65 | $qb->indexBy($dqlAlias, $this->field->transform($qb, $context)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Query/InnerJoin.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | final class InnerJoin extends AbstractJoin 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | protected function modifyJoin(QueryBuilder $qb, string $join, string $alias): void 25 | { 26 | $qb->innerJoin($join, $alias); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Query/Join.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | final class Join extends AbstractJoin 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | protected function modifyJoin(QueryBuilder $qb, string $join, string $alias): void 25 | { 26 | $qb->join($join, $alias); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Query/LeftJoin.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | final class LeftJoin extends AbstractJoin 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | protected function modifyJoin(QueryBuilder $qb, string $join, string $alias): void 25 | { 26 | $qb->leftJoin($join, $alias); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Query/Limit.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | final class Limit implements QueryModifier 20 | { 21 | /** 22 | * @var int limit 23 | */ 24 | private $limit; 25 | 26 | /** 27 | * @param int $limit 28 | */ 29 | public function __construct(int $limit) 30 | { 31 | $this->limit = $limit; 32 | } 33 | 34 | /** 35 | * @param QueryBuilder $qb 36 | * @param string $context 37 | */ 38 | public function modify(QueryBuilder $qb, string $context): void 39 | { 40 | $qb->setMaxResults($this->limit); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Query/Offset.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | final class Offset implements QueryModifier 20 | { 21 | /** 22 | * @var int 23 | */ 24 | private $offset; 25 | 26 | /** 27 | * @param int $offset 28 | */ 29 | public function __construct(int $offset) 30 | { 31 | $this->offset = $offset; 32 | } 33 | 34 | /** 35 | * @param QueryBuilder $qb 36 | * @param string $context 37 | */ 38 | public function modify(QueryBuilder $qb, string $context): void 39 | { 40 | $qb->setFirstResult($this->offset); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Query/OrderBy.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Operand\Alias; 19 | use Happyr\DoctrineSpecification\Operand\Field; 20 | 21 | final class OrderBy implements QueryModifier 22 | { 23 | public const ASC = 'ASC'; 24 | 25 | public const DESC = 'DESC'; 26 | 27 | /** 28 | * @var Field|Alias 29 | */ 30 | private $field; 31 | 32 | /** 33 | * @var string 34 | */ 35 | private $order; 36 | 37 | /** 38 | * @var string|null 39 | */ 40 | private $context; 41 | 42 | /** 43 | * @param Field|Alias|string $field 44 | * @param string $order 45 | * @param string|null $context 46 | */ 47 | public function __construct($field, string $order = self::ASC, ?string $context = null) 48 | { 49 | if (!($field instanceof Field) && !($field instanceof Alias)) { 50 | $field = new Field($field); 51 | } 52 | 53 | $this->field = $field; 54 | $this->order = $order; 55 | $this->context = $context; 56 | } 57 | 58 | /** 59 | * @param QueryBuilder $qb 60 | * @param string $context 61 | */ 62 | public function modify(QueryBuilder $qb, string $context): void 63 | { 64 | if (null !== $this->context) { 65 | $context = sprintf('%s.%s', $context, $this->context); 66 | } 67 | 68 | $qb->addOrderBy($this->field->transform($qb, $context), $this->order); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Query/QueryModifier.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | interface QueryModifier 20 | { 21 | /** 22 | * @param QueryBuilder $qb 23 | * @param string $context 24 | */ 25 | public function modify(QueryBuilder $qb, string $context): void; 26 | } 27 | -------------------------------------------------------------------------------- /src/Query/QueryModifierCollection.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Exception\InvalidArgumentException; 19 | 20 | final class QueryModifierCollection implements QueryModifier 21 | { 22 | /** 23 | * @var QueryModifier[] 24 | */ 25 | private $children; 26 | 27 | /** 28 | * Construct it with one or more instances of QueryModifier. 29 | * 30 | * @param QueryModifier ...$children 31 | */ 32 | public function __construct(...$children) 33 | { 34 | $this->children = $children; 35 | } 36 | 37 | /** 38 | * @param QueryBuilder $qb 39 | * @param string $context 40 | */ 41 | public function modify(QueryBuilder $qb, string $context): void 42 | { 43 | foreach ($this->children as $child) { 44 | if (!$child instanceof QueryModifier) { 45 | throw new InvalidArgumentException(sprintf( 46 | 'Child passed to QueryModifierCollection must be an instance of %s, but instance of %s found', 47 | QueryModifier::class, 48 | get_class($child) 49 | )); 50 | } 51 | 52 | $child->modify($qb, $context); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Query/Select.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | final class Select extends AbstractSelect 20 | { 21 | /** 22 | * @param QueryBuilder $qb 23 | * @param string[] $selections 24 | */ 25 | protected function modifySelection(QueryBuilder $qb, array $selections): void 26 | { 27 | $qb->select($selections); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Query/SelectNew.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Operand\ArgumentToOperandConverter; 19 | use Happyr\DoctrineSpecification\Operand\Operand; 20 | 21 | /** 22 | * Using the NEW operator you can construct Data Transfer Objects (DTOs) directly from DQL queries. 23 | */ 24 | final class SelectNew implements QueryModifier 25 | { 26 | /** 27 | * @var string 28 | */ 29 | private $class; 30 | 31 | /** 32 | * @var Operand[]|mixed[] 33 | */ 34 | private $arguments; 35 | 36 | /** 37 | * @param string $class 38 | * @param Operand|mixed ...$arguments 39 | * 40 | * @phpstan-param class-string $class 41 | */ 42 | public function __construct(string $class, ...$arguments) 43 | { 44 | $this->class = $class; 45 | $this->arguments = $arguments; 46 | } 47 | 48 | /** 49 | * @param QueryBuilder $qb 50 | * @param string $context 51 | */ 52 | public function modify(QueryBuilder $qb, string $context): void 53 | { 54 | $arguments = []; 55 | foreach (ArgumentToOperandConverter::convert($this->arguments) as $argument) { 56 | $arguments[] = $argument->transform($qb, $context); 57 | } 58 | 59 | $qb->select(sprintf('NEW %s(%s)', $this->class, implode(', ', $arguments))); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Query/Selection/AbstractSelectAs.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query\Selection; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Filter\Filter; 19 | use Happyr\DoctrineSpecification\Operand\ArgumentToOperandConverter; 20 | use Happyr\DoctrineSpecification\Operand\Operand; 21 | 22 | abstract class AbstractSelectAs implements Selection 23 | { 24 | /** 25 | * @var Operand|Filter|string 26 | */ 27 | private $expression; 28 | 29 | /** 30 | * @var string 31 | */ 32 | private $newAlias; 33 | 34 | /** 35 | * @param Filter|Operand|string $expression 36 | * @param string $newAlias 37 | */ 38 | public function __construct($expression, string $newAlias) 39 | { 40 | $this->expression = $expression; 41 | $this->newAlias = $newAlias; 42 | } 43 | 44 | /** 45 | * @param QueryBuilder $qb 46 | * @param string $context 47 | * 48 | * @return string 49 | */ 50 | public function transform(QueryBuilder $qb, string $context): string 51 | { 52 | if ($this->expression instanceof Filter) { 53 | $expression = $this->expression->getFilter($qb, $context); 54 | } else { 55 | $expression = ArgumentToOperandConverter::toField($this->expression); 56 | $expression = $expression->transform($qb, $context); 57 | } 58 | 59 | return sprintf($this->getAliasFormat(), $expression, $this->newAlias); 60 | } 61 | 62 | /** 63 | * Return a select format. 64 | * 65 | * @return string 66 | */ 67 | abstract protected function getAliasFormat(): string; 68 | } 69 | -------------------------------------------------------------------------------- /src/Query/Selection/ArgumentToSelectionConverter.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query\Selection; 16 | 17 | use Happyr\DoctrineSpecification\Operand\Field; 18 | 19 | /** 20 | * This service is intended for backward compatibility and may be removed in the future. 21 | */ 22 | final class ArgumentToSelectionConverter 23 | { 24 | /** 25 | * Convert the argument into the field operand if it is not an selection. 26 | * 27 | * @param Selection|string $argument 28 | * 29 | * @return Selection 30 | */ 31 | public static function toSelection($argument): Selection 32 | { 33 | if ($argument instanceof Selection) { 34 | return $argument; 35 | } 36 | 37 | return new Field($argument); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Query/Selection/SelectAs.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query\Selection; 16 | 17 | final class SelectAs extends AbstractSelectAs 18 | { 19 | /** 20 | * @return string 21 | */ 22 | protected function getAliasFormat(): string 23 | { 24 | return '(%s) AS %s'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Query/Selection/SelectEntity.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query\Selection; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\DQLContextResolver; 19 | 20 | final class SelectEntity implements Selection 21 | { 22 | /** 23 | * @var string 24 | */ 25 | private $dqlAliasInContext; 26 | 27 | /** 28 | * @param string $dqlAliasInContext 29 | */ 30 | public function __construct(string $dqlAliasInContext) 31 | { 32 | $this->dqlAliasInContext = $dqlAliasInContext; 33 | } 34 | 35 | /** 36 | * @param QueryBuilder $qb 37 | * @param string $context 38 | * 39 | * @return string 40 | */ 41 | public function transform(QueryBuilder $qb, string $context): string 42 | { 43 | return DQLContextResolver::resolveAlias($qb, sprintf('%s.%s', $context, $this->dqlAliasInContext)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Query/Selection/SelectHiddenAs.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query\Selection; 16 | 17 | final class SelectHiddenAs extends AbstractSelectAs 18 | { 19 | /** 20 | * @return string 21 | */ 22 | protected function getAliasFormat(): string 23 | { 24 | return '(%s) AS HIDDEN %s'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Query/Selection/Selection.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query\Selection; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | interface Selection 20 | { 21 | /** 22 | * @param QueryBuilder $qb 23 | * @param string $context 24 | * 25 | * @return string 26 | */ 27 | public function transform(QueryBuilder $qb, string $context): string; 28 | } 29 | -------------------------------------------------------------------------------- /src/Query/Slice.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | final class Slice implements QueryModifier 20 | { 21 | /** 22 | * @var int 23 | */ 24 | private $sliceSize; 25 | 26 | /** 27 | * @var int 28 | */ 29 | private $sliceNumber = 0; 30 | 31 | /** 32 | * @param int $sliceSize 33 | * @param int $sliceNumber 34 | */ 35 | public function __construct(int $sliceSize, int $sliceNumber = 0) 36 | { 37 | $this->sliceSize = $sliceSize; 38 | $this->sliceNumber = $sliceNumber; 39 | } 40 | 41 | /** 42 | * @param QueryBuilder $qb 43 | * @param string $context 44 | */ 45 | public function modify(QueryBuilder $qb, string $context): void 46 | { 47 | $qb->setMaxResults($this->sliceSize); 48 | 49 | if ($this->sliceNumber > 0) { 50 | $qb->setFirstResult($this->sliceNumber * $this->sliceSize); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Repository/EntitySpecificationRepository.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Repository; 16 | 17 | use Doctrine\ORM\EntityRepository; 18 | 19 | /** 20 | * This class allows you to use a Specification to query entities. 21 | * 22 | * @template T 23 | * @phpstan-extends EntityRepository 24 | */ 25 | class EntitySpecificationRepository extends EntityRepository implements EntitySpecificationRepositoryInterface 26 | { 27 | use EntitySpecificationRepositoryTrait; 28 | } 29 | -------------------------------------------------------------------------------- /src/Repository/RepositoryFactory.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Repository; 16 | 17 | use Doctrine\ORM\EntityManagerInterface; 18 | use Doctrine\ORM\EntityRepository; 19 | use Doctrine\ORM\Repository\RepositoryFactory as RepositoryFactoryInterface; 20 | 21 | /** 22 | * Factory class for creating EntitySpecificationRepository instances. 23 | * 24 | * Provides an implementation of RepositoryFactory so that the 25 | * default repository type in Doctrine can easily be replaced. 26 | */ 27 | class RepositoryFactory implements RepositoryFactoryInterface 28 | { 29 | /** 30 | * Gets the repository for an entity class. 31 | * 32 | * @param EntityManagerInterface $entityManager the EntityManager instance 33 | * @param string $entityName the name of the entity 34 | * 35 | * @return EntityRepository 36 | * 37 | * @phpstan-template T 38 | * @phpstan-param class-string $entityName 39 | * @phpstan-return EntityRepository 40 | */ 41 | public function getRepository(EntityManagerInterface $entityManager, $entityName): EntityRepository 42 | { 43 | return new EntitySpecificationRepository($entityManager, $entityManager->getClassMetadata($entityName)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Result/AsArray.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Result; 16 | 17 | use Doctrine\ORM\AbstractQuery; 18 | use Doctrine\ORM\Query; 19 | 20 | final class AsArray implements ResultModifier 21 | { 22 | /** 23 | * @param AbstractQuery $query 24 | */ 25 | public function modify(AbstractQuery $query): void 26 | { 27 | $query->setHydrationMode(Query::HYDRATE_ARRAY); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Result/AsScalar.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Result; 16 | 17 | use Doctrine\ORM\AbstractQuery; 18 | use Doctrine\ORM\Query; 19 | 20 | final class AsScalar implements ResultModifier 21 | { 22 | /** 23 | * @param AbstractQuery $query 24 | */ 25 | public function modify(AbstractQuery $query): void 26 | { 27 | $query->setHydrationMode(Query::HYDRATE_SCALAR); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Result/AsSingleScalar.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Result; 16 | 17 | use Doctrine\ORM\AbstractQuery; 18 | use Doctrine\ORM\Query; 19 | 20 | final class AsSingleScalar implements ResultModifier 21 | { 22 | /** 23 | * @param AbstractQuery $query 24 | */ 25 | public function modify(AbstractQuery $query): void 26 | { 27 | $query->setHydrationMode(Query::HYDRATE_SINGLE_SCALAR); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Result/Cache.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Result; 16 | 17 | use Doctrine\ORM\AbstractQuery; 18 | 19 | final class Cache implements ResultModifier 20 | { 21 | /** 22 | * @var int How may seconds the cache entry is valid 23 | */ 24 | private $cacheLifetime; 25 | 26 | /** 27 | * @param int $cacheLifetime How many seconds the cached entry is valid 28 | */ 29 | public function __construct(int $cacheLifetime) 30 | { 31 | $this->cacheLifetime = $cacheLifetime; 32 | } 33 | 34 | /** 35 | * @param AbstractQuery $query 36 | */ 37 | public function modify(AbstractQuery $query): void 38 | { 39 | $query->setResultCacheLifetime($this->cacheLifetime); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Result/ResultModifier.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Result; 16 | 17 | use Doctrine\ORM\AbstractQuery; 18 | 19 | interface ResultModifier 20 | { 21 | /** 22 | * @param AbstractQuery $query 23 | */ 24 | public function modify(AbstractQuery $query): void; 25 | } 26 | -------------------------------------------------------------------------------- /src/Result/ResultModifierCollection.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Result; 16 | 17 | use Doctrine\ORM\AbstractQuery; 18 | use Happyr\DoctrineSpecification\Exception\InvalidArgumentException; 19 | 20 | final class ResultModifierCollection implements ResultModifier 21 | { 22 | /** 23 | * @var ResultModifier[] 24 | */ 25 | private $children; 26 | 27 | /** 28 | * Construct it with one or more instances of ResultModifier. 29 | * 30 | * @param ResultModifier ...$children 31 | */ 32 | public function __construct(...$children) 33 | { 34 | $this->children = $children; 35 | } 36 | 37 | /** 38 | * @param AbstractQuery $query 39 | */ 40 | public function modify(AbstractQuery $query): void 41 | { 42 | foreach ($this->children as $child) { 43 | if (!$child instanceof ResultModifier) { 44 | throw new InvalidArgumentException(sprintf( 45 | 'Child passed to ResultModifierCollection must be an instance of %s, but instance of %s found', 46 | ResultModifier::class, 47 | get_class($child) 48 | )); 49 | } 50 | 51 | $child->modify($query); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Result/RoundDateTime.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Result; 16 | 17 | use Doctrine\ORM\AbstractQuery; 18 | 19 | /** 20 | * Round a \DateTime and \DateTimeImmutable to enable caching. 21 | */ 22 | final class RoundDateTime implements ResultModifier 23 | { 24 | /** 25 | * @var int How may seconds to round time 26 | */ 27 | private $roundSeconds; 28 | 29 | /** 30 | * @param int $roundSeconds How may seconds to round time 31 | */ 32 | public function __construct(int $roundSeconds) 33 | { 34 | $this->roundSeconds = $roundSeconds; 35 | } 36 | 37 | /** 38 | * @param AbstractQuery $query 39 | */ 40 | public function modify(AbstractQuery $query): void 41 | { 42 | foreach ($query->getParameters() as $parameter) { 43 | $value = $parameter->getValue(); 44 | 45 | if ($value instanceof \DateTimeInterface) { 46 | // round down so that the results do not include data that should not be there. 47 | $uts = (int) (floor($value->getTimestamp() / $this->roundSeconds) * $this->roundSeconds); 48 | $date = (new \DateTimeImmutable('now', $value->getTimezone()))->setTimestamp($uts); 49 | 50 | $query->setParameter($parameter->getName(), $date, $parameter->getType()); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Specification/CountOf.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Specification; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\DQLContextResolver; 19 | use Happyr\DoctrineSpecification\Filter\Filter; 20 | use Happyr\DoctrineSpecification\Filter\Satisfiable; 21 | use Happyr\DoctrineSpecification\Query\QueryModifier; 22 | 23 | final class CountOf implements Specification 24 | { 25 | /** 26 | * @var Filter|QueryModifier 27 | */ 28 | private $child; 29 | 30 | /** 31 | * @param Filter|QueryModifier $child 32 | */ 33 | public function __construct($child) 34 | { 35 | $this->child = $child; 36 | } 37 | 38 | /** 39 | * @param QueryBuilder $qb 40 | * @param string $context 41 | * 42 | * @return string 43 | */ 44 | public function getFilter(QueryBuilder $qb, string $context): string 45 | { 46 | $dqlAlias = DQLContextResolver::resolveAlias($qb, $context); 47 | 48 | $qb->select(sprintf('COUNT(%s)', $dqlAlias)); 49 | 50 | if ($this->child instanceof Filter) { 51 | return $this->child->getFilter($qb, $context); 52 | } 53 | 54 | return ''; 55 | } 56 | 57 | /** 58 | * @param QueryBuilder $qb 59 | * @param string $context 60 | */ 61 | public function modify(QueryBuilder $qb, string $context): void 62 | { 63 | if ($this->child instanceof QueryModifier) { 64 | $this->child->modify($qb, $context); 65 | } 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | public function filterCollection(iterable $collection, ?string $context = null): iterable 72 | { 73 | if ($this->child instanceof Satisfiable) { 74 | return $this->child->filterCollection($collection, $context); 75 | } 76 | 77 | return $collection; 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function isSatisfiedBy($candidate, ?string $context = null): bool 84 | { 85 | if ($this->child instanceof Satisfiable) { 86 | return $this->child->isSatisfiedBy($candidate, $context); 87 | } 88 | 89 | return true; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Specification/Specification.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification\Specification; 16 | 17 | use Happyr\DoctrineSpecification\Filter\Filter; 18 | use Happyr\DoctrineSpecification\Filter\Satisfiable; 19 | use Happyr\DoctrineSpecification\Query\QueryModifier; 20 | 21 | interface Specification extends Filter, QueryModifier, Satisfiable 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/ValueConverter.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Happyr\DoctrineSpecification; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | 19 | final class ValueConverter 20 | { 21 | /** 22 | * @param mixed $value 23 | * @param QueryBuilder $qb 24 | * 25 | * @return mixed 26 | */ 27 | public static function convertToDatabaseValue($value, QueryBuilder $qb) 28 | { 29 | if ($type = DBALTypesResolver::tryGetTypeForValue($value)) { 30 | return $type->convertToDatabaseValue( 31 | $value, 32 | $qb->getEntityManager()->getConnection()->getDatabasePlatform() 33 | ); 34 | } 35 | 36 | return $value; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Filter/MemberOfXSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Filter; 16 | 17 | use Doctrine\Common\Collections\ArrayCollection; 18 | use Doctrine\ORM\Query\Expr; 19 | use Doctrine\ORM\Query\Expr\Comparison; 20 | use Doctrine\ORM\QueryBuilder; 21 | use Happyr\DoctrineSpecification\Filter\Filter; 22 | use Happyr\DoctrineSpecification\Filter\MemberOfX; 23 | use PhpSpec\ObjectBehavior; 24 | 25 | /** 26 | * @mixin MemberOfX 27 | */ 28 | final class MemberOfXSpec extends ObjectBehavior 29 | { 30 | public function let(): void 31 | { 32 | $this->beConstructedWith(18, 'age', null); 33 | } 34 | 35 | public function it_is_initializable(): void 36 | { 37 | $this->shouldHaveType(MemberOfX::class); 38 | } 39 | 40 | public function it_is_an_expression(): void 41 | { 42 | $this->shouldBeAnInstanceOf(Filter::class); 43 | } 44 | 45 | public function it_returns_expression_func_object(QueryBuilder $qb, ArrayCollection $parameters, Expr $exp): void 46 | { 47 | $exp_comparison = new Comparison(':comparison_10', 'MEMBER OF', 'a.age'); 48 | $qb->expr()->willReturn($exp); 49 | $qb->getParameters()->willReturn($parameters); 50 | $parameters->count()->willReturn(10); 51 | 52 | $qb->setParameter('comparison_10', 18, null)->shouldBeCalled(); 53 | $exp->isMemberOf(':comparison_10', 'a.age')->willReturn($exp_comparison); 54 | 55 | $this->getFilter($qb, 'a')->shouldReturn(':comparison_10 MEMBER OF a.age'); 56 | } 57 | 58 | public function it_returns_expression_func_object_in_context( 59 | QueryBuilder $qb, 60 | ArrayCollection $parameters, 61 | Expr $exp 62 | ): void { 63 | $this->beConstructedWith(18, 'age', 'user'); 64 | 65 | $exp_comparison = new Comparison(':comparison_10', 'MEMBER OF', 'user.age'); 66 | $qb->expr()->willReturn($exp); 67 | $qb->getParameters()->willReturn($parameters); 68 | $parameters->count()->willReturn(10); 69 | 70 | $qb->setParameter('comparison_10', 18, null)->shouldBeCalled(); 71 | $exp->isMemberOf(':comparison_10', 'user.age')->willReturn($exp_comparison); 72 | 73 | $qb->getDQLPart('join')->willReturn([]); 74 | $qb->getAllAliases()->willReturn([]); 75 | $qb->join('root.user', 'user')->willReturn($qb); 76 | 77 | $this->getFilter($qb, 'root')->shouldReturn(':comparison_10 MEMBER OF user.age'); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/Game.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification; 16 | 17 | final class Game 18 | { 19 | /** 20 | * @var string 21 | */ 22 | public $name; 23 | 24 | /** 25 | * @var \DateTimeInterface|null 26 | */ 27 | public $releaseAt; 28 | 29 | /** 30 | * @param string $name 31 | * @param \DateTimeInterface|null $releaseAt 32 | */ 33 | public function __construct(string $name, ?\DateTimeInterface $releaseAt = null) 34 | { 35 | $this->name = $name; 36 | $this->releaseAt = $releaseAt; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Operand/AliasSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 19 | use Happyr\DoctrineSpecification\Operand\Alias; 20 | use Happyr\DoctrineSpecification\Operand\Operand; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin Alias 25 | */ 26 | final class AliasSpec extends ObjectBehavior 27 | { 28 | private $alias = 'foo'; 29 | 30 | public function let(): void 31 | { 32 | $this->beConstructedWith($this->alias); 33 | } 34 | 35 | public function it_is_a_alias(): void 36 | { 37 | $this->shouldBeAnInstanceOf(Alias::class); 38 | } 39 | 40 | public function it_is_a_operand(): void 41 | { 42 | $this->shouldBeAnInstanceOf(Operand::class); 43 | } 44 | 45 | public function it_is_transformable(QueryBuilder $qb): void 46 | { 47 | $this->transform($qb, 'a')->shouldReturn($this->alias); 48 | } 49 | 50 | public function it_is_executable(): void 51 | { 52 | $candidate = null; // not used 53 | 54 | $this->shouldThrow(OperandNotExecuteException::class)->duringExecute($candidate); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Operand/DivisionSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand; 16 | 17 | use Doctrine\Common\Collections\ArrayCollection; 18 | use Doctrine\ORM\QueryBuilder; 19 | use Happyr\DoctrineSpecification\Operand\Division; 20 | use Happyr\DoctrineSpecification\Operand\Field; 21 | use Happyr\DoctrineSpecification\Operand\Operand; 22 | use Happyr\DoctrineSpecification\Operand\Value; 23 | use PhpSpec\ObjectBehavior; 24 | use tests\Happyr\DoctrineSpecification\Player; 25 | 26 | /** 27 | * @mixin Division 28 | */ 29 | final class DivisionSpec extends ObjectBehavior 30 | { 31 | private $field = 'foo'; 32 | 33 | private $value = 'bar'; 34 | 35 | public function let(): void 36 | { 37 | $this->beConstructedWith($this->field, $this->value); 38 | } 39 | 40 | public function it_is_a_div(): void 41 | { 42 | $this->shouldBeAnInstanceOf(Division::class); 43 | } 44 | 45 | public function it_is_a_operand(): void 46 | { 47 | $this->shouldBeAnInstanceOf(Operand::class); 48 | } 49 | 50 | public function it_is_transformable(QueryBuilder $qb, ArrayCollection $parameters): void 51 | { 52 | $qb->getParameters()->willReturn($parameters); 53 | $parameters->count()->willReturn(10); 54 | 55 | $qb->setParameter('comparison_10', $this->value, null)->shouldBeCalled(); 56 | 57 | $this->transform($qb, 'a')->shouldReturn('(a.foo / :comparison_10)'); 58 | } 59 | 60 | public function it_is_transformable_add_fields(QueryBuilder $qb): void 61 | { 62 | $this->beConstructedWith(new Field('foo'), new Field('bar')); 63 | $this->transform($qb, 'a')->shouldReturn('(a.foo / a.bar)'); 64 | } 65 | 66 | public function it_is_executable_object(): void 67 | { 68 | $this->beConstructedWith('points', 10); 69 | 70 | $player = new Player('Moe', 'M', 1230); 71 | 72 | $this->execute($player)->shouldReturn(123); 73 | } 74 | 75 | public function it_is_executable_object_with_operands(): void 76 | { 77 | $this->beConstructedWith(new Field('points'), new Value(10)); 78 | 79 | $player = new Player('Moe', 'M', 1230); 80 | 81 | $this->execute($player)->shouldReturn(123); 82 | } 83 | 84 | public function it_is_executable_array(): void 85 | { 86 | $this->beConstructedWith('points', 10); 87 | 88 | $player = ['pseudo' => 'Moe', 'gender' => 'M', 'points' => 1230]; 89 | 90 | $this->execute($player)->shouldReturn(123); 91 | } 92 | 93 | public function it_is_executable_array_with_operands(): void 94 | { 95 | $this->beConstructedWith(new Field('points'), new Value(10)); 96 | 97 | $player = ['pseudo' => 'Moe', 'gender' => 'M', 'points' => 1230]; 98 | 99 | $this->execute($player)->shouldReturn(123); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/Operand/LikePatternSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand; 16 | 17 | use Doctrine\Common\Collections\ArrayCollection; 18 | use Doctrine\ORM\QueryBuilder; 19 | use Happyr\DoctrineSpecification\Operand\LikePattern; 20 | use Happyr\DoctrineSpecification\Operand\Operand; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin LikePattern 25 | */ 26 | final class LikePatternSpec extends ObjectBehavior 27 | { 28 | private $value = 'foo'; 29 | 30 | private $format = LikePattern::CONTAINS; 31 | 32 | public function let(): void 33 | { 34 | $this->beConstructedWith($this->value, $this->format); 35 | } 36 | 37 | public function it_is_a_like_pattern(): void 38 | { 39 | $this->shouldBeAnInstanceOf(LikePattern::class); 40 | } 41 | 42 | public function it_is_a_operand(): void 43 | { 44 | $this->shouldBeAnInstanceOf(Operand::class); 45 | } 46 | 47 | public function it_is_transformable(QueryBuilder $qb, ArrayCollection $parameters): void 48 | { 49 | $context = 'a'; 50 | 51 | $qb->getParameters()->willReturn($parameters); 52 | $parameters->count()->willReturn(10); 53 | 54 | $qb->setParameter('comparison_10', sprintf($this->format, $this->value))->shouldBeCalled(); 55 | 56 | $this->transform($qb, $context)->shouldReturn(':comparison_10'); 57 | } 58 | 59 | public function it_is_executable(): void 60 | { 61 | $candidate = null; // not used 62 | 63 | $this->execute($candidate)->shouldReturn(sprintf($this->format, $this->value)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/AvgSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 19 | use Happyr\DoctrineSpecification\Operand\Operand; 20 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Avg; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin Avg 25 | */ 26 | final class AvgSpec extends ObjectBehavior 27 | { 28 | private $field = 'foo'; 29 | 30 | public function let(): void 31 | { 32 | $this->beConstructedWith($this->field); 33 | } 34 | 35 | public function it_is_a_count_distinct(): void 36 | { 37 | $this->shouldBeAnInstanceOf(Avg::class); 38 | } 39 | 40 | public function it_is_a_operand(): void 41 | { 42 | $this->shouldBeAnInstanceOf(Operand::class); 43 | } 44 | 45 | public function it_is_transformable(QueryBuilder $qb): void 46 | { 47 | $this->transform($qb, 'a')->shouldReturn('AVG(a.foo)'); 48 | } 49 | 50 | public function it_is_transformable_distinct(QueryBuilder $qb): void 51 | { 52 | $this->beConstructedWith($this->field, true); 53 | 54 | $this->transform($qb, 'a')->shouldReturn('AVG(DISTINCT a.foo)'); 55 | } 56 | 57 | public function it_is_executable(): void 58 | { 59 | $candidate = null; // not used 60 | 61 | $this->shouldThrow(OperandNotExecuteException::class)->duringExecute($candidate); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/CountSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 19 | use Happyr\DoctrineSpecification\Operand\Operand; 20 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Count; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin Count 25 | */ 26 | final class CountSpec extends ObjectBehavior 27 | { 28 | private $field = 'foo'; 29 | 30 | public function let(): void 31 | { 32 | $this->beConstructedWith($this->field); 33 | } 34 | 35 | public function it_is_a_count_distinct(): void 36 | { 37 | $this->shouldBeAnInstanceOf(Count::class); 38 | } 39 | 40 | public function it_is_a_operand(): void 41 | { 42 | $this->shouldBeAnInstanceOf(Operand::class); 43 | } 44 | 45 | public function it_is_transformable(QueryBuilder $qb): void 46 | { 47 | $this->transform($qb, 'a')->shouldReturn('COUNT(a.foo)'); 48 | } 49 | 50 | public function it_is_transformable_distinct(QueryBuilder $qb): void 51 | { 52 | $this->beConstructedWith($this->field, true); 53 | 54 | $this->transform($qb, 'a')->shouldReturn('COUNT(DISTINCT a.foo)'); 55 | } 56 | 57 | public function it_is_executable(): void 58 | { 59 | $candidate = null; // not used 60 | 61 | $this->shouldThrow(OperandNotExecuteException::class)->duringExecute($candidate); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/AbsExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\AbsExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin AbsExecutor 22 | */ 23 | final class AbsExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_return_absolute_value(): void 26 | { 27 | $this(-5)->shouldBe(5); 28 | $this(5)->shouldBe(5); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/BitAndExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\BitAndExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin BitAndExecutor 22 | */ 23 | final class BitAndExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_add_bit(): void 26 | { 27 | $this(1, 2)->shouldBe(0); 28 | $this(3, 2)->shouldBe(2); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/BitOrExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\BitOrExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin BitOrExecutor 22 | */ 23 | final class BitOrExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_or_bit(): void 26 | { 27 | $this(1, 2)->shouldBe(3); 28 | $this(3, 2)->shouldBe(3); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/ConcatExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\ConcatExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin ConcatExecutor 22 | */ 23 | final class ConcatExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_concat_strings(): void 26 | { 27 | $this('foo', 'bar')->shouldBe('foobar'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/CurrentDateExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\CurrentDateExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin CurrentDateExecutor 22 | */ 23 | final class CurrentDateExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_return_current_date(): void 26 | { 27 | $this()->shouldBeAnInstanceOf(\DateTimeImmutable::class); 28 | $this()->shouldBeWithDefaultTimeZone(); 29 | $this()->shouldBeCurrentDate(); 30 | } 31 | 32 | public function getMatchers(): array 33 | { 34 | return [ 35 | 'beWithDefaultTimeZone' => function (\DateTimeInterface $subject): bool { 36 | return $subject->getTimezone()->getName() === date_default_timezone_get(); 37 | }, 38 | 'beCurrentDate' => function (\DateTimeInterface $subject): bool { 39 | return $subject->getTimestamp() === (new \DateTimeImmutable())->setTime(0, 0)->getTimestamp(); 40 | }, 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/CurrentTimeExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\CurrentTimeExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin CurrentTimeExecutor 22 | */ 23 | final class CurrentTimeExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_return_current_time(): void 26 | { 27 | $this()->shouldBeAnInstanceOf(\DateTimeImmutable::class); 28 | $this()->shouldBeWithDefaultTimeZone(); 29 | $this()->shouldBeCurrentTime(); 30 | } 31 | 32 | public function getMatchers(): array 33 | { 34 | return [ 35 | 'beWithDefaultTimeZone' => function (\DateTimeInterface $subject): bool { 36 | return $subject->getTimezone()->getName() === date_default_timezone_get(); 37 | }, 38 | 'beCurrentTime' => function (\DateTimeInterface $subject): bool { 39 | return $subject->getTimestamp() === (new \DateTimeImmutable())->setDate(1, 1, 1)->getTimestamp(); 40 | }, 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/CurrentTimestampExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\CurrentTimestampExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin CurrentTimestampExecutor 22 | */ 23 | final class CurrentTimestampExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_return_current_timestamp(): void 26 | { 27 | $this()->shouldBeAnInstanceOf(\DateTimeImmutable::class); 28 | $this()->shouldBeWithDefaultTimeZone(); 29 | $this()->shouldBeCurrentTimestamp(); 30 | } 31 | 32 | public function getMatchers(): array 33 | { 34 | return [ 35 | 'beWithDefaultTimeZone' => function (\DateTimeInterface $subject): bool { 36 | return $subject->getTimezone()->getName() === date_default_timezone_get(); 37 | }, 38 | 'beCurrentTimestamp' => function (\DateTimeInterface $subject): bool { 39 | return $subject->getTimestamp() === (new \DateTimeImmutable())->getTimestamp(); 40 | }, 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/DateDiffExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\DateDiffExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin DateDiffExecutor 22 | */ 23 | final class DateDiffExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_make_a_date_diff(): void 26 | { 27 | $date1 = new \DateTimeImmutable('2019-01-12 11:24:46'); 28 | $date2 = new \DateTimeImmutable('2020-12-17 17:31:12'); 29 | 30 | $this($date1, $date2)->shouldBeSameDateInterval($date1->diff($date2)); 31 | } 32 | 33 | public function getMatchers(): array 34 | { 35 | return [ 36 | 'beSameDateInterval' => function (\DateInterval $subject, \DateInterval $expected): bool { 37 | return 38 | $subject->y === $expected->y && 39 | $subject->m === $expected->m && 40 | $subject->d === $expected->d && 41 | $subject->h === $expected->h && 42 | $subject->i === $expected->i && 43 | $subject->s === $expected->s && 44 | $subject->invert === $expected->invert 45 | ; 46 | }, 47 | ]; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/IdentityExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 18 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\IdentityExecutor; 19 | use PhpSpec\ObjectBehavior; 20 | 21 | /** 22 | * @mixin IdentityExecutor 23 | */ 24 | final class IdentityExecutorSpec extends ObjectBehavior 25 | { 26 | public function it_should_throw_exception_on_execute(): void 27 | { 28 | $this->shouldThrow(OperandNotExecuteException::class)->during('__invoke'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/LengthExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\LengthExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin LengthExecutor 22 | */ 23 | final class LengthExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_return_string_length(): void 26 | { 27 | $this('foo')->shouldBe(3); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/LocateExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\LocateExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin LocateExecutor 22 | */ 23 | final class LocateExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_not_found_string(): void 26 | { 27 | $this('foo', 'bar')->shouldBe(0); 28 | $this('foo', 'bar', 1)->shouldBe(0); 29 | } 30 | 31 | public function it_should_not_found_string_with_offset(): void 32 | { 33 | $this('foo', 'foobar', 3)->shouldBe(0); 34 | } 35 | 36 | public function it_should_found_string(): void 37 | { 38 | $this('foo', 'barfoobaz')->shouldBe(4); 39 | } 40 | 41 | public function it_should_found_string_with_offset(): void 42 | { 43 | $this('foo', 'barfoobaz', 3)->shouldBe(4); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/LowerExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\LowerExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin LowerExecutor 22 | */ 23 | final class LowerExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_return_value_in_lower_case(): void 26 | { 27 | $this('FoO bAr')->shouldBe('foo bar'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/ModExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\ModExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin ModExecutor 22 | */ 23 | final class ModExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_return_remainder_modulo(): void 26 | { 27 | $this(5.7, 1.3)->shouldBe(.5); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/PlatformFunctionExecutorRegistrySpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Exception\PlatformFunctionExecutorException; 18 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\PlatformFunctionExecutorRegistry; 19 | use PhpSpec\ObjectBehavior; 20 | 21 | /** 22 | * @mixin PlatformFunctionExecutorRegistry 23 | */ 24 | final class PlatformFunctionExecutorRegistrySpec extends ObjectBehavior 25 | { 26 | public function let(): void 27 | { 28 | $this->beConstructedWith([ 29 | 'abs' => 'abs', 30 | ]); 31 | } 32 | 33 | public function it_should_register_executor(): void 34 | { 35 | $executor = function (int $x, int $y): int { 36 | return $x + $y; 37 | }; 38 | 39 | $this->register('Sum', $executor); 40 | $this->has('sum')->shouldBe(true); 41 | $this->has('SUM')->shouldBe(true); 42 | } 43 | 44 | public function it_should_throw_exception_on_register_exist_executor(): void 45 | { 46 | $executor = function ($x) { 47 | return $x > 0 ? $x : $x * -1; 48 | }; 49 | 50 | $this->shouldThrow(PlatformFunctionExecutorException::class)->during('register', ['abs', $executor]); 51 | } 52 | 53 | public function it_should_override_executor(): void 54 | { 55 | $executor = function ($x) { 56 | return $x > 0 ? $x : $x * -1; 57 | }; 58 | 59 | $this->override('Abs', $executor); 60 | $this->has('abs')->shouldBe(true); 61 | $this->has('ABS')->shouldBe(true); 62 | } 63 | 64 | public function it_should_throw_exception_on_override_undefined_executor(): void 65 | { 66 | $executor = function (int $x, int $y): int { 67 | return $x + $y; 68 | }; 69 | 70 | $this->shouldThrow(PlatformFunctionExecutorException::class)->during('override', ['sum', $executor]); 71 | } 72 | 73 | public function it_should_execute(): void 74 | { 75 | $this->execute('abs', -5)->shouldBe(5); 76 | $this->execute('abs', 5)->shouldBe(5); 77 | } 78 | 79 | public function it_should_execute_custom_executor(): void 80 | { 81 | $executor = function (int $x, int $y): int { 82 | return $x + $y; 83 | }; 84 | 85 | $this->register('Sum', $executor); 86 | $this->execute('sum', 2, 3)->shouldBe(5); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/SizeExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\SizeExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin SizeExecutor 22 | */ 23 | final class SizeExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_return_array_size(): void 26 | { 27 | $this(['foo', 'bar'])->shouldBe(2); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/SqrtExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\SqrtExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin SqrtExecutor 22 | */ 23 | final class SqrtExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_return_square_root(): void 26 | { 27 | $this(9.0)->shouldBe(3.0); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/SubstringExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\SubstringExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin SubstringExecutor 22 | */ 23 | final class SubstringExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_substring_value(): void 26 | { 27 | $this('foo bar', 0)->shouldBe('foo bar'); 28 | $this('foo bar', 4)->shouldBe('bar'); 29 | $this('foo bar', -3)->shouldBe('bar'); 30 | $this('foo bar', 0, 3)->shouldBe('foo'); 31 | $this('foo bar', 0, -4)->shouldBe('foo'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/Executor/UpperExecutorSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor; 16 | 17 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Executor\UpperExecutor; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * @mixin UpperExecutor 22 | */ 23 | final class UpperExecutorSpec extends ObjectBehavior 24 | { 25 | public function it_should_return_value_in_upper_case(): void 26 | { 27 | $this('fOo BaR')->shouldBe('FOO BAR'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/MaxSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 19 | use Happyr\DoctrineSpecification\Operand\Operand; 20 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Max; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin Max 25 | */ 26 | final class MaxSpec extends ObjectBehavior 27 | { 28 | private $field = 'foo'; 29 | 30 | public function let(): void 31 | { 32 | $this->beConstructedWith($this->field); 33 | } 34 | 35 | public function it_is_a_count_distinct(): void 36 | { 37 | $this->shouldBeAnInstanceOf(Max::class); 38 | } 39 | 40 | public function it_is_a_operand(): void 41 | { 42 | $this->shouldBeAnInstanceOf(Operand::class); 43 | } 44 | 45 | public function it_is_transformable(QueryBuilder $qb): void 46 | { 47 | $this->transform($qb, 'a')->shouldReturn('MAX(a.foo)'); 48 | } 49 | 50 | public function it_is_transformable_distinct(QueryBuilder $qb): void 51 | { 52 | $this->beConstructedWith($this->field, true); 53 | 54 | $this->transform($qb, 'a')->shouldReturn('MAX(DISTINCT a.foo)'); 55 | } 56 | 57 | public function it_is_executable(): void 58 | { 59 | $candidate = null; // not used 60 | 61 | $this->shouldThrow(OperandNotExecuteException::class)->duringExecute($candidate); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/MinSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 19 | use Happyr\DoctrineSpecification\Operand\Operand; 20 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Min; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin Min 25 | */ 26 | final class MinSpec extends ObjectBehavior 27 | { 28 | private $field = 'foo'; 29 | 30 | public function let(): void 31 | { 32 | $this->beConstructedWith($this->field); 33 | } 34 | 35 | public function it_is_a_count_distinct(): void 36 | { 37 | $this->shouldBeAnInstanceOf(Min::class); 38 | } 39 | 40 | public function it_is_a_operand(): void 41 | { 42 | $this->shouldBeAnInstanceOf(Operand::class); 43 | } 44 | 45 | public function it_is_transformable(QueryBuilder $qb): void 46 | { 47 | $this->transform($qb, 'a')->shouldReturn('MIN(a.foo)'); 48 | } 49 | 50 | public function it_is_transformable_distinct(QueryBuilder $qb): void 51 | { 52 | $this->beConstructedWith($this->field, true); 53 | 54 | $this->transform($qb, 'a')->shouldReturn('MIN(DISTINCT a.foo)'); 55 | } 56 | 57 | public function it_is_executable(): void 58 | { 59 | $candidate = null; // not used 60 | 61 | $this->shouldThrow(OperandNotExecuteException::class)->duringExecute($candidate); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Operand/PlatformFunction/SumSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand\PlatformFunction; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Exception\OperandNotExecuteException; 19 | use Happyr\DoctrineSpecification\Operand\Operand; 20 | use Happyr\DoctrineSpecification\Operand\PlatformFunction\Sum; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin Sum 25 | */ 26 | final class SumSpec extends ObjectBehavior 27 | { 28 | private $field = 'foo'; 29 | 30 | public function let(): void 31 | { 32 | $this->beConstructedWith($this->field); 33 | } 34 | 35 | public function it_is_a_count_distinct(): void 36 | { 37 | $this->shouldBeAnInstanceOf(Sum::class); 38 | } 39 | 40 | public function it_is_a_operand(): void 41 | { 42 | $this->shouldBeAnInstanceOf(Operand::class); 43 | } 44 | 45 | public function it_is_transformable(QueryBuilder $qb): void 46 | { 47 | $this->transform($qb, 'a')->shouldReturn('SUM(a.foo)'); 48 | } 49 | 50 | public function it_is_transformable_distinct(QueryBuilder $qb): void 51 | { 52 | $this->beConstructedWith($this->field, true); 53 | 54 | $this->transform($qb, 'a')->shouldReturn('SUM(DISTINCT a.foo)'); 55 | } 56 | 57 | public function it_is_executable(): void 58 | { 59 | $candidate = null; // not used 60 | 61 | $this->shouldThrow(OperandNotExecuteException::class)->duringExecute($candidate); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Operand/ValueSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand; 16 | 17 | use Doctrine\Common\Collections\ArrayCollection; 18 | use Doctrine\ORM\QueryBuilder; 19 | use Happyr\DoctrineSpecification\Operand\Operand; 20 | use Happyr\DoctrineSpecification\Operand\Value; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin Value 25 | */ 26 | final class ValueSpec extends ObjectBehavior 27 | { 28 | private $value = 'foo'; 29 | 30 | private $valueType; 31 | 32 | public function let(): void 33 | { 34 | $this->beConstructedWith($this->value, $this->valueType); 35 | } 36 | 37 | public function it_is_a_value(): void 38 | { 39 | $this->shouldBeAnInstanceOf(Value::class); 40 | } 41 | 42 | public function it_is_a_operand(): void 43 | { 44 | $this->shouldBeAnInstanceOf(Operand::class); 45 | } 46 | 47 | public function it_is_transformable(QueryBuilder $qb, ArrayCollection $parameters): void 48 | { 49 | $context = 'a'; 50 | 51 | $qb->getParameters()->willReturn($parameters); 52 | $parameters->count()->willReturn(10); 53 | 54 | $qb->setParameter('comparison_10', $this->value, $this->valueType)->shouldBeCalled(); 55 | 56 | $this->transform($qb, $context)->shouldReturn(':comparison_10'); 57 | } 58 | 59 | public function it_is_transformable_dbal_type(QueryBuilder $qb, ArrayCollection $parameters): void 60 | { 61 | $valueType = 'date'; 62 | $this->beConstructedWith($this->value, $valueType); 63 | 64 | $qb->getParameters()->willReturn($parameters); 65 | $parameters->count()->willReturn(10); 66 | 67 | $qb->setParameter('comparison_10', $this->value, $valueType)->shouldBeCalled(); 68 | 69 | $this->transform($qb, 'a')->shouldReturn(':comparison_10'); 70 | } 71 | 72 | public function it_is_transformable_pdo_type(QueryBuilder $qb, ArrayCollection $parameters): void 73 | { 74 | $valueType = \PDO::PARAM_INT; 75 | $this->beConstructedWith($this->value, $valueType); 76 | 77 | $qb->getParameters()->willReturn($parameters); 78 | $parameters->count()->willReturn(10); 79 | 80 | $qb->setParameter('comparison_10', $this->value, $valueType)->shouldBeCalled(); 81 | 82 | $this->transform($qb, 'a')->shouldReturn(':comparison_10'); 83 | } 84 | 85 | public function it_is_executable(): void 86 | { 87 | $candidate = null; // not used 88 | 89 | $this->execute($candidate)->shouldReturn($this->value); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/Operand/ValuesSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Operand; 16 | 17 | use Doctrine\Common\Collections\ArrayCollection; 18 | use Doctrine\ORM\QueryBuilder; 19 | use Happyr\DoctrineSpecification\Operand\Operand; 20 | use Happyr\DoctrineSpecification\Operand\Values; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin Values 25 | */ 26 | final class ValuesSpec extends ObjectBehavior 27 | { 28 | private $values = ['foo', 'bar']; 29 | 30 | private $valueType; 31 | 32 | public function let(): void 33 | { 34 | $this->beConstructedWith($this->values, $this->valueType); 35 | } 36 | 37 | public function it_is_a_values(): void 38 | { 39 | $this->shouldBeAnInstanceOf(Values::class); 40 | } 41 | 42 | public function it_is_a_operand(): void 43 | { 44 | $this->shouldBeAnInstanceOf(Operand::class); 45 | } 46 | 47 | public function it_is_transformable(QueryBuilder $qb, ArrayCollection $parameters): void 48 | { 49 | $context = 'a'; 50 | 51 | $qb->getParameters()->willReturn($parameters); 52 | $parameters->count()->willReturn(10); 53 | 54 | $qb->setParameter('comparison_10', $this->values, $this->valueType)->shouldBeCalled(); 55 | 56 | $this->transform($qb, $context)->shouldReturn(':comparison_10'); 57 | } 58 | 59 | public function it_is_transformable_dbal_type(QueryBuilder $qb, ArrayCollection $parameters): void 60 | { 61 | $valueType = 'date'; 62 | $this->beConstructedWith($this->values, $valueType); 63 | 64 | $qb->getParameters()->willReturn($parameters); 65 | $parameters->count()->willReturn(10); 66 | 67 | $qb->setParameter('comparison_10', $this->values, $valueType)->shouldBeCalled(); 68 | 69 | $this->transform($qb, 'a')->shouldReturn(':comparison_10'); 70 | } 71 | 72 | public function it_is_transformable_pdo_type(QueryBuilder $qb, ArrayCollection $parameters): void 73 | { 74 | $valueType = \PDO::PARAM_INT; 75 | $this->beConstructedWith($this->values, $valueType); 76 | 77 | $qb->getParameters()->willReturn($parameters); 78 | $parameters->count()->willReturn(10); 79 | 80 | $qb->setParameter('comparison_10', $this->values, $valueType)->shouldBeCalled(); 81 | 82 | $this->transform($qb, 'a')->shouldReturn(':comparison_10'); 83 | } 84 | 85 | public function it_is_executable(): void 86 | { 87 | $candidate = null; // not used 88 | 89 | $this->execute($candidate)->shouldReturn($this->values); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/Player.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification; 16 | 17 | final class Player 18 | { 19 | /** 20 | * @var string 21 | */ 22 | public $pseudo; 23 | 24 | /** 25 | * @var string 26 | */ 27 | public $gender; 28 | 29 | /** 30 | * @var int|null 31 | */ 32 | public $points; 33 | 34 | /** 35 | * @var Game|null 36 | */ 37 | public $inGame; 38 | 39 | /** 40 | * @param string $pseudo 41 | * @param string $gender 42 | * @param int|null $points 43 | * @param Game|null $game 44 | */ 45 | public function __construct(string $pseudo, string $gender, ?int $points, ?Game $game = null) 46 | { 47 | $this->pseudo = $pseudo; 48 | $this->gender = $gender; 49 | $this->points = $points; 50 | $this->inGame = $game; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Query/AddSelectSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Operand\Field; 19 | use Happyr\DoctrineSpecification\Query\AddSelect; 20 | use Happyr\DoctrineSpecification\Query\QueryModifier; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin AddSelect 25 | */ 26 | final class AddSelectSpec extends ObjectBehavior 27 | { 28 | public function let(): void 29 | { 30 | $this->beConstructedWith('foo'); 31 | } 32 | 33 | public function it_is_a_add_select(): void 34 | { 35 | $this->shouldBeAnInstanceOf(AddSelect::class); 36 | } 37 | 38 | public function it_is_a_query_modifier(): void 39 | { 40 | $this->shouldHaveType(QueryModifier::class); 41 | } 42 | 43 | public function it_add_select_single_filed(QueryBuilder $qb): void 44 | { 45 | $qb->addSelect(['a.foo'])->shouldBeCalled(); 46 | $this->modify($qb, 'a'); 47 | } 48 | 49 | public function it_add_select_several_fields(QueryBuilder $qb): void 50 | { 51 | $this->beConstructedWith('foo', 'bar'); 52 | $qb->addSelect(['b.foo', 'b.bar'])->shouldBeCalled(); 53 | $this->modify($qb, 'b'); 54 | } 55 | 56 | public function it_add_select_operand(QueryBuilder $qb): void 57 | { 58 | $this->beConstructedWith('foo', new Field('bar')); 59 | $qb->addSelect(['b.foo', 'b.bar'])->shouldBeCalled(); 60 | $this->modify($qb, 'b'); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Query/DistinctSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Query\Distinct; 19 | use Happyr\DoctrineSpecification\Query\QueryModifier; 20 | use PhpSpec\ObjectBehavior; 21 | 22 | /** 23 | * @mixin Distinct 24 | */ 25 | final class DistinctSpec extends ObjectBehavior 26 | { 27 | public function it_is_a_distinct(): void 28 | { 29 | $this->shouldBeAnInstanceOf(Distinct::class); 30 | } 31 | 32 | public function it_is_a_query_modifier(): void 33 | { 34 | $this->shouldHaveType(QueryModifier::class); 35 | } 36 | 37 | public function it_add_having(QueryBuilder $qb): void 38 | { 39 | $qb->distinct()->shouldBeCalled(); 40 | $this->modify($qb, 'a'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Query/HavingSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Filter\Filter; 19 | use Happyr\DoctrineSpecification\Query\Having; 20 | use Happyr\DoctrineSpecification\Query\QueryModifier; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin Having 25 | */ 26 | final class HavingSpec extends ObjectBehavior 27 | { 28 | public function let(Filter $filter): void 29 | { 30 | $this->beConstructedWith($filter); 31 | } 32 | 33 | public function it_is_a_having(): void 34 | { 35 | $this->shouldBeAnInstanceOf(Having::class); 36 | } 37 | 38 | public function it_is_a_query_modifier(): void 39 | { 40 | $this->shouldHaveType(QueryModifier::class); 41 | } 42 | 43 | public function it_add_having(QueryBuilder $qb, Filter $filter): void 44 | { 45 | $this->beConstructedWith($filter); 46 | $filter->getFilter($qb, 'a')->willReturn('foo = :bar'); 47 | $qb->having('foo = :bar')->shouldBeCalled(); 48 | $this->modify($qb, 'a'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Query/IndexBySpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Query\IndexBy; 19 | use Happyr\DoctrineSpecification\Query\QueryModifier; 20 | use PhpSpec\ObjectBehavior; 21 | 22 | /** 23 | * @mixin IndexBy 24 | */ 25 | final class IndexBySpec extends ObjectBehavior 26 | { 27 | private $field = 'the_field'; 28 | 29 | private $alias = 'f'; 30 | 31 | public function let(): void 32 | { 33 | $this->beConstructedWith($this->field, null); 34 | } 35 | 36 | public function it_is_a_query_modifier(): void 37 | { 38 | $this->shouldBeAnInstanceOf(QueryModifier::class); 39 | } 40 | 41 | public function it_indexes(QueryBuilder $qb): void 42 | { 43 | $qb->indexBy('a', sprintf('a.%s', $this->field))->shouldBeCalled(); 44 | 45 | $this->modify($qb, 'a'); 46 | } 47 | 48 | public function it_indexes_in_context(QueryBuilder $qb): void 49 | { 50 | $this->beConstructedWith('thing', 'user'); 51 | 52 | $qb->indexBy('user', 'user.thing')->shouldBeCalled(); 53 | 54 | $qb->getDQLPart('join')->willReturn([]); 55 | $qb->getAllAliases()->willReturn([]); 56 | $qb->join('root.user', 'user')->willReturn($qb); 57 | 58 | $this->modify($qb, 'root'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Query/InnerJoinSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Query\InnerJoin; 19 | use Happyr\DoctrineSpecification\Query\QueryModifier; 20 | use PhpSpec\ObjectBehavior; 21 | 22 | /** 23 | * @mixin InnerJoin 24 | */ 25 | final class InnerJoinSpec extends ObjectBehavior 26 | { 27 | public function let(): void 28 | { 29 | $this->beConstructedWith('user', 'authUser', null); 30 | } 31 | 32 | public function it_is_a_query_modifier(): void 33 | { 34 | $this->shouldHaveType(QueryModifier::class); 35 | } 36 | 37 | public function it_joins_with_default_dql_alias(QueryBuilder $qb): void 38 | { 39 | $qb->innerJoin('a.user', 'authUser')->shouldBeCalled(); 40 | 41 | $this->modify($qb, 'a'); 42 | } 43 | 44 | public function it_joins_in_context(QueryBuilder $qb): void 45 | { 46 | $this->beConstructedWith('user', 'authUser', 'x'); 47 | 48 | $qb->innerJoin('x.user', 'authUser')->shouldBeCalled(); 49 | 50 | $qb->getDQLPart('join')->willReturn([]); 51 | $qb->getAllAliases()->willReturn([]); 52 | $qb->join('root.x', 'x')->willReturn($qb); 53 | 54 | $this->modify($qb, 'root'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Query/JoinSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Query\Join; 19 | use Happyr\DoctrineSpecification\Query\QueryModifier; 20 | use PhpSpec\ObjectBehavior; 21 | 22 | /** 23 | * @mixin Join 24 | */ 25 | final class JoinSpec extends ObjectBehavior 26 | { 27 | public function let(): void 28 | { 29 | $this->beConstructedWith('user', 'authUser', null); 30 | } 31 | 32 | public function it_is_a_query_modifier(): void 33 | { 34 | $this->shouldHaveType(QueryModifier::class); 35 | } 36 | 37 | public function it_joins_with_default_dql_alias(QueryBuilder $qb): void 38 | { 39 | $qb->join('a.user', 'authUser')->shouldBeCalled(); 40 | 41 | $this->modify($qb, 'a'); 42 | } 43 | 44 | public function it_joins_in_context(QueryBuilder $qb): void 45 | { 46 | $this->beConstructedWith('user', 'authUser', 'x'); 47 | 48 | $qb->join('x.user', 'authUser')->shouldBeCalled(); 49 | 50 | $qb->getDQLPart('join')->willReturn([]); 51 | $qb->getAllAliases()->willReturn([]); 52 | $qb->join('root.x', 'x')->willReturn($qb); 53 | 54 | $this->modify($qb, 'root'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Query/LeftJoinSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Query\LeftJoin; 19 | use Happyr\DoctrineSpecification\Query\QueryModifier; 20 | use PhpSpec\ObjectBehavior; 21 | 22 | /** 23 | * @mixin LeftJoin 24 | */ 25 | final class LeftJoinSpec extends ObjectBehavior 26 | { 27 | public function let(): void 28 | { 29 | $this->beConstructedWith('user', 'authUser', null); 30 | } 31 | 32 | public function it_is_a_query_modifier(): void 33 | { 34 | $this->shouldHaveType(QueryModifier::class); 35 | } 36 | 37 | public function it_joins_with_default_dql_alias(QueryBuilder $qb): void 38 | { 39 | $qb->leftJoin('a.user', 'authUser')->shouldBeCalled(); 40 | 41 | $this->modify($qb, 'a'); 42 | } 43 | 44 | public function it_joins_in_context(QueryBuilder $qb): void 45 | { 46 | $this->beConstructedWith('user', 'authUser', 'x'); 47 | 48 | $qb->leftJoin('x.user', 'authUser')->shouldBeCalled(); 49 | 50 | $qb->getDQLPart('join')->willReturn([]); 51 | $qb->getAllAliases()->willReturn([]); 52 | $qb->join('root.x', 'x')->willReturn($qb); 53 | 54 | $this->modify($qb, 'root'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Query/SelectNewSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\Common\Collections\ArrayCollection; 18 | use Doctrine\ORM\QueryBuilder; 19 | use Happyr\DoctrineSpecification\Operand\Addition; 20 | use Happyr\DoctrineSpecification\Operand\Field; 21 | use Happyr\DoctrineSpecification\Operand\Value; 22 | use Happyr\DoctrineSpecification\Query\QueryModifier; 23 | use Happyr\DoctrineSpecification\Query\SelectNew; 24 | use PhpSpec\ObjectBehavior; 25 | use tests\Happyr\DoctrineSpecification\Player; 26 | 27 | /** 28 | * @mixin SelectNew 29 | */ 30 | final class SelectNewSpec extends ObjectBehavior 31 | { 32 | public function let(): void 33 | { 34 | $this->beConstructedWith(Player::class); 35 | } 36 | 37 | public function it_is_a_select_new(): void 38 | { 39 | $this->shouldBeAnInstanceOf(SelectNew::class); 40 | } 41 | 42 | public function it_is_a_query_modifier(): void 43 | { 44 | $this->shouldHaveType(QueryModifier::class); 45 | } 46 | 47 | public function it_select_empty_object(QueryBuilder $qb): void 48 | { 49 | $qb->select(sprintf('NEW %s()', Player::class))->shouldBeCalled(); 50 | 51 | $this->modify($qb, 'a'); 52 | } 53 | 54 | public function it_select_with_field(QueryBuilder $qb): void 55 | { 56 | $this->beConstructedWith(Player::class, 'pseudo'); 57 | 58 | $qb->select(sprintf('NEW %s(a.pseudo)', Player::class))->shouldBeCalled(); 59 | 60 | $this->modify($qb, 'a'); 61 | } 62 | 63 | public function it_select_with_field_and_value(QueryBuilder $qb, ArrayCollection $parameters): void 64 | { 65 | $this->beConstructedWith(Player::class, 'pseudo', 'F'); 66 | 67 | $qb->getParameters()->willReturn($parameters); 68 | $parameters->count()->willReturn(10); 69 | 70 | $qb->setParameter('comparison_10', 'F', null)->shouldBeCalled(); 71 | $qb->select(sprintf('NEW %s(a.pseudo, :comparison_10)', Player::class))->shouldBeCalled(); 72 | 73 | $this->modify($qb, 'a'); 74 | } 75 | 76 | public function it_select_with_operands(QueryBuilder $qb, ArrayCollection $parameters): void 77 | { 78 | $this->beConstructedWith( 79 | Player::class, 80 | new Field('pseudo'), 81 | new Value('F'), 82 | new Addition(new Field('foo'), new Field('bar')) 83 | ); 84 | 85 | $qb->getParameters()->willReturn($parameters); 86 | $parameters->count()->willReturn(10); 87 | 88 | $qb->setParameter('comparison_10', 'F', null)->shouldBeCalled(); 89 | $qb->select(sprintf('NEW %s(a.pseudo, :comparison_10, (a.foo + a.bar))', Player::class))->shouldBeCalled(); 90 | 91 | $this->modify($qb, 'a'); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/Query/SelectSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Operand\Field; 19 | use Happyr\DoctrineSpecification\Query\QueryModifier; 20 | use Happyr\DoctrineSpecification\Query\Select; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin Select 25 | */ 26 | final class SelectSpec extends ObjectBehavior 27 | { 28 | public function let(): void 29 | { 30 | $this->beConstructedWith('foo'); 31 | } 32 | 33 | public function it_is_a_select(): void 34 | { 35 | $this->shouldBeAnInstanceOf(Select::class); 36 | } 37 | 38 | public function it_is_a_query_modifier(): void 39 | { 40 | $this->shouldHaveType(QueryModifier::class); 41 | } 42 | 43 | public function it_select_single_filed(QueryBuilder $qb): void 44 | { 45 | $qb->select(['a.foo'])->shouldBeCalled(); 46 | $this->modify($qb, 'a'); 47 | } 48 | 49 | public function it_select_several_fields(QueryBuilder $qb): void 50 | { 51 | $this->beConstructedWith('foo', 'bar'); 52 | $qb->select(['b.foo', 'b.bar'])->shouldBeCalled(); 53 | $this->modify($qb, 'b'); 54 | } 55 | 56 | public function it_select_operand(QueryBuilder $qb): void 57 | { 58 | $this->beConstructedWith('foo', new Field('bar')); 59 | $qb->select(['b.foo', 'b.bar'])->shouldBeCalled(); 60 | $this->modify($qb, 'b'); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Query/Selection/ArgumentToSelectionConverterSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Query\Selection; 16 | 17 | use Happyr\DoctrineSpecification\Operand\Field; 18 | use Happyr\DoctrineSpecification\Query\Selection\ArgumentToSelectionConverter; 19 | use PhpSpec\ObjectBehavior; 20 | 21 | /** 22 | * @mixin ArgumentToSelectionConverter 23 | */ 24 | final class ArgumentToSelectionConverterSpec extends ObjectBehavior 25 | { 26 | public function it_is_a_converter(): void 27 | { 28 | $this->shouldBeAnInstanceOf(ArgumentToSelectionConverter::class); 29 | } 30 | 31 | public function it_not_convert_field_to_selection(): void 32 | { 33 | $field = new Field('foo'); 34 | 35 | $this->toSelection($field)->shouldReturn($field); 36 | } 37 | 38 | public function it_convert_argument_to_field(): void 39 | { 40 | $this->toSelection('foo')->shouldBeAnInstanceOf(Field::class); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Query/Selection/SelectEntitySpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Query\Selection; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Query\Selection\SelectEntity; 19 | use Happyr\DoctrineSpecification\Query\Selection\Selection; 20 | use PhpSpec\ObjectBehavior; 21 | 22 | /** 23 | * @mixin SelectEntity 24 | */ 25 | final class SelectEntitySpec extends ObjectBehavior 26 | { 27 | private $dqlAlias = 'u'; 28 | 29 | public function let(): void 30 | { 31 | $this->beConstructedWith($this->dqlAlias); 32 | } 33 | 34 | public function it_is_a_select_entity(): void 35 | { 36 | $this->shouldBeAnInstanceOf(SelectEntity::class); 37 | } 38 | 39 | public function it_is_a_selection(): void 40 | { 41 | $this->shouldBeAnInstanceOf(Selection::class); 42 | } 43 | 44 | public function it_is_transformable(QueryBuilder $qb): void 45 | { 46 | $qb->getDQLPart('join')->willReturn([]); 47 | $qb->getAllAliases()->willReturn([]); 48 | $qb->join(sprintf('a.%s', $this->dqlAlias), $this->dqlAlias)->willReturn($qb); 49 | 50 | $this->transform($qb, 'a')->shouldReturn($this->dqlAlias); 51 | } 52 | 53 | public function it_is_transformable_in_context(QueryBuilder $qb): void 54 | { 55 | $context = 'foo.bar'; 56 | 57 | $this->beConstructedWith(sprintf('%s.%s', $context, $this->dqlAlias)); 58 | 59 | $qb->getDQLPart('join')->willReturn([]); 60 | $qb->getAllAliases()->willReturn([]); 61 | $qb->join('a.foo', 'foo')->willReturn($qb); 62 | $qb->join('foo.bar', 'bar')->willReturn($qb); 63 | $qb->join(sprintf('bar.%s', $this->dqlAlias), $this->dqlAlias)->willReturn($qb); 64 | 65 | $this->transform($qb, 'a')->shouldReturn($this->dqlAlias); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/Query/SliceSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Query; 16 | 17 | use Doctrine\ORM\QueryBuilder; 18 | use Happyr\DoctrineSpecification\Query\QueryModifier; 19 | use Happyr\DoctrineSpecification\Query\Slice; 20 | use PhpSpec\ObjectBehavior; 21 | 22 | /** 23 | * @mixin Slice 24 | */ 25 | final class SliceSpec extends ObjectBehavior 26 | { 27 | /** 28 | * @var int 29 | */ 30 | private $sliceSize = 25; 31 | 32 | public function let(): void 33 | { 34 | $this->beConstructedWith($this->sliceSize, 0); 35 | } 36 | 37 | public function it_is_a_query_modifier(): void 38 | { 39 | $this->shouldHaveType(QueryModifier::class); 40 | } 41 | 42 | public function it_slice_with_zero_index(QueryBuilder $qb): void 43 | { 44 | $this->beConstructedWith($this->sliceSize, 0); 45 | 46 | $qb->setMaxResults($this->sliceSize)->shouldBeCalled(); 47 | 48 | $this->modify($qb, 'a'); 49 | } 50 | 51 | public function it_slice_with_second_index(QueryBuilder $qb): void 52 | { 53 | $sliceNumber = 1; 54 | 55 | $this->beConstructedWith($this->sliceSize, $sliceNumber); 56 | 57 | $qb->setMaxResults($this->sliceSize)->shouldBeCalled(); 58 | $qb->setFirstResult($this->sliceSize * $sliceNumber)->shouldBeCalled(); 59 | 60 | $this->modify($qb, 'a'); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Result/AsArraySpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Result; 16 | 17 | use Doctrine\ORM\AbstractQuery; 18 | use Doctrine\ORM\Query; 19 | use Happyr\DoctrineSpecification\Result\AsArray; 20 | use Happyr\DoctrineSpecification\Result\ResultModifier; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin AsArray 25 | */ 26 | final class AsArraySpec extends ObjectBehavior 27 | { 28 | public function it_is_a_result_modifier(): void 29 | { 30 | $this->shouldBeAnInstanceOf(ResultModifier::class); 31 | } 32 | 33 | public function it_sets_hydration_mode_to_array(AbstractQuery $query): void 34 | { 35 | $query->setHydrationMode(Query::HYDRATE_ARRAY)->shouldBeCalled(); 36 | 37 | $this->modify($query); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Result/AsScalarSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Result; 16 | 17 | use Doctrine\ORM\AbstractQuery; 18 | use Doctrine\ORM\Query; 19 | use Happyr\DoctrineSpecification\Result\AsScalar; 20 | use Happyr\DoctrineSpecification\Result\ResultModifier; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin AsScalar 25 | */ 26 | final class AsScalarSpec extends ObjectBehavior 27 | { 28 | public function it_is_a_result_modifier(): void 29 | { 30 | $this->shouldBeAnInstanceOf(ResultModifier::class); 31 | } 32 | 33 | public function it_sets_hydration_mode_to_object(AbstractQuery $query): void 34 | { 35 | $query->setHydrationMode(Query::HYDRATE_SCALAR)->shouldBeCalled(); 36 | 37 | $this->modify($query); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Result/AsSingleScalarSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Result; 16 | 17 | use Doctrine\ORM\AbstractQuery; 18 | use Doctrine\ORM\Query; 19 | use Happyr\DoctrineSpecification\Result\AsSingleScalar; 20 | use Happyr\DoctrineSpecification\Result\ResultModifier; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin AsSingleScalar 25 | */ 26 | final class AsSingleScalarSpec extends ObjectBehavior 27 | { 28 | public function it_is_a_result_modifier(): void 29 | { 30 | $this->shouldBeAnInstanceOf(ResultModifier::class); 31 | } 32 | 33 | public function it_sets_hydration_mode_to_object(AbstractQuery $query): void 34 | { 35 | $query->setHydrationMode(Query::HYDRATE_SINGLE_SCALAR)->shouldBeCalled(); 36 | 37 | $this->modify($query); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Result/CacheSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Result; 16 | 17 | use Doctrine\ORM\AbstractQuery; 18 | use Happyr\DoctrineSpecification\Result\Cache; 19 | use Happyr\DoctrineSpecification\Result\ResultModifier; 20 | use PhpSpec\ObjectBehavior; 21 | 22 | /** 23 | * @mixin Cache 24 | */ 25 | final class CacheSpec extends ObjectBehavior 26 | { 27 | private $lifetime = 3600; 28 | 29 | public function let(): void 30 | { 31 | $this->beConstructedWith($this->lifetime); 32 | } 33 | 34 | public function it_is_a_specification(): void 35 | { 36 | $this->shouldBeAnInstanceOf(ResultModifier::class); 37 | } 38 | 39 | public function it_caches_query_for_given_time(AbstractQuery $query): void 40 | { 41 | $query->setResultCacheLifetime($this->lifetime)->shouldBeCalled(); 42 | 43 | $this->modify($query); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Result/RoundDateTimeSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Result; 16 | 17 | use Doctrine\Common\Collections\ArrayCollection; 18 | use Doctrine\ORM\AbstractQuery; 19 | use Doctrine\ORM\Query\Parameter; 20 | use Happyr\DoctrineSpecification\Result\RoundDateTime; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * @mixin RoundDateTime 25 | */ 26 | final class RoundDateTimeSpec extends ObjectBehavior 27 | { 28 | private $roundSeconds = 3600; 29 | 30 | public function let(): void 31 | { 32 | $this->beConstructedWith($this->roundSeconds); 33 | } 34 | 35 | public function it_is_a_specification(): void 36 | { 37 | $this->shouldBeAnInstanceOf(RoundDateTime::class); 38 | } 39 | 40 | public function it_round_date_time_in_query_parameters_for_given_time(AbstractQuery $query): void 41 | { 42 | $name = 'now'; 43 | $type = 'datetime'; 44 | $actual = new \DateTime('15:55:34'); 45 | $expected = new \DateTimeImmutable('15:00:00'); 46 | 47 | $query->getParameters()->willReturn(new ArrayCollection([ 48 | new Parameter('status', 'active'), // scalar param 49 | new Parameter($name, $actual, $type), 50 | ])); 51 | $query->setParameter($name, $expected, $type)->shouldBeCalled(); 52 | 53 | $this->modify($query); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Specification/CountOfSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * Kacper Gunia 9 | * Peter Gribanov 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace tests\Happyr\DoctrineSpecification\Specification; 16 | 17 | use Doctrine\Common\Collections\ArrayCollection; 18 | use Doctrine\ORM\QueryBuilder; 19 | use Happyr\DoctrineSpecification\Filter\Equals; 20 | use Happyr\DoctrineSpecification\Query\GroupBy; 21 | use Happyr\DoctrineSpecification\Specification\CountOf; 22 | use Happyr\DoctrineSpecification\Specification\Specification; 23 | use PhpSpec\ObjectBehavior; 24 | 25 | /** 26 | * @mixin CountOf 27 | */ 28 | final class CountOfSpec extends ObjectBehavior 29 | { 30 | public function let(): void 31 | { 32 | $this->beConstructedWith(null); 33 | } 34 | 35 | public function it_is_a_CountOf(): void 36 | { 37 | $this->shouldBeAnInstanceOf(CountOf::class); 38 | } 39 | 40 | public function it_is_a_specification(): void 41 | { 42 | $this->shouldHaveType(Specification::class); 43 | } 44 | 45 | public function it_count_of_all(QueryBuilder $qb): void 46 | { 47 | $context = 'a'; 48 | 49 | $qb->select(sprintf('COUNT(%s)', $context))->shouldBeCalled(); 50 | 51 | $this->getFilter($qb, $context)->shouldBe(''); 52 | $this->modify($qb, $context); 53 | } 54 | 55 | public function it_count_of_all_grouped_by_id(QueryBuilder $qb): void 56 | { 57 | $field = 'id'; 58 | $context = 'user'; 59 | 60 | $this->beConstructedWith(new GroupBy($field, $context)); 61 | 62 | $qb->select('COUNT(root)')->shouldBeCalled(); 63 | $qb->addGroupBy(sprintf('%s.%s', $context, $field))->shouldBeCalled(); 64 | 65 | $qb->getDQLPart('join')->willReturn([]); 66 | $qb->getAllAliases()->willReturn([]); 67 | $qb->join('root.user', 'user')->willReturn($qb); 68 | 69 | $this->getFilter($qb, 'root')->shouldBe(''); 70 | $this->modify($qb, 'root'); 71 | } 72 | 73 | public function it_count_of_all_with_group_is_foo(QueryBuilder $qb): void 74 | { 75 | $field = 'group'; 76 | $value = 'foo'; 77 | $context = 'user'; 78 | $parametersCount = 0; 79 | $paramName = 'comparison_'.$parametersCount; 80 | 81 | $this->beConstructedWith(new Equals($field, $value, $context)); 82 | 83 | $qb->select('COUNT(root)')->shouldBeCalled(); 84 | $qb->getParameters()->willReturn(new ArrayCollection()); 85 | $qb->setParameter($paramName, $value, null)->shouldBeCalled(); 86 | 87 | $qb->getDQLPart('join')->willReturn([]); 88 | $qb->getAllAliases()->willReturn([]); 89 | $qb->join('root.user', 'user')->willReturn($qb); 90 | 91 | $this->getFilter($qb, 'root')->shouldBe(sprintf('%s.%s = :%s', $context, $field, $paramName)); 92 | $this->modify($qb, 'root'); 93 | } 94 | } 95 | --------------------------------------------------------------------------------