Passed
Push — sam-rework ( 767b4b...beba2c )
by Alexander
14:35
created

Container::getRootContainer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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 ReflectionClass;
12
use SplObjectStorage;
13
use yii\di\contracts\DeferredServiceProvider;
14
use yii\di\contracts\Definition;
15
use yii\di\contracts\ServiceProvider;
16
use yii\di\definitions\ArrayDefinition;
17
use yii\di\definitions\Normalizer;
18
use yii\di\exceptions\CircularReferenceException;
19
use yii\di\exceptions\InvalidConfigException;
20
use yii\di\exceptions\NotFoundException;
21
use yii\di\exceptions\NotInstantiableException;
22
23
/**
24
 * Container implements a [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection) container.
25
 */
26
class Container implements ContainerInterface
27
{
28
    /**
29
     * @var Definition[] object definitions indexed by their types
30
     */
31
    private $definitions = [];
32
    /**
33
     * @var array used to collect ids instantiated during build
34
     * to detect circular references
35
     */
36
    private $building = [];
37
    /**
38
     * @var contracts\DeferredServiceProvider[]|\SplObjectStorage list of providers
39
     * deferred to register till their services would be requested
40
     */
41
    private $deferredProviders;
42
    /**
43
     * @var Injector injector with this container.
44
     */
45
    protected $injector;
46
47
    /**
48
     * @var object[]
49
     */
50
    private $instances;
51
52
    /** @var ?ContainerInterface */
53
    private $rootContainer;
54
    /**
55
     * Container constructor.
56
     *
57
     * @param array $definitions
58
     * @param ServiceProvider[] $providers
59
     *
60
     * @throws InvalidConfigException
61
     * @throws NotInstantiableException
62
     */
63
    public function __construct(
64
        array $definitions = [],
65
        array $providers = [],
66
        ?ContainerInterface $rootContainer = null
67
    ) {
68
        $this->rootContainer = $rootContainer;
69
        $this->setMultiple($definitions);
70
        $this->deferredProviders = new SplObjectStorage();
71
        foreach ($providers as $provider) {
72
            $this->addProvider($provider);
73
        }
74
    }
75
76
    /**
77
     * Returns an instance by either interface name or alias.
78
     *
79
     * Same instance of the class will be returned each time this method is called.
80
     *
81
     * @param string|Reference $id the interface or an alias name that was previously registered via [[set()]].
82
     * @return object an instance of the requested interface.
83
     * @throws CircularReferenceException
84
     * @throws InvalidConfigException
85
     * @throws NotFoundException
86
     * @throws NotInstantiableException
87
     */
88
    public function get($id)
89
    {
90
        return $this->getWithParams($id);
91
    }
92
93
    public function getWithParams($id, array $params = [])
94
    {
95
        $id = $this->getId($id);
96
        if (!isset($this->instances[$id])) {
97
            $object = $this->build($id, $params);
98
            $this->instances[$id] = $object;
99
            $this->initObject($object);
100
        }
101
102
        return $this->instances[$id];
103
    }
104
105
    public function getId($id): string
106
    {
107
        return is_string($id) ? $id : $id->getId();
108
    }
109
110
    /**
111
     * Returns normalized definition for a given id
112
     */
113
    public function getDefinition(string $id): ?Definition
114
    {
115
        return $this->definitions[$id] ?? null;
116
    }
117
118
    /**
119
     * Creates new instance by either interface name or alias.
120
     *
121
     * @param string $id the interface or an alias name that was previously registered via [[set()]].
122
     * @param array $config
123
     * @return object new built instance of the specified class.
124
     * @throws CircularReferenceException
125
     * @throws InvalidConfigException
126
     * @throws NotFoundException if there is nothing registered with alias or interface specified
127
     * @throws NotInstantiableException
128
     * @internal
129
     */
130
    protected function build(string $id, array $params = [])
131
    {
132
        if (isset($this->building[$id])) {
133
            if ($id === 'container') {
134
                return $this;
135
            }
136
            throw new CircularReferenceException(sprintf(
137
                'Circular reference to "%s" detected while building: %s',
138
                $id,
139
                implode(',', array_keys($this->building))
140
            ));
141
        }
142
143
        $this->building[$id] = 1;
144
        $this->registerProviderIfDeferredFor($id);
145
        $object = $this->buildInternal($id, $params);
146
        unset($this->building[$id]);
147
148
        return $object;
149
    }
150
151
    protected function buildInternal(string $id, array $params = [])
152
    {
153
        if (!isset($this->definitions[$id])) {
154
            if (isset($this->rootContainer)) {
155
                if ($this->rootContainer instanceof self) {
156
                    return $this->rootContainer->getWithParams($id, $params);
0 ignored issues
show
Bug introduced by
The method getWithParams() does not exist on Psr\Container\ContainerInterface. It seems like you code against a sub-type of Psr\Container\ContainerInterface such as yii\di\Container. ( Ignorable by Annotation )

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

156
                    return $this->rootContainer->/** @scrutinizer ignore-call */ getWithParams($id, $params);
Loading history...
157
                } else {
158
                    return $this->rootContainer->get($id);
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

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

158
                    return $this->rootContainer->/** @scrutinizer ignore-call */ get($id);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
159
                }
160
            }
161
            $res = $this->buildPrimitive($id, $params);
162
            if ($res !== null) {
163
                return $res;
164
            }
165
            throw new NotFoundException("No definition for $id");
166
        }
167
168
        $definition = $this->definitions[$id];
169
170
        return $definition->resolve($this, $params);
171
    }
172
173
    protected function buildPrimitive(string $class, array $params = [])
174
    {
175
        if ($class === ContainerInterface::class) {
176
            return $this;
177
        }
178
        if (class_exists($class)) {
179
            $definition = ArrayDefinition::fromClassName($class);
180
181
            return $definition->resolve($this, $params);
182
        }
183
184
        return null;
185
    }
186
187
    /**
188
     * Register providers from {@link deferredProviders} if they provide
189
     * definition for given identifier.
190
     *
191
     * @param string $id class or identifier of a service.
192
     */
193
    private function registerProviderIfDeferredFor(string $id): void
194
    {
195
        $providers = $this->deferredProviders;
196
197
        foreach ($providers as $provider) {
198
            if ($provider->hasDefinitionFor($id)) {
199
                $provider->register($this);
200
201
                // provider should be removed after registration to not be registered again
202
                $providers->detach($provider);
203
            }
204
        }
205
    }
206
207
    /**
208
     * Sets a definition to the container. Definition may be defined multiple ways.
209
     * @see `Normalizer::normalize()`
210
     * @param string $id
211
     * @param mixed $definition
212
     */
213
    public function set(string $id, $definition): void
214
    {
215
        $this->instances[$id] = null;
216
        $this->definitions[$id] = Normalizer::normalize($definition, $id);
217
    }
218
219
    /**
220
     * Sets multiple definitions at once.
221
     * @param array $config definitions indexed by their ids
222
     */
223
    public function setMultiple(array $config): void
224
    {
225
        foreach ($config as $id => $definition) {
226
            $this->set($id, $definition);
227
        }
228
    }
229
230
    /**
231
     * Returns a value indicating whether the container has the definition of the specified name.
232
     * @param string $id class name, interface name or alias name
233
     * @return bool whether the container is able to provide instance of class specified.
234
     * @throws CircularReferenceException
235
     * @see set()
236
     */
237
    public function has($id): bool
238
    {
239
        return isset($this->definitions[$id]);
240
    }
241
242
    /**
243
     * Does after build object initialization.
244
     * At the moment only `init()` if class implements Initiable interface.
245
     *
246
     * @param object $object
247
     * @return object
248
     */
249
    protected function initObject($object)
250
    {
251
        if ($object instanceof Initiable) {
252
            $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

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