Issues (138)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Reflection/ReflectionClass.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 assert;
47
use function implode;
48
use function in_array;
49
use function is_object;
50
use function ltrim;
51
use function sha1;
52
use function sprintf;
53
use function strtolower;
54
55
class ReflectionClass implements Reflection
56
{
57
    public const ANONYMOUS_CLASS_NAME_PREFIX = '[email protected]';
58
59
    /** @var Reflector */
60
    private $reflector;
61
62
    /** @var NamespaceNode|null */
63
    private $declaringNamespace;
64
65
    /** @var LocatedSource */
66
    private $locatedSource;
67
68
    /** @var ClassLikeNode */
69
    private $node;
70
71
    /**
72
     * @var ReflectionClassConstant[]|null indexed by name, when present
73
     * @psalm-var ?array<string, ReflectionClassConstant>
74
     */
75
    private $cachedReflectionConstants;
76
77
    /**
78
     * @var ReflectionProperty[]|null
79
     * @psalm-var ?array<string, ReflectionProperty>
80
     */
81
    private $cachedImmediateProperties;
82
83
    /**
84
     * @var ReflectionProperty[]|null
85
     * @psalm-var ?array<string, ReflectionProperty>
86
     */
87
    private $cachedProperties;
88
89
    /**
90
     * @var ReflectionMethod[]|null
91
     * @psalm-var ?array<string, ReflectionMethod>
92
     */
93
    private $cachedMethods;
94
95 134
    private function __construct()
96
    {
97 134
    }
98
99 1
    public function __toString() : string
100
    {
101 1
        return ReflectionClassStringCast::toString($this);
102
    }
103
104
    /**
105
     * Create a ReflectionClass by name, using default reflectors etc.
106
     *
107
     * @throws IdentifierNotFound
108
     */
109 5
    public static function createFromName(string $className) : self
110
    {
111 5
        return (new BetterReflection())->classReflector()->reflect($className);
112
    }
113
114
    /**
115
     * Create a ReflectionClass from an instance, using default reflectors etc.
116
     *
117
     * This is simply a helper method that calls ReflectionObject::createFromInstance().
118
     *
119
     * @see ReflectionObject::createFromInstance
120
     *
121
     * @param object $instance
122
     *
123
     * @throws IdentifierNotFound
124
     * @throws ReflectionException
125
     * @throws InvalidArgumentException
126
     *
127
     * @psalm-suppress DocblockTypeContradiction
128
     */
129 2
    public static function createFromInstance($instance) : self
130
    {
131 2
        if (! is_object($instance)) {
132 1
            throw new InvalidArgumentException('Instance must be an instance of an object');
133
        }
134
135 1
        return ReflectionObject::createFromInstance($instance);
136
    }
137
138
    /**
139
     * Create from a Class Node.
140
     *
141
     * @internal
142
     *
143
     * @param ClassLikeNode      $node      Node has to be processed by the PhpParser\NodeVisitor\NameResolver
144
     * @param NamespaceNode|null $namespace optional - if omitted, we assume it is global namespaced class
145
     */
146 134
    public static function createFromNode(
147
        Reflector $reflector,
148
        ClassLikeNode $node,
149
        LocatedSource $locatedSource,
150
        ?NamespaceNode $namespace = null
151
    ) : self {
152 134
        $class = new self();
153
154 134
        $class->reflector     = $reflector;
155 134
        $class->locatedSource = $locatedSource;
156 134
        $class->node          = $node;
157
158 134
        if ($namespace !== null) {
159 81
            $class->declaringNamespace = $namespace;
160
        }
161
162 134
        return $class;
163
    }
164
165
    /**
166
     * Get the "short" name of the class (e.g. for A\B\Foo, this will return
167
     * "Foo").
168
     */
169 67
    public function getShortName() : string
170
    {
171 67
        if (! $this->isAnonymous()) {
172 63
            assert($this->node->name instanceof Node\Identifier);
173
174 63
            return $this->node->name->name;
175
        }
176
177 4
        $fileName = $this->getFileName();
178
179 4
        if ($fileName === null) {
180 1
            $fileName = sha1($this->locatedSource->getSource());
181
        }
182
183 4
        return sprintf('%s%c%s(%d)', self::ANONYMOUS_CLASS_NAME_PREFIX, "\0", $fileName, $this->getStartLine());
184
    }
185
186
    /**
187
     * Get the "full" name of the class (e.g. for A\B\Foo, this will return
188
     * "A\B\Foo").
189
     *
190
     * @psalm-return class-string
191
     */
192 134
    public function getName() : string
193
    {
194 134
        if (! $this->inNamespace()) {
195 63
            return $this->getShortName();
196
        }
197
198 81
        return $this->node->namespacedName->toString();
199
    }
200
201
    /**
202
     * Get the "namespace" name of the class (e.g. for A\B\Foo, this will
203
     * return "A\B").
204
     *
205
     * @psalm-suppress PossiblyNullPropertyFetch
206
     */
207 3
    public function getNamespaceName() : string
208
    {
209 3
        if (! $this->inNamespace()) {
210 2
            return '';
211
        }
212
213 1
        return implode('\\', $this->declaringNamespace->name->parts);
214
    }
215
216
    /**
217
     * Decide if this class is part of a namespace. Returns false if the class
218
     * is in the global namespace or does not have a specified namespace.
219
     */
220 134
    public function inNamespace() : bool
221
    {
222 134
        return $this->declaringNamespace !== null
223 134
            && $this->declaringNamespace->name !== null;
224
    }
225
226 2
    public function getExtensionName() : ?string
227
    {
228 2
        return $this->locatedSource->getExtensionName();
229
    }
230
231
    /**
232
     * Construct a flat list of all methods from current class, traits,
233
     * parent classes and interfaces in this precise order.
234
     *
235
     * @return ReflectionMethod[]
236
     */
237 25
    private function getAllMethods() : array
238
    {
239 25
        return array_merge(
240 25
            [],
241 25
            array_map(
242
                function (ClassMethod $methodNode) : ReflectionMethod {
243 24
                    return ReflectionMethod::createFromNode(
244 24
                        $this->reflector,
245
                        $methodNode,
246 24
                        $this->declaringNamespace,
247
                        $this,
248
                        $this
249
                    );
250 25
                },
251 25
                $this->node->getMethods()
252
            ),
253 25
            ...array_map(
254
                function (ReflectionClass $trait) : array {
255
                    return array_map(function (ReflectionMethod $method) use ($trait) : ReflectionMethod {
256 4
                        $methodAst = $method->getAst();
257 4
                        assert($methodAst instanceof ClassMethod);
258
259 4
                        return ReflectionMethod::createFromNode(
260 4
                            $this->reflector,
261
                            $methodAst,
262 4
                            $this->declaringNamespace,
263
                            $trait,
264
                            $this
265
                        );
266 4
                    }, $trait->getMethods());
267 25
                },
268 25
                $this->getTraits()
269
            ),
270 25
            ...array_map(
271
                static function (ReflectionClass $ancestor) : array {
272 5
                    return $ancestor->getMethods();
273 25
                },
274 25
                array_values(array_merge(
275 25
                    array_filter([$this->getParentClass()]),
276 25
                    $this->getInterfaces()
277
                ))
278
            )
279
        );
280
    }
281
282
    /**
283
     * Construct a flat list of methods that are available. This will search up
284
     * all parent classes/traits/interfaces/current scope for methods.
285
     *
286
     * Methods are not merged via their name as array index, since internal PHP method
287
     * sorting does not follow `\array_merge()` semantics.
288
     *
289
     * @return ReflectionMethod[] indexed by method name
290
     *
291
     * @psalm-return array<string, ReflectionMethod>
292
     */
293 25
    private function getMethodsIndexedByName() : array
294
    {
295 25
        if ($this->cachedMethods !== null) {
296 6
            return $this->cachedMethods;
297
        }
298
299 25
        $this->cachedMethods = [];
300
301 25
        $traitAliases = $this->getTraitAliases();
302
303 25
        foreach ($this->getAllMethods() as $method) {
304 24
            $methodName              = $method->getName();
305 24
            $methodNameWithClassName = sprintf('%s::%s', $method->getDeclaringClass()->getName(), $methodName);
306
307 24
            foreach ($traitAliases as $methodAlias => $traitMethodNameWithTraitName) {
308 2
                if ($methodNameWithClassName !== $traitMethodNameWithTraitName) {
309 2
                    continue;
310
                }
311
312 2
                if (isset($this->cachedMethods[$methodAlias])) {
313
                    continue;
314
                }
315
316 2
                $this->cachedMethods[$methodAlias] = $method;
317
            }
318
319 24
            if (isset($this->cachedMethods[$methodName])) {
320 2
                continue;
321
            }
322
323 24
            $this->cachedMethods[$methodName] = $method;
324
        }
325
326 25
        return $this->cachedMethods;
327
    }
328
329
    /**
330
     * Fetch an array of all methods for this class.
331
     *
332
     * Filter the results to include only methods with certain attributes. Defaults
333
     * to no filtering.
334
     * Any combination of \ReflectionMethod::IS_STATIC,
335
     * \ReflectionMethod::IS_PUBLIC,
336
     * \ReflectionMethod::IS_PROTECTED,
337
     * \ReflectionMethod::IS_PRIVATE,
338
     * \ReflectionMethod::IS_ABSTRACT,
339
     * \ReflectionMethod::IS_FINAL.
340
     * For example if $filter = \ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_FINAL
341
     * the only the final public methods will be returned
342
     *
343
     * @return ReflectionMethod[]
344
     *
345
     * @psalm-return list<ReflectionMethod>
346
     */
347 22
    public function getMethods(?int $filter = null) : array
348
    {
349 22
        if ($filter === null) {
350 15
            return array_values($this->getMethodsIndexedByName());
351
        }
352
353 7
        return array_values(
354 7
            array_filter(
355 7
                $this->getMethodsIndexedByName(),
356
                static function (ReflectionMethod $method) use ($filter) : bool {
357 7
                    return (bool) ($filter & $method->getModifiers());
358 7
                }
359
            )
360
        );
361
    }
362
363
    /**
364
     * Get only the methods that this class implements (i.e. do not search
365
     * up parent classes etc.)
366
     *
367
     * @see ReflectionClass::getMethods for the usage of $filter
368
     *
369
     * @return ReflectionMethod[]
370
     */
371 8
    public function getImmediateMethods(?int $filter = null) : array
372
    {
373
        /** @var ReflectionMethod[] $methods */
374 8
        $methods = array_map(
375
            function (ClassMethod $methodNode) : ReflectionMethod {
376 8
                return ReflectionMethod::createFromNode(
377 8
                    $this->reflector,
378
                    $methodNode,
379 8
                    $this->declaringNamespace,
380
                    $this,
381
                    $this
382
                );
383 8
            },
384 8
            $this->node->getMethods()
385
        );
386
387 8
        $methodsByName = [];
388
389 8
        foreach ($methods as $method) {
390 8
            if ($filter !== null && ! ($filter & $method->getModifiers())) {
391 6
                continue;
392
            }
393
394 8
            $methodsByName[$method->getName()] = $method;
395
        }
396
397 8
        return $methodsByName;
398
    }
399
400
    /**
401
     * Get a single method with the name $methodName.
402
     *
403
     * @throws OutOfBoundsException
404
     */
405 7
    public function getMethod(string $methodName) : ReflectionMethod
406
    {
407 7
        $methods = $this->getMethodsIndexedByName();
408
409 7
        if (! isset($methods[$methodName])) {
410 4
            throw new OutOfBoundsException('Could not find method: ' . $methodName);
411
        }
412
413 7
        return $methods[$methodName];
414
    }
415
416
    /**
417
     * Does the class have the specified method method?
418
     */
419 6
    public function hasMethod(string $methodName) : bool
420
    {
421
        try {
422 6
            $this->getMethod($methodName);
423
424 6
            return true;
425 4
        } catch (OutOfBoundsException $exception) {
426 4
            return false;
427
        }
428
    }
429
430
    /**
431
     * Get an associative array of only the constants for this specific class (i.e. do not search
432
     * up parent classes etc.), with keys as constant names and values as constant values.
433
     *
434
     * @psalm-return array<string, scalar|array<scalar>|null>
435
     */
436 1
    public function getImmediateConstants() : array
437
    {
438
        return array_map(static function (ReflectionClassConstant $classConstant) {
439 1
            return $classConstant->getValue();
440 1
        }, $this->getImmediateReflectionConstants());
441
    }
442
443
    /**
444
     * Get an associative array of the defined constants in this class,
445
     * with keys as constant names and values as constant values.
446
     *
447
     * @psalm-return array<string, scalar|array<scalar>|null>
448
     */
449 4
    public function getConstants() : array
450
    {
451
        return array_map(static function (ReflectionClassConstant $classConstant) {
452 4
            return $classConstant->getValue();
453 4
        }, $this->getReflectionConstants());
454
    }
455
456
    /**
457
     * Get the value of the specified class constant.
458
     *
459
     * Returns null if not specified.
460
     *
461
     * @return bool|int|float|string|array|null
462
     *
463
     * @psalm-return scalar|array<scalar>|null
464
     */
465 3
    public function getConstant(string $name)
466
    {
467 3
        $reflectionConstant = $this->getReflectionConstant($name);
468
469 3
        if (! $reflectionConstant) {
470 2
            return null;
471
        }
472
473 2
        return $reflectionConstant->getValue();
474
    }
475
476
    /**
477
     * Does this class have the specified constant?
478
     */
479 2
    public function hasConstant(string $name) : bool
480
    {
481 2
        return $this->getReflectionConstant($name) !== null;
482
    }
483
484
    /**
485
     * Get the reflection object of the specified class constant.
486
     *
487
     * Returns null if not specified.
488
     */
489 25
    public function getReflectionConstant(string $name) : ?ReflectionClassConstant
490
    {
491 25
        return $this->getReflectionConstants()[$name] ?? null;
492
    }
493
494
    /**
495
     * Get an associative array of only the constants for this specific class (i.e. do not search
496
     * up parent classes etc.), with keys as constant names and values as {@see ReflectionClassConstant} objects.
497
     *
498
     * @return ReflectionClassConstant[] indexed by name
499
     *
500
     * @psalm-return array<string, ReflectionClassConstant>
501
     */
502 33
    public function getImmediateReflectionConstants() : array
503
    {
504 33
        if ($this->cachedReflectionConstants !== null) {
505 4
            return $this->cachedReflectionConstants;
506
        }
507
508 33
        $constants = array_merge(
509 33
            [],
510 33
            ...array_map(
511
                function (ConstNode $constNode) : array {
512 33
                    $constants = [];
513
514 33
                    foreach ($constNode->consts as $constantPositionInNode => $constantNode) {
515 33
                        $constants[] = ReflectionClassConstant::createFromNode($this->reflector, $constNode, $constantPositionInNode, $this);
516
                    }
517
518 33
                    return $constants;
519 33
                },
520 33
                array_filter(
521 33
                    $this->node->stmts,
522
                    static function (Node\Stmt $stmt) : bool {
523 33
                        return $stmt instanceof ConstNode;
524 33
                    }
525
                )
526
            )
527
        );
528
529 33
        return $this->cachedReflectionConstants = array_combine(
530 33
            array_map(
531
                static function (ReflectionClassConstant $constant) : string {
532 33
                    return $constant->getName();
533 33
                },
534 33
                $constants
535
            ),
536 33
            $constants
537
        );
538
    }
539
540
    /**
541
     * Get an associative array of the defined constants in this class,
542
     * with keys as constant names and values as {@see ReflectionClassConstant} objects.
543
     *
544
     * @return ReflectionClassConstant[] indexed by name
545
     *
546
     * @psalm-return array<string, ReflectionClassConstant>
547
     */
548 31
    public function getReflectionConstants() : array
549
    {
550
        // Note: constants are not merged via their name as array index, since internal PHP constant
551
        //       sorting does not follow `\array_merge()` semantics
552
        /** @var ReflectionClassConstant[] $allReflectionConstants */
553 31
        $allReflectionConstants = array_merge(
554 31
            array_values($this->getImmediateReflectionConstants()),
555 31
            ...array_map(
556
                static function (ReflectionClass $ancestor) : array {
557 2
                    return array_filter(
558 2
                        array_values($ancestor->getReflectionConstants()),
559
                        static function (ReflectionClassConstant $classConstant) : bool {
560 2
                            return ! $classConstant->isPrivate();
561 2
                        }
562
                    );
563 31
                },
564 31
                array_filter([$this->getParentClass()])
565
            ),
566 31
            ...array_map(
567
                static function (ReflectionClass $interface) : array {
568 2
                    return array_values($interface->getReflectionConstants());
569 31
                },
570 31
                array_values($this->getInterfaces())
571
            )
572
        );
573
574 31
        $reflectionConstants = [];
575
576 31
        foreach ($allReflectionConstants as $constant) {
577 31
            $constantName = $constant->getName();
578
579 31
            if (isset($reflectionConstants[$constantName])) {
580 2
                continue;
581
            }
582
583 31
            $reflectionConstants[$constantName] = $constant;
584
        }
585
586 31
        return $reflectionConstants;
587
    }
588
589
    /**
590
     * Get the constructor method for this class.
591
     *
592
     * @throws OutOfBoundsException
593
     */
594 9
    public function getConstructor() : ReflectionMethod
595
    {
596
        $constructors = array_values(array_filter($this->getMethods(), static function (ReflectionMethod $method) : bool {
597 8
            return $method->isConstructor();
598 9
        }));
599
600 9
        if (! isset($constructors[0])) {
601 4
            throw new OutOfBoundsException('Could not find method: __construct');
602
        }
603
604 7
        return $constructors[0];
605
    }
606
607
    /**
608
     * Get only the properties for this specific class (i.e. do not search
609
     * up parent classes etc.)
610
     *
611
     * @see ReflectionClass::getProperties() for the usage of filter
612
     *
613
     * @return ReflectionProperty[]
614
     *
615
     * @psalm-return array<string, ReflectionProperty>
616
     */
617 18
    public function getImmediateProperties(?int $filter = null) : array
618
    {
619 18
        if ($this->cachedImmediateProperties === null) {
620 18
            $properties = [];
621 18
            foreach ($this->node->stmts as $stmt) {
622 18
                if (! ($stmt instanceof PropertyNode)) {
623 12
                    continue;
624
                }
625
626 18
                foreach ($stmt->props as $propertyPositionInNode => $propertyNode) {
627 18
                    $prop                         = ReflectionProperty::createFromNode(
628 18
                        $this->reflector,
629
                        $stmt,
630
                        $propertyPositionInNode,
631 18
                        $this->declaringNamespace,
632
                        $this,
633
                        $this
634
                    );
635 18
                    $properties[$prop->getName()] = $prop;
636
                }
637
            }
638
639 18
            $this->cachedImmediateProperties = $properties;
640
        }
641
642 18
        if ($filter === null) {
643 18
            return $this->cachedImmediateProperties;
644
        }
645
646 5
        return array_filter(
647 5
            $this->cachedImmediateProperties,
648
            static function (ReflectionProperty $property) use ($filter) : bool {
649 5
                return (bool) ($filter & $property->getModifiers());
650 5
            }
651
        );
652
    }
653
654
    /**
655
     * Get the properties for this class.
656
     *
657
     * Filter the results to include only properties with certain attributes. Defaults
658
     * to no filtering.
659
     * Any combination of \ReflectionProperty::IS_STATIC,
660
     * \ReflectionProperty::IS_PUBLIC,
661
     * \ReflectionProperty::IS_PROTECTED,
662
     * \ReflectionProperty::IS_PRIVATE.
663
     * For example if $filter = \ReflectionProperty::IS_STATIC | \ReflectionProperty::IS_PUBLIC
664
     * only the static public properties will be returned
665
     *
666
     * @return ReflectionProperty[]
667
     *
668
     * @psalm-return array<string, ReflectionProperty>
669
     */
670 18
    public function getProperties(?int $filter = null) : array
671
    {
672 18
        if ($this->cachedProperties === null) {
673
            // merging together properties from parent class, traits, current class (in this precise order)
674 18
            $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...
675 18
                array_merge(
676 18
                    [],
677 18
                    ...array_map(
678
                        static function (ReflectionClass $ancestor) use ($filter) : array {
679 3
                            return array_filter(
680 3
                                $ancestor->getProperties($filter),
681
                                static function (ReflectionProperty $property) : bool {
682 3
                                    return ! $property->isPrivate();
683 3
                                }
684
                            );
685 18
                        },
686 18
                        array_filter([$this->getParentClass()])
687
                    ),
688 18
                    ...array_map(
689
                        function (ReflectionClass $trait) use ($filter) {
690
                            return array_map(function (ReflectionProperty $property) use ($trait) : ReflectionProperty {
691 2
                                return ReflectionProperty::createFromNode(
692 2
                                    $this->reflector,
693 2
                                    $property->getAst(),
694 2
                                    $property->getPositionInAst(),
695 2
                                    $trait->declaringNamespace,
696 2
                                    $property->getDeclaringClass(),
697
                                    $this
698
                                );
699 2
                            }, $trait->getProperties($filter));
700 18
                        },
701 18
                        $this->getTraits()
702
                    )
703
                ),
704 18
                $this->getImmediateProperties()
705
            );
706
        }
707
708 18
        if ($filter === null) {
709 13
            return $this->cachedProperties;
710
        }
711
712 5
        return array_filter(
713 5
            $this->cachedProperties,
714
            static function (ReflectionProperty $property) use ($filter) : bool {
715 5
                return (bool) ($filter & $property->getModifiers());
716 5
            }
717
        );
718
    }
