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
|
|||
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 |
Let’s take a look at an example:
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
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the parent class: