Passed
Pull Request — master (#86)
by Alexander
02:41
created

ParameterDefinition::getType()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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