ObjectTypeAnnotationParser::resolveFields()   F
last analyzed

Complexity

Conditions 30
Paths 4680

Size

Total Lines 139
Code Lines 91

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 70
CRAP Score 41.0649

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 91
c 5
b 1
f 0
dl 0
loc 139
ccs 70
cts 91
cp 0.7692
rs 0
cc 30
nc 4680
nop 2
crap 41.0649

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 Doctrine\Common\Annotations\Reader;
14
use Ynlo\GraphQLBundle\Annotation;
15
use Ynlo\GraphQLBundle\Definition\ArgumentDefinition;
16
use Ynlo\GraphQLBundle\Definition\FieldDefinition;
17
use Ynlo\GraphQLBundle\Definition\FieldsAwareDefinitionInterface;
18
use Ynlo\GraphQLBundle\Definition\ImplementorInterface;
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\NodeAwareDefinitionInterface;
23
use Ynlo\GraphQLBundle\Definition\ObjectDefinition;
24
use Ynlo\GraphQLBundle\Definition\ObjectDefinitionInterface;
25
use Ynlo\GraphQLBundle\Definition\Registry\Endpoint;
26
use Ynlo\GraphQLBundle\Resolver\FieldExpressionResolver;
27
use Ynlo\GraphQLBundle\Util\TypeUtil;
28
29
/**
30
 * Parse the ObjectType annotation to fetch object definitions
31
 */
