Completed
Push — master ( ff0374...3c2b88 )
by Matthieu
02:06
created

Container::createDefaultDefinitionSource()   A

Complexity

Conditions 1
Paths 1

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 7
Code Lines 4

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace DI;
4
5
use DI\Definition\Definition;
6
use DI\Definition\FactoryDefinition;
7
use DI\Definition\Helper\DefinitionHelper;
8
use DI\Definition\InstanceDefinition;
9
use DI\Definition\ObjectDefinition;
10
use DI\Definition\Resolver\DefinitionResolver;
11
use DI\Definition\Resolver\ResolverDispatcher;
12
use DI\Definition\Source\CachedDefinitionSource;
13
use DI\Definition\Source\DefinitionArray;
14
use DI\Definition\Source\DefinitionSource;
15
use DI\Definition\Source\MutableDefinitionSource;
16
use DI\Definition\Source\ReflectionBasedAutowiring;
17
use DI\Definition\Source\SourceChain;
18
use DI\Invoker\DefinitionParameterResolver;
19
use DI\Proxy\ProxyFactory;
20
use Exception;
21
use Interop\Container\ContainerInterface as InteropContainerInterface;
22
use InvalidArgumentException;
23
use Invoker\Invoker;
24
use Invoker\ParameterResolver\AssociativeArrayResolver;
25
use Invoker\ParameterResolver\Container\TypeHintContainerResolver;
26
use Invoker\ParameterResolver\DefaultValueResolver;
27
use Invoker\ParameterResolver\NumericArrayResolver;
28
use Invoker\ParameterResolver\ResolverChain;
29
use Psr\Container\ContainerInterface;
30
31
/**
32
 * Dependency Injection Container.
33
 *
34
 * @author Matthieu Napoli <[email protected]>
35
 */
