Completed
Push — sam-rework ( a5f68e...28559d )
by Andrii
10:44
created

Container::getId()   A

Complexity

Conditions 2
Paths 2

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 2
nc 2
nop 1
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->setAll($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 name or an alias name (e.g. `foo`) 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->initObject($object);
98
            $this->instances[$id] = $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 name or an alias name (e.g. `foo`) 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($rootContainer)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $rootContainer seems to never exist and therefore isset should always be false.
Loading history...
154
                return $rootContainer->get($id, $params);
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
    public function alreadyBuilding($id): bool
183
    {
184
        return isset($this->building[$id]);
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 setAll($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 DefinitionInterface[] $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 DefinitionInterface $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 DefinitionInterface $dependency
279
     * @return DefinitionInterface
280
     */
281
    public function resolve(DefinitionInterface $dependency)
282
    {
283
        while ($dependency instanceof DefinitionInterface) {
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 DeferredServiceProviderInterface) {
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 ServiceProviderInterface instance of service provider;
316
     *
317
     * @throws InvalidConfigException
318
     * @throws NotInstantiableException
319
     */
320
    private function buildProvider($providerDefinition): ServiceProviderInterface
321
    {
322
        $provider = Normalizer::normalize($providerDefinition)->resolve($this);
323
        if (!($provider instanceof ServiceProviderInterface)) {
324
            throw new InvalidConfigException(
325
                'Service provider should be an instance of ' . ServiceProviderInterface::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