32
class ObjectTypeAnnotationParser implements AnnotationParserInterface
33
{
34
    /**
35
     * @var Reader
36
     */
37
    protected $reader;
38
39
    /**
40
     * @var FieldDefinitionDecoratorInterface[]
41
     */
42
    protected $fieldDecorators;
43
    /**
44
     * @var Endpoint
45
     */
46
    protected $endpoint;
47
48
    /**
49
     * ObjectTypeAnnotationParser constructor.
50
     *
51
     * @param Reader   $reader
52
     * @param iterable $fieldDecorators
53
     */
54 43
    public function __construct(Reader $reader, iterable $fieldDecorators = [])
55
    {
56 43
        $this->reader = $reader;
57 43
        $this->fieldDecorators = $fieldDecorators;
0 ignored issues
show
Documentation Bug introduced by
It seems like $fieldDecorators can also be of type iterable. However, the property $fieldDecorators is declared as type Ynlo\GraphQLBundle\Defin...ionDecoratorInterface[]. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
58 43
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 43
    public function supports($annotation): bool
64
    {
65 43
        return $annotation instanceof Annotation\ObjectType || $annotation instanceof Annotation\InputObjectType;
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71 37
    public function parse($annotation, \ReflectionClass $refClass, Endpoint $endpoint)
72
    {
73 37
        $this->endpoint = $endpoint;
74
75 37
        if ($annotation instanceof Annotation\ObjectType) {
76 36
            $objectDefinition = new ObjectDefinition();
77
        } else {
78 8
            $objectDefinition = new InputObjectDefinition();
79
        }
80
81 37
        $objectDefinition->setName($annotation->name);
82 37
        $objectDefinition->setExclusionPolicy($annotation->exclusionPolicy);
83 37
        $objectDefinition->setClass($refClass->name);
84 37
        $objectDefinition->setMetas($annotation->options);
85
86 37
        if (!$objectDefinition->getName()) {
87 33
            preg_match('/\w+$/', $refClass->getName(), $matches);
88 33
            $objectDefinition->setName($matches[0] ?? '');
89
        }
90
91 37
        if ($objectDefinition instanceof NodeAwareDefinitionInterface) {
92 36
            $objectDefinition->setNode($objectDefinition->getName());
93
        }
94
95 37
        if ($endpoint->hasType($objectDefinition->getName())) {
96
            return;
97
        }
98
99 37
        $objectDefinition->setDescription($annotation->description);
100
101 37
        if ($objectDefinition instanceof ImplementorInterface) {
102 36
            $this->resolveDefinitionInterfaces($refClass, $objectDefinition, $endpoint, $annotation);
103
        }
104
105 37
        $this->loadInheritedProperties($refClass, $objectDefinition);
106 37
        $this->resolveFields($refClass, $objectDefinition);
107 37
        $endpoint->addType($objectDefinition);
108 37
    }
109
110
    /**
111
     * @param \ReflectionClass      $refClass
112
     * @param ImplementorInterface  $implementor
113
     * @param Endpoint              $endpoint
114
     * @param Annotation\ObjectType $annotation
115
     */
116 36
    protected function resolveDefinitionInterfaces(\ReflectionClass $refClass, ImplementorInterface $implementor, Endpoint $endpoint, Annotation\ObjectType $annotation)
117
    {
118 36
        $interfaceDefinitions = $this->extractInterfaceDefinitions($refClass);
119 36
        foreach ($interfaceDefinitions as $interfaceDefinition) {
120 32
            if (in_array($interfaceDefinition->getName(), $annotation->ignoreInterface)) {
121
                continue;
122
            }
123
124 32
            $implementor->addInterface($interfaceDefinition->getName());
125
126 32
            if (!$endpoint->hasType($interfaceDefinition->getName())) {
127 32
                $endpoint->addType($interfaceDefinition);
128
            } else {
129 12
                $interfaceDefinition = $endpoint->getType($interfaceDefinition->getName());
130
            }
131
132 32
            $interfaceDefinition->addImplementor($implementor->getName());
0 ignored issues
show
Bug introduced by
The method addImplementor() does not exist on Ynlo\GraphQLBundle\Definition\DefinitionInterface. It seems like you code against a sub-type of Ynlo\GraphQLBundle\Definition\DefinitionInterface such as Ynlo\GraphQLBundle\Definition\InterfaceDefinition or Ynlo\GraphQLBundle\Definition\InterfaceDefinition. ( Ignorable by Annotation )

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

132
            $interfaceDefinition->/** @scrutinizer ignore-call */ 
133
                                  addImplementor($implementor->getName());
Loading history...
133 32
            $this->copyFieldsFromInterface($interfaceDefinition, $implementor);
134
        }
135
136
        //support interface inheritance
137
        //Interface inheritance is not implemented in GraphQL
138
        //@see https://github.com/facebook/graphql/issues/295
139
        //BUT, GraphQLBundle use this feature in some places like extensions etc.
140 36
        foreach ($interfaceDefinitions as $interfaceDefinition) {
141 32
            if ($interfaceDefinition->getClass()) {
142 32
                $childInterface = new \ReflectionClass($interfaceDefinition->getClass());
143
                /** @var Annotation\InterfaceType $interface */
144 32
                $interface = $this->reader->getClassAnnotation($childInterface, Annotation\InterfaceType::class);
145 32
                $parentDefinitions = $this->extractInterfaceDefinitions($childInterface);
146 32
                foreach ($parentDefinitions as $parentDefinition) {
147 32
                    if (in_array($parentDefinition->getName(), $interface->ignoreParent)) {
148
                        continue;
149
                    }
150 32
                    $this->copyFieldsFromInterface($parentDefinition, $interfaceDefinition);
151 32
                    if ($endpoint->hasType($parentDefinition->getName())) {
152 32
                        $existentParentDefinition = $endpoint->getType($parentDefinition->getName());
153 32
                        if ($existentParentDefinition instanceof InterfaceDefinition) {
154 32
                            $existentParentDefinition->addImplementor($interfaceDefinition->getName());
155
                        }
156
                    }
157
                }
158
            }
159
        }
160 36
    }
161
162
    /**
163
     * @param \ReflectionClass $refClass
164
     *
165
     * @return InterfaceDefinition[]
166
     */
167 36
    protected function extractInterfaceDefinitions(\ReflectionClass $refClass)
168
    {
169 36
        $int = $refClass->getInterfaces();
170
171
        //get recursively all parent abstract classes to use as interfaces
172 36
        $currentClass = $refClass;
173 36
        while ($currentClass->getParentClass()) {
174 28
            if ($currentClass->getParentClass()->isAbstract()) {
175 28
                $int[] = $currentClass->getParentClass();
176
            }
177 28
            $currentClass = $currentClass->getParentClass();
178
        }
179
180
        //current class can be a object and interface at the same time,
181
        //When use different object types using discriminator map
182 36
        if ($this->reader->getClassAnnotation($refClass, Annotation\InterfaceType::class)) {
183 32
            $int[] = $refClass;
184
        }
185
186 36
        $definitions = [];
187 36
        foreach ($int as $intRef) {
188
            /** @var Annotation\InterfaceType $intAnnot */
189 32
            $intAnnot = $this->reader->getClassAnnotation(
190 32
                $intRef,
191 32
                Annotation\InterfaceType::class
192
            );
193
194 32
            if ($intAnnot) {
195 32
                $intDef = new InterfaceDefinition();
196 32
                $intDef->setName($intAnnot->name);
197 32
                $intDef->setClass($intRef->getName());
198
199 32
                if (!$intDef->getName() && preg_match('/\w+$/', $intRef->getName(), $matches)) {
200 32
                    $intDef->setName(preg_replace('/Interface$/', null, $matches[0]));
201
                }
202
203 32
                $intDef->setMetas($intAnnot->options);
204 32
                $intDef->setDescription($intAnnot->description);
205 32
                $intDef->setDeprecationReason($intAnnot->deprecationReason);
206 32
                $intDef->setDiscriminatorMap($intAnnot->discriminatorMap);
207 32
                $intDef->setDiscriminatorProperty($intAnnot->discriminatorProperty);
208 32
                $intDef->setExclusionPolicy($intAnnot->exclusionPolicy);
209 32
                $this->resolveFields($intRef, $intDef);
210 32
                if (!$intDef->getName() && preg_match('/\w+$/', $intRef->getName(), $matches)) {
211
                    $intDef->setName(preg_replace('/Interface$/', null, $matches[0]));
212
                }
213
214 32
                $definitions[] = $intDef;
215
            }
216
        }
217
218 36
        return $definitions;
219
    }
220
221
    /**
222
     * @param \ReflectionClass          $refClass
223
     * @param ObjectDefinitionInterface $objectDefinition
224
     */
225 37
    protected function loadInheritedProperties(\ReflectionClass $refClass, ObjectDefinitionInterface $objectDefinition)
226
    {
227 37
        while ($parent = $refClass->getParentClass()) {
228 28
            $this->resolveFields($refClass, $objectDefinition);
229 28
            $refClass = $parent;
230
        }
231 37
    }
232
233
    /**
234
     * Copy all fields from interface to given object implementor
235
     *
236
     * @param InterfaceDefinition            $intDef
237
     * @param FieldsAwareDefinitionInterface $fieldsAwareDefinition
238
     */
239 32
    protected function copyFieldsFromInterface(InterfaceDefinition $intDef, FieldsAwareDefinitionInterface $fieldsAwareDefinition)
240
    {
241 32
        foreach ($intDef->getFields() as $field) {
242 32
            if (!$fieldsAwareDefinition->hasField($field->getName())) {
243 32
                $newField = clone $field;
244 32
                $newField->addInheritedFrom($intDef->getName());
245 32
                $fieldsAwareDefinition->addField($newField);
246
            } else {
247 32
                $fieldsAwareDefinition->getField($field->getName())->addInheritedFrom($intDef->getName());
248
            }
249
        }
250 32
    }
251
252
    /**
253
     * Extract all fields for given definition
254
     *
255
     * @param \ReflectionClass          $refClass
256
     * @param ObjectDefinitionInterface $objectDefinition
257
     */
258 37
    protected function resolveFields(\ReflectionClass $refClass, ObjectDefinitionInterface $objectDefinition)
259
    {
260 37
        $props = array_merge($this->getClassProperties($refClass), $this->getClassMethods($refClass));
261 37
        foreach ($props as $prop) {
262 37
            if (!$this->isExposed($objectDefinition, $prop)) {
263 37
                continue;
264
            }
265
266 37
            $field = new FieldDefinition();
267 37
            foreach ($this->fieldDecorators as $fieldDecorator) {
268 37
                $fieldDecorator->decorateFieldDefinition($prop, $field, $objectDefinition);
269
            }
270
271
            //in case the object already have this field (inherited from interface),
272
            //copy interfaces and remove the existent field to add the new definition
273 37
            if ($objectDefinition->hasField($field->getName())) {
274 32
                $existentField = $objectDefinition->getField($field->getName());
275 32
                $field->setInheritedFrom($existentField->getInheritedFrom());
276 32
                $objectDefinition->removeField($existentField->getName());
277
                //force type compatibility
278 32
                $field->setList($existentField->isList());
279 32
                $field->setNonNull($existentField->isNonNull());
280 32
                $field->setNonNullList($existentField->isNonNullList());
281
            }
282
283 37
            $objectDefinition->addField($field);
284
285 37
            $field->setOriginName($prop->name);
286 37
            $field->setOriginType(\get_class($prop));
287
288
            //resolve field arguments
289 37
            if ($prop instanceof \ReflectionMethod) {
290 32
                foreach ($this->reader->getMethodAnnotations($prop) as $argAnnotation) {
291 32
                    if (!$argAnnotation instanceof Annotation\Argument) {
292 32
                        continue;
293
                    }
294
295 28
                    $arg = new ArgumentDefinition();
296 28
                    $arg->setName($argAnnotation->name);
297 28
                    $arg->setDescription($argAnnotation->description);
298 28
                    $arg->setInternalName($argAnnotation->internalName);
299 28
                    $arg->setDefaultValue($argAnnotation->defaultValue);
300 28
                    $arg->setType(TypeUtil::normalize($argAnnotation->type));
301 28
                    $arg->setList(TypeUtil::isTypeList($argAnnotation->type));
302 28
                    $arg->setNonNullList(TypeUtil::isTypeNonNullList($argAnnotation->type));
303 28
                    $arg->setNonNull(TypeUtil::isTypeNonNull($argAnnotation->type));
304 28
                    $field->addArgument($arg);
305
                }
306
            }
307
        }
308
309
        //load overrides
310 37
        foreach ($this->reader->getClassAnnotations($refClass) as $annotation) {
311 37
            if (!$annotation instanceof Annotation\OverrideField) {
312 37
                continue;
313
            }
314
315 28
            if ($annotation->in && !\in_array($objectDefinition->getName(), $annotation->in)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $annotation->in of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
316
                continue;
317
            }
318
319 28
            if ($annotation->notIn && \in_array($objectDefinition->getName(), $annotation->notIn)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $annotation->notIn of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
320
                continue;
321
            }
322
323 28
            if (!$objectDefinition->hasField($annotation->name)) {
324
                throw new \InvalidArgumentException(sprintf(
325
                    'The object definition "%s" does not have any field called "%s" in any of its parents definitions.',
326
                    $objectDefinition->getName(),
327
                    $annotation->name
328
                ));
329
            }
330
331 28
            if (true === $annotation->hidden) {
332
                $objectDefinition->removeField($annotation->name);
333
334
                continue;
335
            }
336
337 28
            $fieldDefinition = $objectDefinition->getField($annotation->name);
338
339 28
            if ($annotation->type) {
340
                $fieldDefinition->setType(TypeUtil::normalize($annotation->type));
341
                $fieldDefinition->setNonNull(TypeUtil::isTypeNonNull($annotation->type));
342
                $fieldDefinition->setNonNullList(TypeUtil::isTypeNonNullList($annotation->type));
343
                $fieldDefinition->setList(TypeUtil::isTypeList($annotation->type));
344
            }
345 28
            if ($annotation->alias) {
346 28
                $objectDefinition->removeField($fieldDefinition->getName());
347 28
                $fieldDefinition->setName($annotation->alias);
348 28
                $objectDefinition->addField(clone $fieldDefinition);
349
            }
350 28
            if ($annotation->description) {
351 28
                $fieldDefinition->setDescription($annotation->description);
352
            }
353 28
            if ($annotation->deprecationReason || false === $annotation->deprecationReason) {
354
                $fieldDefinition->setDeprecationReason($annotation->deprecationReason);
355
            }
356 28
            if ($annotation->complexity) {
357
                $fieldDefinition->setComplexity($annotation->complexity);
358
            }
359
        }
360
361
        //load virtual fields
362 37
        $annotations = $this->reader->getClassAnnotations($refClass);
363 37
        foreach ($annotations as $annotation) {
364 37
            if ($annotation instanceof Annotation\VirtualField) {
365 28
                if ($annotation->in && !\in_array($objectDefinition->getName(), $annotation->in)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $annotation->in of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
366
                    continue;
367
                }
368
369 28
                if ($annotation->notIn && \in_array($objectDefinition->getName(), $annotation->notIn)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $annotation->notIn of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
370
                    continue;
371
                }
372
373 28
                if (!$objectDefinition->hasField($annotation->name)) {
374 28
                    $fieldDefinition = new FieldDefinition();
375 28
                    $fieldDefinition->setName($annotation->name);
376 28
                    $fieldDefinition->setDescription($annotation->description);
377 28
                    $fieldDefinition->setDeprecationReason($annotation->deprecationReason);
378 28
                    $fieldDefinition->setType(TypeUtil::normalize($annotation->type));
379 28
                    $fieldDefinition->setNonNull(TypeUtil::isTypeNonNull($annotation->type));
380 28
                    $fieldDefinition->setNonNullList(TypeUtil::isTypeNonNullList($annotation->type));
381 28
                    $fieldDefinition->setList(TypeUtil::isTypeList($annotation->type));
382 28
                    $fieldDefinition->setMeta('expression', $annotation->expression);
383 28
                    $fieldDefinition->setResolver(FieldExpressionResolver::class);
384 28
                    $fieldDefinition->setComplexity($annotation->complexity);
385 28
                    $objectDefinition->addField($fieldDefinition);
386
                } else {
387 28
                    $fieldDefinition = $objectDefinition->getField($annotation->name);
388 28
                    if ($fieldDefinition->getResolver() === FieldExpressionResolver::class) {
389 28
                        continue;
390
                    }
391
                    $error = sprintf(
392
                        'The object definition "%s" already has a field called "%s".',
393
                        $objectDefinition->getName(),
394
                        $annotation->name
395
                    );
396
                    throw new \InvalidArgumentException($error);
397
                }
398
            }
399
        }
400 37
    }
401
402
    /**
403
     * Verify if a given property for given definition is exposed or not
404
     *
405
     * @param ObjectDefinitionInterface             $definition
406
     * @param \ReflectionMethod|\ReflectionProperty $prop
407
     *
408
     * @return boolean
409
     */
410 37
    protected function isExposed(ObjectDefinitionInterface $definition, $prop): bool
411
    {
412 37
        $exposed = $definition->getExclusionPolicy() === ObjectDefinitionInterface::EXCLUDE_NONE;
413
414 37
        if ($prop instanceof \ReflectionMethod) {
415 37
            $exposed = false;
416
417
            //implicit inclusion
418 37
            if ($this->getFieldAnnotation($prop, Annotation\Field::class)) {
419 32
                $exposed = true;
420
            }
421
        }
422
423 37
        if ($exposed && $this->getFieldAnnotation($prop, Annotation\Exclude::class)) {
424 28
            $exposed = false;
425 37
        } elseif (!$exposed && $this->getFieldAnnotation($prop, Annotation\Expose::class)) {
426 11
            $exposed = true;
427
        }
428
429
        /** @var Annotation\Field $fieldAnnotation */
430 37
        if ($fieldAnnotation = $this->getFieldAnnotation($prop, Annotation\Field::class)) {
431 33
            $exposed = true;
432 33
            if ($fieldAnnotation->in) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fieldAnnotation->in of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
433
                $exposed = \in_array($definition->getName(), $fieldAnnotation->in);
434 33
            } elseif (($fieldAnnotation->notIn)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fieldAnnotation->notIn of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
435
                $exposed = !\in_array($definition->getName(), $fieldAnnotation->notIn);
436
            }
437
        }
438
439 37
        return $exposed;
440
    }
441
442
    /**
443
     * Get field specific annotation matching given implementor
444
     *
445
     * @param \ReflectionMethod|\ReflectionProperty $prop
446
     * @param string                                $annotationClass
447
     *
448
     * @return mixed
449
     */
450 37
    protected function getFieldAnnotation($prop, string $annotationClass)
451
    {
452 37
        if ($prop instanceof \ReflectionProperty) {
453 37
            return $this->reader->getPropertyAnnotation($prop, $annotationClass);
454
        }
455
456 37
        return $this->reader->getMethodAnnotation($prop, $annotationClass);
457
    }
458
459
    /**
460
     * @param \ReflectionClass $refClass
461
     *
462
     * @return array
463
     */
464 37
    protected function getClassProperties(\ReflectionClass $refClass)
465
    {
466 37
        $props = [];
467
468
        //fields from parents, including private fields
469 37
        $currentClass = $refClass;
470 37
        while ($parent = $currentClass->getParentClass()) {
471 28
            foreach ($parent->getProperties() as $prop) {
472 28
                $props[$prop->name] = $prop;
473
            }
474 28
            $currentClass = $parent;
475
        }
476
477 37
        foreach ($refClass->getProperties() as $prop) {
478 37
            $props[$prop->name] = $prop;
479
        }
480
481 37
        return $props;
482
    }
483
484
    /**
485
     * @param \ReflectionClass $refClass
486
     *
487
     * @return array
488
     */
489 37
    protected function getClassMethods(\ReflectionClass $refClass)
490
    {
491 37
        $methods = [];
492 37
        foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
493 37
            $methods[$method->name] = $method;
494
        }
495
496 37
        return $methods;
497
    }
498
}
499