Completed
Push — master ( 300eda...15c856 )
by Rafael
09:26
created

NamespaceDefinitionPlugin::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 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\Plugin;
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 NamespaceDefinitionPlugin extends AbstractDefinitionPlugin
32
{
33
    protected $globalConfig = [];
34
35
    public function __construct(array $config = [])
36
    {
37
        $this->globalConfig = $config;
38
    }
39
40
    /**
41
     * {@inheritDoc}
42
     */
43
    public function buildConfig(ArrayNodeDefinition $root): void
44
    {
45
        $root
46
            ->info('Enable/Disable namespace for queries and mutations')
47
            ->canBeDisabled()
48
            ->children();
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function configure(DefinitionInterface $definition, Endpoint $endpoint, array $config): void
55
    {
56
        $node = null;
57
        $nodeClass = null;
58
59
        if (!($config['enabled'] ?? true)) {
60
            return;
61
        }
62
63
        if ($definition instanceof NodeAwareDefinitionInterface && isset($this->globalConfig['nodes']['enabled']) && $definition->getNode()) {
64
            $node = $definition->getNode();
65
66
            if (class_exists($node)) {
67
                $nodeClass = $node;
68
            } else {
69
                $nodeClass = $endpoint->getClassForType($node);
70
            }
71
72
            if (isset($this->globalConfig['nodes']['aliases'][$node])) {
73
                $node = $this->globalConfig['nodes']['aliases'][$node];
74
            }
75
76
            if ($node && \in_array($node, $this->globalConfig['nodes']['ignore'], true)) {
77
                $node = null;
78
            }
79
        }
80
81
        $bundle = null;
82
        if ($this->globalConfig['bundles']['enabled'] ?? false) {
83
            if ($node && $nodeClass && $endpoint->hasType($node)) {
84
                preg_match_all('/\\\\?(\w+Bundle)\\\\/', $nodeClass, $matches);
85
                if ($matches) {
86
                    $bundle = current(array_reverse($matches[1]));
87
                }
88
89
                if (isset($this->globalConfig['bundles']['aliases'][$bundle])) {
90
                    $bundle = $this->globalConfig['bundles']['aliases'][$bundle];
91
                }
92
93
                if ($bundle && \in_array($bundle, $this->globalConfig['bundles']['ignore'], true)) {
94
                    $bundle = null;
95
                }
96
97
                if ($bundle) {
98
                    $bundle = preg_replace('/Bundle$/', null, $bundle);
99
                }
100
            }
101
        }
102
103
        if ($bundle || $node) {
104
            $definition->setMeta('namespace', ['bundle' => $bundle, 'node' => $node]);
105
        }
106
    }
107
108
    /**
109
     * {@inheritDoc}
110
     */
111
    public function configureEndpoint(Endpoint $endpoint): void
112
    {
113
        $groupByBundle = $this->globalConfig['bundles']['enabled'] ?? false;
114
        $groupByNode = $this->globalConfig['bundles']['enabled'] ?? false;
115
        if ($groupByBundle || $groupByNode) {
116
            $endpoint->setQueries($this->namespaceDefinitions($endpoint->allQueries(), $endpoint));
117
            $endpoint->setMutations($this->namespaceDefinitions($endpoint->allMutations(), $endpoint));
118
        }
119
    }
120
121
    private function namespaceDefinitions(array $definitions, Endpoint $endpoint): array
122
    {
123
        $namespacedDefinitions = [];
124
        /** @var DefinitionInterface $definition */
125
        foreach ($definitions as $definition) {
126
            if (!$definition->hasMeta('namespace') || !$definition->getMeta('namespace')) {
127
                $namespacedDefinitions[] = $definition;
128
                continue;
129
            }
130
131
            $root = null;
132
            $parent = null;
133
            $namespace = $definition->getMeta('namespace');
134
            if ($bundle = $namespace['bundle'] ?? null) {
135
                $bundleQuerySuffix = $this->globalConfig['bundle']['query_suffix'] ?? 'BundleQuery';
136
                $bundleMutationSuffix = $this->globalConfig['bundle']['mutation_suffix'] ?? 'BundleMutation';
137
138
                $name = lcfirst($bundle);
139
                $typeName = ucfirst($name).(($definition instanceof MutationDefinition) ? $bundleMutationSuffix : $bundleQuerySuffix);
140
                $root = $this->createRootNamespace(\get_class($definition), $name, $typeName, $endpoint);
141
                $parent = $endpoint->getType($root->getType());
142
            }
143
144
            if ($nodeName = $namespace['node'] ?? null) {
145
                if ($endpoint->hasTypeForClass($nodeName)) {
146
                    $nodeName = $endpoint->getTypeForClass($nodeName);
147
                }
148
149
                $name = Inflector::pluralize(lcfirst($nodeName));
150
151
                $querySuffix = $this->globalConfig['nodes']['query_suffix'] ?? 'Query';
152
                $mutationSuffix = $this->globalConfig['nodes']['mutation_suffix'] ?? 'Mutation';
153
154
                $typeName = ucfirst($nodeName).(($definition instanceof MutationDefinition) ? $mutationSuffix : $querySuffix);
155
                if (!$root) {
156
                    $root = $this->createRootNamespace(\get_class($definition), $name, $typeName, $endpoint);
157
                    $parent = $endpoint->getType($root->getType());
158
                } elseif ($parent) {
159
                    $parent = $this->createChildNamespace($parent, $name, $typeName, $endpoint);
160
                }
161
162
                //remove node suffix on namespaced definitions
163
                $definition->setName(preg_replace(sprintf("/(\w+)%s$/", $nodeName), '$1', $definition->getName()));
164
                $definition->setName(preg_replace(sprintf("/(\w+)%s$/", Inflector::pluralize($nodeName)), '$1', $definition->getName()));
165
            }
166
167
            if ($root && $parent) {
168
                $this->addDefinitionToNamespace($parent, $definition);
169
                $namespacedDefinitions[] = $root;
170
            }
171
        }
172
173
        return $namespacedDefinitions;
174
    }
175
176
    private function addDefinitionToNamespace(FieldsAwareDefinitionInterface $fieldsAwareDefinition, ExecutableDefinitionInterface $definition)
177
    {
178
        $field = new FieldDefinition();
179
        $field->setName($definition->getName());
180
        $field->setType($definition->getType());
181
        $field->setResolver($definition->getResolver());
182
        $field->setArguments($definition->getArguments());
183
        $field->setList($definition->isList());
184
        $field->setMetas($definition->getMetas());
185
        $field->setNode($definition->getNode());
186
        $field->setRoles($definition->getRoles());
0 ignored issues
show
Bug introduced by
The method getRoles() does not exist on Ynlo\GraphQLBundle\Defin...ableDefinitionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Ynlo\GraphQLBundle\Defin...ableDefinitionInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

186
        $field->setRoles($definition->/** @scrutinizer ignore-call */ getRoles());
Loading history...
187
        $field->setComplexity($definition->getComplexity());
0 ignored issues
show
Bug introduced by
The method getComplexity() does not exist on Ynlo\GraphQLBundle\Defin...ableDefinitionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Ynlo\GraphQLBundle\Defin...ableDefinitionInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

187
        $field->setComplexity($definition->/** @scrutinizer ignore-call */ getComplexity());
Loading history...
188
        $fieldsAwareDefinition->addField($field);
189
    }
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
    private function createChildNamespace(ObjectDefinitionInterface $parent, string $name, string $typeName, Endpoint $endpoint): ObjectDefinition
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
    private function createRootNamespace($rootType, $name, $typeName, Endpoint $endpoint): ExecutableDefinitionInterface
228
    {
229
        /** @var ExecutableDefinitionInterface $rootDefinition */
230
        $rootDefinition = new $rootType();
231
        $rootDefinition->setName($name);
232
        $rootDefinition->setResolver(EmptyObjectResolver::class);
233
234
        $type = new ObjectDefinition();
235
        $type->setName($typeName);
236
        if ($endpoint->hasType($type->getName())) {
237
            $type = $endpoint->getType($type->getName());
238
        } else {
239
            $endpoint->add($type);
240
        }
241
242
        $rootDefinition->setType($type->getName());
243
244
        return $rootDefinition;
245
    }
246
}
247