Passed
Push — master ( 54c9d7...129e56 )
by Chema
01:29 queued 13s
created

DocBlockResolver::isProjectCacheEnabled()   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 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework\DocBlockResolver;
6
7
use Gacela\Framework\ClassResolver\Cache\GacelaCache;
8
use Gacela\Framework\ClassResolver\ClassNameCacheInterface;
9
use Gacela\Framework\ClassResolver\DocBlockService\CustomServicesCache;
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\InMemoryCache;
14
use Gacela\Framework\Config\Config;
15
use ReflectionClass;
16
17
use function get_class;
18
use function is_string;
19
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
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
26
    private string $callerClass;
27
28
    /** @var class-string|string */
1 ignored issue
show
Documentation Bug introduced by
The doc comment class-string|string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|string.
Loading history...
29
    private string $callerParentClass;
30
31
    /**
32
     * @param class-string $callerClass
1 ignored issue
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
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
57
    {
58 13
        $className = $this->getClassName($method);
59 11
        $resolvableType = $this->normalizeResolvableType($className);
60
61 11
        return new DocBlockResolvable($className, $resolvableType);
62
    }
63
64
    /**
65
     * @return class-string
1 ignored issue
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
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
112
    {
113 13
        return (new GacelaCache(Config::getInstance()))
114 13
            ->isProjectCacheEnabled();
115
    }
116
117
    /**
118
     * @return class-string
1 ignored issue
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
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
145
    {
146 11
        $fileName = (string)$reflectionClass->getFileName();
147 11
        if (!isset(self::$fileContentCache[$fileName])) {
148 9
            self::$fileContentCache[$fileName] = (string)file_get_contents($fileName);
149
        }
150 11
        $phpFile = self::$fileContentCache[$fileName];
151
152 11
        return (new UseBlockParser())->getUseStatement($className, $phpFile);
153
    }
154
}
155