Completed
Pull Request — master (#7)
by Yonel Ceruto
10:45 queued 04:14
created

NamespaceDefinitionExtension   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 218
Duplicated Lines 0 %

Test Coverage

Coverage 76.72%

Importance

Changes 0
Metric Value
wmc 44
dl 0
loc 218
ccs 89
cts 116
cp 0.7672
rs 8.3396
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A createRootNamespace() 0 18 2
A addDefinitionToNamespace() 0 13 1
D configure() 0 55 21
C namespaceDefinitions() 0 53 13
A createChildNamespace() 0 18 2
A configureEndpoint() 0 7 3
A buildConfig() 0 6 1
A __construct() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like NamespaceDefinitionExtension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use NamespaceDefinitionExtension, and based on these observations, apply Extract Interface, too.

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\Extension;
12
13
use Doctrine\Common\Util\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\Model\NodeInterface;
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 NamespaceDefinitionExtension extends AbstractDefinitionExtension
32
{
33
    protected $globalConfig = [];
34
35 22
    public function __construct(array $config = [])
36
    {
37 22
        $this->globalConfig = $config;
38 22
    }
39
40
    /**
41
     * {@inheritDoc}
42
     */
43 22
    public function buildConfig(ArrayNodeDefinition $root): void
44
    {
45
        $root
46 22
            ->info('Enable/Disable namespace for queries and mutations')
47 22
            ->canBeDisabled()
48 22
            ->children();
49 22
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54 22
    public function configure(DefinitionInterface $definition, Endpoint $endpoint, array $config): void
55
    {
56 22
        $node = null;
57 22
        $nodeClass = null;
58
59 22
        if (!($config['enabled'] ?? true)) {
60
            return;
61
        }
62
63 22
        if ($definition instanceof NodeAwareDefinitionInterface && isset($this->globalConfig['nodes']['enabled']) && $definition->getNode()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $definition->getNode() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
64 22
            $node = $definition->getNode();
65
66 22
            if (class_exists($node)) {
67
                $nodeClass = $node;
68
            } else {
69 22
                $nodeClass = $endpoint->getClassForType($node);
70
            }
71
72 22
            if (!is_a($nodeClass, NodeInterface::class, true)) {
73
                return;
74
            }
75
76 22
            if (isset($this->globalConfig['nodes']['aliases'][$node])) {
77
                $node = $this->globalConfig['nodes']['aliases'][$node];
78
            }
79
80 22
            if ($node && \in_array($node, $this->globalConfig['nodes']['ignore'], true)) {
81 22
                $node = null;
82
            }
83
        }
84
85 22
        $bundle = null;
86 22
        if ($this->globalConfig['bundles']['enabled'] ?? false) {
87 22
            if ($node && $nodeClass && $endpoint->hasType($node)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $nodeClass of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
88 22
                preg_match_all('/\\\\?(\w+Bundle)\\\\/', $nodeClass, $matches);
89 22
                if ($matches) {
90 22
                    $bundle = current(array_reverse($matches[1]));
91
                }
92
93 22
                if (isset($this->globalConfig['bundles']['aliases'][$bundle])) {
94
                    $bundle = $this->globalConfig['bundles']['aliases'][$bundle];
95
                }
96
97 22
                if ($bundle && \in_array($bundle, $this->globalConfig['bundles']['ignore'], true)) {
98 22
                    $bundle = null;
99
                }
100
101 22
                if ($bundle) {
102
                    $bundle = preg_replace('/Bundle$/', null, $bundle);
103
                }
104
            }
105
        }
106
107 22
        if ($bundle || $node) {
108 22
            $definition->setMeta('namespace', ['bundle' => $bundle, 'node' => $node]);
109
        }
110 22
    }
111
112
    /**
113
     * {@inheritDoc}
114
     */
115 22
    public function configureEndpoint(Endpoint $endpoint): void
116
    {
117 22
        $groupByBundle = $this->globalConfig['bundles']['enabled'] ?? false;
118 22
        $groupByNode = $this->globalConfig['bundles']['enabled'] ?? false;
119 22
        if ($groupByBundle || $groupByNode) {
120 22
            $endpoint->setQueries($this->namespaceDefinitions($endpoint->allQueries(), $endpoint));
121 22
            $endpoint->setMutations($this->namespaceDefinitions($endpoint->allMutations(), $endpoint));
122
        }
123 22
    }
124
125 22
    private function namespaceDefinitions(array $definitions, Endpoint $endpoint): array
126
    {
127 22
        $namespacedDefinitions = [];
128
        /** @var DefinitionInterface $definition */
129 22
        foreach ($definitions as $definition) {
130 22
            if (!$definition->hasMeta('namespace') || !$definition->getMeta('namespace')) {
131 22
                $namespacedDefinitions[] = $definition;
132 22
                continue;
133
            }
134
135 22
            $root = null;
136 22
            $parent = null;
137 22
            $namespace = $definition->getMeta('namespace');
138 22
            if ($bundle = $namespace['bundle'] ?? null) {
139
                $bundleQuerySuffix = $this->globalConfig['bundle']['query_suffix'] ?? 'BundleQuery';
140
                $bundleMutationSuffix = $this->globalConfig['bundle']['mutation_suffix'] ?? 'BundleMutation';
141
142
                $name = lcfirst($bundle);
143
                $typeName = ucfirst($name).(($definition instanceof MutationDefinition) ? $bundleMutationSuffix : $bundleQuerySuffix);
144
                $root = $this->createRootNamespace(\get_class($definition), $name, $typeName, $endpoint);
145
                $parent = $endpoint->getType($root->getType());
146
            }
147
148 22
            if ($nodeName = $namespace['node'] ?? null) {
149 22
                if ($endpoint->hasTypeForClass($nodeName)) {
150
                    $nodeName = $endpoint->getTypeForClass($nodeName);
151
                }
152
153 22
                $name = Inflector::pluralize(lcfirst($nodeName));
154
155 22
                $querySuffix = $this->globalConfig['nodes']['query_suffix'] ?? 'Query';
156 22
                $mutationSuffix = $this->globalConfig['nodes']['mutation_suffix'] ?? 'Mutation';
157
158 22
                $typeName = ucfirst($nodeName).(($definition instanceof MutationDefinition) ? $mutationSuffix : $querySuffix);
159 22
                if (!$root) {
160 22
                    $root = $this->createRootNamespace(\get_class($definition), $name, $typeName, $endpoint);
161 22
                    $parent = $endpoint->getType($root->getType());
162
                } elseif ($parent) {
163
                    $parent = $this->createChildNamespace($parent, $name, $typeName, $endpoint);
164
                }
165
166
                //remove node suffix on namespaced definitions
167 22
                $definition->setName(preg_replace(sprintf("/(\w+)%s$/", $nodeName), '$1', $definition->getName()));
168 22
                $definition->setName(preg_replace(sprintf("/(\w+)%s$/", Inflector::pluralize($nodeName)), '$1', $definition->getName()));
169
            }
170
171 22
            if ($root && $parent) {
172 22
                $this->addDefinitionToNamespace($parent, $definition);
173 22
                $namespacedDefinitions[] = $root;
174
            }
175
        }
176
177 22
        return $namespacedDefinitions;
178
    }
179
180 22
    private function addDefinitionToNamespace(FieldsAwareDefinitionInterface $fieldsAwareDefinition, ExecutableDefinitionInterface $definition)
181
    {
182 22
        $field = new FieldDefinition();
183 22
        $field->setName($definition->getName());
184 22
        $field->setType($definition->getType());
185 22
        $field->setResolver($definition->getResolver());
186 22
        $field->setArguments($definition->getArguments());
187 22
        $field->setList($definition->isList());
188 22
        $field->setMetas($definition->getMetas());
189 22
        $field->setNode($definition->getNode());
190 22
        $field->setRoles($definition->getRoles());
0 ignored issues
show
Bug introduced by
The method getRoles() 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

190
        $field->setRoles($definition->/** @scrutinizer ignore-call */ getRoles());
Loading history...
191 22
        $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

191
        $field->setComplexity($definition->/** @scrutinizer ignore-call */ getComplexity());
Loading history...
192 22
        $fieldsAwareDefinition->addField($field);
193 22
    }
194
195
    /**
196
     * @param ObjectDefinitionInterface $parent   parent definition to add a child field
197
     * @param string                    $name     name of the field
198
     * @param string                    $typeName name of the type to create
199
     * @param Endpoint                  $endpoint Endpoint instance to extract definitions
200
     *
201
     * @return ObjectDefinition
202
     */
203
    private function createChildNamespace(ObjectDefinitionInterface $parent, string $name, string $typeName, Endpoint $endpoint): ObjectDefinition
204
    {
205
        $child = new FieldDefinition();
206
        $child->setName($name);
207
        $child->setResolver(EmptyObjectResolver::class);
208
209
        $type = new ObjectDefinition();
210
        $type->setName($typeName);
211
        if ($endpoint->hasType($type->getName())) {
212
            $type = $endpoint->getType($type->getName());
213
        } else {
214
            $endpoint->add($type);
215
        }
216
217
        $child->setType($type->getName());
218
        $parent->addField($child);
219
220
        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...
221
    }
222
223
    /**
224
     * @param string   $rootType Class of the root type to create QueryDefinition or MutationDefinition
225
     * @param string   $name     name of the root field
226
     * @param string   $typeName name for the root type
227
     * @param Endpoint $endpoint Endpoint interface to extract existent definitions
228
     *
229
     * @return ExecutableDefinitionInterface
230
     */
231 22
    private function createRootNamespace($rootType, $name, $typeName, Endpoint $endpoint): ExecutableDefinitionInterface
232
    {
233
        /** @var ExecutableDefinitionInterface $rootDefinition */
234 22
        $rootDefinition = new $rootType();
235 22
        $rootDefinition->setName($name);
236 22
        $rootDefinition->setResolver(EmptyObjectResolver::class);
237
238 22
        $type = new ObjectDefinition();
239 22
        $type->setName($typeName);
240 22
        if ($endpoint->hasType($type->getName())) {
241 22
            $type = $endpoint->getType($type->getName());
242
        } else {
243 22
            $endpoint->add($type);
244
        }
245
246 22
        $rootDefinition->setType($type->getName());
247
248 22
        return $rootDefinition;
249
    }
250
}
251