Passed
Push — master ( 38b002...67e4f3 )
by Rafael
05:37
created

NamespaceDefinitionPlugin::createChildNamespace()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 18
c 0
b 0
f 0
ccs 12
cts 12
cp 1
rs 9.8666
cc 2
nc 2
nop 4
crap 2
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
$parent of type null is incompatible with the type Ynlo\GraphQLBundle\Defin...jectDefinitionInterface expected by parameter $parent of Ynlo\GraphQLBundle\Defin...:createChildNamespace(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

243
                        $parent = $this->createChildNamespace(/** @scrutinizer ignore-type */ $parent, $name, $typeName, $endpoint);
Loading history...
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
Bug introduced by
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 ignore-type  annotation

249
                $this->addDefinitionToNamespace(/** @scrutinizer ignore-type */ $parent, $definition, $definition->getName());
Loading history...
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 1
                $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
Bug introduced by
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 ignore-call  annotation

346
        $field->setComplexity($definition->/** @scrutinizer ignore-call */ getComplexity());
Loading history...
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
Bug Best Practice introduced by
The expression return $type could return the type Ynlo\GraphQLBundle\Definition\DefinitionInterface which includes types incompatible with the type-hinted return Ynlo\GraphQLBundle\Definition\ObjectDefinition. Consider adding an additional type-check to rule them out.
Loading history...
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