AnonymousGlobal   A
last analyzed

Complexity

Total Complexity 15

Size/Duplication

Total Lines 114
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 15
eloc 37
dl 0
loc 114
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A resetCache() 0 3 1
A addGlobal() 0 11 2
A createCacheKey() 0 3 1
A addCachedGlobalInstance() 0 3 1
A validateTypeForAnonymousGlobalRegistration() 0 7 2
A extractContextNameFromContext() 0 13 3
A getByClassName() 0 8 1
A getGlobalKeyFromClassName() 0 3 1
A getByKey() 0 9 2
A overrideExistingResolvedClass() 0 5 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework\ClassResolver\GlobalInstance;
6
7
use Gacela\Framework\ClassResolver\ClassInfo;
8
use Gacela\Framework\ClassResolver\GlobalKey;
9
use Gacela\Framework\ClassResolver\ResolvableType;
10
use RuntimeException;
11
12
use function in_array;
13
use function is_string;
14
use function sprintf;
15
16
/**
17
 * @internal
18
 */
19
final class AnonymousGlobal
20
{
21
    private const ALLOWED_TYPES_FOR_ANONYMOUS_GLOBAL = [
22
        'Config',
23
        'Factory',
24
        'Provider',
25
    ];
26
27
    /** @var array<string,object> */
28
    private static array $cachedGlobalInstances = [];
29
30
    /**
31
     * @internal
32
     */
33
    public static function resetCache(): void
34
    {
35
        self::$cachedGlobalInstances = [];
36
    }
37
38
    /**
39
     * @template T
40
     *
41
     * @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...
42
     *
43
     * @return ?T
44
     *
45
     * @internal so the Locator can access to the global instances before creating a new instance
46
     */
47
    public static function getByClassName(string $className)
48
    {
49
        $key = self::getGlobalKeyFromClassName($className);
50
51
        /** @var ?T $instance */
52
        $instance = self::getByKey($key); // @phpstan-ignore-line
53
54
        return $instance;
55
    }
56
57
    public static function getByKey(string $key): ?object
58
    {
59
        if (isset(self::$cachedGlobalInstances[$key])) {
60
            return self::$cachedGlobalInstances[$key];
61
        }
62
63
        $normalizedKey = '\\' . ltrim($key, '\\');
64
65
        return self::$cachedGlobalInstances[$normalizedKey] ?? null;
66
    }
67
68
    /**
69
     * Add an anonymous class as 'Config', 'Factory' or 'Provider' as a global resource
70
     * bound to the context that it's pass as first argument. It can be the string-key
71
     * (from a non-class/file context) or the class/object itself.
72
     */
73
    public static function addGlobal(object|string $context, object $resolvedClass): void
74
    {
75
        $contextName = self::extractContextNameFromContext($context);
76
        $parentClass = get_parent_class($resolvedClass);
77
        $type = is_string($parentClass)
0 ignored issues
show
introduced by
The condition is_string($parentClass) is always true.
Loading history...
78
            ? ResolvableType::fromClassName($parentClass)->resolvableType()
79
            : $contextName;
80
81
        self::validateTypeForAnonymousGlobalRegistration($type);
82
        $key = self::createCacheKey($contextName, $type);
83
        self::addCachedGlobalInstance($key, $resolvedClass);
84
    }
85
86
    public static function createCacheKey(string $contextName, string $type): string
87
    {
88
        return sprintf('\%s\%s\%s', ClassInfo::MODULE_NAME_ANONYMOUS, $contextName, $type);
89
    }
90
91
    public static function overrideExistingResolvedClass(string $className, object $resolvedClass): void
92
    {
93
        $key = self::getGlobalKeyFromClassName($className);
94
95
        self::addCachedGlobalInstance($key, $resolvedClass);
96
    }
97
98
    private static function extractContextNameFromContext(object|string $context): string
99
    {
100
        if (is_string($context)) {
101
            return $context;
102
        }
103
104
        $callerClass = $context::class;
105
        /** @var list<string> $callerClassParts */
106
        $callerClassParts = explode('\\', ltrim($callerClass, '\\'));
107
108
        $lastCallerClassParts = end($callerClassParts);
109
110
        return is_string($lastCallerClassParts) ? $lastCallerClassParts : '';
111
    }
112
113
    private static function validateTypeForAnonymousGlobalRegistration(string $type): void
114
    {
115
        if (!in_array($type, self::ALLOWED_TYPES_FOR_ANONYMOUS_GLOBAL, true)) {
116
            throw new RuntimeException(
117
                sprintf("Type '%s' not allowed. Valid types: ", $type) . implode(
118
                    ', ',
119
                    self::ALLOWED_TYPES_FOR_ANONYMOUS_GLOBAL,
120
                ),
121
            );
122
        }
123
    }
124
125
    private static function addCachedGlobalInstance(string $key, object $resolvedClass): void
126
    {
127
        self::$cachedGlobalInstances[$key] = $resolvedClass;
128
    }
129
130
    private static function getGlobalKeyFromClassName(string $className): string
131
    {
132
        return GlobalKey::fromClassName($className);
133
    }
134
}
135