Completed
Push — master ( ccadb8...77e09c )
by Rafael
08:07
created

addDefinitionToNamespace()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 11
c 0
b 0
f 0
ccs 10
cts 10
cp 1
rs 9.4285
cc 1
eloc 9
nc 1
nop 2
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
     */
43 21
    public function __construct($config = [])
44
    {
45 21
        $this->globalConfig = $config;
46 21
    }
47
48
    /**
49
     * {@inheritDoc}
50
     */
51 21
    public function buildConfig(ArrayNodeDefinition $root)
52
    {
53
        $root
54 21
            ->info('Enable/Disable namespace for queries and mutations')
55 21
            ->canBeDisabled()
56 21
            ->children();
57 21
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62 21
    public function configure(DefinitionInterface $definition, Endpoint $endpoint, array $config)
63
    {
64 21
        $node = null;
65 21
        $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 21
            if (class_exists($node)) {
75
                $nodeClass = $node;
76
            } else {
77 21
                $nodeClass = $endpoint->getClassForType($node);
78
            }
79
80 21
            if (!is_a($nodeClass, NodeInterface::class, true)) {
81
                return;
82
            }
83
84 21
            if (isset($this->globalConfig['nodes']['aliases'][$node])) {
85
                $node = $this->globalConfig['nodes']['aliases'][$node];
86
            }
87
88 21
            if ($node && in_array($node, $this->globalConfig['nodes']['ignore'])) {
89 21
                $node = null;
90
            }
91
        }
92
93 21
        $bundle = null;
94 21
        if ($this->globalConfig['bundles']['enabled'] ?? false) {
95 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...
96 21
                if ($endpoint->hasType($node) && $nodeClass) {
97 21
                    preg_match_all('/\\\\?(\w+Bundle)\\\\/', $nodeClass, $matches);
98 21
                    if ($matches) {
99 21
                        $bundle = current(array_reverse($matches[1]));
100
                    }
101
102 21
                    if (isset($this->globalConfig['bundles']['aliases'][$bundle])) {
103
                        $bundle = $this->globalConfig['bundles']['aliases'][$bundle];
104
                    }
105
106 21
                    if ($bundle && in_array($bundle, $this->globalConfig['bundles']['ignore'])) {
107 21
                        $bundle = null;
108
                    }
109
110 21
                    if ($bundle) {
111
                        $bundle = preg_replace('/Bundle$/', null, $bundle);
112
                    }
113
                }
114
            }
115
        }
116
117 21
        if ($bundle || $node) {
118 21
            $definition->setMeta('namespace', ['bundle' => $bundle, 'node' => $node]);
119
        }
120 21
    }
121
122
    /**
123
     * {@inheritDoc}
124
     */
125 21
    public function configureEndpoint(Endpoint $endpoint)
126
    {
127 21
        $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 21
    }
134
135
    /**
136
     * @param array    $definitions
137
     * @param Endpoint $endpoint
138
     *
139
     * @return array
140
     */
141 21
    private function namespaceDefinitions($definitions, Endpoint $endpoint)
142
    {
143 21
        $namespacedDefinitions = [];
144
        /** @var DefinitionInterface $definition */
145 21
        foreach ($definitions as $definition) {
146 21
            if (!$definition->hasMeta('namespace') || !$definition->getMeta('namespace')) {
147 21
                $namespacedDefinitions[] = $definition;
148 21
                continue;
149
            }
150
151 21
            $root = null;
152 21
            $parent = null;
153 21
            $namespace = $definition->getMeta('namespace');
154 21
            if ($bundle = $namespace['bundle'] ?? null) {
155
                $bundleSuffix = $this->globalConfig['bundles']['suffix'] ?? 'Bundle';
156
                $name = lcfirst($bundle);
157
                $typeName = ucfirst($name).$bundleSuffix;
158
                $root = $this->createRootNamespace(get_class($definition), $name, $typeName, $endpoint);
159
                $parent = $endpoint->getType($root->getType());
160
            }
161
162 21
            if ($nodeName = $namespace['node'] ?? null) {
163 21
                if ($endpoint->hasTypeForClass($nodeName)) {
164
                    $nodeName = $endpoint->getTypeForClass($nodeName);
165
                }
166
167 21
                $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
172 21
                $typeName = ucfirst($nodeName).(($definition instanceof MutationDefinition) ? $mutationSuffix : $querySuffix);
173 21
                if (!$root) {
174 21
                    $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 21
                $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
            }
185
186 21
            if ($root && $parent) {
187 21
                $this->addDefinitionToNamespace($parent, $definition);
188 21
                $namespacedDefinitions[] = $root;
189
            }
190
        }
191
192 21
        return $namespacedDefinitions;
193
    }
194
195
    /**
196
     * @param FieldsAwareDefinitionInterface $fieldsAwareDefinition
197
     * @param ExecutableDefinitionInterface  $definition
198
     */
199 21
    private function addDefinitionToNamespace(FieldsAwareDefinitionInterface $fieldsAwareDefinition, ExecutableDefinitionInterface $definition)
200
    {
201 21
        $field = new FieldDefinition();
202 21
        $field->setName($definition->getName());
203 21
        $field->setType($definition->getType());
204 21
        $field->setResolver($definition->getResolver());
205 21
        $field->setArguments($definition->getArguments());
206 21
        $field->setList($definition->isList());
207 21
        $field->setMetas($definition->getMetas());
208 21
        $field->setNode($definition->getNode());
209 21
        $fieldsAwareDefinition->addField($field);
210 21
    }
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
            $endpoint->add($type);
232
        }
233
234
        $child->setType($type->getName());
235
        $parent->addField($child);
236
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
    }
239
240
    /**
241
     * @param string   $rootType Class of the root type to create QueryDefinition or MutationDefinition
242
     * @param string   $name     name of the root field
243
     * @param string   $typeName name for the root type
244
     * @param Endpoint $endpoint Endpoint interface to extract existent definitions
245
     *
246
     * @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 21
        $rootDefinition = new $rootType();
252 21
        $rootDefinition->setName($name);
253 21
        $rootDefinition->setResolver(EmptyObjectResolver::class);
254
255 21
        $type = new ObjectDefinition();
256 21
        $type->setName($typeName);
257 21
        if ($endpoint->hasType($type->getName())) {
258 21
            $type = $endpoint->getType($type->getName());
259
        } else {
260 21
            $endpoint->add($type);
261
        }
262
263 21
        $rootDefinition->setType($type->getName());
264
265 21
        return $rootDefinition;
266
    }
267
}
268