Passed
Push — main ( 00e164...d31815 )
by Breno
01:52
created

Container::assertString()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 7
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace Habemus;
5
6
use ArrayAccess;
7
use Habemus\Autowiring\Attributes\AttributesInjection;
8
use Habemus\Autowiring\ClassResolver;
9
use Habemus\Autowiring\ReflectionResolver;
10
use Habemus\Autowiring\Reflector;
11
use Habemus\Definition\AutoDetection;
12
use Habemus\Definition\Build\ClassDefinition;
13
use Habemus\Definition\Build\RawDefinition;
14
use Habemus\Definition\Definition;
15
use Habemus\Definition\DefinitionDetection;
16
use Habemus\Definition\DefinitionBuilder;
17
use Habemus\Definition\DefinitionList;
18
use Habemus\Definition\DefinitionResolver;
19
use Habemus\Definition\DefinitionResolverInterface;
20
use Habemus\Definition\DefinitionWrapper;
21
use Habemus\Definition\Sharing\Shareable;
22
use Habemus\Exception\ContainerException;
23
use Habemus\Exception\NotFoundException;
24
use Habemus\ServiceProvider\ServiceProvider;
25
use Habemus\ServiceProvider\ServiceProviderManager;
26
use Psr\Container\ContainerInterface;
27
28
class Container implements ContainerInterface, ArrayAccess
29
{
30
    use DefinitionBuilder;
31
32
    /**
33
     * @var CircularDependencyDetection
34
     */
35
    protected $circularDependencyDetection;
36
37
    /**
38
     * @var DefinitionList
39
     */
40
    protected $definitions;
41
42
    /**
43
     * @var ResolvedList
44
     */
45
    protected $resolved;
46
47
    /**
48
     * @var DefinitionDetection
49
     */
50
    protected $detection;
51
52
    /**
53
     * @var DefinitionResolverInterface
54
     */
55
    protected $definitionResolver;
56
57
    /**
58
     * @var ClassResolver
59
     */
60
    protected $classResolver;
61
62
    /**
63
     * @var Reflector
64
     */
65
    protected $reflector;
66
67
    /**
68
     * @var AttributesInjection
69
     */
70
    protected $attributesInjection;
71
72
    /**
73
     * @var ServiceProviderManager
74
     */
75
    protected $providers;
76
77
    /**
78
     * @var ContainerComposite
79
     */
80
    protected $delegates;
81
82
    /**
83
     * @var bool
84
     */
85
    protected $defaultShared;
86
87
    /**
88
     * @var bool
89
     */
90
    protected $useAutowire;
91
92
    /**
93
     * @var bool
94
     */
95
    protected $useAttributes;
96
97
    public function __construct()
98
    {
99
        $this->reflector = new Reflector();
100
        $this->useAutowire = true;
101
        $this->defaultShared = true;
102
        $this->useAttributes = $this->reflector->attributesAvailable();
103
104
        $this->circularDependencyDetection = new CircularDependencyDetection();
105
        $this->definitions = new DefinitionList();
106
        $this->resolved = new ResolvedList();
107
        $this->delegates = new ContainerComposite();
108
        $this->providers = new ServiceProviderManager($this);
109
        $this->attributesInjection = new AttributesInjection($this, $this->reflector);
110
        $this->classResolver = new ReflectionResolver($this, $this->attributesInjection, $this->reflector);
111
        $this->definitionResolver = new DefinitionResolver($this, $this->resolved, $this->attributesInjection);
112
        $this->detection = new AutoDetection($this, $this->classResolver);
113
114
        $this->add(ContainerInterface::class, new RawDefinition($this));
115
        $this->add(self::class, new RawDefinition($this));
116
    }
117
118
    public function add(string $id, $value = null): DefinitionWrapper
119
    {
120
        $value = $value === null && count(func_get_args()) === 1 ? $id : $value;
121
        $definition = $this->detection->detect($value);
122
        $definition->setIdentity($id);
123
124
        $this->resolved->delete($id);
125
        $this->definitions->add($definition);
126
127
        if ($definition instanceof Shareable && $definition->isShared() === null) {
128
            $definition->setShared($this->defaultShared);
129
        }
130
131
        if ($definition instanceof RawDefinition) {
132
            $this->resolved->share($id, $definition->getValue());
133
        }
134
135
        return new DefinitionWrapper($definition);
136
    }
137
138
    public function has($id): bool
139
    {
140
        $this->assertString($id);
141
        return
142
            $this->resolved->has($id)           ||
143
            $this->definitions->has($id)        ||
144
            $this->definitions->hasTag($id)     ||
145
            $this->providers->provides($id)     ||
146
            $this->delegates->has($id)          ||
147
            $this->shouldAutowireResolve($id);
148
    }
149
150
    public function get($id)
151
    {
152
        $this->assertString($id);
153
        if ($this->resolved->has($id)) {
154
            return $this->resolved->get($id);
155
        }
156
157
        return
158
            $this->circularDependencyDetection->execute($id, function () use ($id) {
159
                return $this->resolve($id);
160
            });
161
    }
162
163
    protected function resolve(string $id)
164
    {
165
        $this->providers->registerLazyProviderFor($id);
166
167
        if ($this->definitions->has($id)) {
168
            $definition = $this->definitions->get($id);
169
            return $this->definitionResolver->resolve($definition);
170
        }
171
172
        if ($this->definitions->hasTag($id)) {
173
            $tagged = $this->definitions->getTagged($id);
174
            return $this->definitionResolver->resolveMany(...$tagged);
175
        }
176
177
        if ($this->shouldAutowireResolve($id)) {
178
            $definition =
179
                (new ClassDefinition($id))
180
                    ->setIdentity($id)
181
                    ->setShared($this->defaultShared)
182
                    ->setClassResolver($this->classResolver);
183
            return $this->definitionResolver->resolve($definition);
184
        }
185
186
        if ($this->delegates->has($id)) {
187
            return $this->delegates->get($id);
188
        }
189
190
        throw NotFoundException::noEntryWasFound($id);
191
    }
192
193
    public function delete(string $id): void
194
    {
195
        $this->definitions->delete($id);
196
        $this->resolved->delete($id);
197
    }
198
199
    public function definition(string $id): DefinitionWrapper
200
    {
201
        return new DefinitionWrapper($this->definitions->get($id));
202
    }
203
204
    public function injectDependency($object)
205
    {
206
        $this->attributesInjection->inject($object);
207
    }
208
209
    public function addProvider(ServiceProvider ...$providers): self
210
    {
211
        $this->providers->add(...$providers);
212
        return $this;
213
    }
214
215
    public function addDelegate(ContainerInterface $container, ?int $priority = null): self
216
    {
217
        $this->delegates->add($container, $priority);
218
        return $this;
219
    }
220
221
    public function useDefaultShared(bool $share): self
222
    {
223
        $this->defaultShared = $share;
224
        return $this;
225
    }
226
227
    public function defaultShared(): bool
228
    {
229
        return $this->defaultShared;
230
    }
231
232
    public function autowireEnabled(): bool
233
    {
234
        return $this->useAutowire;
235
    }
236
237
    public function useAutowire(bool $enabled): self
238
    {
239
        $this->useAutowire = $enabled;
240
        return $this;
241
    }
242
243
    public function attributesEnabled(): bool
244
    {
245
        return $this->useAttributes;
246
    }
247
248
    public function useAttributes(bool $useAttributes): self
249
    {
250
        if ($useAttributes) {
251
            $this->reflector->assertAttributesAvailable();
252
        }
253
254
        $this->useAttributes = $useAttributes;
255
        return $this;
256
    }
257
258
    protected function shouldAutowireResolve($id): bool
259
    {
260
        return $this->useAutowire && $this->classResolver->canResolve($id);
261
    }
262
263
    public function offsetExists($offset): bool
264
    {
265
        return $this->has($offset);
266
    }
267
268
    public function offsetGet($offset)
269
    {
270
        return $this->get($offset);
271
    }
272
273
    public function offsetSet($offset, $value)
274
    {
275
        $this->add($offset, $value);
276
    }
277
278
    public function offsetUnset($offset)
279
    {
280
        $this->delete($offset);
281
    }
282
283
    protected function assertString($value): void
284
    {
285
        if (!is_string($value)) {
286
            throw new ContainerException(
287
                sprintf(
288
                    "Expected string. Got: %s.",
289
                    is_object($value) ? get_class($value) : gettype($value)
290
                )
291
            );
292
        }
293
    }
294
}
295