719
720
    /**
721
     * Get the property called $name.
722
     *
723
     * Returns null if property does not exist.
724
     */
725 8
    public function getProperty(string $name) : ?ReflectionProperty
726
    {
727 8
        $properties = $this->getProperties();
728
729 8
        if (! isset($properties[$name])) {
730 6
            return null;
731
        }
732
733 6
        return $properties[$name];
734
    }
735
736
    /**
737
     * Does this class have the specified property?
738
     */
739 3
    public function hasProperty(string $name) : bool
740
    {
741 3
        return $this->getProperty($name) !== null;
742
    }
743
744
    /**
745
     * @psalm-return array<string, scalar|array<scalar>|null>
746
     */
747 1
    public function getDefaultProperties() : array
748
    {
749 1
        return array_map(
750
            static function (ReflectionProperty $property) {
751 1
                return $property->getDefaultValue();
752 1
            },
753
            array_filter($this->getProperties(), static function (ReflectionProperty $property) : bool {
754 1
                return $property->isDefault();
755 1
            })
756
        );
757
    }
758
759 7
    public function getFileName() : ?string
760
    {
761 7
        return $this->locatedSource->getFileName();
762
    }
763
764 28
    public function getLocatedSource() : LocatedSource
765
    {
766 28
        return $this->locatedSource;
767
    }
