Completed
Pull Request — master (#107)
by Alexander
03:29
created

ReflectionClassLikeTrait::getDefaultProperties()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7.5375

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 14
cts 18
cp 0.7778
rs 8.5226
c 0
b 0
f 0
cc 7
nc 6
nop 0
crap 7.5375
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
            $buildString($methods)
203
        );
204
205
        return $string;
206
    }
207
208
209
    /**
210
     * {@inheritDoc}
211
     */
212 10
    public function getConstant($name)
213
    {
214 10
        if ($this->hasConstant($name)) {
215 10
            return $this->constants[$name];
216
        }
217
218
        return false;
219
    }
220
221
    /**
222
     * {@inheritDoc}
223
     */
224 38
    public function getConstants()
225
    {
226 38
        if (!isset($this->constants)) {
227 38
            $this->constants = $this->recursiveCollect(
228
                function (array &$result, \ReflectionClass $instance) {
229 11
                    $result += $instance->getConstants();
230 38
                }
231
            );
232 38
            $this->collectSelfConstants();
233
        }
234
235 38
        return $this->constants;
236
    }
237
238
    /**
239
     * {@inheritDoc}
240
     */
241 19
    public function getConstructor()
242
    {
243 19
        $constructor = $this->getMethod('__construct');
244 19
        if (!$constructor) {
245 17
            return null;
246
        }
247
248 2
        return $constructor;
249
    }
250
251
    /**
252
     * Gets default properties from a class (including inherited properties).
253
     *
254
     * @link http://php.net/manual/en/reflectionclass.getdefaultproperties.php
255
     *
256
     * @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
     */
259 29
    public function getDefaultProperties()
260
    {
261 29
        $defaultValues = [];
262 29
        $properties    = $this->getProperties();
263 29
        $staticOrder   = [true, false];
264 29
        foreach ($staticOrder as $shouldBeStatic) {
265 29
            foreach ($properties as $property) {
266 7
                $isStaticProperty = $property->isStatic();
267 7
                if ($shouldBeStatic !== $isStaticProperty) {
268 7
                    continue;
269
                }
270 7
                $propertyName         = $property->getName();
271 7
                $isInternalReflection = get_class($property) === \ReflectionProperty::class;
272
273 7
                if (!$isInternalReflection || $isStaticProperty) {
274 7
                    $defaultValues[$propertyName] = $property->getValue();
275
                } elseif (!$isStaticProperty) {
276
                    // Internal reflection and dynamic property
277
                    $classProperties = $property->getDeclaringClass()
278
                                                ->getDefaultProperties()
279
                    ;
280
281
                    $defaultValues[$propertyName] = $classProperties[$propertyName];
282
                }
283
            }
284
        }
285
286 29
        return $defaultValues;
287
    }
288
289
    /**
290
     * {@inheritDoc}
291
     */
292 29
    public function getDocComment()
293
    {
294 29
        $docComment = $this->classLikeNode->getDocComment();
295
296 29
        return $docComment ? $docComment->getText() : false;
297
    }
298
299 29
    public function getEndLine()
300
    {
301 29
        return $this->classLikeNode->getAttribute('endLine');
302
    }
303
304 29
    public function getExtension()
305
    {
306 29
        return null;
307
    }
308
309 29
    public function getExtensionName()
310
    {
311 29
        return false;
312
    }
313
314 21
    public function getFileName()
315
    {
316 21
        return $this->classLikeNode->getAttribute('fileName');
317
    }
318
319
    /**
320
     * {@inheritDoc}
321
     */
322 30
    public function getInterfaceNames()
323
    {
324 30
        return array_keys($this->getInterfaces());
325
    }
326
327
    /**
328
     * {@inheritDoc}
329
     */
330 59
    public function getInterfaces()
331
    {
332 59
        if (!isset($this->interfaceClasses)) {
333 30
            $this->interfaceClasses = $this->recursiveCollect(
334
                function (array &$result, \ReflectionClass $instance) {
335 7
                    if ($instance->isInterface()) {
336 1
                        $result[$instance->name] = $instance;
337
                    }
338 7
                    $result += $instance->getInterfaces();
339 30
                }
340
            );
341
        }
342
343 59
        return $this->interfaceClasses;
344
    }
345
346
    /**
347
     * {@inheritdoc}
348
     * @param string $name
349
     */
350 2047
    public function getMethod($name)
