Completed
Push — sam-rework ( 4004fe...b97ee5 )
by Andrii
10:55
created

Container::getWithParams()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 2
dl 0
loc 10
rs 10
c 0
b 0
f 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 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\DeferredServiceProvider[]|\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 ServiceProvider[] $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
        return $this->getWithParams($id);
95
    }
96
97
    public function getWithParams($id, array $params = [])
98
    {
99
        $id = $this->getId($id);
100
        if (!isset($this->instances[$id])) {
101
            $object = $this->build($id, $params);
102
            $this->instances[$id] = $object;
103
            $this->initObject($object);
104
        }
105
106
        return $this->instances[$id];
107
    }
108
109
    public function getId($id): string
110
    {
111
        return is_string($id) ? $id : $id->getId();
112
    }
113
114
    /**
115
     * Returns normalized definition for a given id
116
     */
117
    public function getDefinition(string $id): ?Definition
118
    {
119
        return $this->definitions[$id] ?? null;
120
    }
121
122
    /**
123
     * Creates new instance by either interface name or alias.
124
     *
125
     * @param string $id the interface or an alias name that was previously registered via [[set()]].
126
     * @param array $config
127
     * @return object new built instance of the specified class.
128
     * @throws CircularReferenceException
129
     * @throws InvalidConfigException
130
     * @throws NotFoundException if there is nothing registered with alias or interface specified
131
     * @throws NotInstantiableException
132
     * @internal
133
     */
134
    protected function build(string $id, array $params = [])
135
    {
136
        if (isset($this->building[$id])) {
137
            if ($id === 'container') {
138
                return $this;
139
            }
140
            throw new CircularReferenceException(sprintf(
141
                'Circular reference to "%s" detected while building: %s',
142
                $id,
143
                implode(',', array_keys($this->building))
144
            ));
145
        }
146
147
        $this->building[$id] = 1;
148
        $this->registerProviderIfDeferredFor($id);
149
        $object = $this->buildInternal($id, $params);
150
        unset($this->building[$id]);
151
152
        return $object;
153
    }
154
155
    protected function buildInternal(string $id, array $params = [])
156
    {
157
        if (!isset($this->definitions[$id])) {
158
            if (isset($this->rootContainer)) {
159
                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

159
                return $this->rootContainer->/** @scrutinizer ignore-call */ getWithParams($id, $params);
Loading history...
Bug introduced by
The method getWithParams() 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

159
                return $this->rootContainer->/** @scrutinizer ignore-call */ getWithParams($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...
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 Definition
280
     */
281
    public function resolve(Definition $dependency)
282
    {
283
        while ($dependency instanceof Definition) {
284
            $dependency = $dependency->resolve($this->getRootContainer());
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
    public function getRootContainer(): ContainerInterface
347
    {
348
        return $this->rootContainer ?? $this;
349
    }
350
351
    /**
352
     * Returns a value indicating whether the container has already instantiated
353
     * instance of the specified name.
354
     * @param string|Reference $id class name, interface name or alias name
355
     * @return bool whether the container has instance of class specified.
356
     * @throws CircularReferenceException
357
     */
358
    public function hasInstance($id): bool
359
    {
360
        $id = $this->getId($id);
361
362
        return isset($this->instances[$id]);
363
    }
364
365
    /**
366
     * Returns all instances set in container
367
     * @return array list of instance
368
     */
369
    public function getInstances() : array
370
    {
371
        return $this->instances;
372
    }
373
}
374