From cbf1956cd812fa0ec5b8675e47c571464411135a Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Sat, 26 Oct 2024 12:45:00 +0200 Subject: [PATCH 1/3] Allow customizing PhpEnumType --- CHANGELOG.md | 6 +++++ src/Type/Definition/EnumType.php | 6 ++--- src/Type/Definition/InputObjectType.php | 6 ++--- src/Type/Definition/InterfaceType.php | 6 ++--- src/Type/Definition/NamedType.php | 4 +-- src/Type/Definition/ObjectType.php | 6 ++--- src/Type/Definition/PhpEnumType.php | 25 ++++++++++++----- src/Type/Definition/ScalarType.php | 2 +- src/Type/Definition/UnionType.php | 2 +- src/Type/Schema.php | 2 +- src/Type/SchemaValidationContext.php | 36 ++++++++++++------------- src/Utils/ASTDefinitionBuilder.php | 14 +++++----- src/Utils/SchemaExtender.php | 32 +++++++++++----------- tests/Type/PhpEnumTypeTest.php | 14 ++++++++++ 14 files changed, 94 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7554a103..4aa0d38db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ You can find and compare releases at the [GitHub release page](https://github.co ## Unreleased +## v15.17.0 + +### Added + +- Allow customizing PhpEnumType + ## v15.16.1 ### Fixed diff --git a/src/Type/Definition/EnumType.php b/src/Type/Definition/EnumType.php index f094147df..567abed8f 100644 --- a/src/Type/Definition/EnumType.php +++ b/src/Type/Definition/EnumType.php @@ -30,7 +30,7 @@ * description?: string|null, * values: EnumValues|callable(): EnumValues, * astNode?: EnumTypeDefinitionNode|null, - * extensionASTNodes?: array|null + * extensionASTNodes?: array|null * } */ class EnumType extends Type implements InputType, OutputType, LeafType, NullableType, NamedType @@ -39,7 +39,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable public ?EnumTypeDefinitionNode $astNode; - /** @var array */ + /** @var array */ public array $extensionASTNodes; /** @phpstan-var EnumTypeConfig */ @@ -264,7 +264,7 @@ public function astNode(): ?EnumTypeDefinitionNode return $this->astNode; } - /** @return array */ + /** @return array */ public function extensionASTNodes(): array { return $this->extensionASTNodes; diff --git a/src/Type/Definition/InputObjectType.php b/src/Type/Definition/InputObjectType.php index dfdd3b3e4..182f6bedb 100644 --- a/src/Type/Definition/InputObjectType.php +++ b/src/Type/Definition/InputObjectType.php @@ -20,7 +20,7 @@ * fields: iterable|callable(): iterable, * parseValue?: callable(array): mixed, * astNode?: InputObjectTypeDefinitionNode|null, - * extensionASTNodes?: array|null + * extensionASTNodes?: array|null * } */ class InputObjectType extends Type implements InputType, NullableType, NamedType @@ -29,7 +29,7 @@ class InputObjectType extends Type implements InputType, NullableType, NamedType public ?InputObjectTypeDefinitionNode $astNode; - /** @var array */ + /** @var array */ public array $extensionASTNodes; /** @phpstan-var InputObjectConfig */ @@ -203,7 +203,7 @@ public function astNode(): ?InputObjectTypeDefinitionNode return $this->astNode; } - /** @return array */ + /** @return array */ public function extensionASTNodes(): array { return $this->extensionASTNodes; diff --git a/src/Type/Definition/InterfaceType.php b/src/Type/Definition/InterfaceType.php index 2cc9bd5a9..2cec79388 100644 --- a/src/Type/Definition/InterfaceType.php +++ b/src/Type/Definition/InterfaceType.php @@ -20,7 +20,7 @@ * interfaces?: iterable|callable(): iterable, * resolveType?: ResolveType|null, * astNode?: InterfaceTypeDefinitionNode|null, - * extensionASTNodes?: array|null + * extensionASTNodes?: array|null * } */ class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, HasFieldsType, NamedType, ImplementingType @@ -31,7 +31,7 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT public ?InterfaceTypeDefinitionNode $astNode; - /** @var array */ + /** @var array */ public array $extensionASTNodes; /** @phpstan-var InterfaceConfig */ @@ -99,7 +99,7 @@ public function astNode(): ?InterfaceTypeDefinitionNode return $this->astNode; } - /** @return array */ + /** @return array */ public function extensionASTNodes(): array { return $this->extensionASTNodes; diff --git a/src/Type/Definition/NamedType.php b/src/Type/Definition/NamedType.php index 58dbfcfd8..c9ca191bb 100644 --- a/src/Type/Definition/NamedType.php +++ b/src/Type/Definition/NamedType.php @@ -19,7 +19,7 @@ * @property string $name * @property string|null $description * @property (Node&TypeDefinitionNode)|null $astNode - * @property array $extensionASTNodes + * @property array $extensionASTNodes */ interface NamedType { @@ -36,6 +36,6 @@ public function description(): ?string; /** @return (Node&TypeDefinitionNode)|null */ public function astNode(): ?Node; - /** @return array */ + /** @return array */ public function extensionASTNodes(): array; } diff --git a/src/Type/Definition/ObjectType.php b/src/Type/Definition/ObjectType.php index f69cbfa30..64ce38bca 100644 --- a/src/Type/Definition/ObjectType.php +++ b/src/Type/Definition/ObjectType.php @@ -62,7 +62,7 @@ * interfaces?: iterable|callable(): iterable, * isTypeOf?: (callable(mixed $objectValue, mixed $context, ResolveInfo $resolveInfo): (bool|Deferred|null))|null, * astNode?: ObjectTypeDefinitionNode|null, - * extensionASTNodes?: array|null + * extensionASTNodes?: array|null * } */ class ObjectType extends Type implements OutputType, CompositeType, NullableType, HasFieldsType, NamedType, ImplementingType @@ -73,7 +73,7 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType public ?ObjectTypeDefinitionNode $astNode; - /** @var array */ + /** @var array */ public array $extensionASTNodes; /** @@ -172,7 +172,7 @@ public function astNode(): ?ObjectTypeDefinitionNode return $this->astNode; } - /** @return array */ + /** @return array */ public function extensionASTNodes(): array { return $this->extensionASTNodes; diff --git a/src/Type/Definition/PhpEnumType.php b/src/Type/Definition/PhpEnumType.php index 19e2d84d5..67d28f2a3 100644 --- a/src/Type/Definition/PhpEnumType.php +++ b/src/Type/Definition/PhpEnumType.php @@ -3,6 +3,8 @@ namespace GraphQL\Type\Definition; use GraphQL\Error\SerializationError; +use GraphQL\Language\AST\EnumTypeDefinitionNode; +use GraphQL\Language\AST\EnumTypeExtensionNode; use GraphQL\Utils\PhpDoc; use GraphQL\Utils\Utils; @@ -16,16 +18,23 @@ class PhpEnumType extends EnumType protected string $enumClass; /** - * @param class-string<\UnitEnum> $enum + * @param class-string<\UnitEnum> $enumClass The fully qualified class name of a native PHP enum * @param string|null $name The name the enum will have in the schema, defaults to the basename of the given class + * @param string|null $description The description the enum will have in the schema, defaults to PHPDoc of the given class + * @param array|null $extensionASTNodes * * @throws \Exception * @throws \ReflectionException */ - public function __construct(string $enum, ?string $name = null) - { - $this->enumClass = $enum; - $reflection = new \ReflectionEnum($enum); + public function __construct( + string $enumClass, + ?string $name = null, + ?string $description = null, + ?EnumTypeDefinitionNode $astNode = null, + ?array $extensionASTNodes = null + ) { + $this->enumClass = $enumClass; + $reflection = new \ReflectionEnum($enumClass); /** * @var array $enumDefinitions @@ -40,9 +49,11 @@ public function __construct(string $enum, ?string $name = null) } parent::__construct([ - 'name' => $name ?? $this->baseName($enum), + 'name' => $name ?? $this->baseName($enumClass), 'values' => $enumDefinitions, - 'description' => $this->extractDescription($reflection), + 'description' => $description ?? $this->extractDescription($reflection), + 'astNode' => $astNode, + 'extensionASTNodes' => $extensionASTNodes, ]); } diff --git a/src/Type/Definition/ScalarType.php b/src/Type/Definition/ScalarType.php index 453671fe3..af0587fac 100644 --- a/src/Type/Definition/ScalarType.php +++ b/src/Type/Definition/ScalarType.php @@ -69,7 +69,7 @@ public function astNode(): ?ScalarTypeDefinitionNode return $this->astNode; } - /** @return array */ + /** @return array */ public function extensionASTNodes(): array { return $this->extensionASTNodes; diff --git a/src/Type/Definition/UnionType.php b/src/Type/Definition/UnionType.php index 7e7963424..03dd3ef09 100644 --- a/src/Type/Definition/UnionType.php +++ b/src/Type/Definition/UnionType.php @@ -132,7 +132,7 @@ public function astNode(): ?UnionTypeDefinitionNode return $this->astNode; } - /** @return array */ + /** @return array */ public function extensionASTNodes(): array { return $this->extensionASTNodes; diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 31920e5eb..390cf44a9 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -71,7 +71,7 @@ class Schema public ?SchemaDefinitionNode $astNode; - /** @var array */ + /** @var array */ public array $extensionASTNodes = []; /** diff --git a/src/Type/SchemaValidationContext.php b/src/Type/SchemaValidationContext.php index acbd16b04..38493f7dc 100644 --- a/src/Type/SchemaValidationContext.php +++ b/src/Type/SchemaValidationContext.php @@ -386,36 +386,38 @@ private function validateFields(Type $type): void /** * @param Schema|ObjectType|InterfaceType|UnionType|EnumType|InputObjectType|Directive $obj * - * @return array|array|array|array|array|array|array + * @return list|list|list|list|list< EnumTypeDefinitionNode|EnumTypeExtensionNode>|list|list */ private function getAllNodes(object $obj): array { + $astNode = $obj->astNode; + if ($obj instanceof Schema) { - $astNode = $obj->astNode; $extensionNodes = $obj->extensionASTNodes; } elseif ($obj instanceof Directive) { - $astNode = $obj->astNode; $extensionNodes = []; } else { - $astNode = $obj->astNode; $extensionNodes = $obj->extensionASTNodes; } - return $astNode !== null - ? \array_merge([$astNode], $extensionNodes) - : $extensionNodes; + $allNodes = $astNode === null + ? [] + : [$astNode]; + foreach ($extensionNodes as $extensionNode) { + $allNodes[] = $extensionNode; + } + + return $allNodes; } /** * @param ObjectType|InterfaceType $type * - * @return array + * @return list */ private function getAllFieldNodes(Type $type, string $fieldName): array { - $allNodes = $type->astNode !== null - ? \array_merge([$type->astNode], $type->extensionASTNodes) - : $type->extensionASTNodes; + $allNodes = array_filter([$type->astNode, ...$type->extensionASTNodes]); $matchingFieldNodes = []; @@ -574,13 +576,11 @@ private function getImplementsInterfaceNode(ImplementingType $type, NamedType $s * @param ObjectType|InterfaceType $type * @param Type&NamedType $shouldBeInterface * - * @return array + * @return list */ private function getAllImplementsInterfaceNodes(ImplementingType $type, NamedType $shouldBeInterface): array { - $allNodes = $type->astNode !== null - ? \array_merge([$type->astNode], $type->extensionASTNodes) - : $type->extensionASTNodes; + $allNodes = array_filter([$type->astNode, ...$type->extensionASTNodes]); $shouldBeInterfaceName = $shouldBeInterface->name; $matchingInterfaceNodes = []; @@ -735,12 +735,10 @@ private function validateUnionMembers(UnionType $union): void } } - /** @return array */ + /** @return list */ private function getUnionMemberTypeNodes(UnionType $union, string $typeName): array { - $allNodes = $union->astNode !== null - ? \array_merge([$union->astNode], $union->extensionASTNodes) - : $union->extensionASTNodes; + $allNodes = array_filter([$union->astNode, ...$union->extensionASTNodes]); $types = []; foreach ($allNodes as $node) { diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index 9d18e801d..7dfd7a571 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -339,7 +339,7 @@ private function makeSchemaDef(Node $def): Type private function makeTypeDef(ObjectTypeDefinitionNode $def): ObjectType { $name = $def->name->value; - /** @var array $extensionASTNodes (proven by schema validation) */ + /** @var array $extensionASTNodes (proven by schema validation) */ $extensionASTNodes = $this->typeExtensionsMap[$name] ?? []; $allNodes = [$def, ...$extensionASTNodes]; @@ -453,7 +453,7 @@ private function makeImplementedInterfaces(array $nodes): array private function makeInterfaceDef(InterfaceTypeDefinitionNode $def): InterfaceType { $name = $def->name->value; - /** @var array $extensionASTNodes (proven by schema validation) */ + /** @var array $extensionASTNodes (proven by schema validation) */ $extensionASTNodes = $this->typeExtensionsMap[$name] ?? []; $allNodes = [$def, ...$extensionASTNodes]; @@ -475,7 +475,7 @@ private function makeInterfaceDef(InterfaceTypeDefinitionNode $def): InterfaceTy private function makeEnumDef(EnumTypeDefinitionNode $def): EnumType { $name = $def->name->value; - /** @var array $extensionASTNodes (proven by schema validation) */ + /** @var array $extensionASTNodes (proven by schema validation) */ $extensionASTNodes = $this->typeExtensionsMap[$name] ?? []; $values = []; @@ -502,7 +502,7 @@ private function makeEnumDef(EnumTypeDefinitionNode $def): EnumType private function makeUnionDef(UnionTypeDefinitionNode $def): UnionType { $name = $def->name->value; - /** @var array $extensionASTNodes (proven by schema validation) */ + /** @var array $extensionASTNodes (proven by schema validation) */ $extensionASTNodes = $this->typeExtensionsMap[$name] ?? []; return new UnionType([ @@ -531,15 +531,15 @@ private function makeUnionDef(UnionTypeDefinitionNode $def): UnionType private function makeScalarDef(ScalarTypeDefinitionNode $def): CustomScalarType { $name = $def->name->value; - /** @var array $extensionASTNodes (proven by schema validation) */ + /** @var array $extensionASTNodes (proven by schema validation) */ $extensionASTNodes = $this->typeExtensionsMap[$name] ?? []; return new CustomScalarType([ 'name' => $name, 'description' => $def->description->value ?? null, + 'serialize' => static fn ($value) => $value, 'astNode' => $def, 'extensionASTNodes' => $extensionASTNodes, - 'serialize' => static fn ($value) => $value, ]); } @@ -547,7 +547,7 @@ private function makeScalarDef(ScalarTypeDefinitionNode $def): CustomScalarType private function makeInputObjectDef(InputObjectTypeDefinitionNode $def): InputObjectType { $name = $def->name->value; - /** @var array $extensionASTNodes (proven by schema validation) */ + /** @var array $extensionASTNodes (proven by schema validation) */ $extensionASTNodes = $this->typeExtensionsMap[$name] ?? []; return new InputObjectType([ diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index 9a3da7ed1..91345c68b 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -188,10 +188,7 @@ function (string $typeName) use ($schema): Type { } } - $schemaExtensionASTNodes = \array_merge($schema->extensionASTNodes, $schemaExtensions); - - return new Schema( - (new SchemaConfig()) + $schemaConfig = (new SchemaConfig()) // @phpstan-ignore-next-line the root types may be invalid, but just passing them leads to more actionable errors ->setQuery($operationTypes['query']) // @phpstan-ignore-next-line the root types may be invalid, but just passing them leads to more actionable errors @@ -201,8 +198,9 @@ function (string $typeName) use ($schema): Type { ->setTypes($types) ->setDirectives($this->getMergedDirectives($schema, $directiveDefinitions)) ->setAstNode($schema->astNode ?? $schemaDef) - ->setExtensionASTNodes($schemaExtensionASTNodes) - ); + ->setExtensionASTNodes([...$schema->extensionASTNodes, ...$schemaExtensions]); + + return new Schema($schemaConfig); } /** @@ -212,10 +210,10 @@ function (string $typeName) use ($schema): Type { */ protected function extensionASTNodes(NamedType $type): ?array { - return \array_merge( - $type->extensionASTNodes ?? [], - $this->typeExtensionsMap[$type->name] ?? [] - ); + return [ + ...$type->extensionASTNodes ?? [], + ...$this->typeExtensionsMap[$type->name] ?? [], + ]; } /** @@ -225,7 +223,7 @@ protected function extensionASTNodes(NamedType $type): ?array */ protected function extendScalarType(ScalarType $type): CustomScalarType { - /** @var array $extensionASTNodes */ + /** @var array $extensionASTNodes */ $extensionASTNodes = $this->extensionASTNodes($type); return new CustomScalarType([ @@ -242,7 +240,7 @@ protected function extendScalarType(ScalarType $type): CustomScalarType /** @throws InvariantViolation */ protected function extendUnionType(UnionType $type): UnionType { - /** @var array $extensionASTNodes */ + /** @var array $extensionASTNodes */ $extensionASTNodes = $this->extensionASTNodes($type); return new UnionType([ @@ -262,7 +260,7 @@ protected function extendUnionType(UnionType $type): UnionType */ protected function extendEnumType(EnumType $type): EnumType { - /** @var array $extensionASTNodes */ + /** @var array $extensionASTNodes */ $extensionASTNodes = $this->extensionASTNodes($type); return new EnumType([ @@ -277,16 +275,16 @@ protected function extendEnumType(EnumType $type): EnumType /** @throws InvariantViolation */ protected function extendInputObjectType(InputObjectType $type): InputObjectType { - /** @var array $extensionASTNodes */ + /** @var array $extensionASTNodes */ $extensionASTNodes = $this->extensionASTNodes($type); return new InputObjectType([ 'name' => $type->name, 'description' => $type->description, 'fields' => fn (): array => $this->extendInputFieldMap($type), + 'parseValue' => [$type, 'parseValue'], 'astNode' => $type->astNode, 'extensionASTNodes' => $extensionASTNodes, - 'parseValue' => [$type, 'parseValue'], ]); } @@ -529,7 +527,7 @@ protected function extendFieldMap(Type $type): array /** @throws InvariantViolation */ protected function extendObjectType(ObjectType $type): ObjectType { - /** @var array $extensionASTNodes */ + /** @var array $extensionASTNodes */ $extensionASTNodes = $this->extensionASTNodes($type); return new ObjectType([ @@ -548,7 +546,7 @@ protected function extendObjectType(ObjectType $type): ObjectType /** @throws InvariantViolation */ protected function extendInterfaceType(InterfaceType $type): InterfaceType { - /** @var array $extensionASTNodes */ + /** @var array $extensionASTNodes */ $extensionASTNodes = $this->extensionASTNodes($type); return new InterfaceType([ diff --git a/tests/Type/PhpEnumTypeTest.php b/tests/Type/PhpEnumTypeTest.php index c49aa5f93..13bedb6e3 100644 --- a/tests/Type/PhpEnumTypeTest.php +++ b/tests/Type/PhpEnumTypeTest.php @@ -44,6 +44,20 @@ enum PhpEnum { GRAPHQL, SchemaPrinter::printType($enumType)); } + public function testConstructEnumTypeFromPhpEnumWithOverwrittenDefaults(): void + { + $enumType = new PhpEnumType(PhpEnum::class, 'MyEnum', 'My description.'); + self::assertSame(<<<'GRAPHQL' +"My description." +enum MyEnum { + "bar" + A + B @deprecated + C @deprecated(reason: "baz") +} +GRAPHQL, SchemaPrinter::printType($enumType)); + } + public function testConstructEnumTypeFromIntPhpEnum(): void { $enumType = new PhpEnumType(IntPhpEnum::class); From baf8b60bdcad511e913ecf3caab775bfe34b5a4f Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Sat, 26 Oct 2024 12:48:00 +0200 Subject: [PATCH 2/3] link PR --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa0d38db..4f47b373c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ You can find and compare releases at the [GitHub release page](https://github.co ### Added -- Allow customizing PhpEnumType +- Allow customizing PhpEnumType https://github.com/webonyx/graphql-php/pull/1623 ## v15.16.1 From 58b6a4c5cc079e03e8f99a559c37fce4f103eb00 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Sat, 26 Oct 2024 12:52:49 +0200 Subject: [PATCH 3/3] test ast nodes --- tests/Type/PhpEnumTypeTest.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/Type/PhpEnumTypeTest.php b/tests/Type/PhpEnumTypeTest.php index 13bedb6e3..138971496 100644 --- a/tests/Type/PhpEnumTypeTest.php +++ b/tests/Type/PhpEnumTypeTest.php @@ -5,6 +5,7 @@ use GraphQL\Error\DebugFlag; use GraphQL\Error\SerializationError; use GraphQL\GraphQL; +use GraphQL\Language\Parser; use GraphQL\Tests\TestCaseBase; use GraphQL\Tests\Type\PhpEnumType\DocBlockPhpEnum; use GraphQL\Tests\Type\PhpEnumType\IntPhpEnum; @@ -46,7 +47,22 @@ enum PhpEnum { public function testConstructEnumTypeFromPhpEnumWithOverwrittenDefaults(): void { - $enumType = new PhpEnumType(PhpEnum::class, 'MyEnum', 'My description.'); + $astNode = Parser::enumTypeDefinition(<<<'GRAPHQL' +enum MyEnum @directiveA +GRAPHQL); + $extensionASTNode1 = Parser::enumTypeExtension(<<<'GRAPHQL' +extend enum MyEnum @directiveB +GRAPHQL); + $extensionASTNode2 = Parser::enumTypeExtension(<<<'GRAPHQL' +extend enum MyEnum @directiveC +GRAPHQL); + $enumType = new PhpEnumType( + PhpEnum::class, + 'MyEnum', + 'My description.', + $astNode, + [$extensionASTNode1, $extensionASTNode2] + ); self::assertSame(<<<'GRAPHQL' "My description." enum MyEnum { @@ -56,6 +72,8 @@ enum MyEnum { C @deprecated(reason: "baz") } GRAPHQL, SchemaPrinter::printType($enumType)); + self::assertSame($astNode, $enumType->astNode); + self::assertSame([$extensionASTNode1, $extensionASTNode2], $enumType->extensionASTNodes); } public function testConstructEnumTypeFromIntPhpEnum(): void