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

ObjectTypeAnnotationParser::resolveFields()   D

Complexity

Conditions 18
Paths 360

Size

Total Lines 72
Code Lines 48

Duplication

Lines 14
Ratio 19.44 %

Code Coverage

Tests 35
CRAP Score 24.4341

Importance

Changes 0
Metric Value
cc 18
eloc 48
nc 360
nop 2
dl 14
loc 72
ccs 35
cts 48
cp 0.7292
crap 24.4341
rs 4.1123
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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