Completed
Push — master ( b0867d...8ab030 )
by Alexander
24s queued 11s
created

Container   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 291
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 31
eloc 68
dl 0
loc 291
rs 9.92
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getDefinition() 0 3 1
A __construct() 0 10 2
A setMultiple() 0 4 2
A getId() 0 3 2
A buildPrimitive() 0 9 2
A hasInstance() 0 5 1
A has() 0 3 1
A buildInternal() 0 11 3
A set() 0 4 1
A addProvider() 0 8 2
A buildProvider() 0 10 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 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 $rootContainer;
53
54
    /**
55
     * Container constructor.
56
     *
57
     * @param array $definitions
58
     * @param ServiceProvider[] $providers
59
     *
60
     * @param ContainerInterface|null $rootContainer
61
     * @throws InvalidConfigException
62
     * @throws NotInstantiableException
63
     */
64
    public function __construct(
65
        array $definitions = [],
66
        array $providers = [],
67
        ?ContainerInterface $rootContainer = null
68
    ) {
69
        $this->rootContainer = $rootContainer;
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
     * @param array $parameters parameters to set for the object obtained
84
     * @return object an instance of the requested interface.
85
     * @throws CircularReferenceException
86
     * @throws InvalidConfigException
87
     * @throws NotFoundException
88
     * @throws NotInstantiableException
89
     */
90
    public function get($id, array $parameters = [])
91
    {
92
        $id = $this->getId($id);
93
        if (!isset($this->instances[$id])) {
94
            $object = $this->build($id, $parameters);
95
            $this->instances[$id] = $object;
96
            $this->initObject($object);
97
        }
98
99
        return $this->instances[$id];
100
    }
101
102
    public function getId($id): string
103
    {
104
        return is_string($id) ? $id : $id->getId();
105
    }
106
107
    /**
108
     * Returns normalized definition for a given id
109
     * @param string $id
110
     * @return Definition|null
111
     */
112
    public function getDefinition(string $id): ?Definition
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 $params
122
     * @return object new built instance of the specified class.
123
     * @throws CircularReferenceException
124
     * @internal
125
     */
126
    protected function build(string $id, array $params = [])
127
    {
128
        if (isset($this->building[$id])) {
129
            throw new CircularReferenceException(sprintf(
130
                'Circular reference to "%s" detected while building: %s',
131
                $id,
132
                implode(',', array_keys($this->building))
133
            ));
134
        }
135
136
        $this->building[$id] = 1;
137
        $this->registerProviderIfDeferredFor($id);
138
        $object = $this->buildInternal($id, $params);
139
        unset($this->building[$id]);
140
141
        return $object;
142
    }
143
144
    private function buildInternal(string $id, array $params = [])
145
    {
146
        if (!isset($this->definitions[$id])) {
147
            if ($this->rootContainer !== null) {
148
                /** @noinspection PhpMethodParametersCountMismatchInspection passing parameters for containers supporting them */
149
                return $this->rootContainer->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

149
                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...
150
            }
151
            return $this->buildPrimitive($id, $params);
152
        }
153
154
        return $this->definitions[$id]->resolve($this, $params);
155
    }
156
157
    private function buildPrimitive(string $class, array $params = [])
158
    {
159
        if (class_exists($class)) {
160
            $definition = ArrayDefinition::fromClassName($class);
161
162
            return $definition->resolve($this, $params);
163
        }
164
165
        throw new NotFoundException("No definition for $class");
166
    }
167
168
    /**
169
     * Register providers from {@link deferredProviders} if they provide
170
     * definition for given identifier.
171
     *
172
     * @param string $id class or identifier of a service.
173
     */
174
    private function registerProviderIfDeferredFor(string $id): void
175
    {
176
        $providers = $this->deferredProviders;
177
178
        foreach ($providers as $provider) {
179
            if ($provider->hasDefinitionFor($id)) {
180
                $provider->register($this);
181
182
                // provider should be removed after registration to not be registered again
183
                $providers->detach($provider);
184
            }
185
        }
186
    }
187
188
    /**
189
     * Sets a definition to the container. Definition may be defined multiple ways.
190
     * @param string $id
191
     * @param mixed $definition
192
     * @throws InvalidConfigException
193
     * @see `Normalizer::normalize()`
194
     */
195
    public function set(string $id, $definition): void
196
    {
197
        $this->instances[$id] = null;
198
        $this->definitions[$id] = Normalizer::normalize($definition, $id);
199
    }
200
201
    /**
202
     * Sets multiple definitions at once.
203
     * @param array $config definitions indexed by their ids
204
     * @throws InvalidConfigException
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
     * @see set()
218
     */
219
    public function has($id): bool
220
    {
221
        return isset($this->definitions[$id]);
222
    }
223
224
    /**
225
     * Does after build object initialization.
226
     * At the moment only `init()` if class implements Initiable interface.
227
     *
228
     * @param object $object
229
     * @return object
230
     */
231
    private function initObject($object)
232
    {
233
        if ($object instanceof Initiable) {
234
            $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

234
            /** @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...
235
        }
236
237
        return $object;
238
    }
239
240
    /**
241
     * Adds service provider to the container. Unless service provider is deferred
242
     * it would be immediately registered.
243
     *
244
     * @param string|array $providerDefinition
245
     *
246
     * @throws InvalidConfigException
247
     * @throws NotInstantiableException
248
     * @see ServiceProvider
249
     * @see DeferredServiceProvider
250
     */
251
    public function addProvider($providerDefinition): void
252
    {
253
        $provider = $this->buildProvider($providerDefinition);
254
255
        if ($provider instanceof DeferredServiceProvider) {
256
            $this->deferredProviders->attach($provider);
257
        } else {
258
            $provider->register($this);
259
        }
260
    }
261
262
    /**
263
     * Builds service provider by definition.
264
     *
265
     * @param string|array $providerDefinition class name or definition of provider.
266
     * @return ServiceProvider instance of service provider;
267
     *
268
     * @throws InvalidConfigException
269
     */
270
    private function buildProvider($providerDefinition): ServiceProvider
271
    {
272
        $provider = Normalizer::normalize($providerDefinition)->resolve($this);
273
        if (!($provider instanceof ServiceProvider)) {
274
            throw new InvalidConfigException(
275
                'Service provider should be an instance of ' . ServiceProvider::class
276
            );
277
        }
278
279
        return $provider;
280
    }
281
282
    /**
283
     * Returns injector.
284
     *
285
     * @return Injector
286
     */
287
    public function getInjector(): Injector
288
    {
289
        if ($this->injector === null) {
290
            $this->injector = new Injector($this);
291
        }
292
293
        return $this->injector;
294
    }
295
296
    /**
297
     * Returns a value indicating whether the container has already instantiated
298
     * instance of the specified name.
299
     * @param string|Reference $id class name, interface name or alias name
300
     * @return bool whether the container has instance of class specified.
301
     */
302
    public function hasInstance($id): bool
303
    {
304
        $id = $this->getId($id);
305
306
        return isset($this->instances[$id]);
307
    }
308
309
    /**
310
     * Returns all instances set in container
311
     * @return array list of instance
312
     */
313
    public function getInstances() : array
314
    {
315
        return $this->instances;
316
    }
317
}
318