44
55namespace TypePHP \Contract ;
66
7+ use ReflectionClass ;
8+ use ReflectionMethod ;
9+
710/**
811 * @internal Resolves class, interface, trait, and method inheritance hierarchies from child to root.
912 */
@@ -12,14 +15,14 @@ final class HierarchyResolver
1215 /**
1316 * In-memory cache for resolved ReflectionMethod hierarchy arrays.
1417 *
15- * @var array<string, array<int, \ ReflectionMethod>>
18+ * @var array<string, array<int, ReflectionMethod>>
1619 */
1720 private static array $ methodHierarchyCache = [];
1821
1922 /**
2023 * In-memory cache for resolved ReflectionClass hierarchy arrays.
2124 *
22- * @var array<string, array<int, \ ReflectionClass<object>>>
25+ * @var array<string, array<int, ReflectionClass<object>>>
2326 */
2427 private static array $ classHierarchyCache = [];
2528
@@ -35,16 +38,9 @@ public static function reset(): void
3538 /**
3639 * Builds an array of ReflectionMethods representing the inheritance hierarchy from child to root.
3740 *
38- * Resolution Flow:
39- * 1. Target Class: Uses $ref->class to inspect the target executing class (ensures interface
40- * contracts are discovered even when method bodies are fulfilled by a Trait).
41- * 2. Parent Classes: Traverses up the parent class chain to inherit parent method docblocks.
42- * 3. Interfaces: Traverses implemented interfaces to inherit interface contract docblocks.
43- * 4. Traits: Traverses used traits to inherit trait method docblocks.
44- *
45- * @return array<int, \ReflectionMethod>
41+ * @return array<int, ReflectionMethod>
4642 */
47- public static function getMethodHierarchy (\ ReflectionMethod $ ref ): array
43+ public static function getMethodHierarchy (ReflectionMethod $ ref ): array
4844 {
4945 $ cacheKey = $ ref ->class . ':: ' . $ ref ->getName ();
5046 if (isset (self ::$ methodHierarchyCache [$ cacheKey ])) {
@@ -55,7 +51,18 @@ public static function getMethodHierarchy(\ReflectionMethod $ref): array
5551 $ methodName = $ ref ->getName ();
5652 $ targetClassName = $ ref ->class ;
5753
58- $ targetClass = new \ReflectionClass ($ targetClassName );
54+ $ targetClass = new ReflectionClass ($ targetClassName );
55+
56+ $ traitAliases = $ targetClass ->getTraitAliases ();
57+ if (isset ($ traitAliases [$ methodName ])) {
58+ [$ traitName , $ originalMethodName ] = explode (':: ' , $ traitAliases [$ methodName ], 2 );
59+ if (trait_exists ($ traitName )) {
60+ $ traitRef = new ReflectionClass ($ traitName );
61+ if ($ traitRef ->hasMethod ($ originalMethodName )) {
62+ $ hierarchy [] = $ traitRef ->getMethod ($ originalMethodName );
63+ }
64+ }
65+ }
5966
6067 $ parent = $ targetClass ->getParentClass ();
6168 while ($ parent !== false ) {
@@ -81,40 +88,46 @@ public static function getMethodHierarchy(\ReflectionMethod $ref): array
8188 }
8289
8390 /**
84- * Builds an array of ReflectionClasses representing the class inheritance hierarchy from child to root.
91+ * Builds an array of ReflectionClasses representing the complete inheritance hierarchy from child to root.
92+ * Recursively traverses parent classes, implemented interfaces, and used traits across all levels.
8593 *
86- * Resolution Flow:
87- * 1. Target Class: Includes the primary reflection class.
88- * 2. Parent Classes: Traverses parent classes up the inheritance tree.
89- * 3. Interfaces: Collects all implemented interfaces.
90- * 4. Traits: Collects all used traits.
94+ * @param ReflectionClass<object> $ref
9195 *
92- * @param \ReflectionClass<object> $ref
93- *
94- * @return array<int, \ReflectionClass<object>>
96+ * @return array<int, ReflectionClass<object>>
9597 */
96- public static function getClassHierarchy (\ ReflectionClass $ ref ): array
98+ public static function getClassHierarchy (ReflectionClass $ ref ): array
9799 {
98100 $ cacheKey = $ ref ->getName ();
99101 if (isset (self ::$ classHierarchyCache [$ cacheKey ])) {
100102 return self ::$ classHierarchyCache [$ cacheKey ];
101103 }
102104
103- $ hierarchy = [$ ref ];
105+ $ hierarchy = [];
106+ $ visited = [];
104107
105- $ parent = $ ref ->getParentClass ();
106- while ($ parent !== false ) {
107- $ hierarchy [] = $ parent ;
108- $ parent = $ parent ->getParentClass ();
109- }
108+ $ collect = function (ReflectionClass $ class ) use (&$ collect , &$ hierarchy , &$ visited ): void {
109+ $ name = $ class ->getName ();
110+ if (isset ($ visited [$ name ])) {
111+ return ;
112+ }
113+ $ visited [$ name ] = true ;
114+ $ hierarchy [] = $ class ;
110115
111- foreach ($ ref ->getInterfaces () as $ interface ) {
112- $ hierarchy [] = $ interface ;
113- }
116+ $ parent = $ class ->getParentClass ();
117+ if ($ parent !== false ) {
118+ $ collect ($ parent );
119+ }
114120
115- foreach ($ ref ->getTraits () as $ trait ) {
116- $ hierarchy [] = $ trait ;
117- }
121+ foreach ($ class ->getInterfaces () as $ interface ) {
122+ $ collect ($ interface );
123+ }
124+
125+ foreach ($ class ->getTraits () as $ trait ) {
126+ $ collect ($ trait );
127+ }
128+ };
129+
130+ $ collect ($ ref );
118131
119132 return self ::$ classHierarchyCache [$ cacheKey ] = $ hierarchy ;
120133 }
0 commit comments