Completed
Push — master ( 555240...fa2155 )
by Alexander
115:41 queued 90:43
created

ReflectionClassLikeTrait::isAbstract()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 4
cts 4
cp 1
rs 9.5555
c 0
b 0
f 0
cc 5
nc 3
nop 0
crap 5
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * Parser Reflection API
6
 *
7
 * @copyright Copyright 2015, Lisachenko Alexander <[email protected]>
8
 *
9
 * This source file is subject to the license that is bundled
10
 * with this source code in the file LICENSE.
11
 */
12
13
namespace Go\ParserReflection\Traits;
14
15
use Closure;
16
use Go\ParserReflection\ReflectionClass;
17
use Go\ParserReflection\ReflectionClassConstant;
18
use Go\ParserReflection\ReflectionException;
19
use Go\ParserReflection\ReflectionMethod;
20
use Go\ParserReflection\ReflectionProperty;
21
use Go\ParserReflection\ValueResolver\NodeExpressionResolver;
22
use PhpParser\Node\Name\FullyQualified;
23
use PhpParser\Node\Stmt\Class_;
24
use PhpParser\Node\Stmt\ClassConst;
25
use PhpParser\Node\Stmt\ClassLike;
26
use PhpParser\Node\Stmt\Interface_;
27
use PhpParser\Node\Stmt\Trait_;
28
use PhpParser\Node\Stmt\TraitUseAdaptation;
29
use ReflectionObject;
30
use RuntimeException;
31
32
use function func_num_args;
33
34
/**
35
 * General class-like reflection
36
 */