768
769
    /**
770
     * Get the line number that this class starts on.
771
     */
772 8
    public function getStartLine() : int
773
    {
774 8
        return $this->node->getStartLine();
775
    }
776
777
    /**
778
     * Get the line number that this class ends on.
779
     */
780 4
    public function getEndLine() : int
781
    {
782 4
        return $this->node->getEndLine();
783
    }
784
785 3
    public function getStartColumn() : int
786
    {
787 3
        return CalculateReflectionColum::getStartColumn($this->locatedSource->getSource(), $this->node);
788
    }
789
790 3
    public function getEndColumn() : int
791
    {
792 3
        return CalculateReflectionColum::getEndColumn($this->locatedSource->getSource(), $this->node);
793
    }
794
795
    /**
796
     * Get the parent class, if it is defined. If this class does not have a
797
     * specified parent class, this will throw an exception.
798
     *
799
     * @throws NotAClassReflection
800
     */
801 89
    public function getParentClass() : ?ReflectionClass
802
    {
803 89
        if (! ($this->node instanceof ClassNode) || $this->node->extends === null) {
804 86
            return null;
805
        }
806
807 19
        $parent = $this->reflector->reflect($this->node->extends->toString());
808
        // @TODO use actual `ClassReflector` or `FunctionReflector`?
809 19
        assert($parent instanceof self);
810
811 19
        if ($parent->isInterface() || $parent->isTrait()) {
812 2
            throw NotAClassReflection::fromReflectionClass($parent);
813
        }
814
815 17
        return $parent;
816
    }
