Completed
Push — master ( 3ae95d...2e8c19 )
by Rafael
11:16
created

NamespaceDefinitionExtension   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 219
Duplicated Lines 16.89 %

Test Coverage

Coverage 76.64%

Importance

Changes 0
Metric Value
wmc 42
dl 37
loc 219
ccs 82
cts 107
cp 0.7664
rs 8.295
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
D configure() 0 52 21
A configureEndpoint() 0 7 3
A __construct() 0 3 1
A createRootNamespace() 18 18 2
A addDefinitionToNamespace() 0 11 1
C namespaceDefinitions() 0 52 12
A createChildNamespace() 18 18 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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 Ynlo\GraphQLBundle\Definition\DefinitionInterface;
15
use Ynlo\GraphQLBundle\Definition\ExecutableDefinitionInterface;
16
use Ynlo\GraphQLBundle\Definition\FieldDefinition;
17
use Ynlo\GraphQLBundle\Definition\FieldsAwareDefinitionInterface;
18
use Ynlo\GraphQLBundle\Definition\MutationDefinition;
19
use Ynlo\GraphQLBundle\Definition\NodeAwareDefinitionInterface;
20
use Ynlo\GraphQLBundle\Definition\ObjectDefinition;
21
use Ynlo\GraphQLBundle\Definition\ObjectDefinitionInterface;
22
use Ynlo\GraphQLBundle\Definition\Registry\Endpoint;
23
use Ynlo\GraphQLBundle\Model\NodeInterface;
24
use Ynlo\GraphQLBundle\Resolver\EmptyObjectResolver;
25
26
/**
27
 * This extension configure namespace in definitions
28
 * using definition node and bundle in the node
29
 */
