Passed
Pull Request — master (#123)
by Dmitriy
01:45
created

Container   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 265
Duplicated Lines 0 %

Test Coverage

Coverage 97.33%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 67
c 1
b 0
f 0
dl 0
loc 265
ccs 73
cts 75
cp 0.9733
rs 9.6
wmc 35

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
A has() 0 3 2
A processDefinition() 0 4 2
A setMultiple() 0 4 2
A delegateLookup() 0 7 2
A get() 0 8 2
A getId() 0 3 2
A set() 0 5 1
A addProviders() 0 4 2
A buildProvider() 0 10 2
A build() 0 15 2
A buildInternal() 0 9 2
B validateDefinition() 0 23 7
A buildPrimitive() 0 9 2
A addProvider() 0 10 3
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\DynamicReference;
9
use Yiisoft\Factory\Definitions\Reference;
10
use Yiisoft\Factory\Exceptions\CircularReferenceException;
11
use Yiisoft\Factory\Exceptions\InvalidConfigException;
12
use Yiisoft\Factory\Exceptions\NotFoundException;
13
use Yiisoft\Factory\Exceptions\NotInstantiableException;
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 array 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 56
    public function __construct(
49
        array $definitions = [],
50
        array $providers = [],
51
        ContainerInterface $rootContainer = null
52
    ) {
53 56
        $this->setMultiple($definitions);
54 55
        $this->addProviders($providers);
55 54
        if ($rootContainer !== null) {
56 5
            $this->delegateLookup($rootContainer);
57
        }
58
    }
59
60
    /**
61
     * Returns a value indicating whether the container has the definition of the specified name.
62
     * @param string $id class name, interface name or alias name
63
     * @return bool whether the container is able to provide instance of class specified.
64
     * @see set()
65
     */
66 24
    public function has($id): bool
67
    {
68 24
        return isset($this->definitions[$id]) || class_exists($id);
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 50
    public function get($id)
85
    {
86 50
        $id = $this->getId($id);
87 50
        if (!isset($this->instances[$id])) {
88 50
            $this->instances[$id] = $this->build($id);
89
        }
90
91 41
        return $this->instances[$id];
92
    }
93
94
    /**
95
     * Delegate service lookup to another container.
96
     * @param ContainerInterface $container
97
     */
98 5
    protected function delegateLookup(ContainerInterface $container): void
99
    {
100 5
        if ($this->rootContainer === null) {
101 5
            $this->rootContainer = new CompositeContainer();
102
        }
103
104 5
        $this->rootContainer->attach($container);
0 ignored issues
show
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

104
        $this->rootContainer->/** @scrutinizer ignore-call */ 
105
                              attach($container);
Loading history...
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

104
        $this->rootContainer->/** @scrutinizer ignore-call */ 
105
                              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...
105
    }
106
107
    /**
108
     * Sets a definition to the container. Definition may be defined multiple ways.
109
     * @param string $id
110
     * @param mixed $definition
111
     * @throws InvalidConfigException
112
     * @see `Normalizer::normalize()`
113
     */
114 48
    protected function set(string $id, $definition): void
115
    {
116 48
        $this->validateDefinition($definition);
117 47
        $this->instances[$id] = null;
118 47
        $this->definitions[$id] = $definition;
119
    }
120
121
    /**
122
     * Sets multiple definitions at once.
123
     * @param array $config definitions indexed by their ids
124
     * @throws InvalidConfigException
125
     */
126 56
    protected function setMultiple(array $config): void
127
    {
128 56
        foreach ($config as $id => $definition) {
129 45
            $this->set($id, $definition);
130
        }
131
    }
132
133
    /**
134
     * Creates new instance by either interface name or alias.
135
     *
136
     * @param string $id the interface or an alias name that was previously registered via [[set()]].
137
     * @param array $params
138
     * @return object new built instance of the specified class.
139
     * @throws CircularReferenceException
140
     * @throws InvalidConfigException
141
     * @throws NotFoundException
142
     * @internal
143
     */
144 50
    private function build(string $id)
145
    {
146 50
        if (isset($this->building[$id])) {
147 7
            throw new CircularReferenceException(sprintf(
148 7
                'Circular reference to "%s" detected while building: %s',
149
                $id,
150 7
                implode(',', array_keys($this->building))
151
            ));
152
        }
153
154 50
        $this->building[$id] = 1;
155 50
        $object = $this->buildInternal($id);
156 41
        unset($this->building[$id]);
157
158 41
        return $object;
159
    }
160
161 42
    private function processDefinition($definition): void
162
    {
163 42
        if ($definition instanceof DeferredServiceProviderInterface) {
164 1
            $definition->register($this);
165
        }
166
    }
167
168 48
    private function validateDefinition($definition): void
169
    {
170 48
        if ($definition instanceof Reference || $definition instanceof DynamicReference) {
171 4
            return;
172
        }
173
174 47
        if (\is_string($definition)) {
175 38
            return;
176
        }
177
178 16
        if (\is_callable($definition)) {
179 5
            return;
180
        }
181
182 11
        if (\is_array($definition)) {
183 7
            return;
184
        }
185
186 4
        if (\is_object($definition)) {
187 3
            return;
188
        }
189
190 1
        throw new InvalidConfigException('Invalid definition:' . var_export($definition, true));
191
    }
192
193 50
    private function getId($id): string
194
    {
195 50
        return is_string($id) ? $id : $id->getId();
196
    }
197
198
    /**
199
     * @param string $id
200
     * @param array $params
201
     *
202
     * @return mixed|object
203
     * @throws InvalidConfigException
204
     * @throws NotFoundException
205
     */
206 50
    private function buildInternal(string $id)
207
    {
208 50
        if (!isset($this->definitions[$id])) {
209 36
            return $this->buildPrimitive($id);
210
        }
211 42
        $this->processDefinition($this->definitions[$id]);
212 42
        $definition = Normalizer::normalize($this->definitions[$id], $id);
213
214 42
        return $definition->resolve($this->rootContainer ?? $this);
215
    }
216
217
    /**
218
     * @param string $class
219
     * @param array $params
220
     *
221
     * @return mixed|object
222
     * @throws InvalidConfigException
223
     * @throws NotFoundException
224
     */
225 36
    private function buildPrimitive(string $class)
226
    {
227 36
        if (class_exists($class)) {
228 34
            $definition = new ArrayDefinition($class);
229
230 34
            return $definition->resolve($this->rootContainer ?? $this);
231
        }
232
233 3
        throw new NotFoundException("No definition for $class");
234
    }
235
236 55
    private function addProviders(array $providers): void
237
    {
238 55
        foreach ($providers as $provider) {
239 4
            $this->addProvider($provider);
240
        }
241
    }
242
243
    /**
244
     * Adds service provider to the container. Unless service provider is deferred
245
     * it would be immediately registered.
246
     *
247
     * @param string|array $providerDefinition
248
     *
249
     * @throws InvalidConfigException
250
     * @throws NotInstantiableException
251
     * @see ServiceProviderInterface
252
     * @see DeferredServiceProviderInterface
253
     */
254 4
    private function addProvider($providerDefinition): void
255
    {
256 4
        $provider = $this->buildProvider($providerDefinition);
257
258 3
        if ($provider instanceof DeferredServiceProviderInterface) {
259 1
            foreach ($provider->provides() as $id) {
260 1
                $this->definitions[$id] = $provider;
261
            }
262
        } else {
263 2
            $provider->register($this);
264
        }
265
    }
266
267
    /**
268
     * Builds service provider by definition.
269
     *
270
     * @param string|array $providerDefinition class name or definition of provider.
271
     * @return ServiceProviderInterface instance of service provider;
272
     *
273
     * @throws InvalidConfigException
274
     */
275 4
    private function buildProvider($providerDefinition): ServiceProviderInterface
276
    {
277 4
        $provider = Normalizer::normalize($providerDefinition)->resolve($this);
278 3
        if (!($provider instanceof ServiceProviderInterface)) {
279
            throw new InvalidConfigException(
280
                'Service provider should be an instance of ' . ServiceProviderInterface::class
281
            );
282
        }
283
284 3
        return $provider;
285
    }
286
}
287