Completed
Push — master ( 793727...6af734 )
by Alexander
02:36
created

src/Traits/ReflectionClassLikeTrait.php (1 issue)

Labels
Severity

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
 * Parser Reflection API
4
 *
5
 * @copyright Copyright 2015, Lisachenko Alexander <[email protected]>
6
 *
7
 * This source file is subject to the license that is bundled
8
 * with this source code in the file LICENSE.
9
 */
10
11
namespace Go\ParserReflection\Traits;
12
13
use Go\ParserReflection\ReflectionClass;
14
use Go\ParserReflection\ReflectionClassConstant;
15
use Go\ParserReflection\ReflectionException;
16
use Go\ParserReflection\ReflectionMethod;
17
use Go\ParserReflection\ReflectionProperty;
18
use Go\ParserReflection\ValueResolver\NodeExpressionResolver;
19
use PhpParser\Node\Name\FullyQualified;
20
use PhpParser\Node\Stmt\Class_;
21
use PhpParser\Node\Stmt\ClassConst;
22
use PhpParser\Node\Stmt\ClassLike;
23
use PhpParser\Node\Stmt\Interface_;
24
use PhpParser\Node\Stmt\Trait_;
25
use PhpParser\Node\Stmt\TraitUseAdaptation;
26
27
/**
28
 * General class-like reflection
29
 */
