Passed
Pull Request — master (#55)
by Jesús
03:20
created

AbstractClassResolver   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 177
Duplicated Lines 0 %

Test Coverage

Coverage 98.72%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 65
dl 0
loc 177
rs 10
c 5
b 0
f 0
ccs 77
cts 78
cp 0.9872
wmc 24

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getCacheKey() 0 3 1
A getClassNameFinder() 0 7 2
A resolveGlobal() 0 11 2
A findClassName() 0 5 1
A overrideExistingResolvedClass() 0 5 1
A getGlobalKeyFromClassName() 0 3 1
A validateTypeForAnonymousGlobalRegistration() 0 5 2
A extractContextNameFromContext() 0 11 2
A addAnonymousGlobal() 0 13 2
A getDependencyResolver() 0 12 2
A createInstance() 0 11 2
A getCachedGlobalInstance() 0 7 1
A addCachedGlobalInstance() 0 3 1
A doResolve() 0 21 4
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
        return end($callerClassParts);
70
    }
71
72 4
    public static function overrideExistingResolvedClass(string $className, object $resolvedClass): void
73
    {
74 4
        $key = self::getGlobalKeyFromClassName($className);
75
76 4
        self::addCachedGlobalInstance($key, $resolvedClass);
77 4
    }
78
79
    /**
80
     * @internal so the Locator can access to the global instances before creating a new instance
81
     */
82 8
    public static function getCachedGlobalInstance(string $className): ?object
83
    {
84 8
        $key = self::getGlobalKeyFromClassName($className);
85
86 8
        return self::$cachedGlobalInstances[$key]
87 4
            ?? self::$cachedGlobalInstances['\\' . $key]
88 8
            ?? null;
89
    }
90
91 8
    private static function getGlobalKeyFromClassName(string $className): string
92
    {
93 8
        return GlobalKey::fromClassName($className);
94
    }
95
96 5
    private static function validateTypeForAnonymousGlobalRegistration(string $type): void
97
    {
98 5
        if (!in_array($type, self::ALLOWED_TYPES_FOR_ANONYMOUS_GLOBAL)) {
99 1
            throw new RuntimeException(
100 1
                "Type '$type' not allowed. Valid types: " . implode(', ', self::ALLOWED_TYPES_FOR_ANONYMOUS_GLOBAL)
101
            );
102
        }
103 4
    }
104
105 8
    private static function addCachedGlobalInstance(string $key, object $resolvedClass): void
106
    {
107 8
        self::$cachedGlobalInstances[$key] = $resolvedClass;
108 8
    }
109
110 19
    public function doResolve(object $callerClass): ?object
111
    {
112 19
        $classInfo = new ClassInfo($callerClass);
113 19
        $cacheKey = $this->getCacheKey($classInfo);
114 19
        if (isset(self::$cachedInstances[$cacheKey])) {
115 5
            return self::$cachedInstances[$cacheKey];
116
        }
117
118 14
        $resolvedClass = $this->resolveGlobal($cacheKey);
119 14
        if (null !== $resolvedClass) {
120 1
            return $resolvedClass;
121
        }
122
123 13
        $resolvedClassName = $this->findClassName($classInfo);
124 13
        if (null === $resolvedClassName) {
125 3
            return null;
126
        }
127
128 12
        self::$cachedInstances[$cacheKey] = $this->createInstance($resolvedClassName);
129
130 12
        return self::$cachedInstances[$cacheKey];
131
    }
132
133 14
    private function resolveGlobal(string $cacheKey): ?object
134
    {
135 14
        $resolvedClass = self::$cachedGlobalInstances[$cacheKey] ?? null;
136
137 14
        if (null === $resolvedClass) {
138 13
            return null;
139
        }
140
141 1
        self::$cachedInstances[$cacheKey] = $resolvedClass;
142
143 1
        return self::$cachedInstances[$cacheKey];
144
    }
145
146 19
    private function getCacheKey(ClassInfo $classInfo): string
147
    {
148 19
        return $classInfo->getCacheKey($this->getResolvableType());
149
    }
150
151 13
    private function findClassName(ClassInfo $classInfo): ?string
152
    {
153 13
        return $this->getClassNameFinder()->findClassName(
154 13
            $classInfo,
155 13
            $this->getResolvableType()
156
        );
157
    }
158
159 13
    private function getClassNameFinder(): ClassNameFinderInterface
160
    {
161 13
        if (null === self::$classNameFinder) {
162 1
            self::$classNameFinder = (new ClassResolverFactory())->createClassNameFinder();
163
        }
164
165 13
        return self::$classNameFinder;
166
    }
167
168 12
    private function createInstance(string $resolvedClassName): ?object
169
    {
170 12
        if (class_exists($resolvedClassName)) {
171 12
            $dependencies = $this->getDependencyResolver()
172 12
                ->resolveDependencies($resolvedClassName);
173
174
            /** @psalm-suppress MixedMethodCall */
175 12
            return new $resolvedClassName(...$dependencies);
176
        }
177
178
        return null;
179
    }
180
181 12
    private function getDependencyResolver(): DependencyResolver
182
    {
183 12
        if (null === $this->dependencyResolver) {
184 12
            $gacelaFileConfig = Config::getInstance()
185 12
                ->getFactory()
186 12
                ->createGacelaConfigFileFactory()
187 12
                ->createGacelaFileConfig();
188
189 12
            $this->dependencyResolver = new DependencyResolver($gacelaFileConfig);
190
        }
191
192 12
        return $this->dependencyResolver;
193
    }
194
}
195