Passed
Push — sam-rework ( a7ef4a...16881f )
by Alexander
19:47 queued 04:49
created

Container   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 324
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 35
eloc 75
dl 0
loc 324
rs 9.6
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
A getId() 0 3 2
A buildPrimitive() 0 9 2
A hasInstance() 0 5 1
A has() 0 3 1
A buildInternal() 0 10 3
A resolveDependencies() 0 9 2
A set() 0 4 1
A addProvider() 0 8 2
A resolve() 0 6 2
A buildProvider() 0 10 2
A setMultiple() 0 4 2
A initObject() 0 7 2
A build() 0 16 2
A registerProviderIfDeferredFor() 0 10 3
A getInstances() 0 3 1
A getInjector() 0 7 2
A getDefinition() 0 3 1
A get() 0 10 2
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\di;
9
10
use Psr\Container\ContainerInterface;
11
use SplObjectStorage;
12
use yii\di\contracts\DeferredServiceProvider;
13
use yii\di\contracts\Definition;
14
use yii\di\contracts\ServiceProvider;
15
use yii\di\definitions\ArrayDefinition;
16
use yii\di\definitions\Normalizer;
17
use yii\di\exceptions\CircularReferenceException;
18
use yii\di\exceptions\InvalidConfigException;
19
use yii\di\exceptions\NotFoundException;
20
use yii\di\exceptions\NotInstantiableException;
21
22
/**
23
 * Container implements a [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection) container.
24
 */
