Passed
Push — feature/improve-file-cache ( 6742d2 )
by Chema
04:18
created

DocBlockResolver::isProjectCacheEnabled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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