Completed
Push — master ( 2e8c19...ccadb8 )
by Rafael
11:08
created

NamespaceDefinitionExtension::buildConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1
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
    /**
34
     * @var array
35
     */
36
    protected $globalConfig = [];
37
38
    /**
39
     * PaginationDefinitionExtension constructor.
40
     *
41
     * @param array $config
42 21
     */
43
    public function __construct($config = [])
44 21
    {
45 21
        $this->globalConfig = $config;
46
    }
47
48
    /**
49
     * {@inheritDoc}
50 21
     */
51
    public function buildConfig(ArrayNodeDefinition $root)
52 21
    {
53 21
        $root
54 21
            ->info('Enable/Disable namespace for queries and mutations')
55 21
            ->canBeDisabled()
56
            ->children();
57 21
    }
58
59
    /**
60 21
     * {@inheritdoc}
61
     */
62
    public function configure(DefinitionInterface $definition, Endpoint $endpoint, array $config)
63 21
    {
64
        $node = null;
65
        $nodeClass = null;
66
67 21
        if (!($config['enabled'] ?? true)) {
68
            return;
69
        }
70
71 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...
72 21
            $node = $definition->getNode();
73
74
            if (class_exists($node)) {
75
                $nodeClass = $node;
76 21
            } else {
77 21
                $nodeClass = $endpoint->getClassForType($node);
78 21
            }
79 21
80 21
            if (!is_a($nodeClass, NodeInterface::class, true)) {
81 21
                return;
82 21
            }
83
84
            if (isset($this->globalConfig['nodes']['aliases'][$node])) {
85 21
                $node = $this->globalConfig['nodes']['aliases'][$node];
86
            }
87
88
            if ($node && in_array($node, $this->globalConfig['nodes']['ignore'])) {
89 21
                $node = null;
90 21
            }
91
        }
92
93 21
        $bundle = null;
94
        if ($this->globalConfig['bundles']['enabled'] ?? false) {
95
            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...
96
                if ($endpoint->hasType($node) && $nodeClass) {
97
                    preg_match_all('/\\\\?(\w+Bundle)\\\\/', $nodeClass, $matches);
98
                    if ($matches) {
99
                        $bundle = current(array_reverse($matches[1]));
100 21
                    }
101 21
102
                    if (isset($this->globalConfig['bundles']['aliases'][$bundle])) {
103 21
                        $bundle = $this->globalConfig['bundles']['aliases'][$bundle];
104
                    }
105
106
                    if ($bundle && in_array($bundle, $this->globalConfig['bundles']['ignore'])) {
107
                        $bundle = null;
108 21
                    }
109
110 21
                    if ($bundle) {
111 21
                        $bundle = preg_replace('/Bundle$/', null, $bundle);
112 21
                    }
113 21
                }
114 21
            }
115
        }
116 21
117
        if ($bundle || $node) {
118
            $definition->setMeta('namespace', ['bundle' => $bundle, 'node' => $node]);
119
        }
120
    }
121
122
    /**
123
     * {@inheritDoc}
124 21
     */
125
    public function configureEndpoint(Endpoint $endpoint)
126 21
    {
127
        $groupByBundle = $this->globalConfig['bundles']['enabled'] ?? false;
128 21
        $groupByNode = $this->globalConfig['bundles']['enabled'] ?? false;
129 21
        if ($groupByBundle || $groupByNode) {
130 21
            $endpoint->setQueries($this->namespaceDefinitions($endpoint->allQueries(), $endpoint));
131 21
            $endpoint->setMutations($this->namespaceDefinitions($endpoint->allMutations(), $endpoint));
132
        }
133
    }
134 21
135 21
    /**
136 21
     * @param array    $definitions
137 21
     * @param Endpoint $endpoint
138
     *
139
     * @return array
140
     */
141
    private function namespaceDefinitions($definitions, Endpoint $endpoint)