37
trait ReflectionClassLikeTrait
38
{
39
    use InitializationTrait;
40
41
    /**
42
     * @var ClassLike
43
     */
44
    protected $classLikeNode;
45
46
    /**
47
     * Short name of the class, without namespace
48
     *
49
     * @var string
50
     */
51
    protected $className;
52
53
    /**
54
     * List of all constants from the class
55
     *
56
     * @var array
57
     */
58
    protected $constants;
59
60
    /**
61
     * Interfaces, empty array or null if not initialized yet
62
     *
63
     * @var \ReflectionClass[]|array|null
64
     */
65
    protected $interfaceClasses;
66
67
    /**
68
     * List of traits, empty array or null if not initialized yet
69
     *
70
     * @var  \ReflectionClass[]|array|null
71
     */
72
    protected $traits;
73
74
    /**
75
     * Additional list of trait adaptations
76
     *
77
     * @var TraitUseAdaptation[]|array
78
     */
79
    protected $traitAdaptations;
80
81
    /**
82
     * @var array|ReflectionMethod[]
83
     */
84
    protected $methods;
85
86
    /**
87
     * Namespace name
88
     *
89
     * @var string
90
     */
91
    protected $namespaceName = '';
92
93
    /**
94
     * Parent class, or false if not present, null if uninitialized yet
95
     *
96
     * @var \ReflectionClass|false|null
97
     */
98
    protected $parentClass;
99
100
    /**
101
     * @var array|ReflectionProperty[]
102
     */
103
    protected $properties;
104
105
    /**
106
     * @var array|ReflectionClassConstant[]
107
     */
108
    protected $classConstants;
109
110
    /**
111
     * Returns the string representation of the ReflectionClass object.
112
     *
113
     * @return string
114
     */
115
    public function __toString()
116
    {
117
        $isObject = $this instanceof ReflectionObject;
118
119
        $staticProperties = $staticMethods = $defaultProperties = $dynamicProperties = $methods = [];
120
121
        $format = "%s [ <user> %sclass %s%s%s ] {\n";
122
        $format .= "  @@ %s %d-%d\n\n";
123
        $format .= "  - Constants [%d] {%s\n  }\n\n";
124
        $format .= "  - Static properties [%d] {%s\n  }\n\n";
125
        $format .= "  - Static methods [%d] {%s\n  }\n\n";
126
        $format .= "  - Properties [%d] {%s\n  }\n\n";
127
        $format .= ($isObject ? "  - Dynamic properties [%d] {%s\n  }\n\n" : '%s%s');
128
        $format .= "  - Methods [%d] {%s\n  }\n";
129
        $format .= "}\n";
130
131
        foreach ($this->getProperties() as $property) {
132
            if ($property->isStatic()) {
133
                $staticProperties[] = $property;
134
            } elseif ($property->isDefault()) {
135
                $defaultProperties[] = $property;
136
            } else {
137
                $dynamicProperties[] = $property;
138
            }
139
        }
140
141
        foreach ($this->getMethods() as $method) {
142
            if ($method->isStatic()) {
143
                $staticMethods[] = $method;
144
            } else {
145
                $methods[] = $method;
146
            }
147
        }
148
149
        $buildString = static function (array $items, $indentLevel = 4) {
150
            if (!count($items)) {
151
                return '';
152
            }
153
            $indent = "\n" . str_repeat(' ', $indentLevel);
154
155
            return $indent . implode($indent, explode("\n", implode("\n", $items)));
156
        };
157
158
        $buildConstants = static function (array $items, $indentLevel = 4) {
159
            $str = '';
160
            foreach ($items as $name => $value) {
161
                $str .= "\n" . str_repeat(' ', $indentLevel);
162
                $str .= sprintf(
163
                    'Constant [ %s %s ] { %s }',
164
                    gettype($value),
165
                    $name,
166
                    $value
167
                );
168
            }
169
170
            return $str;
171
        };
172
        $interfaceNames = $this->getInterfaceNames();
173
        $parentClass    = $this->getParentClass();
174
        $modifiers      = '';
175
        if ($this->isAbstract()) {
176
            $modifiers = 'abstract ';
177
        } elseif ($this->isFinal()) {
178
            $modifiers = 'final ';
179
        }
180
181
        $string = sprintf(
182
            $format,
183
            ($isObject ? 'Object of class' : 'Class'),
184
            $modifiers,
185
            $this->getName(),
186
            false !== $parentClass ? (' extends ' . $parentClass->getName()) : '',
187
            $interfaceNames ? (' implements ' . implode(', ', $interfaceNames)) : '',
188
            $this->getFileName(),
189
            $this->getStartLine(),
190
            $this->getEndLine(),
191
            count($this->getConstants()),
192
            $buildConstants($this->getConstants()),
193
            count($staticProperties),
194
            $buildString($staticProperties),
195
            count($staticMethods),
196
            $buildString($staticMethods),
197
            count($defaultProperties),
198
            $buildString($defaultProperties),
199
            $isObject ? count($dynamicProperties) : '',
200
            $isObject ? $buildString($dynamicProperties) : '',
201
            count($methods),
202 10
            $buildString($methods)
203
        );
204 10
205 10
        return $string;
206
    }
207
208
209
    /**
210
     * {@inheritDoc}
211
     */
212
    public function getConstant($name)
213
    {
214 38
        if ($this->hasConstant($name)) {
215
            return $this->constants[$name];
216 38
        }
217
218 11
        return false;
219 38
    }
220 38
221
    /**
222
     * {@inheritDoc}
223 38
     */
224
    public function getConstants()
225
    {
226
        if (!isset($this->constants)) {
227
            $this->constants = $this->recursiveCollect(
228
                function (array &$result, \ReflectionClass $instance) {
229 19
                    $result += $instance->getConstants();
230
                }
231 19
            );
232 19
            $this->collectSelfConstants();
233 17
        }
234
235
        return $this->constants;
236 2
    }
237
238
    /**
239
     * {@inheritDoc}
240
     */
241
    public function getConstructor()
242
    {
243
        $constructor = $this->getMethod('__construct');
244
        if (!$constructor) {
245
            return null;
246
        }
247 29
248
        return $constructor;
249 29
    }
250 29
251 29
    /**
252 29
     * Gets default properties from a class (including inherited properties).
253 29
     *
254 7
     * @link http://php.net/manual/en/reflectionclass.getdefaultproperties.php
255 7
     *
256 7
     * @return array An array of default properties, with the key being the name of the property and the value being
257
     * the default value of the property or NULL if the property doesn't have a default value
258 7
     */
259 7
    public function getDefaultProperties()
260
    {
261 7
        $defaultValues = [];
262 7
        $properties    = $this->getProperties();
263
        $staticOrder   = [true, false];
264
        foreach ($staticOrder as $shouldBeStatic) {
265
            foreach ($properties as $property) {
266
                $isStaticProperty = $property->isStatic();
267
                if ($shouldBeStatic !== $isStaticProperty) {
268
                    continue;
269
                }
270
                $propertyName         = $property->getName();
271 29
                $isInternalReflection = get_class($property) === \ReflectionProperty::class;
272
273
                if (!$isInternalReflection || $isStaticProperty) {
274
                    $defaultValues[$propertyName] = $property->getValue();
275
                } elseif (!$isStaticProperty) {
276
                    // Internal reflection and dynamic property
277 29
                    $classProperties = $property->getDeclaringClass()
278
                                                ->getDefaultProperties()
279 29
                    ;
280
281 29
                    $defaultValues[$propertyName] = $classProperties[$propertyName];
282
                }
283
            }
284 29
        }
285
286 29
        return $defaultValues;
287
    }
288
289 29
    /**
290
     * {@inheritDoc}
291 29
     */
292
    public function getDocComment()
293
    {
294 29
        $docComment = $this->classLikeNode->getDocComment();
295
296 29
        return $docComment ? $docComment->getText() : false;
297
    }
298
299 21
    public function getEndLine()
300
    {
301 21
        return $this->classLikeNode->getAttribute('endLine');
302
    }
303
304
    public function getExtension()
305
    {
306
        return null;
307 30
    }
308
309 30
    public function getExtensionName()
310
    {
311
        return false;
312
    }
313
314
    public function getFileName()
315 59
    {
316
        return $this->classLikeNode->getAttribute('fileName');
317 59
    }
318
319 7
    /**
320 1
     * {@inheritDoc}
321
     */
322 7
    public function getInterfaceNames()
323 30
    {
324
        return array_keys($this->getInterfaces());
325
    }
326 59
327
    /**
328
     * {@inheritDoc}
329
     */
330
    public function getInterfaces()
331
    {
332
        if (!isset($this->interfaceClasses)) {
333 2047
            $this->interfaceClasses = $this->recursiveCollect(
334
                function (array &$result, \ReflectionClass $instance) {
335 2047
                    if ($instance->isInterface()) {
336 2047
                        $result[$instance->name] = $instance;
337 2038
                    }
338 2030
                    $result += $instance->getInterfaces();
339
                }
340
            );
341
        }
342 17
343
        return $this->interfaceClasses;
344
    }
345
346
    /**
347
     * {@inheritdoc}
348
     * @param string $name
349
     */
350
    public function getMethod($name)
351
    {
352 2073
        $methods = $this->getMethods();
353
        foreach ($methods as $method) {
354 2073
            if ($method->getName() === $name) {
355 56
                return $method;
356
            }
357 16
        }
358 16
359 11
        return false;
360 11
    }
361
362
    /**
363 16
     * Returns list of reflection methods
364 56
     *
365 56
     * @param null|int $filter Optional filter
366
     *
367 56
     * @return array|\ReflectionMethod[]
368
     */
369 2073
    public function getMethods($filter = null)
370 2072
    {
371
        if (!isset($this->methods)) {
372
            $directMethods = ReflectionMethod::collectFromClassNode($this->classLikeNode, $this);
373 1
            $parentMethods = $this->recursiveCollect(
374 1
                function (array &$result, \ReflectionClass $instance, $isParent) {
375 1
                    $reflectionMethods = [];
376 1
                    foreach ($instance->getMethods() as $reflectionMethod) {
377
                        if (!$isParent || !$reflectionMethod->isPrivate()) {
378 1
                            $reflectionMethods[$reflectionMethod->name] = $reflectionMethod;
379
                        }
380
                    }
381 1
                    $result += $reflectionMethods;
382
                }
383
            );
384
            $methods       = $directMethods + $parentMethods;
385
386
            $this->methods = $methods;
387
        }
388
        if (!isset($filter)) {
389
            return array_values($this->methods);
390
        }
391
392
        $methods = [];
393
        foreach ($this->methods as $method) {
394
            if (!($filter & $method->getModifiers())) {
395
                continue;
396
            }
397
            $methods[] = $method;
398
        }
399
400
        return $methods;
401
    }
402
403
    /**
404
     * Returns a bitfield of the access modifiers for this class.
405
     *
406
     * @link http://php.net/manual/en/reflectionclass.getmodifiers.php
407
     *
408
     * NB: this method is not fully compatible with original value because of hidden internal constants
409
     *
410
     * @return int
411
     */
412
    public function getModifiers()
413
    {
414
        $modifiers = 0;
415
416
        if ($this->isFinal()) {
417
            $modifiers += \ReflectionClass::IS_FINAL;
418
        }
419
420
        if (PHP_VERSION_ID < 70000 && $this->isTrait()) {
421
            $modifiers += \ReflectionClass::IS_EXPLICIT_ABSTRACT;
422
        }
423
424 3001
        if ($this->classLikeNode instanceof Class_ && $this->classLikeNode->isAbstract()) {
425
            $modifiers += \ReflectionClass::IS_EXPLICIT_ABSTRACT;
426 3001
        }
427
428 3001
        if ($this->isInterface()) {
429
            $abstractMethods = $this->getMethods();
430
        } else {
431
            $abstractMethods = $this->getMethods(\ReflectionMethod::IS_ABSTRACT);
432
        }
433
        if (!empty($abstractMethods)) {
434 50
            $modifiers += \ReflectionClass::IS_IMPLICIT_ABSTRACT;
435
        }
436 50
437
        return $modifiers;
438
    }
439
440
    /**
441
     * {@inheritDoc}
442 235
     */
443
    public function getName()
444 235
    {
445 94
        $namespaceName = $this->namespaceName ? $this->namespaceName . '\\' : '';
446
447 94
        return $namespaceName . $this->getShortName();
448 94
    }
449 94
450 94
    /**
451 24
     * {@inheritDoc}
452 24
     */
453
    public function getNamespaceName()
454 94
    {
455
        return $this->namespaceName;
456
    }
457 235
458
    /**
459
     * {@inheritDoc}
460
     */
461
    public function getParentClass()
462
    {
463
        if (!isset($this->parentClass)) {
464
            static $extendsField = 'extends';
465
466
            $parentClass = false;
467
            $hasExtends  = in_array($extendsField, $this->classLikeNode->getSubNodeNames(), true);
468 318
            $extendsNode = $hasExtends ? $this->classLikeNode->$extendsField : null;
469
            if ($extendsNode instanceof FullyQualified) {
470 318
                $extendsName = $extendsNode->toString();
471 44
                $parentClass = $this->createReflectionForClass($extendsName);
472
            }
473 10
            $this->parentClass = $parentClass;
474 10
        }
475 4
476 4
        return $this->parentClass;
477
    }
478
479 10
    /**
480 44
     * Retrieves reflected properties.
481 44
     *
482
     * @param int $filter The optional filter, for filtering desired property types.
483 44
     *                    It's configured using the ReflectionProperty constants, and defaults to all property types.
484
     *
485
     * @return ReflectionProperty[]
486
     */
487 318
    public function getProperties($filter = null)
488 288
    {
489
        if (!isset($this->properties)) {
490
            $directProperties = ReflectionProperty::collectFromClassNode($this->classLikeNode, $this->getName());
491 30
            $parentProperties = $this->recursiveCollect(
492 30
                function (array &$result, \ReflectionClass $instance, $isParent) {
493 8
                    $reflectionProperties = [];
494 5
                    foreach ($instance->getProperties() as $reflectionProperty) {
495
                        if (!$isParent || !$reflectionProperty->isPrivate()) {
496 6
                            $reflectionProperties[$reflectionProperty->name] = $reflectionProperty;
497
                        }
498
                    }
499 30
                    $result += $reflectionProperties;
500
                }
501
            );
502
            $properties       = $directProperties + $parentProperties;
503
504
            $this->properties = $properties;
505 255
        }
506
507 255
        // Without filter we can just return the full list
508 255
        if (!isset($filter)) {
509 255
            return array_values($this->properties);
510 255
        }
511
512
        $properties = [];
513
        foreach ($this->properties as $property) {
514
            if (!($filter & $property->getModifiers())) {
515
                continue;
516
            }
517
            $properties[] = $property;
518
        }
519
520 3
        return $properties;
521
    }
522 3
523 3
    /**
524 3
     * {@inheritdoc}
525 3
     */
526
    public function getProperty($name)
527
    {
528
        $properties = $this->getProperties();
529 1
        foreach ($properties as $property) {
530
            if ($property->getName() === $name) {
531
                return $property;
532
            }
533
        }
534
535 8
        return false;
536
    }
537 8
538 8
    /**
539 8
     * @inheritDoc
540 2
     */
541 2
    public function getReflectionConstant($name)
542 2
    {
543 2
        $classConstants = $this->getReflectionConstants();
544
        foreach ($classConstants as $classConstant) {
545
            if ($classConstant->getName() === $name) {
546 2
                return $classConstant;
547 8
            }
548 8
        }
549
550 8
        return false;
551
    }
552
553 8
    /**
554
     * @inheritDoc
555
     */
556
    public function getReflectionConstants()
557
    {
558
        if (!isset($this->classConstants)) {
559 3001
            $directClassConstants = ReflectionClassConstant::collectFromClassNode(
560
                $this->classLikeNode,
561 3001
                $this->getName()
562
            );
563
            $parentClassConstants = $this->recursiveCollect(
564 29
                function (array &$result, \ReflectionClass $instance, $isParent) {
565
                    $reflectionClassConstants = [];
566 29
                    foreach ($instance->getReflectionConstants() as $reflectionClassConstant) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ReflectionClass as the method getReflectionConstants() does only exist in the following sub-classes of ReflectionClass: Go\ParserReflection\ReflectionClass. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
567
                        if (!$isParent || !$reflectionClassConstant->isPrivate()) {
568
                            $reflectionClassConstants[$reflectionClassConstant->name] = $reflectionClassConstant;
569
                        }
570
                    }
571
                    $result += $reflectionClassConstants;
572
                }
573
            );
574
            $classConstants       = $directClassConstants + $parentClassConstants;
575
576
            $this->classConstants = $classConstants;
577 33
        }
578
579 33
        return array_values($this->classConstants);
580 33
    }
