Completed
Push — master ( fadeb4...d2fa33 )
by Alexander
02:05
created

Container::addProvider()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 8
ccs 0
cts 7
cp 0
crap 6
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\ServiceProviderInterace;
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;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Factory\Definitions\DefinitionInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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 ServiceProviderInterace[] $providers
47
     *
48
     * @throws InvalidConfigException
49
     * @throws NotInstantiableException
50
     */
51
    public function __construct(
52
        array $definitions = [],
53
        array $providers = []
54
    ) {
55
        $this->setMultiple($definitions);
56
        $this->deferredProviders = new SplObjectStorage();
57
        foreach ($providers as $provider) {
58
            $this->addProvider($provider);
59
        }
60
    }
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
    public function get($id, array $parameters = [])
76
    {
77
        $id = $this->getId($id);
78
        if (!isset($this->instances[$id])) {
79
            $this->instances[$id] = $this->build($id, $parameters);
80
        }
81
82
        return $this->instances[$id];
83
    }
84
85
    public function getId($id): string
86
    {
87
        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
    protected function build(string $id, array $params = [])
100
    {
101
        if (isset($this->building[$id])) {
102
            throw new CircularReferenceException(sprintf(
103
                'Circular reference to "%s" detected while building: %s',
104
                $id,
105
                implode(',', array_keys($this->building))
106
            ));
107
        }
108
109
        $this->building[$id] = 1;
110
        $this->registerProviderIfDeferredFor($id);
111
        $object = $this->buildInternal($id, $params);
112
        unset($this->building[$id]);
113
114
        return $object;
115
    }
116
117
    private function buildInternal(string $id, array $params = [])
118
    {
119
        if (!isset($this->definitions[$id])) {
120
            return $this->buildPrimitive($id, $params);
121
        }
122
123
        return $this->definitions[$id]->resolve($this, $params);
124
    }
125
126
    private function buildPrimitive(string $class, array $params = [])
127
    {
128
        if (class_exists($class)) {
129
            $definition = new ArrayDefinition($class);
130
131
            return $definition->resolve($this, $params);
132
        }
133
134
        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
    private function registerProviderIfDeferredFor(string $id): void
144
    {
145
        $providers = $this->deferredProviders;
146
147
        foreach ($providers as $provider) {
148
            if ($provider->hasDefinitionFor($id)) {
149
                $provider->register($this);
150
151
                // provider should be removed after registration to not be registered again
152
                $providers->detach($provider);
153
            }
154
        }
155
    }
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
    public function set(string $id, $definition): void
165
    {
166
        $this->instances[$id] = null;
167
        $this->definitions[$id] = Normalizer::normalize($definition, $id);
168
    }
169
170
    /**
171
     * Sets multiple definitions at once.
172
     * @param array $config definitions indexed by their ids
173
     * @throws InvalidConfigException
174
     */
175
    public function setMultiple(array $config): void
176
    {
177
        foreach ($config as $id => $definition) {
178
            $this->set($id, $definition);
179
        }
180
    }
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
    public function has($id): bool
189
    {
190
        return isset($this->definitions[$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 ServiceProviderInterace
202
     * @see DeferredServiceProviderInterface
203
     */
204
    public function addProvider($providerDefinition): void
205
    {
206
        $provider = $this->buildProvider($providerDefinition);
207
208
        if ($provider instanceof DeferredServiceProviderInterface) {
209
            $this->deferredProviders->attach($provider);
210
        } else {
211
            $provider->register($this);
212
        }
213
    }
214
215
    /**
216
     * Builds service provider by definition.
217
     *
218
     * @param string|array $providerDefinition class name or definition of provider.
219
     * @return ServiceProviderInterace instance of service provider;
220
     *
221
     * @throws InvalidConfigException
222
     */
223
    private function buildProvider($providerDefinition): ServiceProviderInterace
224
    {
225
        $provider = Normalizer::normalize($providerDefinition)->resolve($this);
226
        if (!($provider instanceof ServiceProviderInterace)) {
227
            throw new InvalidConfigException(
228
                'Service provider should be an instance of ' . ServiceProviderInterace::class
229
            );
230
        }
231
232
        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
    public function hasInstance($id): bool
242
    {
243
        $id = $this->getId($id);
244
245
        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