Passed
Pull Request — 1.x (#342)
by Akihito
01:37
created

getRequiredWithoutExpandedParams()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 2
dl 0
loc 17
ccs 0
cts 0
cp 0
crap 20
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource;
6
7
use Ray\InputQuery\Attribute\Input;
8
use ReflectionAttribute;
9
use ReflectionClass;
10
use ReflectionException;
11
use ReflectionMethod;
12
use ReflectionNamedType;
13
use ReflectionParameter;
14
15 7
use function array_unique;
16
use function array_values;
17 7
use function assert;
18 7
use function in_array;
19
use function is_array;
20 7
use function is_string;
21
use function method_exists;
22 7
23
/**
24 7
 * @psalm-import-type OptionsResponse from Types
25
 * @psalm-import-type InsMap from Types
26
 * @psalm-import-type ParameterMetadata from Types
27
 * @psalm-import-type ParametersMap from Types
28
 * @psalm-import-type RequiredParameterList from Types
29
 * @psalm-import-type ReflectionParameterList from Types
30 7
 */
31
final class OptionsMethodRequest
32 7
{
33 7
    /**
34 7
     * @param ParametersMap $paramDoc
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\ParametersMap 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...
35
     * @param InsMap        $ins
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\InsMap 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...
36 3
     *
37 2
     * @return OptionsResponse
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\OptionsResponse 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...
38
     */
39 1
    public function __invoke(ReflectionMethod $method, array $paramDoc, array $ins): array
40
    {
41 7
        return $this->getParamMetas($method->getParameters(), $paramDoc, $ins);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getParamMe...ers(), $paramDoc, $ins) returns the type array which is incompatible with the documented return type BEAR\Resource\OptionsResponse.
Loading history...
42
    }
43 7
44 7
    /**
45 2
     * @param ParametersMap $paramDoc
46
     *
47 7
     * @psalm-suppress RedundantCondition for BC
48 4
     */
49
    private function getParameterType(ReflectionParameter $parameter, array $paramDoc, string $name): string|null
50 7
    {
51 7
        /** @phpstan-ignore function.alreadyNarrowedType */
52
        $hasType = method_exists($parameter, 'getType') && $parameter->getType();
53 7
        if ($hasType) {
54
            return $this->getType($parameter);
55 7
        }
56
57
        return $paramDoc[$name]['type'] ?? null;
58
    }
59
60
    /**
61 7
     * @param ReflectionParameterList $parameters
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\ReflectionParameterList 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...
62
     * @param ParametersMap           $paramDoc
63 7
     * @param InsMap                  $ins
64 7
     *
65 7
     * @return OptionsResponse
66 7
     */
67
    private function getParamMetas(array $parameters, array $paramDoc, array $ins): array
68
    {
69
        $expandedParameters = [];
70 7
        $expandedRequired = [];
71
        $expandedParamNames = [];
72
73 7
        foreach ($parameters as $parameter) {
74
            // Check for #[Input] attribute with object type
75 7
            $inputAttributes = $parameter->getAttributes(Input::class, ReflectionAttribute::IS_INSTANCEOF);
76 7
            if ($inputAttributes) {
77 2
                $inputResult = $this->expandInputParameter($parameter);
78
                if ($inputResult !== null) {
79
                    [$inputParamDoc, $inputRequired] = $inputResult;
80 7
                    $expandedParameters += $inputParamDoc;
81
                    $expandedRequired = [...$expandedRequired, ...$inputRequired];
82
                    $expandedParamNames[] = $parameter->name;
83 7
                    continue;
84
                }
85 7
            }
86 7
87 7
            $name = (string) $parameter->name;
88
            if (isset($ins[$name])) {
89
                $paramDoc[$name]['in'] = $ins[$parameter->name];
90 7
            }
91
92
            if (! isset($paramDoc[$parameter->name])) {
93 7
                $paramDoc[$name] = [];
94
            }
95 7
96 7
            $paramDoc = $this->paramType($paramDoc, $parameter);
97 4
            $paramDoc = $this->paramDefault($paramDoc, $parameter);
98
        }
99
100 7
        // Merge expanded parameters with regular parameters
101
        $paramDoc = $expandedParameters + $paramDoc;
102
103
        $required = $this->getRequiredWithoutExpandedParams($parameters, $expandedParamNames);
104
        // Merge expanded required with regular required
105
        if ($expandedRequired !== []) {
106 7
            $required = array_values(array_unique([...$expandedRequired, ...$required]));
107
        }
108 7
109 7
        return $this->setParamMetas($paramDoc, $required);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->setParamMetas($paramDoc, $required) returns the type array which is incompatible with the documented return type BEAR\Resource\OptionsResponse.
Loading history...
110 5
    }
111 1
112 1
    /**
113
     * Expand #[Input] parameter to its constructor properties
114 5
     *
115 5
     * @return array{0: ParametersMap, 1: RequiredParameterList}|null
116
     */
117
    private function expandInputParameter(ReflectionParameter $parameter): array|null
118
    {
119 7
        $type = $parameter->getType();
120
        if (! $type instanceof ReflectionNamedType || $type->isBuiltin()) {
121
            return null;
122
        }
123
124
        $className = $type->getName();
125 1
        $refClass = new ReflectionClass($className);
126
        $constructor = $refClass->getConstructor();
127 1
        if ($constructor === null) {
128 1
            return null;
129 1
        }
130
131
        $paramDoc = [];
132 1
        $required = [];
133
134
        foreach ($constructor->getParameters() as $ctorParam) {
135 7
            $name = $ctorParam->getName();
136
            $paramDoc[$name] = [];
137 7
138 7
            // Set type
139 7
            $ctorType = $ctorParam->getType();
140
            if ($ctorType instanceof ReflectionNamedType) {
141 7
                $typeName = $ctorType->getName();
142 7
                if ($typeName === 'int') {
143
                    $typeName = 'integer';
144
                }
145 7
146
                $paramDoc[$name]['type'] = $typeName;
147
            }
148
149
            // Set default if available
150
            if ($ctorParam->isDefaultValueAvailable() && $ctorParam->getDefaultValue() !== null) {
151
                $default = $ctorParam->getDefaultValue();
152
                $paramDoc[$name]['default'] = is_array($default) ? '[]' : (string) $default;
153
            }
154
155
            // Check if required
156
            if ($ctorParam->isOptional()) {
157
                continue;
158
            }
159
160
            $required[] = $name;
161
        }
162
163
        return [$paramDoc, $required];
164
    }
165
166
    /**
167
     * Get required parameters excluding successfully expanded #[Input] parameters
168
     *
169
     * @param ReflectionParameterList $parameters
170
     * @param list<string>            $expandedParamNames Names of parameters that were expanded
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\list 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...
171
     *
172
     * @return RequiredParameterList
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\RequiredParameterList 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...
173
     */
174
    private function getRequiredWithoutExpandedParams(array $parameters, array $expandedParamNames): array
175
    {
176
        $required = [];
177
        foreach ($parameters as $parameter) {
178
            if ($parameter->isOptional()) {
179
                continue;
180
            }
181
182
            // Skip parameters that were successfully expanded
183
            if (in_array($parameter->name, $expandedParamNames, true)) {
184
                continue;
185
            }
186
187
            $required[] = $parameter->name;
188
        }
189
190
        return $required;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $required returns the type array which is incompatible with the documented return type BEAR\Resource\RequiredParameterList.
Loading history...
191
    }
192
193
    /**
194
     * @param ParametersMap $paramDoc
195
     *
196
     * @return ParametersMap
197
     *
198
     * @throws ReflectionException
199
     */
200
    private function paramDefault(array $paramDoc, ReflectionParameter $parameter): array
201
    {
202
        $hasDefault = $parameter->isDefaultValueAvailable() && $parameter->getDefaultValue() !== null;
203
        if ($hasDefault) {
204
            $default = $parameter->getDefaultValue();
205
            $paramDoc[(string) $parameter->name]['default'] = is_array($default) ? '[]' : (string) $parameter->getDefaultValue(); // @phpstan-ignore-lines
206
        }
207
208
        return $paramDoc;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $paramDoc returns the type array which is incompatible with the documented return type BEAR\Resource\ParametersMap.
Loading history...
209
    }
210
211
    /**
212
     * @param ParametersMap $paramDoc
213
     *
214
     * @return ParametersMap
215
     */
216
    private function paramType(array $paramDoc, ReflectionParameter $parameter): array
217
    {
218
        $type = $this->getParameterType($parameter, $paramDoc, $parameter->name);
219
        if (is_string($type)) {
220
            $paramDoc[(string) $parameter->name]['type'] = $type; // override type parameter by reflection over phpdoc param type
221
        }
222
223
        return $paramDoc;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $paramDoc returns the type array which is incompatible with the documented return type BEAR\Resource\ParametersMap.
Loading history...
224
    }
225
226
    private function getType(ReflectionParameter $parameter): string
227
    {
228
        $namedType = $parameter->getType();
229
        assert($namedType instanceof ReflectionNamedType);
230
        $type = $namedType->getName();
231
        if ($type === 'int') {
232
            $type = 'integer';
233
        }
234
235
        return $type;
236
    }
237
238
    /**
239
     * @param ParametersMap         $paramDoc
240
     * @param RequiredParameterList $required
241
     *
242
     * @return OptionsResponse
243
     */
244
    private function setParamMetas(array $paramDoc, array $required): array
245
    {
246
        $paramMetas = [];
247
        if ((bool) $paramDoc) {
248
            $paramMetas['parameters'] = $paramDoc;
249
        }
250
251
        if ((bool) $required) {
252
            $paramMetas['required'] = $required;
253
        }
254
255
        return $paramMetas;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $paramMetas returns the type array which is incompatible with the documented return type BEAR\Resource\OptionsResponse.
Loading history...
256
    }
257
}
258