Passed
Push — master ( e85a2d...ea8174 )
by Rafael
07:43
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
     */
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
                $bundleQuerySuffix = $this->globalConfig['bundle']['query_suffix'] ?? 'BundleQuery';
156
                $bundleMutationSuffix = $this->globalConfig['bundle']['mutation_suffix'] ?? 'BundleMutation';
157
158
                $name = lcfirst($bundle);
159
                $typeName = ucfirst($name).(($definition instanceof MutationDefinition) ? $bundleMutationSuffix : $bundleQuerySuffix);
160
                $root = $this->createRootNamespace(get_class($definition), $name, $typeName, $endpoint);
161
                $parent = $endpoint->getType($root->getType());
162
            }
163
164 21
            if ($nodeName = $namespace['node'] ?? null) {
165 21
                if ($endpoint->hasTypeForClass($nodeName)) {
166
                    $nodeName = $endpoint->getTypeForClass($nodeName);
167
                }
168
169 21
                $name = Inflector::pluralize(lcfirst($nodeName));
170
171 21
                $querySuffix = $this->globalConfig['nodes']['query_suffix'] ?? 'Query';
172 21
                $mutationSuffix = $this->globalConfig['nodes']['mutation_suffix'] ?? 'Mutation';
173
174 21
                $typeName = ucfirst($nodeName).(($definition instanceof MutationDefinition) ? $mutationSuffix : $querySuffix);
175 21
                if (!$root) {
176 21
                    $root = $this->createRootNamespace(get_class($definition), $name, $typeName, $endpoint);
177 21
                    $parent = $endpoint->getType($root->getType());
178
                } elseif ($parent) {
179
                    $parent = $this->createChildNamespace($parent, $name, $typeName, $endpoint);
180
                }
181
182
                //remove node suffix on namespaced definitions
183 21
                $definition->setName(preg_replace(sprintf("/(\w+)%s$/", $nodeName), '$1', $definition->getName()));
184 21
                $definition->setName(preg_replace(sprintf("/(\w+)%s$/", Inflector::pluralize($nodeName)), '$1', $definition->getName()));
185
186
            }
187
188 21
            if ($root && $parent) {
189 21
                $this->addDefinitionToNamespace($parent, $definition);
190 21
                $namespacedDefinitions[] = $root;
191
            }
192
        }
193
194 21
        return $namespacedDefinitions;
195
    }
196
197
    /**
198
     * @param FieldsAwareDefinitionInterface $fieldsAwareDefinition
199
     * @param ExecutableDefinitionInterface  $definition
200
     */
201 21
    private function addDefinitionToNamespace(FieldsAwareDefinitionInterface $fieldsAwareDefinition, ExecutableDefinitionInterface $definition)
202
    {
203 21
        $field = new FieldDefinition();
204 21
        $field->setName($definition->getName());
205 21
        $field->setType($definition->getType());
206 21
        $field->setResolver($definition->getResolver());
207 21
        $field->setArguments($definition->getArguments());
208 21
        $field->setList($definition->isList());
209 21
        $field->setMetas($definition->getMetas());
210 21
        $field->setNode($definition->getNode());
211 21
        $fieldsAwareDefinition->addField($field);
212 21
    }
213
214
    /**
215
     * @param ObjectDefinitionInterface $parent   parent definition to add a child field
216
     * @param string                    $name     name of the field
217
     * @param string                    $typeName name of the type to create
218
     * @param Endpoint                  $endpoint Endpoint instance to extract definitions
219
     *
220
     * @return ObjectDefinition
221
     */
222
    private function createChildNamespace(ObjectDefinitionInterface $parent, $name, $typeName, Endpoint $endpoint): ObjectDefinition
223
    {
224
        $child = new FieldDefinition();
225
        $child->setName($name);
226
        $child->setResolver(EmptyObjectResolver::class);
227
228
        $type = new ObjectDefinition();
229
        $type->setName($typeName);
230
        if ($endpoint->hasType($type->getName())) {
231
            $type = $endpoint->getType($type->getName());
232
        } else {
233
            $endpoint->add($type);
234
        }
235
236
        $child->setType($type->getName());
237
        $parent->addField($child);
238
239
        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...
240
    }
241
242
    /**
243
     * @param string   $rootType Class of the root type to create QueryDefinition or MutationDefinition
244
     * @param string   $name     name of the root field
245
     * @param string   $typeName name for the root type
246
     * @param Endpoint $endpoint Endpoint interface to extract existent definitions
247
     *
248
     * @return ExecutableDefinitionInterface
249
     */
250 21
    private function createRootNamespace($rootType, $name, $typeName, Endpoint $endpoint): ExecutableDefinitionInterface
251
    {
252
        /** @var ExecutableDefinitionInterface $rootDefinition */
253 21
        $rootDefinition = new $rootType();
254 21
        $rootDefinition->setName($name);
255 21
        $rootDefinition->setResolver(EmptyObjectResolver::class);
256
257 21
        $type = new ObjectDefinition();
258 21
        $type->setName($typeName);
259 21
        if ($endpoint->hasType($type->getName())) {
260 21
            $type = $endpoint->getType($type->getName());
261
        } else {
262 21
            $endpoint->add($type);
263
        }
264
265 21
        $rootDefinition->setType($type->getName());
266
267 21
        return $rootDefinition;
268
    }
269
}
270