diff --git a/packages/graphql/README.md b/packages/graphql/README.md index 2ad19a5e2..dababb012 100644 --- a/packages/graphql/README.md +++ b/packages/graphql/README.md @@ -75,6 +75,12 @@ Unlike the `@paginate` (and similar) directive, the `@stream` provides a uniform [Read more](). +## `@type` + +Converts scalar into GraphQL Type. Similar to Lighthouse's `@scalar` directive, but uses Laravel Container to resolve instance and also supports PHP enums. + +[Read more](). + [//]: # (end: 73f7f4a1d86b7731354837c827f1b9f9aa729879639aeab4fe63985913469f48) # Scalars diff --git a/packages/graphql/docs/Directives/@type.md b/packages/graphql/docs/Directives/@type.md new file mode 100644 index 000000000..677d0629b --- /dev/null +++ b/packages/graphql/docs/Directives/@type.md @@ -0,0 +1,35 @@ +# `@type` + +Converts scalar into GraphQL Type. Similar to Lighthouse's `@scalar` directive, but uses Laravel Container to resolve instance and also supports PHP enums. + +[include:exec]: <../../../../dev/artisan dev:directive @type> +[//]: # (start: 90e03adede767b669ea39c2e4de42431b0ee3405074a4297b01666628f98ca45) +[//]: # (warning: Generated automatically. Do not edit.) + +```graphql +""" +Converts scalar into GraphQL Type. Similar to Lighthouse's `@scalar` +directive, but uses Laravel Container to resolve instance and also +supports PHP enums. +""" +directive @type( + """ + Reference to a PHP Class/Enum (FQN). + + If not PHP Enum, the Laravel Container with the following additional + arguments will be used to resolver the instance: + + * `string $name` - the type name. + * `GraphQL\Language\AST\ScalarTypeDefinitionNode $node` - the AST node. + * `array&ScalarConfig $config` - the scalar configuration (if `GraphQL\Type\Definition\ScalarType`). + + Resolved instance must be an `GraphQL\Type\Definition\Type&GraphQL\Type\Definition\NamedType` and have a name equal + to `$name` argument. + """ + class: String! +) +on + | SCALAR +``` + +[//]: # (end: 90e03adede767b669ea39c2e4de42431b0ee3405074a4297b01666628f98ca45) diff --git a/packages/graphql/src/Builder/Contracts/TypeDefinition.php b/packages/graphql/src/Builder/Contracts/TypeDefinition.php index 48bd2b073..52ce8f868 100644 --- a/packages/graphql/src/Builder/Contracts/TypeDefinition.php +++ b/packages/graphql/src/Builder/Contracts/TypeDefinition.php @@ -7,6 +7,7 @@ use GraphQL\Type\Definition\NamedType; use GraphQL\Type\Definition\Type; use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator; +use LastDragon_ru\LaraASP\GraphQL\Utils\TypeReference; interface TypeDefinition { /** @@ -17,7 +18,10 @@ public function getTypeName(TypeSource $source, Context $context): string; /** * Returns the type definition for given Source if possible. The name must be equal to `$name`. * - * @return (TypeDefinitionNode&Node)|(Type&NamedType)|null + * @see TypeReference + * + * @return (TypeDefinitionNode&Node)|(Type&NamedType)|null Returning {@see Type} is deprecated, please use + * {@see TypeReference} instead. */ public function getTypeDefinition( Manipulator $manipulator, diff --git a/packages/graphql/src/Builder/Manipulator.php b/packages/graphql/src/Builder/Manipulator.php index 40ba1da75..e8fc0836e 100644 --- a/packages/graphql/src/Builder/Manipulator.php +++ b/packages/graphql/src/Builder/Manipulator.php @@ -23,6 +23,7 @@ use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextOperators; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeDefinition; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeProvider; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource; use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\FakeTypeDefinitionIsNotFake; @@ -34,7 +35,9 @@ use LastDragon_ru\LaraASP\GraphQL\Builder\Sources\InterfaceSource; use LastDragon_ru\LaraASP\GraphQL\Builder\Sources\ObjectSource; use LastDragon_ru\LaraASP\GraphQL\Builder\Sources\Source; +use LastDragon_ru\LaraASP\GraphQL\Package; use LastDragon_ru\LaraASP\GraphQL\Utils\AstManipulator; +use LastDragon_ru\LaraASP\GraphQL\Utils\TypeReference; use Nuwave\Lighthouse\Schema\DirectiveLocator; use Nuwave\Lighthouse\Support\Contracts\Directive; use Override; @@ -43,6 +46,7 @@ use function array_unshift; use function count; use function implode; +use function trigger_deprecation; class Manipulator extends AstManipulator implements TypeProvider { // @@ -63,6 +67,17 @@ public function getType(string $definition, TypeSource $source, Context $context // Create new $node = $instance->getTypeDefinition($this, $source, $context, $name); + if ($node instanceof Type && !($node instanceof TypeReference)) { + trigger_deprecation( + Package::Name, + '%{VERSION}', + 'Returning `%s` from `%s` is deprecated, please use `%s` instead.', + Type::class, + TypeDefinition::class.'::getTypeDefinition()', + TypeReference::class, + ); + } + if (!$node) { throw new TypeDefinitionImpossibleToCreateType($definition, $source, $context); } diff --git a/packages/graphql/src/Utils/Definitions/LaraAspAsEnumDirective.php b/packages/graphql/src/Directives/Definitions/TypeDirective.php similarity index 57% rename from packages/graphql/src/Utils/Definitions/LaraAspAsEnumDirective.php rename to packages/graphql/src/Directives/Definitions/TypeDirective.php index 0b3713582..513f77a0c 100644 --- a/packages/graphql/src/Utils/Definitions/LaraAspAsEnumDirective.php +++ b/packages/graphql/src/Directives/Definitions/TypeDirective.php @@ -1,13 +1,10 @@ directiveArgValue(self::ArgClass)); + $node = Cast::to(ScalarTypeDefinitionNode::class, $value->getTypeDefinition()); + $name = $value->getTypeDefinitionName(); + $type = match (true) { + is_a($class, GraphQLType::class, true) && is_a($class, NamedType::class, true) + => $this->createType($name, $class, $node), + is_a($class, UnitEnum::class, true) + => $this->createEnum($name, $class, $node), + default + => null, + }; + + if (!($type instanceof GraphQLType) || !($type instanceof NamedType)) { + throw new DefinitionException( + "The `{$class}` is not a GraphQL type (`scalar {$name}`).", + ); + } elseif ($type->name() !== $name) { + throw new DefinitionException( + "The type name must be `{$name}`, `{$type->name()}` given (`scalar {$name}`).", + ); + } else { + // ok + } + + // Return + return $type; + } + + /** + * @param class-string $class + */ + private function createEnum(string $name, string $class, ScalarTypeDefinitionNode $node): EnumType { + return new PhpEnumType($class, $name); + } + + /** + * @param class-string $class + */ + private function createType(string $name, string $class, ScalarTypeDefinitionNode $node): object { + $args = [ + 'name' => $name, + 'node' => $node, + ]; + + if (is_a($class, ScalarType::class, true)) { + $args['config'] = $this->createTypeScalarConfig($name, $class, $node); + } + + return $this->container->getInstance()->make($class, $args); + } + + /** + * @return ScalarConfig + */ + private function createTypeScalarConfig(string $name, string $class, ScalarTypeDefinitionNode $node): array { + return [ + 'name' => $name, + 'astNode' => $node, + 'description' => $node->description?->value, + ]; + } +} diff --git a/packages/graphql/src/Directives/TypeTest.php b/packages/graphql/src/Directives/TypeTest.php new file mode 100644 index 000000000..e70c3d9f8 --- /dev/null +++ b/packages/graphql/src/Directives/TypeTest.php @@ -0,0 +1,171 @@ +useGraphQLSchema( + <<make(TypeRegistry::class); + $type = $registry->get($name); + + self::assertInstanceOf(PhpEnumType::class, $type); + self::assertEquals($name, $type->name()); + self::assertEquals($class, PhpEnumTypeHelper::getEnumClass($type)); + } + + public function testResolveNodeType(): void { + $class = TypeTest_Type::class; + $attr = json_encode($class, JSON_THROW_ON_ERROR); + $name = 'TestType'; + + $this->useGraphQLSchema( + <<make(TypeRegistry::class); + $type = $registry->get($name); + + self::assertInstanceOf($class, $type); + self::assertEquals($name, $type->name()); + self::assertEquals($class, $type::class); + } + + public function testResolveNodeScalar(): void { + $class = TypeTest_Scalar::class; + $attr = json_encode($class, JSON_THROW_ON_ERROR); + $name = 'TestScalar'; + $desc = 'Description.'; + + $this->useGraphQLSchema( + <<make(TypeRegistry::class); + $type = $registry->get($name); + + self::assertInstanceOf($class, $type); + self::assertEquals($name, $type->name()); + self::assertEquals($desc, $type->description()); + self::assertEquals($class, $type::class); + } + + public function testResolveNodeInvalidName(): void { + $class = TypeTest_ScalarInvalidName::class; + $attr = json_encode($class, JSON_THROW_ON_ERROR); + $name = 'TestScalar'; + + $this->useGraphQLSchema( + <<make(TypeRegistry::class); + $registry->get($name); + } + + public function testResolveNodeNotGraphQLType(): void { + $class = stdClass::class; + $attr = json_encode($class, JSON_THROW_ON_ERROR); + $name = 'TestType'; + + $this->useGraphQLSchema( + <<make(TypeRegistry::class); + $registry->get($name); + } +} + +// @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses +// @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +enum TypeTest_Enum { + case A; +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class TypeTest_Type extends StringType { + public function __construct(string $name, ScalarTypeDefinitionNode $node) { + parent::__construct([ + 'name' => $name, + 'astNode' => $node, + ]); + } +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class TypeTest_Scalar extends StringType { + // empty +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class TypeTest_ScalarInvalidName extends StringType { + public function __construct(string $name, ScalarTypeDefinitionNode $node) { + parent::__construct([ + 'name' => "{$name}-changed", + 'astNode' => $node, + ]); + } +} diff --git a/packages/graphql/src/Provider.php b/packages/graphql/src/Provider.php index 76735ec19..676ae72ae 100644 --- a/packages/graphql/src/Provider.php +++ b/packages/graphql/src/Provider.php @@ -8,6 +8,7 @@ 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\Directives\Definitions\TypeDirective; use LastDragon_ru\LaraASP\GraphQL\Printer\DirectiveResolver; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Definitions\SearchByDirective; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Definitions\SearchBySchemaDirective; @@ -18,7 +19,6 @@ use LastDragon_ru\LaraASP\GraphQL\Stream\Contracts\StreamFactory as StreamFactoryContract; use LastDragon_ru\LaraASP\GraphQL\Stream\Definitions\StreamDirective; use LastDragon_ru\LaraASP\GraphQL\Stream\StreamFactory; -use LastDragon_ru\LaraASP\GraphQL\Utils\Definitions\LaraAspAsEnumDirective; use LastDragon_ru\LaraASP\GraphQLPrinter\Contracts\DirectiveResolver as DirectiveResolverContract; use LastDragon_ru\LaraASP\GraphQLPrinter\Contracts\Printer as SchemaPrinterContract; use LastDragon_ru\LaraASP\GraphQLPrinter\Contracts\Settings as SettingsContract; @@ -57,7 +57,7 @@ static function (): array { implode('\\', array_slice(explode('\\', SearchByDirective::class), 0, -1)), implode('\\', array_slice(explode('\\', SortByDirective::class), 0, -1)), implode('\\', array_slice(explode('\\', StreamDirective::class), 0, -1)), - implode('\\', array_slice(explode('\\', LaraAspAsEnumDirective::class), 0, -1)), + implode('\\', array_slice(explode('\\', TypeDirective::class), 0, -1)), ]; }, ); diff --git a/packages/graphql/src/Scalars/JsonStringType.php b/packages/graphql/src/Scalars/JsonStringType.php index f354e26f5..b8e28d642 100644 --- a/packages/graphql/src/Scalars/JsonStringType.php +++ b/packages/graphql/src/Scalars/JsonStringType.php @@ -16,6 +16,7 @@ use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeDefinition; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource; use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator; +use LastDragon_ru\LaraASP\GraphQL\Utils\TypeReference; use Override; use function is_string; @@ -97,7 +98,7 @@ public function getTypeDefinition( Context $context, string $name, ): TypeDefinitionNode|Type|null { - return $this; + return new TypeReference($name, self::class); } // } diff --git a/packages/graphql/src/SearchBy/Directives/DirectiveTest/Implicit.expected.graphql b/packages/graphql/src/SearchBy/Directives/DirectiveTest/Implicit.expected.graphql index b4c9ef1de..976e80d80 100644 --- a/packages/graphql/src/SearchBy/Directives/DirectiveTest/Implicit.expected.graphql +++ b/packages/graphql/src/SearchBy/Directives/DirectiveTest/Implicit.expected.graphql @@ -233,6 +233,30 @@ directive @streamOffset on | ARGUMENT_DEFINITION +""" +Converts scalar into GraphQL Type. Similar to Lighthouse's `@scalar` +directive, but uses Laravel Container to resolve instance and also +supports PHP enums. +""" +directive @type( + """ + Reference to a PHP Class/Enum (FQN). + + If not PHP Enum, the Laravel Container with the following additional + arguments will be used to resolver the instance: + + * `string $name` - the type name. + * `GraphQL\Language\AST\ScalarTypeDefinitionNode $node` - the AST node. + * `array&ScalarConfig $config` - the scalar configuration (if `GraphQL\Type\Definition\ScalarType`). + + Resolved instance must be an `GraphQL\Type\Definition\Type&GraphQL\Type\Definition\NamedType` and have a name equal + to `$name` argument. + """ + class: String! +) +on + | SCALAR + """ Options for the `type` argument of `@hasMany`. """ @@ -1050,7 +1074,7 @@ navigation only to the previous/current/next pages (= cursor pagination). """ scalar StreamOffset -@scalar( +@type( class: "LastDragon_ru\\LaraASP\\GraphQL\\Stream\\Types\\Offset" ) diff --git a/packages/graphql/src/SearchBy/Types/Flag.php b/packages/graphql/src/SearchBy/Types/Flag.php index 61d1f75c3..f8860a113 100644 --- a/packages/graphql/src/SearchBy/Types/Flag.php +++ b/packages/graphql/src/SearchBy/Types/Flag.php @@ -3,7 +3,6 @@ namespace LastDragon_ru\LaraASP\GraphQL\SearchBy\Types; use GraphQL\Language\AST\TypeDefinitionNode; -use GraphQL\Type\Definition\PhpEnumType; use GraphQL\Type\Definition\Type; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeDefinition; @@ -11,6 +10,7 @@ use LastDragon_ru\LaraASP\GraphQL\Builder\Enums\Flag as FlagEnum; use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Directives\Directive; +use LastDragon_ru\LaraASP\GraphQL\Utils\TypeReference; use Override; class Flag implements TypeDefinition { @@ -30,6 +30,6 @@ public function getTypeDefinition( Context $context, string $name, ): TypeDefinitionNode|Type|null { - return new PhpEnumType(FlagEnum::class, $name); + return new TypeReference($name, FlagEnum::class); } } diff --git a/packages/graphql/src/SortBy/Directives/DirectiveTest/Implicit.expected.graphql b/packages/graphql/src/SortBy/Directives/DirectiveTest/Implicit.expected.graphql index 54bd613f3..19386dcfe 100644 --- a/packages/graphql/src/SortBy/Directives/DirectiveTest/Implicit.expected.graphql +++ b/packages/graphql/src/SortBy/Directives/DirectiveTest/Implicit.expected.graphql @@ -83,6 +83,30 @@ directive @streamOffset on | ARGUMENT_DEFINITION +""" +Converts scalar into GraphQL Type. Similar to Lighthouse's `@scalar` +directive, but uses Laravel Container to resolve instance and also +supports PHP enums. +""" +directive @type( + """ + Reference to a PHP Class/Enum (FQN). + + If not PHP Enum, the Laravel Container with the following additional + arguments will be used to resolver the instance: + + * `string $name` - the type name. + * `GraphQL\Language\AST\ScalarTypeDefinitionNode $node` - the AST node. + * `array&ScalarConfig $config` - the scalar configuration (if `GraphQL\Type\Definition\ScalarType`). + + Resolved instance must be an `GraphQL\Type\Definition\Type&GraphQL\Type\Definition\NamedType` and have a name equal + to `$name` argument. + """ + class: String! +) +on + | SCALAR + """ Options for the `type` argument of `@hasMany`. """ @@ -498,7 +522,7 @@ navigation only to the previous/current/next pages (= cursor pagination). """ scalar StreamOffset -@scalar( +@type( class: "LastDragon_ru\\LaraASP\\GraphQL\\Stream\\Types\\Offset" ) diff --git a/packages/graphql/src/SortBy/Types/Direction.php b/packages/graphql/src/SortBy/Types/Direction.php index ba621e127..49b49f528 100644 --- a/packages/graphql/src/SortBy/Types/Direction.php +++ b/packages/graphql/src/SortBy/Types/Direction.php @@ -3,7 +3,6 @@ namespace LastDragon_ru\LaraASP\GraphQL\SortBy\Types; use GraphQL\Language\AST\TypeDefinitionNode; -use GraphQL\Type\Definition\PhpEnumType; use GraphQL\Type\Definition\Type; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeDefinition; @@ -11,6 +10,7 @@ use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator; use LastDragon_ru\LaraASP\GraphQL\SortBy\Directives\Directive; use LastDragon_ru\LaraASP\GraphQL\SortBy\Enums\Direction as DirectionEnum; +use LastDragon_ru\LaraASP\GraphQL\Utils\TypeReference; use Override; class Direction implements TypeDefinition { @@ -30,6 +30,6 @@ public function getTypeDefinition( Context $context, string $name, ): TypeDefinitionNode|Type|null { - return new PhpEnumType(DirectionEnum::class, $name); + return new TypeReference($name, DirectionEnum::class); } } diff --git a/packages/graphql/src/SortBy/Types/Flag.php b/packages/graphql/src/SortBy/Types/Flag.php index 93bd2f4a3..cb8473b0f 100644 --- a/packages/graphql/src/SortBy/Types/Flag.php +++ b/packages/graphql/src/SortBy/Types/Flag.php @@ -3,7 +3,6 @@ namespace LastDragon_ru\LaraASP\GraphQL\SortBy\Types; use GraphQL\Language\AST\TypeDefinitionNode; -use GraphQL\Type\Definition\PhpEnumType; use GraphQL\Type\Definition\Type; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeDefinition; @@ -11,6 +10,7 @@ use LastDragon_ru\LaraASP\GraphQL\Builder\Enums\Flag as FlagEnum; use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator; use LastDragon_ru\LaraASP\GraphQL\SortBy\Directives\Directive; +use LastDragon_ru\LaraASP\GraphQL\Utils\TypeReference; use Override; class Flag implements TypeDefinition { @@ -30,6 +30,6 @@ public function getTypeDefinition( Context $context, string $name, ): TypeDefinitionNode|Type|null { - return new PhpEnumType(FlagEnum::class, $name); + return new TypeReference($name, FlagEnum::class); } } diff --git a/packages/graphql/src/Stream/Directives/DirectiveTest~schema-expected.graphql b/packages/graphql/src/Stream/Directives/DirectiveTest~schema-expected.graphql index 852d48b3a..39acc84b2 100644 --- a/packages/graphql/src/Stream/Directives/DirectiveTest~schema-expected.graphql +++ b/packages/graphql/src/Stream/Directives/DirectiveTest~schema-expected.graphql @@ -109,6 +109,30 @@ directive @streamOffset on | ARGUMENT_DEFINITION +""" +Converts scalar into GraphQL Type. Similar to Lighthouse's `@scalar` +directive, but uses Laravel Container to resolve instance and also +supports PHP enums. +""" +directive @type( + """ + Reference to a PHP Class/Enum (FQN). + + If not PHP Enum, the Laravel Container with the following additional + arguments will be used to resolver the instance: + + * `string $name` - the type name. + * `GraphQL\Language\AST\ScalarTypeDefinitionNode $node` - the AST node. + * `array&ScalarConfig $config` - the scalar configuration (if `GraphQL\Type\Definition\ScalarType`). + + Resolved instance must be an `GraphQL\Type\Definition\Type&GraphQL\Type\Definition\NamedType` and have a name equal + to `$name` argument. + """ + class: String! +) +on + | SCALAR + """ Sort direction. """ @@ -278,7 +302,7 @@ navigation only to the previous/current/next pages (= cursor pagination). """ scalar StreamOffset -@scalar( +@type( class: "LastDragon_ru\\LaraASP\\GraphQL\\Stream\\Types\\Offset" ) diff --git a/packages/graphql/src/Stream/Directives/DirectiveTest~scout-expected.graphql b/packages/graphql/src/Stream/Directives/DirectiveTest~scout-expected.graphql index 2b285d3b2..27d1d2046 100644 --- a/packages/graphql/src/Stream/Directives/DirectiveTest~scout-expected.graphql +++ b/packages/graphql/src/Stream/Directives/DirectiveTest~scout-expected.graphql @@ -89,6 +89,30 @@ directive @streamOffset on | ARGUMENT_DEFINITION +""" +Converts scalar into GraphQL Type. Similar to Lighthouse's `@scalar` +directive, but uses Laravel Container to resolve instance and also +supports PHP enums. +""" +directive @type( + """ + Reference to a PHP Class/Enum (FQN). + + If not PHP Enum, the Laravel Container with the following additional + arguments will be used to resolver the instance: + + * `string $name` - the type name. + * `GraphQL\Language\AST\ScalarTypeDefinitionNode $node` - the AST node. + * `array&ScalarConfig $config` - the scalar configuration (if `GraphQL\Type\Definition\ScalarType`). + + Resolved instance must be an `GraphQL\Type\Definition\Type&GraphQL\Type\Definition\NamedType` and have a name equal + to `$name` argument. + """ + class: String! +) +on + | SCALAR + """ Sort direction. """ @@ -207,7 +231,7 @@ navigation only to the previous/current/next pages (= cursor pagination). """ scalar StreamOffset -@scalar( +@type( class: "LastDragon_ru\\LaraASP\\GraphQL\\Stream\\Types\\Offset" ) diff --git a/packages/graphql/src/Stream/Types/Offset.php b/packages/graphql/src/Stream/Types/Offset.php index 0c347765f..fbf23d27e 100644 --- a/packages/graphql/src/Stream/Types/Offset.php +++ b/packages/graphql/src/Stream/Types/Offset.php @@ -22,6 +22,7 @@ use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator; use LastDragon_ru\LaraASP\GraphQL\Stream\Directives\Directive; use LastDragon_ru\LaraASP\GraphQL\Stream\Offset as StreamOffset; +use LastDragon_ru\LaraASP\GraphQL\Utils\TypeReference; use LastDragon_ru\LaraASP\Serializer\Contracts\Serializer; use Override; @@ -164,7 +165,7 @@ public function getTypeDefinition( Context $context, string $name, ): TypeDefinitionNode|Type|null { - return $this; + return new TypeReference($name, self::class); } // } diff --git a/packages/graphql/src/Testing/Package/TestCase.php b/packages/graphql/src/Testing/Package/TestCase.php index 06158b80b..de0dd118f 100644 --- a/packages/graphql/src/Testing/Package/TestCase.php +++ b/packages/graphql/src/Testing/Package/TestCase.php @@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Model as EloquentModel; use Illuminate\Database\Query\Builder as QueryBuilder; use Laravel\Scout\Builder as ScoutBuilder; +use LastDragon_ru\LaraASP\Core\Provider as CoreProvider; use LastDragon_ru\LaraASP\GraphQL\Provider; use LastDragon_ru\LaraASP\GraphQL\Testing\GraphQLAssertions; use LastDragon_ru\LaraASP\GraphQL\Testing\Package\Data\Models\TestObject; @@ -47,6 +48,7 @@ abstract class TestCase extends PackageTestCase { protected function getPackageProviders(mixed $app): array { return array_merge(parent::getPackageProviders($app), [ Provider::class, + CoreProvider::class, TestProvider::class, SerializerProvider::class, LighthouseServiceProvider::class, diff --git a/packages/graphql/src/Utils/AstManipulator.php b/packages/graphql/src/Utils/AstManipulator.php index 0aec637ea..0a7c0e2b7 100644 --- a/packages/graphql/src/Utils/AstManipulator.php +++ b/packages/graphql/src/Utils/AstManipulator.php @@ -22,6 +22,7 @@ use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\BlockString; use GraphQL\Language\Parser; +use GraphQL\Language\Printer; use GraphQL\Type\Definition\Argument; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumValueDefinition; @@ -41,14 +42,17 @@ use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\WrappingType; +use GraphQL\Utils\AST; use Illuminate\Support\Str; +use LastDragon_ru\LaraASP\Core\Utils\Cast; +use LastDragon_ru\LaraASP\GraphQL\Directives\Definitions\TypeDirective; use LastDragon_ru\LaraASP\GraphQL\Exceptions\ArgumentAlreadyDefined; use LastDragon_ru\LaraASP\GraphQL\Exceptions\NotImplemented; use LastDragon_ru\LaraASP\GraphQL\Exceptions\TypeDefinitionAlreadyDefined; use LastDragon_ru\LaraASP\GraphQL\Exceptions\TypeDefinitionUnknown; use LastDragon_ru\LaraASP\GraphQL\Exceptions\TypeUnexpected; +use LastDragon_ru\LaraASP\GraphQL\Package; use LastDragon_ru\LaraASP\GraphQL\Stream\Directives\Directive as StreamDirective; -use LastDragon_ru\LaraASP\GraphQL\Utils\Definitions\LaraAspAsEnumDirective; use Nuwave\Lighthouse\Pagination\PaginateDirective; use Nuwave\Lighthouse\Schema\AST\ASTHelper; use Nuwave\Lighthouse\Schema\AST\DocumentAST; @@ -67,6 +71,7 @@ use function mb_strlen; use function mb_substr; use function sprintf; +use function trigger_deprecation; use function trim; use const JSON_THROW_ON_ERROR; @@ -278,7 +283,7 @@ public function getTypeDefinition( /** * @template TDefinition of (TypeDefinitionNode&Node)|(Type&NamedType) * - * @param TDefinition $definition + * @param TDefinition $definition Passing {@see Type} is deprecated, please use {@see TypeReference} instead. * * @return TDefinition */ @@ -291,7 +296,27 @@ public function addTypeDefinition(TypeDefinitionNode|Type $definition): TypeDefi if ($definition instanceof TypeDefinitionNode && $definition instanceof Node) { $this->getDocument()->setTypeDefinition($definition); + } elseif ($definition instanceof TypeReference) { + $directive = DirectiveLocator::directiveName(TypeDirective::class); + $class = Cast::to(Node::class, AST::astFromValue($definition->type, Type::string())); + $class = Printer::doPrint($class); + $node = Parser::scalarTypeDefinition( + <<getDocument()->setTypeDefinition($node); } elseif ($definition instanceof ScalarType) { + trigger_deprecation( + Package::Name, + '%{VERSION}', + 'Passing `%s` into `%s` is deprecated, please use `%s` instead.', + ScalarType::class, + __METHOD__, + TypeReference::class, + ); + $class = json_encode($definition::class, JSON_THROW_ON_ERROR); $scalar = Parser::scalarTypeDefinition( <<getDocument()->setTypeDefinition($scalar); } elseif ($definition instanceof PhpEnumType) { - $enum = DirectiveLocator::directiveName(LaraAspAsEnumDirective::class); - $class = PhpEnumTypeHelper::getEnumClass($definition); - $class = json_encode($class, JSON_THROW_ON_ERROR); - $scalar = Parser::scalarTypeDefinition( + trigger_deprecation( + Package::Name, + '%{VERSION}', + 'Passing `%s` into `%s` is deprecated, please use `%s` instead.', + PhpEnumType::class, + __METHOD__, + TypeReference::class, + ); + + $directive = DirectiveLocator::directiveName(TypeDirective::class); + $class = PhpEnumTypeHelper::getEnumClass($definition); + $class = json_encode($class, JSON_THROW_ON_ERROR); + $scalar = Parser::scalarTypeDefinition( <<directiveArgValue(self::ArgClass)); - - if (!is_a($class, UnitEnum::class, true)) { - throw new DefinitionException("The `{$class}` is not an enum."); - } - - // Return - return new PhpEnumType($class, $value->getTypeDefinitionName()); - } -} diff --git a/packages/graphql/src/Utils/Directives/AsEnumTest.php b/packages/graphql/src/Utils/Directives/AsEnumTest.php deleted file mode 100644 index ad56c63e3..000000000 --- a/packages/graphql/src/Utils/Directives/AsEnumTest.php +++ /dev/null @@ -1,71 +0,0 @@ -useGraphQLSchema( - <<make(TypeRegistry::class); - $type = $registry->get($name); - - self::assertInstanceOf(PhpEnumType::class, $type); - self::assertEquals($name, $type->name()); - self::assertEquals($class, PhpEnumTypeHelper::getEnumClass($type)); - } - - public function testResolveNodeNotEnum(): void { - $class = stdClass::class; - $enum = json_encode($class, JSON_THROW_ON_ERROR); - $name = 'TestEnum'; - - $this->useGraphQLSchema( - <<make(TypeRegistry::class); - - self::expectException(DefinitionException::class); - self::expectExceptionMessage("The `{$class}` is not an enum."); - - $registry->get($name); - } -} - -// @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses -// @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps - -/** - * @internal - * @noinspection PhpMultipleClassesDeclarationsInOneFile - */ -enum AsEnumTest_Enum { - case A; -} diff --git a/packages/graphql/src/Utils/TypeReference.php b/packages/graphql/src/Utils/TypeReference.php new file mode 100644 index 000000000..4180ec351 --- /dev/null +++ b/packages/graphql/src/Utils/TypeReference.php @@ -0,0 +1,91 @@ + $type + */ + public function __construct( + public readonly string $name, + public readonly string $type, + ) { + // empty + } + + /** + * @internal + */ + #[Override] + public function name(): string { + return $this->name; + } + + /** + * @internal + */ + #[Override] + public function assertValid(): void { + $this->methodShouldNotBeUsed(); + } + + /** + * @internal + */ + #[Override] + public function isBuiltInType(): bool { + $this->methodShouldNotBeUsed(); + } + + /** + * @internal + */ + #[Override] + public function description(): ?string { + $this->methodShouldNotBeUsed(); + } + + /** + * @internal + * @inheritDoc + */ + #[Override] + public function astNode(): ?Node { + $this->methodShouldNotBeUsed(); + } + + /** + * @internal + * @inheritDoc + */ + #[Override] + public function extensionASTNodes(): array { + $this->methodShouldNotBeUsed(); + } + + /** + * @internal + */ + #[Override] + public function toString(): string { + $this->methodShouldNotBeUsed(); + } + + private function methodShouldNotBeUsed(): never { + throw new LogicException( + 'Method exists only for compatibility with existing API and MUST NOT BE USED.', + ); + } +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ae5d97547..b731a9534 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -46,7 +46,7 @@ parameters: path: packages/graphql/src/Testing/Package/Models/Image.php - - message: "#^Method LastDragon_ru\\\\LaraASP\\\\GraphQL\\\\Utils\\\\AstManipulator\\:\\:addTypeDefinition\\(\\) should return TDefinition of \\(GraphQL\\\\Language\\\\AST\\\\Node&GraphQL\\\\Language\\\\AST\\\\TypeDefinitionNode\\)\\|\\(GraphQL\\\\Type\\\\Definition\\\\NamedType&GraphQL\\\\Type\\\\Definition\\\\Type\\) but returns GraphQL\\\\Type\\\\Definition\\\\PhpEnumType\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType\\|TDefinition of GraphQL\\\\Language\\\\AST\\\\Node&GraphQL\\\\Language\\\\AST\\\\TypeDefinitionNode\\.$#" + message: "#^Method LastDragon_ru\\\\LaraASP\\\\GraphQL\\\\Utils\\\\AstManipulator\\:\\:addTypeDefinition\\(\\) should return TDefinition of \\(GraphQL\\\\Language\\\\AST\\\\Node&GraphQL\\\\Language\\\\AST\\\\TypeDefinitionNode\\)\\|\\(GraphQL\\\\Type\\\\Definition\\\\NamedType&GraphQL\\\\Type\\\\Definition\\\\Type\\) but returns GraphQL\\\\Type\\\\Definition\\\\PhpEnumType\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType\\|LastDragon_ru\\\\LaraASP\\\\GraphQL\\\\Utils\\\\TypeReference\\|TDefinition of GraphQL\\\\Language\\\\AST\\\\Node&GraphQL\\\\Language\\\\AST\\\\TypeDefinitionNode\\.$#" count: 1 path: packages/graphql/src/Utils/AstManipulator.php