ReflectionClassLikeTrait   F
last analyzed

Complexity

Total Complexity 161

Size/Duplication

Total Lines 986
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 14

Test Coverage

Coverage 68.46%

Importance

Changes 0
Metric Value
wmc 161
lcom 2
cbo 14
dl 0
loc 986
ccs 254
cts 371
cp 0.6846
rs 1.614
c 0
b 0
f 0

53 Methods

Rating   Name   Duplication   Size   Complexity  
A getConstant() 0 8 2
A getConstants() 0 11 2
A getConstructor() 0 9 2
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
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
createReflectionForClass() 0 1 ?
A getReflectionConstant() 0 11 3
A getReflectionConstants() 0 20 5
D __toString() 0 89 16
B getDefaultProperties() 0 26 7
B getModifiers() 0 27 8

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\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
                    $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 2030
                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 2073
    public function getMethods($filter = null)
353
    {
354 2073
        if (!isset($this->methods)) {
355 56
            $directMethods = ReflectionMethod::collectFromClassNode($this->classLikeNode, $this);
356
            $parentMethods = $this->recursiveCollect(function (array &$result, \ReflectionClass $instance, $isParent) {
357 16
                $reflectionMethods = [];
358 16
                foreach ($instance->getMethods() as $reflectionMethod) {
359 11
                    if (!$isParent || !$reflectionMethod->isPrivate()) {
360 11
                        $reflectionMethods[$reflectionMethod->name] = $reflectionMethod;
361
                    }
362
                }
363 16
                $result += $reflectionMethods;
364 56
            });
365 56
            $methods = $directMethods + $parentMethods;
366
367 56
            $this->methods = $methods;
368
        }
369 2073
        if (!isset($filter)) {
370 2072
            return array_values($this->methods);
371
        }
372
373 1
        $methods = [];
374 1
        foreach ($this->methods as $method) {
375 1
            if (!($filter & $method->getModifiers())) {
376 1
                continue;
377
            }
378 1
            $methods[] = $method;
379
        }
380
381 1
        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
    public function getModifiers()
394
    {
395
        $modifiers = 0;
396
397
        if ($this->isFinal()) {
398
            $modifiers += \ReflectionClass::IS_FINAL;
399
        }
400
401
        if (PHP_VERSION_ID < 70000 && $this->isTrait()) {
402
            $modifiers += \ReflectionClass::IS_EXPLICIT_ABSTRACT;
403
        }
404
405
        if ($this->classLikeNode instanceof Class_ && $this->classLikeNode->isAbstract()) {
406
            $modifiers += \ReflectionClass::IS_EXPLICIT_ABSTRACT;
407
        }
408
409
        if ($this->isInterface()) {
410
            $abstractMethods = $this->getMethods();
411
        } else {
412
            $abstractMethods = $this->getMethods(\ReflectionMethod::IS_ABSTRACT);
413
        }
414
        if (!empty($abstractMethods)) {
415
            $modifiers += \ReflectionClass::IS_IMPLICIT_ABSTRACT;
416
        }
417
418
        return $modifiers;
419
    }
420
421
    /**
422
     * {@inheritDoc}
423
     */
424 3001
    public function getName()
425
    {
426 3001
        $namespaceName = $this->namespaceName ? $this->namespaceName . '\\' : '';
427
428 3001
        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 235
    public function getParentClass()
443
    {
444 235
        if (!isset($this->parentClass)) {
445 94
            static $extendsField = 'extends';
446
447 94
            $parentClass = false;
448 94
            $hasExtends  = in_array($extendsField, $this->classLikeNode->getSubNodeNames());
449 94
            $extendsNode = $hasExtends ? $this->classLikeNode->$extendsField : null;
450 94
            if ($extendsNode instanceof FullyQualified) {
451 24
                $extendsName = $extendsNode->toString();
452 24
                $parentClass = $this->createReflectionForClass($extendsName);
453
            }
454 94
            $this->parentClass = $parentClass;
455
        }
456
457 235
        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 3
    public function getReflectionConstant($name)
521
    {
522 3
        $classConstants = $this->getReflectionConstants();
523 3
        foreach ($classConstants as $classConstant) {
524 3
            if ($classConstant->getName() == $name) {
525 3
                return $classConstant;
526
            }
527
        }
528
529 1
        return false;
530
    }
531
532
    /**
533
     * @inheritDoc
534
     */
535 8
    public function getReflectionConstants()
536
    {
537 8
        if (!isset($this->classConstants)) {
538 8
            $directClassConstants = ReflectionClassConstant::collectFromClassNode($this->classLikeNode, $this->getName());
539 8
            $parentClassConstants = $this->recursiveCollect(function (array &$result, \ReflectionClass $instance, $isParent) {
540 2
                $reflectionClassConstants = [];
541 2
                foreach ($instance->getReflectionConstants() as $reflectionClassConstant) {
0 ignored issues
show
Bug introduced by MaximilianKresse
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 8
            });
548 8
            $classConstants = $directClassConstants + $parentClassConstants;
549
550 8
            $this->classConstants = $classConstants;
551
        }
552
553 8
        return array_values($this->classConstants);
554
    }
555
556
    /**
557
     * {@inheritDoc}
558
     */
559 3001
    public function getShortName()
560
    {
561 3001
        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 230
    public function getTraits()
618
    {
619 230
        if (!isset($this->traits)) {
620 94
            $traitAdaptations = [];
621 94
            $this->traits     = ReflectionClass::collectTraitsFromClassNode($this->classLikeNode, $traitAdaptations);
622 94
            $this->traitAdaptations = $traitAdaptations;
623
        }
624
625 230
        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 2
                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 29
    public function isFinal()
733
    {
734 29
        $isFinal = $this->classLikeNode instanceof Class_ && $this->classLikeNode->isFinal();
735
736 29
        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);
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...
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 242
    public function isInterface()
772
    {
773 242
        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 172
    private function recursiveCollect(\Closure $collector)
960
    {
961 172
        $result   = [];
962 172
        $isParent = true;
963
964 172
        $traits = $this->getTraits();
965 172
        foreach ($traits as $trait) {
966 8
            $collector($result, $trait, !$isParent);
967
        }
968
969 172
        $parentClass = $this->getParentClass();
970 172
        if ($parentClass) {
971 35
            $collector($result, $parentClass, $isParent);
972
        }
973
974 172
        $interfaces = ReflectionClass::collectInterfacesFromClassNode($this->classLikeNode);
975 172
        foreach ($interfaces as $interface) {
976 7
            $collector($result, $interface, $isParent);
977
        }
978
979 172
        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 17
                        $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