Passed
Push — master ( dc44cc...086e3b )
by Alexander
34:11 queued 02:30
created

Container::addProviders()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 2
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 2
rs 10
1
<?php
2
3
namespace Yiisoft\Di;
4
5
use Psr\Container\ContainerInterface;
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
final class Container extends AbstractContainerConfigurator 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
    /**
33
     * @var object[]
34
     */
35
    private $instances;
36
37
    private ?ContainerInterface $rootContainer = null;
38
39
    /**
40
     * Container constructor.
41
     *
42
     * @param array $definitions
43
     * @param ServiceProviderInterface[] $providers
44
     *
45
     * @throws InvalidConfigException
46
     * @throws NotInstantiableException
47
     */
48 57
    public function __construct(
49
        array $definitions = [],
50
        array $providers = [],
51
        ContainerInterface $rootContainer = null
52
    ) {
53 57
        $this->setMultiple($definitions);
54 56
        $this->addProviders($providers);
55 55
        if ($rootContainer !== null) {
56 5
            $this->delegateLookup($rootContainer);
57
        }
58
    }
59
60 5
    protected function delegateLookup(ContainerInterface $container): void
61
    {
62 5
        if ($this->rootContainer === null) {
63 5
            $this->rootContainer = new CompositeContainer();
64
        }
65
66 5
        $this->rootContainer->attach($container);
0 ignored issues
show
Bug introduced by
The method attach() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

66
        $this->rootContainer->/** @scrutinizer ignore-call */ 
67
                              attach($container);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method attach() does not exist on Psr\Container\ContainerInterface. It seems like you code against a sub-type of Psr\Container\ContainerInterface such as Yiisoft\Di\CompositeContainer or Yiisoft\Di\CompositeContextContainer. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

66
        $this->rootContainer->/** @scrutinizer ignore-call */ 
67
                              attach($container);
Loading history...
67
    }
68
69
    /**
70
     * Returns an instance by either interface name or alias.
71
     *
72
     * Same instance of the class will be returned each time this method is called.
73
     *
74
     * @param string|Reference $id the interface or an alias name that was previously registered via [[set()]].
75
     * @param array $parameters parameters to set for the object obtained
76
     * @return object an instance of the requested interface.
77
     * @throws CircularReferenceException
78
     * @throws InvalidConfigException
79
     * @throws NotFoundException
80
     * @throws NotInstantiableException
81
     */
82 51
    public function get($id, array $parameters = [])
83
    {
84 51
        $id = $this->getId($id);
85 51
        if (!isset($this->instances[$id])) {
86 51
            $this->instances[$id] = $this->build($id, $parameters);
87
        }
88
89 42
        return $this->instances[$id];
90
    }
91
92 51
    private function getId($id): string
93
    {
94 51
        return is_string($id) ? $id : $id->getId();
95
    }
96
97
    /**
98
     * Creates new instance by either interface name or alias.
99
     *
100
     * @param string $id the interface or an alias name that was previously registered via [[set()]].
101
     * @param array $params
102
     * @return object new built instance of the specified class.
103
     * @throws CircularReferenceException
104
     * @throws InvalidConfigException
105
     * @throws NotFoundException
106
     * @internal
107
     */
108 51
    protected function build(string $id, array $params = [])
109
    {
110 51
        if (isset($this->building[$id])) {
111 7
            throw new CircularReferenceException(sprintf(
112 7
                'Circular reference to "%s" detected while building: %s',
113
                $id,
114 7
                implode(',', array_keys($this->building))
115
            ));
116
        }
117
118 51
        $this->building[$id] = 1;
119 51
        $object = $this->buildInternal($id, $params);
120 42
        unset($this->building[$id]);
121
122 42
        return $object;
123
    }
124
125
    /**
126
     * @param string $id
127
     * @param array $params
128
     *
129
     * @return mixed|object
130
     * @throws InvalidConfigException
131
     * @throws NotFoundException
132
     */
133 51
    private function buildInternal(string $id, array $params = [])
134
    {
135 51
        if (!isset($this->definitions[$id])) {
136 37
            return $this->buildPrimitive($id, $params);
137
        }
138 43
        $this->processDefinition($this->definitions[$id]);
139
140 43
        return $this->definitions[$id]->resolve($this->rootContainer ?? $this, $params);
141
    }
142
143 43
    protected function processDefinition($definition): void
144
    {
145 43
        if ($definition instanceof DeferredServiceProviderInterface) {
146 1
            $definition->register($this);
147
        }
148
    }
149
150
    /**
151
     * @param string $class
152
     * @param array $params
153
     *
154
     * @return mixed|object
155
     * @throws InvalidConfigException
156
     * @throws NotFoundException
157
     */
158 37
    private function buildPrimitive(string $class, array $params = [])
159
    {
160 37
        if (class_exists($class)) {
161 35
            $definition = new ArrayDefinition($class);
162
163 35
            return $definition->resolve($this->rootContainer ?? $this, $params);
164
        }
165
166 3
        throw new NotFoundException("No definition for $class");
167
    }
168
169
    /**
170
     * Sets a definition to the container. Definition may be defined multiple ways.
171
     * @param string $id
172
     * @param mixed $definition
173
     * @throws InvalidConfigException
174
     * @see `Normalizer::normalize()`
175
     */
176 49
    protected function set(string $id, $definition): void
177
    {
178 49
        $this->instances[$id] = null;
179 49
        $this->definitions[$id] = Normalizer::normalize($definition, $id);
180
    }
181
182
    /**
183
     * Sets multiple definitions at once.
184
     * @param array $config definitions indexed by their ids
185
     * @throws InvalidConfigException
186
     */
187 57
    protected function setMultiple(array $config): void
188
    {
189 57
        foreach ($config as $id => $definition) {
190 46
            $this->set($id, $definition);
191
        }
192
    }
193
194 56
    private function addProviders(array $providers): void
195
    {
196 56
        foreach ($providers as $provider) {
197 4
            $this->addProvider($provider);
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 25
    public function has($id): bool
208
    {
209 25
        return isset($this->definitions[$id]) || class_exists($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 ServiceProviderInterface
221
     * @see DeferredServiceProviderInterface
222
     */
223 4
    private function addProvider($providerDefinition): void
224
    {
225 4
        $provider = $this->buildProvider($providerDefinition);
226
227 3
        if ($provider instanceof DeferredServiceProviderInterface) {
228 1
            foreach ($provider->provides() as $id) {
229 1
                $this->definitions[$id] = $provider;
230
            }
231
        } else {
232 2
            $provider->register($this);
233
        }
234
    }
235
236
    /**
237
     * Builds service provider by definition.
238
     *
239
     * @param string|array $providerDefinition class name or definition of provider.
240
     * @return ServiceProviderInterface instance of service provider;
241
     *
242
     * @throws InvalidConfigException
243
     */
244 4
    private function buildProvider($providerDefinition): ServiceProviderInterface
245
    {
246 4
        $provider = Normalizer::normalize($providerDefinition)->resolve($this);
247 3
        if (!($provider instanceof ServiceProviderInterface)) {
248
            throw new InvalidConfigException(
249
                'Service provider should be an instance of ' . ServiceProviderInterface::class
250
            );
251
        }
252
253 3
        return $provider;
254
    }
255
256
    /**
257
     * Returns a value indicating whether the container has already instantiated
258
     * instance of the specified name.
259
     * @param string|Reference $id class name, interface name or alias name
260
     * @return bool whether the container has instance of class specified.
261
     */
262 1
    public function hasInstance($id): bool
263
    {
264 1
        $id = $this->getId($id);
265
266 1
        return isset($this->instances[$id]);
267
    }
268
269
    /**
270
     * Returns all instances set in container
271
     * @return array list of instance
272
     */
273
    public function getInstances(): array
274
    {
275
        return $this->instances;
276
    }
277
}
278