Passed
Push — master ( ded9bb...1f71d8 )
by Alexander
14:17 queued 11:01
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 contracts\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 31
    public function __construct(
52
        array $definitions = [],
53
        array $providers = []
54
    ) {
55 31
        $this->setMultiple($definitions);
56 30
        $this->deferredProviders = new SplObjectStorage();
57 30
        foreach ($providers as $provider) {
58
            $this->addProvider($provider);
59
        }
60 30
    }
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 28
    public function get($id, array $parameters = [])
76
    {
77 28
        $id = $this->getId($id);
78 28
        if (!isset($this->instances[$id])) {
79 28
            $this->instances[$id] = $this->build($id, $parameters);
80
        }
81
82 22
        return $this->instances[$id];
83
    }
84
85 28
    public function getId($id): string
86
    {
87 28
        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
     * @internal
98
     */
99 28
    protected function build(string $id, array $params = [])
100
    {
101 28
        if (isset($this->building[$id])) {
102 4
            throw new CircularReferenceException(sprintf(
103 4
                'Circular reference to "%s" detected while building: %s',
104 4
                $id,
105 4
                implode(',', array_keys($this->building))
106
            ));
107
        }
108
109 28
        $this->building[$id] = 1;
110 28
        $this->registerProviderIfDeferredFor($id);
111 28
        $object = $this->buildInternal($id, $params);
112 22
        unset($this->building[$id]);
113
114 22
        return $object;
115
    }
116
117 28
    private function buildInternal(string $id, array $params = [])
118
    {
119 28
        if (!isset($this->definitions[$id])) {
120 17
            return $this->buildPrimitive($id, $params);
121
        }
122
123 24
        return $this->definitions[$id]->resolve($this, $params);
124
    }
125
126 17
    private function buildPrimitive(string $class, array $params = [])
127
    {
128 17
        if (class_exists($class)) {
129 16
            $definition = new ArrayDefinition($class);
130
131 16
            return $definition->resolve($this, $params);
132
        }
133
134 2
        throw new NotFoundException("No definition for $class");
135
    }
136
137
    /**
138
     * Register providers from {@link deferredProviders} if they provide
139
     * definition for given identifier.
140
     *
141
     * @param string $id class or identifier of a service.
142
     */
143 28
    private function registerProviderIfDeferredFor(string $id): void
144
    {
145 28
        $providers = $this->deferredProviders;
146
147 28
        foreach ($providers as $provider) {
148 1
            if ($provider->hasDefinitionFor($id)) {
149 1
                $provider->register($this);
150
151
                // provider should be removed after registration to not be registered again
152 1
                $providers->detach($provider);
153
            }
154
        }
155 28
    }
156
157
    /**
158
     * Sets a definition to the container. Definition may be defined multiple ways.
159
     * @param string $id
160
     * @param mixed $definition
161
     * @throws InvalidConfigException
162
     * @see `Normalizer::normalize()`
163
     */
164 27
    public function set(string $id, $definition): void
165
    {
166 27
        $this->instances[$id] = null;
167 27
        $this->definitions[$id] = Normalizer::normalize($definition, $id);
168 26
    }
169
170
    /**
171
     * Sets multiple definitions at once.
172
     * @param array $config definitions indexed by their ids
173
     * @throws InvalidConfigException
174
     */
175 31
    public function setMultiple(array $config): void
176
    {
177 31
        foreach ($config as $id => $definition) {
178 7
            $this->set($id, $definition);
179
        }
180 30
    }
181
182
    /**
183
     * Returns a value indicating whether the container has the definition of the specified name.
184
     * @param string $id class name, interface name or alias name
185
     * @return bool whether the container is able to provide instance of class specified.
186
     * @see set()
187
     */
188 6
    public function has($id): bool
189
    {
190 6
        return isset($this->definitions[$id]) || (is_string($id) && class_exists($id));
191
    }
192
193
    /**
194
     * Adds service provider to the container. Unless service provider is deferred
195
     * it would be immediately registered.
196
     *
197
     * @param string|array $providerDefinition
198
     *
199
     * @throws InvalidConfigException
200
     * @throws NotInstantiableException
201
     * @see ServiceProviderInterface
202
     * @see DeferredServiceProviderInterface
203
     */
204 4
    public function addProvider($providerDefinition): void
205
    {
206 4
        $provider = $this->buildProvider($providerDefinition);
207
208 3
        if ($provider instanceof DeferredServiceProviderInterface) {
209 1
            $this->deferredProviders->attach($provider);
210
        } else {
211 2
            $provider->register($this);
212
        }
213 3
    }
214
215
    /**
216
     * Builds service provider by definition.
217
     *
218
     * @param string|array $providerDefinition class name or definition of provider.
219
     * @return ServiceProviderInterface instance of service provider;
220
     *
221
     * @throws InvalidConfigException
222
     */
223 4
    private function buildProvider($providerDefinition): ServiceProviderInterface
224
    {
225 4
        $provider = Normalizer::normalize($providerDefinition)->resolve($this);
226 3
        if (!($provider instanceof ServiceProviderInterface)) {
227
            throw new InvalidConfigException(
228
                'Service provider should be an instance of ' . ServiceProviderInterface::class
229
            );
230
        }
231
232 3
        return $provider;
233
    }
234
235
    /**
236
     * Returns a value indicating whether the container has already instantiated
237
     * instance of the specified name.
238
     * @param string|Reference $id class name, interface name or alias name
239
     * @return bool whether the container has instance of class specified.
240
     */
241 1
    public function hasInstance($id): bool
242
    {
243 1
        $id = $this->getId($id);
244
245 1
        return isset($this->instances[$id]);
246
    }
247
248
    /**
249
     * Returns all instances set in container
250
     * @return array list of instance
251
     */
252
    public function getInstances() : array
253
    {
254
        return $this->instances;
255
    }
256
}
257