351
    {
352 2047
        $methods = $this->getMethods();
353 2047
        foreach ($methods as $method) {
354 2038
            if ($method->getName() === $name) {
355 2030
                return $method;
356
            }
357
        }
358
359 17
        return false;
360
    }
361
362
    /**
363
     * Returns list of reflection methods
364
     *
365
     * @param null|int $filter Optional filter
366
     *
367
     * @return array|\ReflectionMethod[]
368
     */
369 2073
    public function getMethods($filter = null)
370
    {
371 2073
        if (!isset($this->methods)) {
372 56
            $directMethods = ReflectionMethod::collectFromClassNode($this->classLikeNode, $this);
373 56
            $parentMethods = $this->recursiveCollect(
374
                function (array &$result, \ReflectionClass $instance, $isParent) {
375 16
                    $reflectionMethods = [];
376 16
                    foreach ($instance->getMethods() as $reflectionMethod) {
377 11
                        if (!$isParent || !$reflectionMethod->isPrivate()) {
378 11
                            $reflectionMethods[$reflectionMethod->name] = $reflectionMethod;
379
                        }
380
                    }
381 16
                    $result += $reflectionMethods;
382 56
                }
383
            );
384 56
            $methods       = $directMethods + $parentMethods;
385
386 56
            $this->methods = $methods;
387
        }
388 2073
        if (!isset($filter)) {
389 2072
            return array_values($this->methods);
390
        }
391
392 1
        $methods = [];
393 1
        foreach ($this->methods as $method) {
394 1
            if (!($filter & $method->getModifiers())) {
395 1
                continue;
396
            }
397 1
            $methods[] = $method;
398
        }
399
400 1
        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
        if ($this->classLikeNode instanceof Class_ && $this->classLikeNode->isAbstract()) {
425
            $modifiers += \ReflectionClass::IS_EXPLICIT_ABSTRACT;
426
        }
427
428
        if ($this->isInterface()) {
429
            $abstractMethods = $this->getMethods();
430
        } else {
431
            $abstractMethods = $this->getMethods(\ReflectionMethod::IS_ABSTRACT);
432
        }
433
        if (!empty($abstractMethods)) {
434
            $modifiers += \ReflectionClass::IS_IMPLICIT_ABSTRACT;
435
        }
436
437
        return $modifiers;
438
    }
439
440
    /**
441
     * {@inheritDoc}
442
     */
443 3001
    public function getName()
444
    {
445 3001
        $namespaceName = $this->namespaceName ? $this->namespaceName . '\\' : '';
446
447 3001
        return $namespaceName . $this->getShortName();
448
    }
449
450
    /**
451
     * {@inheritDoc}
452
     */
453 50
    public function getNamespaceName()
454
    {
455 50
        return $this->namespaceName;
456
    }
457
458
    /**
459
     * {@inheritDoc}
460
     */
461 235
    public function getParentClass()
462
    {
463 235
        if (!isset($this->parentClass)) {
464 94
            static $extendsField = 'extends';
465
466 94
            $parentClass = false;
467 94
            $hasExtends  = in_array($extendsField, $this->classLikeNode->getSubNodeNames(), true);
468 94
            $extendsNode = $hasExtends ? $this->classLikeNode->$extendsField : null;
469 94
            if ($extendsNode instanceof FullyQualified) {
470 24
                $extendsName = $extendsNode->toString();
471 24
                $parentClass = $this->createReflectionForClass($extendsName);
472
            }
473 94
            $this->parentClass = $parentClass;
474
        }
475
476 235
        return $this->parentClass;
477
    }
478
479
    /**
480
     * Retrieves reflected properties.
481
     *
482
     * @param int $filter The optional filter, for filtering desired property types.
483
     *                    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
    {
489 318
        if (!isset($this->properties)) {
490 44
            $directProperties = ReflectionProperty::collectFromClassNode($this->classLikeNode, $this->getName());
491 44
            $parentProperties = $this->recursiveCollect(
492
                function (array &$result, \ReflectionClass $instance, $isParent) {
493 10
                    $reflectionProperties = [];
494 10
                    foreach ($instance->getProperties() as $reflectionProperty) {
495 4
                        if (!$isParent || !$reflectionProperty->isPrivate()) {
496 4
                            $reflectionProperties[$reflectionProperty->name] = $reflectionProperty;
497
                        }
498
                    }
499 10
                    $result += $reflectionProperties;
500 44
                }
501
            );
502 44
            $properties       = $directProperties + $parentProperties;
503
504 44
            $this->properties = $properties;
505
        }
506
507
        // Without filter we can just return the full list
508 318
        if (!isset($filter)) {
509 288
            return array_values($this->properties);
510
        }
511
512 30
        $properties = [];
513 30
        foreach ($this->properties as $property) {
514 8
            if (!($filter & $property->getModifiers())) {
515 5
                continue;
516
            }
517 6
            $properties[] = $property;
518
        }
519
520 30
        return $properties;
521
    }
522
523
    /**
524
     * {@inheritdoc}
525
     */
