ReflectionClassLikeTrait   F
last analyzed

Complexity

Total Complexity 153

Size/Duplication

Total Lines 942
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 13

Test Coverage

Coverage 70.22%

Importance

Changes 0
Metric Value
wmc 153
lcom 2
cbo 13
dl 0
loc 942
ccs 250
cts 356
cp 0.7022
rs 1.658
c 0
b 0
f 0

51 Methods

Rating   Name   Duplication   Size   Complexity  
createReflectionForClass() 0 1 ?
D __toString() 0 89 16
A getConstant() 0 8 2
A getConstants() 0 11 2
A getConstructor() 0 9 2
B getDefaultProperties() 0 26 7
A getDocComment() 0 6 2
A getEndLine() 0 4 1
A getExtension() 0 4 1
A getExtensionName() 0 4 1
A getFileName() 0 4 1
A getInterfaceNames() 0 4 1
A getInterfaces() 0 13 3
A getMethod() 0 11 3
B getMethods() 0 31 8
B getModifiers() 0 27 8
A getName() 0 6 2
A getNamespaceName() 0 4 1
A getParentClass() 0 17 4
B getProperties() 0 33 8
A getProperty() 0 11 3
A getShortName() 0 4 1
A getStartLine() 0 4 1
A getTraitAliases() 0 20 5
A getTraitNames() 0 4 1
A getTraits() 0 10 2
A hasConstant() 0 7 2
A hasMethod() 0 11 3
A hasProperty() 0 11 3
A implementsInterface() 0 6 1
A inNamespace() 0 4 1
B isAbstract() 0 12 7
A isAnonymous() 0 4 1
A isCloneable() 0 12 5
A isFinal() 0 6 2
A isInstance() 0 9 3
A isInstantiable() 0 12 5
A isInterface() 0 4 1
A isInternal() 0 5 1
A isIterateable() 0 4 1
B isSubclassOf() 0 23 7
A isTrait() 0 4 1
A isUserDefined() 0 5 1
A getStaticProperties() 0 21 5
A getStaticPropertyValue() 0 11 4
A newInstance() 0 7 1
A newInstanceArgs() 0 7 1
A newInstanceWithoutConstructor() 0 7 1
A setStaticPropertyValue() 0 6 1
A recursiveCollect() 0 22 4
A collectSelfConstants() 0 19 5

How to fix   Complexity   

Complex Class

Complex classes like ReflectionClassLikeTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ReflectionClassLikeTrait, and based on these observations, apply Extract Interface, too.

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