Passed
Push — develop ( efe27a...afc25d )
by Baptiste
01:56
created

Services::get()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
declare(strict_types = 1);
3
4
namespace Innmind\Compose;
5
6
use Innmind\Compose\{
7
    Definition\Name,
8
    Definition\Service,
9
    Definition\Service\Argument,
10
    Definition\Dependency,
11
    Exception\ArgumentNotProvided,
12
    Exception\ArgumentNotDefined,
13
    Exception\CircularDependency
14
};
15
use Innmind\Immutable\{
16
    Sequence,
17
    MapInterface,
18
    Map,
19
    Pair,
20
    Stream
21
};
22
23
final class Services
24
{
25
    private $definitions;
26
    private $exposed;
27
    private $arguments;
28
    private $dependencies;
29
    private $building;
30
    private $instances;
31
32 163
    public function __construct(
33
        Arguments $arguments,
34
        Dependencies $dependencies,
35
        Service ...$definitions
36
    ) {
37 163
        $this->arguments = $arguments;
38 163
        $this->dependencies = $dependencies;
39 163
        $this->definitions = Sequence::of(...$definitions)->reduce(
40 163
            new Map('string', Service::class),
41 163
            static function(Map $definitions, Service $definition): Map {
42 161
                return $definitions->put(
43 161
                    (string) $definition->name(),
44 161
                    $definition
45
                );
46 163
            }
47
        );
48 163
        $this->exposed = $this
49 163
            ->definitions
50 163
            ->filter(static function(string $name, Service $definition): bool {
51 161
                return $definition->exposed();
52 163
            })
53 163
            ->map(static function(string $name, Service $definition): Pair {
54 55
                return new Pair(
55 55
                    (string) $definition->exposedAs(),
56 55
                    $definition
57
                );
58 163
            });
59
60 163
        $this->building = Stream::of('string');
61 163
        $this->instances = new Map('string', 'object');
62 163
    }
63
64 3
    public function expose(Name $name, Name $as): self
65
    {
66 3
        $service = $this->get($name)->exposeAs($as);
67
68 3
        $self = clone $this;
69 3
        $self->definitions = $self->definitions->put(
70 3
            (string) $name,
71 3
            $service
72
        );
73 3
        $self->exposed = $self->exposed->put(
74 3
            (string) $as,
75 3
            $service
76
        );
77
78 3
        return $self;
79
    }
80
81 3
    public function stack(Name $name, Name $highest, Name $lower, Name ...$rest): self
82
    {
83 3
        $stack = Sequence::of($lower, ...$rest)->reverse();
84 3
        $stacked = Sequence::of(
85 3
            $this->get($stack->first())
86
        );
87
88 3
        $stacked = $stack->drop(1)->reduce(
89 3
            $stacked,
90 3
            function(Sequence $stacked, Name $decorator): Sequence {
91 3
                return $stacked->add(
92
                    $this
93 3
                        ->get($decorator)
94 3
                        ->decorate($stacked->last()->name())
95
                );
96 3
            }
97
        );
98 3
        $stacked = $stacked->add(
99
            $this
100 3
                ->get($highest)
101 3
                ->decorate($stacked->last()->name(), $name)
102
        );
103
104 3
        $self = clone $this;
105 3
        $self->definitions = $stacked->reduce(
106 3
            $self->definitions,
107 3
            static function(Map $definitions, Service $service): Map {
108 3
                return $definitions->put(
109 3
                    (string) $service->name(),
110 3
                    $service
111
                );
112 3
            }
113
        );
114
115 3
        return $self;
116
    }
117
118
    /**
119
     * This method must only be called from Dependencies::bind() through self::inject()
120
     *
121
     * The goal is to iteratively replace in Services the initial Dependency
122
     * instance by its resolved version, so other Dependency instances can rely
123
     * on it
124
     */
125 4
    public function feed(Name $dependency): self
126
    {
127 4
        $self = clone $this;
128 4
        $self->dependencies = $self->dependencies->feed($dependency, $self);
129
130 4
        return $self;
131
    }
132
133
    /**
134
     * @param MapInterface<string, mixed> $arguments
135
     */
136 35
    public function inject(MapInterface $arguments): self
137
    {
138 35
        $self = clone $this;
139 35
        $self->arguments = $self->arguments->bind($arguments);
140 35
        $self->building = $self->building->clear();
141 35
        $self->instances = $self->instances->clear();
142
143 35
        return $self->dependencies->bind($self);
144
    }
145
146 104
    public function build(Name $name): object
147
    {
148 104
        $definition = $this->get($name);
149 104
        $name = $definition->name();
150
151 104
        if ($this->instances->contains((string) $name)) {
152 85
            return $this->instances->get((string) $name);
153
        }
154
155
        try {
156 104
            if ($this->building->contains((string) $name)) {
157 2
                throw new CircularDependency(
158
                    (string) $this
159 2
                        ->building
160 2
                        ->add((string) $name)
161 2
                        ->join(' -> ')
162
                );
163
            }
164
165 104
            $this->building = $this->building->add((string) $name);
166
167 104
            $service = $definition->build($this);
168
169 103
            $this->instances = $this->instances->put((string) $name, $service);
170 103
            $this->building = $this->building->dropEnd(1);
171 6
        } catch (\Throwable $e) {
172 6
            $this->building = $this->building->clear();
173
174 6
            throw $e;
175
        }
176
177 103
        return $service;
178
    }
179
180 34
    public function arguments(): Arguments
181
    {
182 34
        return $this->arguments;
183
    }
184
185 21
    public function dependencies(): Dependencies
186
    {
187 21
        return $this->dependencies;
188
    }
189
190 131
    public function has(Name $name): bool
191
    {
192 131
        if ($this->definitions->contains((string) $name)) {
193 116
            return true;
194
        }
195
196 25
        return $this->exposed->contains((string) $name);
197
    }
198
199 119
    public function get(Name $name): Service
200
    {
201
        try {
202 119
            return $this->definitions->get((string) $name);
203 27
        } catch (\Exception $e) {
204 27
            return $this->exposed->get((string) $name);
205
        }
206
    }
207
}
208