142
    {
143
        $namespacedDefinitions = [];
144
        /** @var DefinitionInterface $definition */
145 21
        foreach ($definitions as $definition) {
146 21
            if (!$definition->hasMeta('namespace') || !$definition->getMeta('namespace')) {
147
                $namespacedDefinitions[] = $definition;
148
                continue;
149
            }
150 21
151
            $root = null;
152 21
            $parent = null;
153 21
            $namespace = $definition->getMeta('namespace');
154
            if ($bundle = $namespace['bundle'] ?? null) {
155 21
                $bundleSuffix = $this->globalConfig['bundles']['suffix'] ?? 'Bundle';
156 21
                $name = lcfirst($bundle);
157 21
                $typeName = ucfirst($name).$bundleSuffix;
158 21
                $root = $this->createRootNamespace(get_class($definition), $name, $typeName, $endpoint);
159
                $parent = $endpoint->getType($root->getType());
160
            }
161
162
            if ($nodeName = $namespace['node'] ?? null) {
163
                if ($endpoint->hasTypeForClass($nodeName)) {
164 21
                    $nodeName = $endpoint->getTypeForClass($nodeName);
165 21
                }
166
167
                $name = Inflector::pluralize(lcfirst($nodeName));
168
169 21
                $querySuffix = $this->globalConfig['nodes']['query_suffix'] ?? 'Query';
170 21
                $mutationSuffix = $this->globalConfig['nodes']['mutation_suffix'] ?? 'Mutation';
171 21
172
                $typeName = ucfirst($nodeName).(($definition instanceof MutationDefinition) ? $mutationSuffix : $querySuffix);
173
                if (!$root) {
174
                    $root = $this->createRootNamespace(get_class($definition), $name, $typeName, $endpoint);
175 21
                    $parent = $endpoint->getType($root->getType());
176
                } elseif ($parent) {
177
                    $parent = $this->createChildNamespace($parent, $name, $typeName, $endpoint);
178
                }
179
180
                //remove node suffix on namespaced definitions
181
                $definition->setName(preg_replace(sprintf("/(\w+)%s$/", $nodeName), '$1', $definition->getName()));
182 21
                $definition->setName(preg_replace(sprintf("/(\w+)%s$/", Inflector::pluralize($nodeName)), '$1', $definition->getName()));
183
184 21
            }
185 21
186 21
            if ($root && $parent) {
187 21
                $this->addDefinitionToNamespace($parent, $definition);
188 21
                $namespacedDefinitions[] = $root;
189 21
            }
190 21
        }
191 21
192 21
        return $namespacedDefinitions;
193 21
    }
194
195
    /**
196
     * @param FieldsAwareDefinitionInterface $fieldsAwareDefinition
197
     * @param ExecutableDefinitionInterface  $definition
198
     */
199
    private function addDefinitionToNamespace(FieldsAwareDefinitionInterface $fieldsAwareDefinition, ExecutableDefinitionInterface $definition)
200
    {
201
        $field = new FieldDefinition();
202
        $field->setName($definition->getName());
203
        $field->setType($definition->getType());
204
        $field->setResolver($definition->getResolver());
205
        $field->setArguments($definition->getArguments());
206
        $field->setList($definition->isList());
207
        $field->setMetas($definition->getMetas());
208
        $field->setNode($definition->getNode());
209
        $fieldsAwareDefinition->addField($field);
210
    }
211
212
    /**
213
     * @param ObjectDefinitionInterface $parent   parent definition to add a child field
214
     * @param string                    $name     name of the field
215
     * @param string                    $typeName name of the type to create
216
     * @param Endpoint                  $endpoint Endpoint instance to extract definitions
217
     *
218
     * @return ObjectDefinition
219
     */
220 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...
221
    {
222
        $child = new FieldDefinition();
223
        $child->setName($name);
224
        $child->setResolver(EmptyObjectResolver::class);
225
226
        $type = new ObjectDefinition();
227
        $type->setName($typeName);
228
        if ($endpoint->hasType($type->getName())) {
229
            $type = $endpoint->getType($type->getName());
230
        } else {
231 21
            $endpoint->add($type);
232
        }
233
234 21
        $child->setType($type->getName());
235 21
        $parent->addField($child);
236 21
237
        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...
238 21
    }
239 21
240 21
    /**
241 21
     * @param string   $rootType Class of the root type to create QueryDefinition or MutationDefinition
242
     * @param string   $name     name of the root field
243 21
     * @param string   $typeName name for the root type
244
     * @param Endpoint $endpoint Endpoint interface to extract existent definitions
245
     *
246 21
     * @return ExecutableDefinitionInterface
247
     */
248 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...
249
    {
250
        /** @var ExecutableDefinitionInterface $rootDefinition */
251
        $rootDefinition = new $rootType();
252
        $rootDefinition->setName($name);
253
        $rootDefinition->setResolver(EmptyObjectResolver::class);
254
255
        $type = new ObjectDefinition();
256
        $type->setName($typeName);
257
        if ($endpoint->hasType($type->getName())) {
258
            $type = $endpoint->getType($type->getName());
259
        } else {
260
            $endpoint->add($type);
261
        }
262
263
        $rootDefinition->setType($type->getName());
264
265
        return $rootDefinition;
266
    }
267
}
268