DocBlockResolver::generateCacheKey()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 1
cts 1
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework\DocBlockResolver;
6
7
use Gacela\Framework\ClassResolver\DocBlockService\DocBlockParser;
8
use Gacela\Framework\ClassResolver\DocBlockService\MissingClassDefinitionException;
9
use Gacela\Framework\ClassResolver\DocBlockService\UseBlockParser;
10
use ReflectionClass;
11
12
use function is_string;
13
14
final class DocBlockResolver
15
{
16
    private const SPECIAL_RESOLVABLE_TYPES = ['Facade', 'Factory', 'Config'];
17
18
    /** @var array<string,string> [fileName => fileContent] */
19
    private static array $fileContentCache = [];
20
21
    /** @var class-string */
22
    private readonly string $callerClass;
23
24
    /**
25
     * @param class-string $callerClass
26
     */
27
    private function __construct(string $callerClass)
28 34
    {
29
        /** @psalm-suppress PropertyTypeCoercion */
30
        $this->callerClass = '\\' . ltrim($callerClass, '\\'); // @phpstan-ignore-line
0 ignored issues
show
Bug introduced by
The property callerClass is declared read-only in Gacela\Framework\DocBlockResolver\DocBlockResolver.
Loading history...
31 34
    }
32
33
    public static function fromCaller(object $caller): self
34 34
    {
35
        return new self($caller::class);
36 34
    }
37
38
    public function getDocBlockResolvable(string $method): DocBlockResolvable
39 34
    {
40
        $className = $this->getClassName($method);
41 34
        $resolvableType = $this->normalizeResolvableType($className);
42 31
43
        return new DocBlockResolvable($className, $resolvableType);
44 31
    }
45
46
    /**
47
     * @return class-string
48
     */
49
    private function getClassName(string $method): string
50 34
    {
51
        $cacheKey = $this->generateCacheKey($method);
52 34
        $cache = DocBlockResolverCache::getCacheInstance();
53 34
54
        if (!$cache->has($cacheKey)) {
55 34
            $className = $this->getClassFromDoc($method);
56 25
            $cache->put($cacheKey, $className);
57 22
        }
58
59
        return $cache->get($cacheKey);
60 31
    }
61
62
    private function generateCacheKey(string $method): string
63 34
    {
64
        return $this->callerClass . '::' . $method;
65 34
    }
66
67
    /**
68
     * @return class-string
69
     */
70
    private function getClassFromDoc(string $method): string
71 25
    {
72
        $reflectionClass = new ReflectionClass($this->callerClass);
73 25
        $className = $this->searchClassOverDocBlock($reflectionClass, $method);
74 25
        if (class_exists($className)) {
75 25
            return $className;
76
        }
77
78
        $className = $this->searchClassOverUseStatements($reflectionClass, $className);
79 25
        if (class_exists($className)) {
80 25
            return $className;
81 22
        }
82
83
        throw MissingClassDefinitionException::missingDefinition($this->callerClass, $method, $className);
84 3
    }
85
86
    /**
87 25
     * @param ReflectionClass<object> $reflectionClass
88
     */
89 25
    private function searchClassOverDocBlock(ReflectionClass $reflectionClass, string $method): string
90
    {
91 25
        $docBlock = (string)$reflectionClass->getDocComment();
92
93
        return (new DocBlockParser())->getClassFromMethod($docBlock, $method);
94
    }
95
96
    /**
97 25
     * Look the uses, to find the fully-qualified class name for the className.
98
     *
99 25
     * @param ReflectionClass<object> $reflectionClass
100 25
     */
101 15
    private function searchClassOverUseStatements(ReflectionClass $reflectionClass, string $className): string
102
    {
103 25
        $fileName = (string)$reflectionClass->getFileName();
104
        if (!isset(self::$fileContentCache[$fileName])) {
105 25
            self::$fileContentCache[$fileName] = (string)file_get_contents($fileName);
106
        }
107
108 31
        $phpFile = self::$fileContentCache[$fileName];
109
110
        return (new UseBlockParser())->getUseStatement($className, $phpFile);
111 31
    }
112 31
113
    private function normalizeResolvableType(string $resolvableType): string
114 31
    {
115 31
        /** @var list<string> $resolvableTypeParts */
116
        $resolvableTypeParts = explode('\\', $resolvableType);
117
        $normalizedResolvableType = end($resolvableTypeParts);
118 31
119 31
        $result = is_string($normalizedResolvableType)
120 22
            ? $normalizedResolvableType
121
            : $resolvableType;
122
123
        foreach (self::SPECIAL_RESOLVABLE_TYPES as $specialName) {
124 9
            if (str_contains($result, $specialName)) {
125
                return $specialName;
126
            }
127
        }
128
129
        return $result;
130
    }
131
}
132