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 Doctrine\ORM\QueryBuilder; |
10
|
|
|
use GraphQL\Doctrine\Definition\EntityIDType; |
11
|
|
|
use GraphQL\Doctrine\Definition\JoinTypeType; |
12
|
|
|
use GraphQL\Doctrine\Definition\LogicalOperatorType; |
13
|
|
|
use GraphQL\Doctrine\Definition\Operator\AbstractOperator; |
14
|
|
|
use GraphQL\Doctrine\Definition\SortingOrderType; |
15
|
|
|
use GraphQL\Doctrine\Factory\FilteredQueryBuilderFactory; |
16
|
|
|
use GraphQL\Doctrine\Factory\Type\AbstractTypeFactory; |
17
|
|
|
use GraphQL\Doctrine\Factory\Type\EntityIDTypeFactory; |
18
|
|
|
use GraphQL\Doctrine\Factory\Type\FilterTypeFactory; |
19
|
|
|
use GraphQL\Doctrine\Factory\Type\InputTypeFactory; |
20
|
|
|
use GraphQL\Doctrine\Factory\Type\JoinTypeFactory; |
21
|
|
|
use GraphQL\Doctrine\Factory\Type\ObjectTypeFactory; |
22
|
|
|
use GraphQL\Doctrine\Factory\Type\PartialInputTypeFactory; |
23
|
|
|
use GraphQL\Doctrine\Factory\Type\SortingTypeFactory; |
24
|
|
|
use GraphQL\Type\Definition\InputObjectType; |
25
|
|
|
use GraphQL\Type\Definition\LeafType; |
26
|
|
|
use GraphQL\Type\Definition\ListOfType; |
27
|
|
|
use GraphQL\Type\Definition\ObjectType; |
28
|
|
|
use GraphQL\Type\Definition\Type; |
29
|
|
|
use Psr\Container\ContainerInterface; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Registry of types to manage all GraphQL types |
33
|
|
|
* |
34
|
|
|
* This is the entry point for the library. |
35
|
|
|
*/ |
36
|
|
|
class Types |
37
|
|
|
{ |
38
|
|
|
/** |
39
|
|
|
* @var null|ContainerInterface |
40
|
|
|
*/ |
41
|
|
|
private $customTypes; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var array mapping of type name to type instances |
45
|
|
|
*/ |
46
|
|
|
private $types = []; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var ObjectTypeFactory |
50
|
|
|
*/ |
51
|
|
|
private $objectTypeFactory; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @var InputTypeFactory |
55
|
|
|
*/ |
56
|
|
|
private $inputTypeFactory; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @var PartialInputTypeFactory |
60
|
|
|
*/ |
61
|
|
|
private $partialInputTypeFactory; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @var FilterTypeFactory |
65
|
|
|
*/ |
66
|
|
|
private $filterTypeFactory; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @var EntityManager |
70
|
|
|
*/ |
71
|
|
|
private $entityManager; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @var FilteredQueryBuilderFactory |
75
|
|
|
*/ |
76
|
|
|
private $filteredQueryBuilderFactory; |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @var SortingTypeFactory |
80
|
|
|
*/ |
81
|
|
|
private $sortingTypeFactory; |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* @var EntityIDTypeFactory |
85
|
|
|
*/ |
86
|
|
|
private $entityIDTypeFactory; |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* @var JoinTypeFactory |
90
|
|
|
*/ |
91
|
|
|
private $joinTypeFactory; |
92
|
|
|
|
93
|
36 |
|
public function __construct(EntityManager $entityManager, ?ContainerInterface $customTypes = null) |
94
|
|
|
{ |
95
|
36 |
|
$this->customTypes = $customTypes; |
96
|
36 |
|
$this->entityManager = $entityManager; |
97
|
36 |
|
$this->objectTypeFactory = new ObjectTypeFactory($this, $entityManager); |
98
|
36 |
|
$this->inputTypeFactory = new InputTypeFactory($this, $entityManager); |
99
|
36 |
|
$this->partialInputTypeFactory = new PartialInputTypeFactory($this, $entityManager); |
100
|
36 |
|
$this->filterTypeFactory = new FilterTypeFactory($this, $entityManager); |
101
|
36 |
|
$this->sortingTypeFactory = new SortingTypeFactory($this, $entityManager); |
102
|
36 |
|
$this->entityIDTypeFactory = new EntityIDTypeFactory($this, $entityManager); |
103
|
36 |
|
$this->joinTypeFactory = new JoinTypeFactory($this, $entityManager); |
104
|
36 |
|
$this->filteredQueryBuilderFactory = new FilteredQueryBuilderFactory($this, $entityManager); |
105
|
|
|
|
106
|
36 |
|
$entityManager->getConfiguration()->newDefaultAnnotationDriver(); |
107
|
36 |
|
AnnotationRegistry::registerLoader('class_exists'); |
|
|
|
|
108
|
|
|
|
109
|
36 |
|
$this->initializeInternalTypes(); |
110
|
36 |
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Returns whether a type exists for the given key |
114
|
|
|
* |
115
|
|
|
* @param string $key |
116
|
|
|
* |
117
|
|
|
* @return bool |
118
|
|
|
*/ |
119
|
1 |
|
public function has(string $key): bool |
120
|
|
|
{ |
121
|
1 |
|
return $this->customTypes && $this->customTypes->has($key) || array_key_exists($key, $this->types); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Always return the same instance of `Type` for the given key |
126
|
|
|
* |
127
|
|
|
* It will first look for the type in the custom types container, and then |
128
|
|
|
* use automatically generated types. This allow for custom types to override |
129
|
|
|
* automatic ones. |
130
|
|
|
* |
131
|
|
|
* @param string $key the key the type was registered with (eg: "Post", "PostInput", "PostPartialInput" or "PostStatus") |
132
|
|
|
* |
133
|
|
|
* @return Type |
134
|
|
|
*/ |
135
|
26 |
|
public function get(string $key): Type |
136
|
|
|
{ |
137
|
26 |
|
if ($this->customTypes && $this->customTypes->has($key)) { |
138
|
18 |
|
$t = $this->customTypes->get($key); |
139
|
18 |
|
$this->registerInstance($t); |
140
|
|
|
|
141
|
18 |
|
return $t; |
142
|
|
|
} |
143
|
|
|
|
144
|
22 |
|
if (array_key_exists($key, $this->types)) { |
145
|
21 |
|
return $this->types[$key]; |
146
|
|
|
} |
147
|
|
|
|
148
|
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 . '`.'); |
149
|
|
|
} |
150
|
|
|
|
151
|
30 |
|
private function getViaFactory(string $className, string $typeName, AbstractTypeFactory $factory): Type |
152
|
|
|
{ |
153
|
30 |
|
$this->throwIfNotEntity($className); |
154
|
|
|
|
155
|
29 |
|
if (!isset($this->types[$typeName])) { |
156
|
29 |
|
$instance = $factory->create($className, $typeName); |
157
|
29 |
|
$this->registerInstance($instance); |
158
|
|
|
} |
159
|
|
|
|
160
|
29 |
|
return $this->types[$typeName]; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Returns an output type for the given entity |
165
|
|
|
* |
166
|
|
|
* All entity getter methods will be exposed, unless specified otherwise |
167
|
|
|
* with annotations. |
168
|
|
|
* |
169
|
|
|
* @param string $className the class name of an entity (`Post::class`) |
170
|
|
|
* |
171
|
|
|
* @return ObjectType |
172
|
|
|
*/ |
173
|
14 |
|
public function getOutput(string $className): ObjectType |
174
|
|
|
{ |
175
|
|
|
/** @var ObjectType $type */ |
176
|
14 |
|
$type = $this->getViaFactory($className, Utils::getTypeName($className), $this->objectTypeFactory); |
177
|
|
|
|
178
|
13 |
|
return $type; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Returns an input type for the given entity |
183
|
|
|
* |
184
|
|
|
* This would typically be used in mutations to create new entities. |
185
|
|
|
* |
186
|
|
|
* All entity setter methods will be exposed, unless specified otherwise |
187
|
|
|
* with annotations. |
188
|
|
|
* |
189
|
|
|
* @param string $className the class name of an entity (`Post::class`) |
190
|
|
|
* |
191
|
|
|
* @return InputObjectType |
192
|
|
|
*/ |
193
|
5 |
|
public function getInput(string $className): InputObjectType |
194
|
|
|
{ |
195
|
|
|
/** @var InputObjectType $type */ |
196
|
5 |
|
$type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Input', $this->inputTypeFactory); |
197
|
|
|
|
198
|
5 |
|
return $type; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Returns a partial input type for the given entity |
203
|
|
|
* |
204
|
|
|
* This would typically be used in mutations to update existing entities. |
205
|
|
|
* |
206
|
|
|
* All entity setter methods will be exposed, unless specified otherwise |
207
|
|
|
* with annotations. But they will all be marked as optional and without |
208
|
|
|
* default values. So this allow the API client to specify only some fields |
209
|
|
|
* to be updated, and not necessarily all of them at once. |
210
|
|
|
* |
211
|
|
|
* @param string $className the class name of an entity (`Post::class`) |
212
|
|
|
* |
213
|
|
|
* @return InputObjectType |
214
|
|
|
*/ |
215
|
1 |
|
public function getPartialInput(string $className): InputObjectType |
216
|
|
|
{ |
217
|
|
|
/** @var InputObjectType $type */ |
218
|
1 |
|
$type = $this->getViaFactory($className, Utils::getTypeName($className) . 'PartialInput', $this->partialInputTypeFactory); |
219
|
|
|
|
220
|
1 |
|
return $type; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Returns a filter input type for the given entity |
225
|
|
|
* |
226
|
|
|
* This would typically be used to filter queries. |
227
|
|
|
* |
228
|
|
|
* @param string $className the class name of an entity (`Post::class`) |
229
|
|
|
* |
230
|
|
|
* @return InputObjectType |
231
|
|
|
*/ |
232
|
11 |
|
public function getFilter(string $className): InputObjectType |
233
|
|
|
{ |
234
|
|
|
/** @var InputObjectType $type */ |
235
|
11 |
|
$type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Filter', $this->filterTypeFactory); |
236
|
|
|
|
237
|
11 |
|
return $type; |
238
|
|
|
} |
239
|
|
|
|
240
|
1 |
|
public function getSorting(string $className): ListOfType |
241
|
|
|
{ |
242
|
|
|
/** @var InputObjectType $type */ |
243
|
1 |
|
$type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Sorting', $this->sortingTypeFactory); |
244
|
|
|
|
245
|
1 |
|
return Type::listOf(Type::nonNull($type)); |
246
|
|
|
} |
247
|
|
|
|
248
|
11 |
|
public function getJoin(string $className): InputObjectType |
249
|
|
|
{ |
250
|
|
|
/** @var InputObjectType $type */ |
251
|
11 |
|
$type = $this->getViaFactory($className, 'JoinOn' . Utils::getTypeName($className), $this->joinTypeFactory); |
252
|
|
|
|
253
|
11 |
|
return $type; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* Returns an special ID type for the given entity |
258
|
|
|
* |
259
|
|
|
* This is mostly useful for internal usage when a getter has an entity |
260
|
|
|
* as parameter. This type will automatically load the entity from DB, so |
261
|
|
|
* the resolve functions can use a real instance of entity instead of an ID. |
262
|
|
|
* But this can also be used to build your own schema and thus avoid |
263
|
|
|
* manually fetching objects from database for simple cases. |
264
|
|
|
* |
265
|
|
|
* @param string $className the class name of an entity (`Post::class`) |
266
|
|
|
* |
267
|
|
|
* @return EntityIDType |
268
|
|
|
*/ |
269
|
3 |
|
public function getId(string $className): EntityIDType |
270
|
|
|
{ |
271
|
|
|
/** @var EntityIDType $type */ |
272
|
3 |
|
$type = $this->getViaFactory($className, Utils::getTypeName($className) . 'ID', $this->entityIDTypeFactory); |
273
|
|
|
|
274
|
3 |
|
return $type; |
275
|
|
|
} |
276
|
|
|
|
277
|
6 |
|
public function getOperator(string $className, LeafType $type): AbstractOperator |
278
|
|
|
{ |
279
|
6 |
|
if (!is_a($className, AbstractOperator::class, true)) { |
280
|
|
|
throw new Exception('Must be a class implementing ' . AbstractOperator::class); |
281
|
|
|
} |
282
|
|
|
|
283
|
6 |
|
$key = Utils::getOperatorTypeName($className, $type); |
284
|
|
|
|
285
|
6 |
|
if (!isset($this->types[$key])) { |
286
|
6 |
|
$instance = new $className($type); |
287
|
6 |
|
$this->registerInstance($instance); |
288
|
|
|
} |
289
|
|
|
|
290
|
6 |
|
return $this->types[$key]; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Register the given type in our internal registry with its name |
295
|
|
|
* |
296
|
|
|
* @param Type $instance |
297
|
|
|
*/ |
298
|
36 |
|
public function registerInstance(Type $instance): void |
299
|
|
|
{ |
300
|
36 |
|
$this->types[$instance->name] = $instance; |
301
|
36 |
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Checks if a className is a valid doctrine entity |
305
|
|
|
* |
306
|
|
|
* @param string $className |
307
|
|
|
* |
308
|
|
|
* @return bool |
309
|
|
|
*/ |
310
|
30 |
|
public function isEntity(string $className): bool |
311
|
|
|
{ |
312
|
30 |
|
return class_exists($className) && !$this->entityManager->getMetadataFactory()->isTransient($className); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Initialize internal types for common needs |
317
|
|
|
*/ |
318
|
36 |
|
private function initializeInternalTypes(): void |
319
|
|
|
{ |
320
|
|
|
$phpToGraphQLMapping = [ |
321
|
|
|
// PHP types |
322
|
36 |
|
'id' => Type::id(), |
323
|
36 |
|
'bool' => Type::boolean(), |
324
|
36 |
|
'int' => Type::int(), |
325
|
36 |
|
'float' => Type::float(), |
326
|
36 |
|
'string' => Type::string(), |
327
|
|
|
|
328
|
|
|
// Doctrine types |
329
|
36 |
|
'boolean' => Type::boolean(), |
330
|
36 |
|
'integer' => Type::int(), |
331
|
36 |
|
'smallint' => Type::int(), |
332
|
36 |
|
'bigint' => Type::int(), |
333
|
36 |
|
'decimal' => Type::string(), |
334
|
36 |
|
'text' => Type::string(), |
335
|
|
|
]; |
336
|
|
|
|
337
|
36 |
|
$this->types = $phpToGraphQLMapping; |
338
|
36 |
|
$this->registerInstance(new LogicalOperatorType()); |
339
|
36 |
|
$this->registerInstance(new JoinTypeType()); |
340
|
36 |
|
$this->registerInstance(new SortingOrderType()); |
341
|
36 |
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* Throw an exception if the class name is not Doctrine entity |
345
|
|
|
* |
346
|
|
|
* @param string $className |
347
|
|
|
* |
348
|
|
|
* @throws \UnexpectedValueException |
349
|
|
|
*/ |
350
|
30 |
|
private function throwIfNotEntity(string $className): void |
351
|
|
|
{ |
352
|
30 |
|
if (!$this->isEntity($className)) { |
353
|
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.'); |
354
|
|
|
} |
355
|
29 |
|
} |
356
|
|
|
|
357
|
10 |
|
public function createFilteredQueryBuilder(string $className, array $filter, array $sorting): QueryBuilder |
358
|
|
|
{ |
359
|
10 |
|
return $this->filteredQueryBuilderFactory->create($className, $filter, $sorting); |
360
|
|
|
} |
361
|
|
|
} |
362
|
|
|
|
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.