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