Completed
Push — sam-rework ( 1696e6 )
by Andrii
38:13 queued 23:11
created

Container::buildProvider()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 10
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\DependencyInterface;
15
use yii\di\contracts\ServiceProviderInterface;
16
use yii\di\exceptions\CircularReferenceException;
17
use yii\di\exceptions\InvalidConfigException;
18
use yii\di\exceptions\NotFoundException;
19
use yii\di\exceptions\NotInstantiableException;
20
21
/**
22
 * Container implements a [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection) container.
23
 */
24
class Container implements ContainerInterface
25
{
26
    /**
27
     * @var Definition[] object definitions indexed by their types
28
     */
29
    private $definitions;
30
    /**
31
     * @var ReflectionClass[] cached ReflectionClass objects indexed by class/interface names
32
     */
33
    private $reflections = [];
0 ignored issues
show
introduced by
The private property $reflections is not used, and could be removed.
Loading history...
34
    /**
35
     * @var array used to collect ids instantiated during build
36
     * to detect circular references
37
     */
38
    private $building = [];
39
    /**
40
     * @var contracts\DeferredServiceProviderInterface[]|\SplObjectStorage list of providers
41
     * deferred to register till their services would be requested
42
     */
43
    private $deferredProviders;
44
    /**
45
     * @var Injector injector with this container.
46
     */
47
    protected $injector;
48
49
    /**
50
     * @var object[]
51
     */
52
    private $instances;
53
54
    /** @var ?ContainerInterface */
55
    private $rootContainer;
56
    /**
57
     * Container constructor.
58
     *
59
     * @param array $definitions
60
     * @param ServiceProviderInterface[] $providers
61
     *
62
     * @throws InvalidConfigException
63
     * @throws NotInstantiableException
64
     */
65
    public function __construct(
66
        array $definitions = [],
67
        array $providers = [],
68
        ?ContainerInterface $rootContainer = null
69
    ) {
70
        $this->rootContainer = $rootContainer;
71
        $this->setAll($definitions);
72
        $this->deferredProviders = new SplObjectStorage();
73
        foreach ($providers as $provider) {
74
            $this->addProvider($provider);
75
        }
76
    }
77
78
    /**
79
     * Returns an instance by either interface name or alias.
80
     *
81
     * Same instance of the class will be returned each time this method is called.
82
     *
83
     * @param string $id the interface name or an alias name (e.g. `foo`) that was previously registered via [[set()]].
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)
91
    {
92
        $reference = is_string($id) ? Reference::to($id) : $id;
0 ignored issues
show
introduced by
The condition is_string($id) is always true.
Loading history...
93
        $id = $reference->getId();
94
        if (!isset($this->instances[$id])) {
95
            $object = $this->build($reference);
96
            $this->initObject($object);
97
            $this->instances[$id] = $object;
98
        }
99
        return $this->instances[$id];
100
    }
101
102
    /**
103
     * Returns normalized definition for a given id
104
     */
105
    public function getDefinition(string $id): ?Definition
106
    {
107
        return $this->definitions[$id] ?? null;
108
    }
109
110
    /**
111
     * Creates new instance by either interface name or alias.
112
     *
113
     * @param Reference $id the interface name or an alias name (e.g. `foo`) that was previously registered via [[set()]].
114
     * @param array $config
115
     * @return object new built instance of the specified class.
116
     * @throws CircularReferenceException
117
     * @throws InvalidConfigException
118
     * @throws NotFoundException if there is nothing registered with alias or interface specified
119
     * @throws NotInstantiableException
120
     * @internal
121
     */
122
    protected function build(Reference $reference)
123
    {
124
        $id = $reference->getId();
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
        $this->building[$id] = 1;
134
135
        $this->registerProviderIfDeferredFor($id);
136
137
        if (!isset($this->definitions[$id])) {
138
            throw new NotFoundException("No definition for $id");
139
        }
140
141
142
        $definition = $this->definitions[$id];
143
        return $definition->resolve($this);
144
    }
145
146
    /**
147
     * Register providers from {@link deferredProviders} if they provide
148
     * definition for given identifier.
149
     *
150
     * @param string $id class or identifier of a service.
151
     */
152
    private function registerProviderIfDeferredFor(string $id): void
153
    {
154
        $providers = $this->deferredProviders;
155
156
        foreach ($providers as $provider) {
157
            if ($provider->hasDefinitionFor($id)) {
158
                $provider->register($this);
159
160
                // provider should be removed after registration to not be registered again
161
                $providers->detach($provider);
162
            }
163
        }
164
    }
165
166
    /**
167
     * Sets a definition to the container. Definition may be defined multiple ways.
168
     *
169
     * Interface name as string:
170
     *
171
     * ```php
172
     * $container->set('interface_name', EngineInterface::class);
173
     * ```
174
     *
175
     * A closure:
176
     *
177
     * ```php
178
     * $container->set('closure', function($container) {
179
     *     return new MyClass($container->get('db'));
180
     * });
181
     * ```
182
     *
183
     * A callable array:
184
     *
185
     * ```php
186
     * $container->set('static_call', [MyClass::class, 'create']);
187
     * ```
188
     *
189
     * A definition array:
190
     *
191
     * ```php
192
     * $container->set('full_definition', [
193
     *     '__class' => EngineMarkOne::class,
194
     *     '__construct()' => [42],
195
     *     'argName' => 'value',
196
     *     'setX()' => [42],
197
     * ]);
198
     * ```
199
     *
200
     * @param string $id
201
     * @param mixed $definition
202
     */
203
    public function set(string $id, $definition): void
204
    {
205
        $this->instances[$id] = null;
206
        $this->definitions[$id] = Definition::normalize($definition);
207
    }
208
209
    /**
210
     * Sets multiple definitions at once.
211
     * @param array $config definitions indexed by their ids
212
     */
213
    public function setAll($config): void
214
    {
215
        foreach ($config as $id => $definition) {
216
            $this->set($id, $definition);
217
        }
218
    }
219
220
    /**
221
     * Returns a value indicating whether the container has the definition of the specified name.
222
     * @param string $id class name, interface name or alias name
223
     * @return bool whether the container is able to provide instance of class specified.
224
     * @throws CircularReferenceException
225
     * @see set()
226
     */
227
    public function has($id): bool
228
    {
229
        return isset($this->definitions[$id]);
230
    }
231
232
    /**
233
     * Does after build object initialization.
234
     * At the moment only `init()` if class implements Initiable interface.
235
     *
236
     * @param object $object
237
     * @return object
238
     */
239
    protected function initObject($object)
240
    {
241
        if ($object instanceof Initiable) {
242
            $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

242
            /** @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...
243
        }
244
245
        return $object;
246
    }
247
248
249
250
251
    /**
252
     * Resolves dependencies by replacing them with the actual object instances.
253
     * @param DependencyInterface[] $dependencies the dependencies
254
     * @return array the resolved dependencies
255
     * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled.
256
     */
257
    protected function resolveDependencies(array $dependencies): array
258
    {
259
        $result = [];
260
        /** @var DependencyInterface $dependency */
261
        foreach ($dependencies as $dependency) {
262
            $result[] = $this->resolve($dependency);
263
        }
264
265
        return $result;
266
    }
267
268
    /**
269
     * Adds service provider to the container. Unless service provider is deferred
270
     * it would be immediately registered.
271
     *
272
     * @param string|array $providerDefinition
273
     *
274
     * @throws InvalidConfigException
275
     * @throws NotInstantiableException
276
     * @see ServiceProvider
277
     * @see DeferredServiceProvider
278
     */
279
    public function addProvider($providerDefinition): void
280
    {
281
        $provider = $this->buildProvider($providerDefinition);
282
283
        if ($provider instanceof DeferredServiceProviderInterface) {
284
            $this->deferredProviders->attach($provider);
285
        } else {
286
            $provider->register($this);
287
        }
288
    }
289
290
    /**
291
     * Builds service provider by definition.
292
     *
293
     * @param string|array $providerDefinition class name or definition of provider.
294
     * @return ServiceProviderInterface instance of service provider;
295
     *
296
     * @throws InvalidConfigException
297
     * @throws NotInstantiableException
298
     */
299
    private function buildProvider($providerDefinition): ServiceProviderInterface
300
    {
301
        $provider = Definition::normalize($providerDefinition)->resolve($this);
302
        if (!($provider instanceof ServiceProviderInterface)) {
303
            throw new InvalidConfigException(
304
                'Service provider should be an instance of ' . ServiceProviderInterface::class
305
            );
306
        }
307
308
        return $provider;
309
    }
310
311
    /**
312
     * Returns injector.
313
     *
314
     * @return Injector
315
     */
316
    public function getInjector(): Injector
317
    {
318
        if ($this->injector === null) {
319
            $this->injector = new Injector($this);
320
        }
321
322
        return $this->injector;
323
    }
324
325
    /**
326
     * This function resolves a dependency recursively, checking for loops.
327
     * @param DependencyInterface $dependency
328
     * @return DependencyInterface
329
     */
330
    protected function resolve(DependencyInterface $dependency)
331
    {
332
        while ($dependency instanceof DependencyInterface) {
333
            $dependency = $dependency->resolve($this->getRootContainer());
334
        }
335
        return $dependency;
336
    }
337
338
    public function getRootContainer(): ContainerInterface
339
    {
340
        return $this->rootContainer ?? $this;
341
    }
342
343
    /**
344
     * Returns a value indicating whether the container has already instantiated
345
     * instance of the specified name.
346
     * @param string $id class name, interface name or alias name
347
     * @return bool whether the container has instance of class specified.
348
     * @throws CircularReferenceException
349
     */
350
    public function hasInstance($id): bool
351
    {
352
        $reference = is_string($id) ? Reference::to($id) : $id;
0 ignored issues
show
introduced by
The condition is_string($id) is always true.
Loading history...
353
        $id = $reference->getId();
354
355
        return isset($this->instances[$id]);
356
    }
357
358
    /**
359
     * Returns all instances set in container
360
     * @return array list of instance
361
     */
362
    public function getInstances() : array
363
    {
364
        return $this->instances;
365
    }
366
}
367