Passed
Push — develop ( d83a0e...c7823f )
by Baptiste
02:04
created

Services::all()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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\Service\Constructor,
11
    Definition\Dependency,
12
    Compilation,
13
    Exception\ArgumentNotProvided,
14
    Exception\ArgumentNotDefined,
15
    Exception\CircularDependency,
16
    Exception\ServiceNotFound,
17
};
18
use Innmind\Immutable\{
19
    Sequence,
20
    MapInterface,
21
    Map,
22
    Pair,
23
    Stream,
24
    SetInterface,
25
    Set
26
};
27
28
final class Services
29
{
30
    private $definitions;
31
    private $exposed;
32
    private $arguments;
33
    private $dependencies;
34
    private $building;
35
    private $instances;
36
37 201
    public function __construct(
38
        Arguments $arguments,
39
        Dependencies $dependencies,
40
        Service ...$definitions
41
    ) {
42 201
        $this->arguments = $arguments;
43 201
        $this->dependencies = $dependencies;
44 201
        $this->definitions = Sequence::of(...$definitions)->reduce(
45 201
            new Map('string', Service::class),
46 201
            static function(Map $definitions, Service $definition): Map {
47 195
                return $definitions->put(
48 195
                    (string) $definition->name(),
49 195
                    $definition
50
                );
51 201
            }
52
        );
53 201
        $this->exposed = $this
54 201
            ->definitions
55 201
            ->filter(static function(string $name, Service $definition): bool {
56 195
                return $definition->exposed();
57 201
            })
58 201
            ->map(static function(string $name, Service $definition): Pair {
59 77
                return new Pair(
60 77
                    (string) $definition->exposedAs(),
61 77
                    $definition
62
                );
63 201
            });
64
65 201
        $this->building = Stream::of('string');
66 201
        $this->instances = new Map('string', 'object');
67 201
    }
68
69 8
    public function expose(Name $name, Name $as): self
70
    {
71 8
        $service = $this->get($name)->exposeAs($as);
72
73 8
        $self = clone $this;
74 8
        $self->definitions = $self->definitions->put(
75 8
            (string) $name,
76 8
            $service
77
        );
78 8
        $self->exposed = $self->exposed->put(
79 8
            (string) $as,
80 8
            $service
81
        );
82
83 8
        return $self;
84
    }
85
86 9
    public function stack(Name $name, Name $highest, Name $lower, Name ...$rest): self
87
    {
88 9
        $stack = Sequence::of($highest, $lower, ...$rest)->reverse();
89 9
        $stacked = Sequence::of(
90 9
            $this->decorate(
91 9
                $stack->get(1),
92 9
                $stack->first(),
93 9
                $stack->size() === 2 ? $name : null
94
            )
95
        );
96
97 9
        $stacked = $stack->drop(2)->dropEnd(1)->reduce(
98 9
            $stacked,
99 9
            function(Sequence $stacked, Name $decorator): Sequence {
100 1
                return $stacked->add(
101 1
                    $this->decorate($decorator, $stacked->last()->name())
102
                );
103 9
            }
104
        );
105
106 9
        if ($stack->size() > 2) {
107 8
            $stacked = $stacked->add(
108 8
                $this->decorate($highest, $stacked->last()->name(), $name)
109
            );
110
        }
111
112 9
        $self = clone $this;
113 9
        $self->definitions = $stacked->reduce(
114 9
            $self->definitions,
115 9
            static function(Map $definitions, Service $service): Map {
116 9
                return $definitions->put(
117 9
                    (string) $service->name(),
118 9
                    $service
119
                );
120 9
            }
121
        );
122
123 9
        return $self;
124
    }
125
126
    /**
127
     * This method must only be called from Dependencies::bind() through self::inject()
128
     *
129
     * The goal is to iteratively replace in Services the initial Dependency
130
     * instance by its resolved version, so other Dependency instances can rely
131
     * on it
132
     */
133 6
    public function feed(Name $dependency): self
134
    {
135 6
        $self = clone $this;
136 6
        $self->dependencies = $self->dependencies->feed($dependency, $self);
137
138 6
        return $self;
139
    }
140
141
    /**
142
     * @param MapInterface<string, mixed> $arguments
143
     */
144 36
    public function inject(MapInterface $arguments): self
145
    {
146 36
        $self = clone $this;
147 36
        $self->arguments = $self->arguments->bind($arguments);
148 36
        $self->building = $self->building->clear();
149 36
        $self->instances = $self->instances->clear();
150
151 36
        return $self->dependencies->bind($self);
152
    }
153
154 106
    public function build(Name $name): object
155
    {
156 106
        $definition = $this->get($name);
157 106
        $name = $definition->name();
158
159 106
        if ($this->instances->contains((string) $name)) {
160 85
            return $this->instances->get((string) $name);
161
        }
162
163
        try {
164 106
            if ($this->building->contains((string) $name)) {
165 2
                throw new CircularDependency(
166
                    (string) $this
167 2
                        ->building
168 2
                        ->add((string) $name)
169 2
                        ->join(' -> ')
170
                );
171
            }
172
173 106
            $this->building = $this->building->add((string) $name);
174
175 106
            $service = $definition->build($this);
176
177 105
            $this->instances = $this->instances->put((string) $name, $service);
178 105
            $this->building = $this->building->dropEnd(1);
179 6
        } catch (\Throwable $e) {
180 6
            $this->building = $this->building->clear();
181
182 6
            throw $e;
183
        }
184
185 105
        return $service;
186
    }
187
188 44
    public function arguments(): Arguments
189
    {
190 44
        return $this->arguments;
191
    }
192
193 32
    public function dependencies(): Dependencies
194
    {
195 32
        return $this->dependencies;
196
    }
197
198 154
    public function has(Name $name): bool
199
    {
200 154
        if ($this->definitions->contains((string) $name)) {
201 128
            return true;
202
        }
203
204 44
        return $this->exposed->contains((string) $name);
205
    }
206
207 139
    public function get(Name $name): Service
208
    {
209
        try {
210 139
            return $this->definitions->get((string) $name);
211 43
        } catch (\Exception $e) {
212
            //pass
213
        }
214
215
        try {
216 43
            return $this->exposed->get((string) $name);
217 1
        } catch (\Exception $e) {
218
            //pass
219
        }
220
221 1
        throw new ServiceNotFound((string) $name);
222
    }
223
224
    /**
225
     * The list of exposed services name with their constructor
226
     *
227
     * @return MapInterface<Name, Constructor>
228
     */
229 10
    public function exposed(): MapInterface
230
    {
231 10
        return $this->exposed->reduce(
232 10
            new Map(Name::class, Constructor::class),
233 10
            static function(Map $exposed, string $name, Service $service): Map {
234 10
                return $exposed->put(
235 10
                    $service->exposedAs(),
236 10
                    $service->constructor()
237
                );
238 10
            }
239
        );
240
    }
241
242
    /**
243
     * @return SetInterface<Service>
244
     */
245 9
    public function all(): SetInterface
246
    {
247 9
        return Set::of(Service::class, ...$this->definitions->values());
248
    }
249
250 7
    public function compile(): Compilation\Services
251
    {
252 7
        return new Compilation\Services($this);
253
    }
254
255 9
    private function decorate(
256
        Name $decorator,
257
        Name $decorated,
258
        Name $newName = null
259
    ): Service {
260 9
        if ($this->has($decorator)) {
261 9
            return $this->get($decorator)->decorate($decorated, $newName);
262
        }
263
264 8
        return $this->dependencies->decorate($decorator, $decorated, $newName);
265
    }
266
}
267