581 33
582
    /**
583
     * {@inheritDoc}
584
     */
585
    public function getShortName()
586
    {
587
        return $this->className;
588
    }
589
590
    public function getStartLine()
591
    {
592
        return $this->classLikeNode->getAttribute('startLine');
593
    }
594
595 33
    /**
596
     * Returns an array of trait aliases
597
     *
598
     * @link http://php.net/manual/en/reflectionclass.gettraitaliases.php
599
     *
600
     * @return array|null an array with new method names in keys and original names (in the format
601
     *                    "TraitName::original") in values.
602
     */
603
    public function getTraitAliases()
604
    {
605 29
        $aliases = [];
606
        $traits  = $this->getTraits();
607 29
        foreach ($this->traitAdaptations as $adaptation) {
608
            if ($adaptation instanceof TraitUseAdaptation\Alias) {
609
                $methodName = $adaptation->method;
610
                $traitName  = null;
611
                foreach ($traits as $trait) {
612
                    if ($trait->hasMethod($methodName)) {
613
                        $traitName = $trait->getName();
614
                        break;
615
                    }
616
                }
617 230
                $aliases[$adaptation->newName] = $traitName . '::' . $methodName;
618
            }
619 230
        }
620 94
621 94
        return $aliases;
622 94
    }
623
624
    /**
625 230
     * Returns an array of names of traits used by this class
626
     *
627
     * @link http://php.net/manual/en/reflectionclass.gettraitnames.php
628
     *
629
     * @return array
630
     */
