Passed
Pull Request — master (#234)
by Chema
06:34 queued 03:12
created

Container::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
ccs 3
cts 3
cp 1
crap 1
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 1
            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 29
        $this->frozenServices[$id] = true;
80
81
        if (
82 29
            isset($this->raw[$id])
83 20
            || !is_object($this->services[$id])
84 29
            || !method_exists($this->services[$id], '__invoke')
85
        ) {
86 21
            return $this->services[$id];
87
        }
88
89 14
        if (isset($this->factoryServices[$this->services[$id]])) {
90 1
            return $this->services[$id]($this);
91
        }
92
93 13
        $rawService = $this->services[$id];
94
95
        /** @psalm-suppress InvalidFunctionCall */
96 13
        $this->services[$id] = $rawService($this);
97
98
        /** @var mixed $resolvedService */
99 13
        $resolvedService = $this->services[$id];
100 13
        $this->raw[$id] = $rawService;
101
102 13
        return $resolvedService;
103
    }
104
105 2
    public function factory(object $service): object
106
    {
107 2
        if (!method_exists($service, '__invoke')) {
108 1
            throw ContainerException::serviceNotInvokable();
109
        }
110
111 1
        $this->factoryServices->attach($service);
112
113 1
        return $service;
114
    }
115
116 2
    public function remove(string $id): void
117
    {
118
        unset(
119 2
            $this->raw[$id],
120 2
            $this->services[$id],
121 2
            $this->frozenServices[$id],
122
        );
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected ')' on line 122 at column 8
Loading history...
123
    }
124
125
    /**
126
     * @psalm-suppress MixedAssignment
127
     */
128 9
    public function extend(string $id, Closure $service): Closure
129
    {
130 9
        if (!$this->has($id)) {
131 3
            $this->extendLater($id, $service);
132
133 3
            return $service;
134
        }
135
136 8
        if (isset($this->frozenServices[$id])) {
137 4
            throw ContainerException::serviceFrozen($id);
138
        }
139
140 6
        $factory = $this->services[$id];
141 6
        $extended = $this->generateExtendedService($service, $factory);
142 5
        $this->set($id, $extended);
143
144 5
        return $extended;
145
    }
146
147 3
    private function extendLater(string $id, Closure $service): void
148
    {
149 3
        if (!isset($this->servicesToExtend[$id])) {
150 3
            $this->servicesToExtend[$id] = [];
151
        }
152
153 3
        $this->servicesToExtend[$id][] = $service;
154
    }
155
156
    /**
157
     * @psalm-suppress MissingClosureReturnType,MixedAssignment
158
     *
159
     * @param mixed $factory
160
     */
161 6
    private function generateExtendedService(Closure $service, $factory): Closure
162
    {
163 6
        if (is_callable($factory)) {
164 5
            return static function (self $container) use ($service, $factory) {
165 4
                $r1 = $factory($container);
166 4
                $r2 = $service($r1, $container);
167
168 4
                return $r2 ?? $r1;
169
            };
170
        }
171
172 4
        if (is_object($factory)) {
173 3
            return static function (self $container) use ($service, $factory) {
174 3
                $r = $service($factory, $container);
175
176 3
                return $r ?? $factory;
177
            };
178
        }
179
180 1
        throw ContainerException::serviceNotExtendable();
181
    }
182
183 23
    private function extendService(string $id): void
184
    {
185 23
        if (!isset($this->servicesToExtend[$id]) || count($this->servicesToExtend[$id]) === 0) {
186 23
            return;
187
        }
188 3
        $this->currentlyExtending = $id;
189
190 3
        foreach ($this->servicesToExtend[$id] as $service) {
191 3
            $extended = $this->extend($id, $service);
192
        }
193
194 3
        unset($this->servicesToExtend[$id]);
195 3
        $this->currentlyExtending = null;
196
197 3
        $this->set($id, $extended);
198
    }
199
}
200