526 255
    public function getProperty($name)
527
    {
528 255
        $properties = $this->getProperties();
529 255
        foreach ($properties as $property) {
530 255
            if ($property->getName() === $name) {
531 255
                return $property;
532
            }
533
        }
534
535
        return false;
536
    }
537
538
    /**
539
     * @inheritDoc
540
     */
541 3
    public function getReflectionConstant($name)
542
    {
543 3
        $classConstants = $this->getReflectionConstants();
544 3
        foreach ($classConstants as $classConstant) {
545 3
            if ($classConstant->getName() === $name) {
546 3
                return $classConstant;
547
            }
548
        }
549
550 1
        return false;
551
    }
552
553
    /**
554
     * @inheritDoc
555
     */
556 8
    public function getReflectionConstants()
557
    {
558 8
        if (!isset($this->classConstants)) {
559 8
            $directClassConstants = ReflectionClassConstant::collectFromClassNode(
560 8
                $this->classLikeNode,
561 8
                $this->getName()
562
            );
563 8
            $parentClassConstants = $this->recursiveCollect(
564 8
                function (array &$result, \ReflectionClass $instance, $isParent) {
565 2
                    $reflectionClassConstants = [];
566 2
                    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 2
                        if (!$isParent || !$reflectionClassConstant->isPrivate()) {
568 2
                            $reflectionClassConstants[$reflectionClassConstant->name] = $reflectionClassConstant;
569
                        }
570
                    }
571 2
                    $result += $reflectionClassConstants;
572 8
                }
573
            );
574 8
            $classConstants       = $directClassConstants + $parentClassConstants;
575
576 8
            $this->classConstants = $classConstants;
577
        }
578
579 8
        return array_values($this->classConstants);
580
    }
581
582
    /**
583
     * {@inheritDoc}
584
     */
585 3001
    public function getShortName()
586
    {
587 3001
        return $this->className;
588
    }
589
590 29
    public function getStartLine()
591
    {
592 29
        return $this->classLikeNode->getAttribute('startLine');
593
    }
594
595
    /**
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 33
    public function getTraitAliases()
604
    {
605 33
        $aliases = [];
606 33
        $traits  = $this->getTraits();
607 33
        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
                $aliases[$adaptation->newName] = $traitName . '::' . $methodName;
618
            }
619
        }
620
621 33
        return $aliases;
622
    }
623
624
    /**
625
     * 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 29
    public function getTraitNames()
632
    {
633 29
        return array_keys($this->getTraits());
634
    }
635
636
    /**
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 230
    public function getTraits()
644
    {
645 230
        if (!isset($this->traits)) {
646 94
            $traitAdaptations       = [];
647 94
            $this->traits           = ReflectionClass::collectTraitsFromClassNode(
648 94
                $this->classLikeNode,
649 94
                $traitAdaptations
650
            );
651 94
            $this->traitAdaptations = $traitAdaptations;
652
        }
653
654 230
        return $this->traits;
655
    }
656
657
    /**
658
     * {@inheritDoc}
659
     */
660 11
    public function hasConstant($name)
661
    {
662 11
        $constants   = $this->getConstants();
663 11
        $hasConstant = isset($constants[$name]) || array_key_exists($name, $constants);
664
665 11
        return $hasConstant;
666
    }
667
668
    /**
669
     * {@inheritdoc}
670
     * @param string $name
671
     */
672 20
    public function hasMethod($name)
673
    {
674 20
        $methods = $this->getMethods();
675 20
        foreach ($methods as $method) {
676 11
            if ($method->getName() === $name) {
677 2
                return true;
678
            }
679
        }
680
681 18
        return false;
682
    }
683
684
    /**
685
     * {@inheritdoc}
686
     */
