Passed
Push — feature/remove-raw-container ( c29d67...e5c5e2 )
by Chema
07:36 queued 04:14
created

Container::generateExtendedService()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

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