Completed
Push — master ( 155292...0503ab )
by Alexander
60:25 queued 45:26
created

Container::hasInstance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
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
    /**
52
     * Container constructor.
53
     *
54
     * @param array $definitions
55
     * @param ServiceProvider[] $providers
56
     *
57
     * @throws InvalidConfigException
58
     * @throws NotInstantiableException
59
     */
60
    public function __construct(
61
        array $definitions = [],
62
        array $providers = []
63
    ) {
64
        $this->setMultiple($definitions);
65
        $this->deferredProviders = new SplObjectStorage();
66
        foreach ($providers as $provider) {
67
            $this->addProvider($provider);
68
        }
69
    }
70
71
    /**
72
     * Returns an instance by either interface name or alias.
73
     *
74
     * Same instance of the class will be returned each time this method is called.
75
     *
76
     * @param string|Reference $id the interface or an alias name that was previously registered via [[set()]].
77
     * @param array $parameters parameters to set for the object obtained
78
     * @return object an instance of the requested interface.
79
     * @throws CircularReferenceException
80
     * @throws InvalidConfigException
81
     * @throws NotFoundException
82
     * @throws NotInstantiableException
83
     */
84
    public function get($id, array $parameters = [])
85
    {
86
        $id = $this->getId($id);
87
        if (!isset($this->instances[$id])) {
88
            $this->instances[$id] = $this->build($id, $parameters);
89
        }
90
91
        return $this->instances[$id];
92
    }
93
94
    public function getId($id): string
95
    {
96
        return is_string($id) ? $id : $id->getId();
97
    }
98
99
    /**
100
     * Returns normalized definition for a given id
101
     * @param string $id
102
     * @return Definition|null
103
     */
104
    public function getDefinition(string $id): ?Definition
105
    {
106
        return $this->definitions[$id] ?? null;
107
    }
108
109
    /**
110
     * Creates new instance by either interface name or alias.
111
     *
112
     * @param string $id the interface or an alias name that was previously registered via [[set()]].
113
     * @param array $params
114
     * @return object new built instance of the specified class.
115
     * @throws CircularReferenceException
116
     * @internal
117
     */
118
    protected function build(string $id, array $params = [])
119
    {
120
        if (isset($this->building[$id])) {
121
            throw new CircularReferenceException(sprintf(
122
                'Circular reference to "%s" detected while building: %s',
123
                $id,
124
                implode(',', array_keys($this->building))
125
            ));
126
        }
127
128
        $this->building[$id] = 1;
129
        $this->registerProviderIfDeferredFor($id);
130
        $object = $this->buildInternal($id, $params);
131
        unset($this->building[$id]);
132
133
        return $object;
134
    }
135
136
    private function buildInternal(string $id, array $params = [])
137
    {
138
        if (!isset($this->definitions[$id])) {
139
            return $this->buildPrimitive($id, $params);
140
        }
141
142
        return $this->definitions[$id]->resolve($this, $params);
143
    }
144
145
    private function buildPrimitive(string $class, array $params = [])
146
    {
147
        if (class_exists($class)) {
148
            $definition = new ArrayDefinition($class);
149
150
            return $definition->resolve($this, $params);
151
        }
152
153
        throw new NotFoundException("No definition for $class");
154
    }
155
156
    /**
157
     * Register providers from {@link deferredProviders} if they provide
158
     * definition for given identifier.
159
     *
160
     * @param string $id class or identifier of a service.
161
     */
162
    private function registerProviderIfDeferredFor(string $id): void
163
    {
164
        $providers = $this->deferredProviders;
165
166
        foreach ($providers as $provider) {
167
            if ($provider->hasDefinitionFor($id)) {
168
                $provider->register($this);
169
170
                // provider should be removed after registration to not be registered again
171
                $providers->detach($provider);
172
            }
173
        }
174
    }
175
176
    /**
177
     * Sets a definition to the container. Definition may be defined multiple ways.
178
     * @param string $id
179
     * @param mixed $definition
180
     * @throws InvalidConfigException
181
     * @see `Normalizer::normalize()`
182
     */
183
    public function set(string $id, $definition): void
184
    {
185
        $this->instances[$id] = null;
186
        $this->definitions[$id] = Normalizer::normalize($definition, $id);
187
    }
188
189
    /**
190
     * Sets multiple definitions at once.
191
     * @param array $config definitions indexed by their ids
192
     * @throws InvalidConfigException
193
     */
194
    public function setMultiple(array $config): void
195
    {
196
        foreach ($config as $id => $definition) {
197
            $this->set($id, $definition);
198
        }
199
    }
200
201
    /**
202
     * Returns a value indicating whether the container has the definition of the specified name.
203
     * @param string $id class name, interface name or alias name
204
     * @return bool whether the container is able to provide instance of class specified.
205
     * @see set()
206
     */
207
    public function has($id): bool
208
    {
209
        return isset($this->definitions[$id]);
210
    }
211
212
    /**
213
     * Adds service provider to the container. Unless service provider is deferred
214
     * it would be immediately registered.
215
     *
216
     * @param string|array $providerDefinition
217
     *
218
     * @throws InvalidConfigException
219
     * @throws NotInstantiableException
220
     * @see ServiceProvider
221
     * @see DeferredServiceProvider
222
     */
223
    public function addProvider($providerDefinition): void
224
    {
225
        $provider = $this->buildProvider($providerDefinition);
226
227
        if ($provider instanceof DeferredServiceProvider) {
228
            $this->deferredProviders->attach($provider);
229
        } else {
230
            $provider->register($this);
231
        }
232
    }
233
234
    /**
235
     * Builds service provider by definition.
236
     *
237
     * @param string|array $providerDefinition class name or definition of provider.
238
     * @return ServiceProvider instance of service provider;
239
     *
240
     * @throws InvalidConfigException
241
     */
242
    private function buildProvider($providerDefinition): ServiceProvider
243
    {
244
        $provider = Normalizer::normalize($providerDefinition)->resolve($this);
245
        if (!($provider instanceof ServiceProvider)) {
246
            throw new InvalidConfigException(
247
                'Service provider should be an instance of ' . ServiceProvider::class
248
            );
249
        }
250
251
        return $provider;
252
    }
253
254
    /**
255
     * Returns injector.
256
     *
257
     * @return Injector
258
     */
259
    public function getInjector(): Injector
260
    {
261
        if ($this->injector === null) {
262
            $this->injector = new Injector($this);
263
        }
264
265
        return $this->injector;
266
    }
267
268
    /**
269
     * Returns a value indicating whether the container has already instantiated
270
     * instance of the specified name.
271
     * @param string|Reference $id class name, interface name or alias name
272
     * @return bool whether the container has instance of class specified.
273
     */
274
    public function hasInstance($id): bool
275
    {
276
        $id = $this->getId($id);
277
278
        return isset($this->instances[$id]);
279
    }
280
281
    /**
282
     * Returns all instances set in container
283
     * @return array list of instance
284
     */
285
    public function getInstances() : array
286
    {
287
        return $this->instances;
288
    }
289
}
290