631 11
    public function getTraitNames()
632
    {
633 11
        return array_keys($this->getTraits());
634 11
    }
635
636 11
    /**
637
     * Returns an array of traits used by this class
638
     *
639
     * @link http://php.net/manual/en/reflectionclass.gettraits.php
640
     *
641
     * @return array|\ReflectionClass[]
642
     */
643 20
    public function getTraits()
644
    {
645 20
        if (!isset($this->traits)) {
646 20
            $traitAdaptations       = [];
647 11
            $this->traits           = ReflectionClass::collectTraitsFromClassNode(
648 2
                $this->classLikeNode,
649
                $traitAdaptations
650
            );
651
            $this->traitAdaptations = $traitAdaptations;
652 18
        }
653
654
        return $this->traits;
655
    }
656
657
    /**
658
     * {@inheritDoc}
659
     */
660
    public function hasConstant($name)
661
    {
662
        $constants   = $this->getConstants();
663
        $hasConstant = isset($constants[$name]) || array_key_exists($name, $constants);
664
665
        return $hasConstant;
666
    }
667
668
    /**
669
     * {@inheritdoc}
670
     * @param string $name
671
     */
672
    public function hasMethod($name)
673
    {
674 29
        $methods = $this->getMethods();
675
        foreach ($methods as $method) {
676 29
            if ($method->getName() === $name) {
677
                return true;
678 29
            }
679
        }
680
681
        return false;
682
    }
