ReflectionClass   F
last analyzed

Complexity

Total Complexity 149

Size/Duplication

Total Lines 1350
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 24

Test Coverage

Coverage 97.68%

Importance

Changes 0
Metric Value
wmc 149
lcom 1
cbo 24
dl 0
loc 1350
ccs 463
cts 474
cp 0.9768
rs 0.8
c 0
b 0
f 0

73 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A __toString() 0 4 1
A createFromName() 0 4 1
A createFromInstance() 0 8 2
A createFromNode() 0 18 2
A getShortName() 0 15 3
A getName() 0 8 2
A getNamespaceName() 0 8 2
A inNamespace() 0 5 2
A getExtensionName() 0 4 1
A getAllMethods() 0 44 1
B getMethodsIndexedByName() 0 35 7
A getMethods() 0 15 2
A getImmediateMethods() 0 28 4
A getMethod() 0 10 2
A hasMethod() 0 10 2
A getImmediateConstants() 0 6 1
A getConstants() 0 6 1
A getConstant() 0 10 2
A hasConstant() 0 4 1
A getReflectionConstant() 0 4 1
A getImmediateReflectionConstants() 0 37 3
A getReflectionConstants() 0 40 3
A getConstructor() 0 12 2
B getImmediateProperties() 0 36 6
A getProperties() 0 49 3
A getProperty() 0 10 2
A hasProperty() 0 4 1
A getDefaultProperties() 0 11 1
A getFileName() 0 4 1
A getLocatedSource() 0 4 1
A getStartLine() 0 4 1
A getEndLine() 0 4 1
A getStartColumn() 0 4 1
A getEndColumn() 0 4 1
A getParentClass() 0 16 5
A getParentClassNames() 0 6 1
A getDocComment() 0 4 1
A isAnonymous() 0 4 1
A isInternal() 0 4 1
A isUserDefined() 0 4 1
A isAbstract() 0 4 2
A isFinal() 0 4 2
A getModifiers() 0 8 3
A isTrait() 0 4 1
A isInterface() 0 4 1
A getTraits() 0 19 1
A reflectClassForNamedNode() 0 13 2
A getTraitNames() 0 9 1
B getTraitAliases() 0 38 6
A getInterfaces() 0 9 1
A getImmediateInterfaces() 0 4 1
A getInterfaceNames() 0 9 1
A isInstance() 0 12 2
A isSubclassOf() 0 13 1
A implementsInterface() 0 4 1
A isInstantiable() 0 22 5
A isCloneable() 0 12 3
A isIterateable() 0 4 2
A getCurrentClassImplementedInterfacesIndexedByName() 0 23 3
A getInheritanceClassHierarchy() 0 8 2
A getInterfacesHierarchy() 0 21 2
A __clone() 0 4 1
A getStaticPropertyValue() 0 10 3
A setStaticPropertyValue() 0 10 3
A getStaticProperties() 0 14 3
A getAst() 0 4 1
A getDeclaringNamespaceAst() 0 4 1
A setFinal() 0 14 3
A removeMethod() 0 13 4
A addMethod() 0 5 1
A addProperty() 0 26 4
A removeProperty() 0 24 4

How to fix   Complexity   

Complex Class

