Skip to content

Commit 3e067cc

Browse files
committed
Readd type decorator and fix lazy type loading
1 parent f9a366e commit 3e067cc

File tree

4 files changed

+208
-16
lines changed

4 files changed

+208
-16
lines changed

src/Type/Schema.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public function __construct($config)
134134
if ($config->subscription) {
135135
$this->resolvedTypes[$config->subscription->name] = $config->subscription;
136136
}
137-
if ($this->config->types) {
137+
if (is_array($this->config->types)) {
138138
foreach ($this->resolveAdditionalTypes() as $type) {
139139
if (isset($this->resolvedTypes[$type->name])) {
140140
Utils::invariant(

src/Utils/ASTDefinitionBuilder.php

+65-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ class ASTDefinitionBuilder
3838
*/
3939
private $typeDefintionsMap;
4040

41+
/**
42+
* @var callable
43+
*/
44+
private $typeConfigDecorator;
45+
4146
/**
4247
* @var array
4348
*/
@@ -53,9 +58,10 @@ class ASTDefinitionBuilder
5358
*/
5459
private $cache;
5560

56-
public function __construct(array $typeDefintionsMap, $options, callable $resolveType)
61+
public function __construct(array $typeDefintionsMap, $options, callable $resolveType, callable $typeConfigDecorator = null)
5762
{
5863
$this->typeDefintionsMap = $typeDefintionsMap;
64+
$this->typeConfigDecorator = $typeConfigDecorator;
5965
$this->options = $options;
6066
$this->resolveType = $resolveType;
6167

@@ -101,7 +107,41 @@ private function getNamedTypeNode(TypeNode $typeNode)
101107
private function internalBuildType($typeName, $typeNode = null) {
102108
if (!isset($this->cache[$typeName])) {
103109
if (isset($this->typeDefintionsMap[$typeName])) {
104-
$this->cache[$typeName] = $this->makeSchemaDef($this->typeDefintionsMap[$typeName]);
110+
$type = $this->makeSchemaDef($this->typeDefintionsMap[$typeName]);
111+
if ($this->typeConfigDecorator) {
112+
$fn = $this->typeConfigDecorator;
113+
try {
114+
$config = $fn($type->config, $this->typeDefintionsMap[$typeName], $this->typeDefintionsMap);
115+
} catch (\Exception $e) {
116+
throw new Error(
117+
"Type config decorator passed to " . (static::class) . " threw an error " .
118+
"when building $typeName type: {$e->getMessage()}",
119+
null,
120+
null,
121+
null,
122+
null,
123+
$e
124+
);
125+
} catch (\Throwable $e) {
126+
throw new Error(
127+
"Type config decorator passed to " . (static::class) . " threw an error " .
128+
"when building $typeName type: {$e->getMessage()}",
129+
null,
130+
null,
131+
null,
132+
null,
133+
$e
134+
);
135+
}
136+
if (!is_array($config) || isset($config[0])) {
137+
throw new Error(
138+
"Type config decorator passed to " . (static::class) . " is expected to return an array, but got " .
139+
Utils::getVariableType($config)
140+
);
141+
}
142+
$type = $this->makeSchemaDefFromConfig($this->typeDefintionsMap[$typeName], $config);
143+
}
144+
$this->cache[$typeName] = $type;
105145
} else {
106146
$fn = $this->resolveType;
107147
$this->cache[$typeName] = $fn($typeName, $typeNode);
@@ -186,6 +226,29 @@ private function makeSchemaDef($def)
186226
}
187227
}
188228

229+
private function makeSchemaDefFromConfig($def, array $config)
230+
{
231+
if (!$def) {
232+
throw new Error('def must be defined.');
233+
}
234+
switch ($def->kind) {
235+
case NodeKind::OBJECT_TYPE_DEFINITION:
236+
return new ObjectType($config);
237+
case NodeKind::INTERFACE_TYPE_DEFINITION:
238+
return new InterfaceType($config);
239+
case NodeKind::ENUM_TYPE_DEFINITION:
240+
return new EnumType($config);
241+
case NodeKind::UNION_TYPE_DEFINITION:
242+
return new UnionType($config);
243+
case NodeKind::SCALAR_TYPE_DEFINITION:
244+
return new CustomScalarType($config);
245+
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
246+
return new InputObjectType($config);
247+
default:
248+
throw new Error("Type kind of {$def->kind} not supported.");
249+
}
250+
}
251+
189252
private function makeTypeDef(ObjectTypeDefinitionNode $def)
190253
{
191254
$typeName = $def->name->value;

src/Utils/BuildSchema.php

+13-12
Original file line numberDiff line numberDiff line change
@@ -26,33 +26,34 @@ class BuildSchema
2626
* Given that AST it constructs a GraphQL\Type\Schema. The resulting schema
2727
* has no resolve methods, so execution will use default resolvers.
2828
*
29-
* Accepts options as a second argument:
29+
* Accepts options as a third argument:
3030
*
3131
* - commentDescriptions:
3232
* Provide true to use preceding comments as the description.
3333
*
3434
*
3535
* @api
3636
* @param DocumentNode $ast
37+
* @param callable $typeConfigDecorator
3738
* @param array $options
3839
* @return Schema
3940
* @throws Error
4041
*/
41-
public static function buildAST(DocumentNode $ast, array $options = [])
42+
public static function buildAST(DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [])
4243
{
43-
$builder = new self($ast, $options);
44+
$builder = new self($ast, $typeConfigDecorator, $options);
4445
return $builder->buildSchema();
4546
}
4647

4748
private $ast;
4849
private $nodeMap;
49-
private $loadedTypeDefs;
50+
private $typeConfigDecorator;
5051
private $options;
5152

52-
public function __construct(DocumentNode $ast, array $options = [])
53+
public function __construct(DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [])
5354
{
5455
$this->ast = $ast;
55-
$this->loadedTypeDefs = [];
56+
$this->typeConfigDecorator = $typeConfigDecorator;
5657
$this->options = $options;
5758
}
5859

@@ -101,7 +102,8 @@ public function buildSchema()
101102
$defintionBuilder = new ASTDefinitionBuilder(
102103
$this->nodeMap,
103104
$this->options,
104-
function($typeName) { throw new Error('Type "'. $typeName . '" not found in document.'); }
105+
function($typeName) { throw new Error('Type "'. $typeName . '" not found in document.'); },
106+
$this->typeConfigDecorator
105107
);
106108

107109
$directives = array_map(function($def) use ($defintionBuilder) {
@@ -152,9 +154,7 @@ function($typeName) { throw new Error('Type "'. $typeName . '" not found in docu
152154
'types' => function () use ($defintionBuilder) {
153155
$types = [];
154156
foreach ($this->nodeMap as $name => $def) {
155-
if (!isset($this->loadedTypeDefs[$name])) {
156-
$types[] = $defintionBuilder->buildType($def->name->value);
157-
}
157+
$types[] = $defintionBuilder->buildType($def->name->value);
158158
}
159159
return $types;
160160
}
@@ -196,12 +196,13 @@ private function getOperationTypes($schemaDef)
196196
*
197197
* @api
198198
* @param DocumentNode|Source|string $source
199+
* @param callable $typeConfigDecorator
199200
* @param array $options
200201
* @return Schema
201202
*/
202-
public static function build($source, array $options = [])
203+
public static function build($source, callable $typeConfigDecorator = null, array $options = [])
203204
{
204205
$doc = $source instanceof DocumentNode ? $source : Parser::parse($source);
205-
return self::buildAST($doc, $options);
206+
return self::buildAST($doc, $typeConfigDecorator, $options);
206207
}
207208
}

tests/Utils/BuildSchemaTest.php

+129-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class BuildSchemaTest extends \PHPUnit_Framework_TestCase
2020
private function cycleOutput($body, $options = [])
2121
{
2222
$ast = Parser::parse($body);
23-
$schema = BuildSchema::buildAST($ast, $options);
23+
$schema = BuildSchema::buildAST($ast, null, $options);
2424
return "\n" . SchemaPrinter::doPrint($schema, $options);
2525
}
2626

@@ -1140,4 +1140,132 @@ public function testForbidsDuplicateTypeDefinitions()
11401140
$this->setExpectedException('GraphQL\Error\Error', 'Type "Repeated" was defined more than once.');
11411141
BuildSchema::buildAST($doc);
11421142
}
1143+
1144+
public function testSupportsTypeConfigDecorator()
1145+
{
1146+
$body = '
1147+
schema {
1148+
query: Query
1149+
}
1150+
1151+
type Query {
1152+
str: String
1153+
color: Color
1154+
hello: Hello
1155+
}
1156+
1157+
enum Color {
1158+
RED
1159+
GREEN
1160+
BLUE
1161+
}
1162+
1163+
interface Hello {
1164+
world: String
1165+
}
1166+
';
1167+
$doc = Parser::parse($body);
1168+
1169+
$decorated = [];
1170+
$calls = [];
1171+
1172+
$typeConfigDecorator = function($defaultConfig, $node, $allNodesMap) use (&$decorated, &$calls) {
1173+
$decorated[] = $defaultConfig['name'];
1174+
$calls[] = [$defaultConfig, $node, $allNodesMap];
1175+
return ['description' => 'My description of ' . $node->name->value] + $defaultConfig;
1176+
};
1177+
1178+
$schema = BuildSchema::buildAST($doc, $typeConfigDecorator);
1179+
$schema->getTypeMap();
1180+
$this->assertEquals(['Query', 'Color', 'Hello'], $decorated);
1181+
1182+
list($defaultConfig, $node, $allNodesMap) = $calls[0];
1183+
$this->assertInstanceOf(ObjectTypeDefinitionNode::class, $node);
1184+
$this->assertEquals('Query', $defaultConfig['name']);
1185+
$this->assertInstanceOf(\Closure::class, $defaultConfig['fields']);
1186+
$this->assertInstanceOf(\Closure::class, $defaultConfig['interfaces']);
1187+
$this->assertArrayHasKey('description', $defaultConfig);
1188+
$this->assertCount(5, $defaultConfig);
1189+
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
1190+
$this->assertEquals('My description of Query', $schema->getType('Query')->description);
1191+
1192+
1193+
list($defaultConfig, $node, $allNodesMap) = $calls[1];
1194+
$this->assertInstanceOf(EnumTypeDefinitionNode::class, $node);
1195+
$this->assertEquals('Color', $defaultConfig['name']);
1196+
$enumValue = [
1197+
'description' => '',
1198+
'deprecationReason' => ''
1199+
];
1200+
$this->assertArraySubset([
1201+
'RED' => $enumValue,
1202+
'GREEN' => $enumValue,
1203+
'BLUE' => $enumValue,
1204+
], $defaultConfig['values']);
1205+
$this->assertCount(4, $defaultConfig); // 3 + astNode
1206+
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
1207+
$this->assertEquals('My description of Color', $schema->getType('Color')->description);
1208+
1209+
list($defaultConfig, $node, $allNodesMap) = $calls[2];
1210+
$this->assertInstanceOf(InterfaceTypeDefinitionNode::class, $node);
1211+
$this->assertEquals('Hello', $defaultConfig['name']);
1212+
$this->assertInstanceOf(\Closure::class, $defaultConfig['fields']);
1213+
$this->assertArrayHasKey('description', $defaultConfig);
1214+
$this->assertCount(4, $defaultConfig);
1215+
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
1216+
$this->assertEquals('My description of Hello', $schema->getType('Hello')->description);
1217+
}
1218+
1219+
public function testCreatesTypesLazily()
1220+
{
1221+
$body = '
1222+
schema {
1223+
query: Query
1224+
}
1225+
1226+
type Query {
1227+
str: String
1228+
color: Color
1229+
hello: Hello
1230+
}
1231+
1232+
enum Color {
1233+
RED
1234+
GREEN
1235+
BLUE
1236+
}
1237+
1238+
interface Hello {
1239+
world: String
1240+
}
1241+
1242+
type World implements Hello {
1243+
world: String
1244+
}
1245+
';
1246+
$doc = Parser::parse($body);
1247+
$created = [];
1248+
1249+
$typeConfigDecorator = function($config, $node) use (&$created) {
1250+
$created[] = $node->name->value;
1251+
return $config;
1252+
};
1253+
1254+
$schema = BuildSchema::buildAST($doc, $typeConfigDecorator);
1255+
$this->assertEquals(['Query'], $created);
1256+
1257+
$schema->getType('Color');
1258+
$this->assertEquals(['Query', 'Color'], $created);
1259+
1260+
$schema->getType('Hello');
1261+
$this->assertEquals(['Query', 'Color', 'Hello'], $created);
1262+
1263+
$types = $schema->getTypeMap();
1264+
$this->assertEquals(['Query', 'Color', 'Hello', 'World'], $created);
1265+
$this->assertArrayHasKey('Query', $types);
1266+
$this->assertArrayHasKey('Color', $types);
1267+
$this->assertArrayHasKey('Hello', $types);
1268+
$this->assertArrayHasKey('World', $types);
1269+
}
1270+
11431271
}

0 commit comments

Comments
 (0)