NamedParamMetas::getAnnotationParamMetas()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 8
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource;
6
7
use BEAR\Resource\Annotation\RequestParamInterface;
8
use BEAR\Resource\Annotation\ResourceParam;
9
use Override;
10
use Ray\Aop\ReflectionMethod;
11
use Ray\Di\Di\Assisted;
12
use Ray\InputQuery\Attribute\Input;
13
use Ray\InputQuery\Attribute\InputFile;
14
use Ray\InputQuery\FileUploadFactoryInterface;
15
use Ray\InputQuery\InputQueryInterface;
16
use Ray\WebContextParam\Annotation\AbstractWebContextParam;
17
use ReflectionAttribute;
18
use ReflectionNamedType;
19
use ReflectionParameter;
20
21
/**
22
 * @psalm-import-type ParamMap from Types
23
 * @psalm-import-type WebContextParamMap from Types
24
 * @psalm-import-type ReflectionParameterMap from Types
25
 * @psalm-import-type ObjectList from Types
26
 */
27
final class NamedParamMetas implements NamedParamMetasInterface
28
{
29
    /** @param InputQueryInterface<object> $inputQuery */
30
    public function __construct(
31
        private readonly InputQueryInterface $inputQuery,
32
        private readonly FileUploadFactoryInterface $factory,
33
    ) {
34
    }
35
36
    /**
37
     * {@inheritDoc}
38
     */
39
    #[Override]
40
    public function __invoke(callable $callable): array
41
    {
42
        // callable is [object, string] but native type doesn't allow array access
43
        /** @psalm-suppress InvalidArrayAccess, MixedArgument */
44
        /** @var array{0:object, 1:string} $callable */
45
        $method = new ReflectionMethod($callable[0], $callable[1]); // @phpstan-ignore-line
46
        $paramMetas = $this->getAttributeParamMetas($method);
47
48
        if (! $paramMetas) {
49
            $paramMetas = $this->getAnnotationParamMetas($method);
50
        }
51
52
        return $paramMetas;
53
    }
54
55
    /** @return array<string, AssistedWebContextParam|ParamInterface> */
56
    private function getAnnotationParamMetas(ReflectionMethod $method): array
57
    {
58
        $parameters = $method->getParameters();
59
        $annotations = $method->getAnnotations();
60
        $assistedNames = $this->getAssistedNames($annotations);
61
        $webContext = $this->getWebContext($annotations);
62
63
        return $this->addNamedParams($parameters, $assistedNames, $webContext);
64
    }
65
66
    /**
67
     * @return ParamMap
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\ParamMap 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...
68
     *
69
     * @psalm-suppress TooManyTemplateParams $refAttribute
70
     * @psalm-suppress PossiblyInvalidArrayAssignment
71
     */
72
    private function getAttributeParamMetas(ReflectionMethod $method): array
73
    {
74
        $parameters = $method->getParameters();
75
        $names = $valueParams = [];
76
        foreach ($parameters as $parameter) {
77
            $refAttribute = $parameter->getAttributes(RequestParamInterface::class, ReflectionAttribute::IS_INSTANCEOF);
78
            if ($refAttribute) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $refAttribute of type ReflectionAttribute[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
79
                /** @var ?ResourceParam $resourceParam */
80
                $resourceParam = $refAttribute[0]->newInstance();
81
                if ($resourceParam instanceof ResourceParam) {
82
                    $names[$parameter->name] = new AssistedResourceParam($resourceParam);
83
                    continue;
84
                }
85
            }
86
87
            $refWebContext = $parameter->getAttributes(AbstractWebContextParam::class, ReflectionAttribute::IS_INSTANCEOF);
88
            if ($refWebContext) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $refWebContext of type ReflectionAttribute[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
89
                $webParam = $refWebContext[0]->newInstance();
90
                $default = $this->getDefault($parameter);
91
                $param = new AssistedWebContextParam($webParam, $default);
92
                $names[$parameter->name] = $param;
93
                continue;
94
            }
95
96
            // #[Input]
97
            $inputAttribute = $parameter->getAttributes(Input::class, ReflectionAttribute::IS_INSTANCEOF);
98
            if ($inputAttribute) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $inputAttribute of type ReflectionAttribute[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
99
                $names[$parameter->name] = new InputParam($this->inputQuery, $parameter);
100
                continue;
101
            }
102
103
            // #[InputFile]
104
            $inputFileAttributes = $parameter->getAttributes(InputFile::class, ReflectionAttribute::IS_INSTANCEOF);
105
            if ($inputFileAttributes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $inputFileAttributes of type ReflectionAttribute[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
106
                $this->setInputFileParam($parameter, $inputFileAttributes, $names);
107
                continue;
108
            }
109
110
            $valueParams[$parameter->name] = $parameter;
111
        }
112
113
        $names = $this->getNames($names, $valueParams);
114
115
        return $names;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $names returns the type array which is incompatible with the documented return type BEAR\Resource\ParamMap.
Loading history...
116
    }
117
118
    /**
119
     * @param ObjectList $annotations
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\ObjectList 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...
120
     *
121
     * @return ParamMap
122
     */
123
    private function getAssistedNames(array $annotations): array
124
    {
125
        $names = [];
126
        foreach ($annotations as $annotation) {
127
            if ($annotation instanceof ResourceParam) {
128
                $names[$annotation->param] = new AssistedResourceParam($annotation);
129
            }
130
131
            if (! ($annotation instanceof Assisted)) {
132
                continue;
133
            }
134
135
            // @codeCoverageIgnoreStart
136
            $names = $this->setAssistedAnnotation($names, $annotation); // BC for annotation
137
            // @codeCoverageIgnoreEnd
138
        }
139
140
        return $names;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $names returns the type array which is incompatible with the documented return type BEAR\Resource\ParamMap.
Loading history...
141
    }
142
143
    /**
144
     * @param ObjectList $annotations
145
     *
146
     * @return WebContextParamMap
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\WebContextParamMap 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...
147
     */
148
    private function getWebContext(array $annotations): array
149
    {
150
        $webcontext = [];
151
        foreach ($annotations as $annotation) {
152
            if (! ($annotation instanceof AbstractWebContextParam)) {
153
                continue;
154
            }
155
156
            $webcontext[$annotation->param] = $annotation;
157
        }
158
159
        return $webcontext;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $webcontext returns the type array which is incompatible with the documented return type BEAR\Resource\WebContextParamMap.
Loading history...
160
    }
161
162
    /**
163
     * @param ParamMap $names
164
     *
165
     * @return ParamMap
166
     *
167
     * @codeCoverageIgnore BC for annotation
168
     */
169
    private function setAssistedAnnotation(array $names, Assisted $assisted): array
170
    {
171
        foreach ($assisted->values as $assistedParam) {
172
            $names[$assistedParam] = new AssistedParam();
173
        }
174
175
        return $names;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $names returns the type array which is incompatible with the documented return type BEAR\Resource\ParamMap.
Loading history...
176
    }
177
178
    /**
179
     * @param list<ReflectionParameter> $parameters
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...
180
     * @param ParamMap                  $assistedNames
181
     * @param WebContextParamMap        $webcontext
182
     *
183
     * @return (AssistedWebContextParam|ParamInterface)[]
184
     * @psalm-return array<string, AssistedWebContextParam|ParamInterface>
185
     *
186
     * @psalm-suppress InvalidArgument
187
     */
188
    private function addNamedParams(array $parameters, array $assistedNames, array $webcontext): array
189
    {
190
        $names = [];
191
        foreach ($parameters as $parameter) {
192
            $name = $parameter->name;
193
            if (isset($assistedNames[$name])) {
194
                $names[$name] = $assistedNames[$parameter->name];
195
196
                continue;
197
            }
198
199
            if (isset($webcontext[$name])) {
200
                $default = $this->getDefault($parameter);
201
                $names[$name] = new AssistedWebContextParam($webcontext[$name], $default);
202
203
                continue;
204
            }
205
206
            $names[$name] = $this->getParam($parameter);
207
        }
208
209
        return $names;
210
    }
211
212
    /**
213
     * @param array<ReflectionAttribute<InputFile>> $inputFileAttributes
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<ReflectionAttribute<InputFile>> at position 2 could not be parsed: Expected '>' at position 2, but found 'ReflectionAttribute'.
Loading history...
214
     * @param ParamMap                              $names
215
     */
216
    private function setInputFileParam(ReflectionParameter $parameter, array $inputFileAttributes, array &$names): void
217
    {
218
        $type = $parameter->getType();
219
        $isArray = $type instanceof ReflectionNamedType && $type->isBuiltin() && $type->getName() === 'array';
220
        if ($isArray) {
221
            $names[$parameter->name] = new InputFormsParam($this->factory, $parameter, $inputFileAttributes);
222
223
            return;
224
        }
225
226
        $names[$parameter->name] = new InputFormParam($this->factory, $parameter, $inputFileAttributes);
227
    }
228
229
    /** @psalm-return DefaultParam<mixed>|NoDefaultParam */
230
    private function getDefault(ReflectionParameter $parameter): DefaultParam|NoDefaultParam
231
    {
232
        return $parameter->isDefaultValueAvailable() === true ? new DefaultParam($parameter->getDefaultValue()) : new NoDefaultParam();
233
    }
234
235
    /**
236
     * @param ParamMap               $names
237
     * @param ReflectionParameterMap $valueParams
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\ReflectionParameterMap 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...
238
     *
239
     * @return ParamMap
240
     */
241
    private function getNames(array $names, array $valueParams): array
242
    {
243
        // if there is more than single attributes
244
        if ($names) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $names of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
245
            foreach ($valueParams as $paramName => $valueParam) {
246
                $names[$paramName] = $this->getParam($valueParam);
247
            }
248
        }
249
250
        return $names;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $names returns the type array which is incompatible with the documented return type BEAR\Resource\ParamMap.
Loading history...
251
    }
252
253
    /**
254
     * @return ClassParam|OptionalParam|RequiredParam
255
     * @psalm-return ClassParam|OptionalParam<mixed>|RequiredParam
256
     */
257
    private function getParam(ReflectionParameter $parameter): ParamInterface
258
    {
259
        $type = $parameter->getType();
260
        if ($type instanceof ReflectionNamedType && ! $type->isBuiltin()) {
261
            return new ClassParam($type, $parameter);
262
        }
263
264
        return $parameter->isDefaultValueAvailable() === true ? new OptionalParam($parameter->getDefaultValue()) : new RequiredParam();
265
    }
266
}
267