Passed
Push — bugfix/support-windows ( 517fb4...0e99e7 )
by Chema
04:47
created

DocBlockResolver::createProfiler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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