Completed
Push — master ( fcf75e...aa8db4 )
by Rafael
03:53
created

createChildNamespace()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 12

Duplication

Lines 18
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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