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 |
||
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) |
|
210 | |||
211 | /** |
||
212 | * {@inheritDoc} |
||
213 | */ |
||
214 | 38 | public function getConstants() |
|
225 | |||
226 | /** |
||
227 | * {@inheritDoc} |
||
228 | */ |
||
229 | 19 | public function getConstructor() |
|
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() |
|
283 | |||
284 | 29 | public function getEndLine() |
|
288 | |||
289 | 29 | public function getExtension() |
|
293 | |||
294 | 29 | public function getExtensionName() |
|
298 | |||
299 | 21 | public function getFileName() |
|
303 | |||
304 | /** |
||
305 | * {@inheritDoc} |
||
306 | */ |
||
307 | 30 | public function getInterfaceNames() |
|
311 | |||
312 | /** |
||
313 | * {@inheritDoc} |
||
314 | */ |
||
315 | 59 | public function getInterfaces() |
|
328 | |||
329 | /** |
||
330 | * {@inheritdoc} |
||
331 | * @param string $name |
||
332 | */ |
||
333 | 2047 | public function getMethod($name) |
|
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) |
|
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() |
|
430 | |||
431 | /** |
||
432 | * {@inheritDoc} |
||
433 | */ |
||
434 | 50 | public function getNamespaceName() |
|
438 | |||
439 | /** |
||
440 | * {@inheritDoc} |
||
441 | */ |
||
442 | 235 | public function getParentClass() |
|
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) |
|
501 | |||
502 | /** |
||
503 | * {@inheritdoc} |
||
504 | */ |
||
505 | 255 | public function getProperty($name) |
|
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) { |
|
|
|||
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() |
|
563 | |||
564 | 29 | public function getStartLine() |
|
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() |
|
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() |
|
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() |
|
627 | |||
628 | /** |
||
629 | * {@inheritDoc} |
||
630 | */ |
||
631 | 11 | public function hasConstant($name) |
|
638 | |||
639 | /** |
||
640 | * {@inheritdoc} |
||
641 | * @param string $name |
||
642 | */ |
||
643 | 20 | public function hasMethod($name) |
|
654 | |||
655 | /** |
||
656 | * {@inheritdoc} |
||
657 | */ |
||
658 | public function hasProperty($name) |
||
669 | |||
670 | /** |
||
671 | * {@inheritDoc} |
||
672 | * @param string $interfaceName |
||
673 | */ |
||
674 | 29 | public function implementsInterface($interfaceName) |
|
680 | |||
681 | /** |
||
682 | * {@inheritDoc} |
||
683 | */ |
||
684 | 29 | public function inNamespace() |
|
688 | |||
689 | /** |
||
690 | * {@inheritDoc} |
||
691 | */ |
||
692 | 75 | public function isAbstract() |
|
704 | |||
705 | /** |
||
706 | * Currently, anonymous classes aren't supported for parsed reflection |
||
707 | */ |
||
708 | public function isAnonymous() |
||
712 | |||
713 | /** |
||
714 | * {@inheritDoc} |
||
715 | */ |
||
716 | 29 | public function isCloneable() |
|
728 | |||
729 | /** |
||
730 | * {@inheritDoc} |
||
731 | */ |
||
732 | 29 | public function isFinal() |
|
738 | |||
739 | /** |
||
740 | * {@inheritDoc} |
||
741 | */ |
||
742 | public function isInstance($object) |
||
751 | |||
752 | /** |
||
753 | * {@inheritDoc} |
||
754 | */ |
||
755 | 29 | public function isInstantiable() |
|
767 | |||
768 | /** |
||
769 | * {@inheritDoc} |
||
770 | */ |
||
771 | 242 | public function isInterface() |
|
775 | |||
776 | /** |
||
777 | * {@inheritDoc} |
||
778 | */ |
||
779 | 29 | public function isInternal() |
|
784 | |||
785 | /** |
||
786 | * {@inheritDoc} |
||
787 | */ |
||
788 | 29 | public function isIterateable() |
|
792 | |||
793 | /** |
||
794 | * {@inheritDoc} |
||
795 | */ |
||
796 | public function isSubclassOf($class) |
||
819 | |||
820 | /** |
||
821 | * {@inheritDoc} |
||
822 | */ |
||
823 | 106 | public function isTrait() |
|
827 | |||
828 | /** |
||
829 | * {@inheritDoc} |
||
830 | */ |
||
831 | 29 | public function isUserDefined() |
|
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() |
|
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) |
|
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) |
|
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 = []) |
|
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) |
|
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) |
|
958 | |||
959 | 172 | private function recursiveCollect(\Closure $collector) |
|
981 | |||
982 | /** |
||
983 | * Collects list of constants from the class itself |
||
984 | */ |
||
985 | 38 | private function collectSelfConstants() |
|
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: