1
|
|
|
<?php |
2
|
|
|
/******************************************************************************* |
3
|
|
|
* This file is part of the GraphQL Bundle package. |
4
|
|
|
* |
5
|
|
|
* (c) YnloUltratech <[email protected]> |
6
|
|
|
* |
7
|
|
|
* For the full copyright and license information, please view the LICENSE |
8
|
|
|
* file that was distributed with this source code. |
9
|
|
|
******************************************************************************/ |
10
|
|
|
|
11
|
|
|
namespace Ynlo\GraphQLBundle\Definition\Loader\Annotation; |
12
|
|
|
|
13
|
|
|
use Doctrine\Common\Util\Inflector; |
14
|
|
|
use Ynlo\GraphQLBundle\Annotation; |
15
|
|
|
use Ynlo\GraphQLBundle\Definition\DefinitionInterface; |
16
|
|
|
use Ynlo\GraphQLBundle\Definition\ObjectDefinitionInterface; |
17
|
|
|
use Ynlo\GraphQLBundle\Definition\Registry\Endpoint; |
18
|
|
|
use Ynlo\GraphQLBundle\Form\Node\NodeDeleteInput; |
19
|
|
|
use Ynlo\GraphQLBundle\Model\AddNodePayload; |
20
|
|
|
use Ynlo\GraphQLBundle\Model\DeleteNodePayload; |
21
|
|
|
use Ynlo\GraphQLBundle\Model\NodeInterface; |
22
|
|
|
use Ynlo\GraphQLBundle\Model\UpdateNodePayload; |
23
|
|
|
use Ynlo\GraphQLBundle\Mutation\AddNode; |
24
|
|
|
use Ynlo\GraphQLBundle\Mutation\DeleteNode; |
25
|
|
|
use Ynlo\GraphQLBundle\Mutation\UpdateNode; |
26
|
|
|
use Ynlo\GraphQLBundle\Query\Node\AllNodesWithPagination; |
27
|
|
|
use Ynlo\GraphQLBundle\Query\Node\Node; |
28
|
|
|
use Ynlo\GraphQLBundle\Query\Node\Nodes; |
29
|
|
|
use Ynlo\GraphQLBundle\Util\ClassUtils; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* CRUDAnnotationParser |
33
|
|
|
*/ |
34
|
|
|
class CRUDAnnotationParser implements AnnotationParserInterface |
35
|
|
|
{ |
36
|
|
|
use AnnotationReaderAwareTrait; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var QueryAnnotationParser |
40
|
|
|
*/ |
41
|
|
|
protected $queryParser; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var MutationAnnotationParser |
45
|
|
|
*/ |
46
|
|
|
protected $mutationParser; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* CRUDAnnotationParser constructor. |
50
|
|
|
* |
51
|
|
|
* @param QueryAnnotationParser $queryParser |
52
|
|
|
* @param MutationAnnotationParser $mutationParser |
53
|
|
|
*/ |
54
|
1 |
|
public function __construct(QueryAnnotationParser $queryParser, MutationAnnotationParser $mutationParser) |
55
|
|
|
{ |
56
|
1 |
|
$this->queryParser = $queryParser; |
57
|
1 |
|
$this->mutationParser = $mutationParser; |
58
|
1 |
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* {@inheritdoc} |
62
|
|
|
*/ |
63
|
1 |
|
public function supports($annotation): bool |
64
|
|
|
{ |
65
|
1 |
|
return $annotation instanceof Annotation\CRUDOperations; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* {@inheritDoc} |
70
|
|
|
*/ |
71
|
1 |
|
public function parse($annotation, \ReflectionClass $refClass, Endpoint $endpoint) |
72
|
|
|
{ |
73
|
1 |
|
if (!$endpoint->hasTypeForClass($refClass->getName())) { |
74
|
|
|
throw new \Exception(sprintf('Can`t apply CRUD operations to "%s", CRUD operations can only be applied to valid GraphQL object types.', $refClass->getName())); |
75
|
|
|
} |
76
|
|
|
|
77
|
1 |
|
if (!$refClass->implementsInterface(NodeInterface::class)) { |
78
|
|
|
throw new \Exception( |
79
|
|
|
sprintf( |
80
|
|
|
'Can`t apply CRUD operations to "%s", CRUD operations can only be applied to nodes. |
81
|
|
|
You are implementing NodeInterface in this class?', |
82
|
|
|
$refClass->getName() |
83
|
|
|
) |
84
|
|
|
); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** @var Annotation\CRUDOperations $annotation */ |
88
|
1 |
|
if ($annotation->exclude) { |
|
|
|
|
89
|
|
|
$annotation->include = array_diff($annotation->include, $annotation->exclude); |
90
|
|
|
} |
91
|
|
|
|
92
|
1 |
|
$definition = $endpoint->getType($endpoint->getTypeForClass($refClass->getName())); |
93
|
|
|
|
94
|
1 |
|
$bundleNamespace = ClassUtils::relatedBundleNamespace($refClass->getName()); |
95
|
|
|
|
96
|
|
|
//All query |
97
|
1 |
View Code Duplication |
if (in_array('list', $annotation->include)) { |
|
|
|
|
98
|
1 |
|
if ($annotation->list) { |
99
|
|
|
$query = $annotation->list; |
100
|
|
|
} else { |
101
|
1 |
|
$query = new Annotation\Query(); |
102
|
|
|
} |
103
|
1 |
|
$this->createListOperation($definition, $query, $endpoint, $bundleNamespace); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
//Add mutation |
107
|
1 |
View Code Duplication |
if (in_array('add', $annotation->include)) { |
|
|
|
|
108
|
1 |
|
if ($annotation->add) { |
109
|
|
|
$mutation = $annotation->add; |
110
|
|
|
} else { |
111
|
1 |
|
$mutation = new Annotation\Mutation(); |
112
|
|
|
} |
113
|
1 |
|
$this->createAddOperation($definition, $mutation, $endpoint, $bundleNamespace); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
//Update mutation |
117
|
1 |
View Code Duplication |
if (in_array('update', $annotation->include)) { |
|
|
|
|
118
|
1 |
|
if ($annotation->update) { |
119
|
|
|
$mutation = $annotation->update; |
120
|
|
|
} else { |
121
|
1 |
|
$mutation = new Annotation\Mutation(); |
122
|
|
|
} |
123
|
1 |
|
$this->createUpdateOperation($definition, $mutation, $endpoint, $bundleNamespace); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
//Delete mutation |
127
|
1 |
View Code Duplication |
if (in_array('delete', $annotation->include)) { |
|
|
|
|
128
|
1 |
|
if ($annotation->delete) { |
129
|
|
|
$mutation = $annotation->delete; |
130
|
|
|
} else { |
131
|
1 |
|
$mutation = new Annotation\Mutation(); |
132
|
|
|
} |
133
|
1 |
|
$this->createDeleteOperation($definition, $mutation, $endpoint, $bundleNamespace); |
134
|
|
|
} |
135
|
1 |
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* @param ObjectDefinitionInterface $definition |
139
|
|
|
* @param Annotation\Query $query |
140
|
|
|
* @param Endpoint $endpoint |
141
|
|
|
* @param string $bundleNamespace |
142
|
|
|
*/ |
143
|
|
View Code Duplication |
protected function createGetsOperation(ObjectDefinitionInterface $definition, Annotation\Query $query, Endpoint $endpoint, $bundleNamespace) |
|
|
|
|
144
|
|
|
{ |
145
|
|
|
$query->name = $query->name ?? Inflector::pluralize(lcfirst($definition->getName())); |
146
|
|
|
$query->node = $query->node ?? $definition->getName(); |
147
|
|
|
$query->list = true; |
148
|
|
|
$resolverReflection = new \ReflectionClass(Nodes::class); |
149
|
|
|
|
150
|
|
|
$resolver = ClassUtils::applyNamingConvention($bundleNamespace, 'Query', $definition->getName(), $query->name); |
151
|
|
|
if (class_exists($resolver)) { |
152
|
|
|
$query->resolver = $resolver; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
$this->queryParser->parse($query, $resolverReflection, $endpoint); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* @param ObjectDefinitionInterface $definition |
160
|
|
|
* @param Annotation\Query $query |
161
|
|
|
* @param Endpoint $endpoint |
162
|
|
|
* @param string $bundleNamespace |
163
|
|
|
*/ |
164
|
|
View Code Duplication |
protected function createGetOperation(ObjectDefinitionInterface $definition, Annotation\Query $query, Endpoint $endpoint, $bundleNamespace) |
|
|
|
|
165
|
|
|
{ |
166
|
|
|
$query->name = $query->name ?? Inflector::singularize(lcfirst($definition->getName())); |
167
|
|
|
$query->node = $query->node ?? $definition->getName(); |
168
|
|
|
$resolverReflection = new \ReflectionClass(Node::class); |
169
|
|
|
|
170
|
|
|
$resolver = ClassUtils::applyNamingConvention($bundleNamespace, 'Query', $definition->getName(), $query->name); |
171
|
|
|
if (class_exists($resolver)) { |
172
|
|
|
$query->resolver = $resolver; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
$this->queryParser->parse($query, $resolverReflection, $endpoint); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* @param ObjectDefinitionInterface $definition |
180
|
|
|
* @param Annotation\Query $query |
181
|
|
|
* @param Endpoint $endpoint |
182
|
|
|
* @param string $bundleNamespace |
183
|
|
|
*/ |
184
|
1 |
View Code Duplication |
protected function createListOperation(ObjectDefinitionInterface $definition, Annotation\Query $query, Endpoint $endpoint, $bundleNamespace) |
|
|
|
|
185
|
|
|
{ |
186
|
1 |
|
$query->name = $query->name ?? Inflector::pluralize(lcfirst($definition->getName())); |
187
|
1 |
|
$query->node = $query->node ?? $definition->getName(); |
188
|
1 |
|
$query->options = array_merge(['pagination' => true], $query->options); |
189
|
1 |
|
$resolverReflection = new \ReflectionClass(AllNodesWithPagination::class); |
190
|
|
|
|
191
|
1 |
|
$resolver = ClassUtils::applyNamingConvention($bundleNamespace, 'Query', $definition->getName(), $query->name); |
192
|
1 |
|
if (class_exists($resolver)) { |
193
|
|
|
$query->resolver = $resolver; |
194
|
|
|
} |
195
|
|
|
|
196
|
1 |
|
$this->queryParser->parse($query, $resolverReflection, $endpoint); |
197
|
1 |
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* @param ObjectDefinitionInterface $definition |
201
|
|
|
* @param Annotation\Mutation $mutation |
202
|
|
|
* @param Endpoint $endpoint |
203
|
|
|
* @param string $bundleNamespace |
204
|
|
|
*/ |
205
|
1 |
View Code Duplication |
protected function createAddOperation(ObjectDefinitionInterface $definition, Annotation\Mutation $mutation, Endpoint $endpoint, $bundleNamespace) |
|
|
|
|
206
|
|
|
{ |
207
|
1 |
|
$mutation->name = $mutation->name ?? 'add'.ucfirst($definition->getName()); |
208
|
1 |
|
$mutation->payload = $mutation->payload ?? null; |
209
|
1 |
|
if (!$mutation->payload) { |
210
|
|
|
//deep cloning |
211
|
|
|
/** @var ObjectDefinitionInterface $payload */ |
212
|
1 |
|
$payload = unserialize(serialize($endpoint->getType(AddNodePayload::class)), [DefinitionInterface::class]); |
213
|
1 |
|
$payload->setName(ucfirst($mutation->name.'Payload')); |
214
|
|
|
|
215
|
1 |
|
if (!$endpoint->hasType($payload->getName())) { |
216
|
1 |
|
$payload->getField('node')->setType($definition->getName()); |
217
|
1 |
|
$endpoint->add($payload); |
218
|
|
|
} |
219
|
|
|
|
220
|
1 |
|
$mutation->payload = $payload->getName(); |
221
|
|
|
} |
222
|
1 |
|
$mutation->node = $mutation->node ?? $definition->getName(); |
223
|
|
|
|
224
|
1 |
|
if ($endpoint->hasTypeForClass($mutation->node)) { |
225
|
|
|
$mutation->node = $endpoint->getTypeForClass($mutation->node); |
226
|
|
|
} |
227
|
|
|
|
228
|
1 |
|
$formType = true; |
229
|
1 |
|
$options = []; |
230
|
1 |
|
$generalForm = ClassUtils::applyNamingConvention($bundleNamespace, 'Form\Input', $mutation->node, $mutation->node, 'Input'); |
231
|
1 |
|
$specificForm = ClassUtils::applyNamingConvention($bundleNamespace, 'Form\Input', $mutation->node, $mutation->name, 'Input'); |
232
|
1 |
|
if (class_exists($specificForm)) { |
233
|
1 |
|
$formType = $specificForm; |
234
|
|
|
} elseif (class_exists($generalForm)) { |
235
|
|
|
$formType = $generalForm; |
236
|
|
|
$options['operation'] = $mutation->name; |
237
|
|
|
} |
238
|
|
|
|
239
|
1 |
|
$mutation->options = array_merge(['form' => ['type' => $formType, 'options' => $options]], $mutation->options); |
240
|
1 |
|
$resolverReflection = new \ReflectionClass(AddNode::class); |
241
|
|
|
|
242
|
1 |
|
$resolver = ClassUtils::applyNamingConvention($bundleNamespace, 'Mutation', $definition->getName(), $mutation->name); |
243
|
1 |
|
if (class_exists($resolver)) { |
244
|
|
|
$mutation->resolver = $resolver; |
245
|
|
|
} |
246
|
|
|
|
247
|
1 |
|
$this->mutationParser->parse($mutation, $resolverReflection, $endpoint); |
248
|
1 |
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* @param ObjectDefinitionInterface $definition |
252
|
|
|
* @param Annotation\Mutation $mutation |
253
|
|
|
* @param Endpoint $endpoint |
254
|
|
|
* @param string $bundleNamespace |
255
|
|
|
*/ |
256
|
1 |
View Code Duplication |
protected function createUpdateOperation(ObjectDefinitionInterface $definition, Annotation\Mutation $mutation, Endpoint $endpoint, $bundleNamespace) |
|
|
|
|
257
|
|
|
{ |
258
|
1 |
|
$mutation->name = $mutation->name ?? 'update'.ucfirst($definition->getName()); |
259
|
1 |
|
$mutation->payload = $mutation->payload ?? null; |
260
|
1 |
|
if (!$mutation->payload) { |
261
|
|
|
//deep cloning |
262
|
|
|
/** @var ObjectDefinitionInterface $payload */ |
263
|
1 |
|
$payload = unserialize(serialize($endpoint->getType(UpdateNodePayload::class)), [DefinitionInterface::class]); |
264
|
1 |
|
$payload->setName(ucfirst($mutation->name.'Payload')); |
265
|
|
|
|
266
|
1 |
|
if (!$endpoint->hasType($payload->getName())) { |
267
|
1 |
|
$payload->getField('node')->setType($definition->getName()); |
268
|
1 |
|
$endpoint->add($payload); |
269
|
|
|
} |
270
|
|
|
|
271
|
1 |
|
$mutation->payload = $payload->getName(); |
272
|
|
|
} |
273
|
1 |
|
$mutation->node = $mutation->node ?? $definition->getName(); |
274
|
|
|
|
275
|
1 |
|
if ($endpoint->hasTypeForClass($mutation->node)) { |
276
|
|
|
$mutation->node = $endpoint->getTypeForClass($mutation->node); |
277
|
|
|
} |
278
|
|
|
|
279
|
1 |
|
$formType = true; |
280
|
1 |
|
$options = []; |
281
|
1 |
|
$generalForm = ClassUtils::applyNamingConvention($bundleNamespace, 'Form\Input', $mutation->node, $mutation->node, 'Input'); |
282
|
1 |
|
$specificForm = ClassUtils::applyNamingConvention($bundleNamespace, 'Form\Input', $mutation->node, $mutation->name, 'Input'); |
283
|
1 |
|
if (class_exists($specificForm)) { |
284
|
1 |
|
$formType = $specificForm; |
285
|
|
|
} elseif (class_exists($generalForm)) { |
286
|
|
|
$formType = $generalForm; |
287
|
|
|
$options['operation'] = $mutation->name; |
288
|
|
|
} |
289
|
|
|
|
290
|
1 |
|
$mutation->options = array_merge(['form' => ['type' => $formType, 'options' => $options]], $mutation->options); |
291
|
1 |
|
$resolverReflection = new \ReflectionClass(UpdateNode::class); |
292
|
|
|
|
293
|
1 |
|
$resolver = ClassUtils::applyNamingConvention($bundleNamespace, 'Mutation', $definition->getName(), $mutation->name); |
294
|
1 |
|
if (class_exists($resolver)) { |
295
|
|
|
$mutation->resolver = $resolver; |
296
|
|
|
} |
297
|
|
|
|
298
|
1 |
|
$this->mutationParser->parse($mutation, $resolverReflection, $endpoint); |
299
|
1 |
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* @param ObjectDefinitionInterface $definition |
303
|
|
|
* @param Annotation\Mutation $mutation |
304
|
|
|
* @param Endpoint $endpoint |
305
|
|
|
* @param string $bundleNamespace |
306
|
|
|
*/ |
307
|
1 |
|
protected function createDeleteOperation(ObjectDefinitionInterface $definition, Annotation\Mutation $mutation, Endpoint $endpoint, $bundleNamespace) |
308
|
|
|
{ |
309
|
1 |
|
$mutation->name = $mutation->name ?? 'delete'.ucfirst($definition->getName()); |
310
|
1 |
|
$mutation->payload = $mutation->payload ?? null; |
311
|
1 |
|
if (!$mutation->payload) { |
312
|
1 |
|
$mutation->payload = DeleteNodePayload::class; |
313
|
|
|
} |
314
|
1 |
|
$mutation->node = $mutation->node ?? $definition->getName(); |
315
|
1 |
|
$mutation->options = array_merge(['form' => ['type' => NodeDeleteInput::class]], $mutation->options); |
316
|
1 |
|
$resolverReflection = new \ReflectionClass(DeleteNode::class); |
317
|
|
|
|
318
|
1 |
|
$resolver = ClassUtils::applyNamingConvention($bundleNamespace, 'Mutation', $definition->getName(), $mutation->name); |
319
|
1 |
|
if (class_exists($resolver)) { |
320
|
|
|
$mutation->resolver = $resolver; |
321
|
|
|
} |
322
|
|
|
|
323
|
1 |
|
$this->mutationParser->parse($mutation, $resolverReflection, $endpoint); |
324
|
1 |
|
} |
325
|
|
|
} |
326
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.