Passed
Push — feature/233-frozen-used-servic... ( 09599e )
by Chema
04:50
created

Container::extendLater()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 7
rs 10
ccs 4
cts 4
cp 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework\Container;
6
7
use Closure;
8
use Gacela\Framework\Container\Exception\ContainerException;
9
use Gacela\Framework\Container\Exception\ContainerKeyNotFoundException;
10
use SplObjectStorage;
11
12
use function count;
13
use function is_callable;
14
use function is_object;
15
16
final class Container implements ContainerInterface
17
{
18
    /** @var array<string,mixed> */
19
    private array $raw = [];
20
21
    /** @var array<string,mixed> */
22
    private array $services = [];
23
24
    private SplObjectStorage $factoryServices;
25
26
    /** @var array<string,list<Closure>> */
27
    private array $servicesToExtend;
28
29
    /** @var array<string,bool> */
30
    private array $frozenServices = [];
31
32
    private ?string $currentlyExtending = null;
33
34
    /**
35
     * @param array<string,list<Closure>> $servicesToExtend
36
     */
37 28
    public function __construct(array $servicesToExtend = [])
38
    {
39 28
        $this->servicesToExtend = $servicesToExtend;
40 28
        $this->factoryServices = new SplObjectStorage();
41
    }
42
43 1
    public function getLocator(): Locator
44
    {
45 1
        return Locator::getInstance();
46
    }
47
48 23
    public function set(string $id, $service): void
49
    {
50 23
        if (isset($this->frozenServices[$id])) {
51 2
            throw ContainerException::serviceFrozen($id);
52
        }
53
54 23
        $this->services[$id] = $service;
55
56 23
        if ($this->currentlyExtending === $id) {
57 3
            return;
58
        }
59
60 23
        $this->extendService($id);
61
    }
62
63 36
    public function has(string $id): bool
64
    {
65 36
        return isset($this->services[$id]);
66
    }
67
68
    /**
69
     * @throws ContainerKeyNotFoundException
70
     *
71
     * @return mixed
72
     */
73 34
    public function get(string $id)
74
    {
75 34
        if (!$this->has($id)) {
76 5
            throw new ContainerKeyNotFoundException($this, $id);
77
        }
78
79
        if (
80 29
            isset($this->raw[$id])
81 20
            || !is_object($this->services[$id])
82 29
            || !method_exists($this->services[$id], '__invoke')
83
        ) {
84 20
            return $this->services[$id];
85
        }
86
87 15
        if (isset($this->factoryServices[$this->services[$id]])) {
88 1
            return $this->services[$id]($this);
89
        }
90
91 14
        $rawService = $this->services[$id];
92
93
        /** @psalm-suppress InvalidFunctionCall */
94 14
        $this->services[$id] = $rawService($this);
95
96
        /** @var mixed $resolvedService */
97 14
        $resolvedService = $this->services[$id];
98 14
        $this->raw[$id] = $rawService;
99 14
        $this->frozenServices[$id] = true;
100
101 14
        return $resolvedService;
102
    }
103
104 2
    public function factory(object $service): object
105
    {
106 2
        if (!method_exists($service, '__invoke')) {
107 1
            throw ContainerException::serviceNotInvokable();
108
        }
109
110 1
        $this->factoryServices->attach($service);
111
112 1
        return $service;
113
    }
114
115 2
    public function remove(string $id): void
116
    {
117
        unset(
118 2
            $this->raw[$id],
119 2
            $this->services[$id],
120 2
            $this->frozenServices[$id],
121
        );
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected ')' on line 121 at column 8
Loading history...
122
    }
123
124
    /**
125
     * @psalm-suppress MixedAssignment
126
     */
127 9
    public function extend(string $id, Closure $service): Closure
128
    {
129 9
        if (!$this->has($id)) {
130 3
            $this->extendLater($id, $service);
131
132 3
            return $service;
133
        }
134
135 8
        if (isset($this->frozenServices[$id])) {
136 2
            throw ContainerException::serviceFrozen($id);
137
        }
138
139 7
        $factory = $this->services[$id];
140 7
        $extended = $this->generateExtendedService($service, $factory);
141 6
        $this->set($id, $extended);
142
143 6
        return $extended;
144
    }
145
146 3
    private function extendLater(string $id, Closure $service): void
147
    {
148 3
        if (!isset($this->servicesToExtend[$id])) {
149 3
            $this->servicesToExtend[$id] = [];
150
        }
151
152 3
        $this->servicesToExtend[$id][] = $service;
153
    }
154
155
    /**
156
     * @psalm-suppress MissingClosureReturnType,MixedAssignment
157
     *
158
     * @param mixed $factory
159
     */
160 7
    private function generateExtendedService(Closure $service, $factory): Closure
161
    {
162 7
        if (is_callable($factory)) {
163 5
            return static function (self $container) use ($service, $factory) {
164 4
                $r1 = $factory($container);
165 4
                $r2 = $service($r1, $container);
166
167 4
                return $r2 ?? $r1;
168
            };
169
        }
170
171 5
        if (is_object($factory)) {
172 4
            return static function (self $container) use ($service, $factory) {
173 4
                $r = $service($factory, $container);
174
175 4
                return $r ?? $factory;
176
            };
177
        }
178
179 1
        throw ContainerException::serviceNotExtendable();
180
    }
181
182 23
    private function extendService(string $id): void
183
    {
184 23
        if (!isset($this->servicesToExtend[$id]) || count($this->servicesToExtend[$id]) === 0) {
185 23
            return;
186
        }
187 3
        $this->currentlyExtending = $id;
188
189 3
        foreach ($this->servicesToExtend[$id] as $service) {
190 3
            $extended = $this->extend($id, $service);
191
        }
192
193 3
        unset($this->servicesToExtend[$id]);
194 3
        $this->currentlyExtending = null;
195
196 3
        $this->set($id, $extended);
197
    }
198
}
199