Passed
Pull Request — master (#41)
by Sergei
02:52
created

ParameterDefinition::resolve()   C

Complexity

Conditions 12
Paths 20

Size

Total Lines 52
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 12

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 12
eloc 27
nc 20
nop 1
dl 0
loc 52
ccs 24
cts 24
cp 1
crap 12
rs 6.9666
c 2
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Definitions;
6
7
use Psr\Container\ContainerInterface;
8
use ReflectionNamedType;
9
use ReflectionParameter;
10
use ReflectionUnionType;
11
use Throwable;
12
use Yiisoft\Definitions\Contract\DefinitionInterface;
13
use Yiisoft\Definitions\Exception\CircularReferenceException;
14
use Yiisoft\Definitions\Exception\NotInstantiableException;
15
use Yiisoft\Definitions\Exception\InvalidConfigException;
16
17
/**
18
 * Parameter definition resolves an object based on information from `ReflectionParameter` instance.
19
 */
20
final class ParameterDefinition implements DefinitionInterface
21
{
22 74
    public function __construct(
23
        private ReflectionParameter $parameter
24
    ) {
25
    }
26
27 13
    public function getReflection(): ReflectionParameter
28
    {
29 13
        return $this->parameter;
30
    }
31
32 70
    public function isVariadic(): bool
33
    {
34 70
        return $this->parameter->isVariadic();
35
    }
36
37 27
    public function isOptional(): bool
38
    {
39 27
        return $this->parameter->isOptional();
40
    }
41
42 20
    public function hasValue(): bool
43
    {
44 20
        return $this->parameter->isDefaultValueAvailable();
45
    }
46
47 51
    public function resolve(ContainerInterface $container): mixed
48
    {
49 51
        $type = $this->parameter->getType();
50
51 51
        if ($type === null || $this->isVariadic()) {
52 2
            return $this->resolveVariadicOrBuiltinOrNonTyped();
53
        }
54
55 49
        if ($this->isUnionType()) {
56 8
            return $this->resolveUnionType($container);
57
        }
58
59
        /** @var ReflectionNamedType|null $type */
60 41
        $type = $this->parameter->getType();
61 41
        $isBuiltin = $type !== null && $type->isBuiltin();
62
63 41
        if (!$isBuiltin) {
64
            /** @var ReflectionNamedType $type */
65 21
            $typeName = $type->getName();
66 21
            if ($typeName === 'self') {
67
                // If type name is "self", it means that called class and
68
                // $parameter->getDeclaringClass() returned instance of `ReflectionClass`.
69
                /** @psalm-suppress PossiblyNullReference */
70 1
                $typeName = $this->parameter->getDeclaringClass()->getName();
71
            }
72
73
            try {
74
                /** @var mixed */
75 21
                $result = $container->get($typeName);
76 13
            } catch (Throwable $t) {
77
                if (
78 13
                    $this->parameter->isOptional()
79
                    && (
80
                        $t instanceof CircularReferenceException
81 13
                        || !$container->has($typeName)
82
                    )
83
                ) {
84 6
                    return $this->parameter->getDefaultValue();
85
                }
86 7
                throw $t;
87
            }
88
89 8
            if (!$result instanceof $typeName) {
90 1
                $actualType = get_debug_type($result);
91 1
                throw new InvalidConfigException(
92 1
                    "Container returned incorrect type \"$actualType\" for service \"{$type->getName()}\"."
93
                );
94
            }
95 7
            return $result;
96
        }
97
98 22
        return $this->resolveVariadicOrBuiltinOrNonTyped();
99
    }
100
101
    /**
102
     * @throws NotInstantiableException
103
     *
104
     * @return mixed
105
     */
106 25
    private function resolveVariadicOrBuiltinOrNonTyped()
107
    {
108 25
        if ($this->parameter->isDefaultValueAvailable()) {
109 22
            return $this->parameter->getDefaultValue();
110
        }
111
112 3
        if ($this->isOptional()) {
113
            throw new NotInstantiableException(
114
                sprintf(
115
                    'Can not determine default value of parameter "%s" when instantiating "%s" ' .
116
                    'because it is PHP internal. Please specify argument explicitly.',
117
                    $this->parameter->getName(),
118
                    $this->getCallable(),
119
                )
120
            );
121
        }
122
123 3
        $type = $this->getType();
124
125 3
        if ($type === null) {
126 1
            throw new NotInstantiableException(
127 1
                sprintf(
128
                    'Can not determine value of the "%s" parameter without type when instantiating "%s". ' .
129 1
                    'Please specify argument explicitly.',
130 1
                    $this->parameter->getName(),
131 1
                    $this->getCallable(),
132
                )
133
            );
134
        }
135
136 2
        throw new NotInstantiableException(
137 2
            sprintf(
138
                'Can not determine value of the "%s" parameter of type "%s" when instantiating "%s". ' .
139 2
                'Please specify argument explicitly.',
140 2
                $this->parameter->getName(),
141
                $type,
142 2
                $this->getCallable(),
143
            )
144
        );
145
    }
146
147
    /**
148
     * Resolve union type string provided as a class name.
149
     *
150
     * @throws InvalidConfigException If an object of incorrect type was created.
151
     * @throws Throwable
152
     *
153
     * @return mixed|null Ready to use object or null if definition can
154
     * not be resolved and is marked as optional.
155
     */
156 8
    private function resolveUnionType(ContainerInterface $container)
157
    {
158
        /**
159
         * @var ReflectionUnionType $parameterType
160
         */
161 8
        $parameterType = $this->parameter->getType();
162
163
        /**
164
         * @var ReflectionNamedType[] $types
165
         */
166 8
        $types = $parameterType->getTypes();
167 8
        $class = implode('|', $types);
168
169 8
        foreach ($types as $type) {
170 8
            if (!$type->isBuiltin()) {
171 7
                $typeName = $type->getName();
172
                /**
173
                 * @psalm-suppress TypeDoesNotContainType
174
                 *
175
                 * @link https://github.com/vimeo/psalm/issues/6756
176
                 */
177 7
                if ($typeName === 'self') {
178
                    // If type name is "self", it means that called class and
179
                    // $parameter->getDeclaringClass() returned instance of `ReflectionClass`.
180
                    /** @psalm-suppress PossiblyNullReference */
181 1
                    $typeName = $this->parameter->getDeclaringClass()->getName();
182
                }
183
184
                try {
185
                    /** @var mixed */
186 7
                    $result = $container->get($typeName);
187 3
                    $resolved = true;
188 6
                } catch (Throwable $t) {
189 6
                    $error = $t;
190 6
                    $resolved = false;
191
                }
192
193 7
                if ($resolved) {
194
                    /** @var mixed $result Exist, because $resolved is true */
195 3
                    if (!$result instanceof $typeName) {
196 1
                        $actualType = get_debug_type($result);
197 1
                        throw new InvalidConfigException(
198 1
                            "Container returned incorrect type \"$actualType\" for service \"$class\"."
199
                        );
200
                    }
201 2
                    return $result;
202
                }
203
204
                /** @var Throwable $error Exist, because $resolved is false */
205
                if (
206
                    !$error instanceof CircularReferenceException
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $error does not seem to be defined for all execution paths leading up to this point.
Loading history...
207 6
                    && $container->has($typeName)
208
                ) {
209 1
                    throw $error;
210
                }
211
            }
212
        }
213
214 4
        if ($this->parameter->isOptional()) {
215 1
            return null;
216
        }
217
218 3
        if (!isset($error)) {
219 1
            return $this->resolveVariadicOrBuiltinOrNonTyped();
220
        }
221
222 2
        throw $error;
223
    }
224
225 49
    private function isUnionType(): bool
226
    {
227 49
        return $this->parameter->getType() instanceof ReflectionUnionType;
228
    }
229
230 3
    private function getType(): ?string
231
    {
232 3
        $type = $this->parameter->getType();
233
234 3
        if ($type instanceof ReflectionUnionType) {
235 1
            $namedTypes = $type->getTypes();
236 1
            $names = array_map(
237 1
                static fn (ReflectionNamedType $t) => $t->getName(),
238
                $namedTypes
239
            );
240 1
            return implode('|', $names);
241
        }
242
243 2
        if ($type instanceof ReflectionNamedType) {
244 1
            return $type->getName();
245
        }
246
247 1
        return null;
248
    }
249
250 3
    private function getCallable(): string
251
    {
252 3
        $callable = [];
253
254 3
        $class = $this->parameter->getDeclaringClass();
255 3
        if ($class !== null) {
256 3
            $callable[] = $class->getName();
257
        }
258 3
        $callable[] = $this->parameter
259 3
                ->getDeclaringFunction()
260 3
                ->getName() .
261
            '()';
262
263 3
        return implode('::', $callable);
264
    }
265
}
266