| Total Complexity | 20 | 
| Total Lines | 133 | 
| Duplicated Lines | 0 % | 
| Coverage | 92.86% | 
| Changes | 1 | ||
| Bugs | 0 | Features | 0 | 
| 1 | <?php  | 
            ||
| 20 | final class DocBlockResolver  | 
            ||
| 21 | { | 
            ||
| 22 | /** @var array<string,string> [fileName => fileContent] */  | 
            ||
| 23 | private static array $fileContentCache = [];  | 
            ||
| 24 | |||
| 25 | /** @var class-string */  | 
            ||
| 
                                                                                                    
                         1 ignored issue 
                            –
                            show
                         | 
                |||
| 26 | private string $callerClass;  | 
            ||
| 27 | |||
| 28 | /** @var class-string|string */  | 
            ||
| 
                                                                                                    
                         1 ignored issue 
                            –
                            show
                         | 
                |||
| 29 | private string $callerParentClass;  | 
            ||
| 30 | |||
| 31 | /**  | 
            ||
| 32 | * @param class-string $callerClass  | 
            ||
| 
                                                                                                    
                         1 ignored issue 
                            –
                            show
                         | 
                |||
| 33 | * @param class-string|string $callerParentClass  | 
            ||
| 34 | */  | 
            ||
| 35 | 13 | private function __construct(string $callerClass, string $callerParentClass)  | 
            |
| 36 |     { | 
            ||
| 37 | 13 | $this->callerClass = $callerClass;  | 
            |
| 38 | 13 | $this->callerParentClass = $callerParentClass;  | 
            |
| 39 | }  | 
            ||
| 40 | |||
| 41 | 13 | public static function fromCaller(object $caller): self  | 
            |
| 42 |     { | 
            ||
| 43 | 13 | return new self(  | 
            |
| 44 | 13 | get_class($caller),  | 
            |
| 45 | 13 | get_parent_class($caller) ?: ''  | 
            |
| 46 | );  | 
            ||
| 47 | }  | 
            ||
| 48 | |||
| 49 | public function hasParentCallMethod(): bool  | 
            ||
| 50 |     { | 
            ||
| 51 | /** @psalm-suppress ArgumentTypeCoercion */  | 
            ||
| 52 | return $this->callerParentClass !== ''  | 
            ||
| 53 | && method_exists($this->callerParentClass, '__call');  | 
            ||
| 54 | }  | 
            ||
| 55 | |||
| 56 | 13 | public function getDocBlockResolvable(string $method): DocBlockResolvable  | 
            |
| 62 | }  | 
            ||
| 63 | |||
| 64 | /**  | 
            ||
| 65 | * @return class-string  | 
            ||
| 
                                                                                                    
                         1 ignored issue 
                            –
                            show
                         | 
                |||
| 66 | */  | 
            ||
| 67 | 13 | private function getClassName(string $method): string  | 
            |
| 68 |     { | 
            ||
| 69 | 13 | $cacheKey = $this->generateCacheKey($method);  | 
            |
| 70 | 13 | $cache = $this->createClassNameCache();  | 
            |
| 71 | |||
| 72 | 13 |         if (!$cache->has($cacheKey)) { | 
            |
| 73 | 11 | $className = $this->getClassFromDoc($method);  | 
            |
| 74 | 9 | $cache->put($cacheKey, $className);  | 
            |
| 75 | }  | 
            ||
| 76 | |||
| 77 | /** @psalm-suppress ArgumentTypeCoercion */  | 
            ||
| 78 | /** @var class-string $className */  | 
            ||
| 79 | 11 | $className = $cache->get($cacheKey);  | 
            |
| 80 | |||
| 81 | 11 | return $className;  | 
            |
| 82 | }  | 
            ||
| 83 | |||
| 84 | 11 | private function normalizeResolvableType(string $resolvableType): string  | 
            |
| 85 |     { | 
            ||
| 86 | /** @var list<string> $resolvableTypeParts */  | 
            ||
| 87 | 11 |         $resolvableTypeParts = explode('\\', ltrim($resolvableType, '\\')); | 
            |
| 88 | 11 | $normalizedResolvableType = end($resolvableTypeParts);  | 
            |
| 89 | |||
| 90 | 11 | return is_string($normalizedResolvableType)  | 
            |
| 91 | 11 | ? $normalizedResolvableType  | 
            |
| 92 | 11 | : $resolvableType;  | 
            |
| 93 | }  | 
            ||
| 94 | |||
| 95 | 13 | private function generateCacheKey(string $method): string  | 
            |
| 96 |     { | 
            ||
| 97 | 13 | return $this->callerClass . '::' . $method;  | 
            |
| 98 | }  | 
            ||
| 99 | |||
| 100 | 13 | private function createClassNameCache(): ClassNameCacheInterface  | 
            |
| 101 |     { | 
            ||
| 102 | 13 |         if (!$this->isProjectCacheEnabled()) { | 
            |
| 103 | 4 | return new InMemoryCache(CustomServicesCache::class);  | 
            |
| 104 | }  | 
            ||
| 105 | |||
| 106 | 9 | return new CustomServicesCache(  | 
            |
| 107 | 9 | Config::getInstance()->getCacheDir()  | 
            |
| 108 | );  | 
            ||
| 109 | }  | 
            ||
| 110 | |||
| 111 | 13 | private function isProjectCacheEnabled(): bool  | 
            |
| 115 | }  | 
            ||
| 116 | |||
| 117 | /**  | 
            ||
| 118 | * @return class-string  | 
            ||
| 
                                                                                                    
                         1 ignored issue 
                            –
                            show
                         | 
                |||
| 119 | */  | 
            ||
| 120 | 11 | private function getClassFromDoc(string $method): string  | 
            |
| 121 |     { | 
            ||
| 122 | 11 | $reflectionClass = new ReflectionClass($this->callerClass);  | 
            |
| 123 | 11 | $className = $this->searchClassOverDocBlock($reflectionClass, $method);  | 
            |
| 124 | 11 |         if (class_exists($className)) { | 
            |
| 125 | return $className;  | 
            ||
| 126 | }  | 
            ||
| 127 | 11 | $className = $this->searchClassOverUseStatements($reflectionClass, $className);  | 
            |
| 128 | 11 |         if (class_exists($className)) { | 
            |
| 129 | 9 | return $className;  | 
            |
| 130 | }  | 
            ||
| 131 | 2 | throw MissingClassDefinitionException::missingDefinition($this->callerClass, $method, $className);  | 
            |
| 132 | }  | 
            ||
| 133 | |||
| 134 | 11 | private function searchClassOverDocBlock(ReflectionClass $reflectionClass, string $method): string  | 
            |
| 135 |     { | 
            ||
| 136 | 11 | $docBlock = (string)$reflectionClass->getDocComment();  | 
            |
| 137 | |||
| 138 | 11 | return (new DocBlockParser())->getClassFromMethod($docBlock, $method);  | 
            |
| 139 | }  | 
            ||
| 140 | |||
| 141 | /**  | 
            ||
| 142 | * Look the uses, to find the fully-qualified class name for the className.  | 
            ||
| 143 | */  | 
            ||
| 144 | 11 | private function searchClassOverUseStatements(ReflectionClass $reflectionClass, string $className): string  | 
            |
| 153 | }  | 
            ||
| 154 | }  | 
            ||
| 155 |