30
class NamespaceDefinitionExtension extends AbstractDefinitionExtension
31
{
32
    /**
33
     * @var array
34
     */
35
    protected $globalConfig = [];
36
37
    /**
38
     * PaginationDefinitionExtension constructor.
39
     *
40
     * @param array $config
41
     */
42 21
    public function __construct($config = [])
43
    {
44 21
        $this->globalConfig = $config;
45 21
    }
46
47
    /**
48
     * {@inheritdoc}
49
     */
50 21
    public function configure(DefinitionInterface $definition, Endpoint $endpoint, array $config)
51
    {
52 21
        $node = null;
53 21
        $nodeClass = null;
54 21
        if (($this->globalConfig['nodes']['enabled'] ?? false) && $definition instanceof NodeAwareDefinitionInterface && $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...
55 21
            $node = $definition->getNode();
56
57 21
            if (class_exists($node)) {
58
                $nodeClass = $node;
59
            } else {
60 21
                $nodeClass = $endpoint->getClassForType($node);
61
            }
62
63 21
            if (!is_a($nodeClass, NodeInterface::class, true)) {
64
                return;
65
            }
66
67 21
            if (isset($this->globalConfig['nodes']['aliases'][$node])) {
68
                $node = $this->globalConfig['nodes']['aliases'][$node];
69
            }
70
71 21
            if ($node && in_array($node, $this->globalConfig['nodes']['ignore'])) {
72 21
                $node = null;
73
            }
74
        }
75
76 21
        $bundle = null;
77 21
        if ($this->globalConfig['bundles']['enabled'] ?? false) {
78 21
            if ($node && $nodeClass) {
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...
79 21
                if ($endpoint->hasType($node) && $nodeClass) {
80 21
                    preg_match_all('/\\\\?(\w+Bundle)\\\\/', $nodeClass, $matches);
81 21
                    if ($matches) {
82 21
                        $bundle = current(array_reverse($matches[1]));
83
                    }
84
85 21
                    if (isset($this->globalConfig['bundles']['aliases'][$bundle])) {
86
                        $bundle = $this->globalConfig['bundles']['aliases'][$bundle];
87
                    }
88
89 21
                    if ($bundle && in_array($bundle, $this->globalConfig['bundles']['ignore'])) {
90 21
                        $bundle = null;
91
                    }
92
93 21
                    if ($bundle) {
94
                        $bundle = preg_replace('/Bundle$/', null, $bundle);
95
                    }
96
                }
97
            }
98
        }
99
100 21
        if ($bundle || $node) {
101 21
            $definition->setMeta('namespace', ['bundle' => $bundle, 'node' => $node]);
102
        }
103 21
    }
104
105
    /**
106
     * {@inheritDoc}
107
     */
108 21
    public function configureEndpoint(Endpoint $endpoint)
109
    {
110 21
        $groupByBundle = $this->globalConfig['bundles']['enabled'] ?? false;
111 21
        $groupByNode = $this->globalConfig['bundles']['enabled'] ?? false;
112 21
        if ($groupByBundle || $groupByNode) {
113 21
            $endpoint->setQueries($this->namespaceDefinitions($endpoint->allQueries(), $endpoint));
114 21
            $endpoint->setMutations($this->namespaceDefinitions($endpoint->allMutations(), $endpoint));
115
        }
116 21
    }
117
118
    /**
119
     * @param array    $definitions
120
     * @param Endpoint $endpoint
121
     *
122
     * @return array
123
     */
124 21
    private function namespaceDefinitions($definitions, Endpoint $endpoint)
125
    {
126 21
        $namespacedDefinitions = [];
127
        /** @var DefinitionInterface $definition */
128 21
        foreach ($definitions as $definition) {
129 21
            if (!$definition->hasMeta('namespace') || !$definition->getMeta('namespace')) {
130 21
                $namespacedDefinitions[] = $definition;
131 21
                continue;
132
            }
133
134 21
            $root = null;
135 21
            $parent = null;
136 21
            $namespace = $definition->getMeta('namespace');
137 21
            if ($bundle = $namespace['bundle'] ?? null) {
138
                $bundleSuffix = $this->globalConfig['bundles']['suffix'] ?? 'Bundle';
139
                $name = lcfirst($bundle);
140
                $typeName = ucfirst($name).$bundleSuffix;
141
                $root = $this->createRootNamespace(get_class($definition), $name, $typeName, $endpoint);
142
                $parent = $endpoint->getType($root->getType());
143
            }
144
145 21
            if ($nodeName = $namespace['node'] ?? null) {
146 21
                if ($endpoint->hasTypeForClass($nodeName)) {
147
                    $nodeName = $endpoint->getTypeForClass($nodeName);
148
                }
149
150 21
                $name = Inflector::pluralize(lcfirst($nodeName));
151
152 21
                $querySuffix = $this->globalConfig['nodes']['query_suffix'] ?? 'Query';
153 21
                $mutationSuffix = $this->globalConfig['nodes']['mutation_suffix'] ?? 'Mutation';
154
155 21
                $typeName = ucfirst($nodeName).(($definition instanceof MutationDefinition) ? $mutationSuffix : $querySuffix);
156 21
                if (!$root) {
157 21
                    $root = $this->createRootNamespace(get_class($definition), $name, $typeName, $endpoint);
158 21
                    $parent = $endpoint->getType($root->getType());
159
                } elseif ($parent) {
160
                    $parent = $this->createChildNamespace($parent, $name, $typeName, $endpoint);
161
                }
162
163
                //remove node suffix on namespaced definitions
164 21
                $definition->setName(preg_replace(sprintf("/(\w+)%s$/", $nodeName), '$1', $definition->getName()));
165 21
                $definition->setName(preg_replace(sprintf("/(\w+)%s$/", Inflector::pluralize($nodeName)), '$1', $definition->getName()));
166
167
            }
168
169 21
            if ($root && $parent) {
170 21
                $this->addDefinitionToNamespace($parent, $definition);
171 21
                $namespacedDefinitions[] = $root;
172
            }
173
        }
174
175 21
        return $namespacedDefinitions;
176
    }
177
178
    /**
179
     * @param FieldsAwareDefinitionInterface $fieldsAwareDefinition
180
     * @param ExecutableDefinitionInterface  $definition
181
     */
182 21
    private function addDefinitionToNamespace(FieldsAwareDefinitionInterface $fieldsAwareDefinition, ExecutableDefinitionInterface $definition)
183
    {
184 21
        $field = new FieldDefinition();
185 21
        $field->setName($definition->getName());
186 21
        $field->setType($definition->getType());
187 21
        $field->setResolver($definition->getResolver());
188 21
        $field->setArguments($definition->getArguments());
189 21
        $field->setList($definition->isList());
190 21
        $field->setMetas($definition->getMetas());
191 21
        $field->setNode($definition->getNode());
192 21
        $fieldsAwareDefinition->addField($field);
193 21
    }
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 View Code Duplication
    private function createChildNamespace(ObjectDefinitionInterface $parent, $name, $typeName, Endpoint $endpoint): ObjectDefinition
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 21 View Code Duplication
    private function createRootNamespace($rootType, $name, $typeName, Endpoint $endpoint): ExecutableDefinitionInterface
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
232
    {
233
        /** @var ExecutableDefinitionInterface $rootDefinition */
234 21
        $rootDefinition = new $rootType();
235 21
        $rootDefinition->setName($name);
236 21
        $rootDefinition->setResolver(EmptyObjectResolver::class);
237
238 21
        $type = new ObjectDefinition();
239 21
        $type->setName($typeName);
240 21
        if ($endpoint->hasType($type->getName())) {
241 21
            $type = $endpoint->getType($type->getName());
242
        } else {
243 21
            $endpoint->add($type);
244
        }
245
246 21
        $rootDefinition->setType($type->getName());
247
248 21
        return $rootDefinition;
249
    }
250
}
251