Skip to content

Commit 2ed3a66

Browse files
authored
Lazy field definitions (#861)
1 parent 060ed79 commit 2ed3a66

15 files changed

+420
-136
lines changed

benchmarks/Utils/QueryGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function __construct(Schema $schema, float $percentOfLeafFields)
4141
continue;
4242
}
4343

44-
$totalFields += count($type->getFields());
44+
$totalFields += count($type->getFieldNames());
4545
}
4646

4747
$this->maxLeafFields = max(1, (int) round($totalFields * $percentOfLeafFields));

phpstan-baseline.neon

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,6 @@ parameters:
220220
count: 1
221221
path: src/Type/SchemaValidationContext.php
222222

223-
-
224-
message: "#^Only booleans are allowed in a negated boolean, array\\<GraphQL\\\\Type\\\\Definition\\\\FieldDefinition\\> given\\.$#"
225-
count: 1
226-
path: src/Type/SchemaValidationContext.php
227-
228223
-
229224
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\FieldDefinitionNode\\|null given\\.$#"
230225
count: 1
@@ -525,11 +520,6 @@ parameters:
525520
count: 1
526521
path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php
527522

528-
-
529-
message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given on the left side\\.$#"
530-
count: 1
531-
path: src/Validator/Rules/QuerySecurityRule.php
532-
533523
-
534524
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#"
535525
count: 1

src/Executor/ReferenceExecutor.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -619,9 +619,7 @@ protected function getFieldDef(Schema $schema, ObjectType $parentType, string $f
619619
return $typeNameMetaFieldDef;
620620
}
621621

622-
$tmp = $parentType->getFields();
623-
624-
return $tmp[$fieldName] ?? null;
622+
return $parentType->findField($fieldName);
625623
}
626624

627625
/**

src/Type/Definition/FieldDefinition.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,17 @@ public static function defineFieldMap(Type $type, $fields): array
128128
$fieldDef = self::create($field);
129129
} elseif ($field instanceof self) {
130130
$fieldDef = $field;
131+
} elseif (is_callable($field)) {
132+
if (! is_string($name)) {
133+
throw new InvariantViolation(
134+
sprintf(
135+
'%s lazy fields must be an associative array with field names as keys.',
136+
$type->name
137+
)
138+
);
139+
}
140+
141+
$fieldDef = new UnresolvedFieldDefinition($type, $name, $field);
131142
} else {
132143
if (! is_string($name) || ! $field) {
133144
throw new InvariantViolation(
@@ -143,7 +154,7 @@ public static function defineFieldMap(Type $type, $fields): array
143154
$fieldDef = self::create(['name' => $name, 'type' => $field]);
144155
}
145156

146-
$map[$fieldDef->name] = $fieldDef;
157+
$map[$fieldDef->getName()] = $fieldDef;
147158
}
148159

149160
return $map;
@@ -181,6 +192,11 @@ public function getArg(string $name): ?FieldArgument
181192
return null;
182193
}
183194

195+
public function getName(): string
196+
{
197+
return $this->name;
198+
}
199+
184200
public function getType(): Type
185201
{
186202
if (! isset($this->type)) {

src/Type/Definition/HasFieldsType.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace GraphQL\Type\Definition;
6+
7+
use GraphQL\Error\InvariantViolation;
8+
9+
interface HasFieldsType
10+
{
11+
/**
12+
* @throws InvariantViolation
13+
*/
14+
public function getField(string $name): FieldDefinition;
15+
16+
public function hasField(string $name): bool;
17+
18+
public function findField(string $name): ?FieldDefinition;
19+
20+
/**
21+
* @return array<string, FieldDefinition>
22+
*
23+
* @throws InvariantViolation
24+
*/
25+
public function getFields(): array;
26+
27+
/**
28+
* @return array<int, string>
29+
*
30+
* @throws InvariantViolation
31+
*/
32+
public function getFieldNames(): array;
33+
}

src/Type/Definition/InterfaceType.php

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,14 @@
1616
use function is_string;
1717
use function sprintf;
1818

