Passed
Push — master ( 4c0c0d...f49598 )
by Chema
01:37 queued 12s
created

DocBlockResolver::hasParentCallMethod()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 2
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 5
ccs 0
cts 3
cp 0
crap 6
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework\DocBlockResolver;
6
7
use Gacela\Framework\ClassResolver\Cache\ClassNameCacheInterface;
8
use Gacela\Framework\ClassResolver\Cache\InMemoryClassNameCache;
9
use Gacela\Framework\ClassResolver\Cache\ProfiledInMemoryCache;
10
use Gacela\Framework\ClassResolver\DocBlockService\DocBlockParser;
11
use Gacela\Framework\ClassResolver\DocBlockService\MissingClassDefinitionException;
12
use Gacela\Framework\ClassResolver\DocBlockService\UseBlockParser;
13
use Gacela\Framework\ClassResolver\Profiler\CustomServicesJsonProfiler;
14
use Gacela\Framework\ClassResolver\Profiler\FileProfilerInterface;
15
use Gacela\Framework\ClassResolver\Profiler\GacelaProfiler;
16
use Gacela\Framework\Config\Config;
17
use ReflectionClass;
18
19
use function get_class;
20
use function is_string;
21
22
final class DocBlockResolver
23
{
24
    /** @var array<string,string> [fileName => fileContent] */
25
    private static array $fileContentCache = [];
26
27
    /** @var class-string */
28
    private string $callerClass;
29
30
    /**
31
     * @param class-string $callerClass
32
     */
33 25
    private function __construct(string $callerClass)
34
    {
35 25
        $this->callerClass = $callerClass;
36
    }
37
38 25
    public static function fromCaller(object $caller): self
39
    {
40 25
        return new self(get_class($caller));
41
    }
42
43 25
    public function getDocBlockResolvable(string $method): DocBlockResolvable
44
    {
45 25
        $className = $this->getClassName($method);
46 23
        $resolvableType = $this->normalizeResolvableType($className);
47
48 23
        return new DocBlockResolvable($className, $resolvableType);
49
    }
50
51
    /**
52
     * @return class-string
53
     */
54 25
    private function getClassName(string $method): string
55
    {
56 25
        $cacheKey = $this->generateCacheKey($method);
57 25
        $cache = $this->createClassNameCache();
58
59 25
        if (!$cache->has($cacheKey)) {
60 15
            $className = $this->getClassFromDoc($method);
61 13
            $cache->put($cacheKey, $className);
62
        }
63
64 23
        return $cache->get($cacheKey);
65
    }
66
67 23
    private function normalizeResolvableType(string $resolvableType): string
68
    {
69
        /** @var list<string> $resolvableTypeParts */
70 23
        $resolvableTypeParts = explode('\\', $resolvableType);
71 23
        $normalizedResolvableType = end($resolvableTypeParts);
72
73 23
        return is_string($normalizedResolvableType)
74 23
            ? $normalizedResolvableType
75 23
            : $resolvableType;
76
    }
77
78 25
    private function generateCacheKey(string $method): string
79
    {
80 25
        return '\\' . ltrim($this->callerClass, '\\') . '::' . $method;
81
    }
82
83 25
    private function createClassNameCache(): ClassNameCacheInterface
84
    {
85 25
        $inMemoryCache = new InMemoryClassNameCache(CustomServicesJsonProfiler::class);
86
87 25
        if ($this->isProjectProfilerEnabled()) {
88 4
            return new ProfiledInMemoryCache(
89
                $inMemoryCache,
90 4
                $this->createProfiler()
91
            );
92
        }
93
94 21
        return $inMemoryCache;
95
    }
96
97 25
    private function isProjectProfilerEnabled(): bool
98
    {
99 25
        return (new GacelaProfiler(Config::getInstance()))->isEnabled();
100
    }
101
102
    /**
103
     * @return class-string
104
     */
105 15
    private function getClassFromDoc(string $method): string
106
    {
107 15
        $reflectionClass = new ReflectionClass($this->callerClass);
108 15
        $className = $this->searchClassOverDocBlock($reflectionClass, $method);
109 15
        if (class_exists($className)) {
110
            return $className;
111
        }
112 15
        $className = $this->searchClassOverUseStatements($reflectionClass, $className);
113 15
        if (class_exists($className)) {
114 13
            return $className;
115
        }
116 2
        throw MissingClassDefinitionException::missingDefinition($this->callerClass, $method, $className);
117
    }
118
119 15
    private function searchClassOverDocBlock(ReflectionClass $reflectionClass, string $method): string
120
    {
121 15
        $docBlock = (string)$reflectionClass->getDocComment();
122
123 15
        return (new DocBlockParser())->getClassFromMethod($docBlock, $method);
124
    }
125
126
    /**
127
     * Look the uses, to find the fully-qualified class name for the className.
128
     */
129 15
    private function searchClassOverUseStatements(ReflectionClass $reflectionClass, string $className): string
130
    {
131 15
        $fileName = (string)$reflectionClass->getFileName();
132 15
        if (!isset(self::$fileContentCache[$fileName])) {
133 12
            self::$fileContentCache[$fileName] = (string)file_get_contents($fileName);
134
        }
135 15
        $phpFile = self::$fileContentCache[$fileName];
136
137 15
        return (new UseBlockParser())->getUseStatement($className, $phpFile);
138
    }
139
140 4
    private function createProfiler(): FileProfilerInterface
141
    {
142 4
        return new CustomServicesJsonProfiler(
143 4
            Config::getInstance()->getProfilerDir(),
144
        );
145
    }
146
}
147