Complex classes like ReflectionClass 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 ReflectionClass, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Roave\BetterReflection\Reflection;
6
7
use InvalidArgumentException;
8
use OutOfBoundsException;
9
use PhpParser\Node;
10
use PhpParser\Node\Stmt\Class_ as ClassNode;
11
use PhpParser\Node\Stmt\ClassConst as ConstNode;
12
use PhpParser\Node\Stmt\ClassLike as ClassLikeNode;
13
use PhpParser\Node\Stmt\ClassMethod;
14
use PhpParser\Node\Stmt\Interface_ as InterfaceNode;
15
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
16
use PhpParser\Node\Stmt\Namespace_;
17
use PhpParser\Node\Stmt\Property as PropertyNode;
18
use PhpParser\Node\Stmt\Trait_ as TraitNode;
19
use PhpParser\Node\Stmt\TraitUse;
20
use ReflectionClass as CoreReflectionClass;
21
use ReflectionException;
22
use ReflectionProperty as CoreReflectionProperty;
23
use Roave\BetterReflection\BetterReflection;
24
use Roave\BetterReflection\Reflection\Exception\ClassDoesNotExist;
25
use Roave\BetterReflection\Reflection\Exception\NoObjectProvided;
26
use Roave\BetterReflection\Reflection\Exception\NotAClassReflection;
27
use Roave\BetterReflection\Reflection\Exception\NotAnInterfaceReflection;
28
use Roave\BetterReflection\Reflection\Exception\NotAnObject;
29
use Roave\BetterReflection\Reflection\Exception\ObjectNotInstanceOfClass;
30
use Roave\BetterReflection\Reflection\Exception\PropertyDoesNotExist;
31
use Roave\BetterReflection\Reflection\Exception\Uncloneable;
32
use Roave\BetterReflection\Reflection\StringCast\ReflectionClassStringCast;
33
use Roave\BetterReflection\Reflector\Exception\IdentifierNotFound;
34
use Roave\BetterReflection\Reflector\Reflector;
35
use Roave\BetterReflection\SourceLocator\Located\LocatedSource;
36
use Roave\BetterReflection\Util\CalculateReflectionColum;
37
use Roave\BetterReflection\Util\GetFirstDocComment;
38
use Traversable;
39
use function array_combine;
40
use function array_filter;
41
use function array_map;
42
use function array_merge;
43
use function array_reverse;
44
use function array_slice;
45
use function array_values;
46
use function implode;
47
use function in_array;
48
use function is_object;
49
use function ltrim;
50
use function sha1;
51
use function sprintf;
52
use function strtolower;
53
54
class ReflectionClass implements Reflection
55
{
56
    public const ANONYMOUS_CLASS_NAME_PREFIX = '[email protected]';
57
58
    /** @var Reflector */
59
    private $reflector;
60
61
    /** @var NamespaceNode|null */
62
    private $declaringNamespace;
63
64
    /** @var LocatedSource */
65
    private $locatedSource;
66
67
    /** @var ClassLikeNode */
68
    private $node;
69
70
    /** @var ReflectionClassConstant[]|null indexed by name, when present */
71
    private $cachedReflectionConstants;
72
73
    /** @var ReflectionProperty[]|null */
74
    private $cachedImmediateProperties;
75
76
    /** @var ReflectionProperty[]|null */
77
    private $cachedProperties;
78
79
    /** @var ReflectionMethod[]|null */
80
    private $cachedMethods;
81
82
    private function __construct()
83 135
    {
84
    }
85 135
86
    public function __toString() : string
87
    {
88
        return ReflectionClassStringCast::toString($this);
89
    }
90 1
91
    /**
92 1
     * Create a ReflectionClass by name, using default reflectors etc.
93
     *
94
     * @throws IdentifierNotFound
95
     */
96 1
    public static function createFromName(string $className) : self
97
    {
98
        return (new BetterReflection())->classReflector()->reflect($className);
99 2
    }
100
101 2
    /**
102
     * Create a ReflectionClass from an instance, using default reflectors etc.
103
     *
104
     * This is simply a helper method that calls ReflectionObject::createFromInstance().
105
     *
106
     * @see ReflectionObject::createFromInstance
107
     *
108
     * @param object $instance
109 6
     *
110
     * @throws IdentifierNotFound
111 6
     * @throws ReflectionException
112
     * @throws InvalidArgumentException
113
     *
114
     * @psalm-suppress DocblockTypeContradiction
115
     */
116
    public static function createFromInstance($instance) : self
117
    {
118
        if (! is_object($instance)) {
119
            throw new InvalidArgumentException('Instance must be an instance of an object');
120
        }
121
122
        return ReflectionObject::createFromInstance($instance);
123
    }
124
125
    /**
126
     * Create from a Class Node.
127
     *
128
     * @internal
129 2
     *
130
     * @param ClassLikeNode      $node      Node has to be processed by the PhpParser\NodeVisitor\NameResolver
131 2
     * @param NamespaceNode|null $namespace optional - if omitted, we assume it is global namespaced class
132 1
     */
133
    public static function createFromNode(
134
        Reflector $reflector,
135 1
        ClassLikeNode $node,
136
        LocatedSource $locatedSource,
137
        ?NamespaceNode $namespace = null
138
    ) : self {
139
        $class = new self();
140
141
        $class->reflector     = $reflector;
142
        $class->locatedSource = $locatedSource;
143
        $class->node          = $node;
144
145
        if ($namespace !== null) {
146 135
            $class->declaringNamespace = $namespace;
147
        }
148
149
        return $class;
150
    }
151
152 135
    /**
153
     * Get the "short" name of the class (e.g. for A\B\Foo, this will return
154 135
     * "Foo").
155 135
     */
156 135
    public function getShortName() : string
157
    {
158 135
        if (! $this->isAnonymous()) {
159 81
            /** @var Node\Identifier $this->node->name */
160
            return $this->node->name->name;
161
        }
162 135
163
        $fileName = $this->getFileName();
164
165
        if ($fileName === null) {
166
            $fileName = sha1($this->locatedSource->getSource());
167
        }
168
169 68
        return sprintf('%s%c%s(%d)', self::ANONYMOUS_CLASS_NAME_PREFIX, "\0", $fileName, $this->getStartLine());
170
    }
171 68
172
    /**
173 64
     * Get the "full" name of the class (e.g. for A\B\Foo, this will return
174
     * "A\B\Foo").
175
     */
176 4
    public function getName() : string
177
    {
178 4
        if (! $this->inNamespace()) {
179 1
            return $this->getShortName();
180
        }
181
182 4
        return $this->node->namespacedName->toString();
183
    }
184
185
    /**
186
     * Get the "namespace" name of the class (e.g. for A\B\Foo, this will
187
     * return "A\B").
188
     *
189 135
     * @psalm-suppress PossiblyNullPropertyFetch
190
     */
191 135
    public function getNamespaceName() : string
192 64
    {
193
        if (! $this->inNamespace()) {
194
            return '';
195 81
        }
196
197
        return implode('\\', $this->declaringNamespace->name->parts);
198
    }
199
200
    /**
201
     * Decide if this class is part of a namespace. Returns false if the class
202
     * is in the global namespace or does not have a specified namespace.
203
     */
204 3
    public function inNamespace() : bool
205
    {
206 3
        return $this->declaringNamespace !== null
207 2
            && $this->declaringNamespace->name !== null;
208
    }
209
210 1
    public function getExtensionName() : ?string
211
    {
212
        return $this->locatedSource->getExtensionName();
213
    }
214
215
    /**
216
     * Construct a flat list of all methods from current class, traits,
217 135
     * parent classes and interfaces in this precise order.
218
     *
219 135
     * @return ReflectionMethod[]
220 135
     */
221
    private function getAllMethods() : array
222
    {
223 2
        return array_merge(
224
            [],
225 2
            array_map(
226
                function (ClassMethod $methodNode) : ReflectionMethod {
227
                    return ReflectionMethod::createFromNode(
228
                        $this->reflector,
229
                        $methodNode,
230
                        $this->declaringNamespace,
231
                        $this,
232
                        $this
233
                    );
234 25
                },
235
                $this->node->getMethods()
236 25
            ),
237 25
            ...array_map(
238 25
                function (ReflectionClass $trait) : array {
239
                    return array_map(function (ReflectionMethod $method) use ($trait) : ReflectionMethod {
240 24
                        /** @var ClassMethod $methodAst */
241 24
                        $methodAst = $method->getAst();
242 24
243 24
                        return ReflectionMethod::createFromNode(
244 24
                            $this->reflector,
245 24
                            $methodAst,
246
                            $this->declaringNamespace,
247 25
                            $trait,
248 25
                            $this
249
                        );
250 25
                    }, $trait->getMethods());
251
                },
252
                $this->getTraits()
253
            ),
254 4
            ...array_map(
255
                static function (ReflectionClass $ancestor) : array {
256 4
                    return $ancestor->getMethods();
257 4
                },
258 4
                array_values(array_merge(
259 4
                    array_filter([$this->getParentClass()]),
260 4
                    $this->getInterfaces()
261 4
                ))
262
            )
263 4
        );
264 25
    }
265 25
266
    /**
267 25
     * Construct a flat list of methods that are available. This will search up
268
     * all parent classes/traits/interfaces/current scope for methods.
269 5
     *
270 25
     * Methods are not merged via their name as array index, since internal PHP method
271 25
     * sorting does not follow `\array_merge()` semantics.
272 25
     *
273 25
     * @return ReflectionMethod[] indexed by method name
274
     */
275
    private function getMethodsIndexedByName() : array
276
    {
277
        if ($this->cachedMethods !== null) {
278
            return $this->cachedMethods;
279
        }
280
281
        $this->cachedMethods = [];
282
283
        $traitAliases = $this->getTraitAliases();
284
285
        foreach ($this->getAllMethods() as $method) {
286
            $methodName              = $method->getName();
287
            $methodNameWithClassName = sprintf('%s::%s', $method->getDeclaringClass()->getName(), $methodName);
288 25
289
            foreach ($traitAliases as $methodAlias => $traitMethodNameWithTraitName) {
290 25
                if ($methodNameWithClassName !== $traitMethodNameWithTraitName) {
291 7
                    continue;
292
                }
293
294 25
                if (isset($this->cachedMethods[$methodAlias])) {
295
                    continue;
296 25
                }
297
298 25
                $this->cachedMethods[$methodAlias] = $method;
299 24
            }
300 24
301
            if (isset($this->cachedMethods[$methodName])) {
302 24
                continue;
303 2
            }
304 2
305
            $this->cachedMethods[$methodName] = $method;
306
        }
307 2
308
        return $this->cachedMethods;
309
    }
310
311 2
    /**
312
     * Fetch an array of all methods for this class.
313
     *
314 24
     * Filter the results to include only methods with certain attributes. Defaults
315 2
     * to no filtering.
316
     * Any combination of \ReflectionMethod::IS_STATIC,
317
     * \ReflectionMethod::IS_PUBLIC,
318 24
     * \ReflectionMethod::IS_PROTECTED,
319
     * \ReflectionMethod::IS_PRIVATE,
320
     * \ReflectionMethod::IS_ABSTRACT,
321 25
     * \ReflectionMethod::IS_FINAL.
322
     * For example if $filter = \ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_FINAL
323
     * the only the final public methods will be returned
324
     *
325
     * @return ReflectionMethod[]
326
     */
327
    public function getMethods(?int $filter = null) : array
328
    {
329
        if ($filter === null) {
330
            return array_values($this->getMethodsIndexedByName());
331
        }
332
333
        return array_values(
334
            array_filter(
335
                $this->getMethodsIndexedByName(),
336
                static function (ReflectionMethod $method) use ($filter) : bool {
337
                    return (bool) ($filter & $method->getModifiers());
338
                }
339
            )
340 22
        );
341
    }
342 22
343 15
    /**
344
     * Get only the methods that this class implements (i.e. do not search
345
     * up parent classes etc.)
346 7
     *
347 7
     * @see ReflectionClass::getMethods for the usage of $filter
348 7
     *
349
     * @return ReflectionMethod[]
350 7
     */
351 7
    public function getImmediateMethods(?int $filter = null) : array
352
    {
353
        /** @var ReflectionMethod[] $methods */
354
        $methods = array_map(
355
            function (ClassMethod $methodNode) : ReflectionMethod {
356
                return ReflectionMethod::createFromNode(
357
                    $this->reflector,
358
                    $methodNode,
359
                    $this->declaringNamespace,
360
                    $this,
361
                    $this
362
                );
363
            },
364 8
            $this->node->getMethods()
365
        );
366
367 8
        $methodsByName = [];
368
369 8
        foreach ($methods as $method) {
370 8
            if ($filter !== null && ! ($filter & $method->getModifiers())) {
371 8
                continue;
372 8
            }
373 8
374 8
            $methodsByName[$method->getName()] = $method;
375
        }
376 8
377 8
        return $methodsByName;
378
    }
379
380 8
    /**
381
     * Get a single method with the name $methodName.
382 8
     *
383 8
     * @throws OutOfBoundsException
384 6
     */
385
    public function getMethod(string $methodName) : ReflectionMethod
386
    {
387 8
        $methods = $this->getMethodsIndexedByName();
388
389
        if (! isset($methods[$methodName])) {
390 8
            throw new OutOfBoundsException('Could not find method: ' . $methodName);
391
        }
392
393
        return $methods[$methodName];
394
    }
395
396
    /**
397
     * Does the class have the specified method method?
398 7
     */
399
    public function hasMethod(string $methodName) : bool
400 7
    {
401
        try {
402 7
            $this->getMethod($methodName);
403 4
404
            return true;
405
        } catch (OutOfBoundsException $exception) {
406 7
            return false;
407
        }
408
    }
409
410
    /**
411
     * Get an associative array of only the constants for this specific class (i.e. do not search
412 6
     * up parent classes etc.), with keys as constant names and values as constant values.
413
     *
414
     * @return string[]|int[]|float[]|array[]|bool[]|null[]
415 6
     */
416
    public function getImmediateConstants() : array
417 6
    {
418 4
        return array_map(static function (ReflectionClassConstant $classConstant) {
419 4
            return $classConstant->getValue();
420
        }, $this->getImmediateReflectionConstants());
421
    }
422
423
    /**
424
     * Get an associative array of the defined constants in this class,
425
     * with keys as constant names and values as constant values.
426
     *
427
     * @return string[]|int[]|float[]|array[]|bool[]|null[]
428
     */
429 1
    public function getConstants() : array
430
    {
431
        return array_map(static function (ReflectionClassConstant $classConstant) {
432 1
            return $classConstant->getValue();
433 1
        }, $this->getReflectionConstants());
434
    }
435
436
    /**
437
     * Get the value of the specified class constant.
438
     *
439
     * Returns null if not specified.
440
     *
441
     * @return mixed|null
442 4
     */
443
    public function getConstant(string $name)
444
    {
445 4
        $reflectionConstant = $this->getReflectionConstant($name);
446 4
447
        if (! $reflectionConstant) {
448
            return null;
449
        }
450
451
        return $reflectionConstant->getValue();
452
    }
453
454
    /**
455
     * Does this class have the specified constant?
456 3
     */
457
    public function hasConstant(string $name) : bool
458 3
    {
459
        return $this->getReflectionConstant($name) !== null;
460 3
    }
461 2
462
    /**
463
     * Get the reflection object of the specified class constant.
464 2
     *
465
     * Returns null if not specified.
466
     */
467
    public function getReflectionConstant(string $name) : ?ReflectionClassConstant
468
    {
469
        return $this->getReflectionConstants()[$name] ?? null;
470 2
    }
471
472 2
    /**
473
     * Get an associative array of only the constants for this specific class (i.e. do not search
474
     * up parent classes etc.), with keys as constant names and values as {@see ReflectionClassConstant} objects.
475
     *
476
     * @return ReflectionClassConstant[] indexed by name
477
     */
478
    public function getImmediateReflectionConstants() : array
479
    {
480 25
        if ($this->cachedReflectionConstants !== null) {
481
            return $this->cachedReflectionConstants;
482 25
        }
483
484
        $constants = array_merge(
485
            [],
486
            ...array_map(
487
                function (ConstNode $constNode) : array {
488
                    $constants = [];
489
490
                    foreach ($constNode->consts as $constantPositionInNode => $constantNode) {
491 34
                        $constants[] = ReflectionClassConstant::createFromNode($this->reflector, $constNode, $constantPositionInNode, $this);
492
                    }
493 34
494 4
                    return $constants;
495
                },
496
                array_filter(
497 34
                    $this->node->stmts,
498 34
                    static function (Node\Stmt $stmt) : bool {
499 34
                        return $stmt instanceof ConstNode;
500
                    }
501 34
                )
502
            )
503 34
        );
504 34
505
        return $this->cachedReflectionConstants = array_combine(
506
            array_map(
507 34
                static function (ReflectionClassConstant $constant) : string {
508 34
                    return $constant->getName();
509 34
                },
510 34
                $constants
511
            ),
512 34
            $constants
513 34
        );
514
    }
515
516
    /**
517
     * Get an associative array of the defined constants in this class,
518 34
     * with keys as constant names and values as {@see ReflectionClassConstant} objects.
519 34
     *
520
     * @return ReflectionClassConstant[] indexed by name
521 34
     */
522 34
    public function getReflectionConstants() : array
523 34
    {
524
        // Note: constants are not merged via their name as array index, since internal PHP constant
525 34
        //       sorting does not follow `\array_merge()` semantics
526
        /** @var ReflectionClassConstant[] $allReflectionConstants */
527
        $allReflectionConstants = array_merge(
528
            array_values($this->getImmediateReflectionConstants()),
529
            ...array_map(
530
                static function (ReflectionClass $ancestor) : array {
531
                    return array_filter(
532
                        array_values($ancestor->getReflectionConstants()),
533
                        static function (ReflectionClassConstant $classConstant) : bool {
534
                            return ! $classConstant->isPrivate();
535 32
                        }
536
                    );
537
                },
538
                array_filter([$this->getParentClass()])
539
            ),
540 32
            ...array_map(
541 32
                static function (ReflectionClass $interface) : array {
542 32
                    return array_values($interface->getReflectionConstants());
543
                },
544 2
                array_values($this->getInterfaces())
545 2
            )
546
        );
547 2
548 2
        $reflectionConstants = [];
549
550 32
        foreach ($allReflectionConstants as $constant) {
551 32
            $constantName = $constant->getName();
552
553 32
            if (isset($reflectionConstants[$constantName])) {
554
                continue;
555 2
            }
556 32
557 32
            $reflectionConstants[$constantName] = $constant;
558
        }
559
560
        return $reflectionConstants;
561 32
    }
562
563 32
    /**
564 32
     * Get the constructor method for this class.
565
     *
566 32
     * @throws OutOfBoundsException
567 2
     */
568
    public function getConstructor() : ReflectionMethod
569
    {
570 32
        $constructors = array_filter($this->getMethods(), static function (ReflectionMethod $method) : bool {
571
            return $method->isConstructor();
572
        });
573 32
574
        if (! isset($constructors[0])) {
575
            throw new OutOfBoundsException('Could not find method: __construct');
576
        }
577
578
        return $constructors[0];
579
    }
580
581 8
    /**
582
     * Get only the properties for this specific class (i.e. do not search
583
     * up parent classes etc.)
584 7
     *
585 8
     * @see ReflectionClass::getProperties() for the usage of filter
586
     *
587 8
     * @return ReflectionProperty[]
588 4
     */
589
    public function getImmediateProperties(?int $filter = null) : array
590
    {
591 6
        if ($this->cachedImmediateProperties === null) {
592
            $properties = [];
593
            foreach ($this->node->stmts as $stmt) {
594
                if (! ($stmt instanceof PropertyNode)) {
595
                    continue;
596
                }
597
598
                foreach ($stmt->props as $propertyPositionInNode => $propertyNode) {
599
                    $prop                         = ReflectionProperty::createFromNode(
600
                        $this->reflector,
601
                        $stmt,
602 19
                        $propertyPositionInNode,
603
                        $this->declaringNamespace,
604 19
                        $this,
605 19
                        $this
606 19
                    );
607 19
                    $properties[$prop->getName()] = $prop;
608 13
                }
609
            }
610
611 19
            $this->cachedImmediateProperties = $properties;
612 19
        }
613 19
614 19
        if ($filter === null) {
615 19
            return $this->cachedImmediateProperties;
616 19
        }
617 19
618 19
        return array_filter(
619
            $this->cachedImmediateProperties,
620 19
            static function (ReflectionProperty $property) use ($filter) : bool {
621
                return (bool) ($filter & $property->getModifiers());
622
            }
623
        );
624 19
    }
625
626
    /**
627 19
     * Get the properties for this class.
628 19
     *
629
     * Filter the results to include only properties with certain attributes. Defaults
630
     * to no filtering.
631 5
     * Any combination of \ReflectionProperty::IS_STATIC,
632 5
     * \ReflectionProperty::IS_PUBLIC,
633
     * \ReflectionProperty::IS_PROTECTED,
634 5
     * \ReflectionProperty::IS_PRIVATE.
635 5
     * For example if $filter = \ReflectionProperty::IS_STATIC | \ReflectionProperty::IS_PUBLIC
636
     * only the static public properties will be returned
637
     *
638
     * @return ReflectionProperty[]
639
     */
640
    public function getProperties(?int $filter = null) : array
641
    {
642
        if ($this->cachedProperties === null) {
643
            // merging together properties from parent class, traits, current class (in this precise order)
644
            $this->cachedProperties = array_merge(
0 ignored issues
show
Documentation Bug introduced by Jaroslav Hanslík
It seems like \array_merge(\array_merg...tImmediateProperties()) of type array is incompatible with the declared type array<integer,object<Roa...flectionProperty>>|null of property $cachedProperties.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
645
                array_merge(
646
                    [],
647
                    ...array_map(
648
                        static function (ReflectionClass $ancestor) use ($filter) : array {
649
                            return array_filter(
650
                                $ancestor->getProperties($filter),
651
                                static function (ReflectionProperty $property) : bool {
652
                                    return ! $property->isPrivate();
653 19
                                }
654
                            );
655 19
                        },
656
                        array_filter([$this->getParentClass()])
657 19
                    ),
658 19
                    ...array_map(
659 19
                        function (ReflectionClass $trait) use ($filter) {
660 19
                            return array_map(function (ReflectionProperty $property) use ($trait) : ReflectionProperty {
661
                                return ReflectionProperty::createFromNode(
662 3
                                    $this->reflector,
663 3
                                    $property->getAst(),
664
                                    $property->getPositionInAst(),
665 3
                                    $trait->declaringNamespace,
666 3
                                    $property->getDeclaringClass(),
667
                                    $this
668 19
                                );
669 19
                            }, $trait->getProperties($filter));
670
                        },
671 19
                        $this->getTraits()
672
                    )
673
                ),
674 2
                $this->getImmediateProperties()
675 2
            );
676 2
        }
677 2
678 2
        if ($filter === null) {
679 2
            return $this->cachedProperties;
680 2
        }
681
682 2
        return array_filter(
683 19
            $this->cachedProperties,
684 19
            static function (ReflectionProperty $property) use ($filter) : bool {
685
                return (bool) ($filter & $property->getModifiers());
686
            }
687 19
        );
688
    }
689
690
    /**
691 19
     * Get the property called $name.
692 14
     *
693
     * Returns null if property does not exist.
694
     */
695 5
    public function getProperty(string $name) : ?ReflectionProperty
696 5
    {
697
        $properties = $this->getProperties();
698 5
699 5
        if (! isset($properties[$name])) {
700
            return null;
701
        }
702
703
        return $properties[$name];
704
    }
705
706
    /**
707
     * Does this class have the specified property?
708 8
     */
709
    public function hasProperty(string $name) : bool
710 8
    {
711
        return $this->getProperty($name) !== null;
712 8
    }
713 6
714
    /**
715
     * @return mixed[]
716 6
     */
717
    public function getDefaultProperties() : array
718
    {
719
        return array_map(
720
            static function (ReflectionProperty $property) {
721
                return $property->getDefaultValue();
722 3
            },
723
            array_filter($this->getProperties(), static function (ReflectionProperty $property) : bool {
724 3
                return $property->isDefault();
725
            })
726
        );
727
    }
728
729
    public function getFileName() : ?string
730 1
    {
731
        return $this->locatedSource->getFileName();
732 1
    }
733
734 1
    public function getLocatedSource() : LocatedSource
735 1
    {
736
        return $this->locatedSource;
737 1
    }
738 1
739
    /**
740
     * Get the line number that this class starts on.
741
     */
742 8
    public function getStartLine() : int
743
    {
744 8
        return $this->node->getStartLine();
745
    }
746
747 28
    /**
748
     * Get the line number that this class ends on.
749 28
     */
750
    public function getEndLine() : int
751
    {
752
        return $this->node->getEndLine();
753
    }
754
755 9
    public function getStartColumn() : int
756
    {
757 9
        return CalculateReflectionColum::getStartColumn($this->locatedSource->getSource(), $this->node);
758
    }
759
760
    public function getEndColumn() : int
761
    {
762
        return CalculateReflectionColum::getEndColumn($this->locatedSource->getSource(), $this->node);
763 5
    }
764
765 5
    /**
766
     * Get the parent class, if it is defined. If this class does not have a
767
     * specified parent class, this will throw an exception.
768 3
     *
769
     * @throws NotAClassReflection
770 3
     */
771
    public function getParentClass() : ?ReflectionClass
772
    {
773 3
        if (! ($this->node instanceof ClassNode) || $this->node->extends === null) {
774
            return null;
775 3
        }
776
777
        // @TODO use actual `ClassReflector` or `FunctionReflector`?
778
        /** @var self $parent */
779
        $parent = $this->reflector->reflect($this->node->extends->toString());
780
781
        if ($parent->isInterface() || $parent->isTrait()) {
782
            throw NotAClassReflection::fromReflectionClass($parent);
783
        }
784 89
785
        return $parent;
786 89
    }
787 86
788
    /**
789
     * Gets the parent class names.
790
     *
791
     * @return string[] A numerical array with parent class names as the values.
792 19
     */
793
    public function getParentClassNames() : array
794 19
    {
795 2
        return array_map(static function (self $parentClass) : string {
796
            return $parentClass->getName();
797
        }, array_slice(array_reverse($this->getInheritanceClassHierarchy()), 1));
798 17
    }
799
800
    public function getDocComment() : string
801
    {
802
        return GetFirstDocComment::forNode($this->node);
803
    }
804
805
    public function isAnonymous() : bool
806 1
    {
807
        return $this->node->name === null;
808
    }
809 1
810 1
    /**
811
     * Is this an internal class?
812
     */
813 3
    public function isInternal() : bool
814
    {
815 3
        return $this->locatedSource->isInternal();
816
    }
817
818 73
    /**
819
     * Is this a user-defined function (will always return the opposite of
820 73
     * whatever isInternal returns).
821
     */
822
    public function isUserDefined() : bool
823
    {
824
        return ! $this->isInternal();
825
    }
826 4
827
    /**
828 4
     * Is this class an abstract class.
829
     */
830
    public function isAbstract() : bool
831
    {
832
        return $this->node instanceof ClassNode && $this->node->isAbstract();
833
    }
834
835 4
    /**
836
     * Is this class a final class.
837 4
     */
838
    public function isFinal() : bool
839
    {
840
        return $this->node instanceof ClassNode && $this->node->isFinal();
841
    }
842
843 9
    /**
844
     * Get the core-reflection-compatible modifier values.
845 9
     */
846
    public function getModifiers() : int
847
    {
848
        $val  = 0;
849
        $val += $this->isAbstract() ? CoreReflectionClass::IS_EXPLICIT_ABSTRACT : 0;
850
        $val += $this->isFinal() ? CoreReflectionClass::IS_FINAL : 0;
851 7
852
        return $val;
853 7
    }
854
855
    /**
856
     * Is this reflection a trait?
857
     */
858
    public function isTrait() : bool
859 3
    {
860
        return $this->node instanceof TraitNode;
861 3
    }
862 3
863 3
    /**
864
     * Is this reflection an interface?
865 3
     */
866
    public function isInterface() : bool
867
    {
868
        return $this->node instanceof InterfaceNode;
869
    }
870
871 27
    /**
872
     * Get the traits used, if any are defined. If this class does not have any
873 27
     * defined traits, this will return an empty array.
874
     *
875
     * @return ReflectionClass[]
876
     */
877
    public function getTraits() : array
878
    {
879 33
        return array_map(
880
            function (Node\Name $importedTrait) : ReflectionClass {
881 33
                return $this->reflectClassForNamedNode($importedTrait);
882
            },
883
            array_merge(
884
                [],
885
                ...array_map(
886
                    static function (TraitUse $traitUse) : array {
887
                        return $traitUse->traits;
888
                    },
889
                    array_filter($this->node->stmts, static function (Node $node) : bool {
890 45
                        return $node instanceof TraitUse;
891
                    })
892 45
                )
893
            )
894 8
        );
895 45
    }
896 45
897 45
    /**
898 45
     * Given an AST Node\Name, create a new ReflectionClass for the element.
899
     */
900 8
    private function reflectClassForNamedNode(Node\Name $node) : self
901 45
    {
902
        // @TODO use actual `ClassReflector` or `FunctionReflector`?
903 43
        if ($this->isAnonymous()) {
904 45
            /** @var self $class */
905
            $class = (new BetterReflection())->classReflector()->reflect($node->toString());
906
        } else {
907
            /** @var self $class */
908
            $class = $this->reflector->reflect($node->toString());
909
        }
910
911
        return $class;
912
    }
913 23
914
    /**
915
     * Get the names of the traits used as an array of strings, if any are
916 23
     * defined. If this class does not have any defined traits, this will
917
     * return an empty array.
918
     *
919
     * @return string[]
920
     */
921 23
    public function getTraitNames() : array
922
    {
923
        return array_map(
924 23
            static function (ReflectionClass $trait) : string {
925
                return $trait->getName();
926
            },
927
            $this->getTraits()
928
        );
929
    }
930
931
    /**
932
     * Return a list of the aliases used when importing traits for this class.
933
     * The returned array is in key/value pair in this format:.
934 1
     *
935
     *   'aliasedMethodName' => 'ActualClass::actualMethod'
936 1
     *
937
     * @return string[]
938 1
     *
939 1
     * @example
940 1
     * // When reflecting a class such as:
941
     * class Foo
942
     * {
943
     *     use MyTrait {
944
     *         myTraitMethod as myAliasedMethod;
945
     *     }
946
     * }
947
     * // This method would return
948
     * //   ['myAliasedMethod' => 'MyTrait::myTraitMethod']
949
     */
950
    public function getTraitAliases() : array
951
    {
952
        /** @var Node\Stmt\TraitUse[] $traitUsages */
953
        $traitUsages = array_filter($this->node->stmts, static function (Node $node) : bool {
954
            return $node instanceof TraitUse;
955
        });
956
957
        $resolvedAliases = [];
958
959
        foreach ($traitUsages as $traitUsage) {
960
            $traitNames  = $traitUsage->traits;
961
            $adaptations = $traitUsage->adaptations;
962
963 26
            foreach ($adaptations as $adaptation) {
964
                /** @var Node\Name|null $usedTrait */
965
                $usedTrait = $adaptation->trait;
966
                if ($usedTrait === null) {
967 25
                    $usedTrait = $traitNames[0];
968 26
                }
969
970 26
                if (! ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Alias)) {
971
                    continue;
972 26
                }
973 5
974 5
                if (! $adaptation->newName) {
975
                    continue;
976 5
                }
977
978 3
                $resolvedAliases[$adaptation->newName->name] = sprintf(
979 3
                    '%s::%s',
980 2
                    $usedTrait->toString(),
981
                    $adaptation->method->toString()
982
                );
983 3
            }
984 1
        }
985
986
        return $resolvedAliases;
987 3
    }
