Skip to content

Commit 6b191d5

Browse files
committed
Load all nodes implementing interface on root Schema level
When an implementation is not defined in a schema, it's not automatically loaded by the resolvers. Therefore, load all interface implementation types on the root schema level as well.
1 parent 4cfca53 commit 6b191d5

File tree

5 files changed

+117
-1
lines changed

5 files changed

+117
-1
lines changed

phpstan-baseline.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,24 @@
325325
'count' => 1,
326326
'path' => __DIR__ . '/src/Resolver/Type/UnionTypeResolver.php',
327327
];
328+
$ignoreErrors[] = [
329+
'message' => '#^Method Jerowork\\\\GraphqlAttributeSchema\\\\SchemaBuilder\\:\\:getTypesImplementingInterface\\(\\) return type has no value type specified in iterable type iterable\\<mixed\\>\\.$#',
330+
'identifier' => 'missingType.iterableValue',
331+
'count' => 1,
332+
'path' => __DIR__ . '/src/SchemaBuilder.php',
333+
];
334+
$ignoreErrors[] = [
335+
'message' => '#^PHPDoc tag @return contains unresolvable type\\.$#',
336+
'identifier' => 'return.unresolvableType',
337+
'count' => 1,
338+
'path' => __DIR__ . '/src/SchemaBuilder.php',
339+
];
340+
$ignoreErrors[] = [
341+
'message' => '#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Schema constructor expects array\\{query\\?\\: \\(callable\\(\\)\\: \\(GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null\\)\\)\\|GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null, mutation\\?\\: \\(callable\\(\\)\\: \\(GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null\\)\\)\\|GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null, subscription\\?\\: \\(callable\\(\\)\\: \\(GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null\\)\\)\\|GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null, types\\?\\: \\(callable\\(\\)\\: iterable\\<callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\Type&GraphQL\\\\Type\\\\Definition\\\\NamedType\\>\\)\\|\\(callable\\(\\)\\: iterable\\<GraphQL\\\\Type\\\\Definition\\\\NamedType&GraphQL\\\\Type\\\\Definition\\\\Type\\>\\)\\|iterable\\<\\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\Type&GraphQL\\\\Type\\\\Definition\\\\NamedType\\)\\|\\(GraphQL\\\\Type\\\\Definition\\\\NamedType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\>\\|null, directives\\?\\: array\\<GraphQL\\\\Type\\\\Definition\\\\Directive\\>\\|null, typeLoader\\?\\: \\(callable\\(string\\)\\: \\(\\(GraphQL\\\\Type\\\\Definition\\\\NamedType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null\\)\\)\\|null, assumeValid\\?\\: bool\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\SchemaDefinitionNode\\|null, \\.\\.\\.\\}\\|GraphQL\\\\Type\\\\SchemaConfig, array\\{query\\: GraphQL\\\\Type\\\\Definition\\\\ObjectType, mutation\\: GraphQL\\\\Type\\\\Definition\\\\ObjectType, types\\: iterable\\<mixed\\>\\} given\\.$#',
342+
'identifier' => 'argument.type',
343+
'count' => 1,
344+
'path' => __DIR__ . '/src/SchemaBuilder.php',
345+
];
328346
$ignoreErrors[] = [
329347
'message' => '#^Method Jerowork\\\\GraphqlAttributeSchema\\\\Util\\\\Reflector\\\\Reflector\\:\\:getClasses\\(\\) return type with generic class ReflectionClass does not specify its types\\: T$#',
330348
'identifier' => 'missingType.generics',

src/Ast.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
use Jerowork\GraphqlAttributeSchema\Node\AliasedNode;
88
use Jerowork\GraphqlAttributeSchema\Node\ArraySerializable;
9+
use Jerowork\GraphqlAttributeSchema\Node\InterfaceTypeNode;
910
use Jerowork\GraphqlAttributeSchema\Node\Node;
11+
use Jerowork\GraphqlAttributeSchema\Node\TypeNode;
1012

1113
/**
1214
* @phpstan-type AstPayload array{
@@ -69,6 +71,28 @@ public function getNodeByClassName(string $className): ?Node
6971
return null;
7072
}
7173

74+
/**
75+
* @return list<TypeNode|InterfaceTypeNode>
76+
*/
77+
public function getNodesImplementingInterface(): array
78+
{
79+
$nodes = [];
80+
81+
foreach ($this->nodes as $node) {
82+
if (!$node instanceof InterfaceTypeNode && !$node instanceof TypeNode) {
83+
continue;
84+
}
85+
86+
if ($node->implementsInterfaces === []) {
87+
continue;
88+
}
89+
90+
$nodes[] = $node;
91+
}
92+
93+
return $nodes;
94+
}
95+
7296
/**
7397
* @return AstPayload
7498
*/

src/SchemaBuilder.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,24 @@
44

55
namespace Jerowork\GraphqlAttributeSchema;
66

7+
use GraphQL\Type\Definition\ListOfType;
8+
use GraphQL\Type\Definition\NonNull;
79
use GraphQL\Type\Definition\ObjectType;
10+
use GraphQL\Type\Definition\Type;
811
use GraphQL\Type\Schema;
912
use Jerowork\GraphqlAttributeSchema\Node\MutationNode;
1013
use Jerowork\GraphqlAttributeSchema\Node\QueryNode;
14+
use Jerowork\GraphqlAttributeSchema\Node\TypeReference\ObjectTypeReference;
15+
use Jerowork\GraphqlAttributeSchema\Resolver\BuiltTypesRegistry;
1116
use Jerowork\GraphqlAttributeSchema\Resolver\RootTypeResolver;
17+
use Jerowork\GraphqlAttributeSchema\Resolver\Type\TypeResolverSelector;
1218

1319
final readonly class SchemaBuilder
1420
{
1521
public function __construct(
1622
private AstContainer $astContainer,
23+
private BuiltTypesRegistry $builtTypesRegistry,
24+
private TypeResolverSelector $typeResolverSelector,
1725
private RootTypeResolver $rootTypeResolver,
1826
) {}
1927

@@ -45,6 +53,43 @@ public function build(Ast $ast): Schema
4553
'name' => 'Mutation',
4654
'fields' => $mutations,
4755
]),
56+
'types' => $this->getTypesImplementingInterface(),
4857
]);
4958
}
59+
60+
/**
61+
* Get all types implementing an interface.
62+
* When an implementation is not defined in a schema, it's not automatically loaded by the resolvers.
63+
* Therefore, load all interface implementation types on the root schema level as well.
64+
*
65+
* @return iterable<Closure(): Type>
66+
*/
67+
private function getTypesImplementingInterface(): iterable
68+
{
69+
foreach ($this->astContainer->getAst()->getNodesImplementingInterface() as $node) {
70+
$typeReference = ObjectTypeReference::create($node->getClassName());
71+
72+
if ($this->builtTypesRegistry->hasType($node->getClassName())) {
73+
continue;
74+
}
75+
76+
$type = $this->typeResolverSelector->getResolver($typeReference)->createType($typeReference);
77+
78+
if ($type instanceof NonNull) {
79+
$type = $type->getWrappedType();
80+
}
81+
82+
if ($type instanceof ListOfType) {
83+
$type = $type->getWrappedType();
84+
85+
if ($type instanceof NonNull) {
86+
$type = $type->getWrappedType();
87+
}
88+
}
89+
90+
$this->builtTypesRegistry->addType($node->getClassName(), $type);
91+
92+
yield fn() => $type;
93+
}
94+
}
5095
}