25
class Container implements ContainerInterface
26
{
27
    /**
28
     * @var Definition[] object definitions indexed by their types
29
     */
30
    private $definitions = [];
31
    /**
32
     * @var array used to collect ids instantiated during build
33
     * to detect circular references
34
     */
35
    private $building = [];
36
    /**
37
     * @var contracts\DeferredServiceProvider[]|\SplObjectStorage list of providers
38
     * deferred to register till their services would be requested
39
     */
40
    private $deferredProviders;
41
    /**
42
     * @var Injector injector with this container.
43
     */
44
    private $injector;
45
46
    /**
47
     * @var object[]
48
     */
49
    private $instances;
50
51
    /** @var ?ContainerInterface */
52
    private $parentContainer;
53
54
    /**
55
     * Container constructor.
56
     *
57
     * @param array $definitions
58
     * @param ServiceProvider[] $providers
59
     *
60
     * @param ContainerInterface|null $parentContainer
61
     * @throws InvalidConfigException
62
     * @throws NotInstantiableException
63
     */
64
    public function __construct(
65
        array $definitions = [],
66
        array $providers = [],
67
        ?ContainerInterface $parentContainer = null
68
    ) {
69
        $this->parentContainer = $parentContainer;
70
        $this->setMultiple($definitions);
71
        $this->deferredProviders = new SplObjectStorage();
72
        foreach ($providers as $provider) {
73
            $this->addProvider($provider);
74
        }
75
    }
76
77
    /**
78
     * Returns an instance by either interface name or alias.
79
     *
80
     * Same instance of the class will be returned each time this method is called.
81
     *
82
     * @param string|Reference $id the interface or an alias name that was previously registered via [[set()]].
83
     * @return object an instance of the requested interface.
84
     * @throws CircularReferenceException
85
     * @throws InvalidConfigException
86
     * @throws NotFoundException
87
     * @throws NotInstantiableException
88
     */
89
    public function get($id, array $params = [])
90
    {
91
        $id = $this->getId($id);
92
        if (!isset($this->instances[$id])) {
93
            $object = $this->build($id, $params);
94
            $this->instances[$id] = $object;
95
            $this->initObject($object);
96
        }
97
98
        return $this->instances[$id];
99
    }
100
101
    public function getId($id): string
102
    {
103
        return is_string($id) ? $id : $id->getId();
104
    }
105
106
    /**
107
     * Returns normalized definition for a given id
108
     * @param string $id
109
     * @return Definition|null
110
     */
111
    public function getDefinition(string $id): ?Definition
112
    {
113
        return $this->definitions[$id] ?? null;
114
    }
115
116
    /**
117
     * Creates new instance by either interface name or alias.
118
     *
119
     * @param string $id the interface or an alias name that was previously registered via [[set()]].
120
     * @param array $params
121
     * @return object new built instance of the specified class.
122
     * @throws CircularReferenceException
123
     * @throws InvalidConfigException
124
     * @throws NotFoundException if there is nothing registered with alias or interface specified
125
     * @throws NotInstantiableException
126
     * @internal
127
     */
128
    protected function build(string $id, array $params = [])
129
    {
130
        if (isset($this->building[$id])) {
131
            throw new CircularReferenceException(sprintf(
132
                'Circular reference to "%s" detected while building: %s',
133
                $id,
134
                implode(',', array_keys($this->building))
135
            ));
136
        }
137
138
        $this->building[$id] = 1;
139
        $this->registerProviderIfDeferredFor($id);
140
        $object = $this->buildInternal($id, $params);
141
        unset($this->building[$id]);
142
143
        return $object;
144
    }
145
146
    private function buildInternal(string $id, array $params = [])
147
    {
148
        if (!isset($this->definitions[$id])) {
149
            if ($this->parentContainer !== null) {
150
                return $this->parentContainer->get($id, $params);
0 ignored issues
show
Unused Code introduced by
The call to Psr\Container\ContainerInterface::get() has too many arguments starting with $params. ( Ignorable by Annotation )

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

150
                return $this->parentContainer->/** @scrutinizer ignore-call */ get($id, $params);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
151
            }
152
            return $this->buildPrimitive($id, $params);
153
        }
154
155
        return $this->definitions[$id]->resolve($this, $params);
156
    }
157
158
    private function buildPrimitive(string $class, array $params = [])
159
    {
160
        if (class_exists($class)) {
161
            $definition = ArrayDefinition::fromClassName($class);
162
163
            return $definition->resolve($this, $params);
164
        }
165
166
        throw new NotFoundException("No definition for $class");
167
    }
168
169
    /**
170
     * Register providers from {@link deferredProviders} if they provide
171
     * definition for given identifier.
172
     *
173
     * @param string $id class or identifier of a service.
174
     */
175
    private function registerProviderIfDeferredFor(string $id): void
176
    {
177
        $providers = $this->deferredProviders;
178
179
        foreach ($providers as $provider) {
180
            if ($provider->hasDefinitionFor($id)) {
181
                $provider->register($this);
182
183
                // provider should be removed after registration to not be registered again
184
                $providers->detach($provider);
185
            }
186
        }
187
    }
188
189
    /**
190
     * Sets a definition to the container. Definition may be defined multiple ways.
191
     * @param string $id
192
     * @param mixed $definition
193
     * @throws InvalidConfigException
194
     * @see `Normalizer::normalize()`
195
     */
196
    public function set(string $id, $definition): void
197
    {
198
        $this->instances[$id] = null;
199
        $this->definitions[$id] = Normalizer::normalize($definition, $id);
200
    }
201
202
    /**
203
     * Sets multiple definitions at once.
204
     * @param array $config definitions indexed by their ids
205
     */
206
    public function setMultiple(array $config): void
207
    {
208
        foreach ($config as $id => $definition) {
209
            $this->set($id, $definition);
210
        }
211
    }
212
213
    /**
214
     * Returns a value indicating whether the container has the definition of the specified name.
215
     * @param string $id class name, interface name or alias name
216
     * @return bool whether the container is able to provide instance of class specified.
217
     * @throws CircularReferenceException
218
     * @see set()
219
     */
220
    public function has($id): bool
221
    {
222
        return isset($this->definitions[$id]);
223
    }
224
225
    /**
226
     * Does after build object initialization.
227
     * At the moment only `init()` if class implements Initiable interface.
228
     *
229
     * @param object $object
230
     * @return object
231
     */
232
    private function initObject($object)
233
    {
234
        if ($object instanceof Initiable) {
235
            $object->init();
0 ignored issues
show
Deprecated Code introduced by
The function yii\di\Initiable::init() has been deprecated: use constructor and getters/setters instead ( Ignorable by Annotation )

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

235
            /** @scrutinizer ignore-deprecated */ $object->init();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
236
        }
237
238
        return $object;
239
    }
240
241
    /**
242
     * Resolves dependencies by replacing them with the actual object instances.
243
     * @param Definition[] $dependencies the dependencies
244
     * @return array the resolved dependencies
245
     * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled.
246
     */
247
    public function resolveDependencies(array $dependencies): array
248
    {
249
        $result = [];
250
        /** @var Definition $dependency */
251
        foreach ($dependencies as $dependency) {
252
            $result[] = $this->resolve($dependency);
253
        }
254
255
        return $result;
256
    }
257
258
    /**
259
     * This function resolves a dependency recursively, checking for loops.
260
     * TODO add checking for loops
261
     * @param Definition $dependency
262
     * @return mixed
263
     */
264
    public function resolve(Definition $dependency)
265
    {
266
        while ($dependency instanceof Definition) {
267
            $dependency = $dependency->resolve($this->parentContainer ?? $this);
268
        }
269
        return $dependency;
270
    }
271
272
    /**
273
     * Adds service provider to the container. Unless service provider is deferred
274
     * it would be immediately registered.
275
     *
276
     * @param string|array $providerDefinition
277
     *
278
     * @throws InvalidConfigException
279
     * @throws NotInstantiableException
280
     * @see ServiceProvider
281
     * @see DeferredServiceProvider
282
     */
283
    public function addProvider($providerDefinition): void
284
    {
285
        $provider = $this->buildProvider($providerDefinition);
286
287
        if ($provider instanceof DeferredServiceProvider) {
288
            $this->deferredProviders->attach($provider);
289
        } else {
290
            $provider->register($this);
291
        }
292
    }
293
294
    /**
295
     * Builds service provider by definition.
296
     *
297
     * @param string|array $providerDefinition class name or definition of provider.
298
     * @return ServiceProvider instance of service provider;
299
     *
300
     * @throws InvalidConfigException
301
     * @throws NotInstantiableException
302
     */
303
    private function buildProvider($providerDefinition): ServiceProvider
304
    {
305
        $provider = Normalizer::normalize($providerDefinition)->resolve($this);
306
        if (!($provider instanceof ServiceProvider)) {
307
            throw new InvalidConfigException(
308
                'Service provider should be an instance of ' . ServiceProvider::class
309
            );
310
        }
311
312
        return $provider;
313
    }
314
315
    /**
316
     * Returns injector.
317
     *
318
     * @return Injector
319
     */
320
    public function getInjector(): Injector
321
    {
322
        if ($this->injector === null) {
323
            $this->injector = new Injector($this);
324
        }
325
326
        return $this->injector;
327
    }
328
329
    /**
330
     * Returns a value indicating whether the container has already instantiated
331
     * instance of the specified name.
332
     * @param string|Reference $id class name, interface name or alias name
333
     * @return bool whether the container has instance of class specified.
334
     */
335
    public function hasInstance($id): bool
336
    {
337
        $id = $this->getId($id);
338
339
        return isset($this->instances[$id]);
340
    }
341
342
    /**
343
     * Returns all instances set in container
344
     * @return array list of instance
345
     */
346
    public function getInstances() : array
347
    {
348
        return $this->instances;
349
    }
350
}
351