Passed
Push — 79-remove-initiable ( 291dcf )
by Alexander
21:05 queued 06:32
created

Container   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 273
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 29
eloc 63
dl 0
loc 273
rs 10
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 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 setMultiple() 0 4 2
A build() 0 16 2
A registerProviderIfDeferredFor() 0 10 3
A getInstances() 0 3 1
A getInjector() 0 7 2
A getDefinition() 0 3 1
A get() 0 8 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
            $this->instances[$id] = $this->build($id, $parameters);
95
        }
96
97
        return $this->instances[$id];
98
    }
99
100
    public function getId($id): string
101
    {
102
        return is_string($id) ? $id : $id->getId();
103
    }
104
105
    /**
106
     * Returns normalized definition for a given id
107
     * @param string $id
108
     * @return Definition|null
109
     */
110
    public function getDefinition(string $id): ?Definition
111
    {
112
        return $this->definitions[$id] ?? null;
113
    }
114
115
    /**
116
     * Creates new instance by either interface name or alias.
117
     *
118
     * @param string $id the interface or an alias name that was previously registered via [[set()]].
119
     * @param array $params
120
     * @return object new built instance of the specified class.
121
     * @throws CircularReferenceException
122
     * @internal
123
     */
124
    protected function build(string $id, array $params = [])
125
    {
126
        if (isset($this->building[$id])) {
127
            throw new CircularReferenceException(sprintf(
128
                'Circular reference to "%s" detected while building: %s',
129
                $id,
130
                implode(',', array_keys($this->building))
131
            ));
132
        }
133
134
        $this->building[$id] = 1;
135
        $this->registerProviderIfDeferredFor($id);
136
        $object = $this->buildInternal($id, $params);
137
        unset($this->building[$id]);
138
139
        return $object;
140
    }
141
142
    private function buildInternal(string $id, array $params = [])
143
    {
144
        if (!isset($this->definitions[$id])) {
145
            if ($this->rootContainer !== null) {
146
                /** @noinspection PhpMethodParametersCountMismatchInspection passing parameters for containers supporting them */
147
                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

147
                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...
148
            }
149
            return $this->buildPrimitive($id, $params);
150
        }
151
152
        return $this->definitions[$id]->resolve($this, $params);
153
    }
154
155
    private function buildPrimitive(string $class, array $params = [])
156
    {
157
        if (class_exists($class)) {
158
            $definition = new ArrayDefinition($class);
159
160
            return $definition->resolve($this, $params);
161
        }
162
163
        throw new NotFoundException("No definition for $class");
164
    }
165
166
    /**
167
     * Register providers from {@link deferredProviders} if they provide
168
     * definition for given identifier.
169
     *
170
     * @param string $id class or identifier of a service.
171
     */
172
    private function registerProviderIfDeferredFor(string $id): void
173
    {
174
        $providers = $this->deferredProviders;
175
176
        foreach ($providers as $provider) {
177
            if ($provider->hasDefinitionFor($id)) {
178
                $provider->register($this);
179
180
                // provider should be removed after registration to not be registered again
181
                $providers->detach($provider);
182
            }
183
        }
184
    }
185
186
    /**
187
     * Sets a definition to the container. Definition may be defined multiple ways.
188
     * @param string $id
189
     * @param mixed $definition
190
     * @throws InvalidConfigException
191
     * @see `Normalizer::normalize()`
192
     */
193
    public function set(string $id, $definition): void
194
    {
195
        $this->instances[$id] = null;
196
        $this->definitions[$id] = Normalizer::normalize($definition, $id);
197
    }
198
199
    /**
200
     * Sets multiple definitions at once.
201
     * @param array $config definitions indexed by their ids
202
     * @throws InvalidConfigException
203
     */
204
    public function setMultiple(array $config): void
205
    {
206
        foreach ($config as $id => $definition) {
207
            $this->set($id, $definition);
208
        }
209
    }
210
211
    /**
212
     * Returns a value indicating whether the container has the definition of the specified name.
213
     * @param string $id class name, interface name or alias name
214
     * @return bool whether the container is able to provide instance of class specified.
215
     * @see set()
216
     */
217
    public function has($id): bool
218
    {
219
        return isset($this->definitions[$id]);
220
    }
221
222
    /**
223
     * Adds service provider to the container. Unless service provider is deferred
224
     * it would be immediately registered.
225
     *
226
     * @param string|array $providerDefinition
227
     *
228
     * @throws InvalidConfigException
229
     * @throws NotInstantiableException
230
     * @see ServiceProvider
231
     * @see DeferredServiceProvider
232
     */
233
    public function addProvider($providerDefinition): void
234
    {
235
        $provider = $this->buildProvider($providerDefinition);
236
237
        if ($provider instanceof DeferredServiceProvider) {
238
            $this->deferredProviders->attach($provider);
239
        } else {
240
            $provider->register($this);
241
        }
242
    }
243
244
    /**
245
     * Builds service provider by definition.
246
     *
247
     * @param string|array $providerDefinition class name or definition of provider.
248
     * @return ServiceProvider instance of service provider;
249
     *
250
     * @throws InvalidConfigException
251
     */
252
    private function buildProvider($providerDefinition): ServiceProvider
253
    {
254
        $provider = Normalizer::normalize($providerDefinition)->resolve($this);
255
        if (!($provider instanceof ServiceProvider)) {
256
            throw new InvalidConfigException(
257
                'Service provider should be an instance of ' . ServiceProvider::class
258
            );
259
        }
260
261
        return $provider;
262
    }
263
264
    /**
265
     * Returns injector.
266
     *
267
     * @return Injector
268
     */
269
    public function getInjector(): Injector
270
    {
271
        if ($this->injector === null) {
272
            $this->injector = new Injector($this);
273
        }
274
275
        return $this->injector;
276
    }
277
278
    /**
279
     * Returns a value indicating whether the container has already instantiated
280
     * instance of the specified name.
281
     * @param string|Reference $id class name, interface name or alias name
282
     * @return bool whether the container has instance of class specified.
283
     */
284
    public function hasInstance($id): bool
285
    {
286
        $id = $this->getId($id);
287
288
        return isset($this->instances[$id]);
289
    }
290
291
    /**
292
     * Returns all instances set in container
293
     * @return array list of instance
294
     */
295
    public function getInstances() : array
296
    {
297
        return $this->instances;
298
    }
299
}
300