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\Type; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Registry of types to manage all GraphQL types |
18
|
|
|
* |
19
|
|
|
* This is the entry point for the library. |
20
|
|
|
*/ |
21
|
|
|
class Types |
22
|
|
|
{ |
23
|
|
|
/** |
24
|
|
|
* @var array mapping of type name to type instances |
25
|
|
|
*/ |
26
|
|
|
private $types = []; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var ObjectTypeFactory |
30
|
|
|
*/ |
31
|
|
|
private $objectTypeFactory; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var InputTypeFactory |
35
|
|
|
*/ |
36
|
|
|
private $inputTypeFactory; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var PartialInputTypeFactory |
40
|
|
|
*/ |
41
|
|
|
private $partialInputTypeFactory; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var EntityManager |
45
|
|
|
*/ |
46
|
|
|
private $entityManager; |
47
|
|
|
|
48
|
21 |
|
public function __construct(EntityManager $entityManager, array $customTypeMapping = []) |
|
|
|
|
49
|
|
|
{ |
50
|
21 |
|
$this->types = $this->getPhpToGraphQLMapping(); |
51
|
21 |
|
$this->entityManager = $entityManager; |
52
|
21 |
|
$this->objectTypeFactory = new ObjectTypeFactory($this, $entityManager); |
53
|
21 |
|
$this->inputTypeFactory = new InputTypeFactory($this, $entityManager); |
54
|
21 |
|
$this->partialInputTypeFactory = new PartialInputTypeFactory($this, $entityManager); |
55
|
|
|
|
56
|
21 |
|
$entityManager->getConfiguration()->newDefaultAnnotationDriver(); |
57
|
21 |
|
AnnotationRegistry::registerLoader('class_exists'); |
58
|
|
|
|
59
|
21 |
|
foreach ($customTypeMapping as $phpType => $graphQLType) { |
60
|
21 |
|
$instance = $this->createInstance($graphQLType); |
61
|
21 |
|
$this->registerInstance($phpType, $instance); |
62
|
21 |
|
$this->registerInstance($graphQLType, $instance); |
63
|
|
|
} |
64
|
21 |
|
} |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Always return the same instance of `Type` for the given class name |
68
|
|
|
* |
69
|
|
|
* All entity getter methods will be exposed, unless specified otherwise |
70
|
|
|
* with annotations. |
71
|
|
|
* |
72
|
|
|
* @param string $className the class name of either a scalar type (`PostStatus::class`), or an entity (`Post::class`) |
73
|
|
|
* |
74
|
|
|
* @return Type |
75
|
|
|
*/ |
76
|
19 |
|
public function get(string $className): Type |
77
|
|
|
{ |
78
|
19 |
|
$key = $this->normalizedClassName($className); |
79
|
|
|
|
80
|
19 |
|
if (!isset($this->types[$key])) { |
81
|
14 |
|
$instance = $this->createInstance($className); |
82
|
13 |
|
$this->registerInstance($key, $instance); |
83
|
|
|
} |
84
|
|
|
|
85
|
18 |
|
return $this->types[$key]; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Returns an input type for the given entity |
90
|
|
|
* |
91
|
|
|
* This would typically be used in mutations to create new entities. |
92
|
|
|
* |
93
|
|
|
* All entity setter methods will be exposed, unless specified otherwise |
94
|
|
|
* with annotations. |
95
|
|
|
* |
96
|
|
|
* @param string $className the class name of an entity (`Post::class`) |
97
|
|
|
* |
98
|
|
|
* @return InputObjectType |
99
|
|
|
*/ |
100
|
5 |
View Code Duplication |
public function getInput(string $className): InputObjectType |
|
|
|
|
101
|
|
|
{ |
102
|
5 |
|
$this->throwIfNotEntity($className); |
103
|
5 |
|
$key = Utils::getInputTypeName($className); |
104
|
|
|
|
105
|
5 |
|
if (!isset($this->types[$key])) { |
106
|
5 |
|
$instance = $this->inputTypeFactory->create($className); |
107
|
5 |
|
$this->registerInstance($key, $instance); |
108
|
|
|
} |
109
|
|
|
|
110
|
5 |
|
return $this->types[$key]; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Returns a partial input type for the given entity |
115
|
|
|
* |
116
|
|
|
* This would typically be used in mutations to update existing entities. |
117
|
|
|
* |
118
|
|
|
* All entity setter methods will be exposed, unless specified otherwise |
119
|
|
|
* with annotations. But they will all be marked as optional and without |
120
|
|
|
* default values. So this allow the API client to specify only some fields |
121
|
|
|
* to be updated, and not necessarily all of them at once. |
122
|
|
|
* |
123
|
|
|
* @param string $className the class name of an entity (`Post::class`) |
124
|
|
|
* |
125
|
|
|
* @return InputObjectType |
126
|
|
|
*/ |
127
|
1 |
|
public function getPartialInput(string $className): InputObjectType |
128
|
|
|
{ |
129
|
1 |
|
$this->throwIfNotEntity($className); |
130
|
1 |
|
$key = Utils::getPartialInputTypeName($className); |
131
|
|
|
|
132
|
1 |
|
if (!isset($this->types[$key])) { |
133
|
1 |
|
$instance = $this->partialInputTypeFactory->create($className); |
134
|
1 |
|
$this->registerInstance($key, $instance); |
135
|
|
|
} |
136
|
|
|
|
137
|
1 |
|
return $this->types[$key]; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Returns an special ID type for the given entity |
142
|
|
|
* |
143
|
|
|
* This is mostly useful for internal usage when a getter has an entity |
144
|
|
|
* as parameter. This type will automatically load the entity from DB, so |
145
|
|
|
* the resolve functions can use a real instance of entity instead of an ID. |
146
|
|
|
* But this can also be used to build your own schema and thus avoid |
147
|
|
|
* manually fetching objects from database for simple cases. |
148
|
|
|
* |
149
|
|
|
* @param string $className the class name of an entity (`Post::class`) |
150
|
|
|
* |
151
|
|
|
* @return EntityIDType |
152
|
|
|
*/ |
153
|
3 |
View Code Duplication |
public function getId(string $className): EntityIDType |
|
|
|
|
154
|
|
|
{ |
155
|
3 |
|
$this->throwIfNotEntity($className); |
156
|
3 |
|
$key = Utils::getIDTypeName($className); |
157
|
|
|
|
158
|
3 |
|
if (!isset($this->types[$key])) { |
159
|
3 |
|
$instance = new EntityIDType($this->entityManager, $className); |
160
|
3 |
|
$this->registerInstance($key, $instance); |
161
|
|
|
} |
162
|
|
|
|
163
|
3 |
|
return $this->types[$key]; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Register the given type in our internal registry |
168
|
|
|
* |
169
|
|
|
* @param string $key |
170
|
|
|
* @param Type $instance |
171
|
|
|
*/ |
172
|
21 |
|
private function registerInstance(string $key, Type $instance): void |
173
|
|
|
{ |
174
|
21 |
|
$this->types[$key] = $instance; |
175
|
21 |
|
$this->types[$instance->name] = $instance; |
176
|
21 |
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Create an instance of either a custom, scalar or ObjectType |
180
|
|
|
* |
181
|
|
|
* @param string $className |
182
|
|
|
* |
183
|
|
|
* @return Type |
184
|
|
|
*/ |
185
|
21 |
|
private function createInstance(string $className): Type |
186
|
|
|
{ |
187
|
21 |
|
if (is_a($className, Type::class, true)) { |
188
|
21 |
|
return new $className(); |
189
|
|
|
} |
190
|
|
|
|
191
|
12 |
|
$this->throwIfNotEntity($className); |
192
|
|
|
|
193
|
11 |
|
return $this->objectTypeFactory->create($className); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Checks if a className is a valid doctrine entity |
198
|
|
|
* |
199
|
|
|
* @param string $className |
200
|
|
|
* |
201
|
|
|
* @return bool |
202
|
|
|
*/ |
203
|
16 |
|
public function isEntity(string $className): bool |
204
|
|
|
{ |
205
|
16 |
|
return class_exists($className) && !$this->entityManager->getMetadataFactory()->isTransient($className); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* Returns the list of native GraphQL types |
210
|
|
|
* |
211
|
|
|
* @return array |
212
|
|
|
*/ |
213
|
21 |
|
private function getPhpToGraphQLMapping(): array |
214
|
|
|
{ |
215
|
|
|
return [ |
216
|
21 |
|
'id' => Type::id(), |
217
|
21 |
|
'bool' => Type::boolean(), |
218
|
21 |
|
'int' => Type::int(), |
219
|
21 |
|
'float' => Type::float(), |
220
|
21 |
|
'string' => Type::string(), |
221
|
|
|
]; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Throw an exception if the class name is not Doctrine entity |
226
|
|
|
* |
227
|
|
|
* @param string $className |
228
|
|
|
* |
229
|
|
|
* @throws \UnexpectedValueException |
230
|
|
|
*/ |
231
|
16 |
|
private function throwIfNotEntity(string $className): void |
232
|
|
|
{ |
233
|
16 |
|
if (!$this->isEntity($className)) { |
234
|
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.'); |
235
|
|
|
} |
236
|
15 |
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Remove the leading `\` that may exists in FQCN |
240
|
|
|
* |
241
|
|
|
* @param string $className |
242
|
|
|
* |
243
|
|
|
* @return string |
244
|
|
|
*/ |
245
|
19 |
|
private function normalizedClassName(string $className): string |
246
|
|
|
{ |
247
|
19 |
|
return ltrim($className, '\\'); |
248
|
|
|
} |
249
|
|
|
} |
250
|
|
|
|
The
EntityManager
might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:If that code throws an exception and the
EntityManager
is closed. Any other code which depends on the same instance of theEntityManager
during this request will fail.On the other hand, if you instead inject the
ManagerRegistry
, thegetManager()
method guarantees that you will always get a usable manager instance.