Completed
Push — master ( 5a160c...5540f7 )
by Matthieu
04:15
created

CompiledContainer::resolveFactory()   B

Complexity

Conditions 4
Paths 6

Duplication

Lines 9
Ratio 36 %

Size

Total Lines 25
Code Lines 15

Importance

Changes 0
Metric Value
cc 4
eloc 15
nc 6
nop 3
dl 9
loc 25
rs 8.5806
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace DI;
6
7
use DI\Compiler\RequestedEntryHolder;
8
use DI\Definition\Definition;
9
use DI\Definition\Exception\InvalidDefinition;
10
use DI\Invoker\FactoryParameterResolver;
11
use Invoker\Exception\NotCallableException;
12
use Invoker\Exception\NotEnoughParametersException;
13
use Invoker\Invoker;
14
use Invoker\InvokerInterface;
0 ignored issues
show
Bug introduced by Matthieu Napoli
This use statement conflicts with another class in this namespace, DI\InvokerInterface.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
15
use Invoker\ParameterResolver\AssociativeArrayResolver;
16
use Invoker\ParameterResolver\NumericArrayResolver;
17
use Invoker\ParameterResolver\ResolverChain;
18
19
/**
20
 * Compiled version of the dependency injection container.
21
 *
22
 * @author Matthieu Napoli <[email protected]>
23
 */
24
abstract class CompiledContainer extends Container
25
{
26
    /**
27
     * @var InvokerInterface
28
     */
29
    private $factoryInvoker;
30
31
    /**
32
     * {@inheritdoc}
33
     */
34
    public function get($name)
35
    {
36
        // Try to find the entry in the singleton map
37
        if (isset($this->resolvedEntries[$name]) || array_key_exists($name, $this->resolvedEntries)) {
38
            return $this->resolvedEntries[$name];
39
        }
40
41
        $method = static::METHOD_MAPPING[$name] ?? null;
42
43
        // If it's a compiled entry, then there is a method in this class
44
        if ($method !== null) {
45
            // Check if we are already getting this entry -> circular dependency
46
            if (isset($this->entriesBeingResolved[$name])) {
47
                throw new DependencyException("Circular dependency detected while trying to resolve entry '$name'");
48
            }
49
            $this->entriesBeingResolved[$name] = true;
50
51
            try {
52
                $value = $this->$method();
53
            } finally {
54
                unset($this->entriesBeingResolved[$name]);
55
            }
56
57
            // Store the entry to always return it without recomputing it
58
            $this->resolvedEntries[$name] = $value;
59
60
            return $value;
61
        }
62
63
        return parent::get($name);
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69
    public function has($name)
70
    {
71 View Code Duplication
        if (! is_string($name)) {
72
            throw new \InvalidArgumentException(sprintf(
73
                'The name parameter must be of type string, %s given',
74
                is_object($name) ? get_class($name) : gettype($name)
75
            ));
76
        }
77
78
        // The parent method is overridden to check in our array, it avoids resolving definitions
79
        if (isset(static::METHOD_MAPPING[$name])) {
80
            return true;
81
        }
82
83
        return parent::has($name);
84
    }
85
86
    protected function setDefinition(string $name, Definition $definition)
87
    {
88
        // It needs to be forbidden because that would mean get() must go through the definitions
89
        // every time, which kinds of defeats the performance gains of the compiled container
90
        throw new \LogicException('You cannot set a definition at runtime on a compiled container. You can either put your definitions in a file, disable compilation or ->set() a raw value directly (PHP object, string, int, ...) instead of a PHP-DI definition.');
91
    }
92
93
    /**
94
     * Invoke the given callable.
95
     */
96
    protected function resolveFactory($callable, $entryName, array $extraParameters = [])
97
    {
98
        // Initialize the factory resolver
99 View Code Duplication
        if (! $this->factoryInvoker) {
100
            $parameterResolver = new ResolverChain([
101
                new AssociativeArrayResolver,
102
                new FactoryParameterResolver($this->delegateContainer),
103
                new NumericArrayResolver,
104
            ]);
105
106
            $this->factoryInvoker = new Invoker($parameterResolver, $this->delegateContainer);
107
        }
108
109
        $parameters = [$this->delegateContainer, new RequestedEntryHolder($entryName)];
110
111
        $parameters = array_merge($parameters, $extraParameters);
112
113
        try {
114
            return $this->factoryInvoker->call($callable, $parameters);
115
        } catch (NotCallableException $e) {
116
            throw new InvalidDefinition("Entry \"$entryName\" cannot be resolved: factory " . $e->getMessage());
117
        } catch (NotEnoughParametersException $e) {
118
            throw new InvalidDefinition("Entry \"$entryName\" cannot be resolved: " . $e->getMessage());
119
        }
120
    }
121
}
122