988 2
989
    /**
990
     * Gets the interfaces.
991 3
     *
992 3
     * @link https://php.net/manual/en/reflectionclass.getinterfaces.php
993 3
     *
994 3
     * @return ReflectionClass[] An associative array of interfaces, with keys as interface names and the array
995
     *                           values as {@see ReflectionClass} objects.
996
     */
997
    public function getInterfaces() : array
998
    {
999 26
        return array_merge(...array_map(
1000
            static function (self $reflectionClass) : array {
1001
                return $reflectionClass->getCurrentClassImplementedInterfacesIndexedByName();
1002
            },
1003
            $this->getInheritanceClassHierarchy()
1004
        ));
1005
    }
1006
1007
    /**
1008
     * Get only the interfaces that this class implements (i.e. do not search
1009
     * up parent classes etc.)
1010 66
     *
1011
     * @return ReflectionClass[]
1012 66
     */
1013
    public function getImmediateInterfaces() : array
1014 66
    {
1015 66
        return $this->getCurrentClassImplementedInterfacesIndexedByName();
1016 66
    }
1017
1018
    /**
1019
     * Gets the interface names.
1020
     *
1021
     * @link https://php.net/manual/en/reflectionclass.getinterfacenames.php
1022
     *
1023
     * @return string[] A numerical array with interface names as the values.
1024
     */
1025
    public function getInterfaceNames() : array
1026 4
    {
1027
        return array_values(array_map(
1028 4
            static function (self $interface) : string {
1029
                return $interface->getName();
1030
            },
1031
            $this->getInterfaces()
1032
        ));
1033
    }
1034
1035
    /**
1036
     * Checks whether the given object is an instance.
1037
     *
1038 7
     * @link https://php.net/manual/en/reflectionclass.isinstance.php
1039
     *
1040 7
     * @param object $object
1041
     *
1042 5
     * @throws NotAnObject
1043 7
     *
1044 7
     * @psalm-suppress DocblockTypeContradiction
1045
     */
1046
    public function isInstance($object) : bool
1047
    {