diff --git a/packages/core/src/Utils/Cast.php b/packages/core/src/Utils/Cast.php index 3d70f4787..66a5fbf22 100644 --- a/packages/core/src/Utils/Cast.php +++ b/packages/core/src/Utils/Cast.php @@ -19,36 +19,72 @@ public static function toInt(mixed $value): int { return $value; } + public static function toIntNullable(mixed $value): ?int { + assert($value === null || is_int($value)); + + return $value; + } + public static function toFloat(mixed $value): float { assert(is_float($value)); return $value; } + public static function toFloatNullable(mixed $value): ?float { + assert($value === null || is_float($value)); + + return $value; + } + public static function toString(mixed $value): string { assert(is_string($value)); return $value; } + public static function toStringNullable(mixed $value): ?string { + assert($value === null || is_string($value)); + + return $value; + } + public static function toScalar(mixed $value): int|float|string|bool { assert(is_scalar($value)); return $value; } + public static function toScalarNullable(mixed $value): int|float|string|bool|null { + assert($value === null || is_scalar($value)); + + return $value; + } + public static function toNumber(mixed $value): int|float { assert(is_int($value) || is_float($value)); return $value; } + public static function toNumberNullable(mixed $value): int|float|null { + assert($value === null || is_int($value) || is_float($value)); + + return $value; + } + public static function toBool(mixed $value): bool { assert(is_bool($value)); return $value; } + public static function toBoolNullable(mixed $value): ?bool { + assert($value === null || is_bool($value)); + + return $value; + } + public static function toStringable(mixed $value): Stringable|string { assert(is_string($value) || $value instanceof Stringable); diff --git a/packages/graphql/UPGRADE.md b/packages/graphql/UPGRADE.md index 8bb326a49..73a34b90f 100644 --- a/packages/graphql/UPGRADE.md +++ b/packages/graphql/UPGRADE.md @@ -51,6 +51,8 @@ Please also see [changelog](https://github.com/LastDragon-ru/lara-asp/releases) * [ ] `enum SearchByTypeFlag { yes }` => `enum SearchByTypeFlag { Yes }`. 🤝 +* [ ] `@searchByOperators` => `@searchByExtendOperators`. 🤝 + * [ ] `@searchByOperatorRelation` => `@searchByOperatorRelationship` (and class too; generated types will be named as `SearchByRelationship*` instead of `SearchByComplex*`). * [ ] `LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators::Condition` => `LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators::Object`. @@ -139,6 +141,8 @@ Please also see [changelog](https://github.com/LastDragon-ru/lara-asp/releases) This section is actual only if you are extending the package. Please review and update (listed the most significant changes only): +* [ ] `LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator` must explicitly implement concrete `LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Scope` (used to filter available directive-operators, previously was required implicitly). + * [ ] `LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Handler` * [ ] `LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator` @@ -151,6 +155,8 @@ This section is actual only if you are extending the package. Please review and * [ ] `LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\Client\ConditionTooManyProperties` => `LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\Client\ConditionTooManyFields` +* [ ] `LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\TypeUnknown` removed + * [ ] `LastDragon_ru\LaraASP\GraphQL\Builder\Property` => `LastDragon_ru\LaraASP\GraphQL\Builder\Field` * [ ] `LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator` @@ -163,10 +169,18 @@ This section is actual only if you are extending the package. Please review and * [ ] `getPlaceholderTypeDefinitionNode()` removed => `LastDragon_ru\LaraASP\GraphQL\Utils\AstManipulator::getOriginType()` + * [ ] `getTypeOperators()`/`getOperator()` removed. To get operators the `LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context` should be used instead + + ```php + $context->get(LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextOperators::class)?->value + ``` + * [ ] `LastDragon_ru\LaraASP\GraphQL\Builder\Directives\HandlerDirective` * [ ] `LastDragon_ru\LaraASP\GraphQL\Builder\Directives\PropertyDirective` removed +* [ ] `LastDragon_ru\LaraASP\GraphQL\Builder\Operators` + * [ ] `LastDragon_ru\LaraASP\GraphQL\Builder\Sources\*` * [ ] `LastDragon_ru\LaraASP\GraphQL\Builder\Traits\PropertyOperator` => `LastDragon_ru\LaraASP\GraphQL\Builder\Traits\HandlerOperator` diff --git a/packages/graphql/docs/Directives/@searchBy.md b/packages/graphql/docs/Directives/@searchBy.md index bd590390a..044dbec39 100644 --- a/packages/graphql/docs/Directives/@searchBy.md +++ b/packages/graphql/docs/Directives/@searchBy.md @@ -218,12 +218,32 @@ The package also defines a few own types in addition to the standard GraphQL typ ```graphql scalar MyScalar -@searchByOperators(type: "MyScalar") # Re-use operators for `MyScalar` from config -@searchByOperators(type: "Int") # Re-use operators from `Int` from schema -@searchByOperatorEqual # Package operator -@myOperator # Custom operator +@searchByExtendOperators # Re-use operators for `MyScalar` from config +@searchByExtendOperators(type: "MyScalar") # same +@searchByExtendOperators(type: "Int") # Re-use operators from `Int` from schema +@searchByOperatorEqual # Add package operator +@myOperator # Add custom operator ``` +[include:exec]: <../../../../dev/artisan dev:directive @searchByExtendOperators> +[//]: # (start: fb9508c1688c78899393b1119463a14ebcc2c0872316ca676b2945a296312230) +[//]: # (warning: Generated automatically. Do not edit.) + +```graphql +""" +Extends the list of operators by the operators from the specified +`type` or from the config if `null`. +""" +directive @searchByExtendOperators( + type: String +) +on + | ENUM + | SCALAR +``` + +[//]: # (end: fb9508c1688c78899393b1119463a14ebcc2c0872316ca676b2945a296312230) + ### Schema ```php diff --git a/packages/graphql/src/Builder/Context/HandlerContextOperators.php b/packages/graphql/src/Builder/Context/HandlerContextOperators.php new file mode 100644 index 000000000..f855f08b3 --- /dev/null +++ b/packages/graphql/src/Builder/Context/HandlerContextOperators.php @@ -0,0 +1,13 @@ + + */ + protected static function getDirectiveLocations(): array { + return [ + DirectiveLocation::SCALAR, + DirectiveLocation::ENUM, + ]; + } + + public function getType(): ?string { + return Cast::toStringNullable($this->directiveArgValue('type')); + } +} diff --git a/packages/graphql/src/Builder/Directives/HandlerDirective.php b/packages/graphql/src/Builder/Directives/HandlerDirective.php index 2af537291..70a98c0f7 100644 --- a/packages/graphql/src/Builder/Directives/HandlerDirective.php +++ b/packages/graphql/src/Builder/Directives/HandlerDirective.php @@ -19,11 +19,11 @@ use LastDragon_ru\LaraASP\GraphQL\Builder\Context; use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextBuilderInfo; use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextImplicit; +use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextOperators; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context as ContextContract; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Enhancer; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Handler; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator; -use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Scope; use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\Client\ConditionEmpty; use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\Client\ConditionTooManyFields; use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\Client\ConditionTooManyOperators; @@ -55,23 +55,11 @@ abstract class HandlerDirective extends BaseDirective implements Handler, Enhanc use WithSource; public function __construct( - private ArgumentFactory $factory, + protected readonly ArgumentFactory $argumentFactory, ) { // empty } - // - // ========================================================================= - /** - * @return class-string - */ - abstract public static function getScope(): string; - - protected function getFactory(): ArgumentFactory { - return $this->factory; - } - // - // // ========================================================================= /** @@ -104,7 +92,7 @@ protected function handleAnyBuilder( ): object { if ($value !== null && $this->definitionNode instanceof InputValueDefinitionNode) { $argument = !($value instanceof Argument) - ? $this->getFactory()->getArgument($this->definitionNode, $value) + ? $this->argumentFactory->getArgument($this->definitionNode, $value) : $value; $builder = $this->enhance($builder, $argument, $field, $context); } @@ -287,7 +275,8 @@ protected function getArgumentTypeDefinitionNode( string $operator, ): ListTypeNode|NamedTypeNode|NonNullTypeNode|null { // Supported? - $operator = $manipulator->getOperator($operator, static::getScope(), $argument, $context); + $provider = $context->get(HandlerContextOperators::class)?->value; + $operator = $provider?->getOperator($manipulator, $operator, $argument, $context); if (!$operator) { return null; diff --git a/packages/graphql/src/Builder/Directives/OperatorsDirective.php b/packages/graphql/src/Builder/Directives/OperatorsDirective.php index 8a45007cf..8675c1405 100644 --- a/packages/graphql/src/Builder/Directives/OperatorsDirective.php +++ b/packages/graphql/src/Builder/Directives/OperatorsDirective.php @@ -2,9 +2,7 @@ namespace LastDragon_ru\LaraASP\GraphQL\Builder\Directives; -use GraphQL\Language\DirectiveLocation; use Nuwave\Lighthouse\Schema\DirectiveLocator; -use Nuwave\Lighthouse\Schema\Directives\BaseDirective; use Override; use function array_unique; @@ -12,11 +10,10 @@ use function implode; use function is_string; -abstract class OperatorsDirective extends BaseDirective { - public function __construct() { - // empty - } - +/** + * @deprecated 5.6.0 Use {@see ExtendOperatorsDirective} instead. + */ +abstract class OperatorsDirective extends ExtendOperatorsDirective { #[Override] public static function definition(): string { $name = DirectiveLocator::directiveName(static::class); @@ -25,21 +22,14 @@ public static function definition(): string { return << - */ - protected static function getDirectiveLocations(): array { - return [ - DirectiveLocation::SCALAR, - DirectiveLocation::ENUM, - ]; - } - + #[Override] public function getType(): string { $type = $this->directiveArgValue('type'); diff --git a/packages/graphql/src/Builder/Exceptions/TypeUnknown.php b/packages/graphql/src/Builder/Exceptions/TypeUnknown.php deleted file mode 100644 index a7ebc440c..000000000 --- a/packages/graphql/src/Builder/Exceptions/TypeUnknown.php +++ /dev/null @@ -1,32 +0,0 @@ - $scope - */ - public function __construct( - protected string $scope, - protected string $name, - Throwable $previous = null, - ) { - parent::__construct( - sprintf( - 'Type `%s` in `%s` scope is not defined.', - $this->name, - $this->scope, - ), - $previous, - ); - } - - public function getName(): string { - return $this->name; - } -} diff --git a/packages/graphql/src/Builder/Manipulator.php b/packages/graphql/src/Builder/Manipulator.php index 1d85cd319..40ba1da75 100644 --- a/packages/graphql/src/Builder/Manipulator.php +++ b/packages/graphql/src/Builder/Manipulator.php @@ -5,24 +5,26 @@ use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\InputObjectTypeDefinitionNode; use GraphQL\Language\AST\InterfaceTypeDefinitionNode; +use GraphQL\Language\AST\Node; use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\AST\TypeDefinitionNode; use GraphQL\Language\AST\TypeNode; use GraphQL\Language\BlockString; use GraphQL\Language\Parser; use GraphQL\Language\Printer; +use GraphQL\Type\Definition\Argument; +use GraphQL\Type\Definition\FieldDefinition; +use GraphQL\Type\Definition\InputObjectField; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use Illuminate\Container\Container; +use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextOperators; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; -use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Ignored; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator; -use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Scope; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeProvider; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource; -use LastDragon_ru\LaraASP\GraphQL\Builder\Directives\OperatorsDirective; use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\FakeTypeDefinitionIsNotFake; use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\FakeTypeDefinitionUnknown; use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\OperatorImpossibleToCreateField; @@ -34,21 +36,15 @@ use LastDragon_ru\LaraASP\GraphQL\Builder\Sources\Source; use LastDragon_ru\LaraASP\GraphQL\Utils\AstManipulator; use Nuwave\Lighthouse\Schema\DirectiveLocator; +use Nuwave\Lighthouse\Support\Contracts\Directive; use Override; use function array_map; -use function array_push; use function array_unshift; -use function array_values; use function count; use function implode; class Manipulator extends AstManipulator implements TypeProvider { - /** - * @var array, Operators> - */ - private array $operators = []; - // // ========================================================================= #[Override] @@ -103,89 +99,41 @@ public function getTypeSource(TypeDefinitionNode|TypeNode|Type $type): TypeSourc // // ========================================================================= - public function addOperators(Operators $operators): static { - $this->operators[$operators->getScope()] = $operators; - - return $this; - } - /** * @template T of Operator * - * @param class-string $operator - * @param class-string $scope + * @param Node|(TypeDefinitionNode&Node)|Type|InputObjectField|FieldDefinition|Argument $node + * @param class-string $operator * - * @return T|null + * @return (T&Directive)|null */ - public function getOperator(string $operator, string $scope, TypeSource $source, Context $context): ?Operator { - // Provider? - $provider = $this->operators[$scope] ?? null; - - if (!$provider) { - return null; - } - - // Available? - $operator = $provider->getOperator($operator); - - if (!$operator->isAvailable($this, $source, $context)) { - return null; - } - - // Return - return $operator; - } - - /** - * @param class-string $scope - * - * @return list - */ - public function getTypeOperators( - string $type, - string $scope, + public function getOperatorDirective( + Node|TypeDefinitionNode|Type|InputObjectField|FieldDefinition|Argument $node, + string $operator, TypeSource $source, Context $context, - string ...$extras, - ): array { - // Provider? - $provider = $this->operators[$scope] ?? null; + ): ?Operator { + // Operators? + $provider = $context->get(HandlerContextOperators::class)?->value; if (!$provider) { - return []; - } - - // Operators - $operators = $this->getOperators($provider, $scope, $type); - - if (!$operators) { - return []; - } - - // Extra - foreach ($extras as $extra) { - array_push($operators, ...$this->getOperators($provider, $scope, $extra)); + return null; } - // Unique - $unique = []; + // Search + $instance = null; + $directives = $this->getDirectives($node, $operator); - foreach ($operators as $operator) { - if (isset($unique[$operator::class])) { - continue; - } + foreach ($directives as $directive) { + $directive = $provider->getOperator($this, $directive, $source, $context); - if (!$operator->isAvailable($this, $source, $context)) { - continue; + if ($directive) { + $instance = $directive; + break; } - - $unique[$operator::class] = $operator; } - $unique = array_values($unique); - - // Return - return $unique; + return $instance; } /** @@ -244,15 +192,15 @@ public function getOperatorField( * @param list $operators */ public function getOperatorsFields(array $operators, TypeSource $source, Context $context): string { - return implode( - "\n", - array_map( - function (Operator $operator) use ($source, $context): string { - return $this->getOperatorField($operator, $source, $context, null); - }, - $operators, - ), - ); + $fields = []; + + foreach ($operators as $operator) { + if (!isset($fields[$operator::class])) { + $fields[$operator::class] = $this->getOperatorField($operator, $source, $context, null); + } + } + + return implode("\n", $fields); } // @@ -288,50 +236,5 @@ protected function removeFakeTypeDefinition(string $name): void { // Remove $this->removeTypeDefinition($name); } - - /** - * @param class-string $scope - * - * @return array - */ - private function getOperators(Operators $provider, string $scope, string $type): array { - $ignored = false; - $operators = []; - - if ($this->isTypeDefinitionExists($type)) { - $node = $this->getTypeDefinition($type); - $directives = $this->getDirectives($node); - - foreach ($directives as $directive) { - if (!($directive instanceof $scope)) { - continue; - } - - if ($directive instanceof OperatorsDirective) { - $directiveType = $directive->getType(); - - if ($type !== $directiveType) { - array_push($operators, ...$this->getOperators($provider, $scope, $directiveType)); - } else { - array_push($operators, ...$provider->getOperators($type)); - } - } elseif ($directive instanceof Operator) { - $operators[] = $directive; - } elseif ($directive instanceof Ignored) { - $ignored = true; - $operators = []; - break; - } else { - // empty - } - } - } - - if (!$operators && !$ignored && $provider->hasOperators($type)) { - array_push($operators, ...$provider->getOperators($type)); - } - - return $operators; - } // } diff --git a/packages/graphql/src/Builder/ManipulatorTest.php b/packages/graphql/src/Builder/ManipulatorTest.php deleted file mode 100644 index 01ecc03a2..000000000 --- a/packages/graphql/src/Builder/ManipulatorTest.php +++ /dev/null @@ -1,357 +0,0 @@ - - // ========================================================================= - public function testGetTypeOperators(): void { - // Operators - $scope = new class() implements Scope { - // empty; - }; - $builder = new stdClass(); - $aOperator = ManipulatorTest_OperatorA::class; - $bOperator = ManipulatorTest_OperatorB::class; - $cOperator = ManipulatorTest_OperatorC::class; - - // Types - $types = Container::getInstance()->make(TypeRegistry::class); - - $types->register( - new CustomScalarType([ - 'name' => 'TestScalar', - ]), - ); - $types->register( - new CustomScalarType([ - 'name' => 'TestOperators', - ]), - ); - $types->register( - new CustomScalarType([ - 'name' => 'TestBuiltinOperators', - ]), - ); - - // Directives - $directives = Container::getInstance()->make(DirectiveLocator::class); - - $directives->setResolved('ignored', ManipulatorTest_Ignored::class); - $directives->setResolved('operators', ManipulatorTest_Operators::class); - $directives->setResolved('aOperator', $aOperator); - $directives->setResolved('bOperator', $bOperator); - $directives->setResolved('cOperator', $cOperator); - - // Schema - $this->useGraphQLSchema( - <<<'GRAPHQL' - scalar TestScalar - @aOperator - @bOperator - @cOperator - - scalar TestIgnored - @aOperator - @ignored - - scalar TestOperators - @operators(type: "TestScalar") - - scalar TestBuiltinOperators - @operators(type: "TestBuiltinOperators") - @aOperator - - type Query { - test: Int @all - } - GRAPHQL, - ); - - // Operators - $config = []; - $default = [ - Operators::ID => [ - $aOperator, - $bOperator, - ], - Operators::Int => [ - $bOperator, - $cOperator, - ], - 'TestBuiltinOperators' => [ - $cOperator, - ], - ]; - $operators = new class($config, $default) extends Operators { - /** - * @param array|string>> $operators - * @param array|string>> $default - */ - public function __construct(array $operators = [], array $default = []) { - parent::__construct($operators); - - $this->default = $default; - } - - #[Override] - public function getScope(): string { - return Scope::class; - } - }; - - // Manipulator - $source = Mockery::mock(TypeSource::class); - $context = (new Context())->override([ - HandlerContextBuilderInfo::class => new HandlerContextBuilderInfo( - new BuilderInfo($builder::class, $builder::class), - ), - ]); - $document = Container::getInstance()->make(ASTBuilder::class)->documentAST(); - $manipulator = Container::getInstance()->make(Manipulator::class, [ - 'document' => $document, - ]); - - $manipulator->addOperators($operators); - - // Test - $map = static function (Operator $operator): string { - return $operator::class; - }; - - self::assertEquals( - [ - $aOperator, - ], - array_map($map, $manipulator->getTypeOperators(Operators::ID, $operators->getScope(), $source, $context)), - ); - self::assertEquals( - [ - $aOperator, - $cOperator, - ], - array_map( - $map, - $manipulator->getTypeOperators( - Operators::ID, - $operators->getScope(), - $source, - $context, - Operators::Int, - ), - ), - ); - self::assertEquals( - [ - // empty (another scope) - ], - array_map($map, $manipulator->getTypeOperators(Operators::ID, $scope::class, $source, $context)), - ); - self::assertEquals( - [ - $aOperator, - ], - array_map($map, $manipulator->getTypeOperators('TestScalar', $operators->getScope(), $source, $context)), - ); - self::assertEquals( - [ - $aOperator, - ], - array_map($map, $manipulator->getTypeOperators('TestOperators', $operators->getScope(), $source, $context)), - ); - self::assertEquals( - [ - $cOperator, - $aOperator, - ], - array_map( - $map, - $manipulator->getTypeOperators('TestBuiltinOperators', $operators->getScope(), $source, $context), - ), - ); - self::assertEquals( - [ - // empty - ], - array_map( - $map, - $manipulator->getTypeOperators('Unknown', $operators->getScope(), $source, $context, Operators::ID), - ), - ); - self::assertEquals( - [ - // empty - ], - array_map( - $map, - $manipulator->getTypeOperators('TestIgnored', $operators->getScope(), $source, $context), - ), - ); - } - // -} - -// @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses -// @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps - -/** - * @internal - * @noinspection PhpMultipleClassesDeclarationsInOneFile - */ -class ManipulatorTest_Operators extends OperatorsDirective implements Scope { - // empty -} - -/** - * @internal - * @noinspection PhpMultipleClassesDeclarationsInOneFile - */ -class ManipulatorTest_OperatorA extends OperatorDirective implements Operator, Scope { - #[Override] - public static function getName(): string { - return 'a'; - } - - #[Override] - public function getFieldType(TypeProvider $provider, TypeSource $source, ContextContract $context): ?string { - return $source->getTypeName(); - } - - #[Override] - public function getFieldDescription(): ?string { - return ''; - } - - #[Override] - protected function isBuilderSupported(string $builder): bool { - return is_a($builder, stdClass::class, true); - } - - #[Override] - public function call( - Handler $handler, - object $builder, - Field $field, - Argument $argument, - ContextContract $context, - ): object { - return $builder; - } -} - -/** - * @internal - * @noinspection PhpMultipleClassesDeclarationsInOneFile - */ -class ManipulatorTest_OperatorB extends OperatorDirective implements Operator { - #[Override] - public static function getName(): string { - return 'b'; - } - - #[Override] - public function getFieldType(TypeProvider $provider, TypeSource $source, ContextContract $context): ?string { - return $source->getTypeName(); - } - - #[Override] - public function getFieldDescription(): ?string { - return ''; - } - - #[Override] - protected function isBuilderSupported(string $builder): bool { - return false; - } - - #[Override] - public function call( - Handler $handler, - object $builder, - Field $field, - Argument $argument, - ContextContract $context, - ): object { - return $builder; - } -} - -/** - * @internal - * @noinspection PhpMultipleClassesDeclarationsInOneFile - */ -class ManipulatorTest_OperatorC extends OperatorDirective implements Operator { - #[Override] - public static function getName(): string { - return 'c'; - } - - #[Override] - public function getFieldType(TypeProvider $provider, TypeSource $source, ContextContract $context): ?string { - return $source->getTypeName(); - } - - #[Override] - public function getFieldDescription(): ?string { - return ''; - } - - #[Override] - protected function isBuilderSupported(string $builder): bool { - return is_a($builder, stdClass::class, true); - } - - #[Override] - public function call( - Handler $handler, - object $builder, - Field $field, - Argument $argument, - ContextContract $context, - ): object { - return $builder; - } -} - -/** - * @internal - * @noinspection PhpMultipleClassesDeclarationsInOneFile - */ -class ManipulatorTest_Ignored extends BaseDirective implements Ignored, Scope { - #[Override] - public static function definition(): string { - return <<<'GRAPHQL' - directive @ignored on SCALAR - GRAPHQL; - } -} diff --git a/packages/graphql/src/Builder/Operators.php b/packages/graphql/src/Builder/Operators.php index 016e32b6f..cd9ffae7d 100644 --- a/packages/graphql/src/Builder/Operators.php +++ b/packages/graphql/src/Builder/Operators.php @@ -4,18 +4,18 @@ use GraphQL\Type\Definition\Type; use Illuminate\Container\Container; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Ignored; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Scope; -use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\TypeUnknown; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource; +use LastDragon_ru\LaraASP\GraphQL\Builder\Directives\ExtendOperatorsDirective; +use LastDragon_ru\LaraASP\GraphQL\Utils\AstManipulator; -use function array_key_exists; -use function array_map; use function array_merge; -use function array_push; -use function array_shift; -use function array_unique; use function array_values; use function is_a; +use function is_string; abstract class Operators { public const ID = Type::ID; @@ -42,82 +42,172 @@ abstract class Operators { * @param array|string>> $operators */ public function __construct(array $operators = []) { - foreach ($operators as $key => $value) { - $this->setOperators($key, $value); + foreach ($operators as $type => $value) { + $this->operators[$type] = $value; } } /** * @return class-string */ - abstract public function getScope(): string; + abstract protected function getScope(): string; /** * @template T of Operator * - * @param class-string $operator + * @param T|class-string $operator * - * @return T + * @return T|null */ - public function getOperator(string $operator): Operator { - return Container::getInstance()->make($operator); - } + public function getOperator( + Manipulator $manipulator, + Operator|string $operator, + TypeSource $source, + Context $context, + ): ?Operator { + if (!is_a($operator, $this->getScope(), true)) { + return null; + } + + if (is_string($operator)) { + $operator = Container::getInstance()->make($operator); + } + + if (!$operator->isAvailable($manipulator, $source, $context)) { + return null; + } - public function hasOperators(string $type): bool { - return array_key_exists($type, $this->operators) - || array_key_exists($type, $this->default); + return $operator; } /** - * @param list|string> $operators + * @return list */ - public function setOperators(string $type, array $operators): void { - $this->operators[$type] = $operators; + public function getOperators(Manipulator $manipulator, string $type, TypeSource $source, Context $context): array { + // Operators + $unique = []; + $operators = $this->findOperators($manipulator, $type); + + foreach ($operators as $operator) { + $operator = $this->getOperator($manipulator, $operator, $source, $context); + + if ($operator && !isset($unique[$operator::class])) { + $unique[$operator::class] = $operator; + } + } + + // Return + return array_values($unique); } /** - * @return list + * @param array $processed + * + * @return array|Operator> */ - public function getOperators(string $type): array { - // Is known? - if (!$this->hasOperators($type)) { - throw new TypeUnknown($this->getScope(), $type); + private function findOperators( + AstManipulator $manipulator, + string $type, + int $level = 0, + array &$processed = [], + ): array { + // We have several levels where operators can be defined - AST, config, + // and built-in defaults. We are always starting at the highest level + // and go deeper if there are no operators or if the type with the same + // name is found. + + // Processed? + if (isset($processed[$type])) { + return []; } - // Operators - $operators = $this->findOperators($type); - $operators = array_map($this->getOperator(...), $operators); + // Search for operators + $list = match ($level) { + 0 => $this->findAstOperators($manipulator, $type), + 1 => $this->findConfigOperators($type), + 2 => $this->findDefaultOperators($type), + default => null, + }; + + if ($list === null) { + return []; + } + + // Merge + $operators = []; + + foreach ($list as $operator) { + if (is_a($operator, Operator::class, true)) { + $operators[] = $operator; + } elseif ($type !== $operator) { + $processed[$type] = true; + $operators = array_merge( + $operators, + $this->findOperators($manipulator, $operator, 0, $processed), + ); + } else { + $operators = array_merge( + $operators, + $this->findOperators($manipulator, $operator, $level + 1, $processed), + ); + } + } + + // Empty? + if (!$operators) { + $operators = $this->findOperators($manipulator, $type, $level + 1, $processed); + } + + // Mark + $processed[$type] = true; // Return return $operators; } /** - * @return list> + * @return array|Operator|string>|null */ - private function findOperators(string $type): array { - $extends = $this->operators[$type] ?? $this->default[$type] ?? []; - $operators = []; - $processed = []; + private function findAstOperators(AstManipulator $manipulator, string $type): ?array { + if (!$manipulator->isTypeDefinitionExists($type)) { + return []; + } - do { - $operator = array_shift($extends); + $scope = $this->getScope(); + $operators = []; + $directives = $manipulator->getDirectives($manipulator->getTypeDefinition($type)); - if ($operator === null || isset($processed[$operator])) { + foreach ($directives as $directive) { + if (!($directive instanceof $scope)) { continue; } - if (is_a($operator, Operator::class, true)) { - $operators[] = $operator; - } elseif ($type === $operator) { - array_push($extends, ...($this->default[$operator] ?? [])); + if ($directive instanceof ExtendOperatorsDirective) { + $operators[] = $directive->getType() ?? $type; + } elseif ($directive instanceof Operator) { + $operators[] = $directive; + } elseif ($directive instanceof Ignored) { + $operators = null; + break; } else { - $operators = array_merge($operators, $this->findOperators($operator)); + // empty } + } + + return $operators; + } - $processed[$operator] = true; - } while ($extends); + /** + * @return array|string> + */ + private function findConfigOperators(string $type): array { + return $this->operators[$type] ?? []; + } - return array_values(array_unique($operators)); + /** + * @return array|string> + */ + private function findDefaultOperators(string $type): array { + return $this->default[$type] ?? []; } } diff --git a/packages/graphql/src/Builder/OperatorsTest.php b/packages/graphql/src/Builder/OperatorsTest.php index 886f9fd2b..4e5325c1b 100644 --- a/packages/graphql/src/Builder/OperatorsTest.php +++ b/packages/graphql/src/Builder/OperatorsTest.php @@ -3,15 +3,22 @@ namespace LastDragon_ru\LaraASP\GraphQL\Builder; use Exception; +use Illuminate\Container\Container; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Handler; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Ignored; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Scope; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeProvider; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource; -use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\TypeUnknown; +use LastDragon_ru\LaraASP\GraphQL\Builder\Directives\ExtendOperatorsDirective; +use LastDragon_ru\LaraASP\GraphQL\Builder\Directives\OperatorsDirective; use LastDragon_ru\LaraASP\GraphQL\Testing\Package\TestCase; +use Mockery; use Nuwave\Lighthouse\Execution\Arguments\Argument; +use Nuwave\Lighthouse\Schema\AST\ASTBuilder; +use Nuwave\Lighthouse\Schema\DirectiveLocator; +use Nuwave\Lighthouse\Schema\Directives\BaseDirective; use Override; use PHPUnit\Framework\Attributes\CoversClass; @@ -22,86 +29,124 @@ final class OperatorsTest extends TestCase { // // ========================================================================= - public function testHasOperators(): void { - $operators = new class() extends Operators { - /** - * @inheritDoc - */ - protected array $default = [ - Operators::Int => [ - OperatorsTest__OperatorA::class, - ], - ]; - - #[Override] - public function getScope(): string { - return Scope::class; - } - }; + public function testGetOperators(): void { + // Directives + $directives = Container::getInstance()->make(DirectiveLocator::class); - self::assertTrue($operators->hasOperators(Operators::Int)); - self::assertFalse($operators->hasOperators('unknown')); - } + $directives->setResolved('ignored', OperatorsTest__Ignored::class); + $directives->setResolved('operators', OperatorsTest__OperatorsDirective::class); + $directives->setResolved('extendOperators', OperatorsTest__ExtendOperatorsDirective::class); + $directives->setResolved('aOperator', OperatorsTest__OperatorA::class); + $directives->setResolved('bOperator', OperatorsTest__OperatorB::class); + $directives->setResolved('cOperator', OperatorsTest__OperatorC::class); + $directives->setResolved('externalOperator', OperatorsTest__OperatorExternal::class); - public function testGetOperators(): void { - $config = [ - 'alias' => [ - 'type-a', + // Schema + $this->useGraphQLSchema( + <<<'GRAPHQL' + scalar SchemaTypeA + @aOperator + @bOperator + @cOperator + @externalOperator + + scalar SchemaTypeIgnored + @aOperator + @ignored + + scalar SchemaTypeD + @operators(type: "TypeD") + @externalOperator + + scalar TypeD + @extendOperators + @externalOperator + + scalar SchemaTypeInfiniteLoop + @operators(type: "InfiniteLoop") + @aOperator + + type Query { + test: Int @all + } + GRAPHQL, + ); + + // Config + $config = [ + 'Alias' => [ + 'TypeA', ], - 'type-a' => [ + 'TypeA' => [ + OperatorsTest__OperatorA::class, OperatorsTest__OperatorA::class, + OperatorsTest__OperatorExternal::class, + OperatorsTest__OperatorNotAvailable::class, + ], + 'TypeB' => [ + OperatorsTest__OperatorNotAvailable::class, + OperatorsTest__OperatorExternal::class, OperatorsTest__OperatorA::class, + 'TypeB', + ], + 'TypeD' => [ + 'TypeD', ], - 'type-b' => [ + 'InfiniteLoop' => [ + OperatorsTest__OperatorNotAvailable::class, + OperatorsTest__OperatorExternal::class, OperatorsTest__OperatorA::class, - 'type-b', + 'SchemaTypeInfiniteLoop', ], ]; - $default = [ - 'type-a' => [ + $default = [ + 'TypeA' => [ OperatorsTest__OperatorA::class, OperatorsTest__OperatorB::class, OperatorsTest__OperatorC::class, + OperatorsTest__OperatorExternal::class, + OperatorsTest__OperatorNotAvailable::class, ], - 'type-b' => [ + 'TypeB' => [ + OperatorsTest__OperatorNotAvailable::class, + OperatorsTest__OperatorExternal::class, OperatorsTest__OperatorB::class, - 'type-b', + 'TypeB', ], - 'type-c' => [ + 'TypeC' => [ + OperatorsTest__OperatorNotAvailable::class, + OperatorsTest__OperatorExternal::class, OperatorsTest__OperatorC::class, - 'type-b', - 'type-a', + 'TypeB', + 'TypeA', + ], + 'TypeD' => [ + OperatorsTest__OperatorNotAvailable::class, + OperatorsTest__OperatorExternal::class, + OperatorsTest__OperatorA::class, ], ]; - $operators = new class($config, $default) extends Operators { - /** - * @param array|string>> $operators - * @param array|string>> $default - */ - public function __construct(array $operators = [], array $default = []) { - parent::__construct($operators); - - $this->default = $default; - } - - #[Override] - public function getScope(): string { - return Scope::class; - } - }; + $source = Mockery::mock(TypeSource::class); + $context = Mockery::mock(Context::class); + $operators = new OperatorsTest__Operators($config, $default); + $document = Container::getInstance()->make(ASTBuilder::class)->documentAST(); + $manipulator = Container::getInstance()->make(Manipulator::class, [ + 'document' => $document, + ]); + // Tests self::assertEquals( [ OperatorsTest__OperatorA::class, ], - $this->toClassNames($operators->getOperators('type-a')), + $this->toClassNames($operators->getOperators($manipulator, 'TypeA', $source, $context)), ); self::assertEquals( [ OperatorsTest__OperatorA::class, OperatorsTest__OperatorB::class, ], - $this->toClassNames($operators->getOperators('type-b')), + $this->toClassNames($operators->getOperators($manipulator, 'TypeB', $source, $context)), ); self::assertEquals( [ @@ -109,25 +154,44 @@ public function getScope(): string { OperatorsTest__OperatorA::class, OperatorsTest__OperatorB::class, ], - $this->toClassNames($operators->getOperators('type-c')), + $this->toClassNames($operators->getOperators($manipulator, 'TypeC', $source, $context)), ); self::assertEquals( - $operators->getOperators('type-a'), - $operators->getOperators('alias'), + $operators->getOperators($manipulator, 'TypeA', $source, $context), + $operators->getOperators($manipulator, 'Alias', $source, $context), + ); + self::assertEquals( + [ + OperatorsTest__OperatorA::class, + OperatorsTest__OperatorB::class, + OperatorsTest__OperatorC::class, + ], + $this->toClassNames($operators->getOperators($manipulator, 'SchemaTypeA', $source, $context)), + ); + self::assertEquals( + [ + OperatorsTest__OperatorA::class, + ], + $this->toClassNames($operators->getOperators($manipulator, 'TypeD', $source, $context)), + ); + self::assertEquals( + [ + OperatorsTest__OperatorA::class, + ], + $this->toClassNames($operators->getOperators($manipulator, 'SchemaTypeD', $source, $context)), + ); + self::assertEquals( + [ + // empty + ], + $this->toClassNames($operators->getOperators($manipulator, 'SchemaTypeIgnored', $source, $context)), + ); + self::assertEquals( + [ + OperatorsTest__OperatorA::class, + ], + $this->toClassNames($operators->getOperators($manipulator, 'SchemaTypeInfiniteLoop', $source, $context)), ); - } - - public function testGetOperatorsUnknownType(): void { - $operators = new class() extends Operators { - #[Override] - public function getScope(): string { - return Scope::class; - } - }; - - self::expectExceptionObject(new TypeUnknown($operators->getScope(), 'unknown')); - - $operators->getOperators('unknown'); } // @@ -153,6 +217,53 @@ protected function toClassNames(array $objects): array { // @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses // @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +interface OperatorsTest__Scope extends Scope { + // empty +} + +/** + * @deprecated 5.6.0 + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + * + */ +class OperatorsTest__OperatorsDirective extends OperatorsDirective implements OperatorsTest__Scope { + // empty +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class OperatorsTest__ExtendOperatorsDirective extends ExtendOperatorsDirective implements OperatorsTest__Scope { + // empty +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class OperatorsTest__Operators extends Operators { + /** + * @param array|string>> $operators + * @param array|string>> $default + */ + public function __construct(array $operators = [], array $default = []) { + parent::__construct($operators); + + $this->default = $default; + } + + #[Override] + public function getScope(): string { + return OperatorsTest__Scope::class; + } +} + /** * @internal * @noinspection PhpMultipleClassesDeclarationsInOneFile @@ -170,7 +281,7 @@ public static function getName(): string { #[Override] public function isAvailable(TypeProvider $provider, TypeSource $source, Context $context): bool { - throw new Exception('Should not be called'); + return true; } #[Override] @@ -199,7 +310,7 @@ public function call( * @internal * @noinspection PhpMultipleClassesDeclarationsInOneFile */ -class OperatorsTest__OperatorA extends OperatorsTest__Operator { +class OperatorsTest__OperatorA extends OperatorsTest__Operator implements OperatorsTest__Scope { // empty } @@ -207,7 +318,7 @@ class OperatorsTest__OperatorA extends OperatorsTest__Operator { * @internal * @noinspection PhpMultipleClassesDeclarationsInOneFile */ -class OperatorsTest__OperatorB extends OperatorsTest__Operator { +class OperatorsTest__OperatorB extends OperatorsTest__Operator implements OperatorsTest__Scope { // empty } @@ -215,7 +326,7 @@ class OperatorsTest__OperatorB extends OperatorsTest__Operator { * @internal * @noinspection PhpMultipleClassesDeclarationsInOneFile */ -class OperatorsTest__OperatorC extends OperatorsTest__Operator { +class OperatorsTest__OperatorC extends OperatorsTest__Operator implements OperatorsTest__Scope { // empty } @@ -223,6 +334,30 @@ class OperatorsTest__OperatorC extends OperatorsTest__Operator { * @internal * @noinspection PhpMultipleClassesDeclarationsInOneFile */ -class OperatorsTest__OperatorD extends OperatorsTest__Operator { +class OperatorsTest__OperatorNotAvailable extends OperatorsTest__Operator implements OperatorsTest__Scope { + #[Override] + public function isAvailable(TypeProvider $provider, TypeSource $source, Context $context): bool { + return false; + } +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class OperatorsTest__OperatorExternal extends OperatorsTest__Operator { // empty } + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class OperatorsTest__Ignored extends BaseDirective implements Ignored, OperatorsTest__Scope { + #[Override] + public static function definition(): string { + return <<<'GRAPHQL' + directive @ignored on SCALAR + GRAPHQL; + } +} diff --git a/packages/graphql/src/Builder/Types/InputObject.php b/packages/graphql/src/Builder/Types/InputObject.php index 858d0065a..4f4db6be7 100644 --- a/packages/graphql/src/Builder/Types/InputObject.php +++ b/packages/graphql/src/Builder/Types/InputObject.php @@ -13,10 +13,10 @@ use GraphQL\Type\Definition\InputObjectField; use GraphQL\Type\Definition\Type; use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextImplicit; +use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextOperators; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Ignored; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator; -use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Scope; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeDefinition; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource; use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\TypeDefinitionFieldAlreadyDefined; @@ -46,11 +46,6 @@ public function __construct() { // empty } - /** - * @return class-string - */ - abstract protected function getScope(): string; - abstract protected function getDescription( Manipulator $manipulator, InputSource|ObjectSource|InterfaceSource $source, @@ -141,8 +136,9 @@ protected function getOperators( Context $context, ): array { $type = $this->getTypeForOperators(); - $operators = $type - ? $manipulator->getTypeOperators($type, $this->getScope(), $source, $context) + $provider = $context->get(HandlerContextOperators::class)?->value; + $operators = $type && $provider + ? $provider->getOperators($manipulator, $type, $source, $context) : []; return $operators; @@ -313,7 +309,7 @@ protected function getFieldDefinition( // Operator? $operator = $this->getFieldOperator($manipulator, $field, $context); - if ($operator === null || !$operator->isAvailable($manipulator, $field, $context)) { + if (!$operator) { return null; } @@ -341,10 +337,11 @@ protected function getFieldOperator( $operator = $this->getFieldOperatorDirective($manipulator, $field, $context, $this->getFieldMarkerOperator()); if (!$operator) { - $type = $this->getTypeForFieldOperator(); + $type = $this->getTypeForFieldOperator(); + $provider = $context->get(HandlerContextOperators::class)?->value; - if ($type) { - $operators = $manipulator->getTypeOperators($type, $this->getScope(), $field, $context); + if ($type && $provider) { + $operators = $provider->getOperators($manipulator, $type, $field, $context); $operator = reset($operators) ?: null; } } @@ -370,13 +367,7 @@ protected function getFieldOperatorDirective( $nodes = [$field->getField(), $field->getTypeDefinition()]; foreach ($nodes as $node) { - $operator = $manipulator->getDirective( - $node, - $directive, - static function (Operator $operator) use ($manipulator, $field, $context): bool { - return $operator->isAvailable($manipulator, $field, $context); - }, - ); + $operator = $manipulator->getOperatorDirective($node, $directive, $field, $context); if ($operator) { break; diff --git a/packages/graphql/src/Builder/Types/InputObjectTest.php b/packages/graphql/src/Builder/Types/InputObjectTest.php index f76da5594..8b95274db 100644 --- a/packages/graphql/src/Builder/Types/InputObjectTest.php +++ b/packages/graphql/src/Builder/Types/InputObjectTest.php @@ -158,11 +158,6 @@ public static function definition(): string { * @noinspection PhpMultipleClassesDeclarationsInOneFile */ class InputObjectTest__InputObject extends InputObject { - #[Override] - protected function getScope(): string { - throw new Exception('Should not be called'); - } - #[Override] protected function getDescription( Manipulator $manipulator, diff --git a/packages/graphql/src/Provider.php b/packages/graphql/src/Provider.php index 7c261ced6..6c63d4961 100644 --- a/packages/graphql/src/Provider.php +++ b/packages/graphql/src/Provider.php @@ -8,13 +8,10 @@ use LastDragon_ru\LaraASP\Core\Provider\WithConfig; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\BuilderFieldResolver as BuilderFieldResolverContract; use LastDragon_ru\LaraASP\GraphQL\Builder\Defaults\BuilderFieldResolver; -use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator; use LastDragon_ru\LaraASP\GraphQL\Printer\DirectiveResolver; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Definitions\SearchByDirective; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators as SearchByOperators; use LastDragon_ru\LaraASP\GraphQL\SortBy\Contracts\SorterFactory as SorterFactoryContract; use LastDragon_ru\LaraASP\GraphQL\SortBy\Definitions\SortByDirective; -use LastDragon_ru\LaraASP\GraphQL\SortBy\Operators as SortByOperators; use LastDragon_ru\LaraASP\GraphQL\SortBy\SorterFactory; use LastDragon_ru\LaraASP\GraphQL\Stream\Contracts\StreamFactory as StreamFactoryContract; use LastDragon_ru\LaraASP\GraphQL\Stream\Definitions\StreamDirective; @@ -46,7 +43,6 @@ public function register(): void { parent::register(); $this->registerBindings(); - $this->registerOperators(); $this->registerSchemaPrinter(); } @@ -70,16 +66,6 @@ protected function registerBindings(): void { $this->app->scopedIf(BuilderFieldResolverContract::class, BuilderFieldResolver::class); } - protected function registerOperators(): void { - $this->callAfterResolving( - Manipulator::class, - static function (Manipulator $manipulator): void { - $manipulator->addOperators(new SearchByOperators()); - $manipulator->addOperators(new SortByOperators()); - }, - ); - } - protected function registerSchemaPrinter(): void { $this->app->bindIf(SettingsContract::class, DefaultSettings::class); $this->app->bindIf(DirectiveResolverContract::class, DirectiveResolver::class); diff --git a/packages/graphql/src/SearchBy/Definitions/SearchByExtendOperatorsDirective.php b/packages/graphql/src/SearchBy/Definitions/SearchByExtendOperatorsDirective.php new file mode 100644 index 000000000..307d87d0a --- /dev/null +++ b/packages/graphql/src/SearchBy/Definitions/SearchByExtendOperatorsDirective.php @@ -0,0 +1,11 @@ + - // ========================================================================= - #[Override] - public static function getScope(): string { - return Scope::class; - } - // - // // ========================================================================= #[Override] @@ -62,7 +55,8 @@ protected function getArgDefinitionType( ObjectFieldArgumentSource|InterfaceFieldArgumentSource $argument, Context $context, ): ListTypeNode|NamedTypeNode|NonNullTypeNode { - $type = $this->getArgumentTypeDefinitionNode($manipulator, $document, $argument, $context, Root::class); + $context = $context->override([HandlerContextOperators::class => new HandlerContextOperators(new Operators())]); + $type = $this->getArgumentTypeDefinitionNode($manipulator, $document, $argument, $context, Root::class); if (!$type) { throw new FailedToCreateSearchCondition($argument); diff --git a/packages/graphql/src/SearchBy/Directives/DirectiveTest/ScalarOperators.expected.graphql b/packages/graphql/src/SearchBy/Directives/DirectiveTest/ScalarOperators.expected.graphql index ac41db64c..f0549f6d3 100644 --- a/packages/graphql/src/SearchBy/Directives/DirectiveTest/ScalarOperators.expected.graphql +++ b/packages/graphql/src/SearchBy/Directives/DirectiveTest/ScalarOperators.expected.graphql @@ -5,6 +5,17 @@ directive @searchBy on | ARGUMENT_DEFINITION +""" +Extends the list of operators by the operators from the specified +`type` or from the config if `null`. +""" +directive @searchByExtendOperators( + type: String +) +on + | ENUM + | SCALAR + directive @searchByOperatorAllOf on | ENUM @@ -65,16 +76,6 @@ on | INPUT_FIELD_DEFINITION | SCALAR -""" -Extends the list of operators by the operators from the specified `type`. -""" -directive @searchByOperators( - type: String! -) -on - | ENUM - | SCALAR - enum EnumA @searchByOperatorIn { @@ -215,7 +216,7 @@ scalar Date @scalar( class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date" ) -@searchByOperators( +@searchByExtendOperators( type: "Boolean" ) @searchByOperatorLessThan diff --git a/packages/graphql/src/SearchBy/Directives/DirectiveTest/ScalarOperators.schema.graphql b/packages/graphql/src/SearchBy/Directives/DirectiveTest/ScalarOperators.schema.graphql index d9fcb93ae..ad15482ff 100644 --- a/packages/graphql/src/SearchBy/Directives/DirectiveTest/ScalarOperators.schema.graphql +++ b/packages/graphql/src/SearchBy/Directives/DirectiveTest/ScalarOperators.schema.graphql @@ -10,7 +10,7 @@ input A { scalar Date @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date") -@searchByOperators(type: "Boolean") +@searchByExtendOperators(type: "Boolean") @searchByOperatorLessThan @searchByOperatorLessThanOrEqual diff --git a/packages/graphql/src/SearchBy/Directives/DirectiveTest/Scout.expected.graphql b/packages/graphql/src/SearchBy/Directives/DirectiveTest/Scout.expected.graphql index 374023e01..05912fab8 100644 --- a/packages/graphql/src/SearchBy/Directives/DirectiveTest/Scout.expected.graphql +++ b/packages/graphql/src/SearchBy/Directives/DirectiveTest/Scout.expected.graphql @@ -5,6 +5,17 @@ directive @searchBy on | ARGUMENT_DEFINITION +""" +Extends the list of operators by the operators from the specified +`type` or from the config if `null`. +""" +directive @searchByExtendOperators( + type: String +) +on + | ENUM + | SCALAR + """ Marks that field/definition should be excluded from search. """ @@ -65,16 +76,6 @@ on | INPUT_FIELD_DEFINITION | SCALAR -""" -Extends the list of operators by the operators from the specified `type`. -""" -directive @searchByOperators( - type: String! -) -on - | ENUM - | SCALAR - enum EnumA { One Two @@ -1014,7 +1015,7 @@ scalar ScalarCustom @scalar( class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date" ) -@searchByOperators( +@searchByExtendOperators( type: "Boolean" ) @searchByOperatorLessThan diff --git a/packages/graphql/src/SearchBy/Directives/DirectiveTest/Scout.expected.v10.3.0.graphql b/packages/graphql/src/SearchBy/Directives/DirectiveTest/Scout.expected.v10.3.0.graphql index 466f9c562..883122ca3 100644 --- a/packages/graphql/src/SearchBy/Directives/DirectiveTest/Scout.expected.v10.3.0.graphql +++ b/packages/graphql/src/SearchBy/Directives/DirectiveTest/Scout.expected.v10.3.0.graphql @@ -5,6 +5,17 @@ directive @searchBy on | ARGUMENT_DEFINITION +""" +Extends the list of operators by the operators from the specified +`type` or from the config if `null`. +""" +directive @searchByExtendOperators( + type: String +) +on + | ENUM + | SCALAR + """ Marks that field/definition should be excluded from search. """ @@ -71,16 +82,6 @@ on | INPUT_FIELD_DEFINITION | SCALAR -""" -Extends the list of operators by the operators from the specified `type`. -""" -directive @searchByOperators( - type: String! -) -on - | ENUM - | SCALAR - enum EnumA { One Two @@ -1080,7 +1081,7 @@ scalar ScalarCustom @scalar( class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date" ) -@searchByOperators( +@searchByExtendOperators( type: "Boolean" ) @searchByOperatorLessThan diff --git a/packages/graphql/src/SearchBy/Directives/DirectiveTest/Scout.schema.graphql b/packages/graphql/src/SearchBy/Directives/DirectiveTest/Scout.schema.graphql index bd88c2cf7..d0d60b56c 100644 --- a/packages/graphql/src/SearchBy/Directives/DirectiveTest/Scout.schema.graphql +++ b/packages/graphql/src/SearchBy/Directives/DirectiveTest/Scout.schema.graphql @@ -98,7 +98,7 @@ scalar DateIgnored scalar ScalarCustom @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date") -@searchByOperators(type: "Boolean") +@searchByExtendOperators(type: "Boolean") @searchByOperatorLessThan @searchByOperatorLessThanOrEqual diff --git a/packages/graphql/src/SearchBy/Directives/DirectiveTest/V5CompatScout.expected.graphql b/packages/graphql/src/SearchBy/Directives/DirectiveTest/V5CompatScout.expected.graphql index 83724db8f..ce2aa4e4b 100644 --- a/packages/graphql/src/SearchBy/Directives/DirectiveTest/V5CompatScout.expected.graphql +++ b/packages/graphql/src/SearchBy/Directives/DirectiveTest/V5CompatScout.expected.graphql @@ -61,6 +61,8 @@ on """ Extends the list of operators by the operators from the specified `type`. + +The directive is deprecated! """ directive @searchByOperators( type: String! diff --git a/packages/graphql/src/SearchBy/Directives/ExtendOperators.php b/packages/graphql/src/SearchBy/Directives/ExtendOperators.php new file mode 100644 index 000000000..32ef46b8f --- /dev/null +++ b/packages/graphql/src/SearchBy/Directives/ExtendOperators.php @@ -0,0 +1,10 @@ +isNullable() && ($source->isScalar() || $source->isEnum())); + } + #[Override] public function getFieldDescription(): ?string { return 'Is NOT NULL?'; diff --git a/packages/graphql/src/SearchBy/Operators/Comparison/IsNull.php b/packages/graphql/src/SearchBy/Operators/Comparison/IsNull.php index 383da73ea..02d985e97 100644 --- a/packages/graphql/src/SearchBy/Operators/Comparison/IsNull.php +++ b/packages/graphql/src/SearchBy/Operators/Comparison/IsNull.php @@ -24,6 +24,12 @@ public static function getName(): string { return 'isNull'; } + #[Override] + public function isAvailable(TypeProvider $provider, TypeSource $source, Context $context): bool { + return parent::isAvailable($provider, $source, $context) + && ($source->isNullable() && ($source->isScalar() || $source->isEnum())); + } + #[Override] public function getFieldDescription(): ?string { return 'Is NULL?'; diff --git a/packages/graphql/src/SearchBy/OperatorsTest.php b/packages/graphql/src/SearchBy/OperatorsTest.php index 957958595..b1446287e 100644 --- a/packages/graphql/src/SearchBy/OperatorsTest.php +++ b/packages/graphql/src/SearchBy/OperatorsTest.php @@ -2,11 +2,21 @@ namespace LastDragon_ru\LaraASP\GraphQL\SearchBy; +use Exception; use Illuminate\Container\Container; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Handler; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeProvider; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource; +use LastDragon_ru\LaraASP\GraphQL\Builder\Field; +use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator; use LastDragon_ru\LaraASP\GraphQL\Package; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Definitions\SearchByOperatorEqualDirective; -use LastDragon_ru\LaraASP\GraphQL\SearchBy\Definitions\SearchByOperatorNotEqualDirective; +use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Operator; use LastDragon_ru\LaraASP\GraphQL\Testing\Package\TestCase; +use Mockery; +use Nuwave\Lighthouse\Execution\Arguments\Argument; +use Nuwave\Lighthouse\Schema\AST\DocumentAST; +use Override; use PHPUnit\Framework\Attributes\CoversClass; use function config; @@ -22,25 +32,28 @@ public function testConstructor(): void { config([ Package::Name.'.search_by.operators' => [ Operators::ID => [ - SearchByOperatorEqualDirective::class, + OperatorsTest__Operator::class, ], Operators::Int => [ - SearchByOperatorNotEqualDirective::class, + OperatorsTest__Operator::class, ], ], ]); - $operators = Container::getInstance()->make(Operators::class); + $source = Mockery::mock(TypeSource::class); + $context = Mockery::mock(Context::class); + $operators = Container::getInstance()->make(Operators::class); + $manipulator = Container::getInstance()->make(Manipulator::class, [ + 'document' => Mockery::mock(DocumentAST::class), + ]); - self::assertTrue($operators->hasOperators(Operators::ID)); - self::assertTrue($operators->hasOperators(Operators::Int)); - self::assertFalse($operators->hasOperators('unknown')); + self::assertEquals([], $operators->getOperators($manipulator, 'unknown', $source, $context)); self::assertEquals( [ - SearchByOperatorEqualDirective::class, + OperatorsTest__Operator::class, ], $this->toClassNames( - $operators->getOperators(Operators::ID), + $operators->getOperators($manipulator, Operators::ID, $source, $context), ), ); } @@ -64,3 +77,43 @@ protected function toClassNames(array $objects): array { } // } + +// @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses +// @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class OperatorsTest__Operator extends Operator { + #[Override] + public static function getName(): string { + throw new Exception('Should not be called'); + } + + #[Override] + public function isAvailable(TypeProvider $provider, TypeSource $source, Context $context): bool { + return true; + } + + #[Override] + public function getFieldType(TypeProvider $provider, TypeSource $source, Context $context): ?string { + throw new Exception('Should not be called'); + } + + #[Override] + public function getFieldDescription(): ?string { + throw new Exception('Should not be called'); + } + + #[Override] + public function call( + Handler $handler, + object $builder, + Field $field, + Argument $argument, + Context $context, + ): object { + throw new Exception('Should not be called'); + } +} diff --git a/packages/graphql/src/SearchBy/Types/Condition/Type.php b/packages/graphql/src/SearchBy/Types/Condition/Type.php index ba8d7372f..3392c63ca 100644 --- a/packages/graphql/src/SearchBy/Types/Condition/Type.php +++ b/packages/graphql/src/SearchBy/Types/Condition/Type.php @@ -3,6 +3,7 @@ namespace LastDragon_ru\LaraASP\GraphQL\SearchBy\Types\Condition; use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextBuilderInfo; +use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextOperators; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator as OperatorContract; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource; @@ -34,11 +35,6 @@ public function getTypeName(TypeSource $source, Context $context): string { return "{$directiveName}{$builderName}{$name}{$typeName}"; } - #[Override] - protected function getScope(): string { - return Directive::getScope(); - } - #[Override] protected function getDescription( Manipulator $manipulator, @@ -74,13 +70,11 @@ protected function getFieldOperator( InputFieldSource|ObjectFieldSource|InterfaceFieldSource $field, Context $context, ): ?OperatorContract { + $provider = $context->get(HandlerContextOperators::class)?->value; + $operator = SearchByOperatorConditionDirective::class; + return match (true) { - $field->isScalar(), $field->isEnum() => $manipulator->getOperator( - SearchByOperatorConditionDirective::class, - $this->getScope(), - $field, - $context, - ), + $field->isScalar(), $field->isEnum() => $provider?->getOperator($manipulator, $operator, $field, $context), $field->isObject() => parent::getFieldOperator($manipulator, $field, $context), default => throw new NotImplemented($field), }; diff --git a/packages/graphql/src/SearchBy/Types/Enumeration.php b/packages/graphql/src/SearchBy/Types/Enumeration.php index 177417ca4..0be834119 100644 --- a/packages/graphql/src/SearchBy/Types/Enumeration.php +++ b/packages/graphql/src/SearchBy/Types/Enumeration.php @@ -6,6 +6,7 @@ use GraphQL\Language\Parser; use GraphQL\Type\Definition\Type; use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextBuilderInfo; +use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextOperators; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeDefinition; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource; @@ -14,6 +15,8 @@ use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators; use Override; +use function array_merge; + class Enumeration implements TypeDefinition { public function __construct() { // empty @@ -44,17 +47,30 @@ public function getTypeDefinition( return null; } + // Operators? + $provider = $context->get(HandlerContextOperators::class)?->value; + + if (!$provider) { + return null; + } + // Operators $type = $manipulator->getTypeSource($source->getType()); - $scope = Directive::getScope(); - $extras = $type->isNullable() ? [Operators::Null] : []; - $operators = $manipulator->getTypeOperators($type->getTypeName(), $scope, $type, $context, ...$extras) - ?: $manipulator->getTypeOperators(Operators::Enum, $scope, $type, $context, ...$extras); + $operators = $provider->getOperators($manipulator, $type->getTypeName(), $type, $context) + ?: $provider->getOperators($manipulator, Operators::Enum, $type, $context); if (!$operators) { return null; } + // Nullable? + if ($type->isNullable()) { + $operators = array_merge( + $operators, + $provider->getOperators($manipulator, Operators::Null, $type, $context), + ); + } + // Definition $content = $manipulator->getOperatorsFields($operators, $type, $context); $definition = Parser::inputObjectTypeDefinition( diff --git a/packages/graphql/src/SearchBy/Types/Scalar.php b/packages/graphql/src/SearchBy/Types/Scalar.php index 3620172b9..05740ebd9 100644 --- a/packages/graphql/src/SearchBy/Types/Scalar.php +++ b/packages/graphql/src/SearchBy/Types/Scalar.php @@ -6,6 +6,7 @@ use GraphQL\Language\Parser; use GraphQL\Type\Definition\Type; use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextBuilderInfo; +use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextOperators; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeDefinition; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource; @@ -14,6 +15,8 @@ use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators; use Override; +use function array_merge; + class Scalar implements TypeDefinition { public function __construct() { // empty @@ -44,16 +47,29 @@ public function getTypeDefinition( return null; } + // Operators? + $provider = $context->get(HandlerContextOperators::class)?->value; + + if (!$provider) { + return null; + } + // Operators $type = $manipulator->getTypeSource($source->getType()); - $scope = Directive::getScope(); - $extras = $type->isNullable() ? [Operators::Null] : []; - $operators = $manipulator->getTypeOperators($type->getTypeName(), $scope, $type, $context, ...$extras); + $operators = $provider->getOperators($manipulator, $type->getTypeName(), $type, $context); if (!$operators) { return null; } + // Nullable? + if ($type->isNullable()) { + $operators = array_merge( + $operators, + $provider->getOperators($manipulator, Operators::Null, $type, $context), + ); + } + // Definition $content = $manipulator->getOperatorsFields($operators, $type, $context); $definition = Parser::inputObjectTypeDefinition( diff --git a/packages/graphql/src/SortBy/Directives/Directive.php b/packages/graphql/src/SortBy/Directives/Directive.php index dba921d79..4d5c2c9e5 100644 --- a/packages/graphql/src/SortBy/Directives/Directive.php +++ b/packages/graphql/src/SortBy/Directives/Directive.php @@ -5,13 +5,14 @@ use GraphQL\Language\AST\ListTypeNode; use GraphQL\Language\AST\NamedTypeNode; use GraphQL\Language\AST\NonNullTypeNode; +use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextOperators; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; use LastDragon_ru\LaraASP\GraphQL\Builder\Directives\HandlerDirective; use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator; use LastDragon_ru\LaraASP\GraphQL\Builder\Sources\InterfaceFieldArgumentSource; use LastDragon_ru\LaraASP\GraphQL\Builder\Sources\ObjectFieldArgumentSource; -use LastDragon_ru\LaraASP\GraphQL\SortBy\Contracts\Scope; use LastDragon_ru\LaraASP\GraphQL\SortBy\Exceptions\FailedToCreateSortClause; +use LastDragon_ru\LaraASP\GraphQL\SortBy\Operators; use LastDragon_ru\LaraASP\GraphQL\SortBy\Operators\Root; use Nuwave\Lighthouse\Schema\AST\DocumentAST; use Nuwave\Lighthouse\Schema\DirectiveLocator; @@ -37,14 +38,6 @@ public static function definition(): string { GRAPHQL; } - // - // ========================================================================= - #[Override] - public static function getScope(): string { - return Scope::class; - } - // - // // ========================================================================= #[Override] @@ -59,7 +52,8 @@ protected function getArgDefinitionType( ObjectFieldArgumentSource|InterfaceFieldArgumentSource $argument, Context $context, ): ListTypeNode|NamedTypeNode|NonNullTypeNode { - $type = $this->getArgumentTypeDefinitionNode($manipulator, $document, $argument, $context, Root::class); + $context = $context->override([HandlerContextOperators::class => new HandlerContextOperators(new Operators())]); + $type = $this->getArgumentTypeDefinitionNode($manipulator, $document, $argument, $context, Root::class); if (!$type) { throw new FailedToCreateSortClause($argument); diff --git a/packages/graphql/src/SortBy/Operators.php b/packages/graphql/src/SortBy/Operators.php index 285fbc416..2ec52c70c 100644 --- a/packages/graphql/src/SortBy/Operators.php +++ b/packages/graphql/src/SortBy/Operators.php @@ -5,6 +5,7 @@ use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator as BuilderOperator; use LastDragon_ru\LaraASP\GraphQL\Builder\Operators as BuilderOperators; use LastDragon_ru\LaraASP\GraphQL\Package; +use LastDragon_ru\LaraASP\GraphQL\SortBy\Contracts\Scope; use LastDragon_ru\LaraASP\GraphQL\SortBy\Definitions\SortByOperatorChildDirective; use LastDragon_ru\LaraASP\GraphQL\SortBy\Definitions\SortByOperatorFieldDirective; use LastDragon_ru\LaraASP\GraphQL\SortBy\Definitions\SortByOperatorNullsFirstDirective; @@ -41,6 +42,6 @@ public function __construct() { #[Override] public function getScope(): string { - return Directive::getScope(); + return Scope::class; } } diff --git a/packages/graphql/src/SortBy/OperatorsTest.php b/packages/graphql/src/SortBy/OperatorsTest.php index eca4a7d20..7c4dc44d5 100644 --- a/packages/graphql/src/SortBy/OperatorsTest.php +++ b/packages/graphql/src/SortBy/OperatorsTest.php @@ -2,10 +2,21 @@ namespace LastDragon_ru\LaraASP\GraphQL\SortBy; +use Exception; use Illuminate\Container\Container; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Handler; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeProvider; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource; +use LastDragon_ru\LaraASP\GraphQL\Builder\Field; +use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator; use LastDragon_ru\LaraASP\GraphQL\Package; -use LastDragon_ru\LaraASP\GraphQL\SortBy\Definitions\SortByOperatorRandomDirective; +use LastDragon_ru\LaraASP\GraphQL\SortBy\Operators\Operator; use LastDragon_ru\LaraASP\GraphQL\Testing\Package\TestCase; +use Mockery; +use Nuwave\Lighthouse\Execution\Arguments\Argument; +use Nuwave\Lighthouse\Schema\AST\DocumentAST; +use Override; use PHPUnit\Framework\Attributes\CoversClass; use function config; @@ -21,21 +32,25 @@ public function testConstructor(): void { config([ Package::Name.'.sort_by.operators' => [ Operators::Extra => [ - SortByOperatorRandomDirective::class, + OperatorsTest__Operator::class, ], ], ]); - $operators = Container::getInstance()->make(Operators::class); + $source = Mockery::mock(TypeSource::class); + $context = Mockery::mock(Context::class); + $operators = Container::getInstance()->make(Operators::class); + $manipulator = Container::getInstance()->make(Manipulator::class, [ + 'document' => Mockery::mock(DocumentAST::class), + ]); - self::assertTrue($operators->hasOperators(Operators::Extra)); - self::assertFalse($operators->hasOperators('unknown')); + self::assertEquals([], $operators->getOperators($manipulator, 'unknown', $source, $context)); self::assertEquals( [ - SortByOperatorRandomDirective::class, + OperatorsTest__Operator::class, ], $this->toClassNames( - $operators->getOperators(Operators::Extra), + $operators->getOperators($manipulator, Operators::Extra, $source, $context), ), ); } @@ -59,3 +74,43 @@ protected function toClassNames(array $objects): array { } // } + +// @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses +// @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class OperatorsTest__Operator extends Operator { + #[Override] + public static function getName(): string { + throw new Exception('Should not be called'); + } + + #[Override] + public function isAvailable(TypeProvider $provider, TypeSource $source, Context $context): bool { + return true; + } + + #[Override] + public function getFieldType(TypeProvider $provider, TypeSource $source, Context $context): ?string { + throw new Exception('Should not be called'); + } + + #[Override] + public function getFieldDescription(): ?string { + throw new Exception('Should not be called'); + } + + #[Override] + public function call( + Handler $handler, + object $builder, + Field $field, + Argument $argument, + Context $context, + ): object { + throw new Exception('Should not be called'); + } +} diff --git a/packages/graphql/src/SortBy/Types/Clause/Type.php b/packages/graphql/src/SortBy/Types/Clause/Type.php index c7e5a4a23..a105f7038 100644 --- a/packages/graphql/src/SortBy/Types/Clause/Type.php +++ b/packages/graphql/src/SortBy/Types/Clause/Type.php @@ -3,6 +3,7 @@ namespace LastDragon_ru\LaraASP\GraphQL\SortBy\Types\Clause; use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextBuilderInfo; +use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextOperators; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator as OperatorContract; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource; @@ -34,11 +35,6 @@ public function getTypeName(TypeSource $source, Context $context): string { return "{$directiveName}{$builderName}{$name}{$typeName}"; } - #[Override] - protected function getScope(): string { - return Directive::getScope(); - } - #[Override] protected function getDescription( Manipulator $manipulator, @@ -94,13 +90,11 @@ protected function getFieldOperator( InputFieldSource|ObjectFieldSource|InterfaceFieldSource $field, Context $context, ): ?OperatorContract { + $provider = $context->get(HandlerContextOperators::class)?->value; + $operator = SortByOperatorSortDirective::class; + return match (true) { - $field->isScalar(), $field->isEnum() => $manipulator->getOperator( - SortByOperatorSortDirective::class, - $this->getScope(), - $field, - $context, - ), + $field->isScalar(), $field->isEnum() => $provider?->getOperator($manipulator, $operator, $field, $context), $field->isObject() => parent::getFieldOperator($manipulator, $field, $context), default => throw new NotImplemented($field), }; diff --git a/packages/testing/src/Comparators/DatabaseQueryComparatorTest.php b/packages/testing/src/Comparators/DatabaseQueryComparatorTest.php index 2af6a60ae..985d43b2a 100644 --- a/packages/testing/src/Comparators/DatabaseQueryComparatorTest.php +++ b/packages/testing/src/Comparators/DatabaseQueryComparatorTest.php @@ -55,7 +55,7 @@ public function normalize(Query $query): Query { INNER JOIN b laravel_reserved_10 ON laravel_reserved_10.b = laravel_reserved_2.c INNER JOIN c laravel_reserved_2 ON laravel_reserved_2.c = laravel_reserved_1.a WHERE laravel_reserved_1.a IS NOT NULL - and laravel_reserved_10.b IS NULL + AND laravel_reserved_10.b IS NULL AND laravel_reserved_2.c > 10 SQL, ); @@ -70,7 +70,7 @@ public function normalize(Query $query): Query { INNER JOIN c laravel_reserved_1 ON laravel_reserved_1.c = laravel_reserved_0.a WHERE laravel_reserved_0.a IS NOT NULL - and laravel_reserved_2.b IS NULL + AND laravel_reserved_2.b IS NULL AND laravel_reserved_1.c > 10 SQL;