From 3ec17afb644b882795e9f1a8b793434dcc1c414e Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Sat, 27 Aug 2022 13:42:38 +0400 Subject: [PATCH 1/4] `Scalars` moved to `Builder`. --- packages/graphql/README.md | 10 +- .../Exceptions/ScalarNoOperators.php | 4 +- .../Exceptions/ScalarUnknown.php | 4 +- packages/graphql/src/Builder/Scalars.php | 128 +++++++ packages/graphql/src/Builder/ScalarsTest.php | 347 ++++++++++++++++++ .../src/SearchBy/Directives/Directive.php | 11 +- packages/graphql/src/SearchBy/Manipulator.php | 7 +- .../SearchBy/Operators/Complex/Relation.php | 4 +- packages/graphql/src/SearchBy/Scalars.php | 134 ++----- packages/graphql/src/SearchBy/ScalarsTest.php | 197 ++-------- phpstan-baseline.neon | 10 +- 11 files changed, 543 insertions(+), 313 deletions(-) rename packages/graphql/src/{SearchBy => Builder}/Exceptions/ScalarNoOperators.php (78%) rename packages/graphql/src/{SearchBy => Builder}/Exceptions/ScalarUnknown.php (78%) create mode 100644 packages/graphql/src/Builder/Scalars.php create mode 100644 packages/graphql/src/Builder/ScalarsTest.php diff --git a/packages/graphql/README.md b/packages/graphql/README.md index e673bb115..99f403ba9 100644 --- a/packages/graphql/README.md +++ b/packages/graphql/README.md @@ -133,10 +133,10 @@ query { In addition to standard GraphQL scalars the package defines few own: -* `LastDragon_ru\\LaraASP\\GraphQL\\SearchBy\\Directives\\Directive::ScalarNumber` - any operator for this scalar will be available for `Int` and `Float`; -* `LastDragon_ru\\LaraASP\\GraphQL\\SearchBy\\Directives\\Directive::ScalarNull` - additional operators available for nullable scalars; -* `LastDragon_ru\\LaraASP\\GraphQL\\SearchBy\\Directives\\Directive::ScalarLogic` - list of logical operators, please see below; -* `LastDragon_ru\\LaraASP\\GraphQL\\SearchBy\\Directives\\Directive::ScalarEnum` - default operators for enums; +* `LastDragon_ru\LaraASP\GraphQL\SearchBy\Scalars::ScalarNumber` - any operator for this scalar will be available for `Int` and `Float`; +* `LastDragon_ru\LaraASP\GraphQL\SearchBy\Scalars::ScalarNull` - additional operators available for nullable scalars; +* `LastDragon_ru\LaraASP\GraphQL\SearchBy\Scalars::ScalarLogic` - list of logical operators, please see below; +* `LastDragon_ru\LaraASP\GraphQL\SearchBy\Scalars::ScalarEnum` - default operators for enums; To work with custom scalars you need to configure supported operators for each of them. First, you need to publish package config: @@ -196,7 +196,7 @@ return [ There are three types of operators: * Comparison - used to compare column with value(s), eg `{equal: "value"}`, `{lt: 2}`, etc. To add your own you just need to implement [`Operator`](./src/SearchBy/Contracts/Operator.php) and add it to scalar(s); -* Logical - used to group comparisons into groups, eg `anyOf([{equal: "a"}, {equal: "b"}])`. Adding your own is the same: implement [`Operator`](./src/SearchBy/Contracts/Operator.php) and add it to `Directive::ScalarLogic` scalar; +* Logical - used to group comparisons into groups, eg `anyOf([{equal: "a"}, {equal: "b"}])`. Adding your own is the same: implement [`Operator`](./src/SearchBy/Contracts/Operator.php) and add it to `Scalars::ScalarLogic` scalar; * Complex - used to create conditions for nested Input types and allow implement any logic eg `whereHas`, `whereDoesntHave`, etc. These operators must implement [`ComplexOperator`](./src/SearchBy/Contracts/ComplexOperator.php) (by default the [`Relation`](./src/SearchBy/Operators/Complex/Relation.php) operator will be used, you can use it as example): ```graphql diff --git a/packages/graphql/src/SearchBy/Exceptions/ScalarNoOperators.php b/packages/graphql/src/Builder/Exceptions/ScalarNoOperators.php similarity index 78% rename from packages/graphql/src/SearchBy/Exceptions/ScalarNoOperators.php rename to packages/graphql/src/Builder/Exceptions/ScalarNoOperators.php index f1c210007..61332b5e9 100644 --- a/packages/graphql/src/SearchBy/Exceptions/ScalarNoOperators.php +++ b/packages/graphql/src/Builder/Exceptions/ScalarNoOperators.php @@ -1,12 +1,12 @@ >|string> + */ + protected array $scalars = []; + + /** + * Determines additional operators available for scalar type. + * + * @var array + */ + protected array $extends = []; + + public function __construct( + private Container $container, + ) { + // empty + } + + protected function getContainer(): Container { + return $this->container; + } + + public function isScalar(string $scalar): bool { + return isset($this->scalars[$scalar]); + } + + /** + * @param array>|string $operators + */ + public function addScalar(string $scalar, array|string $operators): void { + if (is_string($operators) && !$this->isScalar($operators)) { + throw new ScalarUnknown($operators); + } + + if (is_array($operators) && !$operators) { + throw new ScalarNoOperators($scalar); + } + + $this->scalars[$scalar] = $operators; + } + + /** + * @return array + */ + public function getScalarOperators(string $scalar, bool $nullable): array { + // Is Scalar? + if (!$this->isScalar($scalar)) { + throw new ScalarUnknown($scalar); + } + + // Base + $base = $scalar; + $operators = $scalar; + + do { + $operators = $this->scalars[$operators] ?? []; + $isAlias = !is_array($operators); + + if ($isAlias) { + $base = $operators; + } + } while ($isAlias); + + // Create Instances + $container = $this->getContainer(); + $operators = array_map(static function (string $operator) use ($container): Operator { + return $container->make($operator); + }, array_unique($operators)); + + // Extends + if (isset($this->extends[$base])) { + $extends = $this->getScalarOperators($this->extends[$base], $nullable); + $operators = array_merge($operators, $extends); + } + + // Add `null` for nullable + if ($nullable) { + array_push($operators, ...$this->getScalarOperators(static::ScalarNull, false)); + } + + // Cleanup + $operators = array_values(array_unique($operators, SORT_REGULAR)); + + // Return + return $operators; + } + + /** + * @return array + */ + public function getEnumOperators(string $enum, bool $nullable): array { + return $this->isScalar($enum) + ? $this->getScalarOperators($enum, $nullable) + : $this->getScalarOperators(static::ScalarEnum, $nullable); + } +} diff --git a/packages/graphql/src/Builder/ScalarsTest.php b/packages/graphql/src/Builder/ScalarsTest.php new file mode 100644 index 000000000..a75013b67 --- /dev/null +++ b/packages/graphql/src/Builder/ScalarsTest.php @@ -0,0 +1,347 @@ + + // ========================================================================= + /** + * @covers ::isScalar + */ + public function testIsScalar(): void { + $scalars = new class($this->app) extends Scalars { + /** + * @inheritdoc + */ + protected array $scalars = [ + Scalars::ScalarInt => [ + ScalarsTest__OperatorA::class, + ], + ]; + }; + + self::assertTrue($scalars->isScalar(Scalars::ScalarInt)); + self::assertFalse($scalars->isScalar('unknown')); + } + + /** + * @covers ::addScalar + * + * @dataProvider dataProviderAddScalar + */ + public function testAddScalar(Exception|bool $expected, string $scalar, mixed $operators): void { + if ($expected instanceof Exception) { + self::expectExceptionObject($expected); + } + + $scalars = new class($this->app) extends Scalars { + // empty + }; + + $scalars->addScalar($scalar, $operators); + + self::assertEquals($expected, $scalars->isScalar($scalar)); + } + + /** + * @covers ::getScalarOperators + */ + public function testGetScalarOperators(): void { + $scalar = __FUNCTION__; + $alias = 'alias'; + $scalars = new class($this->app) extends Scalars { + // empty + }; + + $scalars->addScalar($scalar, [ + ScalarsTest__OperatorA::class, + ScalarsTest__OperatorA::class, + ]); + $scalars->addScalar($alias, $scalar); + $scalars->addScalar(Scalars::ScalarNull, [ + ScalarsTest__OperatorB::class, + ScalarsTest__OperatorC::class, + ]); + + self::assertEquals( + [ScalarsTest__OperatorA::class], + $this->toClassNames($scalars->getScalarOperators($scalar, false)), + ); + self::assertEquals( + [ + ScalarsTest__OperatorA::class, + ScalarsTest__OperatorB::class, + ScalarsTest__OperatorC::class, + ], + $this->toClassNames($scalars->getScalarOperators($scalar, true)), + ); + self::assertEquals( + $scalars->getScalarOperators($scalar, false), + $scalars->getScalarOperators($alias, false), + ); + self::assertEquals( + $scalars->getScalarOperators($scalar, true), + $scalars->getScalarOperators($alias, true), + ); + } + + /** + * @covers ::getScalarOperators + */ + public function testGetScalarOperatorsExtends(): void { + $scalars = new class($this->app) extends Scalars { + /** + * @inheritdoc + */ + protected array $extends = [ + 'test' => 'base', + ]; + }; + + $scalars->addScalar('test', [ + ScalarsTest__OperatorA::class, + ScalarsTest__OperatorA::class, + ]); + $scalars->addScalar('base', [ + ScalarsTest__OperatorD::class, + ]); + $scalars->addScalar('alias', 'test'); + $scalars->addScalar(Scalars::ScalarNull, [ + ScalarsTest__OperatorB::class, + ScalarsTest__OperatorC::class, + ]); + + self::assertEquals( + [ScalarsTest__OperatorA::class, ScalarsTest__OperatorD::class], + $this->toClassNames($scalars->getScalarOperators('test', false)), + ); + self::assertEquals( + [ + ScalarsTest__OperatorA::class, + ScalarsTest__OperatorD::class, + ScalarsTest__OperatorB::class, + ScalarsTest__OperatorC::class, + ], + $this->toClassNames($scalars->getScalarOperators('test', true)), + ); + self::assertEquals( + [ + ScalarsTest__OperatorA::class, + ScalarsTest__OperatorD::class, + ScalarsTest__OperatorB::class, + ScalarsTest__OperatorC::class, + ], + $this->toClassNames($scalars->getScalarOperators('alias', true)), + ); + } + + /** + * @covers ::getScalarOperators + */ + public function testGetScalarOperatorsUnknownScalar(): void { + self::expectExceptionObject(new ScalarUnknown('unknown')); + + $scalars = new class($this->app) extends Scalars { + // empty + }; + + $scalars->getScalarOperators('unknown', false); + } + + /** + * @covers ::getEnumOperators + */ + public function testGetEnumOperators(): void { + $enum = __FUNCTION__; + $alias = 'alias'; + $scalars = new class($this->app) extends Scalars { + // empty + }; + + $scalars->addScalar($enum, [ + ScalarsTest__OperatorA::class, + ScalarsTest__OperatorA::class, + ]); + $scalars->addScalar($alias, $enum); + $scalars->addScalar(Scalars::ScalarEnum, [ + ScalarsTest__OperatorD::class, + ScalarsTest__OperatorD::class, + ]); + $scalars->addScalar(Scalars::ScalarNull, [ + ScalarsTest__OperatorB::class, + ScalarsTest__OperatorC::class, + ]); + + self::assertEquals( + [ + ScalarsTest__OperatorD::class, + ], + $this->toClassNames($scalars->getEnumOperators('unknown', false)), + ); + self::assertEquals( + [ + ScalarsTest__OperatorD::class, + ScalarsTest__OperatorB::class, + ScalarsTest__OperatorC::class, + ], + $this->toClassNames($scalars->getEnumOperators('unknown', true)), + ); + self::assertEquals( + [ScalarsTest__OperatorA::class], + $this->toClassNames($scalars->getEnumOperators($enum, false)), + ); + self::assertEquals( + [ + ScalarsTest__OperatorA::class, + ScalarsTest__OperatorB::class, + ScalarsTest__OperatorC::class, + ], + $this->toClassNames($scalars->getEnumOperators($enum, true)), + ); + self::assertEquals( + $scalars->getEnumOperators($enum, false), + $scalars->getEnumOperators($alias, false), + ); + self::assertEquals( + $scalars->getEnumOperators($enum, true), + $scalars->getEnumOperators($alias, true), + ); + } + // + + // + // ========================================================================= + /** + * @return array + */ + public function dataProviderAddScalar(): array { + return [ + 'ok' => [true, 'scalar', [IsNot::class]], + 'unknown scalar' => [ + new ScalarUnknown('unknown'), + 'scalar', + 'unknown', + ], + 'empty operators' => [ + new ScalarNoOperators('scalar'), + 'scalar', + [], + ], + ]; + } + // + + // + // ========================================================================= + /** + * @param array $objects + * + * @return array + */ + protected function toClassNames(array $objects): array { + $classes = []; + + foreach ($objects as $object) { + $classes[] = $object::class; + } + + return $classes; + } + // +} + +// @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses +// @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class ScalarsTest__Scalars extends Scalars { + // empty +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +abstract class ScalarsTest__Operator implements Operator { + public static function definition(): string { + throw new Exception('Should not be called'); + } + + public static function getName(): string { + throw new Exception('Should not be called'); + } + + public static function getDirectiveName(): string { + throw new Exception('Should not be called'); + } + + public function getFieldType(TypeProvider $provider, string $type): ?string { + throw new Exception('Should not be called'); + } + + public function getFieldDescription(): string { + throw new Exception('Should not be called'); + } + + public function getFieldDirective(): ?DirectiveNode { + throw new Exception('Should not be called'); + } + + public function isBuilderSupported(object $builder): bool { + throw new Exception('Should not be called'); + } + + public function call(Handler $handler, object $builder, Property $property, Argument $argument): object { + throw new Exception('Should not be called'); + } +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class ScalarsTest__OperatorA extends ScalarsTest__Operator { + // empty +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class ScalarsTest__OperatorB extends ScalarsTest__Operator { + // empty +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class ScalarsTest__OperatorC extends ScalarsTest__Operator { + // empty +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class ScalarsTest__OperatorD extends ScalarsTest__Operator { + // empty +} diff --git a/packages/graphql/src/SearchBy/Directives/Directive.php b/packages/graphql/src/SearchBy/Directives/Directive.php index 53b2f9713..41f167e1e 100644 --- a/packages/graphql/src/SearchBy/Directives/Directive.php +++ b/packages/graphql/src/SearchBy/Directives/Directive.php @@ -15,16 +15,7 @@ use Nuwave\Lighthouse\Support\Contracts\ArgManipulator; class Directive extends HandlerDirective implements ArgManipulator, ArgBuilderDirective { - public const Name = 'SearchBy'; - public const ScalarID = 'ID'; - public const ScalarInt = 'Int'; - public const ScalarFloat = 'Float'; - public const ScalarString = 'String'; - public const ScalarBoolean = 'Boolean'; - public const ScalarEnum = self::Name.'Enum'; - public const ScalarNull = self::Name.'Null'; - public const ScalarLogic = self::Name.'Logic'; - public const ScalarNumber = self::Name.'Number'; + public const Name = 'SearchBy'; public static function definition(): string { return /** @lang GraphQL */ <<<'GRAPHQL' diff --git a/packages/graphql/src/SearchBy/Manipulator.php b/packages/graphql/src/SearchBy/Manipulator.php index 813d44fad..d98e42a52 100644 --- a/packages/graphql/src/SearchBy/Manipulator.php +++ b/packages/graphql/src/SearchBy/Manipulator.php @@ -18,7 +18,7 @@ use Illuminate\Contracts\Container\Container; use Illuminate\Support\Str; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator as OperatorContract; -use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeProvider; +use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\ScalarNoOperators; use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator as BuilderManipulator; use LastDragon_ru\LaraASP\GraphQL\Exceptions\TypeDefinitionUnknown; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Contracts\ComplexOperator; @@ -30,7 +30,6 @@ use LastDragon_ru\LaraASP\GraphQL\SearchBy\Exceptions\FakeTypeDefinitionUnknown; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Exceptions\InputFieldAlreadyDefined; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Exceptions\NotImplemented; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Exceptions\ScalarNoOperators; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Complex\Relation; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Property; use Nuwave\Lighthouse\Schema\AST\DocumentAST; @@ -42,7 +41,7 @@ use function is_string; use function str_starts_with; -class Manipulator extends BuilderManipulator implements TypeProvider { +class Manipulator extends BuilderManipulator { public function __construct( Container $container, DirectiveLocator $directives, @@ -97,7 +96,7 @@ public function getInputType(InputObjectTypeDefinitionNode|InputObjectType $node } // Add type - $operators = $this->getScalarOperators(Directive::ScalarLogic, false); + $operators = $this->getScalarOperators(Scalars::ScalarLogic, false); $scalar = $this->getScalarTypeNode($name); $content = $this->getOperatorsFields($operators, $scalar); $type = $this->addTypeDefinition( diff --git a/packages/graphql/src/SearchBy/Operators/Complex/Relation.php b/packages/graphql/src/SearchBy/Operators/Complex/Relation.php index 875cea687..ee8d7ea54 100644 --- a/packages/graphql/src/SearchBy/Operators/Complex/Relation.php +++ b/packages/graphql/src/SearchBy/Operators/Complex/Relation.php @@ -8,13 +8,13 @@ use GraphQL\Language\Parser; use GraphQL\Type\Definition\InputObjectField; use GraphQL\Type\Definition\InputObjectType; +use GraphQL\Type\Definition\Type; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use LastDragon_ru\LaraASP\Eloquent\ModelHelper; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Handler; use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\OperatorUnsupportedBuilder; use LastDragon_ru\LaraASP\GraphQL\Builder\Property; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Contracts\ComplexOperator; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Directives\Directive; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Exceptions\OperatorInvalidArgumentValue; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Manipulator; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\BaseOperator; @@ -46,7 +46,7 @@ public function getDefinition( string $name, bool $nullable, ): InputObjectTypeDefinitionNode { - $count = $ast->getScalarType($ast->getScalarTypeNode(Directive::ScalarInt), false); + $count = $ast->getScalarType($ast->getScalarTypeNode(Type::INT), false); $where = $ast->getInputType($type); return Parser::inputObjectTypeDefinition( diff --git a/packages/graphql/src/SearchBy/Scalars.php b/packages/graphql/src/SearchBy/Scalars.php index 3f0aac72f..8b2371fec 100644 --- a/packages/graphql/src/SearchBy/Scalars.php +++ b/packages/graphql/src/SearchBy/Scalars.php @@ -4,12 +4,9 @@ use Illuminate\Container\Container; use Illuminate\Contracts\Config\Repository; -use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator as OperatorContract; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator as BuilderOperator; +use LastDragon_ru\LaraASP\GraphQL\Builder\Scalars as BuilderScalars; use LastDragon_ru\LaraASP\GraphQL\Package; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Contracts\Operator; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Directives\Directive; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Exceptions\ScalarNoOperators; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Exceptions\ScalarUnknown; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\Between; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\BitwiseAnd; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\BitwiseLeftShift; @@ -36,42 +33,33 @@ use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Logical\AnyOf; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Logical\Not; -use function array_map; -use function array_merge; -use function array_push; -use function array_unique; -use function array_values; -use function is_array; -use function is_string; +class Scalars extends BuilderScalars { + public const ScalarLogic = 'Logic'; + public const ScalarNumber = 'Number'; -use const SORT_REGULAR; - -class Scalars { /** - * Determines default operators available for each scalar type. - * - * @var array>|string> + * @inheritdoc */ protected array $scalars = [ // Standard types - Directive::ScalarID => [ + Scalars::ScalarID => [ Equal::class, NotEqual::class, In::class, NotIn::class, ], - Directive::ScalarInt => [ + Scalars::ScalarInt => [ BitwiseOr::class, BitwiseXor::class, BitwiseAnd::class, BitwiseLeftShift::class, BitwiseRightShift::class, ], - Directive::ScalarFloat => Directive::ScalarNumber, - Directive::ScalarBoolean => [ + Scalars::ScalarFloat => Scalars::ScalarNumber, + Scalars::ScalarBoolean => [ Equal::class, ], - Directive::ScalarString => [ + Scalars::ScalarString => [ Equal::class, NotEqual::class, Like::class, @@ -84,7 +72,7 @@ class Scalars { ], // Special types - Directive::ScalarNumber => [ + Scalars::ScalarNumber => [ Equal::class, NotEqual::class, LessThan::class, @@ -96,17 +84,17 @@ class Scalars { Between::class, NotBetween::class, ], - Directive::ScalarEnum => [ + Scalars::ScalarEnum => [ Equal::class, NotEqual::class, In::class, NotIn::class, ], - Directive::ScalarNull => [ + Scalars::ScalarNull => [ IsNull::class, IsNotNull::class, ], - Directive::ScalarLogic => [ + Scalars::ScalarLogic => [ AllOf::class, AnyOf::class, Not::class, @@ -114,102 +102,24 @@ class Scalars { ]; /** - * Determines additional operators available for scalar type. - * - * @var array + * @inheritdoc */ protected array $extends = [ - Directive::ScalarInt => Directive::ScalarNumber, - Directive::ScalarFloat => Directive::ScalarNumber, + Scalars::ScalarInt => Scalars::ScalarNumber, + Scalars::ScalarFloat => Scalars::ScalarNumber, ]; public function __construct( - private Container $container, + Container $container, Repository $config, ) { - /** @var array>|string> $scalars */ + parent::__construct($container); + + /** @var array>|string> $scalars */ $scalars = (array) $config->get(Package::Name.'.search_by.scalars'); foreach ($scalars as $scalar => $operators) { $this->addScalar($scalar, $operators); } } - - protected function getContainer(): Container { - return $this->container; - } - - public function isScalar(string $scalar): bool { - return isset($this->scalars[$scalar]); - } - - /** - * @param array>|string $operators - */ - public function addScalar(string $scalar, array|string $operators): void { - if (is_string($operators) && !$this->isScalar($operators)) { - throw new ScalarUnknown($operators); - } - - if (is_array($operators) && !$operators) { - throw new ScalarNoOperators($scalar); - } - - $this->scalars[$scalar] = $operators; - } - - /** - * @return array - */ - public function getScalarOperators(string $scalar, bool $nullable): array { - // Is Scalar? - if (!$this->isScalar($scalar)) { - throw new ScalarUnknown($scalar); - } - - // Base - $base = $scalar; - $operators = $scalar; - - do { - $operators = $this->scalars[$operators] ?? []; - $isAlias = !is_array($operators); - - if ($isAlias) { - $base = $operators; - } - } while ($isAlias); - - // Create Instances - $container = $this->getContainer(); - $operators = array_map(static function (string $operator) use ($container): OperatorContract { - return $container->make($operator); - }, array_unique($operators)); - - // Extends - if (isset($this->extends[$base])) { - $extends = $this->getScalarOperators($this->extends[$base], $nullable); - $operators = array_merge($operators, $extends); - } - - // Add `null` for nullable - if ($nullable) { - array_push($operators, ...$this->getScalarOperators(Directive::ScalarNull, false)); - } - - // Cleanup - $operators = array_values(array_unique($operators, SORT_REGULAR)); - - // Return - return $operators; - } - - /** - * @return array - */ - public function getEnumOperators(string $enum, bool $nullable): array { - return $this->isScalar($enum) - ? $this->getScalarOperators($enum, $nullable) - : $this->getScalarOperators(Directive::ScalarEnum, $nullable); - } } diff --git a/packages/graphql/src/SearchBy/ScalarsTest.php b/packages/graphql/src/SearchBy/ScalarsTest.php index c4c0e15e3..956961549 100644 --- a/packages/graphql/src/SearchBy/ScalarsTest.php +++ b/packages/graphql/src/SearchBy/ScalarsTest.php @@ -2,197 +2,52 @@ namespace LastDragon_ru\LaraASP\GraphQL\SearchBy; -use Exception; -use Hamcrest\Core\IsNot; use Illuminate\Contracts\Config\Repository; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Directives\Directive; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Exceptions\ScalarNoOperators; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Exceptions\ScalarUnknown; +use LastDragon_ru\LaraASP\GraphQL\Package; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\Equal; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\IsNotNull; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\IsNull; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\NotEqual; use LastDragon_ru\LaraASP\GraphQL\Testing\Package\TestCase; -use Mockery\MockInterface; +use Mockery; /** * @internal * @coversDefaultClass \LastDragon_ru\LaraASP\GraphQL\SearchBy\Scalars */ class ScalarsTest extends TestCase { - // - // ========================================================================= - /** - * @before - */ - public function init(): void { - $this->afterApplicationCreated(function (): void { - $this->override(Repository::class, static function (MockInterface $mock): void { - $mock - ->shouldReceive('get') - ->andReturn(null); - }); - }); - } - // - // // ========================================================================= /** - * @covers ::isScalar - */ - public function testIsScalar(): void { - $scalars = $this->app->make(Scalars::class); - - self::assertTrue($scalars->isScalar(Directive::ScalarInt)); - self::assertFalse($scalars->isScalar('unknown')); - } - - /** - * @covers ::addScalar - * - * @dataProvider dataProviderAddScalar - */ - public function testAddScalar(Exception|bool $expected, string $scalar, mixed $operators): void { - if ($expected instanceof Exception) { - self::expectExceptionObject($expected); - } - - $scalars = $this->app->make(Scalars::class); - - $scalars->addScalar($scalar, $operators); - - self::assertEquals($expected, $scalars->isScalar($scalar)); - } - - /** - * @covers ::getScalarOperators + * @covers ::__construct */ - public function testGetScalarOperators(): void { - $scalar = __FUNCTION__; - $alias = 'alias'; - $scalars = $this->app->make(Scalars::class); - - $scalars->addScalar($scalar, [Equal::class, Equal::class]); - $scalars->addScalar($alias, $scalar); - - self::assertEquals( - [Equal::class], - $this->toClassNames($scalars->getScalarOperators($scalar, false)), - ); - self::assertEquals( - [Equal::class, IsNull::class, IsNotNull::class], - $this->toClassNames($scalars->getScalarOperators($scalar, true)), - ); - self::assertEquals( - $scalars->getScalarOperators($scalar, false), - $scalars->getScalarOperators($alias, false), - ); - self::assertEquals( - $scalars->getScalarOperators($scalar, true), - $scalars->getScalarOperators($alias, true), - ); - } + public function testConstructor(): void { + $config = Mockery::mock(Repository::class); + $config + ->shouldReceive('get') + ->with(Package::Name.'.search_by.scalars') + ->andReturn([ + Scalars::ScalarID => [ + Equal::class, + ], + Scalars::ScalarInt => [ + NotEqual::class, + ], + ]); - /** - * @covers ::getScalarOperators - */ - public function testGetScalarOperatorsExtends(): void { - $config = $this->app->make(Repository::class); $scalars = new class($this->app, $config) extends Scalars { - /** - * @var array - */ - protected array $extends = [ - 'test' => 'base', - ]; + // empty }; - $scalars->addScalar('test', [Equal::class, Equal::class]); - $scalars->addScalar('base', [NotEqual::class]); - $scalars->addScalar('alias', 'test'); - - self::assertEquals( - [Equal::class, NotEqual::class], - $this->toClassNames($scalars->getScalarOperators('test', false)), - ); - self::assertEquals( - [Equal::class, NotEqual::class, IsNull::class, IsNotNull::class], - $this->toClassNames($scalars->getScalarOperators('test', true)), - ); - self::assertEquals( - [Equal::class, NotEqual::class, IsNull::class, IsNotNull::class], - $this->toClassNames($scalars->getScalarOperators('alias', true)), - ); - } - - /** - * @covers ::getScalarOperators - */ - public function testGetScalarOperatorsUnknownScalar(): void { - self::expectExceptionObject(new ScalarUnknown('unknown')); - - $this->app->make(Scalars::class)->getScalarOperators('unknown', false); - } - - /** - * @covers ::getEnumOperators - */ - public function testGetEnumOperators(): void { - $enum = __FUNCTION__; - $alias = 'alias'; - $scalars = $this->app->make(Scalars::class); - - $scalars->addScalar($enum, [Equal::class, Equal::class]); - $scalars->addScalar($alias, $enum); - $scalars->addScalar(Directive::ScalarEnum, [NotEqual::class, NotEqual::class]); - - self::assertEquals( - [NotEqual::class], - $this->toClassNames($scalars->getEnumOperators('unknown', false)), - ); - self::assertEquals( - [NotEqual::class, IsNull::class, IsNotNull::class], - $this->toClassNames($scalars->getEnumOperators('unknown', true)), - ); - self::assertEquals( - [Equal::class], - $this->toClassNames($scalars->getEnumOperators($enum, false)), - ); - self::assertEquals( - [Equal::class, IsNull::class, IsNotNull::class], - $this->toClassNames($scalars->getEnumOperators($enum, true)), - ); - self::assertEquals( - $scalars->getEnumOperators($enum, false), - $scalars->getEnumOperators($alias, false), - ); + self::assertTrue($scalars->isScalar(Scalars::ScalarID)); + self::assertTrue($scalars->isScalar(Scalars::ScalarInt)); + self::assertFalse($scalars->isScalar('unknown')); self::assertEquals( - $scalars->getEnumOperators($enum, true), - $scalars->getEnumOperators($alias, true), - ); - } - // - - // - // ========================================================================= - /** - * @return array - */ - public function dataProviderAddScalar(): array { - return [ - 'ok' => [true, 'scalar', [IsNot::class]], - 'unknown scalar' => [ - new ScalarUnknown('unknown'), - 'scalar', - 'unknown', + [ + Equal::class, ], - 'empty operators' => [ - new ScalarNoOperators('scalar'), - 'scalar', - [], - ], - ]; + $this->toClassNames( + $scalars->getScalarOperators(Scalars::ScalarID, false), + ), + ); } // diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 65e3be5df..9093afbf2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -40,6 +40,11 @@ parameters: count: 1 path: packages/graphql/src/Builder/Property.php + - + message: "#^Parameter \\#2 \\$operators of method LastDragon_ru\\\\LaraASP\\\\GraphQL\\\\Builder\\\\Scalars\\:\\:addScalar\\(\\) expects array\\\\>\\|string, mixed given\\.$#" + count: 1 + path: packages/graphql/src/Builder/ScalarsTest.php + - message: "#^Parameter \\#1 \\$enum of class LastDragon_ru\\\\LaraASP\\\\GraphQL\\\\Utils\\\\Enum\\\\EnumType constructor expects class\\-string\\, mixed given\\.$#" count: 1 @@ -60,11 +65,6 @@ parameters: count: 1 path: packages/graphql/src/SearchBy/Directives/DirectiveTest.php - - - message: "#^Parameter \\#2 \\$operators of method LastDragon_ru\\\\LaraASP\\\\GraphQL\\\\SearchBy\\\\Scalars\\:\\:addScalar\\(\\) expects array\\\\>\\|string, mixed given\\.$#" - count: 1 - path: packages/graphql/src/SearchBy/ScalarsTest.php - - message: "#^Parameter \\#2 \\$callback of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:when\\(\\) expects \\(callable\\(Illuminate\\\\Database\\\\Eloquent\\\\Builder\\, string\\|null\\)\\: Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\)\\|null, Closure\\(Illuminate\\\\Database\\\\Eloquent\\\\Builder, string\\)\\: Illuminate\\\\Database\\\\Eloquent\\\\Builder\\ given\\.$#" count: 1 From 2e2dce4310738cecccb1d68fcc85f69c43999d3a Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Sat, 27 Aug 2022 14:36:51 +0400 Subject: [PATCH 2/4] `Scalars` renamed to `Operators` (to be more generic). --- packages/graphql/README.md | 28 +- packages/graphql/config/config.php | 9 +- .../src/Builder/Contracts/TypeDefinition.php | 5 +- .../src/Builder/Contracts/TypeProvider.php | 4 +- .../TypeDefinitionImpossibleToCreateType.php | 10 +- ...larNoOperators.php => TypeNoOperators.php} | 4 +- .../{ScalarUnknown.php => TypeUnknown.php} | 4 +- packages/graphql/src/Builder/Manipulator.php | 20 +- .../Builder/{Scalars.php => Operators.php} | 64 ++-- .../graphql/src/Builder/OperatorsTest.php | 347 ++++++++++++++++++ packages/graphql/src/Builder/ScalarsTest.php | 347 ------------------ packages/graphql/src/Provider.php | 4 +- .../src/SearchBy/Directives/DirectiveTest.php | 2 +- packages/graphql/src/SearchBy/Manipulator.php | 38 +- .../SearchBy/{Scalars.php => Operators.php} | 40 +- .../{ScalarsTest.php => OperatorsTest.php} | 20 +- packages/graphql/src/SearchBy/Types/Flag.php | 12 +- packages/graphql/src/SearchBy/Types/Range.php | 16 +- packages/graphql/src/SortBy/Manipulator.php | 2 +- .../graphql/src/SortBy/Types/Direction.php | 12 +- phpstan-baseline.neon | 9 +- 21 files changed, 496 insertions(+), 501 deletions(-) rename packages/graphql/src/Builder/Exceptions/{ScalarNoOperators.php => TypeNoOperators.php} (77%) rename packages/graphql/src/Builder/Exceptions/{ScalarUnknown.php => TypeUnknown.php} (82%) rename packages/graphql/src/Builder/{Scalars.php => Operators.php} (54%) create mode 100644 packages/graphql/src/Builder/OperatorsTest.php delete mode 100644 packages/graphql/src/Builder/ScalarsTest.php rename packages/graphql/src/SearchBy/{Scalars.php => Operators.php} (80%) rename packages/graphql/src/SearchBy/{ScalarsTest.php => OperatorsTest.php} (72%) diff --git a/packages/graphql/README.md b/packages/graphql/README.md index 99f403ba9..f538289ab 100644 --- a/packages/graphql/README.md +++ b/packages/graphql/README.md @@ -129,16 +129,16 @@ query { ``` -## Scalars +## Config -In addition to standard GraphQL scalars the package defines few own: +In addition to standard GraphQL types the package defines few own: -* `LastDragon_ru\LaraASP\GraphQL\SearchBy\Scalars::ScalarNumber` - any operator for this scalar will be available for `Int` and `Float`; -* `LastDragon_ru\LaraASP\GraphQL\SearchBy\Scalars::ScalarNull` - additional operators available for nullable scalars; -* `LastDragon_ru\LaraASP\GraphQL\SearchBy\Scalars::ScalarLogic` - list of logical operators, please see below; -* `LastDragon_ru\LaraASP\GraphQL\SearchBy\Scalars::ScalarEnum` - default operators for enums; +* `LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators::Number` - any operator for this type will be available for `Int` and `Float`; +* `LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators::Null` - additional operators available for nullable types; +* `LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators::Logical` - list of logical operators, please see below; +* `LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators::Enum` - default operators for enums; -To work with custom scalars you need to configure supported operators for each of them. First, you need to publish package config: +To work with custom types you need to configure supported operators for each of them. First, you need to publish package config: ```shell php artisan vendor:publish --provider=LastDragon_ru\\LaraASP\\GraphQL\\Provider --tag=config @@ -165,15 +165,15 @@ return [ */ 'search_by' => [ /** - * Scalars + * Operators * --------------------------------------------------------------------- * - * You can (re)define scalars and supported operators here. + * You can (re)define types and supported operators here. * - * @var array>> + * @var array>> */ - 'scalars' => [ - // You can define a list of operators for each Scalar + 'operators' => [ + // You can define a list of operators for each type 'Date' => [ Equal::class, Between::class, @@ -195,8 +195,8 @@ return [ There are three types of operators: -* Comparison - used to compare column with value(s), eg `{equal: "value"}`, `{lt: 2}`, etc. To add your own you just need to implement [`Operator`](./src/SearchBy/Contracts/Operator.php) and add it to scalar(s); -* Logical - used to group comparisons into groups, eg `anyOf([{equal: "a"}, {equal: "b"}])`. Adding your own is the same: implement [`Operator`](./src/SearchBy/Contracts/Operator.php) and add it to `Scalars::ScalarLogic` scalar; +* Comparison - used to compare column with value(s), eg `{equal: "value"}`, `{lt: 2}`, etc. To add your own you just need to implement [`Operator`](./src/SearchBy/Contracts/Operator.php) and add it to type(s); +* Logical - used to group comparisons into groups, eg `anyOf([{equal: "a"}, {equal: "b"}])`. Adding your own is the same: implement [`Operator`](./src/SearchBy/Contracts/Operator.php) and add it to `Operators::Logical` type; * Complex - used to create conditions for nested Input types and allow implement any logic eg `whereHas`, `whereDoesntHave`, etc. These operators must implement [`ComplexOperator`](./src/SearchBy/Contracts/ComplexOperator.php) (by default the [`Relation`](./src/SearchBy/Operators/Complex/Relation.php) operator will be used, you can use it as example): ```graphql diff --git a/packages/graphql/config/config.php b/packages/graphql/config/config.php index 6706eeeee..db585cb45 100644 --- a/packages/graphql/config/config.php +++ b/packages/graphql/config/config.php @@ -1,6 +1,7 @@ [ /** - * Scalars + * Operators * --------------------------------------------------------------------- * - * You can (re)define scalars and supported operators here. + * You can (re)define types and supported operators here. * - * @var array>> + * @var array>> */ - 'scalars' => [ + 'operators' => [ // This value has no effect inside the published config. ConfigMerger::Replace => true, ], diff --git a/packages/graphql/src/Builder/Contracts/TypeDefinition.php b/packages/graphql/src/Builder/Contracts/TypeDefinition.php index badccdedf..b0030c99e 100644 --- a/packages/graphql/src/Builder/Contracts/TypeDefinition.php +++ b/packages/graphql/src/Builder/Contracts/TypeDefinition.php @@ -2,17 +2,18 @@ namespace LastDragon_ru\LaraASP\GraphQL\Builder\Contracts; +use GraphQL\Language\AST\Node; use GraphQL\Language\AST\TypeDefinitionNode; interface TypeDefinition { public static function getName(): string; /** - * @return (TypeDefinitionNode&\GraphQL\Language\AST\Node)|null + * @return (TypeDefinitionNode&Node)|null */ public function getTypeDefinitionNode( string $name, - string $scalar = null, + string $type = null, bool $nullable = null, ): ?TypeDefinitionNode; } diff --git a/packages/graphql/src/Builder/Contracts/TypeProvider.php b/packages/graphql/src/Builder/Contracts/TypeProvider.php index f0159e1bc..42fbe8d0f 100644 --- a/packages/graphql/src/Builder/Contracts/TypeProvider.php +++ b/packages/graphql/src/Builder/Contracts/TypeProvider.php @@ -4,7 +4,7 @@ interface TypeProvider { /** - * @param class-string $type + * @param class-string $definition */ - public function getType(string $type, string $scalar = null, bool $nullable = null): string; + public function getType(string $definition, string $type = null, bool $nullable = null): string; } diff --git a/packages/graphql/src/Builder/Exceptions/TypeDefinitionImpossibleToCreateType.php b/packages/graphql/src/Builder/Exceptions/TypeDefinitionImpossibleToCreateType.php index 9496c21a0..6848c029f 100644 --- a/packages/graphql/src/Builder/Exceptions/TypeDefinitionImpossibleToCreateType.php +++ b/packages/graphql/src/Builder/Exceptions/TypeDefinitionImpossibleToCreateType.php @@ -13,15 +13,15 @@ class TypeDefinitionImpossibleToCreateType extends BuilderException { */ public function __construct( protected string $definition, - protected ?string $scalar, + protected ?string $type, protected ?bool $nullable, Throwable $previous = null, ) { parent::__construct( sprintf( - 'Definition `%s`: Impossible to create type for scalar `%s`.', + 'Definition `%s`: Impossible to create type for type `%s`.', $this->definition, - ($this->scalar ?: 'null').($this->nullable ? '' : '!'), + ($this->type ?: 'null').($this->nullable ? '' : '!'), ), $previous, ); @@ -31,8 +31,8 @@ public function getDefinition(): string { return $this->definition; } - public function getScalar(): ?string { - return $this->scalar; + public function getType(): ?string { + return $this->type; } public function isNullable(): ?bool { diff --git a/packages/graphql/src/Builder/Exceptions/ScalarNoOperators.php b/packages/graphql/src/Builder/Exceptions/TypeNoOperators.php similarity index 77% rename from packages/graphql/src/Builder/Exceptions/ScalarNoOperators.php rename to packages/graphql/src/Builder/Exceptions/TypeNoOperators.php index 61332b5e9..379171e01 100644 --- a/packages/graphql/src/Builder/Exceptions/ScalarNoOperators.php +++ b/packages/graphql/src/Builder/Exceptions/TypeNoOperators.php @@ -6,13 +6,13 @@ use function sprintf; -class ScalarNoOperators extends BuilderException { +class TypeNoOperators extends BuilderException { public function __construct( protected string $name, Throwable $previous = null, ) { parent::__construct(sprintf( - 'List of operators for scalar `%s` cannot be empty.', + 'List of operators for type `%s` cannot be empty.', $this->name, ), $previous); } diff --git a/packages/graphql/src/Builder/Exceptions/ScalarUnknown.php b/packages/graphql/src/Builder/Exceptions/TypeUnknown.php similarity index 82% rename from packages/graphql/src/Builder/Exceptions/ScalarUnknown.php rename to packages/graphql/src/Builder/Exceptions/TypeUnknown.php index 9f7e28f9d..701352eae 100644 --- a/packages/graphql/src/Builder/Exceptions/ScalarUnknown.php +++ b/packages/graphql/src/Builder/Exceptions/TypeUnknown.php @@ -6,13 +6,13 @@ use function sprintf; -class ScalarUnknown extends BuilderException { +class TypeUnknown extends BuilderException { public function __construct( protected string $name, Throwable $previous = null, ) { parent::__construct(sprintf( - 'Scalar `%s` is not defined.', + 'Type `%s` is not defined.', $this->name, ), $previous); } diff --git a/packages/graphql/src/Builder/Manipulator.php b/packages/graphql/src/Builder/Manipulator.php index 043990f0a..3f5a6c2c0 100644 --- a/packages/graphql/src/Builder/Manipulator.php +++ b/packages/graphql/src/Builder/Manipulator.php @@ -42,34 +42,34 @@ protected function getContainer(): Container { // // ========================================================================= - public function getType(string $type, string $scalar = null, bool $nullable = null): string { + public function getType(string $definition, string $type = null, bool $nullable = null): string { // Exists? - $name = $this->getTypeName($type::getName(), $scalar, $nullable); + $name = $this->getTypeName($definition::getName(), $type, $nullable); if ($this->isTypeDefinitionExists($name)) { return $name; } // Create new - $instance = $this->getContainer()->make($type); - $definition = $instance->getTypeDefinitionNode($name, $scalar, $nullable); + $instance = $this->getContainer()->make($definition); + $node = $instance->getTypeDefinitionNode($name, $type, $nullable); - if (!$definition) { - throw new TypeDefinitionImpossibleToCreateType($type, $scalar, $nullable); + if (!$node) { + throw new TypeDefinitionImpossibleToCreateType($definition, $type, $nullable); } - if ($name !== $this->getNodeName($definition)) { - throw new TypeDefinitionInvalidTypeName($type, $name, $this->getNodeName($definition)); + if ($name !== $this->getNodeName($node)) { + throw new TypeDefinitionInvalidTypeName($definition, $name, $this->getNodeName($node)); } // Save - $this->addTypeDefinition($definition); + $this->addTypeDefinition($node); // Return return $name; } - abstract protected function getTypeName(string $name, string $scalar = null, bool $nullable = null): string; + abstract protected function getTypeName(string $name, string $type = null, bool $nullable = null): string; // // diff --git a/packages/graphql/src/Builder/Scalars.php b/packages/graphql/src/Builder/Operators.php similarity index 54% rename from packages/graphql/src/Builder/Scalars.php rename to packages/graphql/src/Builder/Operators.php index ca389dcbf..f542f3e52 100644 --- a/packages/graphql/src/Builder/Scalars.php +++ b/packages/graphql/src/Builder/Operators.php @@ -6,8 +6,8 @@ use Illuminate\Container\Container; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator as BuilderOperator; -use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\ScalarNoOperators; -use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\ScalarUnknown; +use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\TypeNoOperators; +use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\TypeUnknown; use function array_map; use function array_merge; @@ -19,24 +19,24 @@ use const SORT_REGULAR; -abstract class Scalars { - public const ScalarID = Type::ID; - public const ScalarInt = Type::INT; - public const ScalarFloat = Type::FLOAT; - public const ScalarString = Type::STRING; - public const ScalarBoolean = Type::BOOLEAN; - public const ScalarEnum = 'Enum'; - public const ScalarNull = 'Null'; +abstract class Operators { + public const ID = Type::ID; + public const Int = Type::INT; + public const Float = Type::FLOAT; + public const String = Type::STRING; + public const Boolean = Type::BOOLEAN; + public const Enum = 'Enum'; + public const Null = 'Null'; /** - * Determines default operators available for each scalar type. + * Determines default operators available for each type. * * @var array>|string> */ - protected array $scalars = []; + protected array $operators = []; /** - * Determines additional operators available for scalar type. + * Determines additional operators available for type. * * @var array */ @@ -52,40 +52,40 @@ protected function getContainer(): Container { return $this->container; } - public function isScalar(string $scalar): bool { - return isset($this->scalars[$scalar]); + public function hasOperators(string $type): bool { + return isset($this->operators[$type]); } /** * @param array>|string $operators */ - public function addScalar(string $scalar, array|string $operators): void { - if (is_string($operators) && !$this->isScalar($operators)) { - throw new ScalarUnknown($operators); + public function addOperators(string $type, array|string $operators): void { + if (is_string($operators) && !$this->hasOperators($operators)) { + throw new TypeUnknown($operators); } if (is_array($operators) && !$operators) { - throw new ScalarNoOperators($scalar); + throw new TypeNoOperators($type); } - $this->scalars[$scalar] = $operators; + $this->operators[$type] = $operators; } /** * @return array */ - public function getScalarOperators(string $scalar, bool $nullable): array { - // Is Scalar? - if (!$this->isScalar($scalar)) { - throw new ScalarUnknown($scalar); + public function getOperators(string $type, bool $nullable): array { + // Is known? + if (!$this->hasOperators($type)) { + throw new TypeUnknown($type); } // Base - $base = $scalar; - $operators = $scalar; + $base = $type; + $operators = $type; do { - $operators = $this->scalars[$operators] ?? []; + $operators = $this->operators[$operators] ?? []; $isAlias = !is_array($operators); if ($isAlias) { @@ -101,13 +101,13 @@ public function getScalarOperators(string $scalar, bool $nullable): array { // Extends if (isset($this->extends[$base])) { - $extends = $this->getScalarOperators($this->extends[$base], $nullable); + $extends = $this->getOperators($this->extends[$base], $nullable); $operators = array_merge($operators, $extends); } // Add `null` for nullable if ($nullable) { - array_push($operators, ...$this->getScalarOperators(static::ScalarNull, false)); + array_push($operators, ...$this->getOperators(static::Null, false)); } // Cleanup @@ -121,8 +121,8 @@ public function getScalarOperators(string $scalar, bool $nullable): array { * @return array */ public function getEnumOperators(string $enum, bool $nullable): array { - return $this->isScalar($enum) - ? $this->getScalarOperators($enum, $nullable) - : $this->getScalarOperators(static::ScalarEnum, $nullable); + return $this->hasOperators($enum) + ? $this->getOperators($enum, $nullable) + : $this->getOperators(static::Enum, $nullable); } } diff --git a/packages/graphql/src/Builder/OperatorsTest.php b/packages/graphql/src/Builder/OperatorsTest.php new file mode 100644 index 000000000..a9e3b4a82 --- /dev/null +++ b/packages/graphql/src/Builder/OperatorsTest.php @@ -0,0 +1,347 @@ + + // ========================================================================= + /** + * @covers ::hasOperators + */ + public function testHasOperators(): void { + $operators = new class($this->app) extends Operators { + /** + * @inheritdoc + */ + protected array $operators = [ + Operators::Int => [ + OperatorsTest__OperatorA::class, + ], + ]; + }; + + self::assertTrue($operators->hasOperators(Operators::Int)); + self::assertFalse($operators->hasOperators('unknown')); + } + + /** + * @covers ::addOperators + * + * @dataProvider dataProviderAddOperators + */ + public function testAddOperators(Exception|bool $expected, string $type, mixed $typeOperators): void { + if ($expected instanceof Exception) { + self::expectExceptionObject($expected); + } + + $operators = new class($this->app) extends Operators { + // empty + }; + + $operators->addOperators($type, $typeOperators); + + self::assertEquals($expected, $operators->hasOperators($type)); + } + + /** + * @covers ::getOperators + */ + public function testGetOperators(): void { + $type = __FUNCTION__; + $alias = 'alias'; + $operators = new class($this->app) extends Operators { + // empty + }; + + $operators->addOperators($type, [ + OperatorsTest__OperatorA::class, + OperatorsTest__OperatorA::class, + ]); + $operators->addOperators($alias, $type); + $operators->addOperators(Operators::Null, [ + OperatorsTest__OperatorB::class, + OperatorsTest__OperatorC::class, + ]); + + self::assertEquals( + [OperatorsTest__OperatorA::class], + $this->toClassNames($operators->getOperators($type, false)), + ); + self::assertEquals( + [ + OperatorsTest__OperatorA::class, + OperatorsTest__OperatorB::class, + OperatorsTest__OperatorC::class, + ], + $this->toClassNames($operators->getOperators($type, true)), + ); + self::assertEquals( + $operators->getOperators($type, false), + $operators->getOperators($alias, false), + ); + self::assertEquals( + $operators->getOperators($type, true), + $operators->getOperators($alias, true), + ); + } + + /** + * @covers ::getOperators + */ + public function testGetOperatorsExtends(): void { + $operators = new class($this->app) extends Operators { + /** + * @inheritdoc + */ + protected array $extends = [ + 'test' => 'base', + ]; + }; + + $operators->addOperators('test', [ + OperatorsTest__OperatorA::class, + OperatorsTest__OperatorA::class, + ]); + $operators->addOperators('base', [ + OperatorsTest__OperatorD::class, + ]); + $operators->addOperators('alias', 'test'); + $operators->addOperators(Operators::Null, [ + OperatorsTest__OperatorB::class, + OperatorsTest__OperatorC::class, + ]); + + self::assertEquals( + [OperatorsTest__OperatorA::class, OperatorsTest__OperatorD::class], + $this->toClassNames($operators->getOperators('test', false)), + ); + self::assertEquals( + [ + OperatorsTest__OperatorA::class, + OperatorsTest__OperatorD::class, + OperatorsTest__OperatorB::class, + OperatorsTest__OperatorC::class, + ], + $this->toClassNames($operators->getOperators('test', true)), + ); + self::assertEquals( + [ + OperatorsTest__OperatorA::class, + OperatorsTest__OperatorD::class, + OperatorsTest__OperatorB::class, + OperatorsTest__OperatorC::class, + ], + $this->toClassNames($operators->getOperators('alias', true)), + ); + } + + /** + * @covers ::getOperators + */ + public function testGetOperatorsUnknownType(): void { + self::expectExceptionObject(new TypeUnknown('unknown')); + + $operators = new class($this->app) extends Operators { + // empty + }; + + $operators->getOperators('unknown', false); + } + + /** + * @covers ::getEnumOperators + */ + public function testGetEnumOperators(): void { + $enum = __FUNCTION__; + $alias = 'alias'; + $operators = new class($this->app) extends Operators { + // empty + }; + + $operators->addOperators($enum, [ + OperatorsTest__OperatorA::class, + OperatorsTest__OperatorA::class, + ]); + $operators->addOperators($alias, $enum); + $operators->addOperators(Operators::Enum, [ + OperatorsTest__OperatorD::class, + OperatorsTest__OperatorD::class, + ]); + $operators->addOperators(Operators::Null, [ + OperatorsTest__OperatorB::class, + OperatorsTest__OperatorC::class, + ]); + + self::assertEquals( + [ + OperatorsTest__OperatorD::class, + ], + $this->toClassNames($operators->getEnumOperators('unknown', false)), + ); + self::assertEquals( + [ + OperatorsTest__OperatorD::class, + OperatorsTest__OperatorB::class, + OperatorsTest__OperatorC::class, + ], + $this->toClassNames($operators->getEnumOperators('unknown', true)), + ); + self::assertEquals( + [OperatorsTest__OperatorA::class], + $this->toClassNames($operators->getEnumOperators($enum, false)), + ); + self::assertEquals( + [ + OperatorsTest__OperatorA::class, + OperatorsTest__OperatorB::class, + OperatorsTest__OperatorC::class, + ], + $this->toClassNames($operators->getEnumOperators($enum, true)), + ); + self::assertEquals( + $operators->getEnumOperators($enum, false), + $operators->getEnumOperators($alias, false), + ); + self::assertEquals( + $operators->getEnumOperators($enum, true), + $operators->getEnumOperators($alias, true), + ); + } + // + + // + // ========================================================================= + /** + * @return array + */ + public function dataProviderAddOperators(): array { + return [ + 'ok' => [true, 'scalar', [IsNot::class]], + 'unknown scalar' => [ + new TypeUnknown('unknown'), + 'scalar', + 'unknown', + ], + 'empty operators' => [ + new TypeNoOperators('scalar'), + 'scalar', + [], + ], + ]; + } + // + + // + // ========================================================================= + /** + * @param array $objects + * + * @return array + */ + protected function toClassNames(array $objects): array { + $classes = []; + + foreach ($objects as $object) { + $classes[] = $object::class; + } + + return $classes; + } + // +} + +// @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses +// @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class OperatorsTest__Operators extends Operators { + // empty +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +abstract class OperatorsTest__Operator implements Operator { + public static function definition(): string { + throw new Exception('Should not be called'); + } + + public static function getName(): string { + throw new Exception('Should not be called'); + } + + public static function getDirectiveName(): string { + throw new Exception('Should not be called'); + } + + public function getFieldType(TypeProvider $provider, string $type): ?string { + throw new Exception('Should not be called'); + } + + public function getFieldDescription(): string { + throw new Exception('Should not be called'); + } + + public function getFieldDirective(): ?DirectiveNode { + throw new Exception('Should not be called'); + } + + public function isBuilderSupported(object $builder): bool { + throw new Exception('Should not be called'); + } + + public function call(Handler $handler, object $builder, Property $property, Argument $argument): object { + throw new Exception('Should not be called'); + } +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class OperatorsTest__OperatorA extends OperatorsTest__Operator { + // empty +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class OperatorsTest__OperatorB extends OperatorsTest__Operator { + // empty +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class OperatorsTest__OperatorC extends OperatorsTest__Operator { + // empty +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class OperatorsTest__OperatorD extends OperatorsTest__Operator { + // empty +} diff --git a/packages/graphql/src/Builder/ScalarsTest.php b/packages/graphql/src/Builder/ScalarsTest.php deleted file mode 100644 index a75013b67..000000000 --- a/packages/graphql/src/Builder/ScalarsTest.php +++ /dev/null @@ -1,347 +0,0 @@ - - // ========================================================================= - /** - * @covers ::isScalar - */ - public function testIsScalar(): void { - $scalars = new class($this->app) extends Scalars { - /** - * @inheritdoc - */ - protected array $scalars = [ - Scalars::ScalarInt => [ - ScalarsTest__OperatorA::class, - ], - ]; - }; - - self::assertTrue($scalars->isScalar(Scalars::ScalarInt)); - self::assertFalse($scalars->isScalar('unknown')); - } - - /** - * @covers ::addScalar - * - * @dataProvider dataProviderAddScalar - */ - public function testAddScalar(Exception|bool $expected, string $scalar, mixed $operators): void { - if ($expected instanceof Exception) { - self::expectExceptionObject($expected); - } - - $scalars = new class($this->app) extends Scalars { - // empty - }; - - $scalars->addScalar($scalar, $operators); - - self::assertEquals($expected, $scalars->isScalar($scalar)); - } - - /** - * @covers ::getScalarOperators - */ - public function testGetScalarOperators(): void { - $scalar = __FUNCTION__; - $alias = 'alias'; - $scalars = new class($this->app) extends Scalars { - // empty - }; - - $scalars->addScalar($scalar, [ - ScalarsTest__OperatorA::class, - ScalarsTest__OperatorA::class, - ]); - $scalars->addScalar($alias, $scalar); - $scalars->addScalar(Scalars::ScalarNull, [ - ScalarsTest__OperatorB::class, - ScalarsTest__OperatorC::class, - ]); - - self::assertEquals( - [ScalarsTest__OperatorA::class], - $this->toClassNames($scalars->getScalarOperators($scalar, false)), - ); - self::assertEquals( - [ - ScalarsTest__OperatorA::class, - ScalarsTest__OperatorB::class, - ScalarsTest__OperatorC::class, - ], - $this->toClassNames($scalars->getScalarOperators($scalar, true)), - ); - self::assertEquals( - $scalars->getScalarOperators($scalar, false), - $scalars->getScalarOperators($alias, false), - ); - self::assertEquals( - $scalars->getScalarOperators($scalar, true), - $scalars->getScalarOperators($alias, true), - ); - } - - /** - * @covers ::getScalarOperators - */ - public function testGetScalarOperatorsExtends(): void { - $scalars = new class($this->app) extends Scalars { - /** - * @inheritdoc - */ - protected array $extends = [ - 'test' => 'base', - ]; - }; - - $scalars->addScalar('test', [ - ScalarsTest__OperatorA::class, - ScalarsTest__OperatorA::class, - ]); - $scalars->addScalar('base', [ - ScalarsTest__OperatorD::class, - ]); - $scalars->addScalar('alias', 'test'); - $scalars->addScalar(Scalars::ScalarNull, [ - ScalarsTest__OperatorB::class, - ScalarsTest__OperatorC::class, - ]); - - self::assertEquals( - [ScalarsTest__OperatorA::class, ScalarsTest__OperatorD::class], - $this->toClassNames($scalars->getScalarOperators('test', false)), - ); - self::assertEquals( - [ - ScalarsTest__OperatorA::class, - ScalarsTest__OperatorD::class, - ScalarsTest__OperatorB::class, - ScalarsTest__OperatorC::class, - ], - $this->toClassNames($scalars->getScalarOperators('test', true)), - ); - self::assertEquals( - [ - ScalarsTest__OperatorA::class, - ScalarsTest__OperatorD::class, - ScalarsTest__OperatorB::class, - ScalarsTest__OperatorC::class, - ], - $this->toClassNames($scalars->getScalarOperators('alias', true)), - ); - } - - /** - * @covers ::getScalarOperators - */ - public function testGetScalarOperatorsUnknownScalar(): void { - self::expectExceptionObject(new ScalarUnknown('unknown')); - - $scalars = new class($this->app) extends Scalars { - // empty - }; - - $scalars->getScalarOperators('unknown', false); - } - - /** - * @covers ::getEnumOperators - */ - public function testGetEnumOperators(): void { - $enum = __FUNCTION__; - $alias = 'alias'; - $scalars = new class($this->app) extends Scalars { - // empty - }; - - $scalars->addScalar($enum, [ - ScalarsTest__OperatorA::class, - ScalarsTest__OperatorA::class, - ]); - $scalars->addScalar($alias, $enum); - $scalars->addScalar(Scalars::ScalarEnum, [ - ScalarsTest__OperatorD::class, - ScalarsTest__OperatorD::class, - ]); - $scalars->addScalar(Scalars::ScalarNull, [ - ScalarsTest__OperatorB::class, - ScalarsTest__OperatorC::class, - ]); - - self::assertEquals( - [ - ScalarsTest__OperatorD::class, - ], - $this->toClassNames($scalars->getEnumOperators('unknown', false)), - ); - self::assertEquals( - [ - ScalarsTest__OperatorD::class, - ScalarsTest__OperatorB::class, - ScalarsTest__OperatorC::class, - ], - $this->toClassNames($scalars->getEnumOperators('unknown', true)), - ); - self::assertEquals( - [ScalarsTest__OperatorA::class], - $this->toClassNames($scalars->getEnumOperators($enum, false)), - ); - self::assertEquals( - [ - ScalarsTest__OperatorA::class, - ScalarsTest__OperatorB::class, - ScalarsTest__OperatorC::class, - ], - $this->toClassNames($scalars->getEnumOperators($enum, true)), - ); - self::assertEquals( - $scalars->getEnumOperators($enum, false), - $scalars->getEnumOperators($alias, false), - ); - self::assertEquals( - $scalars->getEnumOperators($enum, true), - $scalars->getEnumOperators($alias, true), - ); - } - // - - // - // ========================================================================= - /** - * @return array - */ - public function dataProviderAddScalar(): array { - return [ - 'ok' => [true, 'scalar', [IsNot::class]], - 'unknown scalar' => [ - new ScalarUnknown('unknown'), - 'scalar', - 'unknown', - ], - 'empty operators' => [ - new ScalarNoOperators('scalar'), - 'scalar', - [], - ], - ]; - } - // - - // - // ========================================================================= - /** - * @param array $objects - * - * @return array - */ - protected function toClassNames(array $objects): array { - $classes = []; - - foreach ($objects as $object) { - $classes[] = $object::class; - } - - return $classes; - } - // -} - -// @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses -// @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps - -/** - * @internal - * @noinspection PhpMultipleClassesDeclarationsInOneFile - */ -class ScalarsTest__Scalars extends Scalars { - // empty -} - -/** - * @internal - * @noinspection PhpMultipleClassesDeclarationsInOneFile - */ -abstract class ScalarsTest__Operator implements Operator { - public static function definition(): string { - throw new Exception('Should not be called'); - } - - public static function getName(): string { - throw new Exception('Should not be called'); - } - - public static function getDirectiveName(): string { - throw new Exception('Should not be called'); - } - - public function getFieldType(TypeProvider $provider, string $type): ?string { - throw new Exception('Should not be called'); - } - - public function getFieldDescription(): string { - throw new Exception('Should not be called'); - } - - public function getFieldDirective(): ?DirectiveNode { - throw new Exception('Should not be called'); - } - - public function isBuilderSupported(object $builder): bool { - throw new Exception('Should not be called'); - } - - public function call(Handler $handler, object $builder, Property $property, Argument $argument): object { - throw new Exception('Should not be called'); - } -} - -/** - * @internal - * @noinspection PhpMultipleClassesDeclarationsInOneFile - */ -class ScalarsTest__OperatorA extends ScalarsTest__Operator { - // empty -} - -/** - * @internal - * @noinspection PhpMultipleClassesDeclarationsInOneFile - */ -class ScalarsTest__OperatorB extends ScalarsTest__Operator { - // empty -} - -/** - * @internal - * @noinspection PhpMultipleClassesDeclarationsInOneFile - */ -class ScalarsTest__OperatorC extends ScalarsTest__Operator { - // empty -} - -/** - * @internal - * @noinspection PhpMultipleClassesDeclarationsInOneFile - */ -class ScalarsTest__OperatorD extends ScalarsTest__Operator { - // empty -} diff --git a/packages/graphql/src/Provider.php b/packages/graphql/src/Provider.php index bc1703c56..5c13fd122 100644 --- a/packages/graphql/src/Provider.php +++ b/packages/graphql/src/Provider.php @@ -13,7 +13,7 @@ use LastDragon_ru\LaraASP\GraphQL\SchemaPrinter\SchemaPrinter; use LastDragon_ru\LaraASP\GraphQL\SchemaPrinter\Settings\DefaultSettings; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Definitions\SearchByDirective; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Scalars; +use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators; use LastDragon_ru\LaraASP\GraphQL\SortBy\Definitions\SortByDirective; use LastDragon_ru\LaraASP\GraphQL\Utils\Enum\EnumType; use Nuwave\Lighthouse\Events\RegisterDirectiveNamespaces; @@ -57,7 +57,7 @@ static function (): string { } protected function registerSearchByDirective(): void { - $this->app->singleton(Scalars::class); + $this->app->singleton(Operators::class); } protected function registerEnums(): void { diff --git a/packages/graphql/src/SearchBy/Directives/DirectiveTest.php b/packages/graphql/src/SearchBy/Directives/DirectiveTest.php index d93de1669..502d8e44f 100644 --- a/packages/graphql/src/SearchBy/Directives/DirectiveTest.php +++ b/packages/graphql/src/SearchBy/Directives/DirectiveTest.php @@ -273,7 +273,7 @@ static function (TestCase $test): void { $package = Package::Name; $config = $test->app->make(Repository::class); - $config->set("{$package}.search_by.scalars.Date", [ + $config->set("{$package}.search_by.operators.Date", [ Between::class, ]); }, diff --git a/packages/graphql/src/SearchBy/Manipulator.php b/packages/graphql/src/SearchBy/Manipulator.php index d98e42a52..f301e300f 100644 --- a/packages/graphql/src/SearchBy/Manipulator.php +++ b/packages/graphql/src/SearchBy/Manipulator.php @@ -18,7 +18,7 @@ use Illuminate\Contracts\Container\Container; use Illuminate\Support\Str; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator as OperatorContract; -use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\ScalarNoOperators; +use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\TypeNoOperators; use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator as BuilderManipulator; use LastDragon_ru\LaraASP\GraphQL\Exceptions\TypeDefinitionUnknown; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Contracts\ComplexOperator; @@ -47,15 +47,15 @@ public function __construct( DirectiveLocator $directives, DocumentAST $document, TypeRegistry $types, - private Scalars $scalars, + private Operators $operators, ) { parent::__construct($container, $directives, $document, $types); } // // ========================================================================= - protected function getScalars(): Scalars { - return $this->scalars; + protected function getOperators(): Operators { + return $this->operators; } // @@ -96,10 +96,10 @@ public function getInputType(InputObjectTypeDefinitionNode|InputObjectType $node } // Add type - $operators = $this->getScalarOperators(Scalars::ScalarLogic, false); - $scalar = $this->getScalarTypeNode($name); - $content = $this->getOperatorsFields($operators, $scalar); - $type = $this->addTypeDefinition( + $logical = $this->getTypeOperators(Operators::Logical, false); + $scalar = $this->getScalarTypeNode($name); + $content = $this->getOperatorsFields($logical, $scalar); + $type = $this->addTypeDefinition( Parser::inputObjectTypeDefinition( <<getContainer()->make(Property::class); - $scalars = $this->getScalars(); - $fields = $node instanceof InputObjectType + $operators = $this->getOperators(); + $property = $this->getContainer()->make(Property::class); + $fields = $node instanceof InputObjectType ? $node->getFields() : $node->fields; @@ -140,7 +140,7 @@ public function getInputType(InputObjectTypeDefinitionNode|InputObjectType $node try { $fieldTypeNode = $this->getTypeDefinitionNode($field); } catch (TypeDefinitionUnknown $exception) { - if ($scalars->isScalar($fieldType)) { + if ($operators->hasOperators($fieldType)) { $fieldTypeNode = $this->getScalarTypeNode($fieldType); } else { throw $exception; @@ -228,7 +228,7 @@ public function getScalarType(ScalarTypeDefinitionNode|ScalarType $type, bool $n // Determine supported operators $scalar = $this->getNodeName($type); - $operators = $this->getScalarOperators($scalar, $nullable); + $operators = $this->getTypeOperators($scalar, $nullable); // Add type $mark = $nullable ? '' : '!'; @@ -290,8 +290,8 @@ protected function getComplexType( // // ========================================================================= - protected function getTypeName(string $name, string $scalar = null, bool $nullable = null): string { - return Directive::Name.'Type'.Str::studly($name).($scalar ?: '').($nullable ? 'OrNull' : ''); + protected function getTypeName(string $name, string $type = null, bool $nullable = null): string { + return Directive::Name.'Type'.Str::studly($name).($type ?: '').($nullable ? 'OrNull' : ''); } protected function getConditionTypeName(InputObjectTypeDefinitionNode|InputObjectType $node): string { @@ -323,7 +323,7 @@ protected function getComplexTypeName( * @return array */ protected function getEnumOperators(string $enum, bool $nullable): array { - $operators = $this->getScalars()->getEnumOperators($enum, $nullable); + $operators = $this->getOperators()->getEnumOperators($enum, $nullable); if (!$operators) { throw new EnumNoOperators($enum); @@ -335,11 +335,11 @@ protected function getEnumOperators(string $enum, bool $nullable): array { /** * @return array */ - protected function getScalarOperators(string $scalar, bool $nullable): array { - $operators = $this->getScalars()->getScalarOperators($scalar, $nullable); + protected function getTypeOperators(string $type, bool $nullable): array { + $operators = $this->getOperators()->getOperators($type, $nullable); if (!$operators) { - throw new ScalarNoOperators($scalar); + throw new TypeNoOperators($type); } return $operators; diff --git a/packages/graphql/src/SearchBy/Scalars.php b/packages/graphql/src/SearchBy/Operators.php similarity index 80% rename from packages/graphql/src/SearchBy/Scalars.php rename to packages/graphql/src/SearchBy/Operators.php index 8b2371fec..cbae4223d 100644 --- a/packages/graphql/src/SearchBy/Scalars.php +++ b/packages/graphql/src/SearchBy/Operators.php @@ -5,7 +5,7 @@ use Illuminate\Container\Container; use Illuminate\Contracts\Config\Repository; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator as BuilderOperator; -use LastDragon_ru\LaraASP\GraphQL\Builder\Scalars as BuilderScalars; +use LastDragon_ru\LaraASP\GraphQL\Builder\Operators as BuilderOperators; use LastDragon_ru\LaraASP\GraphQL\Package; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\Between; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\BitwiseAnd; @@ -33,33 +33,33 @@ use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Logical\AnyOf; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Logical\Not; -class Scalars extends BuilderScalars { - public const ScalarLogic = 'Logic'; - public const ScalarNumber = 'Number'; +class Operators extends BuilderOperators { + public const Logical = 'Logical'; + public const Number = 'Number'; /** * @inheritdoc */ - protected array $scalars = [ + protected array $operators = [ // Standard types - Scalars::ScalarID => [ + Operators::ID => [ Equal::class, NotEqual::class, In::class, NotIn::class, ], - Scalars::ScalarInt => [ + Operators::Int => [ BitwiseOr::class, BitwiseXor::class, BitwiseAnd::class, BitwiseLeftShift::class, BitwiseRightShift::class, ], - Scalars::ScalarFloat => Scalars::ScalarNumber, - Scalars::ScalarBoolean => [ + Operators::Float => Operators::Number, + Operators::Boolean => [ Equal::class, ], - Scalars::ScalarString => [ + Operators::String => [ Equal::class, NotEqual::class, Like::class, @@ -72,7 +72,7 @@ class Scalars extends BuilderScalars { ], // Special types - Scalars::ScalarNumber => [ + Operators::Number => [ Equal::class, NotEqual::class, LessThan::class, @@ -84,17 +84,17 @@ class Scalars extends BuilderScalars { Between::class, NotBetween::class, ], - Scalars::ScalarEnum => [ + Operators::Enum => [ Equal::class, NotEqual::class, In::class, NotIn::class, ], - Scalars::ScalarNull => [ + Operators::Null => [ IsNull::class, IsNotNull::class, ], - Scalars::ScalarLogic => [ + Operators::Logical => [ AllOf::class, AnyOf::class, Not::class, @@ -105,8 +105,8 @@ class Scalars extends BuilderScalars { * @inheritdoc */ protected array $extends = [ - Scalars::ScalarInt => Scalars::ScalarNumber, - Scalars::ScalarFloat => Scalars::ScalarNumber, + Operators::Int => Operators::Number, + Operators::Float => Operators::Number, ]; public function __construct( @@ -115,11 +115,11 @@ public function __construct( ) { parent::__construct($container); - /** @var array>|string> $scalars */ - $scalars = (array) $config->get(Package::Name.'.search_by.scalars'); + /** @var array>|string> $operators */ + $operators = (array) $config->get(Package::Name.'.search_by.operators'); - foreach ($scalars as $scalar => $operators) { - $this->addScalar($scalar, $operators); + foreach ($operators as $type => $typeOperators) { + $this->addOperators($type, $typeOperators); } } } diff --git a/packages/graphql/src/SearchBy/ScalarsTest.php b/packages/graphql/src/SearchBy/OperatorsTest.php similarity index 72% rename from packages/graphql/src/SearchBy/ScalarsTest.php rename to packages/graphql/src/SearchBy/OperatorsTest.php index 956961549..12bbb1eba 100644 --- a/packages/graphql/src/SearchBy/ScalarsTest.php +++ b/packages/graphql/src/SearchBy/OperatorsTest.php @@ -11,9 +11,9 @@ /** * @internal - * @coversDefaultClass \LastDragon_ru\LaraASP\GraphQL\SearchBy\Scalars + * @coversDefaultClass \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators */ -class ScalarsTest extends TestCase { +class OperatorsTest extends TestCase { // // ========================================================================= /** @@ -23,29 +23,29 @@ public function testConstructor(): void { $config = Mockery::mock(Repository::class); $config ->shouldReceive('get') - ->with(Package::Name.'.search_by.scalars') + ->with(Package::Name.'.search_by.operators') ->andReturn([ - Scalars::ScalarID => [ + Operators::ID => [ Equal::class, ], - Scalars::ScalarInt => [ + Operators::Int => [ NotEqual::class, ], ]); - $scalars = new class($this->app, $config) extends Scalars { + $operators = new class($this->app, $config) extends Operators { // empty }; - self::assertTrue($scalars->isScalar(Scalars::ScalarID)); - self::assertTrue($scalars->isScalar(Scalars::ScalarInt)); - self::assertFalse($scalars->isScalar('unknown')); + self::assertTrue($operators->hasOperators(Operators::ID)); + self::assertTrue($operators->hasOperators(Operators::Int)); + self::assertFalse($operators->hasOperators('unknown')); self::assertEquals( [ Equal::class, ], $this->toClassNames( - $scalars->getScalarOperators(Scalars::ScalarID, false), + $operators->getOperators(Operators::ID, false), ), ); } diff --git a/packages/graphql/src/SearchBy/Types/Flag.php b/packages/graphql/src/SearchBy/Types/Flag.php index b95502453..d2a939634 100644 --- a/packages/graphql/src/SearchBy/Types/Flag.php +++ b/packages/graphql/src/SearchBy/Types/Flag.php @@ -6,8 +6,6 @@ use GraphQL\Language\Parser; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeDefinition; -use function is_null; - class Flag implements TypeDefinition { public function __construct() { // empty @@ -19,13 +17,13 @@ public static function getName(): string { public function getTypeDefinitionNode( string $name, - string $scalar = null, + string $type = null, bool $nullable = null, ): ?TypeDefinitionNode { - $type = null; + $node = null; - if (is_null($scalar) && is_null($nullable)) { - $type = Parser::enumTypeDefinition( + if ($type === null && $nullable === null) { + $node = Parser::enumTypeDefinition( /** @lang GraphQL */ <<getNodeTypeName($node), Directive::Name); } - protected function getTypeName(string $name, string $scalar = null, bool $nullable = null): string { + protected function getTypeName(string $name, string $type = null, bool $nullable = null): string { return Directive::Name.'Type'.Str::studly($name); } diff --git a/packages/graphql/src/SortBy/Types/Direction.php b/packages/graphql/src/SortBy/Types/Direction.php index 4b378b519..0cfd6f443 100644 --- a/packages/graphql/src/SortBy/Types/Direction.php +++ b/packages/graphql/src/SortBy/Types/Direction.php @@ -6,8 +6,6 @@ use GraphQL\Language\Parser; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeDefinition; -use function is_null; - class Direction implements TypeDefinition { public function __construct() { // empty @@ -19,13 +17,13 @@ public static function getName(): string { public function getTypeDefinitionNode( string $name, - string $scalar = null, + string $type = null, bool $nullable = null, ): ?TypeDefinitionNode { - $type = null; + $node = null; - if (is_null($scalar) && is_null($nullable)) { - $type = Parser::enumTypeDefinition( + if ($type === null && $nullable === null) { + $node = Parser::enumTypeDefinition( /** @lang GraphQL */ <<\\) does not accept array\\\\.$#" + message: "#^Parameter \\#2 \\$operators of method LastDragon_ru\\\\LaraASP\\\\GraphQL\\\\Builder\\\\Operators\\:\\:addOperators\\(\\) expects array\\\\>\\|string, mixed given\\.$#" count: 1 - path: packages/graphql/src/Builder/Property.php + path: packages/graphql/src/Builder/OperatorsTest.php - - message: "#^Parameter \\#2 \\$operators of method LastDragon_ru\\\\LaraASP\\\\GraphQL\\\\Builder\\\\Scalars\\:\\:addScalar\\(\\) expects array\\\\>\\|string, mixed given\\.$#" + message: "#^Property LastDragon_ru\\\\LaraASP\\\\GraphQL\\\\Builder\\\\Property\\:\\:\\$path \\(array\\\\) does not accept array\\\\.$#" count: 1 - path: packages/graphql/src/Builder/ScalarsTest.php + path: packages/graphql/src/Builder/Property.php - message: "#^Parameter \\#1 \\$enum of class LastDragon_ru\\\\LaraASP\\\\GraphQL\\\\Utils\\\\Enum\\\\EnumType constructor expects class\\-string\\, mixed given\\.$#" @@ -214,4 +214,3 @@ parameters: message: "#^Parameter \\#2 \\$bindings of class LastDragon_ru\\\\LaraASP\\\\Testing\\\\Database\\\\QueryLog\\\\Query constructor expects array, mixed given\\.$#" count: 1 path: packages/testing/src/Utils/Args.php - From 9ca436ef6be3c86dcf007929cf659f940b9c9da4 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Sat, 27 Aug 2022 14:44:02 +0400 Subject: [PATCH 3/4] `Operators::getEnumOperators()` removed. --- packages/graphql/src/Builder/Operators.php | 11 --- .../graphql/src/Builder/OperatorsTest.php | 68 ------------------- packages/graphql/src/SearchBy/Manipulator.php | 4 +- packages/graphql/src/SearchBy/Operators.php | 1 + 4 files changed, 4 insertions(+), 80 deletions(-) diff --git a/packages/graphql/src/Builder/Operators.php b/packages/graphql/src/Builder/Operators.php index f542f3e52..6baa3c533 100644 --- a/packages/graphql/src/Builder/Operators.php +++ b/packages/graphql/src/Builder/Operators.php @@ -5,7 +5,6 @@ use GraphQL\Type\Definition\Type; use Illuminate\Container\Container; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator; -use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator as BuilderOperator; use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\TypeNoOperators; use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\TypeUnknown; @@ -25,7 +24,6 @@ abstract class Operators { public const Float = Type::FLOAT; public const String = Type::STRING; public const Boolean = Type::BOOLEAN; - public const Enum = 'Enum'; public const Null = 'Null'; /** @@ -116,13 +114,4 @@ public function getOperators(string $type, bool $nullable): array { // Return return $operators; } - - /** - * @return array - */ - public function getEnumOperators(string $enum, bool $nullable): array { - return $this->hasOperators($enum) - ? $this->getOperators($enum, $nullable) - : $this->getOperators(static::Enum, $nullable); - } } diff --git a/packages/graphql/src/Builder/OperatorsTest.php b/packages/graphql/src/Builder/OperatorsTest.php index a9e3b4a82..767a05087 100644 --- a/packages/graphql/src/Builder/OperatorsTest.php +++ b/packages/graphql/src/Builder/OperatorsTest.php @@ -162,66 +162,6 @@ public function testGetOperatorsUnknownType(): void { $operators->getOperators('unknown', false); } - - /** - * @covers ::getEnumOperators - */ - public function testGetEnumOperators(): void { - $enum = __FUNCTION__; - $alias = 'alias'; - $operators = new class($this->app) extends Operators { - // empty - }; - - $operators->addOperators($enum, [ - OperatorsTest__OperatorA::class, - OperatorsTest__OperatorA::class, - ]); - $operators->addOperators($alias, $enum); - $operators->addOperators(Operators::Enum, [ - OperatorsTest__OperatorD::class, - OperatorsTest__OperatorD::class, - ]); - $operators->addOperators(Operators::Null, [ - OperatorsTest__OperatorB::class, - OperatorsTest__OperatorC::class, - ]); - - self::assertEquals( - [ - OperatorsTest__OperatorD::class, - ], - $this->toClassNames($operators->getEnumOperators('unknown', false)), - ); - self::assertEquals( - [ - OperatorsTest__OperatorD::class, - OperatorsTest__OperatorB::class, - OperatorsTest__OperatorC::class, - ], - $this->toClassNames($operators->getEnumOperators('unknown', true)), - ); - self::assertEquals( - [OperatorsTest__OperatorA::class], - $this->toClassNames($operators->getEnumOperators($enum, false)), - ); - self::assertEquals( - [ - OperatorsTest__OperatorA::class, - OperatorsTest__OperatorB::class, - OperatorsTest__OperatorC::class, - ], - $this->toClassNames($operators->getEnumOperators($enum, true)), - ); - self::assertEquals( - $operators->getEnumOperators($enum, false), - $operators->getEnumOperators($alias, false), - ); - self::assertEquals( - $operators->getEnumOperators($enum, true), - $operators->getEnumOperators($alias, true), - ); - } // // @@ -268,14 +208,6 @@ protected function toClassNames(array $objects): array { // @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses // @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps -/** - * @internal - * @noinspection PhpMultipleClassesDeclarationsInOneFile - */ -class OperatorsTest__Operators extends Operators { - // empty -} - /** * @internal * @noinspection PhpMultipleClassesDeclarationsInOneFile diff --git a/packages/graphql/src/SearchBy/Manipulator.php b/packages/graphql/src/SearchBy/Manipulator.php index f301e300f..e71ac91ba 100644 --- a/packages/graphql/src/SearchBy/Manipulator.php +++ b/packages/graphql/src/SearchBy/Manipulator.php @@ -323,7 +323,9 @@ protected function getComplexTypeName( * @return array */ protected function getEnumOperators(string $enum, bool $nullable): array { - $operators = $this->getOperators()->getEnumOperators($enum, $nullable); + $operators = $this->getOperators()->hasOperators($enum) + ? $this->getOperators()->getOperators($enum, $nullable) + : $this->getOperators()->getOperators(Operators::Enum, $nullable); if (!$operators) { throw new EnumNoOperators($enum); diff --git a/packages/graphql/src/SearchBy/Operators.php b/packages/graphql/src/SearchBy/Operators.php index cbae4223d..a149bef35 100644 --- a/packages/graphql/src/SearchBy/Operators.php +++ b/packages/graphql/src/SearchBy/Operators.php @@ -36,6 +36,7 @@ class Operators extends BuilderOperators { public const Logical = 'Logical'; public const Number = 'Number'; + public const Enum = 'Enum'; /** * @inheritdoc From 3b2106f1282f7647f9adf7e03daeef339ed82c3d Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Sat, 27 Aug 2022 14:55:27 +0400 Subject: [PATCH 4/4] `SearchBy\Manipulator::getTypeOperators()` extracted into `WithOperators` trait. --- .../src/Builder/Traits/WithOperators.php | 24 +++++++++++++++++++ packages/graphql/src/SearchBy/Manipulator.php | 24 ++++++------------- packages/graphql/src/SortBy/Manipulator.php | 1 + 3 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 packages/graphql/src/Builder/Traits/WithOperators.php diff --git a/packages/graphql/src/Builder/Traits/WithOperators.php b/packages/graphql/src/Builder/Traits/WithOperators.php new file mode 100644 index 000000000..d6391883e --- /dev/null +++ b/packages/graphql/src/Builder/Traits/WithOperators.php @@ -0,0 +1,24 @@ + + */ + protected function getTypeOperators(string $type, bool $nullable): array { + $operators = $this->getOperators()->getOperators($type, $nullable); + + if (!$operators) { + throw new TypeNoOperators($type); + } + + return $operators; + } +} diff --git a/packages/graphql/src/SearchBy/Manipulator.php b/packages/graphql/src/SearchBy/Manipulator.php index e71ac91ba..68da3af6f 100644 --- a/packages/graphql/src/SearchBy/Manipulator.php +++ b/packages/graphql/src/SearchBy/Manipulator.php @@ -18,8 +18,8 @@ use Illuminate\Contracts\Container\Container; use Illuminate\Support\Str; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator as OperatorContract; -use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\TypeNoOperators; use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator as BuilderManipulator; +use LastDragon_ru\LaraASP\GraphQL\Builder\Traits\WithOperators; use LastDragon_ru\LaraASP\GraphQL\Exceptions\TypeDefinitionUnknown; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Contracts\ComplexOperator; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Directives\Directive; @@ -42,6 +42,8 @@ use function str_starts_with; class Manipulator extends BuilderManipulator { + use WithOperators; + public function __construct( Container $container, DirectiveLocator $directives, @@ -323,9 +325,10 @@ protected function getComplexTypeName( * @return array */ protected function getEnumOperators(string $enum, bool $nullable): array { - $operators = $this->getOperators()->hasOperators($enum) - ? $this->getOperators()->getOperators($enum, $nullable) - : $this->getOperators()->getOperators(Operators::Enum, $nullable); + $operators = $this->getOperators(); + $operators = $operators->hasOperators($enum) + ? $operators->getOperators($enum, $nullable) + : $operators->getOperators(Operators::Enum, $nullable); if (!$operators) { throw new EnumNoOperators($enum); @@ -334,19 +337,6 @@ protected function getEnumOperators(string $enum, bool $nullable): array { return $operators; } - /** - * @return array - */ - protected function getTypeOperators(string $type, bool $nullable): array { - $operators = $this->getOperators()->getOperators($type, $nullable); - - if (!$operators) { - throw new TypeNoOperators($type); - } - - return $operators; - } - protected function getComplexOperator( InputValueDefinitionNode|InputObjectTypeDefinitionNode|InputObjectField|InputObjectType ...$nodes, ): ComplexOperator { diff --git a/packages/graphql/src/SortBy/Manipulator.php b/packages/graphql/src/SortBy/Manipulator.php index 934c8a21e..bd593c1b0 100644 --- a/packages/graphql/src/SortBy/Manipulator.php +++ b/packages/graphql/src/SortBy/Manipulator.php @@ -25,6 +25,7 @@ use Nuwave\Lighthouse\Pagination\PaginateDirective; use Nuwave\Lighthouse\Pagination\PaginationType; use Nuwave\Lighthouse\Support\Contracts\FieldResolver; + use function count; use function mb_strlen; use function mb_substr;