Passed
Push — feature/container-extend ( d5ded5 )
by Chema
04:29 queued 29s
created

Container::extend()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 11
rs 10
c 0
b 0
f 0
eloc 6
nc 2
nop 2
ccs 7
cts 7
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 is_callable;
13
use function is_object;
14
15
final class Container implements ContainerInterface
16
{
17
    /** @var array<string,mixed> */
18
    private array $raw = [];
19
20
    /** @var array<string,mixed> */
21
    private array $services = [];
22
23
    private SplObjectStorage $factoryServices;
24
25 22
    public function __construct()
26
    {
27 22
        $this->factoryServices = new SplObjectStorage();
28
    }
29
30 1
    public function getLocator(): Locator
31
    {
32 1
        return Locator::getInstance();
33
    }
34
35 17
    public function set(string $id, $service): void
36
    {
37 17
        $this->services[$id] = $service;
38
    }
39
40 29
    public function has(string $id): bool
41
    {
42 29
        return isset($this->services[$id]);
43
    }
44
45
    /**
46
     * @throws ContainerKeyNotFoundException
47
     *
48
     * @return mixed
49
     */
50 27
    public function get(string $id)
51
    {
52 27
        if (!$this->has($id)) {
53 5
            throw new ContainerKeyNotFoundException($this, $id);
54
        }
55
56
        if (
57 22
            isset($this->raw[$id])
58 13
            || !is_object($this->services[$id])
59 22
            || !method_exists($this->services[$id], '__invoke')
60
        ) {
61 20
            return $this->services[$id];
62
        }
63
64 8
        if (isset($this->factoryServices[$this->services[$id]])) {
65 1
            return $this->services[$id]($this);
66
        }
67
68 7
        $rawService = $this->services[$id];
69
70
        /** @psalm-suppress InvalidFunctionCall */
71 7
        $this->services[$id] = $rawService($this);
72
73
        /** @var mixed $resolvedService */
74 7
        $resolvedService = $this->services[$id];
75 7
        $this->raw[$id] = $rawService;
76
77 7
        return $resolvedService;
78
    }
79
80 2
    public function factory(object $service): object
81
    {
82 2
        if (!method_exists($service, '__invoke')) {
83 1
            throw ContainerException::serviceNotInvokable();
84
        }
85
86 1
        $this->factoryServices->attach($service);
87
88 1
        return $service;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $service returns the type object which is incompatible with the type-hinted return object.
Loading history...
89
    }
90
91 2
    public function remove(string $id): void
92
    {
93
        unset(
94 2
            $this->raw[$id],
95 2
            $this->services[$id]
96
        );
97
    }
98
99
    /**
100
     * @psalm-suppress MixedAssignment
101
     */
102 4
    public function extend(string $id, Closure $service): object
103
    {
104 4
        if (!$this->has($id)) {
105 1
            return $service;
106
        }
107
108 3
        $factory = $this->services[$id];
109 3
        $extended = $this->generateExtendedService($service, $factory);
110 2
        $this->set($id, $extended);
111
112 2
        return $extended;
113
    }
114
115
    /**
116
     * @psalm-suppress MissingClosureReturnType,MixedAssignment
117
     *
118
     * @param mixed $factory
119
     */
120 3
    private function generateExtendedService(Closure $service, $factory): Closure
121
    {
122 3
        if (!is_callable($factory) && is_object($factory)) {
123 1
            return static function (self $container) use ($service, $factory) {
124 1
                $r = $service($factory, $container);
125
126 1
                return $r ?? $factory;
127
            };
128
        }
129
130 3
        if (is_callable($factory)) {
131 2
            return static function (self $container) use ($service, $factory) {
132 2
                $r1 = $factory($container);
133 2
                $r2 = $service($r1, $container);
134
135 2
                return $r2 ?? $r1;
136
            };
137
        }
138
139 1
        throw ContainerException::serviceNotExtendable();
140
    }
141
}
142