817
818
    /**
819
     * Gets the parent class names.
820
     *
821
     * @return string[] A numerical array with parent class names as the values.
822
     *
823
     * @psalm-return list<class-string>
824
     */
825 1
    public function getParentClassNames() : array
826
    {
827
        return array_map(static function (self $parentClass) : string {
828 1
            return $parentClass->getName();
829 1
        }, array_slice(array_reverse($this->getInheritanceClassHierarchy()), 1));
830
    }
831
832 3
    public function getDocComment() : string
833
    {
834 3
        return GetFirstDocComment::forNode($this->node);
835
    }
836
837 72
    public function isAnonymous() : bool
838
    {
839 72
        return $this->node->name === null;
840
    }
841
842
    /**
843
     * Is this an internal class?
844
     */
845 3
    public function isInternal() : bool
846
    {
847 3
        return $this->locatedSource->isInternal();
848
    }
849
850
    /**
851
     * Is this a user-defined function (will always return the opposite of
852
     * whatever isInternal returns).
853
     */
854 3
    public function isUserDefined() : bool
855
    {
856 3
        return ! $this->isInternal();
857
    }
858
859
    /**
860
     * Is this class an abstract class.
861
     */
862 8
    public function isAbstract() : bool
863
    {
864 8
        return $this->node instanceof ClassNode && $this->node->isAbstract();
865
    }
