Completed
Push — master ( cbfed7...c39fb6 )
by Rafael
05:06
created

ObjectTypeAnnotationParser   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 349
Duplicated Lines 4.01 %

Test Coverage

Coverage 89.74%

Importance

Changes 0
Metric Value
dl 14
loc 349
ccs 140
cts 156
cp 0.8974
rs 6.8539
c 0
b 0
f 0
wmc 54

12 Methods

Rating   Name   Duplication   Size   Complexity  
B getFieldDecorators() 0 35 4
A supports() 0 3 2
A getFieldAnnotation() 0 7 2
A getClassMethods() 0 8 2
A copyFieldsFromInterface() 0 5 3
D resolveFields() 14 72 18
A loadInheritedProperties() 0 5 2
B parse() 0 34 5
B resolveObjectInterfaces() 0 36 6
B isExposed() 0 20 7
A __construct() 0 4 1
A getClassProperties() 0 8 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ObjectTypeAnnotationParser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ObjectTypeAnnotationParser, and based on these observations, apply Extract Interface, too.

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\Loader\Annotation;
12
13
use Symfony\Component\DependencyInjection\Definition;
14
use Ynlo\GraphQLBundle\Annotation;
15
use Ynlo\GraphQLBundle\Component\TaggedServices\TaggedServices;
16
use Ynlo\GraphQLBundle\Definition\ArgumentDefinition;
17
use Ynlo\GraphQLBundle\Definition\ConnectionDefinitionBuilder;
18
use Ynlo\GraphQLBundle\Definition\FieldDefinition;
19
use Ynlo\GraphQLBundle\Definition\InputObjectDefinition;
20
use Ynlo\GraphQLBundle\Definition\InterfaceDefinition;
21
use Ynlo\GraphQLBundle\Definition\Loader\Annotation\FieldDecorator\FieldDefinitionDecoratorInterface;
22
use Ynlo\GraphQLBundle\Definition\ObjectDefinition;
23
use Ynlo\GraphQLBundle\Definition\ObjectDefinitionInterface;
24
use Ynlo\GraphQLBundle\Definition\Registry\DefinitionManager;
25
use Ynlo\GraphQLBundle\Type\DefinitionManagerAwareInterface;
26
use Ynlo\GraphQLBundle\Util\TypeUtil;
27
28
/**
29
 * Parse the ObjectType annotation to fetch object definitions
30
 */
