Skip to content

Commit 219a75c

Browse files
committed
Refactor generics and inheritance handling in templates; add new test cases for inherited interface template binding
1 parent bddfe72 commit 219a75c

11 files changed

Lines changed: 331 additions & 135 deletions

src/Contract/HierarchyResolver.php

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace 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

Comments
 (0)