866
867
    /**
868
     * Is this class a final class.
869
     */
870 6
    public function isFinal() : bool
871
    {
872 6
        return $this->node instanceof ClassNode && $this->node->isFinal();
873
    }
874
875
    /**
876
     * Get the core-reflection-compatible modifier values.
877
     */
878 3
    public function getModifiers() : int
879
    {
880 3
        $val  = 0;
881 3
        $val += $this->isAbstract() ? CoreReflectionClass::IS_EXPLICIT_ABSTRACT : 0;
882 3
        $val += $this->isFinal() ? CoreReflectionClass::IS_FINAL : 0;
883
884 3
        return $val;
885
    }
886
887
    /**
888
     * Is this reflection a trait?
889
     */
890 26
    public function isTrait() : bool
891
    {
892 26
        return $this->node instanceof TraitNode;
893
    }
894
895
    /**
896
     * Is this reflection an interface?
897
     */
898 32
    public function isInterface() : bool
899
    {
900 32
        return $this->node instanceof InterfaceNode;
901
    }
902
903
    /**
904
     * Get the traits used, if any are defined. If this class does not have any
905
     * defined traits, this will return an empty array.
906
     *
907
     * @return ReflectionClass[]
908
     *
909
     * @psalm-return list<ReflectionClass>
910
     */
