From a753f1c56df97dfe105c1c2ac8abc6d72af2af28 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Fri, 3 Oct 2025 13:54:20 +0200 Subject: [PATCH 1/5] Introduce `resolveValue` method to Interface and Union types This allows transforming the object value after type resolution. This is useful when you have a single entity that needs different representations. For example, when your data layer returns a generic `PetEntity` with a type discriminator, but your GraphQL schema has separate `Dog` and `Cat` types. Example: ```php $PetType = new InterfaceType([ 'name' => 'Pet', 'resolveType' => static function (PetEntity $objectValue): string { if ($objectValue->type === 'dog') { return 'Dog'; } return 'Cat'; }, 'resolveValue' => static function (PetEntity $objectValue) { if ($objectValue->type === 'dog') { return new Dog($objectValue->name, $objectValue->woofs); } return new Cat($objectValue->name, $objectValue->meows); }, 'fields' => ['name' => ['type' => Type::string()]], ]); Now field resolvers receive the properly typed Dog or Cat object instead of the generic PetEntity, allowing for type-safe resolution without needing to transform objects before they reach the type resolver. Common use cases: - Database polymorphism (single table with type column) - External APIs returning generic objects with type discriminators --- src/Executor/ReferenceExecutor.php | 1 + src/Type/Definition/AbstractType.php | 11 ++ src/Type/Definition/InterfaceType.php | 11 ++ src/Type/Definition/UnionType.php | 11 ++ tests/Executor/AbstractTest.php | 189 +++++++++++++++++++++++ tests/Executor/TestClasses/PetEntity.php | 21 +++ 6 files changed, 244 insertions(+) create mode 100644 tests/Executor/TestClasses/PetEntity.php diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index c06256d70..e8727bddf 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -1092,6 +1092,7 @@ protected function completeAbstractValue( $contextValue ) { $typeCandidate = $returnType->resolveType($result, $contextValue, $info); + $result = $returnType->resolveValue($result, $contextValue, $info); if ($typeCandidate === null) { $runtimeType = static::defaultTypeResolver($result, $contextValue, $info, $returnType); diff --git a/src/Type/Definition/AbstractType.php b/src/Type/Definition/AbstractType.php index c646b8805..7caf29a61 100644 --- a/src/Type/Definition/AbstractType.php +++ b/src/Type/Definition/AbstractType.php @@ -7,6 +7,7 @@ /** * @phpstan-type ResolveTypeReturn ObjectType|string|callable(): (ObjectType|string|null)|Deferred|null * @phpstan-type ResolveType callable(mixed $objectValue, mixed $context, ResolveInfo $resolveInfo): ResolveTypeReturn + * @phpstan-type ResolveValue callable(mixed $objectValue, mixed $context, ResolveInfo $resolveInfo): mixed */ interface AbstractType { @@ -21,4 +22,14 @@ interface AbstractType * @phpstan-return ResolveTypeReturn */ public function resolveType($objectValue, $context, ResolveInfo $info); + + /** + * Resolves/transforms the value after type resolution. + * + * @param mixed $objectValue The resolved value for the object type + * @param mixed $context The context that was passed to GraphQL::execute() + * + * @return mixed The transformed value (or original if no transformation needed) + */ + public function resolveValue($objectValue, $context, ResolveInfo $info); } diff --git a/src/Type/Definition/InterfaceType.php b/src/Type/Definition/InterfaceType.php index d15342c31..601493156 100644 --- a/src/Type/Definition/InterfaceType.php +++ b/src/Type/Definition/InterfaceType.php @@ -10,6 +10,7 @@ /** * @phpstan-import-type ResolveType from AbstractType + * @phpstan-import-type ResolveValue from AbstractType * @phpstan-import-type FieldsConfig from FieldDefinition * * @phpstan-type InterfaceTypeReference InterfaceType|callable(): InterfaceType @@ -19,6 +20,7 @@ * fields: FieldsConfig, * interfaces?: iterable|callable(): iterable, * resolveType?: ResolveType|null, + * resolveValue?: ResolveValue|null, * astNode?: InterfaceTypeDefinitionNode|null, * extensionASTNodes?: array|null * } @@ -76,6 +78,15 @@ public function resolveType($objectValue, $context, ResolveInfo $info) return null; } + public function resolveValue($objectValue, $context, ResolveInfo $info) + { + if (isset($this->config['resolveValue'])) { + return ($this->config['resolveValue'])($objectValue, $context, $info); + } + + return $objectValue; + } + /** * @throws Error * @throws InvariantViolation diff --git a/src/Type/Definition/UnionType.php b/src/Type/Definition/UnionType.php index c5b03ebbd..4ddf3059d 100644 --- a/src/Type/Definition/UnionType.php +++ b/src/Type/Definition/UnionType.php @@ -10,6 +10,7 @@ /** * @phpstan-import-type ResolveType from AbstractType + * @phpstan-import-type ResolveValue from AbstractType * * @phpstan-type ObjectTypeReference ObjectType|callable(): ObjectType * @phpstan-type UnionConfig array{ @@ -17,6 +18,7 @@ * description?: string|null, * types: iterable|callable(): iterable, * resolveType?: ResolveType|null, + * resolveValue?: ResolveValue|null, * astNode?: UnionTypeDefinitionNode|null, * extensionASTNodes?: array|null * } @@ -115,6 +117,15 @@ public function resolveType($objectValue, $context, ResolveInfo $info) return null; } + public function resolveValue($objectValue, $context, ResolveInfo $info) + { + if (isset($this->config['resolveValue'])) { + return ($this->config['resolveValue'])($objectValue, $context, $info); + } + + return $objectValue; + } + public function assertValid(): void { Utils::assertValidName($this->name); diff --git a/tests/Executor/AbstractTest.php b/tests/Executor/AbstractTest.php index 0280cb811..312794dc7 100644 --- a/tests/Executor/AbstractTest.php +++ b/tests/Executor/AbstractTest.php @@ -13,6 +13,7 @@ use GraphQL\Tests\Executor\TestClasses\Cat; use GraphQL\Tests\Executor\TestClasses\Dog; use GraphQL\Tests\Executor\TestClasses\Human; +use GraphQL\Tests\Executor\TestClasses\PetEntity; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; @@ -773,4 +774,192 @@ public function testHintsOnConflictingTypeInstancesInResolveType(): void $error->getMessage() ); } + + public function testResolveValueAllowsModifyingObjectValueForInterfaceType(): void + { + $PetType = new InterfaceType([ + 'name' => 'Pet', + 'resolveType' => static function (PetEntity $objectValue): string { + if ($objectValue->type === 'dog') { + return 'Dog'; + } + + return 'Cat'; + }, + 'resolveValue' => static function (PetEntity $objectValue) { + if ($objectValue->type === 'dog') { + return new Dog($objectValue->name, $objectValue->vocalizes); + } + + return new Cat($objectValue->name, $objectValue->vocalizes); + }, + 'fields' => [ + 'name' => ['type' => Type::string()], + ], + ]); + + $DogType = new ObjectType([ + 'name' => 'Dog', + 'interfaces' => [$PetType], + 'fields' => [ + 'name' => [ + 'type' => Type::string(), + 'resolve' => fn (Dog $dog) => $dog->name, + ], + 'woofs' => [ + 'type' => Type::boolean(), + 'resolve' => fn (Dog $dog) => $dog->woofs, + ], + ], + ]); + + $CatType = new ObjectType([ + 'name' => 'Cat', + 'interfaces' => [$PetType], + 'fields' => [ + 'name' => [ + 'type' => Type::string(), + 'resolve' => fn (Cat $cat) => $cat->name, + ], + 'meows' => [ + 'type' => Type::boolean(), + 'resolve' => fn (Cat $cat) => $cat->meows, + ], + ], + ]); + + $schema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'pets' => [ + 'type' => Type::listOf($PetType), + 'resolve' => static fn (): array => [ + new PetEntity('dog', 'Odie', true), + new PetEntity('cat', 'Garfield', false), + ], + ], + ], + ]), + 'types' => [$CatType, $DogType], + ]); + + $query = '{ + pets { + name + ... on Dog { + woofs + } + ... on Cat { + meows + } + } + }'; + + $result = GraphQL::executeQuery($schema, $query)->toArray(); + + self::assertEquals( + [ + 'data' => [ + 'pets' => [ + ['name' => 'Odie', 'woofs' => true], + ['name' => 'Garfield', 'meows' => false], + ], + ], + ], + $result + ); + } + + public function testResolveValueAllowsModifyingObjectValueForUnionType(): void + { + $DogType = new ObjectType([ + 'name' => 'Dog', + 'fields' => [ + 'name' => [ + 'type' => Type::string(), + 'resolve' => fn (Dog $dog) => $dog->name, + ], + 'woofs' => [ + 'type' => Type::boolean(), + 'resolve' => fn (Dog $dog) => $dog->woofs, + ], + ], + ]); + + $CatType = new ObjectType([ + 'name' => 'Cat', + 'fields' => [ + 'name' => [ + 'type' => Type::string(), + 'resolve' => fn (Cat $cat) => $cat->name, + ], + 'meows' => [ + 'type' => Type::boolean(), + 'resolve' => fn (Cat $cat) => $cat->meows, + ], + ], + ]); + + $PetType = new UnionType([ + 'name' => 'Pet', + 'types' => [$DogType, $CatType], + 'resolveType' => static function (PetEntity $objectValue): string { + if ($objectValue->type === 'dog') { + return 'Dog'; + } + + return 'Cat'; + }, + 'resolveValue' => static function (PetEntity $objectValue) { + if ($objectValue->type === 'dog') { + return new Dog($objectValue->name, $objectValue->vocalizes); + } + + return new Cat($objectValue->name, $objectValue->vocalizes); + }, + ]); + + $schema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'pets' => [ + 'type' => Type::listOf($PetType), + 'resolve' => static fn (): array => [ + new PetEntity('dog', 'Odie', true), + new PetEntity('cat', 'Garfield', false), + ], + ], + ], + ]), + ]); + + $query = '{ + pets { + ... on Dog { + name + woofs + } + ... on Cat { + name + meows + } + } + }'; + + $result = GraphQL::executeQuery($schema, $query)->toArray(); + + self::assertEquals( + [ + 'data' => [ + 'pets' => [ + ['name' => 'Odie', 'woofs' => true], + ['name' => 'Garfield', 'meows' => false], + ], + ], + ], + $result + ); + } } diff --git a/tests/Executor/TestClasses/PetEntity.php b/tests/Executor/TestClasses/PetEntity.php new file mode 100644 index 000000000..9cb6faceb --- /dev/null +++ b/tests/Executor/TestClasses/PetEntity.php @@ -0,0 +1,21 @@ +type = $type; + $this->name = $name; + $this->vocalizes = $vocalizes; + } +} From f623a9f301337ff21c8eb9327371827f068ad273 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 8 Oct 2025 08:36:03 +0200 Subject: [PATCH 2/5] Clean up AbstractTest --- tests/Executor/AbstractTest.php | 245 +++++++++++++++++--------------- 1 file changed, 128 insertions(+), 117 deletions(-) diff --git a/tests/Executor/AbstractTest.php b/tests/Executor/AbstractTest.php index 312794dc7..6ad655ae7 100644 --- a/tests/Executor/AbstractTest.php +++ b/tests/Executor/AbstractTest.php @@ -543,29 +543,35 @@ interface Pet { public function testReturningInvalidValueFromResolveTypeYieldsUsefulError(): void { // @phpstan-ignore-next-line intentionally wrong - $fooInterface = new InterfaceType([ + $FooInterfaceType = new InterfaceType([ 'name' => 'FooInterface', - 'fields' => ['bar' => ['type' => Type::string()]], + 'fields' => [ + 'bar' => Type::string(), + ], 'resolveType' => static fn (): array => [], ]); - $fooObject = new ObjectType([ + $FooObjectType = new ObjectType([ 'name' => 'FooObject', - 'fields' => ['bar' => ['type' => Type::string()]], - 'interfaces' => [$fooInterface], + 'fields' => [ + 'bar' => Type::string(), + ], + 'interfaces' => [$FooInterfaceType], ]); - $schema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'Query', - 'fields' => [ - 'foo' => [ - 'type' => $fooInterface, - 'resolve' => static fn (): string => 'dummy', - ], + $QueryType = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'foo' => [ + 'type' => $FooInterfaceType, + 'resolve' => static fn (): string => 'dummy', ], - ]), - 'types' => [$fooObject], + ], + ]); + + $schema = new Schema([ + 'query' => $QueryType, + 'types' => [$FooObjectType], ]); $result = GraphQL::executeQuery($schema, '{ foo { bar } }'); @@ -575,7 +581,9 @@ public function testReturningInvalidValueFromResolveTypeYieldsUsefulError(): voi 'errors' => [ [ 'message' => 'Internal server error', - 'locations' => [['line' => 1, 'column' => 3]], + 'locations' => [ + ['line' => 1, 'column' => 3], + ], 'path' => ['foo'], 'extensions' => [ 'debugMessage' => 'Abstract type FooInterface must resolve to an Object type at ' @@ -591,45 +599,47 @@ public function testReturningInvalidValueFromResolveTypeYieldsUsefulError(): voi public function testWarnsAboutOrphanedTypesWhenMissingType(): void { - $fooObject = null; - $fooInterface = new InterfaceType([ + $FooObjectType = null; + $FooInterfaceType = new InterfaceType([ 'name' => 'FooInterface', 'fields' => [ - 'bar' => [ - 'type' => Type::string(), - ], + 'bar' => Type::string(), ], - 'resolveType' => static function () use (&$fooObject): ?ObjectType { - return $fooObject; + 'resolveType' => static function () use (&$FooObjectType): ?ObjectType { + return $FooObjectType; }, ]); - $fooObject = new ObjectType([ + $FooObjectType = new ObjectType([ 'name' => 'FooObject', 'fields' => [ - 'bar' => [ - 'type' => Type::string(), - ], + 'bar' => Type::string(), ], - 'interfaces' => [$fooInterface], + 'interfaces' => [$FooInterfaceType], ]); - $schema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'Query', - 'fields' => [ - 'foo' => [ - 'type' => $fooInterface, - 'resolve' => static fn (): array => ['bar' => 'baz'], + $QueryType = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'foo' => [ + 'type' => $FooInterfaceType, + 'resolve' => static fn (): array => [ + 'bar' => 'baz', ], ], - ]), + ], + ]); + + $schema = new Schema([ + 'query' => $QueryType, ]); $result = GraphQL::executeQuery($schema, '{ foo { bar } }'); - $error = $result->errors[0] ?? null; - self::assertInstanceOf(Error::class, $error); + $errors = $result->errors; + self::assertCount(1, $errors); + + [$error] = $errors; self::assertStringContainsString( 'Schema does not contain type "FooObject". This can happen when an object type is only referenced indirectly through abstract types and never directly through fields.List the type in the option "types" during schema construction, see https://webonyx.github.io/graphql-php/schema-definition/#configuration-options.', $error->getMessage(), @@ -652,7 +662,7 @@ public function testResolveTypeAllowsResolvingWithTypeName(): void return null; }, 'fields' => [ - 'name' => ['type' => Type::string()], + 'name' => Type::string(), ], ]); @@ -660,8 +670,8 @@ public function testResolveTypeAllowsResolvingWithTypeName(): void 'name' => 'Dog', 'interfaces' => [$PetType], 'fields' => [ - 'name' => ['type' => Type::string()], - 'woofs' => ['type' => Type::boolean()], + 'name' => Type::string(), + 'woofs' => Type::boolean(), ], ]); @@ -669,28 +679,31 @@ public function testResolveTypeAllowsResolvingWithTypeName(): void 'name' => 'Cat', 'interfaces' => [$PetType], 'fields' => [ - 'name' => ['type' => Type::string()], - 'meows' => ['type' => Type::boolean()], + 'name' => Type::string(), + 'meows' => Type::boolean(), ], ]); - $schema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'Query', - 'fields' => [ - 'pets' => [ - 'type' => Type::listOf($PetType), - 'resolve' => static fn (): array => [ - new Dog('Odie', true), - new Cat('Garfield', false), - ], + $QueryType = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'pets' => [ + 'type' => Type::listOf($PetType), + 'resolve' => static fn (): array => [ + new Dog('Odie', true), + new Cat('Garfield', false), ], ], - ]), + ], + ]); + + $schema = new Schema([ + 'query' => $QueryType, 'types' => [$CatType, $DogType], ]); - $query = '{ + $query = ' + { pets { name ... on Dog { @@ -700,43 +713,41 @@ public function testResolveTypeAllowsResolvingWithTypeName(): void meows } } - }'; + } + '; - $result = GraphQL::executeQuery($schema, $query)->toArray(); + $result = GraphQL::executeQuery($schema, $query); - self::assertEquals( - [ - 'data' => [ - 'pets' => [ - ['name' => 'Odie', 'woofs' => true], - ['name' => 'Garfield', 'meows' => false], - ], + self::assertSame([ + 'data' => [ + 'pets' => [ + ['name' => 'Odie', 'woofs' => true], + ['name' => 'Garfield', 'meows' => false], ], ], - $result - ); + ], $result->toArray()); } public function testHintsOnConflictingTypeInstancesInResolveType(): void { - /** @var InterfaceType|null $iface */ - $iface = null; + /** @var InterfaceType|null $NodeType */ + $NodeType = null; - $createTest = function () use (&$iface): ObjectType { + $createTest = function () use (&$NodeType): ObjectType { return new ObjectType([ 'name' => 'Test', 'fields' => [ 'a' => Type::string(), ], - 'interfaces' => function () use ($iface): array { - self::assertNotNull($iface); + 'interfaces' => function () use ($NodeType): array { + self::assertNotNull($NodeType); - return [$iface]; + return [$NodeType]; }, ]); }; - $iface = new InterfaceType([ + $NodeType = new InterfaceType([ 'name' => 'Node', 'fields' => [ 'a' => Type::string(), @@ -744,31 +755,33 @@ public function testHintsOnConflictingTypeInstancesInResolveType(): void 'resolveType' => $createTest, ]); - $query = new ObjectType([ + $QueryType = new ObjectType([ 'name' => 'Query', 'fields' => [ - 'node' => $iface, + 'node' => $NodeType, 'test' => $createTest(), ], ]); $schema = new Schema([ - 'query' => $query, + 'query' => $QueryType, ]); $schema->assertValid(); $query = ' - { - node { - a - } + { + node { + a } + } '; $result = Executor::execute($schema, Parser::parse($query), ['node' => ['a' => 'value']]); - $error = $result->errors[0] ?? null; - self::assertInstanceOf(Error::class, $error); + $errors = $result->errors; + self::assertCount(1, $errors); + + [$error] = $errors; self::assertStringContainsString( 'Schema must contain unique named types but contains multiple types named "Test". Make sure that `resolveType` function of abstract type "Node" returns the same type instance as referenced anywhere else within the schema (see https://webonyx.github.io/graphql-php/type-definitions/#type-registry).', $error->getMessage() @@ -786,7 +799,7 @@ public function testResolveValueAllowsModifyingObjectValueForInterfaceType(): vo return 'Cat'; }, - 'resolveValue' => static function (PetEntity $objectValue) { + 'resolveValue' => static function (PetEntity $objectValue): object { if ($objectValue->type === 'dog') { return new Dog($objectValue->name, $objectValue->vocalizes); } @@ -794,7 +807,7 @@ public function testResolveValueAllowsModifyingObjectValueForInterfaceType(): vo return new Cat($objectValue->name, $objectValue->vocalizes); }, 'fields' => [ - 'name' => ['type' => Type::string()], + 'name' => Type::string(), ], ]); @@ -804,11 +817,11 @@ public function testResolveValueAllowsModifyingObjectValueForInterfaceType(): vo 'fields' => [ 'name' => [ 'type' => Type::string(), - 'resolve' => fn (Dog $dog) => $dog->name, + 'resolve' => fn (Dog $dog): string => $dog->name, ], 'woofs' => [ 'type' => Type::boolean(), - 'resolve' => fn (Dog $dog) => $dog->woofs, + 'resolve' => fn (Dog $dog): bool => $dog->woofs, ], ], ]); @@ -819,11 +832,11 @@ public function testResolveValueAllowsModifyingObjectValueForInterfaceType(): vo 'fields' => [ 'name' => [ 'type' => Type::string(), - 'resolve' => fn (Cat $cat) => $cat->name, + 'resolve' => fn (Cat $cat): string => $cat->name, ], 'meows' => [ 'type' => Type::boolean(), - 'resolve' => fn (Cat $cat) => $cat->meows, + 'resolve' => fn (Cat $cat): bool => $cat->meows, ], ], ]); @@ -844,7 +857,8 @@ public function testResolveValueAllowsModifyingObjectValueForInterfaceType(): vo 'types' => [$CatType, $DogType], ]); - $query = '{ + $query = ' + { pets { name ... on Dog { @@ -854,21 +868,19 @@ public function testResolveValueAllowsModifyingObjectValueForInterfaceType(): vo meows } } - }'; + } + '; - $result = GraphQL::executeQuery($schema, $query)->toArray(); + $result = GraphQL::executeQuery($schema, $query); - self::assertEquals( - [ - 'data' => [ - 'pets' => [ - ['name' => 'Odie', 'woofs' => true], - ['name' => 'Garfield', 'meows' => false], - ], + self::assertSame([ + 'data' => [ + 'pets' => [ + ['name' => 'Odie', 'woofs' => true], + ['name' => 'Garfield', 'meows' => false], ], ], - $result - ); + ], $result->toArray()); } public function testResolveValueAllowsModifyingObjectValueForUnionType(): void @@ -878,11 +890,11 @@ public function testResolveValueAllowsModifyingObjectValueForUnionType(): void 'fields' => [ 'name' => [ 'type' => Type::string(), - 'resolve' => fn (Dog $dog) => $dog->name, + 'resolve' => fn (Dog $dog): string => $dog->name, ], 'woofs' => [ 'type' => Type::boolean(), - 'resolve' => fn (Dog $dog) => $dog->woofs, + 'resolve' => fn (Dog $dog): bool => $dog->woofs, ], ], ]); @@ -892,11 +904,11 @@ public function testResolveValueAllowsModifyingObjectValueForUnionType(): void 'fields' => [ 'name' => [ 'type' => Type::string(), - 'resolve' => fn (Cat $cat) => $cat->name, + 'resolve' => fn (Cat $cat): string => $cat->name, ], 'meows' => [ 'type' => Type::boolean(), - 'resolve' => fn (Cat $cat) => $cat->meows, + 'resolve' => fn (Cat $cat): bool => $cat->meows, ], ], ]); @@ -911,7 +923,7 @@ public function testResolveValueAllowsModifyingObjectValueForUnionType(): void return 'Cat'; }, - 'resolveValue' => static function (PetEntity $objectValue) { + 'resolveValue' => static function (PetEntity $objectValue): object { if ($objectValue->type === 'dog') { return new Dog($objectValue->name, $objectValue->vocalizes); } @@ -935,7 +947,8 @@ public function testResolveValueAllowsModifyingObjectValueForUnionType(): void ]), ]); - $query = '{ + $query = ' + { pets { ... on Dog { name @@ -946,20 +959,18 @@ public function testResolveValueAllowsModifyingObjectValueForUnionType(): void meows } } - }'; + } + '; - $result = GraphQL::executeQuery($schema, $query)->toArray(); + $result = GraphQL::executeQuery($schema, $query); - self::assertEquals( - [ - 'data' => [ - 'pets' => [ - ['name' => 'Odie', 'woofs' => true], - ['name' => 'Garfield', 'meows' => false], - ], + self::assertSame([ + 'data' => [ + 'pets' => [ + ['name' => 'Odie', 'woofs' => true], + ['name' => 'Garfield', 'meows' => false], ], ], - $result - ); + ], $result->toArray()); } } From 0657a622255e9207628f218c1f6f9da6809d049d Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 8 Oct 2025 08:45:08 +0200 Subject: [PATCH 3/5] improve comments for resolveValue --- src/Type/Definition/AbstractType.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Type/Definition/AbstractType.php b/src/Type/Definition/AbstractType.php index 7caf29a61..5da106fe6 100644 --- a/src/Type/Definition/AbstractType.php +++ b/src/Type/Definition/AbstractType.php @@ -24,12 +24,12 @@ interface AbstractType public function resolveType($objectValue, $context, ResolveInfo $info); /** - * Resolves/transforms the value after type resolution. + * Receives the original resolved value and transforms it if necessary. * * @param mixed $objectValue The resolved value for the object type * @param mixed $context The context that was passed to GraphQL::execute() * - * @return mixed The transformed value (or original if no transformation needed) + * @return mixed The possibly transformed value */ public function resolveValue($objectValue, $context, ResolveInfo $info); } From d996c27086fae26106ff4208d6c4b70ffebc48a7 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 8 Oct 2025 08:45:30 +0200 Subject: [PATCH 4/5] extract anonymous class --- src/Executor/PromiseExecutor.php | 20 ++++++++++++++++++++ src/Executor/ReferenceExecutor.php | 15 +++------------ 2 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 src/Executor/PromiseExecutor.php diff --git a/src/Executor/PromiseExecutor.php b/src/Executor/PromiseExecutor.php new file mode 100644 index 000000000..7f76309b8 --- /dev/null +++ b/src/Executor/PromiseExecutor.php @@ -0,0 +1,20 @@ +result = $result; + } + + public function doExecute(): Promise + { + return $this->result; + } +} diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index e8727bddf..c9956299d 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -120,19 +120,10 @@ public static function create( ); if (is_array($exeContext)) { - return new class($promiseAdapter->createFulfilled(new ExecutionResult(null, $exeContext))) implements ExecutorImplementation { - private Promise $result; + $executionResult = new ExecutionResult(null, $exeContext); + $fulfilledPromise = $promiseAdapter->createFulfilled($executionResult); - public function __construct(Promise $result) - { - $this->result = $result; - } - - public function doExecute(): Promise - { - return $this->result; - } - }; + return new PromiseExecutor($fulfilledPromise); } return new static($exeContext); From c95d74fea6d90810fbb4496ee2c0589829f58d4f Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 8 Oct 2025 08:45:53 +0200 Subject: [PATCH 5/5] fix $result type --- src/Executor/ReferenceExecutor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index c9956299d..93ba8ce80 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -1064,7 +1064,7 @@ protected function completeLeafValue(LeafType $returnType, &$result) * @param \ArrayObject $fieldNodes * @param list $path * @param list $unaliasedPath - * @param array $result + * @param mixed $result * @param mixed $contextValue * * @throws \Exception @@ -1264,7 +1264,7 @@ protected function completeObjectValue( /** * @param \ArrayObject $fieldNodes - * @param array $result + * @param mixed $result */ protected function invalidReturnTypeError( ObjectType $returnType, @@ -1400,7 +1400,7 @@ protected function executeFields(ObjectType $parentType, $rootValue, array $path * * @param array|mixed $results * - * @return array|\stdClass|mixed + * @return non-empty-array|\stdClass|mixed */ protected static function fixResultsIfEmptyArray($results) {