1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace GraphQL\Doctrine; |
6
|
|
|
|
7
|
|
|
use Doctrine\Common\Annotations\AnnotationRegistry; |
8
|
|
|
use Doctrine\ORM\EntityManager; |
9
|
|
|
use GraphQL\Doctrine\Definition\EntityIDType; |
10
|
|
|
use GraphQL\Doctrine\Factory\InputTypeFactory; |
11
|
|
|
use GraphQL\Doctrine\Factory\ObjectTypeFactory; |
12
|
|
|
use GraphQL\Doctrine\Factory\PartialInputTypeFactory; |
13
|
|
|
use GraphQL\Type\Definition\InputObjectType; |
14
|
|
|
use GraphQL\Type\Definition\ObjectType; |
15
|
|
|
use GraphQL\Type\Definition\Type; |
16
|
|
|
use Psr\Container\ContainerInterface; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Registry of types to manage all GraphQL types |
20
|
|
|
* |
21
|
|
|
* This is the entry point for the library. |
22
|
|
|
*/ |
23
|
|
|
class Types |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* @var null|ContainerInterface |
27
|
|
|
*/ |
28
|
|
|
private $customTypes; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var array mapping of type name to type instances |
32
|
|
|
*/ |
33
|
|
|
private $types = []; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var ObjectTypeFactory |
37
|
|
|
*/ |
38
|
|
|
private $objectTypeFactory; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var InputTypeFactory |
42
|
|
|
*/ |
43
|
|
|
private $inputTypeFactory; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var PartialInputTypeFactory |
47
|
|
|
*/ |
48
|
|
|
private $partialInputTypeFactory; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var EntityManager |
52
|
|
|
*/ |
53
|
|
|
private $entityManager; |
54
|
|
|
|
55
|
24 |
|
public function __construct(EntityManager $entityManager, ?ContainerInterface $customTypes = null) |
56
|
|
|
{ |
57
|
24 |
|
$this->customTypes = $customTypes; |
58
|
24 |
|
$this->types = $this->getPhpToGraphQLMapping(); |
59
|
24 |
|
$this->entityManager = $entityManager; |
60
|
24 |
|
$this->objectTypeFactory = new ObjectTypeFactory($this, $entityManager); |
61
|
24 |
|
$this->inputTypeFactory = new InputTypeFactory($this, $entityManager); |
62
|
24 |
|
$this->partialInputTypeFactory = new PartialInputTypeFactory($this, $entityManager); |
63
|
|
|
|
64
|
24 |
|
$entityManager->getConfiguration()->newDefaultAnnotationDriver(); |
65
|
24 |
|
AnnotationRegistry::registerLoader('class_exists'); |
|
|
|
|
66
|
24 |
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Returns whether a type exists for the given key |
70
|
|
|
* |
71
|
|
|
* @param string $key |
72
|
|
|
* |
73
|
|
|
* @return bool |
74
|
|
|
*/ |
75
|
1 |
|
public function has(string $key): bool |
76
|
|
|
{ |
77
|
1 |
|
return $this->customTypes && $this->customTypes->has($key) || array_key_exists($key, $this->types); |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Always return the same instance of `Type` for the given key |
82
|
|
|
* |
83
|
|
|
* It will first look for the type in the custom types container, and then |
84
|
|
|
* use automatically generated types. This allow for custom types to override |
85
|
|
|
* automatic ones. |
86
|
|
|
* |
87
|
|
|
* @param string $key the key the type was registered with (eg: "Post", "PostInput", "PostPartialInput" or "PostStatus") |
88
|
|
|
* |
89
|
|
|
* @return Type |
90
|
|
|
*/ |
91
|
14 |
|
public function get(string $key): Type |
92
|
|
|
{ |
93
|
14 |
|
if ($this->customTypes && $this->customTypes->has($key)) { |
94
|
12 |
|
$t = $this->customTypes->get($key); |
95
|
12 |
|
$this->registerInstance($t); |
96
|
|
|
|
97
|
12 |
|
return $t; |
98
|
|
|
} |
99
|
|
|
|
100
|
10 |
|
if (array_key_exists($key, $this->types)) { |
101
|
9 |
|
return $this->types[$key]; |
102
|
|
|
} |
103
|
|
|
|
104
|
1 |
|
throw new Exception('No type registered with key `' . $key . '`. Either correct the usage, or register it in your custom types container when instantiating `' . self::class . '`.'); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Returns an output type for the given entity |
109
|
|
|
* |
110
|
|
|
* All entity getter methods will be exposed, unless specified otherwise |
111
|
|
|
* with annotations. |
112
|
|
|
* |
113
|
|
|
* @param string $className the class name of an entity (`Post::class`) |
114
|
|
|
* |
115
|
|
|
* @return ObjectType |
116
|
|
|
*/ |
117
|
14 |
|
public function getOutput(string $className): ObjectType |
118
|
|
|
{ |
119
|
14 |
|
$this->throwIfNotEntity($className); |
120
|
13 |
|
$key = Utils::getTypeName($className); |
121
|
|
|
|
122
|
13 |
|
if (!isset($this->types[$key])) { |
123
|
13 |
|
$instance = $this->objectTypeFactory->create($className); |
124
|
13 |
|
$this->registerInstance($instance); |
125
|
|
|
} |
126
|
|
|
|
127
|
13 |
|
return $this->types[$key]; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Returns an input type for the given entity |
132
|
|
|
* |
133
|
|
|
* This would typically be used in mutations to create new entities. |
134
|
|
|
* |
135
|
|
|
* All entity setter methods will be exposed, unless specified otherwise |
136
|
|
|
* with annotations. |
137
|
|
|
* |
138
|
|
|
* @param string $className the class name of an entity (`Post::class`) |
139
|
|
|
* |
140
|
|
|
* @return InputObjectType |
141
|
|
|
*/ |
142
|
5 |
|
public function getInput(string $className): InputObjectType |
143
|
|
|
{ |
144
|
5 |
|
$this->throwIfNotEntity($className); |
145
|
5 |
|
$key = Utils::getInputTypeName($className); |
146
|
|
|
|
147
|
5 |
|
if (!isset($this->types[$key])) { |
148
|
5 |
|
$instance = $this->inputTypeFactory->create($className); |
149
|
5 |
|
$this->registerInstance($instance); |
150
|
|
|
} |
151
|
|
|
|
152
|
5 |
|
return $this->types[$key]; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Returns a partial input type for the given entity |
157
|
|
|
* |
158
|
|
|
* This would typically be used in mutations to update existing entities. |
159
|
|
|
* |
160
|
|
|
* All entity setter methods will be exposed, unless specified otherwise |
161
|
|
|
* with annotations. But they will all be marked as optional and without |
162
|
|
|
* default values. So this allow the API client to specify only some fields |
163
|
|
|
* to be updated, and not necessarily all of them at once. |
164
|
|
|
* |
165
|
|
|
* @param string $className the class name of an entity (`Post::class`) |
166
|
|
|
* |
167
|
|
|
* @return InputObjectType |
168
|
|
|
*/ |
169
|
1 |
|
public function getPartialInput(string $className): InputObjectType |
170
|
|
|
{ |
171
|
1 |
|
$this->throwIfNotEntity($className); |
172
|
1 |
|
$key = Utils::getPartialInputTypeName($className); |
173
|
|
|
|
174
|
1 |
|
if (!isset($this->types[$key])) { |
175
|
1 |
|
$instance = $this->partialInputTypeFactory->create($className); |
176
|
1 |
|
$this->registerInstance($instance); |
177
|
|
|
} |
178
|
|
|
|
179
|
1 |
|
return $this->types[$key]; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Returns an special ID type for the given entity |
184
|
|
|
* |
185
|
|
|
* This is mostly useful for internal usage when a getter has an entity |
186
|
|
|
* as parameter. This type will automatically load the entity from DB, so |
187
|
|
|
* the resolve functions can use a real instance of entity instead of an ID. |
188
|
|
|
* But this can also be used to build your own schema and thus avoid |
189
|
|
|
* manually fetching objects from database for simple cases. |
190
|
|
|
* |
191
|
|
|
* @param string $className the class name of an entity (`Post::class`) |
192
|
|
|
* |
193
|
|
|
* @return EntityIDType |
194
|
|
|
*/ |
195
|
3 |
|
public function getId(string $className): EntityIDType |
196
|
|
|
{ |
197
|
3 |
|
$this->throwIfNotEntity($className); |
198
|
3 |
|
$key = Utils::getIDTypeName($className); |
199
|
|
|
|
200
|
3 |
|
if (!isset($this->types[$key])) { |
201
|
3 |
|
$instance = new EntityIDType($this->entityManager, $className); |
202
|
3 |
|
$this->registerInstance($instance); |
203
|
|
|
} |
204
|
|
|
|
205
|
3 |
|
return $this->types[$key]; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* Register the given type in our internal registry with its name |
210
|
|
|
* |
211
|
|
|
* @param Type $instance |
212
|
|
|
*/ |
213
|
21 |
|
private function registerInstance(Type $instance): void |
214
|
|
|
{ |
215
|
21 |
|
$this->types[$instance->name] = $instance; |
216
|
21 |
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Checks if a className is a valid doctrine entity |
220
|
|
|
* |
221
|
|
|
* @param string $className |
222
|
|
|
* |
223
|
|
|
* @return bool |
224
|
|
|
*/ |
225
|
18 |
|
public function isEntity(string $className): bool |
226
|
|
|
{ |
227
|
18 |
|
return class_exists($className) && !$this->entityManager->getMetadataFactory()->isTransient($className); |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Returns the list of native GraphQL types |
232
|
|
|
* |
233
|
|
|
* @return array |
234
|
|
|
*/ |
235
|
24 |
|
private function getPhpToGraphQLMapping(): array |
236
|
|
|
{ |
237
|
|
|
return [ |
238
|
24 |
|
'id' => Type::id(), |
239
|
24 |
|
'bool' => Type::boolean(), |
240
|
24 |
|
'int' => Type::int(), |
241
|
24 |
|
'float' => Type::float(), |
242
|
24 |
|
'string' => Type::string(), |
243
|
|
|
]; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Throw an exception if the class name is not Doctrine entity |
248
|
|
|
* |
249
|
|
|
* @param string $className |
250
|
|
|
* |
251
|
|
|
* @throws \UnexpectedValueException |
252
|
|
|
*/ |
253
|
18 |
|
private function throwIfNotEntity(string $className): void |
254
|
|
|
{ |
255
|
18 |
|
if (!$this->isEntity($className)) { |
256
|
1 |
|
throw new \UnexpectedValueException('Given class name `' . $className . '` is not a Doctrine entity. Either register a custom GraphQL type for `' . $className . '` when instantiating `' . self::class . '`, or change the usage of that class to something else.'); |
257
|
|
|
} |
258
|
17 |
|
} |
259
|
|
|
} |
260
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.