31
class ObjectTypeAnnotationParser implements AnnotationParserInterface
32
{
33
    use AnnotationReaderAwareTrait;
34
35
    /**
36
     * @var TaggedServices
37
     */
38
    protected $taggedServices;
39
40
    /**
41
     * @var ConnectionDefinitionBuilder
42
     */
43
    protected $connectionBuilder;
44
45
    /**
46
     * @var DefinitionManager
47
     */
48
    protected $definitionManager;
49
50
    /**
51
     * ObjectTypeAnnotationParser constructor.
52
     *
53
     * @param TaggedServices              $taggedServices
54
     * @param ConnectionDefinitionBuilder $connectionBuilder
55
     */
56 1
    public function __construct(TaggedServices $taggedServices, ConnectionDefinitionBuilder $connectionBuilder)
57
    {
58 1
        $this->taggedServices = $taggedServices;
59 1
        $this->connectionBuilder = $connectionBuilder;
60 1
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65 1
    public function supports($annotation): bool
66
    {
67 1
        return $annotation instanceof Annotation\ObjectType || $annotation instanceof Annotation\InputObjectType;
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73 1
    public function parse($annotation, \ReflectionClass $refClass, DefinitionManager $definitionManager)
74
    {
75 1
        $this->definitionManager = $definitionManager;
76
77 1
        if ($annotation instanceof Annotation\ObjectType) {
78 1
            $objectDefinition = new ObjectDefinition();
79
        } else {
80 1
            $objectDefinition = new InputObjectDefinition();
81
        }
82
83 1
        $objectDefinition->setName($annotation->name);
84 1
        $objectDefinition->setExclusionPolicy($annotation->exclusionPolicy);
85 1
        $objectDefinition->setClass($refClass->name);
86
87 1
        if (!$objectDefinition->getName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $objectDefinition->getName() of type null|string is loosely compared to false; 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...
88 1
            preg_match('/\w+$/', $refClass->getName(), $matches);
89 1
            $objectDefinition->setName($matches[0] ?? '');
90
        }
91
92 1
        if ($definitionManager->hasType($objectDefinition->getName())) {
93
            return;
94
        }
95
96 1
        $objectDefinition->setClass($refClass->getName());
97 1
        $objectDefinition->setDescription($annotation->description);
98
99 1
        if ($objectDefinition instanceof ObjectDefinition) {
100 1
            $this->resolveObjectInterfaces($refClass, $objectDefinition, $definitionManager);
101
        }
102
103 1
        $this->loadInheritedProperties($refClass, $objectDefinition);
104 1
        $this->resolveFields($refClass, $objectDefinition);
105
106 1
        $definitionManager->addType($objectDefinition);
107 1
    }
108
109
    /**
110
     * @param \ReflectionClass  $refClass
111
     * @param ObjectDefinition  $objectDefinition
112
     * @param DefinitionManager $definitionManager
113
     */
114 1
    protected function resolveObjectInterfaces(\ReflectionClass $refClass, ObjectDefinition $objectDefinition, DefinitionManager $definitionManager)
115
    {
116 1
        $int = $refClass->getInterfaces();
117 1
        foreach ($int as $intRef) {
118
119
            /** @var Annotation\InterfaceType $intAnnot */
120 1
            $intAnnot = $this->reader->getClassAnnotation(
121 1
                $intRef,
122 1
                Annotation\InterfaceType::class
123
            );
124
125 1
            if ($intAnnot) {
126 1
                $intDef = new InterfaceDefinition();
127 1
                $intDef->setName($intAnnot->name);
128 1
                $intDef->setClass($intRef->getName());
129
130 1
                if (!$intDef->getName() && preg_match('/\w+$/', $intRef->getName(), $matches)) {
131 1
                    $intDef->setName(preg_replace('/Interface$/', null, $matches[0]));
132
                }
133
134 1
                $objectDefinition->addInterface($intDef->getName());
135
136 1
                if ($definitionManager->hasType($intDef->getName())) {
137
                    /** @var InterfaceDefinition $existentInterfaceDefinition */
138 1
                    $existentInterfaceDefinition = $definitionManager->getType($intDef->getName());
139 1
                    $existentInterfaceDefinition->addImplementor($objectDefinition->getName());
140 1
                    $this->copyFieldsFromInterface($existentInterfaceDefinition, $objectDefinition);
141 1
                    continue;
142
                }
143
144 1
                $intDef->setDescription($intAnnot->description);
145 1
                $intDef->addImplementor($objectDefinition->getName());
146
147 1
                $this->resolveFields($intRef, $intDef);
148 1
                $this->copyFieldsFromInterface($intDef, $objectDefinition);
149 1
                $definitionManager->addType($intDef);
150
            }
151
        }
152 1
    }
153
154
    /**
155
     * @param \ReflectionClass          $refClass
156
     * @param ObjectDefinitionInterface $objectDefinition
157
     */
158 1
    protected function loadInheritedProperties(\ReflectionClass $refClass, ObjectDefinitionInterface $objectDefinition)
159
    {
160 1
        while ($parent = $refClass->getParentClass()) {
161 1
            $this->resolveFields($refClass, $objectDefinition);
162 1
            $refClass = $parent;
163
        }
164 1
    }
165
166
    /**
167
     * Copy all fields from interface to given object implementor
168
     *
169
     * @param InterfaceDefinition $intDef
170
     * @param ObjectDefinition    $objectDefinition
171
     */
172 1
    protected function copyFieldsFromInterface(InterfaceDefinition $intDef, ObjectDefinition $objectDefinition)
173
    {
174 1
        foreach ($intDef->getFields() as $field) {
175 1
            if (!$objectDefinition->hasField($field->getName())) {
176 1
                $objectDefinition->addField(clone $field);
177
            }
178
        }
179 1
    }
180
181
    /**
182
     * Extract all fields for given definition
183
     *
184
     * @param \ReflectionClass          $refClass
185
     * @param ObjectDefinitionInterface $objectDefinition
186
     */
187 1
    protected function resolveFields(\ReflectionClass $refClass, ObjectDefinitionInterface $objectDefinition)
188
    {
189 1
        $props = array_merge($this->getClassProperties($refClass), $this->getClassMethods($refClass));
190
191 1
        $fieldDecorators = $this->getFieldDecorators();
192
193 1
        foreach ($props as $prop) {
194 1
            if ($this->isExposed($objectDefinition, $prop)) {
195 1
                $field = new FieldDefinition();
196 1
                $field->setOriginName($prop->name);
197 1
                $field->setOriginType(\get_class($prop));
198
199 1
                foreach ($fieldDecorators as $fieldDecorator) {
200 1
                    $fieldDecorator->decorateFieldDefinition($prop, $field, $objectDefinition);
201
                }
202
203 1
                if ($objectDefinition->hasField($field->getName())) {
204 1
                    $field = $objectDefinition->getField($field->getName());
205
                } else {
206 1
                    $objectDefinition->addField($field);
207
                }
208
209
                //resolve field arguments
210 1
                if ($prop instanceof \ReflectionMethod) {
211 1
                    $argAnnotations = $this->reader->getMethodAnnotations($prop);
212 1 View Code Duplication
                    foreach ($argAnnotations as $argAnnotation) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
213 1
                        if ($argAnnotation instanceof Annotation\Argument) {
214
                            $arg = new ArgumentDefinition();
215
                            $arg->setName($argAnnotation->name);
216
                            $arg->setDescription($argAnnotation->description);
217
                            $arg->setInternalName($argAnnotation->internalName);
218
                            $arg->setDefaultValue($argAnnotation->defaultValue);
219
                            $arg->setType(TypeUtil::normalize($argAnnotation->type));
220
                            $arg->setList(TypeUtil::isTypeList($argAnnotation->type));
221
                            $arg->setNonNullList(TypeUtil::isTypeNonNullList($argAnnotation->type));
222
                            $arg->setNonNull(TypeUtil::isTypeNonNull($argAnnotation->type));
223 1
                            $field->addArgument($arg);
224
                        }
225
                    }
226
                }
227
228
                /** @var Annotation\Connection $connection */
229 1
                if ($connection = $this->getFieldAnnotation($prop, Annotation\Connection::class)) {
230 1
                    $this->connectionBuilder->setEndpoint($this->definitionManager->getEndpoint());
231 1
                    $this->connectionBuilder->setLimit($connection->limit);
232 1
                    $this->connectionBuilder->setParentField($connection->parentField);
233 1
                    $this->connectionBuilder->build($field, $field->getType());
234
                }
235
            }
236
        }
237
238
        //load overrides
239 1
        $annotations = $this->reader->getClassAnnotations($refClass);
240 1
        foreach ($annotations as $annotation) {
241 1
            if ($annotation instanceof Annotation\OverrideField) {
242 1
                if ($objectDefinition->hasField($annotation->name)) {
243 1
                    $fieldDefinition = $objectDefinition->getField($annotation->name);
244 1
                    if ($annotation->hidden === true) {
245
                        $objectDefinition->removeField($annotation->name);
246
                        continue;
247
                    }
248 1
                    if ($annotation->description) {
249 1
                        $fieldDefinition->setDescription($annotation->description);
250
                    }
251 1
                    if ($annotation->deprecationReason || $annotation->deprecationReason === false) {
252
                        $fieldDefinition->setDeprecationReason($annotation->deprecationReason);
253
                    }
254 1
                    if ($annotation->type) {
255
                        $fieldDefinition->setType($annotation->type);
256
                    }
257 1
                    if ($annotation->alias) {
258 1
                        $fieldDefinition->setName($annotation->alias);
259
                    }
260
                }
261
            }
262
        }
263 1
    }
264
265
    /**
266
     * @return array|FieldDefinitionDecoratorInterface[]
267
     */
268 1
    protected function getFieldDecorators(): array
269
    {
270
        /** @var Definition $resolversServiceDefinition */
271 1
        $decoratorsDef = $this->taggedServices
272 1
            ->findTaggedServices('graphql.field_definition_decorator');
273
274 1
        $decorators = [];
275 1
        foreach ($decoratorsDef as $decoratorDef) {
276 1
            $attr = $decoratorDef->getAttributes();
277 1
            $priority = 0;
278 1
            if (isset($attr['priority'])) {
279 1
                $priority = $attr['priority'];
280
            }
281
282 1
            $decorator = $decoratorDef->getService();
283
284 1
            if ($decorator instanceof DefinitionManagerAwareInterface) {
285
                $decorator->setDefinitionManager($this->definitionManager);
286
            }
287
288 1
            $decorators[] = [$priority, $decorator];
289
        }
290
291
        //sort by priority
292 1
        usort(
293 1
            $decorators,
294 1
            function ($service1, $service2) {
295 1
                list($priority1) = $service1;
296 1
                list($priority2) = $service2;
297
298 1
                return version_compare($priority2, $priority1);
299 1
            }
300
        );
301
302 1
        return array_column($decorators, 1);
303
    }
304
305
    /**
306
     * Verify if a given property for given definition is exposed or not
307
     *
308
     * @param ObjectDefinitionInterface             $definition
309
     * @param \ReflectionMethod|\ReflectionProperty $prop
310
     *
311
     * @return boolean
312
     */
313 1
    protected function isExposed(ObjectDefinitionInterface $definition, $prop): bool
314
    {
315 1
        $exposed = $definition->getExclusionPolicy() === ObjectDefinitionInterface::EXCLUDE_NONE;
316
317 1
        if ($prop instanceof \ReflectionMethod) {
318 1
            $exposed = false;
319
320
            //implicit inclusion
321 1
            if ($this->getFieldAnnotation($prop, Annotation\Field::class)) {
322 1
                $exposed = true;
323
            }
324
        }
325
326 1
        if ($exposed && $this->getFieldAnnotation($prop, Annotation\Exclude::class)) {
327 1
            $exposed = false;
328 1
        } elseif (!$exposed && $this->getFieldAnnotation($prop, Annotation\Expose::class)) {
329
            $exposed = true;
330
        }
331
332 1
        return $exposed;
333
    }
334
335
    /**
336
     * Get field specific annotation matching given objectDefinition
337
     *
338
     * @param \ReflectionMethod|\ReflectionProperty $prop
339
     * @param string                                $annotationClass
340
     *
341
     * @return mixed
342
     */
343 1
    protected function getFieldAnnotation($prop, string $annotationClass)
344
    {
345 1
        if ($prop instanceof \ReflectionProperty) {
346 1
            return $this->reader->getPropertyAnnotation($prop, $annotationClass);
347
        }
348
349 1
        return $this->reader->getMethodAnnotation($prop, $annotationClass);
350
    }
351
352
    /**
353
     * @param \ReflectionClass $refClass
354
     *
355
     * @return array
356
     */
357 1
    protected function getClassProperties(\ReflectionClass $refClass)
358
    {
359 1
        $props = [];
360 1
        foreach ($refClass->getProperties() as $prop) {
361 1
            $props[$prop->name] = $prop;
362
        }
363
364 1
        return $props;
365
    }
366
367
    /**
368
     * @param \ReflectionClass $refClass
369
     *
370
     * @return array
371
     */
372 1
    protected function getClassMethods(\ReflectionClass $refClass)
373
    {
374 1
        $methods = [];
375 1
        foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
376 1
            $methods[$method->name] = $method;
377
        }
378
379 1
        return $methods;
380
    }
381
}
382