Container::autowireEnabled()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 1
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Habemus;
5
6
use ArrayAccess;
7
use Closure;
8
use Habemus\Autowiring\Attributes\AttributesInjection;
9
use Habemus\Autowiring\Parameter\ParameterResolverChain;
10
use Habemus\Autowiring\ReflectionClassResolver;
11
use Habemus\Autowiring\Reflector;
12
use Habemus\Definition\AutoDetection;
13
use Habemus\Definition\Build\ClassDefinition;
14
use Habemus\Definition\Build\RawDefinition;
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\ContainerExceptionInterface;
27
use Psr\Container\ContainerInterface;
28
use Psr\Container\NotFoundExceptionInterface;
29
use ReflectionException;
30
use ReflectionFunction;
31
use ReflectionMethod;
32
33
class Container implements ContainerInterface, ArrayAccess
34
{
35
    use DefinitionBuilder;
36
37
    /**
38
     * @var CircularDependencyDetection
39
     */
40
    protected $circularDependencyDetection;
41
42
    /**
43
     * @var DefinitionList
44
     */
45
    protected $definitions;
46
47
    /**
48
     * @var ResolvedList
49
     */
50
    protected $resolved;
51
52
    /**
53
     * @var DefinitionDetection
54
     */
55
    protected $detection;
56
57
    /**
58
     * @var DefinitionResolverInterface
59
     */
60
    protected $definitionResolver;
61
62
    /**
63
     * @var ReflectionClassResolver
64
     */
65
    protected $classResolver;
66
67
    /**
68
     * @var Reflector
69
     */
70
    protected $reflector;
71
72
    /**
73
     * @var AttributesInjection
74
     */
75
    protected $attributesInjection;
76
77
    /**
78
     * @var ServiceProviderManager
79
     */
80
    protected $providers;
81
82
    /**
83
     * @var ContainerComposite
84
     */
85
    protected $delegates;
86
87
    /**
88
     * @var bool
89
     */
90
    protected $defaultShared;
91
92
    /**
93
     * @var bool
94
     */
95
    protected $useAutowire;
96
97
    /**
98
     * @var bool
99
     */
100
    protected $useAttributes;
101
102
    public function __construct()
103
    {
104
        $this->reflector = new Reflector();
105
        $this->useAutowire = true;
106
        $this->defaultShared = true;
107
        $this->useAttributes = $this->reflector->attributesAvailable();
108
109
        $this->circularDependencyDetection = new CircularDependencyDetection();
110
        $this->definitions = new DefinitionList();
111
        $this->resolved = new ResolvedList();
112
        $this->delegates = new ContainerComposite();
113
        $this->providers = new ServiceProviderManager($this);
114
        $this->attributesInjection = new AttributesInjection($this, $this->reflector);
115
        $this->classResolver = new ReflectionClassResolver(
116
            ParameterResolverChain::default($this, $this->attributesInjection, $this->reflector)
117
        );
118
        $this->definitionResolver = new DefinitionResolver($this, $this->resolved, $this->attributesInjection);
119
        $this->detection = new AutoDetection($this, $this->classResolver);
120
121
        $this->add(ContainerInterface::class, new RawDefinition($this));
122
        $this->add(self::class, new RawDefinition($this));
123
    }
124
125
    public function add(string $id, $value = null): DefinitionWrapper
126
    {
127
        $value = $value === null && count(func_get_args()) === 1 ? $id : $value;
128
        $definition = $this->detection->detect($value);
129
        $definition->setIdentity($id);
130
131
        $this->resolved->delete($id);
132
        $this->definitions->add($definition);
133
134
        if ($definition instanceof Shareable && $definition->isShared() === null) {
135
            $definition->setShared($this->defaultShared);
136
        }
137
138
        if ($definition instanceof RawDefinition) {
139
            $this->resolved->share($id, $definition->getValue());
140
        }
141
142
        return new DefinitionWrapper($definition);
143
    }
144
145
    public function has($id): bool
146
    {
147
        $this->assertString($id);
148
        return
149
            $this->resolved->has($id)           ||
150
            $this->definitions->has($id)        ||
151
            $this->definitions->hasTag($id)     ||
152
            $this->providers->provides($id)     ||
153
            $this->delegates->has($id)          ||
154
            $this->shouldAutowireResolve($id);
155
    }
156
157
    public function get($id)
158
    {
159
        $this->assertString($id);
160
        if ($this->resolved->has($id)) {
161
            return $this->resolved->get($id);
162
        }
163
164
        return
165
            $this->circularDependencyDetection->execute($id, function () use ($id) {
166
                return $this->resolve($id);
167
            });
168
    }
169
170
    protected function resolve(string $id)
171
    {
172
        $this->providers->registerLazyProviderFor($id);
173
174
        if ($this->definitions->has($id)) {
175
            $definition = $this->definitions->get($id);
176
            return $this->definitionResolver->resolve($definition);
177
        }
178
179
        if ($this->definitions->hasTag($id)) {
180
            $tagged = $this->definitions->getTagged($id);
181
            return $this->definitionResolver->resolveMany(...$tagged);
182
        }
183
184
        if ($this->shouldAutowireResolve($id)) {
185
            $definition =
186
                (new ClassDefinition($id))
187
                    ->setIdentity($id)
188
                    ->setShared($this->defaultShared)
189
                    ->setClassResolver($this->classResolver);
190
            return $this->definitionResolver->resolve($definition);
191
        }
192
193
        if ($this->delegates->has($id)) {
194
            return $this->delegates->get($id);
195
        }
196
197
        throw NotFoundException::noEntryWasFound($id);
198
    }
199
200
    public function delete(string $id): void
201
    {
202
        $this->definitions->delete($id);
203
        $this->resolved->delete($id);
204
    }
205
206
    public function definition(string $id): DefinitionWrapper
207
    {
208
        return new DefinitionWrapper($this->definitions->get($id));
209
    }
210
211
    public function injectDependency($object)
212
    {
213
        $this->attributesInjection->inject($object);
214
    }
215
216
    /**
217
     * @param callable|string|array $target
218
     * @param array $args
219
     * @return mixed
220
     * @throws Exception\UnresolvableParameterException
221
     * @throws ReflectionException
222
     * @throws ContainerExceptionInterface
223
     */
224
    public function invoke($target, array $args = [])
225
    {
226
        if (is_callable($target)) {
227
            $reflectionFunction = new ReflectionFunction(Closure::fromCallable($target));
228
            $arguments = $this->classResolver->resolveArguments($reflectionFunction, $args);
229
230
            return $reflectionFunction->invokeArgs($arguments);
231
        }
232
233
        if (is_string($target) && class_exists($target) && method_exists($target, '__invoke')) {
234
            $target = [$target, "__invoke"];
235
        }
236
237
        if (is_array($target) && count($target) === 2) {
238
            list($objectOrClass, $method) = $target;
239
            $reflectionMethod = new ReflectionMethod($objectOrClass, $method);
240
            $arguments = $this->classResolver->resolveArguments($reflectionMethod, $args);
241
242
            if ($reflectionMethod->isStatic()) {
243
                $objectOrClass = null;
244
            } elseif (is_string($objectOrClass) && $this->has($objectOrClass)) {
245
                $objectOrClass = $this->get($objectOrClass);
246
            }
247
248
            return $reflectionMethod->invokeArgs($objectOrClass, $arguments);
249
        }
250
251
        $targetName = is_string($target) ? $target : gettype($target);
252
        throw new ContainerException('Target is not invokable: ' . $targetName);
253
    }
254
255
    public function addProvider(ServiceProvider ...$providers): self
256
    {
257
        $this->providers->add(...$providers);
258
        return $this;
259
    }
260
261
    public function addDelegate(ContainerInterface $container, ?int $priority = null): self
262
    {
263
        $this->delegates->add($container, $priority);
264
        return $this;
265
    }
266
267
    public function useDefaultShared(bool $share): self
268
    {
269
        $this->defaultShared = $share;
270
        return $this;
271
    }
272
273
    public function defaultShared(): bool
274
    {
275
        return $this->defaultShared;
276
    }
277
278
    public function autowireEnabled(): bool
279
    {
280
        return $this->useAutowire;
281
    }
282
283
    public function useAutowire(bool $enabled): self
284
    {
285
        $this->useAutowire = $enabled;
286
        return $this;
287
    }
288
289
    public function attributesEnabled(): bool
290
    {
291
        return $this->useAttributes;
292
    }
293
294
    public function useAttributes(bool $useAttributes): self
295
    {
296
        if ($useAttributes) {
297
            $this->reflector->assertAttributesAvailable();
298
        }
299
300
        $this->useAttributes = $useAttributes;
301
        return $this;
302
    }
303
304
    protected function shouldAutowireResolve($id): bool
305
    {
306
        return $this->useAutowire && $this->classResolver->canResolve($id);
307
    }
308
309
    public function offsetExists($offset): bool
310
    {
311
        return $this->has($offset);
312
    }
313
314
    /**
315
     * @param mixed $offset
316
     * @return mixed
317
     * @throws ContainerExceptionInterface
318
     * @throws NotFoundExceptionInterface
319
     */
320
    #[\ReturnTypeWillChange]
321
    public function offsetGet($offset)
322
    {
323
        return $this->get($offset);
324
    }
325
326
    public function offsetSet($offset, $value): void
327
    {
328
        $this->add($offset, $value);
329
    }
330
331
    public function offsetUnset($offset): void
332
    {
333
        $this->delete($offset);
334
    }
335
336
    protected function assertString($value): void
337
    {
338
        if (!is_string($value)) {
339
            throw new ContainerException(
340
                sprintf(
341
                    "Expected string. Got: %s.",
342
                    is_object($value) ? get_class($value) : gettype($value)
343
                )
344
            );
345
        }
346
    }
347
}
348