687
    public function hasProperty($name)
688
    {
689
        $properties = $this->getProperties();
690
        foreach ($properties as $property) {
691
            if ($property->getName() === $name) {
692
                return true;
693
            }
694
        }
695
696
        return false;
697
    }
698
699
    /**
700
     * {@inheritDoc}
701
     * @param string $interfaceName
702
     */
703 29
    public function implementsInterface($interfaceName)
704
    {
705 29
        $allInterfaces = $this->getInterfaces();
706
707 29
        return isset($allInterfaces[$interfaceName]);
708
    }
709
710
    /**
711
     * {@inheritDoc}
712
     */
713 29
    public function inNamespace()
714
    {
715 29
        return !empty($this->namespaceName);
716
    }
717
718
    /**
719
     * {@inheritDoc}
720
     */
721 75
    public function isAbstract()
722
    {
723 75
        if ($this->classLikeNode instanceof Class_ && $this->classLikeNode->isAbstract()) {
724 12
            return true;
725
        }
726
727 63
        if ($this->isInterface() && !empty($this->getMethods())) {
728 2
            return true;
729
        }
730
731 61
        return false;
732
    }
733
734
    /**
735
     * Currently, anonymous classes aren't supported for parsed reflection
736
     */
737
    public function isAnonymous()
738
    {
739
        return false;
740
    }
741
742
    /**
743
     * {@inheritDoc}
744
     */
745 29
    public function isCloneable()
746
    {
747 29
        if ($this->isInterface() || $this->isTrait() || $this->isAbstract()) {
748 10
            return false;
749
        }
750
751 19
        if ($this->hasMethod('__clone')) {
752 1
            return $this->getMethod('__clone')
753 1
                        ->isPublic()
754
                ;
755
        }
756
757 18
        return true;
758
    }
759
760
    /**
761
     * {@inheritDoc}
762
     */
763 29
    public function isFinal()
764
    {
765 29
        $isFinal = $this->classLikeNode instanceof Class_ && $this->classLikeNode->isFinal();
766
767 29
        return $isFinal;
768
    }
769
770
    /**
771
     * {@inheritDoc}
772
     */
773
    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
        $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
    }
783
784
    /**
785
     * {@inheritDoc}
786
     */
787 29
    public function isInstantiable()
788
    {
789 29
        if ($this->isInterface() || $this->isTrait() || $this->isAbstract()) {
790 10
            return false;
791
        }
792
793 19
        if (null === ($constructor = $this->getConstructor())) {
794 17
            return true;
795
        }
796
797 2
        return $constructor->isPublic();
798
    }
799
800
    /**
801
     * {@inheritDoc}
802
     */
803 242
    public function isInterface()
804
    {
805 242
        return ($this->classLikeNode instanceof Interface_);
806
    }
807
808
    /**
809
     * {@inheritDoc}
810
     */
811 29
    public function isInternal()
812
    {
813
        // never can be an internal method
814 29
        return false;
815
    }
816
817
    /**
818
     * {@inheritDoc}
819
     */
820 29
    public function isIterateable()
821
    {
822 29
        return $this->implementsInterface('Traversable');
823
    }
824
825
    /**
826
     * {@inheritDoc}
827
     */
828
    public function isSubclassOf($class)
829
    {
830
        if (is_object($class)) {
831
            if ($class instanceof ReflectionClass) {
832
                $className = $class->name;
833
            } else {
834
                $className = get_class($class);
835
            }
836
        } else {
837
            $className = $class;
838
        }
839
840
        if (!$this->classLikeNode instanceof Class_) {
841
            return false;
842
        }
843
844
        $extends = $this->classLikeNode->extends;
845
        if ($extends && $extends->toString() === $className) {
846
            return true;
847
        }
848
849
        $parent = $this->getParentClass();
850
851
        return false === $parent ? false : $parent->isSubclassOf($class);
852
    }
853
854
    /**
855
     * {@inheritDoc}
856
     */
857 83
    public function isTrait()
858
    {
859 83
        return ($this->classLikeNode instanceof Trait_);
860
    }
861
862
    /**
863
     * {@inheritDoc}
864
     */
865 29
    public function isUserDefined()
866
    {
867
        // always defined by user, because we parse the source code
868 29
        return true;
869
    }
