Passed
Push — develop ( 7d2249...4ccadf )
by Baptiste
02:09
created

Services   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 217
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 217
ccs 108
cts 108
cp 1
rs 10
c 0
b 0
f 0
wmc 19

13 Methods

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