Passed
Push — feature/custom-services-cache ( 09c1c5...2e1100 )
by Jesús
04:16
created

DocBlockResolverAwareTrait::hasParentClass()   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 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
ccs 0
cts 3
cp 0
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework;
6
7
use Gacela\Framework\ClassResolver\DocBlockService\CustomServicesCache;
8
use Gacela\Framework\ClassResolver\DocBlockService\DocBlockParser;
9
use Gacela\Framework\ClassResolver\DocBlockService\DocBlockServiceResolver;
10
use Gacela\Framework\ClassResolver\DocBlockService\MissingClassDefinitionException;
11
use Gacela\Framework\ClassResolver\DocBlockService\UseBlockParser;
12
use Gacela\Framework\Config\Config;
13
use ReflectionClass;
14
15
use function is_string;
16
17
trait DocBlockResolverAwareTrait
18
{
19
    /** @var array<string,string> */
20
    protected static array $fileContentCache = [];
21
22
    /** @var array<string,?mixed> */
23
    private array $customServices = [];
24
25
    /**
26
     * @param string $method
27
     * @param array $parameters
28
     *
29
     * @return mixed
30
     */
31 8
    public function __call($method, $parameters = [])
32
    {
33 8
        if (isset($this->customServices[$method])) {
34
            return $this->customServices[$method];
35
        }
36
37 8
        $cacheKey = $this->generateCacheKey($method);
38 8
        $cache = $this->createCustomServicesCache();
39
40 8
        if (!$cache->has($cacheKey)) {
41 7
            $className = $this->getClassFromDoc($method);
42 6
            $cache->put($cacheKey, $className);
43
        }
44
45
        /** @psalm-suppress ArgumentTypeCoercion */
46 7
        $className = $cache->get($cacheKey);
47 7
        $resolvableType = $this->normalizeResolvableType($className);
48
49 7
        $resolved = (new DocBlockServiceResolver($resolvableType))
50 7
            ->resolve($className);
51
52 7
        if ($resolved !== null) {
53 7
            return $resolved;
54
        }
55
56
        if ($this->hasParentClass()) {
57
            /** @psalm-suppress ParentNotFound */
58
            $parentReturn = parent::__call($method, $parameters);
59
            $this->customServices[$method] = $parentReturn;
60
61
            return $parentReturn;
62
        }
63
64
        return null;
65
    }
66
67
    private function hasParentClass(): bool
68
    {
69
        /** @psalm-suppress ParentNotFound,MixedArgument */
70
        return class_parents($this)
71
            && method_exists(parent::class, '__call');
72
    }
73
74
    /**
75
     * @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...
76
     */
77 7
    private function getClassFromDoc(string $method): string
78
    {
79 7
        $reflectionClass = new ReflectionClass(static::class);
80 7
        $className = $this->searchClassOverDocBlock($reflectionClass, $method);
81 7
        if (class_exists($className)) {
82
            return $className;
83
        }
84 7
        $className = $this->searchClassOverUseStatements($reflectionClass, $className);
85 7
        if (class_exists($className)) {
86 6
            return $className;
87
        }
88 1
        throw MissingClassDefinitionException::missingDefinition(static::class, $method, $className);
89
    }
90
91 7
    private function searchClassOverDocBlock(ReflectionClass $reflectionClass, string $method): string
92
    {
93 7
        $docBlock = (string)$reflectionClass->getDocComment();
94
95 7
        return (new DocBlockParser())->getClassFromMethod($docBlock, $method);
96
    }
97
98
    /**
99
     * Look the uses, to find the fully-qualified class name for the className.
100
     */
101 7
    private function searchClassOverUseStatements(ReflectionClass $reflectionClass, string $className): string
102
    {
103 7
        $fileName = $reflectionClass->getFileName();
104 7
        if (!isset(static::$fileContentCache[$fileName])) {
105 7
            static::$fileContentCache[$fileName] = file_get_contents($fileName);
106
        }
107 7
        $phpFile = static::$fileContentCache[$fileName];
108
109 7
        return (new UseBlockParser())->getUseStatement($className, $phpFile);
110
    }
111
112 7
    private function normalizeResolvableType(string $resolvableType): string
113
    {
114
        /** @var list<string> $resolvableTypeParts */
115 7
        $resolvableTypeParts = explode('\\', ltrim($resolvableType, '\\'));
116 7
        $normalizedResolvableType = end($resolvableTypeParts);
117
118 7
        return is_string($normalizedResolvableType)
119 7
            ? $normalizedResolvableType
120 7
            : $resolvableType;
121
    }
122
123 8
    private function generateCacheKey(string $method): string
124
    {
125 8
        return self::class . '::' . $method;
126
    }
127
128 8
    private function createCustomServicesCache(): CustomServicesCache
129
    {
130 8
        return new CustomServicesCache($this->getCachedClassNamesDir());
131
    }
132
133 8
    private function getCachedClassNamesDir(): string
134
    {
135 8
        return Config::getInstance()->getAppRootDir() . '/';
136
    }
137
}
138