683
684 29
    /**
685
     * {@inheritdoc}
686 29
     */
687
    public function hasProperty($name)
688
    {
689
        $properties = $this->getProperties();
690
        foreach ($properties as $property) {
691
            if ($property->getName() === $name) {
692 75
                return true;
693
            }
694 75
        }
695 12
696 63
        return false;
697 2
    }
698 61
699 3
    /**
700
     * {@inheritDoc}
701
     * @param string $interfaceName
702 58
     */
703
    public function implementsInterface($interfaceName)
704
    {
705
        $allInterfaces = $this->getInterfaces();
706
707
        return isset($allInterfaces[$interfaceName]);
708
    }
709
710
    /**
711
     * {@inheritDoc}
712
     */
713
    public function inNamespace()
714
    {
715
        return !empty($this->namespaceName);
716 29
    }
717
718 29
    /**
719 10
     * {@inheritDoc}
720
     */
721
    public function isAbstract()
722 19
    {
723 1
        if ($this->classLikeNode instanceof Class_ && $this->classLikeNode->isAbstract()) {
724
            return true;
725
        }
726 18
727
        if ($this->isInterface() && !empty($this->getMethods())) {
728
            return true;
729
        }
730
731
        return false;
732 29
    }
733
734 29
    /**
735
     * Currently, anonymous classes aren't supported for parsed reflection
736 29
     */
