Passed
Push — main ( 0d74ac...5fac52 )
by Breno
10:24 queued 11s
created

Container::invoke()   C

Complexity

Conditions 14
Paths 21

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 19
nc 21
nop 2
dl 0
loc 31
rs 6.2666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\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\Definition;
16
use Habemus\Definition\DefinitionDetection;
17
use Habemus\Definition\DefinitionBuilder;
18
use Habemus\Definition\DefinitionList;
19
use Habemus\Definition\DefinitionResolver;
20
use Habemus\Definition\DefinitionResolverInterface;
21
use Habemus\Definition\DefinitionWrapper;
22
use Habemus\Definition\Sharing\Shareable;
23
use Habemus\Exception\ContainerException;
24
use Habemus\Exception\NotFoundException;
25
use Habemus\ServiceProvider\ServiceProvider;
26
use Habemus\ServiceProvider\ServiceProviderManager;
27
use Psr\Container\ContainerInterface;
28
29
class Container implements ContainerInterface, ArrayAccess
30
{
31
    use DefinitionBuilder;
32
33
    /**
34
     * @var CircularDependencyDetection
35
     */
36
    protected $circularDependencyDetection;
37
38
    /**
39
     * @var DefinitionList
40
     */
41
    protected $definitions;
42
43
    /**
44
     * @var ResolvedList
45
     */
46
    protected $resolved;
47
48
    /**
49
     * @var DefinitionDetection
50
     */
51
    protected $detection;
52
53
    /**
54
     * @var DefinitionResolverInterface
55
     */
56
    protected $definitionResolver;
57
58
    /**
59
     * @var ClassResolver
60
     */
61
    protected $classResolver;
62
63
    /**
64
     * @var Reflector
65
     */
66
    protected $reflector;
67
68
    /**
69
     * @var AttributesInjection
70
     */
71
    protected $attributesInjection;
72
73
    /**
74
     * @var ServiceProviderManager
75
     */
76
    protected $providers;
77
78
    /**
79
     * @var ContainerComposite
80
     */
81
    protected $delegates;
82
83
    /**
84
     * @var bool
85
     */
86
    protected $defaultShared;
87
88
    /**
89
     * @var bool
90
     */
91
    protected $useAutowire;
92
93
    /**
94
     * @var bool
95
     */
96
    protected $useAttributes;
97
98
    public function __construct()
99
    {
100
        $this->reflector = new Reflector();
101
        $this->useAutowire = true;
102
        $this->defaultShared = true;
103
        $this->useAttributes = $this->reflector->attributesAvailable();
104
105
        $this->circularDependencyDetection = new CircularDependencyDetection();
106
        $this->definitions = new DefinitionList();
107
        $this->resolved = new ResolvedList();
108
        $this->delegates = new ContainerComposite();
109
        $this->providers = new ServiceProviderManager($this);
110
        $this->attributesInjection = new AttributesInjection($this, $this->reflector);
111
        $this->classResolver = new ReflectionClassResolver(
112
            ParameterResolverChain::default($this, $this->attributesInjection, $this->reflector)
113
        );
114
        $this->definitionResolver = new DefinitionResolver($this, $this->resolved, $this->attributesInjection);
115
        $this->detection = new AutoDetection($this, $this->classResolver);
116
117
        $this->add(ContainerInterface::class, new RawDefinition($this));
118
        $this->add(self::class, new RawDefinition($this));
119
    }
120
121
    public function add(string $id, $value = null): DefinitionWrapper
122
    {
123
        $value = $value === null && count(func_get_args()) === 1 ? $id : $value;
124
        $definition = $this->detection->detect($value);
125
        $definition->setIdentity($id);
126
127
        $this->resolved->delete($id);
128
        $this->definitions->add($definition);
129
130
        if ($definition instanceof Shareable && $definition->isShared() === null) {
131
            $definition->setShared($this->defaultShared);
132
        }
133
134
        if ($definition instanceof RawDefinition) {
135
            $this->resolved->share($id, $definition->getValue());
136
        }
137
138
        return new DefinitionWrapper($definition);
139
    }
140
141
    public function has($id): bool
142
    {
143
        $this->assertString($id);
144
        return
145
            $this->resolved->has($id)           ||
146
            $this->definitions->has($id)        ||
147
            $this->definitions->hasTag($id)     ||
148
            $this->providers->provides($id)     ||
149
            $this->delegates->has($id)          ||
150
            $this->shouldAutowireResolve($id);
151
    }
152
153
    public function get($id)
154
    {
155
        $this->assertString($id);
156
        if ($this->resolved->has($id)) {
157
            return $this->resolved->get($id);
158
        }
159
160
        return
161
            $this->circularDependencyDetection->execute($id, function () use ($id) {
162
                return $this->resolve($id);
163
            });
164
    }
165
166
    protected function resolve(string $id)
167
    {
168
        $this->providers->registerLazyProviderFor($id);
169
170
        if ($this->definitions->has($id)) {
171
            $definition = $this->definitions->get($id);
172
            return $this->definitionResolver->resolve($definition);
173
        }
174
175
        if ($this->definitions->hasTag($id)) {
176
            $tagged = $this->definitions->getTagged($id);
177
            return $this->definitionResolver->resolveMany(...$tagged);
178
        }
179
180
        if ($this->shouldAutowireResolve($id)) {
181
            $definition =
182
                (new ClassDefinition($id))
183
                    ->setIdentity($id)
184
                    ->setShared($this->defaultShared)
185
                    ->setClassResolver($this->classResolver);
186
            return $this->definitionResolver->resolve($definition);
187
        }
188
189
        if ($this->delegates->has($id)) {
190
            return $this->delegates->get($id);
191
        }
192
193
        throw NotFoundException::noEntryWasFound($id);
194
    }
195
196
    public function delete(string $id): void
197
    {
198
        $this->definitions->delete($id);
199
        $this->resolved->delete($id);
200
    }
201
202
    public function definition(string $id): DefinitionWrapper
203
    {
204
        return new DefinitionWrapper($this->definitions->get($id));
205
    }
206
207
    public function injectDependency($object)
208
    {
209
        $this->attributesInjection->inject($object);
210
    }
211
212
    /**
213
     * @param string|array|\Closure $target
214
     * @param array $args
215
     * @return mixed
216
     * @throws Exception\UnresolvableParameterException
217
     * @throws \ReflectionException
218
     */
219
    public function invoke($target, array $args = [])
220
    {
221
        if ((is_string($target) && function_exists($target)) || $target instanceof \Closure) {
222
            $reflectionFunction = new \ReflectionFunction($target);
223
            $arguments = $this->classResolver->resolveArguments($reflectionFunction, $args);
0 ignored issues
show
Bug introduced by
The method resolveArguments() does not exist on Habemus\Autowiring\ClassResolver. Since it exists in all sub-types, consider adding an abstract or default implementation to Habemus\Autowiring\ClassResolver. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

223
            /** @scrutinizer ignore-call */ 
224
            $arguments = $this->classResolver->resolveArguments($reflectionFunction, $args);
Loading history...
224
            return $reflectionFunction->invokeArgs($arguments);
225
        }
226
227
        if (is_object($target) && method_exists($target, '__invoke')) {
0 ignored issues
show
introduced by
The condition is_object($target) is always false.
Loading history...
228
            $target = [$target, "__invoke"];
229
        }
230
231
        if (is_string($target) && class_exists($target)) {
232
            $target = [$target, "__invoke"];
233
        }
234
235
        if (is_array($target) && count($target) === 2) {
236
            list($objectOrClass, $method) = $target;
237
            $reflectionMethod = new \ReflectionMethod($objectOrClass, $method);
238
            $arguments = $this->classResolver->resolveArguments($reflectionMethod, $args);
239
240
            if ($reflectionMethod->isStatic()) {
241
                $objectOrClass = null;
242
            } elseif (is_string($objectOrClass) && $this->has($objectOrClass)) {
243
                $objectOrClass = $this->get($objectOrClass);
244
            }
245
246
            return $reflectionMethod->invokeArgs($objectOrClass, $arguments);
247
        }
248
        $targetName = is_string($target) ? $target : gettype($target);
249
        throw new ContainerException('Target is not invokable: ' . $targetName);
250
    }
251
252
    public function addProvider(ServiceProvider ...$providers): self
253
    {
254
        $this->providers->add(...$providers);
255
        return $this;
256
    }
257
258
    public function addDelegate(ContainerInterface $container, ?int $priority = null): self
259
    {
260
        $this->delegates->add($container, $priority);
261
        return $this;
262
    }
263
264
    public function useDefaultShared(bool $share): self
265
    {
266
        $this->defaultShared = $share;
267
        return $this;
268
    }
269
270
    public function defaultShared(): bool
271
    {
272
        return $this->defaultShared;
273
    }
274
275
    public function autowireEnabled(): bool
276
    {
277
        return $this->useAutowire;
278
    }
279
280
    public function useAutowire(bool $enabled): self
281
    {
282
        $this->useAutowire = $enabled;
283
        return $this;
284
    }
285
286
    public function attributesEnabled(): bool
287
    {
288
        return $this->useAttributes;
289
    }
290
291
    public function useAttributes(bool $useAttributes): self
292
    {
293
        if ($useAttributes) {
294
            $this->reflector->assertAttributesAvailable();
295
        }
296
297
        $this->useAttributes = $useAttributes;
298
        return $this;
299
    }
300
301
    protected function shouldAutowireResolve($id): bool
302
    {
303
        return $this->useAutowire && $this->classResolver->canResolve($id);
304
    }
305
306
    public function offsetExists($offset): bool
307
    {
308
        return $this->has($offset);
309
    }
310
311
    public function offsetGet($offset)
312
    {
313
        return $this->get($offset);
314
    }
315
316
    public function offsetSet($offset, $value)
317
    {
318
        $this->add($offset, $value);
319
    }
320
321
    public function offsetUnset($offset)
322
    {
323
        $this->delete($offset);
324
    }
325
326
    protected function assertString($value): void
327
    {
328
        if (!is_string($value)) {
329
            throw new ContainerException(
330
                sprintf(
331
                    "Expected string. Got: %s.",
332
                    is_object($value) ? get_class($value) : gettype($value)
333
                )
334
            );
335
        }
336
    }
337
}
338