src/SchemaBuilderFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ public function create(
8080

8181
return new SchemaBuilder(
8282
$astContainer,
83+
$builtTypesRegistry,
84+
$typeResolverSelector,
8385
new RootTypeResolver(
8486
$typeResolverSelector,
8587
$container,

tests/AstTest.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Jerowork\GraphqlAttributeSchema\Node\Child\EnumValueNode;
1010
use Jerowork\GraphqlAttributeSchema\Node\EnumNode;
1111
use Jerowork\GraphqlAttributeSchema\Node\InputTypeNode;
12+
use Jerowork\GraphqlAttributeSchema\Node\InterfaceTypeNode;
1213
use Jerowork\GraphqlAttributeSchema\Node\MutationNode;
1314
use Jerowork\GraphqlAttributeSchema\Node\QueryNode;
1415
use Jerowork\GraphqlAttributeSchema\Node\TypeNode;
@@ -17,6 +18,8 @@
1718
use Jerowork\GraphqlAttributeSchema\Test\Doubles\Enum\TestAnotherEnumType;
1819
use Jerowork\GraphqlAttributeSchema\Test\Doubles\Enum\TestEnumType;
1920
use Jerowork\GraphqlAttributeSchema\Test\Doubles\InputType\TestInputType;
21+
use Jerowork\GraphqlAttributeSchema\Test\Doubles\InterfaceType\TestInterfaceType;
22+
use Jerowork\GraphqlAttributeSchema\Test\Doubles\InterfaceType\TestOtherInterfaceType;
2023
use Jerowork\GraphqlAttributeSchema\Test\Doubles\Mutation\TestMutation;
2124
use Jerowork\GraphqlAttributeSchema\Test\Doubles\Query\TestQuery;
2225
use Jerowork\GraphqlAttributeSchema\Test\Doubles\Type\Loader\TestTypeLoader;
@@ -34,6 +37,7 @@ final class AstTest extends TestCase
3437
private EnumNode $enumNode1;
3538
private EnumNode $enumNode2;
3639
private TypeNode $typeNode;
40+
private InterfaceTypeNode $interfaceTypeNode;
3741

3842
#[Override]
3943
protected function setUp(): void
@@ -56,7 +60,9 @@ protected function setUp(): void
5660
null,
5761
[],
5862
null,
59-
[],
63+
[
64+
TestInterfaceType::class,
65+
],
6066
),
6167
$this->enumNode2 =new EnumNode(
6268
TestAnotherEnumType::class,
@@ -73,6 +79,16 @@ protected function setUp(): void
7379
null,
7480
[],
7581
),
82+
$this->interfaceTypeNode = new InterfaceTypeNode(
83+
TestInterfaceType::class,
84+
'test',
85+
null,
86+
[],
87+
null,
88+
[
89+
TestOtherInterfaceType::class,
90+
],
91+
),
7692
);
7793
}
7894

@@ -90,6 +106,17 @@ public function itShouldGetNodeByTypeId(): void
90106
self::assertSame($this->enumNode2, $this->ast->getNodeByClassName(TestAnotherEnumType::class));
91107
}
92108

109+
#[Test]
110+
public function itShouldGetNodesImplementingAnInterface(): void
111+
{
112+
$nodes = $this->ast->getNodesImplementingInterface();
113+
114+
self::assertSame([
115+
$this->typeNode,
116+
$this->interfaceTypeNode,
117+
], $nodes);
118+
}
119+
93120
#[Test]
94121
public function itShouldSerializeAndDeserialize(): void
95122
{

0 commit comments

Comments
 (0)