Passed
Pull Request — master (#17)
by Dmitriy
02:52
created

ParameterDefinition::isVariadic()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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\NotInstantiableException;
14
use Yiisoft\Definitions\Exception\InvalidConfigException;
15
16
/**
17
 * Parameter definition resolves an object based on information from `ReflectionParameter` instance.
18
 */
19
final class ParameterDefinition implements DefinitionInterface
20
{
21
    private ReflectionParameter $parameter;
22
23 31
    public function __construct(ReflectionParameter $parameter)
24
    {
25 31
        $this->parameter = $parameter;
26 31
    }
27
28 1
    public function getReflection(): ReflectionParameter
29
    {
30 1
        return $this->parameter;
31
    }
32
33 38
    public function isVariadic(): bool
34
    {
35 38
        return $this->parameter->isVariadic();
36
    }
37
38 23
    public function isOptional(): bool
39
    {
40 23
        return $this->parameter->isOptional();
41
    }
42
43 33
    public function isBuiltin() {
44 33
        return $this->parameter->getType()->isBuiltin();
45
    }
46
47 19
    public function hasValue(): bool
48
    {
49 19
        return $this->parameter->isDefaultValueAvailable();
50
    }
51
52 35
    public function resolve(ContainerInterface $container)
53
    {
54
55 35
        $type = $this->parameter->getType();
56
57 35
        if ($type === null || $this->isVariadic()) {
58 2
            return $this->resolveBuiltin();
59
        }
60
61 33
        if ($this->isUnionType()) {
62
            return $this->resolveUnionType($container);
63
        }
64
65 33
        if (!$this->isBuiltin()) {
66 14
            $typeName = $this->parameter->getType()->getName();
0 ignored issues
show
Bug introduced by
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

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

66
            $typeName = $this->parameter->getType()->/** @scrutinizer ignore-call */ getName();
Loading history...
67 14
            if ($typeName === 'self') {
68
                // If type name is "self", it means that called class and
69
                // $parameter->getDeclaringClass() returned instance of `ReflectionClass`.
70
                /** @psalm-suppress PossiblyNullReference */
71
                $typeName = $this->parameter->getDeclaringClass()->getName();
72
            }
73
74
            try {
75
                /** @var mixed */
76 14
                $result = $container->get($typeName);
77 9
            } catch (Throwable $t) {
78 9
                if ($this->parameter->isOptional()) {
79 4
                    return null;
80
                }
81 5
                throw $t;
82
            }
83
84 5
            if (!$result instanceof $typeName) {
85 1
                $actualType = $this->getValueType($result);
86 1
                throw new InvalidConfigException(
87 1
                    "Container returned incorrect type \"$actualType\" for service \"{$this->parameter->getType()->getName()}\"."
88
                );
89
            }
90 4
            return $result;
91
        }
92
93 19
        return $this->resolveBuiltin();
94
    }
95
96 21
    private function resolveBuiltin()
97
    {
98 21
        if ($this->parameter->isDefaultValueAvailable()) {
99 19
            return $this->parameter->getDefaultValue();
100
        }
101
102 2
        if ($this->isOptional()) {
103 2
            throw new NotInstantiableException(
104 2
                sprintf(
105
                    'Can not determine default value of parameter "%s" when instantiating "%s" ' .
106 2
                    'because it is PHP internal. Please specify argument explicitly.',
107 2
                    $this->parameter->getName(),
108 2
                    $this->getCallable(),
109
                )
110
            );
111
        }
112
113
        throw new NotInstantiableException(
114
            sprintf(
115
                'Can not determine value of the "%s" parameter of type "%s" when instantiating "%s". ' .
116
                'Please specify argument explicitly.',
117
                $this->parameter->getName(),
118
                $this->getType(),
119
                $this->getCallable(),
120
            )
121
        );
122
    }
123
124
    /**
125
     * Resolve union type string provided as a class name.
126
     *
127
     * @throws InvalidConfigException If an object of incorrect type was created.
128
     * @throws Throwable
129
     *
130
     * @return mixed|null Ready to use object or null if definition can
131
     * not be resolved and is marked as optional.
132
     */
133
    private function resolveUnionType(ContainerInterface $container)
134
    {
135
        $types = $this->parameter->getType()->getTypes();
0 ignored issues
show
Bug introduced by
The method getTypes() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionUnionType. ( Ignorable by Annotation )

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

135
        $types = $this->parameter->getType()->/** @scrutinizer ignore-call */ getTypes();
Loading history...
136
        $class = implode('|', $types);
137
138
        foreach ($types as $type) {
139
            if (!$type->isBuiltin()) {
140
                $typeName = $type->getName();
141
                if ($typeName === 'self') {
142
                    // If type name is "self", it means that called class and
143
                    // $parameter->getDeclaringClass() returned instance of `ReflectionClass`.
144
                    /** @psalm-suppress PossiblyNullReference */
145
                    $typeName = $this->parameter->getDeclaringClass()->getName();
146
                }
147
                try {
148
149
                    /** @var mixed */
150
                    $result = $container->get($typeName);
151
                    if (!$result instanceof $typeName) {
152
                        $actualType = $this->getValueType($result);
153
                        throw new InvalidConfigException(
154
                            "Container returned incorrect type \"$actualType\" for service \"$class\"."
155
                        );
156
                    }
157
158
                    return $result;
159
                } catch (Throwable $t) {
160
                    $error = $t;
161
                }
162
            }
163
        }
164
165
        if ($this->parameter->isOptional()) {
166
            return null;
167
        }
168
169
        if (!isset($error)) {
170
            return $this->resolveBuiltin();
171
        }
172
173
        throw $error;
174
    }
175
176 33
    private function isUnionType(): bool
177
    {
178 33
        return $this->parameter->getType() instanceof ReflectionUnionType;
179
    }
180
181
    private function getType(): string
182
    {
183
        /**
184
         * @psalm-suppress UndefinedDocblockClass
185
         *
186
         * @var ReflectionNamedType|ReflectionUnionType $type Could not be `null`
187
         * because in self::resolve() checked `$this->parameter->allowsNull()`.
188
         */
189
        $type = $this->parameter->getType();
190
191
        /** @psalm-suppress UndefinedClass, TypeDoesNotContainType */
192
        if ($type instanceof ReflectionUnionType) {
193
            /** @var ReflectionNamedType[] */
194
            $namedTypes = $type->getTypes();
195
            $names = array_map(
196
                static fn (ReflectionNamedType $t) => $t->getName(),
197
                $namedTypes
198
            );
199
            return implode('|', $names);
200
        }
201
202
        /** @var ReflectionNamedType $type */
203
204
        return $type->getName();
205
    }
206
207 2
    private function getCallable(): string
208
    {
209 2
        $callable = [];
210
211 2
        $class = $this->parameter->getDeclaringClass();
212 2
        if ($class !== null) {
213 1
            $callable[] = $class->getName();
214
        }
215 2
        $callable[] = $this->parameter->getDeclaringFunction()->getName() . '()';
216
217 2
        return implode('::', $callable);
218
    }
219
220
    /**
221
     * Get type of the value provided.
222
     *
223
     * @param mixed $value Value to get type for.
224
     */
225 1
    private function getValueType($value): string
226
    {
227 1
        return is_object($value) ? get_class($value) : gettype($value);
228
    }
229
}
230