Passed
Push — sam-rework ( 16881f...5822c3 )
by Alexander
11:19
created

Container::resolveDependencies()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 9
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 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 $parentContainer;
53
54
    /**
55
     * Container constructor.
56
     *
57
     * @param array $definitions
58
     * @param ServiceProvider[] $providers
59
     *
60
     * @param ContainerInterface|null $parentContainer
61
     * @throws InvalidConfigException
62
     * @throws NotInstantiableException
63
     */
64
    public function __construct(
65
        array $definitions = [],
66
        array $providers = [],
67
        ?ContainerInterface $parentContainer = null
68
    ) {
69
        $this->parentContainer = $parentContainer;
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
     * @return object an instance of the requested interface.
84
     * @throws CircularReferenceException
85
     * @throws InvalidConfigException
86
     * @throws NotFoundException
87
     * @throws NotInstantiableException
88
     */
89
    public function get($id, array $params = [])
90
    {
91
        $id = $this->getId($id);
92
        if (!isset($this->instances[$id])) {
93
            $object = $this->build($id, $params);
94
            $this->instances[$id] = $object;
95
            $this->initObject($object);
96
        }
97
98
        return $this->instances[$id];
99
    }
100
101
    public function getId($id): string
102
    {
103
        return is_string($id) ? $id : $id->getId();
104
    }
105
106
    /**
107
     * Returns normalized definition for a given id
108
     * @param string $id
109
     * @return Definition|null
110
     */
111
    public function getDefinition(string $id): ?Definition
112
    {
113
        return $this->definitions[$id] ?? null;
114
    }
115
116
    /**
117
     * Creates new instance by either interface name or alias.
118
     *
119
     * @param string $id the interface or an alias name that was previously registered via [[set()]].
120
     * @param array $params
121
     * @return object new built instance of the specified class.
122
     * @throws CircularReferenceException
123
     * @throws InvalidConfigException
124
     * @throws NotFoundException if there is nothing registered with alias or interface specified
125
     * @throws NotInstantiableException
126
     * @internal
127
     */
128
    protected function build(string $id, array $params = [])
129
    {
130
        if (isset($this->building[$id])) {
131
            throw new CircularReferenceException(sprintf(
132
                'Circular reference to "%s" detected while building: %s',
133
                $id,
134
                implode(',', array_keys($this->building))
135
            ));
136
        }
137
138
        $this->building[$id] = 1;
139
        $this->registerProviderIfDeferredFor($id);
140
        $object = $this->buildInternal($id, $params);
141
        unset($this->building[$id]);
142
143
        return $object;
144
    }
145
146
    private function buildInternal(string $id, array $params = [])
147
    {
148
        if (!isset($this->definitions[$id])) {
149
            if ($this->parentContainer !== null) {
150
                return $this->parentContainer->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

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

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