737
    public function isAnonymous()
738
    {
739
        return false;
740
    }
741
742
    /**
743
     * {@inheritDoc}
744
     */
745
    public function isCloneable()
746
    {
747
        if ($this->isInterface() || $this->isTrait() || $this->isAbstract()) {
748
            return false;
749
        }
750
751
        if ($this->hasMethod('__clone')) {
752
            return $this->getMethod('__clone')
753
                        ->isPublic()
754
                ;
755 29
        }
756
757 29
        return true;
758 10
    }
759
760
    /**
761 19
     * {@inheritDoc}
762 17
     */
763
    public function isFinal()
764
    {
765 2
        $isFinal = $this->classLikeNode instanceof Class_ && $this->classLikeNode->isFinal();
766
767
        return $isFinal;
768
    }
769
770
    /**
771 242
     * {@inheritDoc}
772
     */
773 242
    public function isInstance($object)
774
    {
775
        if (!is_object($object)) {
776
            throw new RuntimeException(sprintf('Parameter must be an object, "%s" provided.', gettype($object)));
777
        }
778
779 29
        $className = $this->getName();
780
781
        return $className === get_class($object) || is_subclass_of($object, $className);
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $className can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
782 29
    }
783
784
    /**
785
     * {@inheritDoc}
786
     */
787
    public function isInstantiable()
