Completed
Push — develop ( fe7998...59b49f )
by Baptiste
01:41
created

Services::dependencies()   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
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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 181
    public function __construct(
33
        Arguments $arguments,
34
        Dependencies $dependencies,
35
        Service ...$definitions
36
    ) {
37 181
        $this->arguments = $arguments;
38 181
        $this->dependencies = $dependencies;
39 181
        $this->definitions = Sequence::of(...$definitions)->reduce(
40 181
            new Map('string', Service::class),
41 181
            static function(Map $definitions, Service $definition): Map {
42 178
                return $definitions->put(
43 178
                    (string) $definition->name(),
44 178
                    $definition
45
                );
46 181
            }
47
        );
48 181
        $this->exposed = $this
49 181
            ->definitions
50 181
            ->filter(static function(string $name, Service $definition): bool {
51 178
                return $definition->exposed();
52 181
            })
53 181
            ->map(static function(string $name, Service $definition): Pair {
54 66
                return new Pair(
55 66
                    (string) $definition->exposedAs(),
56 66
                    $definition
57
                );
58 181
            });
59
60 181
        $this->building = Stream::of('string');
61 181
        $this->instances = new Map('string', 'object');
62 181
    }
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 3
                    $this->decorate($decorator, $stacked->last()->name())
93
                );
94 3
            }
95
        );
96 3
        $stacked = $stacked->add(
97 3
            $this->decorate($highest, $stacked->last()->name(), $name)
98
        );
99
100 3
        $self = clone $this;
101 3
        $self->definitions = $stacked->reduce(
102 3
            $self->definitions,
103 3
            static function(Map $definitions, Service $service): Map {
104 3
                return $definitions->put(
105 3
                    (string) $service->name(),
106 3
                    $service
107
                );
108 3
            }
109
        );
110
111 3
        return $self;
112
    }
113
114
    /**
115
     * This method must only be called from Dependencies::bind() through self::inject()
116
     *
117
     * The goal is to iteratively replace in Services the initial Dependency
118
     * instance by its resolved version, so other Dependency instances can rely
119
     * on it
120
     */
121 6
    public function feed(Name $dependency): self
122
    {
123 6
        $self = clone $this;
124 6
        $self->dependencies = $self->dependencies->feed($dependency, $self);
125
126 6
        return $self;
127
    }
128
129
    /**
130
     * @param MapInterface<string, mixed> $arguments
131
     */
132 35
    public function inject(MapInterface $arguments): self
133
    {
134 35
        $self = clone $this;
135 35
        $self->arguments = $self->arguments->bind($arguments);
136 35
        $self->building = $self->building->clear();
137 35
        $self->instances = $self->instances->clear();
138
139 35
        return $self->dependencies->bind($self);
140
    }
141
142 105
    public function build(Name $name): object
143
    {
144 105
        $definition = $this->get($name);
145 105
        $name = $definition->name();
146
147 105
        if ($this->instances->contains((string) $name)) {
148 85
            return $this->instances->get((string) $name);
149
        }
150
151
        try {
152 105
            if ($this->building->contains((string) $name)) {
153 2
                throw new CircularDependency(
154
                    (string) $this
155 2
                        ->building
156 2
                        ->add((string) $name)
157 2
                        ->join(' -> ')
158
                );
159
            }
160
161 105
            $this->building = $this->building->add((string) $name);
162
163 105
            $service = $definition->build($this);
164
165 104
            $this->instances = $this->instances->put((string) $name, $service);
166 104
            $this->building = $this->building->dropEnd(1);
167 6
        } catch (\Throwable $e) {
168 6
            $this->building = $this->building->clear();
169
170 6
            throw $e;
171
        }
172
173 104
        return $service;
174
    }
175
176 35
    public function arguments(): Arguments
177
    {
178 35
        return $this->arguments;
179
    }
180
181 23
    public function dependencies(): Dependencies
182
    {
183 23
        return $this->dependencies;
184
    }
185
186 148
    public function has(Name $name): bool
187
    {
188 148
        if ($this->definitions->contains((string) $name)) {
189 122
            return true;
190
        }
191
192 38
        return $this->exposed->contains((string) $name);
193
    }
194
195 132
    public function get(Name $name): Service
196
    {
197
        try {
198 132
            return $this->definitions->get((string) $name);
199 36
        } catch (\Exception $e) {
200 36
            return $this->exposed->get((string) $name);
201
        }
202
    }
203
204 3
    private function decorate(
205
        Name $decorator,
206
        Name $decorated,
207
        Name $newName = null
208
    ): Service {
209 3
        if ($this->has($decorator)) {
210 3
            return $this->get($decorator)->decorate($decorated, $newName);
211
        }
212
213 3
        return $this->dependencies->decorate($decorator, $decorated, $newName);
214
    }
215
}
216