911 45
    public function getTraits() : array
912
    {
913 45
        return array_map(
914
            function (Node\Name $importedTrait) : ReflectionClass {
915 8
                return $this->reflectClassForNamedNode($importedTrait);
916 45
            },
917 45
            array_merge(
918 45
                [],
919 45
                ...array_map(
920
                    static function (TraitUse $traitUse) : array {
921 8
                        return $traitUse->traits;
922 45
                    },
923
                    array_filter($this->node->stmts, static function (Node $node) : bool {
924 43
                        return $node instanceof TraitUse;
925 45
                    })
926
                )
927
            )
928
        );
929
    }
930
931
    /**
932
     * Given an AST Node\Name, create a new ReflectionClass for the element.
933
     */
934 23
    private function reflectClassForNamedNode(Node\Name $node) : self
935
    {
936
        // @TODO use actual `ClassReflector` or `FunctionReflector`?
937 23
        if ($this->isAnonymous()) {
938
            $class = (new BetterReflection())->classReflector()->reflect($node->toString());
939
        } else {
940 23
            $class = $this->reflector->reflect($node->toString());
941 23
            assert($class instanceof self);
942
        }
943
944 23
        return $class;
945
    }
946
947
    /**
948
     * Get the names of the traits used as an array of strings, if any are
949
     * defined. If this class does not have any defined traits, this will
950
     * return an empty array.
951
     *
952
     * @return string[]
953
     */
