Test Failed
Pull Request — master (#145)
by Dmitriy
12:25 queued 12s
created

DependencyResolver::create()   B

Complexity

Conditions 7
Paths 34

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 15
c 0
b 0
f 0
nc 34
nop 1
dl 0
loc 22
ccs 10
cts 10
cp 1
crap 7
rs 8.8333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Factory;
6
7
use Psr\Container\ContainerExceptionInterface;
8
use Psr\Container\ContainerInterface;
9
use Psr\Container\NotFoundExceptionInterface;
10
use Yiisoft\Definitions\ArrayDefinition;
11
use Yiisoft\Definitions\Contract\DefinitionInterface;
12
use Yiisoft\Definitions\Contract\DependencyResolverInterface;
13
use Yiisoft\Definitions\Contract\ReferenceInterface;
14
use Yiisoft\Definitions\Exception\CircularReferenceException;
15
use Yiisoft\Definitions\Exception\InvalidConfigException;
16
use Yiisoft\Definitions\Exception\NotFoundException;
17
use Yiisoft\Definitions\Exception\NotInstantiableClassException;
18
use Yiisoft\Definitions\Exception\NotInstantiableException;
19
use Yiisoft\Definitions\Infrastructure\Normalizer;
20
21
use function is_object;
22
use function is_string;
23
24
/**
25
 * @internal
26
 */
27
final class DependencyResolver implements ContainerInterface
28
{
29
    private ?ContainerInterface $container;
30
31
    /**
32
     * @var mixed[] Definitions
33
     * @psalm-var array<string, mixed>
34
     */
35
    private array $definitions = [];
36
37
    /**
38
     * @var DefinitionInterface[] object definitions indexed by their types
39
     * @psalm-var array<string, DefinitionInterface>
40
     */
41
    private array $definitionInstances = [];
42
43
    /**
44
     * @var array used to collect IDs instantiated during build to detect circular references
45
     *
46
     * @psalm-var array<string,1>
47
     */
48
    private array $creatingIds = [];
49
50 84
    public function __construct(?ContainerInterface $container)
51
    {
52 84
        $this->container = $container;
53 84
    }
54
55
    /**
56
     * @throws NotFoundExceptionInterface
57
     * @throws ContainerExceptionInterface
58
     *
59
     * @return mixed|object
60
     *
61
     * @psalm-suppress InvalidThrow
62
     */
63 18
    public function get(string $id)
64
    {
65 18
        if (isset($this->definitions[$id])) {
66 10
            return $this->getFromFactory($id);
67
        }
68
69 9
        if (class_exists($id)) {
70 2
            return $this->getFromFactory($id);
71
        }
72
73 7
        throw new NotInstantiableClassException($id);
74 5
    }
75
76
    public function has(string $id): bool
77 2
    {
78
        return isset($this->definitions[$id]) || ($this->container !== null && $this->container->has($id)) || class_exists($id);
79
    }
80 23
81
    /**
82 23
     * @param mixed $definition
83
     */
84
    public function setFactoryDefinition(string $id, $definition): void
85
    {
86
        $this->definitions[$id] = $definition;
87
    }
88 49
89
    /**
90 49
     * @param mixed $config
91 49
     *
92
     * @throws CircularReferenceException
93
     * @throws NotFoundException
94
     * @throws NotInstantiableException
95
     * @throws InvalidConfigException
96
     *
97
     * @return mixed
98
     */
99
    public function create($config)
100
    {
101
        if (is_string($config)) {
102
            if ($this->canBeCreatedByFactory($config)) {
103 77
                return $this->getFromFactory($config);
104
            }
105 77
            throw new NotFoundException($config);
106 60
        }
107 57
108
        $definition = $this->createDefinition($config);
109 3
110
        if ($definition instanceof ArrayDefinition) {
111
            $definition->setReferenceContainer($this);
0 ignored issues
show
Bug introduced by
The method setReferenceContainer() does not exist on Yiisoft\Definitions\ArrayDefinition. ( Ignorable by Annotation )

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

111
            $definition->/** @scrutinizer ignore-call */ 
112
                         setReferenceContainer($this);

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...
112 19
            $this->creatingIds[$definition->getClass()] = 1;
113
        }
114 18
        try {
115 16
            $container = $this->container === null || $definition instanceof ReferenceInterface ? $this : $this->container;
116
            return $definition->resolve($container);
0 ignored issues
show
Bug introduced by
$container of type Psr\Container\ContainerI...tory\DependencyResolver is incompatible with the type Yiisoft\Definitions\Cont...ndencyResolverInterface expected by parameter $dependencyResolver of Yiisoft\Definitions\ArrayDefinition::resolve(). ( Ignorable by Annotation )

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

116
            return $definition->resolve(/** @scrutinizer ignore-type */ $container);
Loading history...
Bug introduced by
$container of type Psr\Container\ContainerI...tory\DependencyResolver is incompatible with the type Yiisoft\Definitions\Cont...ndencyResolverInterface expected by parameter $dependencyResolver of Yiisoft\Definitions\Reference::resolve(). ( Ignorable by Annotation )

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

116
            return $definition->resolve(/** @scrutinizer ignore-type */ $container);
Loading history...
Bug introduced by
$container of type Psr\Container\ContainerI...tory\DependencyResolver is incompatible with the type Yiisoft\Definitions\Cont...ndencyResolverInterface expected by parameter $dependencyResolver of Yiisoft\Definitions\CallableDefinition::resolve(). ( Ignorable by Annotation )

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

116
            return $definition->resolve(/** @scrutinizer ignore-type */ $container);
Loading history...
Bug introduced by
$container of type Psr\Container\ContainerI...tory\DependencyResolver is incompatible with the type Yiisoft\Definitions\Cont...ndencyResolverInterface expected by parameter $dependencyResolver of Yiisoft\Definitions\Cont...ionInterface::resolve(). ( Ignorable by Annotation )

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

116
            return $definition->resolve(/** @scrutinizer ignore-type */ $container);
Loading history...
Bug introduced by
$container of type Psr\Container\ContainerI...tory\DependencyResolver is incompatible with the type Yiisoft\Definitions\Cont...ndencyResolverInterface expected by parameter $dependencyResolver of Yiisoft\Definitions\ValueDefinition::resolve(). ( Ignorable by Annotation )

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

116
            return $definition->resolve(/** @scrutinizer ignore-type */ $container);
Loading history...
117
        } finally {
118 18
            if ($definition instanceof ArrayDefinition) {
119
                unset($this->creatingIds[$definition->getClass()]);
120 18
                $definition->setReferenceContainer(null);
121 18
            }
122
        }
123
    }
124
125
    /**
126
     * @param mixed $config
127
     *
128
     * @throws InvalidConfigException
129
     */
130
    private function createDefinition($config): DefinitionInterface
131 19
    {
132
        $definition = Normalizer::normalize($config);
133 19
134
        if (
135
            ($definition instanceof ArrayDefinition) &&
136 18
            isset($this->definitions[$definition->getClass()])
137 18
        ) {
138
            $definition = $this->mergeDefinitions(
139 4
                $this->getDefinition($definition->getClass()),
140 4
                $definition
141
            );
142
        }
143
144
        return $definition;
145 18
    }
146
147
    /**
148
     * @param string $id
149
     *
150
     * @throws CircularReferenceException
151
     * @throws InvalidConfigException
152
     * @throws NotFoundException
153
     * @throws NotInstantiableException
154
     *
155
     * @return mixed|object
156
     */
157
    private function getFromFactory(string $id)
158 58
    {
159
        if (isset($this->creatingIds[$id])) {
160 58
            throw new CircularReferenceException(sprintf(
161 5
                'Circular reference to "%s" detected while creating: %s.',
162 5
                $id,
163
                implode(',', array_keys($this->creatingIds))
164 5
            ));
165
        }
166
167
        $definition = $this->getDefinition($id);
168 58
        if ($definition instanceof ArrayDefinition) {
169
            $definition->setReferenceContainer($this);
0 ignored issues
show
Bug introduced by
The method setReferenceContainer() does not exist on Yiisoft\Definitions\Contract\ReferenceInterface. ( Ignorable by Annotation )

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

169
            $definition->/** @scrutinizer ignore-call */ 
170
                         setReferenceContainer($this);

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 setReferenceContainer() does not exist on Yiisoft\Definitions\CallableDefinition. ( Ignorable by Annotation )

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

169
            $definition->/** @scrutinizer ignore-call */ 
170
                         setReferenceContainer($this);

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 setReferenceContainer() does not exist on Yiisoft\Definitions\ValueDefinition. ( Ignorable by Annotation )

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

169
            $definition->/** @scrutinizer ignore-call */ 
170
                         setReferenceContainer($this);

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...
170 58
        }
171
        $this->creatingIds[$id] = 1;
172 58
        try {
173
            $container = $this->container === null || $definition instanceof ReferenceInterface ? $this : $this->container;
174
            try {
175
                return $definition->resolve($container);
0 ignored issues
show
Bug introduced by
$container of type Psr\Container\ContainerI...tory\DependencyResolver is incompatible with the type Yiisoft\Definitions\Cont...ndencyResolverInterface expected by parameter $dependencyResolver of Yiisoft\Definitions\ValueDefinition::resolve(). ( Ignorable by Annotation )

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

175
                return $definition->resolve(/** @scrutinizer ignore-type */ $container);
Loading history...
Bug introduced by
$container of type Psr\Container\ContainerI...tory\DependencyResolver is incompatible with the type Yiisoft\Definitions\Cont...ndencyResolverInterface expected by parameter $dependencyResolver of Yiisoft\Definitions\Cont...ionInterface::resolve(). ( Ignorable by Annotation )

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

175
                return $definition->resolve(/** @scrutinizer ignore-type */ $container);
Loading history...
Bug introduced by
$container of type Psr\Container\ContainerI...tory\DependencyResolver is incompatible with the type Yiisoft\Definitions\Cont...ndencyResolverInterface expected by parameter $dependencyResolver of Yiisoft\Definitions\CallableDefinition::resolve(). ( Ignorable by Annotation )

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

175
                return $definition->resolve(/** @scrutinizer ignore-type */ $container);
Loading history...
176
            } catch (NotFoundExceptionInterface $e) {
177
                return $definition->resolve($this);
0 ignored issues
show
Bug introduced by
$this of type Yiisoft\Factory\DependencyResolver is incompatible with the type Yiisoft\Definitions\Cont...ndencyResolverInterface expected by parameter $dependencyResolver of Yiisoft\Definitions\Cont...ionInterface::resolve(). ( Ignorable by Annotation )

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

177
                return $definition->resolve(/** @scrutinizer ignore-type */ $this);
Loading history...
Bug introduced by
$this of type Yiisoft\Factory\DependencyResolver is incompatible with the type Yiisoft\Definitions\Cont...ndencyResolverInterface expected by parameter $dependencyResolver of Yiisoft\Definitions\CallableDefinition::resolve(). ( Ignorable by Annotation )

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

177
                return $definition->resolve(/** @scrutinizer ignore-type */ $this);
Loading history...
Bug introduced by
$this of type Yiisoft\Factory\DependencyResolver is incompatible with the type Yiisoft\Definitions\Cont...ndencyResolverInterface expected by parameter $dependencyResolver of Yiisoft\Definitions\ValueDefinition::resolve(). ( Ignorable by Annotation )

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

177
                return $definition->resolve(/** @scrutinizer ignore-type */ $this);
Loading history...
178
            }
179 62
        } finally {
180
            unset($this->creatingIds[$id]);
181 62
            if ($definition instanceof ArrayDefinition) {
182 62
                $definition->setReferenceContainer(null);
183 49
            }
184 7
        }
185
    }
186 45
187
    /**
188
     * @throws InvalidConfigException
189 34
     */
190
    private function getDefinition(string $id): DefinitionInterface
191
    {
192
        if (!isset($this->definitionInstances[$id])) {
193 57
            if (isset($this->definitions[$id])) {
194
                if (is_object($this->definitions[$id]) && !($this->definitions[$id] instanceof ReferenceInterface)) {
195
                    return Normalizer::normalize(clone $this->definitions[$id], $id);
196 60
                }
197
                $this->definitionInstances[$id] = Normalizer::normalize($this->definitions[$id], $id);
198 60
            } else {
199
                /** @psalm-var class-string $id */
200
                $this->definitionInstances[$id] = ArrayDefinition::fromPreparedData($id);
201 4
            }
202
        }
203 4
204
        return $this->definitionInstances[$id];
205
    }
206
207
    private function canBeCreatedByFactory(string $id): bool
208
    {
209
        return isset($this->definitions[$id]) || class_exists($id);
210
    }
211
212
    private function mergeDefinitions(DefinitionInterface $one, ArrayDefinition $two): DefinitionInterface
213
    {
214
        return $one instanceof ArrayDefinition ? $one->merge($two) : $two;
215
    }
216
}
217