Skip to content

Commit bc19289

Browse files
authored
Fix type registry examples (#1485)
1 parent 687f3f2 commit bc19289

File tree

3 files changed

+133
-103
lines changed

3 files changed

+133
-103
lines changed

Diff for: docs/schema-definition.md

+42-19
Original file line numberDiff line numberDiff line change
@@ -157,43 +157,66 @@ final class AuthorType extends ObjectType
157157

158158
// Types.php
159159
use GraphQL\Type\Definition\Type;
160+
use GraphQL\Type\Definition\NamedType;
160161

161162
final class Types
162163
{
163164
/** @var array<string, Type&NamedType> */
164165
private static array $types = [];
165166

166-
/** @return \Closure(): Type&NamedType */
167-
public static function get(string $classname): \Closure
167+
/** @return Type&NamedType */
168+
public static function load(string $typeName): Type
168169
{
169-
return static fn () => self::byClassName($classname);
170-
}
170+
if (isset(self::$types[$typeName])) {
171+
return self::$types[$typeName];
172+
}
171173

172-
public static function byTypeName(string $typeName): Type&NamedType
173-
{
174-
return match ($typeName) {
175-
'Boolean' => self::boolean(),
176-
'Float' => self::float(),
177-
'ID' => self::id(),
178-
'Int' => self::int(),
179-
default => self::$types[$typeName] ?? throw new \Exception("Unknown GraphQL type: {$typeName}."),
174+
// For every type, this class must define a method with the same name
175+
// but the first letter is in lower case.
176+
$methodName = match ($typeName) {
177+
'ID' => 'id',
178+
default => lcfirst($typeName),
179+
};
180+
if (! method_exists(self::class, $methodName)) {
181+
throw new \Exception("Unknown GraphQL type: {$typeName}.");
182+
}
183+
184+
$type = self::{$methodName}(); // @phpstan-ignore-line variable static method call
185+
if (is_callable($type)) {
186+
$type = $type();
180187
}
188+
189+
return self::$types[$typeName] = $type;
181190
}
182191

183-
private static function byClassName(string $classname): Type
192+
/** @return Type&NamedType */
193+
private static function byClassName(string $className): Type
184194
{
185-
$parts = \explode('\\', $classname);
186-
$typeName = \preg_replace('~Type$~', '', $parts[\count($parts) - 1]);
195+
$classNameParts = explode('\\', $className);
196+
$baseClassName = end($classNameParts);
197+
// All type classes must use the suffix Type.
198+
// This prevents name collisions between types and PHP keywords.
199+
$typeName = preg_replace('~Type$~', '', $baseClassName);
187200

188201
// Type loading is very similar to PHP class loading, but keep in mind
189202
// that the **typeLoader** must always return the same instance of a type.
190203
// We can enforce that in our type registry by caching known types.
191-
return self::$types[$typeName] ??= new $classname;
204+
return self::$types[$typeName] ??= new $className;
192205
}
193206

194-
public static function author(): callable { return self::get(AuthorType::class); }
195-
public static function story(): callable { return self::get(StoryType::class); }
207+
/** @return \Closure(): (Type&NamedType) */
208+
private static function lazyByClassName(string $className): \Closure
209+
{
210+
return static fn () => self::byClassName($className);
211+
}
196212

213+
public static function boolean(): ScalarType { return Type::boolean(); }
214+
public static function float(): ScalarType { return Type::float(); }
215+
public static function id(): ScalarType { return Type::id(); }
216+
public static function int(): ScalarType { return Type::int(); }
217+
public static function string(): ScalarType { return Type::string(); }
218+
public static function author(): callable { return self::lazyByClassName(AuthorType::class); }
219+
public static function story(): callable { return self::lazyByClassName(StoryType::class); }
197220
...
198221
}
199222

@@ -214,7 +237,7 @@ $schema = new Schema([
214237
],
215238
],
216239
]),
217-
'typeLoader' => Types::byTypeName(...),
240+
'typeLoader' => Types::load(...),
218241
]);
219242
```
220243

Diff for: examples/01-blog/Blog/Types.php

+90-83
Original file line numberDiff line numberDiff line change
@@ -29,143 +29,150 @@ final class Types
2929
/** @var array<string, Type&NamedType> */
3030
private static array $types = [];
3131

32-
public static function user(): callable
32+
/**
33+
* @throws \Exception
34+
*
35+
* @return Type&NamedType
36+
*/
37+
public static function load(string $typeName): Type
3338
{
34-
return self::get(UserType::class);
39+
if (isset(self::$types[$typeName])) {
40+
return self::$types[$typeName];
41+
}
42+
43+
// For every type, this class must define a method with the same name
44+
// but the first letter is in lower case.
45+
switch ($typeName) {
46+
case 'ID':
47+
$methodName = 'id';
48+
break;
49+
default:
50+
$methodName = \lcfirst($typeName);
51+
}
52+
if (! method_exists(self::class, $methodName)) {
53+
throw new \Exception("Unknown GraphQL type: {$typeName}.");
54+
}
55+
56+
$type = self::{$methodName}(); // @phpstan-ignore-line variable static method call
57+
if (is_callable($type)) {
58+
$type = $type();
59+
}
60+
61+
return self::$types[$typeName] = $type;
3562
}
3663

37-
public static function story(): callable
64+
/**
65+
* @param class-string<Type&NamedType> $className
66+
*
67+
* @return Type&NamedType
68+
*/
69+
private static function byClassName(string $className): Type
3870
{
39-
return self::get(StoryType::class);
71+
$classNameParts = \explode('\\', $className);
72+
$baseClassName = end($classNameParts);
73+
// All type classes must use the suffix Type.
74+
// This prevents name collisions between types and PHP keywords.
75+
$typeName = \preg_replace('~Type$~', '', $baseClassName);
76+
assert(is_string($typeName), 'regex is statically known to be correct');
77+
78+
// Type loading is very similar to PHP class loading, but keep in mind
79+
// that the **typeLoader** must always return the same instance of a type.
80+
// We can enforce that in our type registry by caching known types.
81+
return self::$types[$typeName] ??= new $className();
4082
}
4183

42-
public static function comment(): callable
84+
/**
85+
* @param class-string<Type&NamedType> $classname
86+
*
87+
* @return \Closure(): (Type&NamedType)
88+
*/
89+
private static function lazyByClassName(string $classname): \Closure
4390
{
44-
return self::get(CommentType::class);
91+
return static fn () => self::byClassName($classname);
4592
}
4693

47-
public static function image(): callable
94+
/** @throws InvariantViolation */
95+
public static function boolean(): ScalarType
4896
{
49-
return self::get(ImageType::class);
97+
return Type::boolean();
5098
}
5199

52-
public static function node(): callable
100+
/** @throws InvariantViolation */
101+
public static function float(): ScalarType
53102
{
54-
return self::get(NodeType::class);
103+
return Type::float();
55104
}
56105

57-
public static function mention(): callable
106+
/** @throws InvariantViolation */
107+
public static function id(): ScalarType
58108
{
59-
return self::get(SearchResultType::class);
109+
return Type::id();
60110
}
61111

62-
public static function imageSize(): callable
112+
/** @throws InvariantViolation */
113+
public static function int(): ScalarType
63114
{
64-
return self::get(ImageSizeType::class);
115+
return Type::int();
65116
}
66117

67-
public static function contentFormat(): callable
118+
/** @throws InvariantViolation */
119+
public static function string(): ScalarType
68120
{
69-
return self::get(ContentFormatType::class);
121+
return Type::string();
70122
}
71123

72-
public static function storyAffordances(): callable
124+
public static function user(): callable
73125
{
74-
return self::get(StoryAffordancesType::class);
126+
return self::lazyByClassName(UserType::class);
75127
}
76128

77-
public static function email(): callable
129+
public static function story(): callable
78130
{
79-
return self::get(EmailType::class);
131+
return self::lazyByClassName(StoryType::class);
80132
}
81133

82-
public static function url(): callable
134+
public static function comment(): callable
83135
{
84-
return self::get(UrlType::class);
136+
return self::lazyByClassName(CommentType::class);
85137
}
86138

87-
/**
88-
* @param class-string<Type&NamedType> $classname
89-
*
90-
* @return \Closure(): Type
91-
*/
92-
private static function get(string $classname): \Closure
139+
public static function image(): callable
93140
{
94-
return static fn () => self::byClassName($classname);
141+
return self::lazyByClassName(ImageType::class);
95142
}
96143

97-
/** @param class-string<Type&NamedType> $classname */
98-
private static function byClassName(string $classname): Type
144+
public static function node(): callable
99145
{
100-
$parts = \explode('\\', $classname);
101-
102-
$withoutTypePrefix = \preg_replace('~Type$~', '', $parts[\count($parts) - 1]);
103-
assert(is_string($withoutTypePrefix), 'regex is statically known to be correct');
104-
105-
$cacheName = \strtolower($withoutTypePrefix);
106-
107-
if (! isset(self::$types[$cacheName])) {
108-
return self::$types[$cacheName] = new $classname();
109-
}
110-
111-
return self::$types[$cacheName];
146+
return self::lazyByClassName(NodeType::class);
112147
}
113148

114-
/**
115-
* @throws \Exception
116-
*
117-
* @return Type&NamedType
118-
*/
119-
public static function byTypeName(string $shortName): Type
149+
public static function mention(): callable
120150
{
121-
$cacheName = \strtolower($shortName);
122-
123-
if (isset(self::$types[$cacheName])) {
124-
return self::$types[$cacheName];
125-
}
126-
127-
$method = \lcfirst($shortName);
128-
switch ($method) {
129-
case 'boolean':
130-
return self::boolean();
131-
case 'float':
132-
return self::float();
133-
case 'id':
134-
return self::id();
135-
case 'int':
136-
return self::int();
137-
}
138-
139-
throw new \Exception("Unknown graphql type: {$shortName}");
151+
return self::lazyByClassName(SearchResultType::class);
140152
}
141153

142-
/** @throws InvariantViolation */
143-
public static function boolean(): ScalarType
154+
public static function imageSize(): callable
144155
{
145-
return Type::boolean();
156+
return self::lazyByClassName(ImageSizeType::class);
146157
}
147158

148-
/** @throws InvariantViolation */
149-
public static function float(): ScalarType
159+
public static function contentFormat(): callable
150160
{
151-
return Type::float();
161+
return self::lazyByClassName(ContentFormatType::class);
152162
}
153163

154-
/** @throws InvariantViolation */
155-
public static function id(): ScalarType
164+
public static function storyAffordances(): callable
156165
{
157-
return Type::id();
166+
return self::lazyByClassName(StoryAffordancesType::class);
158167
}
159168

160-
/** @throws InvariantViolation */
161-
public static function int(): ScalarType
169+
public static function email(): callable
162170
{
163-
return Type::int();
171+
return self::lazyByClassName(EmailType::class);
164172
}
165173

166-
/** @throws InvariantViolation */
167-
public static function string(): ScalarType
174+
public static function url(): callable
168175
{
169-
return Type::string();
176+
return self::lazyByClassName(UrlType::class);
170177
}
171178
}

Diff for: examples/01-blog/graphql.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
$schema = new Schema(
2626
(new SchemaConfig())
2727
->setQuery(new QueryType())
28-
->setTypeLoader([Types::class, 'byTypename'])
28+
->setTypeLoader([Types::class, 'load'])
2929
);
3030

3131
// Prepare context that will be available in all field resolvers (as 3rd argument):

0 commit comments

Comments
 (0)