36
class Container implements ContainerInterface, InteropContainerInterface, FactoryInterface, \DI\InvokerInterface
37
{
38
    /**
39
     * Map of entries with Singleton scope that are already resolved.
40
     * @var array
41
     */
42
    private $singletonEntries = [];
43
44
    /**
45
     * @var DefinitionSource
46
     */
47
    private $definitionSource;
48
49
    /**
50
     * @var DefinitionResolver
51
     */
52
    private $definitionResolver;
53
54
    /**
55
     * Array of entries being resolved. Used to avoid circular dependencies and infinite loops.
56
     * @var array
57
     */
58
    private $entriesBeingResolved = [];
59
60
    /**
61
     * @var \Invoker\InvokerInterface|null
62
     */
63
    private $invoker;
64
65
    /**
66
     * Container that wraps this container. If none, points to $this.
67
     *
68
     * @var ContainerInterface
69
     */
70
    private $wrapperContainer;
71
72
    /**
73
     * Use `$container = new Container()` if you want a container with the default configuration.
74
     *
75
     * If you want to customize the container's behavior, you are discouraged to create and pass the
76
     * dependencies yourself, the ContainerBuilder class is here to help you instead.
77
     *
78
     * @see ContainerBuilder
79
     *
80
     * @param ContainerInterface $wrapperContainer If the container is wrapped by another container.
81
     */
82
    public function __construct(
83
        DefinitionSource $definitionSource = null,
84
        ProxyFactory $proxyFactory = null,
85
        ContainerInterface $wrapperContainer = null
86
    ) {
87
        $this->wrapperContainer = $wrapperContainer ?: $this;
88
89
        $this->definitionSource = $definitionSource ?: $this->createDefaultDefinitionSource();
90
        $proxyFactory = $proxyFactory ?: new ProxyFactory(false);
91
        $this->definitionResolver = new ResolverDispatcher($this->wrapperContainer, $proxyFactory);
92
93
        // Auto-register the container
94
        $this->singletonEntries[self::class] = $this;
95
        $this->singletonEntries[FactoryInterface::class] = $this;
96
        $this->singletonEntries[InvokerInterface::class] = $this;
97
        $this->singletonEntries[ContainerInterface::class] = $this;
98
    }
99
100
    /**
101
     * Returns an entry of the container by its name.
102
     *
103
     * @param string $name Entry name or a class name.
104
     *
105
     * @throws InvalidArgumentException The name parameter must be of type string.
106
     * @throws DependencyException Error while resolving the entry.
107
     * @throws NotFoundException No entry found for the given name.
108
     * @return mixed
109
     */
110
    public function get($name)
111
    {
112
        // Try to find the entry in the singleton map
113 View Code Duplication
        if (isset($this->singletonEntries[$name]) || array_key_exists($name, $this->singletonEntries)) {
114
            return $this->singletonEntries[$name];
115
        }
116
117
        $definition = $this->definitionSource->getDefinition($name);
118
        if (! $definition) {
119
            throw new NotFoundException("No entry or class found for '$name'");
120
        }
121
122
        $value = $this->resolveDefinition($definition);
123
124
        // If the entry is singleton, we store it to always return it without recomputing it
125
        if ($definition->getScope() === Scope::SINGLETON) {
126
            $this->singletonEntries[$name] = $value;
127
        }
128
129
        return $value;
130
    }
131
132
    /**
133
     * Build an entry of the container by its name.
134
     *
135
     * This method behave like get() except it forces the scope to "prototype",
136
     * which means the definition of the entry will be re-evaluated each time.
137
     * For example, if the entry is a class, then a new instance will be created each time.
138
     *
139
     * This method makes the container behave like a factory.
140
     *
141
     * @param string $name       Entry name or a class name.
142
     * @param array  $parameters Optional parameters to use to build the entry. Use this to force specific parameters
143
     *                           to specific values. Parameters not defined in this array will be resolved using
144
     *                           the container.
145
     *
146
     * @throws InvalidArgumentException The name parameter must be of type string.
147
     * @throws DependencyException Error while resolving the entry.
148
     * @throws NotFoundException No entry found for the given name.
149
     * @return mixed
150
     */
151
    public function make($name, array $parameters = [])
152
    {
153 View Code Duplication
        if (! is_string($name)) {
154
            throw new InvalidArgumentException(sprintf(
155
                'The name parameter must be of type string, %s given',
156
                is_object($name) ? get_class($name) : gettype($name)
157
            ));
158
        }
159
160
        $definition = $this->definitionSource->getDefinition($name);
161
        if (! $definition) {
162
            // Try to find the entry in the singleton map
163
            if (array_key_exists($name, $this->singletonEntries)) {
164
                return $this->singletonEntries[$name];
165
            }
166
167
            throw new NotFoundException("No entry or class found for '$name'");
168
        }
169
170
        return $this->resolveDefinition($definition, $parameters);
171
    }
172
173
    /**
174
     * Test if the container can provide something for the given name.
175
     *
176
     * @param string $name Entry name or a class name.
177
     *
178
     * @throws InvalidArgumentException The name parameter must be of type string.
179
     * @return bool
180
     */
181
    public function has($name)
182
    {
183 View Code Duplication
        if (! is_string($name)) {
184
            throw new InvalidArgumentException(sprintf(
185
                'The name parameter must be of type string, %s given',
186
                is_object($name) ? get_class($name) : gettype($name)
187
            ));
188
        }
189
190
        if (array_key_exists($name, $this->singletonEntries)) {
191
            return true;
192
        }
193
194
        $definition = $this->definitionSource->getDefinition($name);
195
        if ($definition === null) {
196
            return false;
197
        }
198
199
        return $this->definitionResolver->isResolvable($definition);
200
    }
201
202
    /**
203
     * Inject all dependencies on an existing instance.
204
     *
205
     * @param object $instance Object to perform injection upon
206
     * @throws InvalidArgumentException
207
     * @throws DependencyException Error while injecting dependencies
208
     * @return object $instance Returns the same instance
209
     */
210
    public function injectOn($instance)
211
    {
212
        $objectDefinition = $this->definitionSource->getDefinition(get_class($instance));
213
        if (! $objectDefinition instanceof ObjectDefinition) {
214
            return $instance;
215
        }
216
217
        $definition = new InstanceDefinition($instance, $objectDefinition);
218
219
        $this->definitionResolver->resolve($definition);
220
221
        return $instance;
222
    }
223
224
    /**
225
     * Call the given function using the given parameters.
226
     *
227
     * Missing parameters will be resolved from the container.
228
     *
229
     * @param callable $callable   Function to call.
230
     * @param array    $parameters Parameters to use. Can be indexed by the parameter names
231
     *                             or not indexed (same order as the parameters).
232
     *                             The array can also contain DI definitions, e.g. DI\get().
233
     *
234
     * @return mixed Result of the function.
235
     */
236
    public function call($callable, array $parameters = [])
237
    {
238
        return $this->getInvoker()->call($callable, $parameters);
239
    }
240
241
    /**
242
     * Define an object or a value in the container.
243
     *
244
     * @param string                 $name  Entry name
245
     * @param mixed|DefinitionHelper $value Value, use definition helpers to define objects
246
     */
247
    public function set($name, $value)
248
    {
249
        if ($value instanceof DefinitionHelper) {
250
            $value = $value->getDefinition($name);
251
        } elseif ($value instanceof \Closure) {
252
            $value = new FactoryDefinition($name, $value);
253
        }
254
255
        if ($value instanceof Definition) {
256
            $this->setDefinition($name, $value);
257
        } else {
258
            $this->singletonEntries[$name] = $value;
259
        }
260
    }
261
262
    /**
263
     * Resolves a definition.
264
     *
265
     * Checks for circular dependencies while resolving the definition.
266
     *
267
     * @param Definition $definition
268
     * @param array      $parameters
269
     *
270
     * @throws DependencyException Error while resolving the entry.
271
     * @return mixed
272
     */
273
    private function resolveDefinition(Definition $definition, array $parameters = [])
274
    {
275
        $entryName = $definition->getName();
276
277
        // Check if we are already getting this entry -> circular dependency
278
        if (isset($this->entriesBeingResolved[$entryName])) {
279
            throw new DependencyException("Circular dependency detected while trying to resolve entry '$entryName'");
280
        }
281
        $this->entriesBeingResolved[$entryName] = true;
282
283
        // Resolve the definition
284
        try {
285
            $value = $this->definitionResolver->resolve($definition, $parameters);
286
        } catch (Exception $exception) {
287
            unset($this->entriesBeingResolved[$entryName]);
288
            throw $exception;
289
        }
290
291
        unset($this->entriesBeingResolved[$entryName]);
292
293
        return $value;
294
    }
295
296
    private function setDefinition($name, Definition $definition)
297
    {
298
        if ($this->definitionSource instanceof CachedDefinitionSource) {
299
            throw new \LogicException('You cannot set a definition at runtime on a container that has a cache configured. Doing so would risk caching the definition for the next execution, where it might be different. You can either put your definitions in a file, remove the cache or ->set() a raw value directly (PHP object, string, int, ...) instead of a PHP-DI definition.');
300
        }
301
302
        if (! $this->definitionSource instanceof MutableDefinitionSource) {
303
            // This can happen if you instantiate the container yourself
304
            throw new \LogicException('The container has not been initialized correctly');
305
        }
306
307
        // Clear existing entry if it exists
308
        if (array_key_exists($name, $this->singletonEntries)) {
309
            unset($this->singletonEntries[$name]);
310
        }
311
312
        $this->definitionSource->addDefinition($definition);
313
    }
314
315
    /**
316
     * @return \Invoker\InvokerInterface
317
     */
318
    private function getInvoker()
319
    {
320
        if (! $this->invoker) {
321
            $parameterResolver = new ResolverChain([
322
                new DefinitionParameterResolver($this->definitionResolver),
323
                new NumericArrayResolver,
324
                new AssociativeArrayResolver,
325
                new DefaultValueResolver,
326
                new TypeHintContainerResolver($this->wrapperContainer),
0 ignored issues
show
Compatibility introduced by Matthieu Napoli
$this->wrapperContainer of type object<Psr\Container\ContainerInterface> is not a sub-type of object<Interop\Container\ContainerInterface>. It seems like you assume a child interface of the interface Psr\Container\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
327
            ]);
328
329
            $this->invoker = new Invoker($parameterResolver, $this);
330
        }
331
332
        return $this->invoker;
333
    }
334
335
    /**
336
     * @return DefinitionSource
337
     */
338
    private function createDefaultDefinitionSource()
339
    {
340
        $source = new SourceChain([new ReflectionBasedAutowiring]);
341
        $source->setMutableDefinitionSource(new DefinitionArray([], new ReflectionBasedAutowiring));
342
343
        return $source;
344
    }
345
}
346