788 29
    {
789
        if ($this->isInterface() || $this->isTrait() || $this->isAbstract()) {
790 29
            return false;
791
        }
792
793
        if (null === ($constructor = $this->getConstructor())) {
794
            return true;
795
        }
796
797
        return $constructor->isPublic();
798
    }
799
800
    /**
801
     * {@inheritDoc}
802
     */
803
    public function isInterface()
804
    {
805
        return ($this->classLikeNode instanceof Interface_);
806
    }
807
808
    /**
809
     * {@inheritDoc}
810
     */
811
    public function isInternal()
812
    {
813
        // never can be an internal method
814
        return false;
815
    }
816
817
    /**
818
     * {@inheritDoc}
819
     */
820
    public function isIterateable()
821
    {
822
        return $this->implementsInterface('Traversable');
823 106
    }
824
825 106
    /**
826
     * {@inheritDoc}
827
     */
828
    public function isSubclassOf($class)
829
    {
830
        if (is_object($class)) {
831 29
            if ($class instanceof ReflectionClass) {
832
                $className = $class->name;
833
            } else {
834 29
                $className = get_class($class);
835
            }
836
        } else {
837
            $className = $class;
838
        }
839
840
        if (!$this->classLikeNode instanceof Class_) {
841
            return false;
842
        }
843
844 30
        $extends = $this->classLikeNode->extends;
845
        if ($extends && $extends->toString() === $className) {
846
            return true;
847 30
        }
848 1
849
        $parent = $this->getParentClass();
850
851 30
        return false === $parent ? false : $parent->isSubclassOf($class);
852
    }
853 30
854 30
    /**
855 6
     * {@inheritDoc}
856 2
     */
857 2
    public function isTrait()
858
    {
859
        return ($this->classLikeNode instanceof Trait_);
860 6
    }
861
862
    /**
863 30
     * {@inheritDoc}
864
     */
865
    public function isUserDefined()
866
    {
867
        // always defined by user, because we parse the source code
868
        return true;
869
    }
870
871
    /**
872
     * Gets static properties
873
     *
874
     * @link http://php.net/manual/en/reflectionclass.getstaticproperties.php
875
     *
876 1
     * @return array
877
     */
878 1
    public function getStaticProperties()
879 1
    {
880
        // In runtime static properties can be changed in any time
881 1
        if ($this->__isInitialized()) {
882
            return parent::getStaticProperties();
883
        }
884
885 1
        $properties = [];
886
887
        $reflectionProperties = $this->getProperties(ReflectionProperty::IS_STATIC);
888
        foreach ($reflectionProperties as $reflectionProperty) {
889
            if (!$reflectionProperty instanceof ReflectionProperty && !$reflectionProperty->isPublic()) {
890
                $reflectionProperty->setAccessible(true);
891
            }
892
            $properties[$reflectionProperty->getName()] = $reflectionProperty->getValue();
893
        }
894
895
        return $properties;
896
    }
897
898
    /**
899
     * Gets static property value
900
     *
901
     * @param string $name    The name of the static property for which to return a value.
902
     * @param mixed  $default A default value to return in case the class does not declare
903
     *                        a static property with the given name
904 1
     *
905
     * @return mixed
906 1
     * @throws ReflectionException If there is no such property and no default value was given
907 1
     */
908
    public function getStaticPropertyValue($name, $default = null)
909 1
    {
910
        $properties     = $this->getStaticProperties();
911
        $propertyExists = array_key_exists($name, $properties);
912
913
        if (!$propertyExists && func_num_args() === 1) {
914
            throw new ReflectionException("Static property does not exist and no default value is given");
915
        }
916
917
        return $propertyExists ? $properties[$name] : $default;
918
    }
919
920
921 1
    /**
922
     * Creates a new class instance from given arguments.
923 1
     *
924 1
     * @link http://php.net/manual/en/reflectionclass.newinstance.php
925
     *
926 1
     * Signature was hacked to support both 5.6, 7.1.x and 7.2.0 versions
927
     * @see  https://3v4l.org/hW9O9
928
     * @see  https://3v4l.org/sWT3j
929
     * @see  https://3v4l.org/eeVf8
930
     *
931
     * @param mixed $arg  First argument
932
     * @param mixed $args Accepts a variable number of arguments which are passed to the class constructor
933
     *
934
     * @return object
935
     */
