Completed
Push — master ( 88ea05...dd33b4 )
by Alexander
02:50
created

Container::getInstances()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
namespace Yiisoft\Di;
3
4
use Psr\Container\ContainerInterface;
5
use SplObjectStorage;
6
use Yiisoft\Di\Contracts\DeferredServiceProviderInterface;
7
use Yiisoft\Di\Contracts\ServiceProviderInterface;
8
use Yiisoft\Factory\Definitions\Reference;
9
use Yiisoft\Factory\Exceptions\CircularReferenceException;
10
use Yiisoft\Factory\Exceptions\InvalidConfigException;
11
use Yiisoft\Factory\Exceptions\NotFoundException;
12
use Yiisoft\Factory\Exceptions\NotInstantiableException;
13
use Yiisoft\Factory\Definitions\DefinitionInterface;
14
use Yiisoft\Factory\Definitions\Normalizer;
15
use Yiisoft\Factory\Definitions\ArrayDefinition;
16
17
/**
18
 * Container implements a [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection) container.
19
 */
20
class Container implements ContainerInterface
21
{
22
    /**
23
     * @var DefinitionInterface[] object definitions indexed by their types
24
     */
25
    private $definitions = [];
26
    /**
27
     * @var array used to collect ids instantiated during build
28
     * to detect circular references
29
     */
30
    private $building = [];
31
    /**
32
     * @var DeferredServiceProviderInterface[]|\SplObjectStorage list of providers
33
     * deferred to register till their services would be requested
34
     */
35
    private $deferredProviders;
36
37
    /**
38
     * @var object[]
39
     */
40
    private $instances;
41
42
    /**
43
     * Container constructor.
44
     *
45
     * @param array $definitions
46
     * @param ServiceProviderInterface[] $providers
47
     *
48
     * @throws InvalidConfigException
49
     * @throws NotInstantiableException
50
     */
51 32
    public function __construct(
52
        array $definitions = [],
53
        array $providers = []
54
    ) {
55 32
        $this->setMultiple($definitions);
56 31
        $this->deferredProviders = new SplObjectStorage();
57 31
        foreach ($providers as $provider) {
58
            $this->addProvider($provider);
59
        }
60 31
    }
61
62
    /**
63
     * Returns an instance by either interface name or alias.
64
     *
65
     * Same instance of the class will be returned each time this method is called.
66
     *
67
     * @param string|Reference $id the interface or an alias name that was previously registered via [[set()]].
68
     * @param array $parameters parameters to set for the object obtained
69
     * @return object an instance of the requested interface.
70
     * @throws CircularReferenceException
71
     * @throws InvalidConfigException
72
     * @throws NotFoundException
73
     * @throws NotInstantiableException
74
     */
75 29
    public function get($id, array $parameters = [])
76
    {
77 29
        $id = $this->getId($id);
78 29
        if (!isset($this->instances[$id])) {
79 29
            $this->instances[$id] = $this->build($id, $parameters);
80
        }
81
82 23
        return $this->instances[$id];
83
    }
84
85 29
    public function getId($id): string
86
    {
87 29
        return is_string($id) ? $id : $id->getId();
88
    }
89
90
    /**
91
     * Creates new instance by either interface name or alias.
92
     *
93
     * @param string $id the interface or an alias name that was previously registered via [[set()]].
94
     * @param array $params
95
     * @return object new built instance of the specified class.
96
     * @throws CircularReferenceException
97
     * @throws InvalidConfigException
98
     * @throws NotFoundException
99
     * @internal
100
     */
101 29
    protected function build(string $id, array $params = [])
102
    {
103 29
        if (isset($this->building[$id])) {
104 5
            throw new CircularReferenceException(sprintf(
105 5
                'Circular reference to "%s" detected while building: %s',
106
                $id,
107 5
                implode(',', array_keys($this->building))
108
            ));
109
        }
110
111 29
        $this->building[$id] = 1;
112 29
        $this->registerProviderIfDeferredFor($id);
113 29
        $object = $this->buildInternal($id, $params);
114 23
        unset($this->building[$id]);
115
116 23
        return $object;
117
    }
118
119
    /**
120
     * @param string $id
121
     * @param array $params
122
     *
123
     * @return mixed|object
124
     * @throws InvalidConfigException
125
     * @throws NotFoundException
126
     */
127 29
    private function buildInternal(string $id, array $params = [])
128
    {
129 29
        if (!isset($this->definitions[$id])) {
130 17
            return $this->buildPrimitive($id, $params);
131
        }
132
133 25
        return $this->definitions[$id]->resolve($this, $params);
134
    }
135
136
    /**
137
     * @param string $class
138
     * @param array $params
139
     *
140
     * @return mixed|object
141
     * @throws InvalidConfigException
142
     * @throws NotFoundException
143
     */
144 17
    private function buildPrimitive(string $class, array $params = [])
145
    {
146 17
        if (class_exists($class)) {
147 16
            $definition = new ArrayDefinition($class);
148
149 16
            return $definition->resolve($this, $params);
150
        }
151
152 2
        throw new NotFoundException("No definition for $class");
153
    }
154
155
    /**
156
     * Register providers from {@link deferredProviders} if they provide
157
     * definition for given identifier.
158
     *
159
     * @param string $id class or identifier of a service.
160
     */
161 29
    private function registerProviderIfDeferredFor(string $id): void
162
    {
163 29
        $providers = $this->deferredProviders;
164
165 29
        foreach ($providers as $provider) {
166 1
            if ($provider->hasDefinitionFor($id)) {
167 1
                $provider->register($this);
168
169
                // provider should be removed after registration to not be registered again
170 1
                $providers->detach($provider);
171
            }
172
        }
173 29
    }
174
175
    /**
176
     * Sets a definition to the container. Definition may be defined multiple ways.
177
     * @param string $id
178
     * @param mixed $definition
179
     * @throws InvalidConfigException
180
     * @see `Normalizer::normalize()`
181
     */
182 28
    public function set(string $id, $definition): void
183
    {
184 28
        $this->instances[$id] = null;
185 28
        $this->definitions[$id] = Normalizer::normalize($definition, $id);
186 27
    }
187
188
    /**
189
     * Sets multiple definitions at once.
190
     * @param array $config definitions indexed by their ids
191
     * @throws InvalidConfigException
192
     */
193 32
    public function setMultiple(array $config): void
194
    {
195 32
        foreach ($config as $id => $definition) {
196 7
            $this->set($id, $definition);
197
        }
198 31
    }
199
200
    /**
201
     * Returns a value indicating whether the container has the definition of the specified name.
202
     * @param string $id class name, interface name or alias name
203
     * @return bool whether the container is able to provide instance of class specified.
204
     * @see set()
205
     */
206 6
    public function has($id): bool
207
    {
208 6
        return isset($this->definitions[$id]) || class_exists($id);
209
    }
210
211
    /**
212
     * Adds service provider to the container. Unless service provider is deferred
213
     * it would be immediately registered.
214
     *
215
     * @param string|array $providerDefinition
216
     *
217
     * @throws InvalidConfigException
218
     * @throws NotInstantiableException
219
     * @see ServiceProviderInterface
220
     * @see DeferredServiceProviderInterface
221
     */
222 4
    public function addProvider($providerDefinition): void
223
    {
224 4
        $provider = $this->buildProvider($providerDefinition);
225
226 3
        if ($provider instanceof DeferredServiceProviderInterface) {
227 1
            $this->deferredProviders->attach($provider);
228
        } else {
229 2
            $provider->register($this);
230
        }
231 3
    }
232
233
    /**
234
     * Builds service provider by definition.
235
     *
236
     * @param string|array $providerDefinition class name or definition of provider.
237
     * @return ServiceProviderInterface instance of service provider;
238
     *
239
     * @throws InvalidConfigException
240
     */
241 4
    private function buildProvider($providerDefinition): ServiceProviderInterface
242
    {
243 4
        $provider = Normalizer::normalize($providerDefinition)->resolve($this);
244 3
        if (!($provider instanceof ServiceProviderInterface)) {
245
            throw new InvalidConfigException(
246
                'Service provider should be an instance of ' . ServiceProviderInterface::class
247
            );
248
        }
249
250 3
        return $provider;
251
    }
252
253
    /**
254
     * Returns a value indicating whether the container has already instantiated
255
     * instance of the specified name.
256
     * @param string|Reference $id class name, interface name or alias name
257
     * @return bool whether the container has instance of class specified.
258
     */
259 1
    public function hasInstance($id): bool
260
    {
261 1
        $id = $this->getId($id);
262
263 1
        return isset($this->instances[$id]);
264
    }
265
266
    /**
267
     * Returns all instances set in container
268
     * @return array list of instance
269
     */
270
    public function getInstances() : array
271
    {
272
        return $this->instances;
273
    }
274
}
275