Passed
Pull Request — master (#63)
by Jens
08:42
created

AbstractClassResolver   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 188
Duplicated Lines 0 %

Test Coverage

Coverage 98.75%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 25
eloc 67
c 5
b 0
f 0
dl 0
loc 188
ccs 79
cts 80
cp 0.9875
rs 10

14 Methods

Rating   Name   Duplication   Size   Complexity  
A overrideExistingResolvedClass() 0 5 1
A addAnonymousGlobal() 0 13 2
A extractContextNameFromContext() 0 13 3
A doResolve() 0 21 4
A getCacheKey() 0 3 1
A getDependencyResolver() 0 12 2
A getClassNameFinder() 0 7 2
A createInstance() 0 11 2
A resolveGlobal() 0 11 2
A findClassName() 0 5 1
A getGlobalKeyFromClassName() 0 3 1
A getCachedGlobalInstance() 0 10 1
A validateTypeForAnonymousGlobalRegistration() 0 5 2
A addCachedGlobalInstance() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework\ClassResolver;
6
7
use Gacela\Framework\ClassResolver\ClassNameFinder\ClassNameFinderInterface;
8
use Gacela\Framework\ClassResolver\DependencyResolver\DependencyResolver;
9
use Gacela\Framework\Config;
10
use RuntimeException;
11
use function get_class;
12
use function in_array;
13
use function is_string;
14
use function sprintf;
15
16
abstract class AbstractClassResolver
17
{
18
    private const ALLOWED_TYPES_FOR_ANONYMOUS_GLOBAL = ['Config', 'Factory', 'DependencyProvider'];
19
20
    /** @var array<string,null|object> */
21
    private static array $cachedInstances = [];
22
23
    /** @var array<string,object> */
24
    private static array $cachedGlobalInstances = [];
25
26
    private static ?ClassNameFinderInterface $classNameFinder = null;
27
28
    private ?DependencyResolver $dependencyResolver = null;
29
30
    abstract public function resolve(object $callerClass): ?object;
31
32
    abstract protected function getResolvableType(): string;
33
34
    /**
35
     * Add an anonymous class as 'Config', 'Factory' or 'DependencyProvider' as a global resource
36
     * bound to the context that it's pass as first argument. It can be the string-key
37
     * (from a non-class/file context) or the class/object itself.
38
     *
39
     * @param object|string $context
40
     */
41 5
    public static function addAnonymousGlobal($context, object $resolvedClass): void
42
    {
43 5
        $contextName = self::extractContextNameFromContext($context);
44 5
        $parentClass = get_parent_class($resolvedClass);
45
46 5
        $type = is_string($parentClass)
0 ignored issues
show
introduced by
The condition is_string($parentClass) is always true.
Loading history...
47 4
            ? ResolvableType::fromClassName($parentClass)->resolvableType()
48 5
            : $contextName;
49
50 5
        self::validateTypeForAnonymousGlobalRegistration($type);
51
52 4
        $key = sprintf('\%s\%s\%s', ClassInfo::MODULE_NAME_ANONYMOUS, $contextName, $type);
53 4
        self::addCachedGlobalInstance($key, $resolvedClass);
54 4
    }
55
56
    /**
57
     * @param object|string $context
58
     */
59 5
    private static function extractContextNameFromContext($context): string
60
    {
61 5
        if (is_string($context)) {
62 1
            return $context;
63
        }
64
65 5
        $callerClass = get_class($context);
66
        /** @var list<string> $callerClassParts */
67 5
        $callerClassParts = explode('\\', ltrim($callerClass, '\\'));
68
69 5
        $lastCallerClassParts = end($callerClassParts);
70
71 5
        return is_string($lastCallerClassParts) ? $lastCallerClassParts : '';
72
    }
73
74 4
    public static function overrideExistingResolvedClass(string $className, object $resolvedClass): void
75
    {
76 4
        $key = self::getGlobalKeyFromClassName($className);
77
78 4
        self::addCachedGlobalInstance($key, $resolvedClass);
79 4
    }
80
81
    /**
82
     * @template T
83
     *
84
     * @param class-string<T> $className
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
85
     *
86
     * @internal so the Locator can access to the global instances before creating a new instance
87
     *
88
     * @return ?T
89
     */
90 8
    public static function getCachedGlobalInstance(string $className)
91
    {
92 8
        $key = self::getGlobalKeyFromClassName($className);
93
94
        /** @var ?T $instance */
95 8
        $instance = self::$cachedGlobalInstances[$key]
96 4
            ?? self::$cachedGlobalInstances['\\' . $key]
97 8
            ?? null;
98
99 8
        return $instance;
100
    }
101
102 8
    private static function getGlobalKeyFromClassName(string $className): string
103
    {
104 8
        return GlobalKey::fromClassName($className);
105
    }
106
107 5
    private static function validateTypeForAnonymousGlobalRegistration(string $type): void
108
    {
109 5
        if (!in_array($type, self::ALLOWED_TYPES_FOR_ANONYMOUS_GLOBAL)) {
110 1
            throw new RuntimeException(
111 1
                "Type '$type' not allowed. Valid types: " . implode(', ', self::ALLOWED_TYPES_FOR_ANONYMOUS_GLOBAL)
112
            );
113
        }
114 4
    }
115
116 8
    private static function addCachedGlobalInstance(string $key, object $resolvedClass): void
117
    {
118 8
        self::$cachedGlobalInstances[$key] = $resolvedClass;
119 8
    }
120
121 20
    public function doResolve(object $callerClass): ?object
122
    {
123 20
        $classInfo = new ClassInfo($callerClass);
124 20
        $cacheKey = $this->getCacheKey($classInfo);
125 20
        if (isset(self::$cachedInstances[$cacheKey])) {
126 4
            return self::$cachedInstances[$cacheKey];
127
        }
128
129 16
        $resolvedClass = $this->resolveGlobal($cacheKey);
130 16
        if (null !== $resolvedClass) {
131 1
            return $resolvedClass;
132
        }
133
134 15
        $resolvedClassName = $this->findClassName($classInfo);
135 15
        if (null === $resolvedClassName) {
136 3
            return null;
137
        }
138
139 14
        self::$cachedInstances[$cacheKey] = $this->createInstance($resolvedClassName);
140
141 14
        return self::$cachedInstances[$cacheKey];
142
    }
143
144 16
    private function resolveGlobal(string $cacheKey): ?object
145
    {
146 16
        $resolvedClass = self::$cachedGlobalInstances[$cacheKey] ?? null;
147
148 16
        if (null === $resolvedClass) {
149 15
            return null;
150
        }
151
152 1
        self::$cachedInstances[$cacheKey] = $resolvedClass;
153
154 1
        return self::$cachedInstances[$cacheKey];
155
    }
156
157 20
    private function getCacheKey(ClassInfo $classInfo): string
158
    {
159 20
        return $classInfo->getCacheKey($this->getResolvableType());
160
    }
161
162 15
    private function findClassName(ClassInfo $classInfo): ?string
163
    {
164 15
        return $this->getClassNameFinder()->findClassName(
165 15
            $classInfo,
166 15
            $this->getResolvableType()
167
        );
168
    }
169
170 15
    private function getClassNameFinder(): ClassNameFinderInterface
171
    {
172 15
        if (null === self::$classNameFinder) {
173 1
            self::$classNameFinder = (new ClassResolverFactory())->createClassNameFinder();
174
        }
175
176 15
        return self::$classNameFinder;
177
    }
178
179 14
    private function createInstance(string $resolvedClassName): ?object
180
    {
181 14
        if (class_exists($resolvedClassName)) {
182 14
            $dependencies = $this->getDependencyResolver()
183 14
                ->resolveDependencies($resolvedClassName);
184
185
            /** @psalm-suppress MixedMethodCall */
186 14
            return new $resolvedClassName(...$dependencies);
187
        }
188
189
        return null;
190
    }
191
192 14
    private function getDependencyResolver(): DependencyResolver
193
    {
194 14
        if (null === $this->dependencyResolver) {
195 14
            $gacelaFileConfig = Config::getInstance()
196 14
                ->getFactory()
197 14
                ->createGacelaConfigFileFactory()
198 14
                ->createGacelaFileConfig();
199
200 14
            $this->dependencyResolver = new DependencyResolver($gacelaFileConfig);
201
        }
202
203 14
        return $this->dependencyResolver;
204
    }
205
}
206