Skip to content

Commit 0556d05

Browse files
committed
Lazy load types and fields
Improves performance in general, but also allows circular type references (e.g. Blog > Author > Blog).
1 parent 3d918ef commit 0556d05

14 files changed

+782
-1567
lines changed

phpstan-baseline.php

Lines changed: 14 additions & 32 deletions
Large diffs are not rendered by default.

src/Resolver/RootTypeResolver.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ public function __construct(
3737
* @return array{
3838
* name: string,
3939
* description: null|string,
40-
* type: Type,
40+
* type: Closure(): Type,
4141
* args: list<array{
4242
* name: string,
4343
* description: null|string,
44-
* type: Type
44+
* type: Closure(): Type
4545
* }>,
4646
* resolve: Closure,
4747
* deprecationReason?: string
@@ -54,7 +54,7 @@ public function createType(MutationNode|QueryNode $node): array
5454
$rootType = [
5555
'name' => $node->name,
5656
'description' => $node->description,
57-
'type' => $typeResolver->createType($node->outputReference),
57+
'type' => fn() => $typeResolver->createType($node->outputReference),
5858
'args' => [...$this->createArgs($node), ...$this->fieldResolver->getConnectionArgs($node->outputReference)],
5959
'resolve' => $this->resolve($node),
6060
];
@@ -70,7 +70,7 @@ public function createType(MutationNode|QueryNode $node): array
7070
* @return list<array{
7171
* name: string,
7272
* description: null|string,
73-
* type: Type
73+
* type: Closure(): Type
7474
* }>
7575
*/
7676
private function createArgs(MutationNode|QueryNode $node): array
@@ -79,7 +79,7 @@ private function createArgs(MutationNode|QueryNode $node): array
7979
fn(ArgNode $argNode) => [
8080
'name' => $argNode->name,
8181
'description' => $argNode->description,
82-
'type' => $this->typeResolverSelector
82+
'type' => fn() => $this->typeResolverSelector
8383
->getResolver($argNode->reference)
8484
->createType($argNode->reference),
8585
],

src/Resolver/Type/Connection/EdgeTypeResolver.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,17 @@ public function createEdgeType(
5858

5959
$edge = Type::nonNull(new ObjectType([
6060
'name' => $edgeName,
61-
'fields' => [
61+
'fields' => fn() => [
6262
[
6363
'name' => 'node',
64-
'type' => $typeResolverSelector
64+
'type' => fn() => $typeResolverSelector
6565
->getResolver(ObjectTypeReference::create($reference->className))
6666
->createType(ObjectTypeReference::create($reference->className)),
6767
'resolve' => fn($objectValue) => $objectValue,
6868
],
6969
[
7070
'name' => 'cursor',
71-
'type' => $cursorNode?->reference->isValueNullable() !== false ? Type::string() : Type::nonNull(Type::string()),
71+
'type' => fn() => $cursorNode?->reference->isValueNullable() !== false ? Type::string() : Type::nonNull(Type::string()),
7272
'resolve' => $cursorNode !== null ? $this->resolveCursor($node, $cursorNode, $typeResolverSelector) : fn() => null,
7373
],
7474
],

src/Resolver/Type/ConnectionTypeResolver.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ public function createType(TypeReference $reference): Type
6767

6868
$connection = new ObjectType([
6969
'name' => $connectionName,
70-
'fields' => [
70+
'fields' => fn() => [
7171
[
7272
'name' => 'edges',
73-
'type' => Type::nonNull(Type::listOf(
73+
'type' => fn() => Type::nonNull(Type::listOf(
7474
$this->edgeTypeResolver->createEdgeType($reference, $node, $this->getTypeResolverSelector()),
7575
)),
7676
'resolve' => fn(Connection $connection) => $connection,

src/Resolver/Type/FieldResolver.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ public function __construct(
3535
* @return list<array{
3636
* name: string,
3737
* description: null|string,
38-
* type: Type,
38+
* type: Closure(): Type,
3939
* args: list<array{
4040
* name: string,
4141
* description: null|string,
42-
* type: Type
42+
* type: Closure(): Type
4343
* }>,
4444
* resolve: Closure,
4545
* deprecationReason?: string
@@ -56,7 +56,7 @@ public function getFields(array $fieldNodes, TypeResolverSelector $typeResolverS
5656
$field = [
5757
'name' => $fieldNode->name,
5858
'description' => $fieldNode->description,
59-
'type' => $type,
59+
'type' => fn() => $type,
6060
'args' => [...$this->getArgs($fieldNode, $typeResolverSelector), ...$this->getConnectionArgs($fieldNode->reference)],
6161
'resolve' => $this->resolveField($fieldNode, $typeResolverSelector),
6262
];
@@ -75,7 +75,7 @@ public function getFields(array $fieldNodes, TypeResolverSelector $typeResolverS
7575
* @return list<array{
7676
* name: string,
7777
* description: null|string,
78-
* type: Type
78+
* type: Closure(): Type
7979
* }>
8080
*/
8181
public function getArgs(FieldNode $fieldNode, TypeResolverSelector $typeResolverSelector): array
@@ -91,7 +91,7 @@ public function getArgs(FieldNode $fieldNode, TypeResolverSelector $typeResolver
9191
$args[] = [
9292
'name' => $argumentNode->name,
9393
'description' => $argumentNode->description,
94-
'type' => $typeResolver->createType($argumentNode->reference),
94+
'type' => fn() => $typeResolver->createType($argumentNode->reference),
9595
];
9696
}
9797

src/Resolver/Type/InputObjectTypeResolver.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public function createType(TypeReference $reference): InputObjectType
4646
return new InputObjectType([
4747
'name' => $node->name,
4848
'description' => $node->description,
49-
'fields' => $this->getFields($node),
49+
'fields' => fn() => $this->getFields($node),
5050
]);
5151
}
5252

@@ -103,11 +103,11 @@ public function abstract(ArgumentNode|FieldNode $node, array $args): mixed
103103
* @return list<array{
104104
* name: string,
105105
* description: null|string,
106-
* type: Type,
106+
* type: Closure(): Type,
107107
* args: list<array{
108108
* name: string,
109109
* description: null|string,
110-
* type: Type
110+
* type: Closure(): Type
111111
* }>
112112
* }>
113113
*/
@@ -122,7 +122,7 @@ private function getFields(InputTypeNode $node): array
122122
$fields[] = [
123123
'name' => $fieldNode->name,
124124
'description' => $fieldNode->description,
125-
'type' => $type,
125+
'type' => fn() => $type,
126126
'args' => $this->fieldResolver->getArgs($fieldNode, $this->getTypeResolverSelector()),
127127
];
128128
}

src/Resolver/Type/InterfaceTypeResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public function createType(TypeReference $reference): InterfaceType
5050
return new InterfaceType([
5151
'name' => $node->name,
5252
'description' => $node->description,
53-
'fields' => $this->fieldResolver->getFields($node->fieldNodes, $this->getTypeResolverSelector()),
53+
'fields' => fn() => $this->fieldResolver->getFields($node->fieldNodes, $this->getTypeResolverSelector()),
5454
'resolveType' => fn(object $objectValue) => $this->builtTypesRegistry->getType($objectValue::class),
5555
]);
5656
}

src/Resolver/Type/ObjectTypeResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public function createType(TypeReference $reference): ObjectType
5252
$objectTypeConfig = [
5353
'name' => $node->name,
5454
'description' => $node->description,
55-
'fields' => $this->fieldResolver->getFields(
55+
'fields' => fn() => $this->fieldResolver->getFields(
5656
$this->getCombinedFieldNodesFromInterfaceAndNode($node),
5757
$this->getTypeResolverSelector(),
5858
),

tests/AssertSchemaConfig.php

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Jerowork\GraphqlAttributeSchema\Test;
6+
7+
use GraphQL\Type\Definition\Argument;
8+
use GraphQL\Type\Definition\EnumType;
9+
use GraphQL\Type\Definition\EnumValueDefinition;
10+
use GraphQL\Type\Definition\FieldDefinition;
11+
use GraphQL\Type\Definition\InputObjectField;
12+
use GraphQL\Type\Definition\InputObjectType;
13+
use GraphQL\Type\Definition\InterfaceType;
14+
use GraphQL\Type\Definition\ObjectType;
15+
use GraphQL\Type\Definition\UnionType;
16+
use PHPUnit\Framework\TestCase;
17+
18+
final readonly class AssertSchemaConfig
19+
{
20+
/**
21+
* @param array<string, mixed> $expected
22+
*/
23+
public static function assertObjectType(array $expected, ?object $type, ?bool $sortFields = false): void
24+
{
25+
TestCase::assertInstanceOf(ObjectType::class, $type);
26+
27+
$fields = array_map(
28+
fn(FieldDefinition $field) => [
29+
'name' => $field->name,
30+
'type' => $field->getType()->toString(),
31+
'description' => $field->description,
32+
'deprecationReason' => $field->deprecationReason,
33+
'args' => array_map(
34+
fn(Argument $arg) => [
35+
'name' => $arg->name,
36+
'type' => $arg->getType()->toString(),
37+
'description' => $arg->description,
38+
'deprecationReason' => $arg->deprecationReason,
39+
],
40+
$field->args,
41+
),
42+
],
43+
$type->getFields(),
44+
);
45+
46+
// Sort fields as with Queries and Mutations the order can differ on different environments (file loading)
47+
if ($sortFields) {
48+
ksort($fields);
49+
}
50+
51+
TestCase::assertSame($expected, [
52+
'name' => $type->name,
53+
'description' => $type->description,
54+
'fields' => array_values($fields),
55+
]);
56+
}
57+
58+
/**
59+
* @param array<string, mixed> $expected
60+
*/
61+
public static function assertInputObjectType(array $expected, ?object $type): void
62+
{
63+
TestCase::assertInstanceOf(InputObjectType::class, $type);
64+
TestCase::assertSame($expected, [
65+
'name' => $type->name,
66+
'description' => $type->description,
67+
'fields' => array_values(array_map(
68+
fn(InputObjectField $field) => [
69+
'name' => $field->name,
70+
'type' => $field->getType()->toString(),
71+
'description' => $field->description,
72+
'deprecationReason' => $field->deprecationReason,
73+
],
74+
$type->getFields(),
75+
)),
76+
]);
77+
}
78+
79+
/**
80+
* @param array<string, mixed> $expected
81+
*/
82+
public static function assertInterfaceType(array $expected, ?object $type): void
83+
{
84+
TestCase::assertInstanceOf(InterfaceType::class, $type);
85+
TestCase::assertSame($expected, [
86+
'name' => $type->name,
87+
'description' => $type->description,
88+
'fields' => array_values(array_map(
89+
fn(FieldDefinition $field) => [
90+
'name' => $field->name,
91+
'type' => $field->getType()->toString(),
92+
'description' => $field->description,
93+
'deprecationReason' => $field->deprecationReason,
94+
'args' => array_map(
95+
fn(Argument $arg) => [
96+
'name' => $arg->name,
97+
'type' => $arg->getType()->toString(),
98+
'description' => $arg->description,
99+
'deprecationReason' => $arg->deprecationReason,
100+
],
101+
$field->args,
102+
),
103+
],
104+
$type->getFields(),
105+
)),
106+
]);
107+
}
108+
109+
/**
110+
* @param array<string, mixed> $expected
111+
*/
112+
public static function assertUnionType(array $expected, ?object $type): void
113+
{
114+
TestCase::assertInstanceOf(UnionType::class, $type);
115+
TestCase::assertSame($expected, [
116+
'name' => $type->name,
117+
'description' => $type->description,
118+
]);
119+
}
120+
121+
/**
122+
* @param array<string, mixed> $expected
123+
*/
124+
public static function assertEnumType(array $expected, ?object $type): void
125+
{
126+
TestCase::assertInstanceOf(EnumType::class, $type);
127+
TestCase::assertSame($expected, [
128+
'name' => $type->name,
129+
'description' => $type->description,
130+
'values' => array_values(array_map(
131+
fn(EnumValueDefinition $value) => [
132+
'name' => $value->name,
133+
'description' => $value->description,
134+
'deprecationReason' => $value->deprecationReason,
135+
],
136+
$type->getValues(),
137+
)),
138+
]);
139+
}
140+
}

tests/Doubles/FullFeatured/Type/AgentType.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,10 @@ public function getParentRecipient(): string
5050
{
5151
return '';
5252
}
53+
54+
#[Field]
55+
public function getFoobar(): FoobarType
56+
{
57+
return new FoobarType('', null);
58+
}
5359
}

0 commit comments

Comments
 (0)