954 1
    public function getTraitNames() : array
955
    {
956 1
        return array_map(
957
            static function (ReflectionClass $trait) : string {
958 1
                return $trait->getName();
959 1
            },
960 1
            $this->getTraits()
961
        );
962
    }
963
964
    /**
965
     * Return a list of the aliases used when importing traits for this class.
966
     * The returned array is in key/value pair in this format:.
967
     *
968
     *   'aliasedMethodName' => 'ActualClass::actualMethod'
969
     *
970
     * @return array<string, string>
971
     *
972
     * @example
973
     * // When reflecting a class such as:
974
     * class Foo
975
     * {
976
     *     use MyTrait {
977
     *         myTraitMethod as myAliasedMethod;
978
     *     }
979
     * }
980
     * // This method would return
981
     * //   ['myAliasedMethod' => 'MyTrait::myTraitMethod']
982
     */
983 26
    public function getTraitAliases() : array
984
    {
985
        /** @var Node\Stmt\TraitUse[] $traitUsages */
986
        $traitUsages = array_filter($this->node->stmts, static function (Node $node) : bool {
987 25
            return $node instanceof TraitUse;
988 26
        });
989
990 26
        $resolvedAliases = [];
991
992 26
        foreach ($traitUsages as $traitUsage) {
993 5
            $traitNames  = $traitUsage->traits;
994 5
            $adaptations = $traitUsage->adaptations;
995
996 5
            foreach ($adaptations as $adaptation) {
997 3
                $usedTrait = $adaptation->trait;
998 3
                if ($usedTrait === null) {
999 2
                    $usedTrait = $traitNames[0];
1000
                }
1001
1002 3
                if (! ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Alias)) {
1003 1
                    continue;
1004
                }
1005
1006 3
                if (! $adaptation->newName) {
1007 2
                    continue;
1008
                }
1009
1010 3
                $resolvedAliases[$adaptation->newName->name] = sprintf(
1011 3
                    '%s::%s',
1012 3
                    $usedTrait->toString(),
1013 3
                    $adaptation->method->toString()
1014
                );
1015
            }
1016
        }
1017
1018 26
        return $resolvedAliases;
1019
    }
