Passed
Branch main (8d1023)
by Chema
01:53
created

Container::alias()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Container;
6
7
use Closure;
8
use Gacela\Container\Exception\ContainerException;
9
10
use function get_class;
11
use function is_array;
12
use function is_object;
13
use function is_string;
14
15
class Container implements ContainerInterface
16
{
17
    private AliasRegistry $aliasRegistry;
18
19
    private FactoryManager $factoryManager;
20
21
    private InstanceRegistry $instanceRegistry;
22
23
    private DependencyCacheManager $cacheManager;
24
25
    private BindingResolver $bindingResolver;
26
27
    private DependencyTreeAnalyzer $dependencyTreeAnalyzer;
28
29
    /**
30
     * @param  array<class-string, class-string|callable|object>  $bindings
31
     * @param  array<string, list<Closure>>  $instancesToExtend
32
     */
33 61
    public function __construct(
34
        array $bindings = [],
35
        array $instancesToExtend = [],
36
    ) {
37 61
        $this->aliasRegistry = new AliasRegistry();
38 61
        $this->factoryManager = new FactoryManager($instancesToExtend);
39 61
        $this->instanceRegistry = new InstanceRegistry();
40 61
        $this->bindingResolver = new BindingResolver($bindings);
41 61
        $this->cacheManager = new DependencyCacheManager($bindings);
42 61
        $this->dependencyTreeAnalyzer = new DependencyTreeAnalyzer($this->bindingResolver);
43
    }
44
45
    /**
46
     * @param  class-string  $className
47
     */
48 7
    public static function create(string $className): mixed
49
    {
50 7
        return (new self())->get($className);
51
    }
52
53 50
    public function has(string $id): bool
54
    {
55 50
        $id = $this->aliasRegistry->resolve($id);
56 50
        return $this->instanceRegistry->has($id);
57
    }
58
59 27
    public function set(string $id, mixed $instance): void
60
    {
61 27
        $this->instanceRegistry->set($id, $instance);
62
63 27
        if ($this->factoryManager->isCurrentlyExtending($id)) {
64 3
            return;
65
        }
66
67 27
        $this->extendService($id);
68
    }
69
70
    /**
71
     * @param  class-string|string  $id
72
     */
73 44
    public function get(string $id): mixed
74
    {
75 44
        $id = $this->aliasRegistry->resolve($id);
76
77 44
        if ($this->has($id)) {
78 20
            return $this->instanceRegistry->get($id, $this->factoryManager, $this);
79
        }
80
81 24
        return $this->createInstance($id);
82
    }
83
84 5
    public function resolve(callable $callable): mixed
85
    {
86 5
        $callableKey = $this->callableKey($callable);
87 5
        $closure = Closure::fromCallable($callable);
88
89 5
        $dependencies = $this->cacheManager->resolveCallableDependencies($callableKey, $closure);
90
91
        /** @psalm-suppress MixedMethodCall */
92 5
        return $closure(...$dependencies);
93
    }
94
95 4
    public function factory(Closure $instance): Closure
96
    {
97 4
        $this->factoryManager->markAsFactory($instance);
98
99 4
        return $instance;
100
    }
101
102 2
    public function remove(string $id): void
103
    {
104 2
        $id = $this->aliasRegistry->resolve($id);
105 2
        $this->instanceRegistry->remove($id);
106
    }
107
108 4
    public function alias(string $alias, string $id): void
109
    {
110 4
        $this->aliasRegistry->add($alias, $id);
111
    }
112
113
    /**
114
     * @param class-string $className
115
     *
116
     * @return list<string>
117
     */
118 4
    public function getDependencyTree(string $className): array
119
    {
120 4
        return $this->dependencyTreeAnalyzer->analyze($className);
121
    }
122
123
    /**
124
     * @psalm-suppress MixedAssignment
125
     */
126 12
    public function extend(string $id, Closure $instance): Closure
127
    {
128 12
        $id = $this->aliasRegistry->resolve($id);
129
130 12
        if (!$this->has($id)) {
131 4
            $this->factoryManager->scheduleExtension($id, $instance);
132
133 4
            return $instance;
134
        }
135
136 11
        if ($this->instanceRegistry->isFrozen($id)) {
137 4
            throw ContainerException::frozenInstanceExtend($id);
138
        }
139
140 9
        $factory = $this->instanceRegistry->getRaw($id);
141
142 9
        if ($this->factoryManager->isProtected($factory)) {
143 1
            throw ContainerException::instanceProtected($id);
144
        }
145
146 8
        $extended = $this->factoryManager->generateExtendedInstance($instance, $factory, $this);
147 7
        $this->set($id, $extended);
148
149 7
        $this->factoryManager->transferFactoryStatus($factory, $extended);
150
151 7
        return $extended;
152
    }
153
154 2
    public function protect(Closure $instance): Closure
155
    {
156 2
        $this->factoryManager->markAsProtected($instance);
157
158 2
        return $instance;
159
    }
160
161
    /**
162
     * @return list<string>
163
     */
164 1
    public function getRegisteredServices(): array
165
    {
166 1
        return $this->instanceRegistry->getAll();
167
    }
168
169 2
    public function isFactory(string $id): bool
170
    {
171 2
        $id = $this->aliasRegistry->resolve($id);
172
173 2
        if (!$this->has($id)) {
174 1
            return false;
175
        }
176
177 2
        return $this->factoryManager->isFactory($this->instanceRegistry->getRaw($id));
178
    }
179
180 2
    public function isFrozen(string $id): bool
181
    {
182 2
        $id = $this->aliasRegistry->resolve($id);
183 2
        return $this->instanceRegistry->isFrozen($id);
184
    }
185
186
    /**
187
     * @return array<class-string, class-string|callable|object>
188
     */
189 1
    public function getBindings(): array
190
    {
191 1
        return $this->bindingResolver->getBindings();
192
    }
193
194
    /**
195
     * @param list<class-string> $classNames
196
     */
197 2
    public function warmUp(array $classNames): void
198
    {
199 2
        $this->cacheManager->warmUp($classNames);
200
    }
201
202 24
    private function createInstance(string $class): ?object
203
    {
204 24
        return $this->bindingResolver->resolve($class, $this->cacheManager);
205
    }
206
207
    /**
208
     * Generates a unique string key for a given callable.
209
     *
210
     * @psalm-suppress MixedReturnTypeCoercion
211
     */
212 5
    private function callableKey(callable $callable): string
213
    {
214 5
        if (is_array($callable)) {
215
            [$classOrObject, $method] = $callable;
216
217
            $className = is_object($classOrObject)
218
                ? get_class($classOrObject) . '#' . spl_object_id($classOrObject)
219
                : $classOrObject;
220
221
            return $className . '::' . $method;
222
        }
223
224 5
        if (is_string($callable)) {
225
            return $callable;
226
        }
227
228 5
        if ($callable instanceof Closure) {
229 5
            return spl_object_hash($callable);
230
        }
231
232
        // Invokable objects
233
        /** @psalm-suppress RedundantCondition */
234
        if (is_object($callable)) {
235
            return get_class($callable) . '#' . spl_object_id($callable);
236
        }
237
238
        // Fallback for edge cases
239
        /** @psalm-suppress MixedArgument */
240
        return 'callable:' . md5(serialize($callable));
241
    }
242
243 27
    private function extendService(string $id): void
244
    {
245 27
        if (!$this->factoryManager->hasPendingExtensions($id)) {
246 24
            return;
247
        }
248
249 3
        $this->factoryManager->setCurrentlyExtending($id);
250
251 3
        foreach ($this->factoryManager->getPendingExtensions($id) as $instance) {
252 3
            $this->extend($id, $instance);
253
        }
254
255 3
        $this->factoryManager->clearPendingExtensions($id);
256 3
        $this->factoryManager->setCurrentlyExtending(null);
257
    }
258
}
259