30
trait ReflectionClassLikeTrait
31
{
32
    use InitializationTrait;
33
34
    /**
35
     * @var ClassLike
36
     */
37
    protected $classLikeNode;
38
39
    /**
40
     * Short name of the class, without namespace
41
     *
42
     * @var string
43
     */
44
    protected $className;
45
46
    /**
47
     * List of all constants from the class
48
     *
49
     * @var array
50
     */
51
    protected $constants;
52
53
    /**
54
     * Interfaces, empty array or null if not initialized yet
55
     *
56
     * @var \ReflectionClass[]|array|null
57
     */
58
    protected $interfaceClasses;
59
60
    /**
61
     * List of traits, empty array or null if not initialized yet
62
     *
63
     * @var  \ReflectionClass[]|array|null
64
     */
65
    protected $traits;
66
67
    /**
68
     * Additional list of trait adaptations
69
     *
70
     * @var TraitUseAdaptation[]|array
71
     */
72
    protected $traitAdaptations;
73
74
    /**
75
     * @var array|ReflectionMethod[]
76
     */
77
    protected $methods;
78
79
    /**
80
     * Namespace name
81
     *
82
     * @var string
83
     */
84
    protected $namespaceName = '';
85
86
    /**
87
     * Parent class, or false if not present, null if uninitialized yet
88
     *
89
     * @var \ReflectionClass|false|null
90
     */
91
    protected $parentClass;
92
93
    /**
94
     * @var array|ReflectionProperty[]
95
     */
96
    protected $properties;
97
98
    /**
99
     * @var array|ReflectionClassConstant[]
100
     */
101
    protected $classConstants;
102
103
    /**
104
     * Returns the string representation of the ReflectionClass object.
105
     *
106
     * @return string
107
     */
108
    public function __toString()
109
    {
110
        $isObject = $this instanceof \ReflectionObject;
111
112
        $staticProperties = $staticMethods = $defaultProperties = $dynamicProperties = $methods = [];
113
114
        $format  = "%s [ <user> %sclass %s%s%s ] {\n";
115
        $format .= "  @@ %s %d-%d\n\n";
116
        $format .= "  - Constants [%d] {%s\n  }\n\n";
117
        $format .= "  - Static properties [%d] {%s\n  }\n\n";
118
        $format .= "  - Static methods [%d] {%s\n  }\n\n";
119
        $format .= "  - Properties [%d] {%s\n  }\n\n";
120
        $format .= ($isObject ? "  - Dynamic properties [%d] {%s\n  }\n\n" : '%s%s');
121
        $format .= "  - Methods [%d] {%s\n  }\n";
122
        $format .= "}\n";
123
124
        foreach ($this->getProperties() as $property) {
125
            if ($property->isStatic()) {
126
                $staticProperties[] = $property;
127
            } elseif ($property->isDefault()) {
128
                $defaultProperties[] = $property;
129
            } else {
130
                $dynamicProperties[] = $property;
131
            }
132
        }
133
134
        foreach ($this->getMethods() as $method) {
135
            if ($method->isStatic()) {
136
                $staticMethods[] = $method;
137
            } else {
138
                $methods[] = $method;
139
            }
140
        }
141
142
        $buildString = function (array $items, $indentLevel = 4) {
143
            if (!count($items)) {
144
                return '';
145
            }
146
            $indent = "\n" . str_repeat(' ', $indentLevel);
147
            return $indent . implode($indent, explode("\n", implode("\n", $items)));
148
        };
149
        $buildConstants = function (array $items, $indentLevel = 4) {
150
            $str = '';
151
            foreach ($items as $name => $value) {
152
                $str .= "\n" . str_repeat(' ', $indentLevel);
153
                $str .= sprintf(
154
                    'Constant [ %s %s ] { %s }',
155
                    gettype($value),
156
                    $name,
157
                    $value
158
                );
159
            }
160
            return $str;
161
        };
162
        $interfaceNames = $this->getInterfaceNames();
163
        $parentClass    = $this->getParentClass();
164
        $modifiers      = '';
165
        if ($this->isAbstract()) {
166
            $modifiers = 'abstract ';
167
        } elseif ($this->isFinal()) {
168
            $modifiers = 'final ';
169
        };
170
171
        $string = sprintf(
172
            $format,
173
            ($isObject ? 'Object of class' : 'Class'),
174
            $modifiers,
175
            $this->getName(),
176
            false !== $parentClass ? (' extends ' . $parentClass->getName()) : '',
177
            $interfaceNames ? (' implements ' . implode(', ', $interfaceNames)) : '',
178
            $this->getFileName(),
179
            $this->getStartLine(),
180
            $this->getEndLine(),
181
            count($this->getConstants()),
182
            $buildConstants($this->getConstants()),
183
            count($staticProperties),
184
            $buildString($staticProperties),
185
            count($staticMethods),
186
            $buildString($staticMethods),
187
            count($defaultProperties),
188
            $buildString($defaultProperties),
189
            $isObject ? count($dynamicProperties) : '',
190
            $isObject ? $buildString($dynamicProperties) : '',
191
            count($methods),
192
            $buildString($methods)
193
        );
194
195
        return $string;
196
    }
197
198
199
    /**
200
     * {@inheritDoc}
201
     */
202 10
    public function getConstant($name)
203
    {
204 10
        if ($this->hasConstant($name)) {
205 10
            return $this->constants[$name];
206
        }
207
208
        return false;
209
    }
210
211
    /**
212
     * {@inheritDoc}
213
     */
214 38
    public function getConstants()
215
    {
216 38
        if (!isset($this->constants)) {
217
            $this->constants = $this->recursiveCollect(function (array &$result, \ReflectionClass $instance) {
218 11
                $result += $instance->getConstants();
219 38
            });
220 38
            $this->collectSelfConstants();
221
        }
222
223 38
        return $this->constants;
224
    }
225
226
    /**
227
     * {@inheritDoc}
228
     */
229 19
    public function getConstructor()
230
    {
231 19
        $constructor = $this->getMethod('__construct');
232 19
        if (!$constructor) {
233 17
            return null;
234
        }
235
236 2
        return $constructor;
237
    }
238
239
    /**
240
     * Gets default properties from a class (including inherited properties).
241
     *
242
     * @link http://php.net/manual/en/reflectionclass.getdefaultproperties.php
243
     *
244
     * @return array An array of default properties, with the key being the name of the property and the value being
245
     * the default value of the property or NULL if the property doesn't have a default value
246
     */
247 29
    public function getDefaultProperties()
248
    {
249 29
        $defaultValues = [];
250 29
        $properties    = $this->getProperties();
251 29
        $staticOrder   = [true, false];
252 29
        foreach ($staticOrder as $shouldBeStatic) {
253 29
            foreach ($properties as $property) {
254 7
                $isStaticProperty     = $property->isStatic();
255 7
                if ($shouldBeStatic !== $isStaticProperty) {
256 7
                    continue;
257
                }
258 7
                $propertyName         = $property->getName();
259 7
                $isInternalReflection = get_class($property) == \ReflectionProperty::class;
260
261 7
                if (!$isInternalReflection || $isStaticProperty) {
262 7
                    $defaultValues[$propertyName] = $property->getValue();
263
                } elseif (!$isStaticProperty) {
264
                    // Internal reflection and dynamic property
265
                    $classProperties = $property->getDeclaringClass()->getDefaultProperties();
266 29
                    $defaultValues[$propertyName] = $classProperties[$propertyName];
267
                }
268
            }
269
        }
270
271 29
        return $defaultValues;
272
    }
273
274
    /**
275
     * {@inheritDoc}
276
     */
277 29
    public function getDocComment()
278
    {
279 29
        $docComment = $this->classLikeNode->getDocComment();
280
281 29
        return $docComment ? $docComment->getText() : false;
282
    }
283
284 29
    public function getEndLine()
285
    {
286 29
        return $this->classLikeNode->getAttribute('endLine');
287
    }
288
289 29
    public function getExtension()
290
    {
291 29
        return null;
292
    }
293
294 29
    public function getExtensionName()
295
    {
296 29
        return false;
297
    }
298
299 21
    public function getFileName()
300
    {
301 21
        return $this->classLikeNode->getAttribute('fileName');
302
    }
303
304
    /**
305
     * {@inheritDoc}
306
     */
307 30
    public function getInterfaceNames()
308
    {
309 30
        return array_keys($this->getInterfaces());
310
    }
311
312
    /**
313
     * {@inheritDoc}
314
     */
315 59
    public function getInterfaces()
316
    {
317 59
        if (!isset($this->interfaceClasses)) {
318
            $this->interfaceClasses = $this->recursiveCollect(function (array &$result, \ReflectionClass $instance) {
319 7
                if ($instance->isInterface()) {
320 1
                    $result[$instance->name] = $instance;
321
                }
322 7
                $result += $instance->getInterfaces();
323 30
            });
324
        }
325
326 59
        return $this->interfaceClasses;
327
    }
328
329
    /**
330
     * {@inheritdoc}
331
     * @param string $name
332
     */
333 2047
    public function getMethod($name)
334
    {
335 2047
        $methods = $this->getMethods();
336 2047
        foreach ($methods as $method) {
337 2038
            if ($method->getName() == $name) {
338 2038
                return $method;
339
            }
340
        }
341
342 17
        return false;
343
    }
344
345
    /**
346
     * Returns list of reflection methods
347
     *
348
     * @param null|integer $filter Optional filter
349
     *
350
     * @return array|\ReflectionMethod[]
351
     */
352 2077
    public function getMethods($filter = null)
353
    {
354 2077
        if (!isset($this->methods)) {
355 60
            $directMethods = ReflectionMethod::collectFromClassNode($this->classLikeNode, $this);
356
            $parentMethods = $this->recursiveCollect(function (array &$result, \ReflectionClass $instance, $isParent) {
357 18
                $reflectionMethods = [];
358 18
                foreach ($instance->getMethods() as $reflectionMethod) {
359 12
                    if (!$isParent || !$reflectionMethod->isPrivate()) {
360 12
                        $reflectionMethods[$reflectionMethod->name] = $reflectionMethod;
361
                    }
362
                }
363 18
                $result += $reflectionMethods;
364 60
            });
365 60
            $methods = $directMethods + $parentMethods;
366
367 60
            $this->methods = $methods;
368
        }
369 2077
        if (!isset($filter)) {
370 2073
            return array_values($this->methods);
371
        }
372
373 5
        $methods = [];
374 5
        foreach ($this->methods as $method) {
375 4
            if (!($filter & $method->getModifiers())) {
376 4
                continue;
377
            }
378 2
            $methods[] = $method;
379
        }
380
381 5
        return $methods;
382
    }
383
384
    /**
385
     * Returns a bitfield of the access modifiers for this class.
386
     *
387
     * @link http://php.net/manual/en/reflectionclass.getmodifiers.php
388
     *
389
     * NB: this method is not fully compatible with original value because of hidden internal constants
390
     *
391
     * @return int
392
     */
393 4
    public function getModifiers()
394
    {
395 4
        $modifiers = 0;
396
397 4
        if ($this->isFinal()) {
398 1
            $modifiers += \ReflectionClass::IS_FINAL;
399
        }
400
401 4
        if (PHP_VERSION_ID < 70000 && $this->isTrait()) {
402
            $modifiers += \ReflectionClass::IS_EXPLICIT_ABSTRACT;
403
        }
404
405 4
        if ($this->classLikeNode instanceof Class_ && $this->classLikeNode->isAbstract()) {
406 1
            $modifiers += \ReflectionClass::IS_EXPLICIT_ABSTRACT;
407
        }
408
409 4
        if ($this->isInterface()) {
410 1
            $abstractMethods = $this->getMethods();
411
        } else {
412 4
            $abstractMethods = $this->getMethods(\ReflectionMethod::IS_ABSTRACT);
413
        }
414 4
        if (!empty($abstractMethods)) {
415 1
            $modifiers += \ReflectionClass::IS_IMPLICIT_ABSTRACT;
416
        }
417
418 4
        return $modifiers;
419
    }
420
421
    /**
422
     * {@inheritDoc}
423
     */
424 3004
    public function getName()
425
    {
426 3004
        $namespaceName = $this->namespaceName ? $this->namespaceName . '\\' : '';
427
428 3004
        return $namespaceName . $this->getShortName();
429
    }
430
431
    /**
432
     * {@inheritDoc}
433
     */
434 50
    public function getNamespaceName()
435
    {
436 50
        return $this->namespaceName;
437
    }
438
439
    /**
440
     * {@inheritDoc}
441
     */
442 238
    public function getParentClass()
443
    {
444 238
        if (!isset($this->parentClass)) {
445 97
            static $extendsField = 'extends';
446
447 97
            $parentClass = false;
448 97
            $hasExtends  = in_array($extendsField, $this->classLikeNode->getSubNodeNames());
449 97
            $extendsNode = $hasExtends ? $this->classLikeNode->$extendsField : null;
450 97
            if ($extendsNode instanceof FullyQualified) {
451 26
                $extendsName = $extendsNode->toString();
452 26
                $parentClass = $this->createReflectionForClass($extendsName);
453
            }
454 97
            $this->parentClass = $parentClass;
455
        }
456
457 238
        return $this->parentClass;
458
    }
459
460
    /**
461
     * Retrieves reflected properties.
462
     *
463
     * @param int $filter The optional filter, for filtering desired property types.
464
     *                    It's configured using the ReflectionProperty constants, and defaults to all property types.
465
     *
466
     * @return array|\Go\ParserReflection\ReflectionProperty[]
467
     */
468 318
    public function getProperties($filter = null)
469
    {
470 318
        if (!isset($this->properties)) {
471 44
            $directProperties = ReflectionProperty::collectFromClassNode($this->classLikeNode, $this->getName());
472
            $parentProperties = $this->recursiveCollect(function (array &$result, \ReflectionClass $instance, $isParent) {
473 10
                $reflectionProperties = [];
474 10
                foreach ($instance->getProperties() as $reflectionProperty) {
475 4
                    if (!$isParent || !$reflectionProperty->isPrivate()) {
476 4
                        $reflectionProperties[$reflectionProperty->name] = $reflectionProperty;
477
                    }
478
                }
479 10
                $result += $reflectionProperties;
480 44
            });
481 44
            $properties = $directProperties + $parentProperties;
482
483 44
            $this->properties = $properties;
484
        }
485
486
        // Without filter we can just return the full list
487 318
        if (!isset($filter)) {
488 288
            return array_values($this->properties);
489
        }
490
491 30
        $properties = [];
492 30
        foreach ($this->properties as $property) {
493 8
            if (!($filter & $property->getModifiers())) {
494 5
                continue;
495
            }
496 6
            $properties[] = $property;
497
        }
498
499 30
        return $properties;
500
    }
501
502
    /**
503
     * {@inheritdoc}
504
     */
505 255
    public function getProperty($name)
506
    {
507 255
        $properties = $this->getProperties();
508 255
        foreach ($properties as $property) {
509 255
            if ($property->getName() == $name) {
510 255
                return $property;
511
            }
512
        }
513
514
        return false;
515
    }
516
517
    /**
518
     * @inheritDoc
519
     */
520 2
    public function getReflectionConstant($name)
521
    {
522 2
        $classConstants = $this->getReflectionConstants();
523 2
        foreach ($classConstants as $classConstant) {
524 2
            if ($classConstant->getName() == $name) {
525 2
                return $classConstant;
526
            }
527
        }
528
529 1
        return false;
530
    }
531
532
    /**
533
     * @inheritDoc
534
     */
535 7
    public function getReflectionConstants()
536
    {
537 7
        if (!isset($this->classConstants)) {
538 7
            $directClassConstants = ReflectionClassConstant::collectFromClassNode($this->classLikeNode, $this->getName());
539 7
            $parentClassConstants = $this->recursiveCollect(function (array &$result, \ReflectionClass $instance, $isParent) {
540 2
                $reflectionClassConstants = [];
541 2
                foreach ($instance->getReflectionConstants() as $reflectionClassConstant) {
0 ignored issues
show
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...
542 2
                    if (!$isParent || !$reflectionClassConstant->isPrivate()) {
543 2
                        $reflectionClassConstants[$reflectionClassConstant->name] = $reflectionClassConstant;
544
                    }
545
                }
546 2
                $result += $reflectionClassConstants;
547 7
            });
548 7
            $classConstants = $directClassConstants + $parentClassConstants;
549
550 7
            $this->classConstants = $classConstants;
551
        }
552
553 7
        return array_values($this->classConstants);
554
    }
555
556
    /**
557
     * {@inheritDoc}
558
     */
559 3004
    public function getShortName()
560
    {
561 3004
        return $this->className;
562
    }
563
564 29
    public function getStartLine()
565
    {
566 29
        return $this->classLikeNode->getAttribute('startLine');
567
    }
568
569
    /**
570
     * Returns an array of trait aliases
571
     *
572
     * @link http://php.net/manual/en/reflectionclass.gettraitaliases.php
573
     *
574
     * @return array|null an array with new method names in keys and original names (in the format "TraitName::original") in
575
     * values.
576
     */
577 33
    public function getTraitAliases()
578
    {
579 33
        $aliases = [];
580 33
        $traits  = $this->getTraits();
581 33
        foreach ($this->traitAdaptations as $adaptation) {
582
            if ($adaptation instanceof TraitUseAdaptation\Alias) {
583
                $methodName = $adaptation->method;
584
                $traitName  = null;
585
                foreach ($traits as $trait) {
586
                    if ($trait->hasMethod($methodName)) {
587
                        $traitName = $trait->getName();
588
                        break;
589
                    }
590
                }
591
                $aliases[$adaptation->newName] = $traitName . '::'. $methodName;
592
            }
593
        }
594
595 33
        return $aliases;
596
    }
597
598
    /**
599
     * Returns an array of names of traits used by this class
600
     *
601
     * @link http://php.net/manual/en/reflectionclass.gettraitnames.php
602
     *
603
     * @return array
604
     */
605 29
    public function getTraitNames()
606
    {
607 29
        return array_keys($this->getTraits());
608
    }
609
610
    /**
611
     * Returns an array of traits used by this class
612
     *
613
     * @link http://php.net/manual/en/reflectionclass.gettraits.php
614
     *
615
     * @return array|\ReflectionClass[]
616
     */
617 233
    public function getTraits()
618
    {
619 233
        if (!isset($this->traits)) {
620 97
            $traitAdaptations = [];
621 97
            $this->traits     = ReflectionClass::collectTraitsFromClassNode($this->classLikeNode, $traitAdaptations);
622 97
            $this->traitAdaptations = $traitAdaptations;
623
        }
624
625 233
        return $this->traits;
626
    }
627
628
    /**
629
     * {@inheritDoc}
630
     */
631 11
    public function hasConstant($name)
632
    {
633 11
        $constants   = $this->getConstants();
634 11
        $hasConstant = isset($constants[$name]) || array_key_exists($name, $constants);
635
636 11
        return $hasConstant;
637
    }
638
639
    /**
640
     * {@inheritdoc}
641
     * @param string $name
642
     */
643 20
    public function hasMethod($name)
644
    {
645 20
        $methods = $this->getMethods();
646 20
        foreach ($methods as $method) {
647 11
            if ($method->getName() == $name) {
648 11
                return true;
649
            }
650
        }
651
652 18
        return false;
653
    }
654
655
    /**
656
     * {@inheritdoc}
657
     */
658
    public function hasProperty($name)
659
    {
660
        $properties = $this->getProperties();
661
        foreach ($properties as $property) {
662
            if ($property->getName() == $name) {
663
                return true;
664
            }
665
        }
666
667
        return false;
668
    }
669
670
    /**
671
     * {@inheritDoc}
672
     * @param string $interfaceName
673
     */
674 29
    public function implementsInterface($interfaceName)
675
    {
676 29
        $allInterfaces = $this->getInterfaces();
677
678 29
        return isset($allInterfaces[$interfaceName]);
679
    }
680
681
    /**
682
     * {@inheritDoc}
683
     */
684 29
    public function inNamespace()
685
    {
686 29
        return !empty($this->namespaceName);
687
    }
688
689
    /**
690
     * {@inheritDoc}
691
     */
692 75
    public function isAbstract()
693
    {
694 75
        if ($this->classLikeNode instanceof Class_ && $this->classLikeNode->isAbstract()) {
695 12
            return true;
696 63
        } elseif ($this->isInterface() && !empty($this->getMethods())) {
697 2
            return true;
698 61
        } elseif ($this->isTrait()) {
699 3
            return PHP_VERSION_ID < 70000 ? true : false;
700
        }
701
702 58
        return false;
703
    }
704
705
    /**
706
     * Currently, anonymous classes aren't supported for parsed reflection
707
     */
708
    public function isAnonymous()
709
    {
710
        return false;
711
    }
712
713
    /**
714
     * {@inheritDoc}
715
     */
716 29
    public function isCloneable()
717
    {
718 29
        if ($this->isInterface() || $this->isTrait() || $this->isAbstract()) {
719 10
            return false;
720
        }
721
722 19
        if ($this->hasMethod('__clone')) {
723 1
            return $this->getMethod('__clone')->isPublic();
724
        }
725
726 18
        return true;
727
    }
728
729
    /**
730
     * {@inheritDoc}
731
     */
732 33
    public function isFinal()
733
    {
734 33
        $isFinal = $this->classLikeNode instanceof Class_ && $this->classLikeNode->isFinal();
735
736 33
        return $isFinal;
737
    }
738
739
    /**
740
     * {@inheritDoc}
741
     */
742
    public function isInstance($object)
743
    {
744
        if (!is_object($object)) {
745
            throw new \RuntimeException(sprintf('Parameter must be an object, "%s" provided.', gettype($object)));
746
        }
747
748
        $className = $this->getName();
749
        return $className === get_class($object) || is_subclass_of($object, $className);
750
    }
751
752
    /**
753
     * {@inheritDoc}
754
     */
755 29
    public function isInstantiable()
756
    {
757 29
        if ($this->isInterface() || $this->isTrait() || $this->isAbstract()) {
758 10
            return false;
759
        }
760
761 19
        if (null === ($constructor = $this->getConstructor())) {
762 17
            return true;
763
        }
764
765 2
        return $constructor->isPublic();
766
    }
767
768
    /**
769
     * {@inheritDoc}
770
     */
771 246
    public function isInterface()
772
    {
773 246
        return ($this->classLikeNode instanceof Interface_);
774
    }
775
776
    /**
777
     * {@inheritDoc}
778
     */
779 29
    public function isInternal()
780
    {
781
        // never can be an internal method
782 29
        return false;
783
    }
784
785
    /**
786
     * {@inheritDoc}
787
     */
788 29
    public function isIterateable()
789
    {
790 29
        return $this->implementsInterface('Traversable');
791
    }
792
793
    /**
794
     * {@inheritDoc}
795
     */
796
    public function isSubclassOf($class)
797
    {
798
        if (is_object($class)) {
799
            if ($class instanceof ReflectionClass) {
800
                $class = $class->name;
801
            } else {
802
                $class = get_class($class);
803
            }
804
        }
805
806
        if (!$this->classLikeNode instanceof Class_) {
807
            return false;
808
        } else {
809
            $extends = $this->classLikeNode->extends;
810
            if ($extends && $extends->toString() == $class) {
811
                return true;
812
            }
813
        }
814
815
        $parent = $this->getParentClass();
816
817
        return false === $parent ? false : $parent->isSubclassOf($class);
818
    }
819
820
    /**
821
     * {@inheritDoc}
822
     */
823 106
    public function isTrait()
824
    {
825 106
        return ($this->classLikeNode instanceof Trait_);
826
    }
827
828
    /**
829
     * {@inheritDoc}
830
     */
831 29
    public function isUserDefined()
832
    {
833
        // always defined by user, because we parse the source code
834 29
        return true;
835
    }
836
837
    /**
838
     * Gets static properties
839
     *
840
     * @link http://php.net/manual/en/reflectionclass.getstaticproperties.php
841
     *
842
     * @return array
843
     */
844 30
    public function getStaticProperties()
845
    {
846
        // In runtime static properties can be changed in any time
847 30
        if ($this->isInitialized()) {
848 1
            return parent::getStaticProperties();
849
        }
850
851 30
        $properties = [];
852
853 30
        $reflectionProperties = $this->getProperties(ReflectionProperty::IS_STATIC);
854 30
        foreach ($reflectionProperties as $reflectionProperty) {
855 6
            if (!$reflectionProperty instanceof ReflectionProperty) {
856 2
                if (!$reflectionProperty->isPublic()) {
857 2
                    $reflectionProperty->setAccessible(true);
858
                }
859
            }
860 6
            $properties[$reflectionProperty->getName()] = $reflectionProperty->getValue();
861
        }
862
863 30
        return $properties;
864
    }
865
866
    /**
867
     * Gets static property value
868
     *
869
     * @param string $name    The name of the static property for which to return a value.
870
     * @param mixed  $default A default value to return in case the class does not declare
871
     *                        a static property with the given name
872
     *
873
     * @return mixed
874
     * @throws ReflectionException If there is no such property and no default value was given
875
     */
876 1
    public function getStaticPropertyValue($name, $default = null)
877
    {
878 1
        $properties     = $this->getStaticProperties();
879 1
        $propertyExists = array_key_exists($name, $properties);
880
881 1
        if (!$propertyExists && func_num_args() === 1) {
882
            throw new ReflectionException("Static property does not exist and no default value is given");
883
        }
884
885 1
        return $propertyExists ? $properties[$name] : $default;
886
    }
887
888
889
    /**
890
     * Creates a new class instance from given arguments.
891
     *
892
     * @link http://php.net/manual/en/reflectionclass.newinstance.php
893
     *
894
     * Signature was hacked to support both 5.6, 7.1.x and 7.2.0 versions
895
     * @see https://3v4l.org/hW9O9
896
     * @see https://3v4l.org/sWT3j
897
     * @see https://3v4l.org/eeVf8
898
     *
899
     * @param mixed $arg First argument
900
     * @param mixed $args Accepts a variable number of arguments which are passed to the class constructor
901
     *
902
     * @return object
903
     */
904 1
    public function newInstance($arg = null, ...$args)
905
    {
906 1
        $args = array_slice(array_merge([$arg], $args), 0, \func_num_args());
907 1
        $this->initializeInternalReflection();
908
909 1
        return parent::newInstance(...$args);
910
    }
911
912
    /**
913
     * Creates a new class instance from given arguments.
914
     *
915
     * @link http://php.net/manual/en/reflectionclass.newinstanceargs.php
916
     *
917
     * @param array $args The parameters to be passed to the class constructor as an array.
918
     *
919
     * @return object
920
     */
921 1
    public function newInstanceArgs(array $args = [])
922
    {
923 1
        $function = __FUNCTION__;
924 1
        $this->initializeInternalReflection();
925
926 1
        return parent::$function($args);
927
    }
928
929
    /**
930
     * Creates a new class instance without invoking the constructor.
931
     *
932
     * @link http://php.net/manual/en/reflectionclass.newinstancewithoutconstructor.php
933
     *
934
     * @return object
935
     */
936 1
    public function newInstanceWithoutConstructor($args = null)
937
    {
938 1
        $function = __FUNCTION__;
939 1
        $this->initializeInternalReflection();
940
941 1
        return parent::$function($args);
942
    }
943
944
    /**
945
     * Sets static property value
946
     *
947
     * @link http://php.net/manual/en/reflectionclass.setstaticpropertyvalue.php
948
     *
949
     * @param string $name Property name
950
     * @param mixed $value New property value
951
     */
952 1
    public function setStaticPropertyValue($name, $value)
953
    {
954 1
        $this->initializeInternalReflection();
955
956 1
        parent::setStaticPropertyValue($name, $value);
957 1
    }
958
959 175
    private function recursiveCollect(\Closure $collector)
960
    {
961 175
        $result   = [];
962 175
        $isParent = true;
963
964 175
        $traits = $this->getTraits();
965 175
        foreach ($traits as $trait) {
966 9
            $collector($result, $trait, !$isParent);
967
        }
968
969 175
        $parentClass = $this->getParentClass();
970 175
        if ($parentClass) {
971 37
            $collector($result, $parentClass, $isParent);
972
        }
973
974 175
        $interfaces = ReflectionClass::collectInterfacesFromClassNode($this->classLikeNode);
975 175
        foreach ($interfaces as $interface) {
976 8
            $collector($result, $interface, $isParent);
977
        }
978
979 175
        return $result;
980
    }
981
982
    /**
983
     * Collects list of constants from the class itself
984
     */
985 38
    private function collectSelfConstants()
986
    {
987 38
        $expressionSolver = new NodeExpressionResolver($this);
988 38
        $localConstants   = [];
989
990
        // constants can be only top-level nodes in the class, so we can scan them directly
991 38
        foreach ($this->classLikeNode->stmts as $classLevelNode) {
992 34
            if ($classLevelNode instanceof ClassConst) {
993 17
                $nodeConstants = $classLevelNode->consts;
994 17
                if (!empty($nodeConstants)) {
995 17
                    foreach ($nodeConstants as $nodeConstant) {
996 17
                        $expressionSolver->process($nodeConstant->value);
997 17
                        $localConstants[$nodeConstant->name->toString()] = $expressionSolver->getValue();
998 34
                        $this->constants = $localConstants + $this->constants;
999
                    }
1000
                }
1001
            }
1002
        }
1003 38
    }
1004
1005
    /**
1006
     * Create a ReflectionClass for a given class name.
1007
     *
1008
     * @param string $className
1009
     *     The name of the class to create a reflection for.
1010
     *
1011
     * @return ReflectionClass
1012
     *     The apropriate reflection object.
1013
     */
1014
    abstract protected function createReflectionForClass($className);
1015
}
1016