1020
1021
    /**
1022
     * Gets the interfaces.
1023
     *
1024
     * @link https://php.net/manual/en/reflectionclass.getinterfaces.php
1025
     *
1026
     * @return ReflectionClass[] An associative array of interfaces, with keys as interface names and the array
1027
     *                           values as {@see ReflectionClass} objects.
1028
     *
1029
     * @psalm-return array<string, ReflectionClass>
1030
     */
1031 66
    public function getInterfaces() : array
1032
    {
1033 66
        return array_merge(...array_map(
1034
            static function (self $reflectionClass) : array {
1035 66
                return $reflectionClass->getCurrentClassImplementedInterfacesIndexedByName();
1036 66
            },
1037 66
            $this->getInheritanceClassHierarchy()
1038
        ));
1039
    }
1040
1041
    /**
1042
     * Get only the interfaces that this class implements (i.e. do not search
1043
     * up parent classes etc.)
1044
     *
1045
     * @return ReflectionClass[]
1046
     *
1047
     * @psalm-return array<string, ReflectionClass>
1048
     */
1049 3
    public function getImmediateInterfaces() : array
1050
    {
1051 3
        return $this->getCurrentClassImplementedInterfacesIndexedByName();
1052
    }
1053
1054
    /**
1055
     * Gets the interface names.
1056
     *
1057
     * @link https://php.net/manual/en/reflectionclass.getinterfacenames.php
1058
     *
1059
     * @return string[] A numerical array with interface names as the values.
1060
     *
1061
     * @psalm-return list<string>
1062
     */
1063 6
    public function getInterfaceNames() : array
1064
    {
1065 6
        return array_values(array_map(
1066
            static function (self $interface) : string {
1067 5
                return $interface->getName();
1068 6
            },
1069 6
            $this->getInterfaces()
1070
        ));
1071
    }
1072
1073
    /**
1074
     * Checks whether the given object is an instance.
1075
     *
1076
     * @link https://php.net/manual/en/reflectionclass.isinstance.php
1077
     *
1078
     * @param object $object
1079
     *
1080
     * @throws NotAnObject
1081
     *
1082
     * @psalm-suppress DocblockTypeContradiction
1083
     */
1084 1
    public function isInstance($object) : bool
1085
    {
1086 1
        if (! is_object($object)) {
1087 1
            throw NotAnObject::fromNonObject($object);
1088
        }
1089
1090 1
        $className = $this->getName();
1091
1092
        // note: since $object was loaded, we can safely assume that $className is available in the current
1093
        //       php script execution context
1094 1
        return $object instanceof $className;
1095
    }
1096
1097
    /**
1098
     * Checks whether the given class string is a subclass of this class.
1099
     *
1100
     * @link https://php.net/manual/en/reflectionclass.isinstance.php
1101
     */
1102 1
    public function isSubclassOf(string $className) : bool
1103
    {
1104 1
        return in_array(
1105 1
            ltrim($className, '\\'),
1106 1
            array_map(
1107
                static function (self $reflectionClass) : string {
1108 1
                    return $reflectionClass->getName();
1109 1
                },
1110 1
                array_slice(array_reverse($this->getInheritanceClassHierarchy()), 1)
1111
            ),
1112 1
            true
1113
        );
1114
    }
1115
1116
    /**
1117
     * Checks whether this class implements the given interface.
1118
     *
1119
     * @link https://php.net/manual/en/reflectionclass.implementsinterface.php
1120
     */
1121 2
    public function implementsInterface(string $interfaceName) : bool
1122
    {
1123 2
        return in_array(ltrim($interfaceName, '\\'), $this->getInterfaceNames(), true);
1124
    }
1125
1126
    /**
1127
     * Checks whether this reflection is an instantiable class
1128
     *
1129
     * @link https://php.net/manual/en/reflectionclass.isinstantiable.php
1130
     */
1131 3
    public function isInstantiable() : bool
1132
    {
1133
        // @TODO doesn't consider internal non-instantiable classes yet.
1134
1135 3
        if ($this->isAbstract()) {
1136 3
            return false;
1137
        }
1138
1139 3
        if ($this->isInterface()) {
1140 3
            return false;
1141
        }
1142
1143 3
        if ($this->isTrait()) {
1144 2
            return false;
1145
        }
1146
1147
        try {
1148 3
            return $this->getConstructor()->isPublic();
1149 3
        } catch (OutOfBoundsException $e) {
1150 3
            return true;
1151
        }
1152
    }
1153
1154
    /**
1155
     * Checks whether this is a reflection of a class that supports the clone operator
1156
     *
1157
     * @link https://php.net/manual/en/reflectionclass.iscloneable.php
1158