Completed
Push — sam-rework ( 13e21a...6c5366 )
by Alexander
52:41 queued 37:40
created

Container::getInstances()   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\DeferredServiceProviderInterface;
14
use yii\di\contracts\DefinitionInterface;
15
use yii\di\contracts\ServiceProviderInterface;
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 DefinitionInterface[] object definitions indexed by their types
30
     */
31
    private $definitions;
32
    /**
33
     * @var ReflectionClass[] cached ReflectionClass objects indexed by class/interface names
34
     */
35
    private $reflections = [];
0 ignored issues
show
introduced by
The private property $reflections is not used, and could be removed.
Loading history...
36
    /**
37
     * @var array used to collect ids instantiated during build
38
     * to detect circular references
39
     */
40
    private $building = [];
41
    /**
42
     * @var contracts\DeferredServiceProviderInterface[]|\SplObjectStorage list of providers
43
     * deferred to register till their services would be requested
44
     */
45
    private $deferredProviders;
46
    /**
47
     * @var Injector injector with this container.
48
     */
49
    protected $injector;
50
51
    /**
52
     * @var object[]
53
     */
54
    private $instances;
55
56
    /** @var ?ContainerInterface */
57
    private $rootContainer;
58
    /**
59
     * Container constructor.
60
     *
61
     * @param array $definitions
62
     * @param ServiceProviderInterface[] $providers
63
     *
64
     * @throws InvalidConfigException
65
     * @throws NotInstantiableException
66
     */
67
    public function __construct(
68
        array $definitions = [],
69
        array $providers = [],
70
        ?ContainerInterface $rootContainer = null
71
    ) {
72
        $this->rootContainer = $rootContainer;
73
        $this->setMultiple($definitions);
74
        $this->deferredProviders = new SplObjectStorage();
75
        foreach ($providers as $provider) {
76
            $this->addProvider($provider);
77
        }
78
    }
79
80
    /**
81
     * Returns an instance by either interface name or alias.
82
     *
83
     * Same instance of the class will be returned each time this method is called.
84
     *
85
     * @param string|Reference $id the interface or an alias name that was previously registered via [[set()]].
86
     * @return object an instance of the requested interface.
87
     * @throws CircularReferenceException
88
     * @throws InvalidConfigException
89
     * @throws NotFoundException
90
     * @throws NotInstantiableException
91
     */
92
    public function get($id)
93
    {
94
        $id = $this->getId($id);
95
        if (!isset($this->instances[$id])) {
96
            $object = $this->build($id);
97
            $this->instances[$id] = $object;
98
            $this->initObject($object);
99
        }
100
101
        return $this->instances[$id];
102
    }
103
104
    public function getId($id): string
105
    {
106
        return is_string($id) ? $id : $id->getId();
107
    }
108
109
    /**
110
     * Returns normalized definition for a given id
111
     */
112
    public function getDefinition(string $id): ?DefinitionInterface
113
    {
114
        return $this->definitions[$id] ?? null;
115
    }
116
117
    /**
118
     * Creates new instance by either interface name or alias.
119
     *
120
     * @param string $id the interface or an alias name that was previously registered via [[set()]].
121
     * @param array $config
122
     * @return object new built instance of the specified class.
123
     * @throws CircularReferenceException
124
     * @throws InvalidConfigException
125
     * @throws NotFoundException if there is nothing registered with alias or interface specified
126
     * @throws NotInstantiableException
127
     * @internal
128
     */
129
    protected function build(string $id, array $params = [])
130
    {
131
        if (isset($this->building[$id])) {
132
            if ($id === 'container') {
133
                return $this;
134
            }
135
            throw new CircularReferenceException(sprintf(
136
                'Circular reference to "%s" detected while building: %s',
137
                $id,
138
                implode(',', array_keys($this->building))
139
            ));
140
        }
141
142
        $this->building[$id] = 1;
143
        $this->registerProviderIfDeferredFor($id);
144
        $object = $this->buildInternal($id, $params);
145
        unset($this->building[$id]);
146
147
        return $object;
148
    }
149
150
    protected function buildInternal(string $id, array $params = [])
151
    {
152
        if (!isset($this->definitions[$id])) {
153
            if (isset($this->rootContainer)) {
154
                return $this->rootContainer->get($id, $params);
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

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

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...
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

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

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