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\Plugin; |
||||
12 | |||||
13 | use Doctrine\Common\Inflector\Inflector; |
||||
14 | use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; |
||||
15 | use Ynlo\GraphQLBundle\Definition\DefinitionInterface; |
||||
16 | use Ynlo\GraphQLBundle\Definition\ExecutableDefinitionInterface; |
||||
17 | use Ynlo\GraphQLBundle\Definition\FieldDefinition; |
||||
18 | use Ynlo\GraphQLBundle\Definition\FieldsAwareDefinitionInterface; |
||||
19 | use Ynlo\GraphQLBundle\Definition\MutationDefinition; |
||||
20 | use Ynlo\GraphQLBundle\Definition\NodeAwareDefinitionInterface; |
||||
21 | use Ynlo\GraphQLBundle\Definition\ObjectDefinition; |
||||
22 | use Ynlo\GraphQLBundle\Definition\ObjectDefinitionInterface; |
||||
23 | use Ynlo\GraphQLBundle\Definition\Registry\Endpoint; |
||||
24 | use Ynlo\GraphQLBundle\Definition\SubscriptionDefinition; |
||||
25 | use Ynlo\GraphQLBundle\Resolver\EmptyObjectResolver; |
||||
26 | |||||
27 | /** |
||||
28 | * This extension configure namespace in definitions |
||||
29 | * using definition node and bundle in the node |
||||
30 | */ |
||||
31 | class NamespaceDefinitionPlugin extends AbstractDefinitionPlugin |
||||
32 | { |
||||
33 | protected $globalConfig = []; |
||||
34 | |||||
35 | /** |
||||
36 | * NamespaceDefinitionPlugin constructor. |
||||
37 | * |
||||
38 | * Configuration: |
||||
39 | * |
||||
40 | * # Group each bundle into a separate schema definition |
||||
41 | * bundles: |
||||
42 | * enabled: true |
||||
43 | * |
||||
44 | * # The following suffix will be used for bundle query groups |
||||
45 | * query_suffix: BundleQuery |
||||
46 | * |
||||
47 | * # The following suffix will be used for bundle mutation groups |
||||
48 | * mutation_suffix: BundleMutation |
||||
49 | * |
||||
50 | * # The following suffix will be used for bundle subscriptions groups |
||||
51 | * subscription_suffix: BundleSubscription |
||||
52 | * |
||||
53 | * # The following bundles will be ignore for grouping, all definitions will be placed in the root query or mutation |
||||
54 | * ignore: |
||||
55 | * |
||||
56 | * # Default: |
||||
57 | * - AppBundle |
||||
58 | * |
||||
59 | * # Define aliases for bundles to set definitions inside other desired bundle name. |
||||
60 | * # Can be used to group multiple bundles or publish a bundle with a different name |
||||
61 | * aliases: # Example: SecurityBundle: AppBundle |
||||
62 | * |
||||
63 | * # Prototype |
||||
64 | * name: ~ |
||||
65 | * |
||||
66 | * # Group queries and mutations of the same node into a node specific schema definition. |
||||
67 | * nodes: |
||||
68 | * enabled: true |
||||
69 | * |
||||
70 | * # The following suffix will be used to create the name for queries to the same node |
||||
71 | * query_suffix: Query |
||||
72 | * |
||||
73 | * # The following suffix will be used to create the name for mutations to the same node |
||||
74 | * mutation_suffix: Mutation |
||||
75 | * |
||||
76 | * # The following suffix will be used to create the name for subscriptions to the same node |
||||
77 | * subscription_suffix: Subscription |
||||
78 | * |
||||
79 | * # The following nodes will be ignore for grouping, all definitions will be placed in the root query or mutation |
||||
80 | * ignore: |
||||
81 | * |
||||
82 | * # Default: |
||||
83 | * - Node |
||||
84 | * |
||||
85 | * # Define aliases for nodes to set definitions inside other desired node name. |
||||
86 | * # Can be used to group multiple nodes or publish a node with a different group name |
||||
87 | * aliases: # Example: InvoiceItem: Invoice |
||||
88 | * |
||||
89 | * # Prototype |
||||
90 | * name: |
||||
91 | * |
||||
92 | * @param array $config |
||||
93 | */ |
||||
94 | 2 | public function __construct(array $config = []) |
|||
95 | { |
||||
96 | 2 | $this->globalConfig = $config; |
|||
97 | 2 | } |
|||
98 | |||||
99 | /** |
||||
100 | * {@inheritDoc} |
||||
101 | */ |
||||
102 | public function buildConfig(ArrayNodeDefinition $root): void |
||||
103 | { |
||||
104 | $config = $root |
||||
105 | ->info('Enable/Disable namespace for queries, subscriptions & mutations') |
||||
106 | ->canBeDisabled() |
||||
107 | ->children(); |
||||
108 | |||||
109 | $config->scalarNode('namespace'); |
||||
110 | $config->scalarNode('alias'); |
||||
111 | $config->scalarNode('node'); |
||||
112 | $config->scalarNode('bundle'); |
||||
113 | } |
||||
114 | |||||
115 | /** |
||||
116 | * {@inheritdoc} |
||||
117 | */ |
||||
118 | 2 | public function configure(DefinitionInterface $definition, Endpoint $endpoint, array $config): void |
|||
119 | { |
||||
120 | 2 | $node = null; |
|||
121 | 2 | $nodeClass = null; |
|||
122 | |||||
123 | 2 | if (!($config['enabled'] ?? true)) { |
|||
124 | return; |
||||
125 | } |
||||
126 | |||||
127 | 2 | if ($definition instanceof NodeAwareDefinitionInterface && isset($this->globalConfig['nodes']['enabled']) && $definition->getNode()) { |
|||
128 | 2 | $node = $definition->getNode(); |
|||
129 | |||||
130 | 2 | if (class_exists($node)) { |
|||
131 | $nodeClass = $node; |
||||
132 | } else { |
||||
133 | 2 | $nodeClass = $endpoint->getClassForType($node); |
|||
134 | } |
||||
135 | |||||
136 | 2 | if (isset($this->globalConfig['nodes']['aliases'][$node])) { |
|||
137 | 1 | $node = $this->globalConfig['nodes']['aliases'][$node]; |
|||
138 | } |
||||
139 | |||||
140 | 2 | if ($node && \in_array($node, $this->globalConfig['nodes']['ignore'] ?? [], true)) { |
|||
141 | $node = null; |
||||
142 | } |
||||
143 | } |
||||
144 | |||||
145 | 2 | $bundle = null; |
|||
146 | 2 | if ($this->globalConfig['bundles']['enabled'] ?? false) { |
|||
147 | 2 | if ($node && $nodeClass && $endpoint->hasType($node)) { |
|||
148 | 2 | preg_match_all('/\\\\?(\w+Bundle)\\\\/', $nodeClass, $matches); |
|||
149 | 2 | if ($matches) { |
|||
150 | 2 | $bundle = current(array_reverse($matches[1])); |
|||
151 | } |
||||
152 | |||||
153 | 2 | if (isset($this->globalConfig['bundles']['aliases'][$bundle])) { |
|||
154 | 2 | $bundle = $this->globalConfig['bundles']['aliases'][$bundle]; |
|||
155 | } |
||||
156 | |||||
157 | 2 | if ($bundle && \in_array($bundle, $this->globalConfig['bundles']['ignore'] ?? [], true)) { |
|||
158 | 2 | $bundle = null; |
|||
159 | } |
||||
160 | |||||
161 | 2 | if ($bundle) { |
|||
162 | 2 | $bundle = preg_replace('/Bundle$/', null, $bundle); |
|||
163 | } |
||||
164 | } |
||||
165 | } |
||||
166 | |||||
167 | 2 | $node = $config['node'] ?? $node; |
|||
168 | 2 | $bundle = $config['bundle'] ?? $bundle; |
|||
169 | |||||
170 | 2 | if ($bundle || $node) { |
|||
171 | 2 | $config = $definition->getMeta('namespace', []); |
|||
172 | 2 | $definition->setMeta( |
|||
173 | 2 | 'namespace', |
|||
174 | 2 | array_merge( |
|||
175 | [ |
||||
176 | 2 | 'bundle' => $bundle, |
|||
177 | 2 | 'node' => $node, |
|||
178 | ], |
||||
179 | 2 | $config |
|||
180 | ) |
||||
181 | ); |
||||
182 | } |
||||
183 | 2 | } |
|||
184 | |||||
185 | /** |
||||
186 | * {@inheritDoc} |
||||
187 | */ |
||||
188 | 1 | public function configureEndpoint(Endpoint $endpoint): void |
|||
189 | { |
||||
190 | 1 | $groupByBundle = $this->globalConfig['bundles']['enabled'] ?? false; |
|||
191 | 1 | $groupByNode = $this->globalConfig['bundles']['enabled'] ?? false; |
|||
192 | 1 | if ($groupByBundle || $groupByNode) { |
|||
193 | 1 | $endpoint->setQueries($this->namespaceDefinitions($endpoint->allQueries(), $endpoint)); |
|||
194 | 1 | $endpoint->setMutations($this->namespaceDefinitions($endpoint->allMutations(), $endpoint)); |
|||
195 | 1 | $endpoint->setSubscriptions($this->namespaceDefinitions($endpoint->allSubscriptions(), $endpoint)); |
|||
196 | } |
||||
197 | 1 | } |
|||
198 | |||||
199 | /** |
||||
200 | * @param array $definitions |
||||
201 | * @param Endpoint $endpoint |
||||
202 | * |
||||
203 | * @return array |
||||
204 | */ |
||||
205 | 1 | private function namespaceDefinitions(array $definitions, Endpoint $endpoint): array |
|||
206 | { |
||||
207 | 1 | $namespacedDefinitions = []; |
|||
208 | /** @var DefinitionInterface $definition */ |
||||
209 | 1 | foreach ($definitions as $definition) { |
|||
210 | 1 | if (!$definition->hasMeta('namespace') || !$definition->getMeta('namespace')) { |
|||
211 | 1 | $namespacedDefinitions[$definition->getName()] = $definition; |
|||
212 | 1 | continue; |
|||
213 | } |
||||
214 | |||||
215 | 1 | $root = null; |
|||
216 | 1 | $parent = null; |
|||
217 | 1 | $namespaceConfig = $definition->getMeta('namespace'); |
|||
218 | 1 | $namespacePath = $namespaceConfig['namespace'] ?? null; |
|||
219 | 1 | if ($namespacePath) { |
|||
220 | 1 | $querySuffix = $this->globalConfig['nodes']['query_suffix'] ?? 'Query'; |
|||
221 | 1 | $mutationSuffix = $this->globalConfig['nodes']['mutation_suffix'] ?? 'Mutation'; |
|||
222 | 1 | $subscriptionSuffix = $this->globalConfig['nodes']['subscription_suffix'] ?? 'Subscription'; |
|||
223 | 1 | $namespaces = explode('/', $namespacePath); |
|||
224 | 1 | foreach ($namespaces as $namespace) { |
|||
225 | 1 | $name = lcfirst($namespace); |
|||
226 | 1 | $suffix = $querySuffix; |
|||
227 | 1 | if ($definition instanceof MutationDefinition) { |
|||
228 | $suffix = $mutationSuffix; |
||||
229 | } |
||||
230 | 1 | if ($definition instanceof SubscriptionDefinition) { |
|||
231 | $suffix = $subscriptionSuffix; |
||||
232 | } |
||||
233 | 1 | $typeName = ucfirst($name).$suffix; |
|||
234 | 1 | if (!$root) { |
|||
235 | 1 | if (isset($namespacedDefinitions[$name])) { |
|||
236 | $root = $namespacedDefinitions[$name]; |
||||
237 | } else { |
||||
238 | 1 | $root = $this->createRootNamespace($definition, $name, $typeName, $endpoint); |
|||
239 | } |
||||
240 | 1 | $parent = $endpoint->getType($root->getType()); |
|||
241 | 1 | $namespacedDefinitions[$root->getName()] = $root; |
|||
242 | } else { |
||||
243 | 1 | $parent = $this->createChildNamespace($parent, $name, $typeName, $endpoint); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
244 | } |
||||
245 | } |
||||
246 | 1 | if ($alias = $namespaceConfig['alias'] ?? null) { |
|||
247 | $definition->setName($alias); |
||||
248 | } |
||||
249 | 1 | $this->addDefinitionToNamespace($parent, $definition, $definition->getName()); |
|||
0 ignored issues
–
show
It seems like
$parent can also be of type null ; however, parameter $fieldsAwareDefinition of Ynlo\GraphQLBundle\Defin...DefinitionToNamespace() does only seem to accept Ynlo\GraphQLBundle\Defin...wareDefinitionInterface , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
250 | 1 | continue; |
|||
251 | } |
||||
252 | |||||
253 | 1 | if ($bundle = $namespaceConfig['bundle'] ?? null) { |
|||
254 | 1 | $bundleQuerySuffix = $this->globalConfig['bundle']['query_suffix'] ?? 'BundleQuery'; |
|||
255 | 1 | $bundleMutationSuffix = $this->globalConfig['bundle']['mutation_suffix'] ?? 'BundleMutation'; |
|||
256 | 1 | $bundleSubscriptionSuffix = $this->globalConfig['bundle']['subscription_suffix'] ?? 'BundleSubscription'; |
|||
257 | 1 | $name = lcfirst($bundle); |
|||
258 | 1 | $suffix = $bundleQuerySuffix; |
|||
259 | 1 | if ($definition instanceof MutationDefinition) { |
|||
260 | 1 | $suffix = $bundleMutationSuffix; |
|||
261 | } |
||||
262 | 1 | if ($definition instanceof SubscriptionDefinition) { |
|||
263 | $suffix = $bundleSubscriptionSuffix; |
||||
264 | } |
||||
265 | 1 | $typeName = ucfirst($name).$suffix; |
|||
266 | |||||
267 | 1 | if (isset($namespacedDefinitions[$name])) { |
|||
268 | 1 | $root = $namespacedDefinitions[$name]; |
|||
269 | } else { |
||||
270 | 1 | $root = $this->createRootNamespace($definition, $name, $typeName, $endpoint); |
|||
271 | } |
||||
272 | 1 | $parent = $endpoint->getType($root->getType()); |
|||
273 | } |
||||
274 | |||||
275 | 1 | if ($nodeName = $namespaceConfig['node'] ?? null) { |
|||
276 | 1 | if ($endpoint->hasTypeForClass($nodeName)) { |
|||
277 | 1 | $nodeName = $endpoint->getTypeForClass($nodeName); |
|||
278 | } |
||||
279 | |||||
280 | 1 | $name = Inflector::pluralize(lcfirst($nodeName)); |
|||
281 | |||||
282 | 1 | $querySuffix = $this->globalConfig['nodes']['query_suffix'] ?? 'Query'; |
|||
283 | 1 | $mutationSuffix = $this->globalConfig['nodes']['mutation_suffix'] ?? 'Mutation'; |
|||
284 | 1 | $subscriptionSuffix = $this->globalConfig['nodes']['subscription_suffix'] ?? 'Subscription'; |
|||
285 | |||||
286 | 1 | $suffix = $querySuffix; |
|||
287 | 1 | if ($definition instanceof MutationDefinition) { |
|||
288 | 1 | $suffix = $mutationSuffix; |
|||
289 | } |
||||
290 | 1 | if ($definition instanceof SubscriptionDefinition) { |
|||
291 | $suffix = $subscriptionSuffix; |
||||
292 | } |
||||
293 | |||||
294 | 1 | $typeName = ucfirst($nodeName).$suffix; |
|||
295 | 1 | if (!$root) { |
|||
296 | 1 | if (isset($namespacedDefinitions[$name])) { |
|||
297 | 1 | $root = $namespacedDefinitions[$name]; |
|||
298 | } else { |
||||
299 | 1 | $root = $this->createRootNamespace($definition, $name, $typeName, $endpoint); |
|||
300 | } |
||||
301 | 1 | $parent = $endpoint->getType($root->getType()); |
|||
302 | 1 | } elseif ($parent) { |
|||
303 | 1 | $parent = $this->createChildNamespace($parent, $name, $typeName, $endpoint); |
|||
304 | } |
||||
305 | |||||
306 | 1 | if ($alias = $namespaceConfig['alias'] ?? null) { |
|||
307 | $originName = $definition->getName(); |
||||
308 | $definition->setName($alias); |
||||
309 | } else { |
||||
310 | //remove node suffix on namespaced definitions |
||||
311 | 1 | $originName = $definition->getName(); |
|||
312 | 1 | $definition->setName(preg_replace(sprintf("/(\w+)%s$/", $nodeName), '$1', $definition->getName())); |
|||
313 | 1 | $definition->setName(preg_replace(sprintf("/(\w+)%s$/", Inflector::pluralize($nodeName)), '$1', $definition->getName())); |
|||
314 | } |
||||
315 | } |
||||
316 | |||||
317 | 1 | if ($root && $parent) { |
|||
318 | 1 | $this->addDefinitionToNamespace($parent, $definition, $originName ?? null); |
|||
319 | 1 | $namespacedDefinitions[$root->getName()] = $root; |
|||
320 | } else { |
||||
321 | $namespacedDefinitions[$definition->getName()] = $definition; |
||||
322 | } |
||||
323 | } |
||||
324 | |||||
325 | 1 | return $namespacedDefinitions; |
|||
326 | } |
||||
327 | |||||
328 | /** |
||||
329 | * @param FieldsAwareDefinitionInterface $fieldsAwareDefinition |
||||
330 | * @param ExecutableDefinitionInterface $definition |
||||
331 | * @param string $originName |
||||
332 | */ |
||||
333 | 1 | private function addDefinitionToNamespace(FieldsAwareDefinitionInterface $fieldsAwareDefinition, ExecutableDefinitionInterface $definition, $originName): void |
|||
334 | { |
||||
335 | 1 | $field = new FieldDefinition(); |
|||
336 | 1 | $field->setName($definition->getName()); |
|||
337 | 1 | $field->setOriginName($originName); |
|||
338 | 1 | $field->setDescription($definition->getDescription()); |
|||
339 | 1 | $field->setDeprecationReason($definition->getDeprecationReason()); |
|||
340 | 1 | $field->setType($definition->getType()); |
|||
341 | 1 | $field->setResolver($definition->getResolver()); |
|||
342 | 1 | $field->setArguments($definition->getArguments()); |
|||
343 | 1 | $field->setList($definition->isList()); |
|||
344 | 1 | $field->setMetas($definition->getMetas()); |
|||
345 | 1 | $field->setNode($definition->getNode()); |
|||
346 | 1 | $field->setComplexity($definition->getComplexity()); |
|||
0 ignored issues
–
show
The method
getComplexity() does not exist on Ynlo\GraphQLBundle\Defin...ableDefinitionInterface . Since it exists in all sub-types, consider adding an abstract or default implementation to Ynlo\GraphQLBundle\Defin...ableDefinitionInterface .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
347 | 1 | $fieldsAwareDefinition->addField($field); |
|||
348 | 1 | } |
|||
349 | |||||
350 | /** |
||||
351 | * @param ObjectDefinitionInterface $parent parent definition to add a child field |
||||
352 | * @param string $name name of the field |
||||
353 | * @param string $typeName name of the type to create |
||||
354 | * @param Endpoint $endpoint Endpoint instance to extract definitions |
||||
355 | * |
||||
356 | * @return ObjectDefinition |
||||
357 | */ |
||||
358 | 1 | private function createChildNamespace(ObjectDefinitionInterface $parent, string $name, string $typeName, Endpoint $endpoint): ObjectDefinition |
|||
359 | { |
||||
360 | 1 | $child = new FieldDefinition(); |
|||
361 | 1 | $child->setName($name); |
|||
362 | 1 | $child->setResolver(EmptyObjectResolver::class); |
|||
363 | |||||
364 | 1 | $type = new ObjectDefinition(); |
|||
365 | 1 | $type->setName($typeName); |
|||
366 | 1 | if ($endpoint->hasType($type->getName())) { |
|||
367 | 1 | $type = $endpoint->getType($type->getName()); |
|||
368 | } else { |
||||
369 | 1 | $endpoint->add($type); |
|||
370 | } |
||||
371 | |||||
372 | 1 | $child->setType($type->getName()); |
|||
373 | 1 | $parent->addField($child); |
|||
374 | |||||
375 | 1 | return $type; |
|||
0 ignored issues
–
show
|
|||||
376 | } |
||||
377 | |||||
378 | /** |
||||
379 | * @param DefinitionInterface $definition Definition to create the root |
||||
380 | * @param string $name name of the root field |
||||
381 | * @param string $typeName name for the root type |
||||
382 | * @param Endpoint $endpoint Endpoint interface to extract existent definitions |
||||
383 | * |
||||
384 | * @return ExecutableDefinitionInterface |
||||
385 | */ |
||||
386 | 1 | private function createRootNamespace($definition, $name, $typeName, Endpoint $endpoint): ExecutableDefinitionInterface |
|||
387 | { |
||||
388 | 1 | $class = get_class($definition); |
|||
389 | |||||
390 | /** @var ExecutableDefinitionInterface $rootDefinition */ |
||||
391 | 1 | $rootDefinition = new $class(); |
|||
392 | 1 | $rootDefinition->setName($name); |
|||
393 | 1 | $rootDefinition->setResolver(EmptyObjectResolver::class); |
|||
394 | |||||
395 | 1 | $type = new ObjectDefinition(); |
|||
396 | 1 | $type->setName($typeName); |
|||
397 | 1 | if ($endpoint->hasType($type->getName())) { |
|||
398 | $type = $endpoint->getType($type->getName()); |
||||
399 | } else { |
||||
400 | 1 | $endpoint->add($type); |
|||
401 | } |
||||
402 | |||||
403 | 1 | $rootDefinition->setType($type->getName()); |
|||
404 | |||||
405 | 1 | return $rootDefinition; |
|||
406 | } |
||||
407 | } |
||||
408 |