@@ -29,143 +29,150 @@ final class Types
29
29
/** @var array<string, Type&NamedType> */
30
30
private static array $ types = [];
31
31
32
- public static function user (): callable
32
+ /**
33
+ * @throws \Exception
34
+ *
35
+ * @return Type&NamedType
36
+ */
37
+ public static function load (string $ typeName ): Type
33
38
{
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 ;
35
62
}
36
63
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
38
70
{
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 ();
40
82
}
41
83
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
43
90
{
44
- return self ::get (CommentType::class );
91
+ return static fn () => self ::byClassName ( $ classname );
45
92
}
46
93
47
- public static function image (): callable
94
+ /** @throws InvariantViolation */
95
+ public static function boolean (): ScalarType
48
96
{
49
- return self :: get (ImageType::class );
97
+ return Type:: boolean ( );
50
98
}
51
99
52
- public static function node (): callable
100
+ /** @throws InvariantViolation */
101
+ public static function float (): ScalarType
53
102
{
54
- return self :: get (NodeType::class );
103
+ return Type:: float ( );
55
104
}
56
105
57
- public static function mention (): callable
106
+ /** @throws InvariantViolation */
107
+ public static function id (): ScalarType
58
108
{
59
- return self :: get (SearchResultType::class );
109
+ return Type:: id ( );
60
110
}
61
111
62
- public static function imageSize (): callable
112
+ /** @throws InvariantViolation */
113
+ public static function int (): ScalarType
63
114
{
64
- return self :: get (ImageSizeType::class );
115
+ return Type:: int ( );
65
116
}
66
117
67
- public static function contentFormat (): callable
118
+ /** @throws InvariantViolation */
119
+ public static function string (): ScalarType
68
120
{
69
- return self :: get (ContentFormatType::class );
121
+ return Type:: string ( );
70
122
}
71
123
72
- public static function storyAffordances (): callable
124
+ public static function user (): callable
73
125
{
74
- return self ::get (StoryAffordancesType ::class);
126
+ return self ::lazyByClassName (UserType ::class);
75
127
}
76
128
77
- public static function email (): callable
129
+ public static function story (): callable
78
130
{
79
- return self ::get (EmailType ::class);
131
+ return self ::lazyByClassName (StoryType ::class);
80
132
}
81
133
82
- public static function url (): callable
134
+ public static function comment (): callable
83
135
{
84
- return self ::get (UrlType ::class);
136
+ return self ::lazyByClassName (CommentType ::class);
85
137
}
86
138
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
93
140
{
94
- return static fn () => self ::byClassName ( $ classname );
141
+ return self ::lazyByClassName (ImageType::class );
95
142
}
96
143
97
- /** @param class-string<Type&NamedType> $classname */
98
- private static function byClassName (string $ classname ): Type
144
+ public static function node (): callable
99
145
{
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);
112
147
}
113
148
114
- /**
115
- * @throws \Exception
116
- *
117
- * @return Type&NamedType
118
- */
119
- public static function byTypeName (string $ shortName ): Type
149
+ public static function mention (): callable
120
150
{
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);
140
152
}
141
153
142
- /** @throws InvariantViolation */
143
- public static function boolean (): ScalarType
154
+ public static function imageSize (): callable
144
155
{
145
- return Type:: boolean ( );
156
+ return self :: lazyByClassName (ImageSizeType::class );
146
157
}
147
158
148
- /** @throws InvariantViolation */
149
- public static function float (): ScalarType
159
+ public static function contentFormat (): callable
150
160
{
151
- return Type:: float ( );
161
+ return self :: lazyByClassName (ContentFormatType::class );
152
162
}
153
163
154
- /** @throws InvariantViolation */
155
- public static function id (): ScalarType
164
+ public static function storyAffordances (): callable
156
165
{
157
- return Type:: id ( );
166
+ return self :: lazyByClassName (StoryAffordancesType::class );
158
167
}
159
168
160
- /** @throws InvariantViolation */
161
- public static function int (): ScalarType
169
+ public static function email (): callable
162
170
{
163
- return Type:: int ( );
171
+ return self :: lazyByClassName (EmailType::class );
164
172
}
165
173
166
- /** @throws InvariantViolation */
167
- public static function string (): ScalarType
174
+ public static function url (): callable
168
175
{
169
- return Type:: string ( );
176
+ return self :: lazyByClassName (UrlType::class );
170
177
}
171
178
}
0 commit comments