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\Util\ClassUtils; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* CRUDAnnotationParser |
31
|
|
|
* |
32
|
|
|
* @deprecated in favor of QueryList, MutationAdd, MutationUpdate, MutationDelete parsers |
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
|
22 |
|
public function __construct(QueryAnnotationParser $queryParser, MutationAnnotationParser $mutationParser) |
55
|
|
|
{ |
56
|
22 |
|
$this->queryParser = $queryParser; |
57
|
22 |
|
$this->mutationParser = $mutationParser; |
58
|
22 |
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* {@inheritdoc} |
62
|
|
|
*/ |
63
|
22 |
|
public function supports($annotation): bool |
64
|
|
|
{ |
65
|
22 |
|
return $annotation instanceof Annotation\CRUDOperations; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* {@inheritDoc} |
70
|
|
|
*/ |
71
|
22 |
|
public function parse($annotation, \ReflectionClass $refClass, Endpoint $endpoint) |
72
|
|
|
{ |
73
|
22 |
|
if (!$endpoint->hasTypeForClass($refClass->getName())) { |
74
|
|
|
throw new \RuntimeException(sprintf('Can`t apply CRUD operations to "%s", CRUD operations can only be applied to valid GraphQL object types.', $refClass->getName())); |
75
|
|
|
} |
76
|
|
|
|
77
|
22 |
|
if (!$refClass->implementsInterface(NodeInterface::class)) { |
78
|
|
|
throw new \RuntimeException( |
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
|
22 |
|
if ($annotation->exclude) { |
|
|
|
|
89
|
|
|
$annotation->include = array_diff($annotation->include, $annotation->exclude); |
90
|
|
|
} |
91
|
|
|
|
92
|
22 |
|
$definition = $endpoint->getType($endpoint->getTypeForClass($refClass->getName())); |
93
|
|
|
|
94
|
22 |
|
$bundleNamespace = ClassUtils::relatedBundleNamespace($refClass->getName()); |
95
|
|
|
|
96
|
|
|
//All query |
97
|
22 |
|
if (\in_array('list', $annotation->include, true)) { |
98
|
|
|
if ($annotation->list) { |
99
|
|
|
$query = $annotation->list; |
100
|
|
|
} else { |
101
|
|
|
$query = new Annotation\Query(); |
102
|
|
|
} |
103
|
|
|
$this->createListOperation($definition, $query, $endpoint, $bundleNamespace); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
//Add mutation |
107
|
22 |
|
if (\in_array('add', $annotation->include, true)) { |
108
|
|
|
if ($annotation->add) { |
109
|
|
|
$mutation = $annotation->add; |
110
|
|
|
} else { |
111
|
|
|
$mutation = new Annotation\Mutation(); |
112
|
|
|
} |
113
|
|
|
$this->createAddOperation($definition, $mutation, $endpoint, $bundleNamespace); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
//Update mutation |
117
|
22 |
|
if (\in_array('update', $annotation->include, true)) { |
118
|
|
|
if ($annotation->update) { |
119
|
|
|
$mutation = $annotation->update; |
120
|
|
|
} else { |
121
|
|
|
$mutation = new Annotation\Mutation(); |
122
|
|
|
} |
123
|
|
|
$this->createUpdateOperation($definition, $mutation, $endpoint, $bundleNamespace); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
//Delete mutation |
127
|
22 |
|
if (\in_array('delete', $annotation->include, true)) { |
128
|
22 |
|
if ($annotation->delete) { |
129
|
|
|
$mutation = $annotation->delete; |
130
|
|
|
} else { |
131
|
22 |
|
$mutation = new Annotation\Mutation(); |
132
|
|
|
} |
133
|
22 |
|
$this->createDeleteOperation($definition, $mutation, $endpoint, $bundleNamespace); |
134
|
|
|
} |
135
|
22 |
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* @param ObjectDefinitionInterface $definition |
139
|
|
|
* @param Annotation\Query $query |
140
|
|
|
* @param Endpoint $endpoint |
141
|
|
|
* @param string $bundleNamespace |
142
|
|
|
*/ |
143
|
|
|
protected function createListOperation(ObjectDefinitionInterface $definition, Annotation\Query $query, Endpoint $endpoint, $bundleNamespace) |
144
|
|
|
{ |
145
|
|
|
$query->name = $query->name ?? 'all'.Inflector::pluralize(ucfirst($definition->getName())); |
146
|
|
|
$query->type = $query->type ?? $definition->getName(); |
147
|
|
|
$query->options = array_merge(['pagination' => true], $query->options); |
148
|
|
|
$resolver = ClassUtils::applyNamingConvention($bundleNamespace, 'Query', $definition->getName(), $query->name); |
149
|
|
|
if (class_exists($resolver)) { |
150
|
|
|
$query->resolver = $resolver; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
$resolverReflection = new \ReflectionClass(AllNodesWithPagination::class); |
154
|
|
|
$this->queryParser->parse($query, $resolverReflection, $endpoint); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* @param ObjectDefinitionInterface $definition |
159
|
|
|
* @param Annotation\Mutation $mutation |
160
|
|
|
* @param Endpoint $endpoint |
161
|
|
|
* @param string $bundleNamespace |
162
|
|
|
*/ |
163
|
|
|
protected function createAddOperation(ObjectDefinitionInterface $definition, Annotation\Mutation $mutation, Endpoint $endpoint, $bundleNamespace) |
164
|
|
|
{ |
165
|
|
|
$mutation->name = $mutation->name ?? 'add'.ucfirst($definition->getName()); |
166
|
|
|
$mutation->payload = $mutation->payload ?? null; |
167
|
|
|
if (!$mutation->payload) { |
168
|
|
|
//deep cloning |
169
|
|
|
/** @var ObjectDefinitionInterface $payload */ |
170
|
|
|
$payload = unserialize(serialize($endpoint->getType(AddNodePayload::class)), [DefinitionInterface::class]); |
171
|
|
|
$payload->setName(ucfirst($mutation->name.'Payload')); |
172
|
|
|
|
173
|
|
|
if (!$endpoint->hasType($payload->getName())) { |
174
|
|
|
$payload->getField('node')->setType($definition->getName()); |
175
|
|
|
$endpoint->add($payload); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
$mutation->payload = $payload->getName(); |
179
|
|
|
} |
180
|
|
|
$mutation->node = $mutation->node ?? $definition->getName(); |
181
|
|
|
|
182
|
|
|
if ($endpoint->hasTypeForClass($mutation->node)) { |
183
|
|
|
$mutation->node = $endpoint->getTypeForClass($mutation->node); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
$formType = true; |
187
|
|
|
$options = []; |
188
|
|
|
$generalForm = ClassUtils::applyNamingConvention($bundleNamespace, 'Form\Input', $mutation->node, $mutation->node, 'Input'); |
189
|
|
|
$specificForm = ClassUtils::applyNamingConvention($bundleNamespace, 'Form\Input', $mutation->node, $mutation->name, 'Input'); |
190
|
|
|
if (class_exists($specificForm)) { |
191
|
|
|
$formType = $specificForm; |
192
|
|
|
} elseif (class_exists($generalForm)) { |
193
|
|
|
$formType = $generalForm; |
194
|
|
|
$options['operation'] = $mutation->name; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
$mutation->options = array_merge(['form' => ['type' => $formType, 'options' => $options]], $mutation->options); |
198
|
|
|
$resolverReflection = new \ReflectionClass(AddNode::class); |
199
|
|
|
|
200
|
|
|
$resolver = ClassUtils::applyNamingConvention($bundleNamespace, 'Mutation', $definition->getName(), $mutation->name); |
201
|
|
|
if (class_exists($resolver)) { |
202
|
|
|
$mutation->resolver = $resolver; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
$this->mutationParser->parse($mutation, $resolverReflection, $endpoint); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* @param ObjectDefinitionInterface $definition |
210
|
|
|
* @param Annotation\Mutation $mutation |
211
|
|
|
* @param Endpoint $endpoint |
212
|
|
|
* @param string $bundleNamespace |
213
|
|
|
*/ |
214
|
|
|
protected function createUpdateOperation(ObjectDefinitionInterface $definition, Annotation\Mutation $mutation, Endpoint $endpoint, $bundleNamespace) |
215
|
|
|
{ |
216
|
|
|
$mutation->name = $mutation->name ?? 'update'.ucfirst($definition->getName()); |
217
|
|
|
$mutation->payload = $mutation->payload ?? null; |
218
|
|
|
if (!$mutation->payload) { |
219
|
|
|
//deep cloning |
220
|
|
|
/** @var ObjectDefinitionInterface $payload */ |
221
|
|
|
$payload = unserialize(serialize($endpoint->getType(UpdateNodePayload::class)), [DefinitionInterface::class]); |
222
|
|
|
$payload->setName(ucfirst($mutation->name.'Payload')); |
223
|
|
|
|
224
|
|
|
if (!$endpoint->hasType($payload->getName())) { |
225
|
|
|
$payload->getField('node')->setType($definition->getName()); |
226
|
|
|
$endpoint->add($payload); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
$mutation->payload = $payload->getName(); |
230
|
|
|
} |
231
|
|
|
$mutation->node = $mutation->node ?? $definition->getName(); |
232
|
|
|
|
233
|
|
|
if ($endpoint->hasTypeForClass($mutation->node)) { |
234
|
|
|
$mutation->node = $endpoint->getTypeForClass($mutation->node); |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
$formType = true; |
238
|
|
|
$options = []; |
239
|
|
|
$generalForm = ClassUtils::applyNamingConvention($bundleNamespace, 'Form\Input', $mutation->node, $mutation->node, 'Input'); |
240
|
|
|
$specificForm = ClassUtils::applyNamingConvention($bundleNamespace, 'Form\Input', $mutation->node, $mutation->name, 'Input'); |
241
|
|
|
if (class_exists($specificForm)) { |
242
|
|
|
$formType = $specificForm; |
243
|
|
|
} elseif (class_exists($generalForm)) { |
244
|
|
|
$formType = $generalForm; |
245
|
|
|
$options['operation'] = $mutation->name; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
$mutation->options = array_merge(['form' => ['type' => $formType, 'options' => $options]], $mutation->options); |
249
|
|
|
$resolverReflection = new \ReflectionClass(UpdateNode::class); |
250
|
|
|
|
251
|
|
|
$resolver = ClassUtils::applyNamingConvention($bundleNamespace, 'Mutation', $definition->getName(), $mutation->name); |
252
|
|
|
if (class_exists($resolver)) { |
253
|
|
|
$mutation->resolver = $resolver; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
$this->mutationParser->parse($mutation, $resolverReflection, $endpoint); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* @param ObjectDefinitionInterface $definition |
261
|
|
|
* @param Annotation\Mutation $mutation |
262
|
|
|
* @param Endpoint $endpoint |
263
|
|
|
* @param string $bundleNamespace |
264
|
|
|
*/ |
265
|
22 |
|
protected function createDeleteOperation(ObjectDefinitionInterface $definition, Annotation\Mutation $mutation, Endpoint $endpoint, $bundleNamespace) |
266
|
|
|
{ |
267
|
22 |
|
$mutation->name = $mutation->name ?? 'delete'.ucfirst($definition->getName()); |
268
|
22 |
|
$mutation->payload = $mutation->payload ?? null; |
269
|
22 |
|
if (!$mutation->payload) { |
270
|
22 |
|
$mutation->payload = DeleteNodePayload::class; |
271
|
|
|
} |
272
|
22 |
|
$mutation->node = $mutation->node ?? $definition->getName(); |
273
|
22 |
|
$mutation->options = array_merge(['form' => ['type' => NodeDeleteInput::class]], $mutation->options); |
274
|
22 |
|
$resolverReflection = new \ReflectionClass(DeleteNode::class); |
275
|
|
|
|
276
|
22 |
|
$resolver = ClassUtils::applyNamingConvention($bundleNamespace, 'Mutation', $definition->getName(), $mutation->name); |
277
|
22 |
|
if (class_exists($resolver)) { |
278
|
|
|
$mutation->resolver = $resolver; |
279
|
|
|
} |
280
|
|
|
|
281
|
22 |
|
$this->mutationParser->parse($mutation, $resolverReflection, $endpoint); |
282
|
22 |
|
} |
283
|
|
|
} |
284
|
|
|
|
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.