870
871
    /**
872
     * Gets static properties
873
     *
874
     * @link http://php.net/manual/en/reflectionclass.getstaticproperties.php
875
     *
876
     * @return array
877
     */
878 30
    public function getStaticProperties()
879
    {
880
        // In runtime static properties can be changed in any time
881 30
        if ($this->__isInitialized()) {
882 1
            return parent::getStaticProperties();
883
        }
884
885 30
        $properties = [];
886
887 30
        $reflectionProperties = $this->getProperties(ReflectionProperty::IS_STATIC);
888 30
        foreach ($reflectionProperties as $reflectionProperty) {
889 6
            if (!$reflectionProperty instanceof ReflectionProperty && !$reflectionProperty->isPublic()) {
890 2
                $reflectionProperty->setAccessible(true);
891
            }
892 6
            $properties[$reflectionProperty->getName()] = $reflectionProperty->getValue();
893
        }
894
895 30
        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
     *
905
     * @return mixed
906
     * @throws ReflectionException If there is no such property and no default value was given
907
     */
908 1
    public function getStaticPropertyValue($name, $default = null)
909
    {
910 1
        $properties     = $this->getStaticProperties();
911 1
        $propertyExists = array_key_exists($name, $properties);
912
913 1
        if (!$propertyExists && func_num_args() === 1) {
914
            throw new ReflectionException("Static property does not exist and no default value is given");
915
        }
916
917 1
        return $propertyExists ? $properties[$name] : $default;
918
    }
919
920
921
    /**
922
     * Creates a new class instance from given arguments.
923
     *
924
     * @link http://php.net/manual/en/reflectionclass.newinstance.php
925
     *
926
     * 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
     */
953 1
    public function newInstanceArgs(array $args = [])
954
    {
955 1
        $function = __FUNCTION__;
956 1
        $this->initializeInternalReflection();
957
958 1
        return parent::$function($args);
959
    }
960
961
    /**
962
     * Creates a new class instance without invoking the constructor.
963
     *
964
     * @link http://php.net/manual/en/reflectionclass.newinstancewithoutconstructor.php
965
     *
966
     * @return object
967
     */
968 1
    public function newInstanceWithoutConstructor($args = null)
969
    {
970 1
        $function = __FUNCTION__;
971 1
        $this->initializeInternalReflection();
972
973 1
        return parent::$function($args);
974
    }
975
976
    /**
977
     * Sets static property value
978
     *
979
     * @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 1
    public function setStaticPropertyValue($name, $value)
985
    {
986 1
        $this->initializeInternalReflection();
987
988 1
        parent::setStaticPropertyValue($name, $value);
989 1
    }
990
991 172
    private function recursiveCollect(Closure $collector)
992
    {
993 172
        $result   = [];
994 172
        $isParent = true;
995
996 172
        $traits = $this->getTraits();
997 172
        foreach ($traits as $trait) {
998 8
            $collector($result, $trait, !$isParent);
999
        }
1000
1001 172
        $parentClass = $this->getParentClass();
1002 172
        if ($parentClass) {
1003 35
            $collector($result, $parentClass, $isParent);
1004
        }
1005
1006 172
        $interfaces = ReflectionClass::collectInterfacesFromClassNode($this->classLikeNode);
1007 172
        foreach ($interfaces as $interface) {
1008 7
            $collector($result, $interface, $isParent);
1009
        }
1010
1011 172
        return $result;
1012
    }
1013
1014
    /**
1015
     * Collects list of constants from the class itself
1016
     */
1017 38
    private function collectSelfConstants()
1018
    {
1019 38
        $expressionSolver = new NodeExpressionResolver($this);
1020 38
        $localConstants   = [];
1021
1022
        // constants can be only top-level nodes in the class, so we can scan them directly
1023 38
        foreach ($this->classLikeNode->stmts as $classLevelNode) {
1024 34
            if ($classLevelNode instanceof ClassConst) {
1025 17
                $nodeConstants = $classLevelNode->consts;
1026 17
                if (!empty($nodeConstants)) {
1027 17
                    foreach ($nodeConstants as $nodeConstant) {
1028 17
                        $expressionSolver->process($nodeConstant->value);
1029 17
                        $localConstants[$nodeConstant->name->toString()] = $expressionSolver->getValue();
1030
1031 17
                        $this->constants = $localConstants + $this->constants;
1032
                    }
1033
                }
1034
            }
1035
        }
1036 38
    }
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