936 1
    public function newInstance($arg = null, ...$args)
937
    {
938 1
        $args = array_slice(array_merge([$arg], $args), 0, func_num_args());
939 1
        $this->initializeInternalReflection();
940
941 1
        return parent::newInstance(...$args);
942
    }
943
944
    /**
945
     * Creates a new class instance from given arguments.
946
     *
947
     * @link http://php.net/manual/en/reflectionclass.newinstanceargs.php
948
     *
949
     * @param array $args The parameters to be passed to the class constructor as an array.
950
     *
951
     * @return object
952 1
     */
953
    public function newInstanceArgs(array $args = [])
954 1
    {
955
        $function = __FUNCTION__;
956 1
        $this->initializeInternalReflection();
957 1
958
        return parent::$function($args);
959 172
    }
960
961 172
    /**
962 172
     * Creates a new class instance without invoking the constructor.
963
     *
964 172
     * @link http://php.net/manual/en/reflectionclass.newinstancewithoutconstructor.php
965 172
     *
966 8
     * @return object
967
     */
968
    public function newInstanceWithoutConstructor($args = null)
969 172
    {
970 172
        $function = __FUNCTION__;
971 35
        $this->initializeInternalReflection();
972
973
        return parent::$function($args);
974 172
    }
975 172
976 7
    /**
977
     * Sets static property value
978
     *
979 172
     * @link http://php.net/manual/en/reflectionclass.setstaticpropertyvalue.php
980
     *
981
     * @param string $name  Property name
982
     * @param mixed  $value New property value
983
     */
984
    public function setStaticPropertyValue($name, $value)
985 38
    {
986
        $this->initializeInternalReflection();
987 38
988 38
        parent::setStaticPropertyValue($name, $value);
989
    }
990
991 38
    private function recursiveCollect(Closure $collector)
992 34
    {
993 17
        $result   = [];
994 17
        $isParent = true;
995 17
996 17
        $traits = $this->getTraits();
997 17
        foreach ($traits as $trait) {
998 17
            $collector($result, $trait, !$isParent);
999
        }
1000
1001
        $parentClass = $this->getParentClass();
1002
        if ($parentClass) {
1003 38
            $collector($result, $parentClass, $isParent);
1004
        }
1005
1006
        $interfaces = ReflectionClass::collectInterfacesFromClassNode($this->classLikeNode);
1007
        foreach ($interfaces as $interface) {
1008
            $collector($result, $interface, $isParent);
1009
        }
1010
1011
        return $result;
1012
    }
1013
1014
    /**
1015
     * Collects list of constants from the class itself
1016
     */
1017
    private function collectSelfConstants()
1018
    {
1019
        $expressionSolver = new NodeExpressionResolver($this);
1020
        $localConstants   = [];
1021
1022
        // constants can be only top-level nodes in the class, so we can scan them directly
1023
        foreach ($this->classLikeNode->stmts as $classLevelNode) {
1024
            if ($classLevelNode instanceof ClassConst) {
1025
                $nodeConstants = $classLevelNode->consts;
1026
                if (!empty($nodeConstants)) {
1027
                    foreach ($nodeConstants as $nodeConstant) {
1028
                        $expressionSolver->process($nodeConstant->value);
1029
                        $localConstants[$nodeConstant->name->toString()] = $expressionSolver->getValue();
1030
1031
                        $this->constants = $localConstants + $this->constants;
1032
                    }
1033
                }
1034
            }
1035
        }
1036
    }
1037
1038
    /**
1039
     * Create a ReflectionClass for a given class name.
1040
     *
1041
     * @param string $className
1042
     *     The name of the class to create a reflection for.
1043
     *
1044
     * @return ReflectionClass
1045
     *     The appropriate reflection object.
1046
     */
1047
    abstract protected function createReflectionForClass(string $className);
1048
}
1049