19-
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType, ImplementingType
19+
class InterfaceType extends TypeWithFields implements AbstractType, OutputType, CompositeType, NullableType, NamedType, ImplementingType
2020
{
2121
/** @var InterfaceTypeDefinitionNode|null */
2222
public $astNode;
2323

2424
/** @var array<int, InterfaceTypeExtensionNode> */
2525
public $extensionASTNodes;
2626

27-
/**
28-
* Lazily initialized.
29-
*
30-
* @var array<string, FieldDefinition>
31-
*/
32-
private $fields;
33-
3427
/**
3528
* Lazily initialized.
3629
*
@@ -80,44 +73,6 @@ public static function assertInterfaceType($type): self
8073
return $type;
8174
}
8275

83-
public function getField(string $name): FieldDefinition
84-
{
85-
if (! isset($this->fields)) {
86-
$this->initializeFields();
87-
}
88-
89-
Utils::invariant(isset($this->fields[$name]), 'Field "%s" is not defined for type "%s"', $name, $this->name);
90-
91-
return $this->fields[$name];
92-
}
93-
94-
public function hasField(string $name): bool
95-
{
96-
if (! isset($this->fields)) {
97-
$this->initializeFields();
98-
}
99-
100-
return isset($this->fields[$name]);
101-
}
102-
103-
/**
104-
* @return FieldDefinition[]
105-
*/
106-
public function getFields(): array
107-
{
108-
if (! isset($this->fields)) {
109-
$this->initializeFields();
110-
}
111-
112-
return $this->fields;
113-
}
114-
115-
protected function initializeFields(): void
116-
{
117-
$fields = $this->config['fields'] ?? [];
118-
$this->fields = FieldDefinition::defineFieldMap($this, $fields);
119-
}
120-
12176
public function implementsInterface(InterfaceType $interfaceType): bool
12277
{
12378
if (! isset($this->interfaceMap)) {

src/Type/Definition/ObjectType.php

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
* }
5757
* ]);
5858
*/
59-
class ObjectType extends Type implements OutputType, CompositeType, NullableType, NamedType, ImplementingType
59+
class ObjectType extends TypeWithFields implements OutputType, CompositeType, NullableType, NamedType, ImplementingType
6060
{
6161
/** @var ObjectTypeDefinitionNode|null */
6262
public $astNode;
@@ -67,13 +67,6 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType
6767
/** @var ?callable */
6868
public $resolveFieldFn;
6969

70-
/**
71-
* Lazily initialized.
72-
*
73-
* @var FieldDefinition[]
74-
*/
75-
private $fields;
76-
7770
/**
7871
* Lazily initialized.
7972
*
@@ -124,49 +117,6 @@ public static function assertObjectType($type): self
124117
return $type;
125118
}
126119

127-
/**
128-
* @throws InvariantViolation
129-
*/
130-
public function getField(string $name): FieldDefinition
131-
{
132-
if (! isset($this->fields)) {
133-
$this->initializeFields();
134-
}
135-
136-
Utils::invariant(isset($this->fields[$name]), 'Field "%s" is not defined for type "%s"', $name, $this->name);
137-
138-
return $this->fields[$name];
139-
}
140-
141-
public function hasField(string $name): bool
142-
{
143-
if (! isset($this->fields)) {
144-
$this->initializeFields();
145-
}
146-
147-
return isset($this->fields[$name]);
148-
}
149-
150-
/**
151-
* @return FieldDefinition[]
152-
*
153-
* @throws InvariantViolation
154-
*/
155-
public function getFields(): array
156-
{
157-
if (! isset($this->fields)) {
158-
$this->initializeFields();
159-
}
160-
161-
return $this->fields;
162-
}
163-
164-
protected function initializeFields(): void
165-
{
166-
$fields = $this->config['fields'] ?? [];
167-
$this->fields = FieldDefinition::defineFieldMap($this, $fields);
168-
}
169-
170120
public function implementsInterface(InterfaceType $interfaceType): bool
171121
{
172122
if (! isset($this->interfaceMap)) {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace GraphQL\Type\Definition;
6+
7+
use GraphQL\Utils\Utils;
8+
9+
use function array_keys;
10+
11+
abstract class TypeWithFields extends Type implements HasFieldsType
12+
{
13+
/**
14+
* Lazily initialized.
15+
*
16+
* @var array<string, FieldDefinition>
17+
*/
18+
private array $fields;
19+
20+
private function initializeFields(): void
21+
{
22+
if (isset($this->fields)) {
23+
return;
24+
}
25+
26+
$fields = $this->config['fields'] ?? [];
27+
$this->fields = FieldDefinition::defineFieldMap($this, $fields);
28+
}
29+
30+
public function getField(string $name): FieldDefinition
31+
{
32+
Utils::invariant($this->hasField($name), 'Field "%s" is not defined for type "%s"', $name, $this->name);
33+
34+
return $this->findField($name);
35+
}
36+
37+
public function findField(string $name): ?FieldDefinition
38+
{
39+
$this->initializeFields();
40+
41+
if (! isset($this->fields[$name])) {
42+
return null;
43+
}
44+
45+
if ($this->fields[$name] instanceof UnresolvedFieldDefinition) {
46+
$this->fields[$name] = $this->fields[$name]->resolve();
47+
}
48+
49+
return $this->fields[$name];
50+
}
51+
52+
public function hasField(string $name): bool
53+
{
54+
$this->initializeFields();
55+
56+
return isset($this->fields[$name]);
57+
}
58+
59+
/** @inheritDoc */
60+
public function getFields(): array
61+
{
62+
$this->initializeFields();
63+
64+
foreach ($this->fields as $name => $field) {
65+
if (! ($field instanceof UnresolvedFieldDefinition)) {
66+
continue;
67+
}
68+
69+
$this->fields[$name] = $field->resolve();
70+
}
71+
72+
return $this->fields;
73+
}
74+
75+
/** @inheritDoc */
76+
public function getFieldNames(): array
77+
{
78+
$this->initializeFields();
79+
80+
return array_keys($this->fields);
81+
}
82+
}

0 commit comments

Comments
 (0)