Completed
Push — 1.x ( 701e3a...5ad9e3 )
by Akihito
18s queued 14s
created

NamedParamMetas::getAttributeParamMetas()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 44
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 7

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 7
eloc 27
c 6
b 0
f 0
nc 10
nop 1
dl 0
loc 44
rs 8.5546
ccs 20
cts 20
cp 1
crap 7
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
final class NamedParamMetas implements NamedParamMetasInterface
22
{
23
    /** @param InputQueryInterface<object> $inputQuery */
24
    public function __construct(
25 107
        private readonly InputQueryInterface $inputQuery,
26
        private readonly FileUploadFactoryInterface $factory,
27 107
    ) {
28 107
    }
29 107
30
    /**
31 75
     * {@inheritDoc}
32
     */
33 75
    #[Override]
34
    public function __invoke(callable $callable): array
35
    {
36 75
        /** @var array{0:object, 1:string} $callable */
37 75
        $method = new ReflectionMethod($callable[0], $callable[1]);
38 75
        $paramMetas = $this->getAttributeParamMetas($method);
39 8
40
        if (! $paramMetas) {
41 72
            $paramMetas = $this->getAnnotationParamMetas($method);
42 72
        }
43 72
44 72
        return $paramMetas;
45 72
    }
46 72
47 72
    /** @return array<string, AssistedWebContextParam|ParamInterface> */
48
    private function getAnnotationParamMetas(ReflectionMethod $method): array
49 72
    {
50
        $parameters = $method->getParameters();
51
        $annotations = $method->getAnnotations();
52 72
        $assistedNames = $this->getAssistedNames($annotations);
53
        $webContext = $this->getWebContext($annotations);
54 72
55 72
        return $this->addNamedParams($parameters, $assistedNames, $webContext);
56 35
    }
57 3
58
    /**
59 35
     * @return array<string, ParamInterface>
60 35
     *
61
     * @psalm-suppress TooManyTemplateParams $refAttribute
62
     */
63
    private function getAttributeParamMetas(ReflectionMethod $method): array
64 72
    {
65
        $parameters = $method->getParameters();
66
        $names = $valueParams = [];
67 72
        foreach ($parameters as $parameter) {
68
            $refAttribute = $parameter->getAttributes(RequestParamInterface::class, ReflectionAttribute::IS_INSTANCEOF);
69 72
            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...
70 72
                /** @var ?ResourceParam $resourceParam */
71 35
                $resourceParam = $refAttribute[0]->newInstance();
72 35
                if ($resourceParam instanceof ResourceParam) {
73
                    $names[$parameter->name] = new AssistedResourceParam($resourceParam);
74
                    continue;
75
                }
76 72
            }
77
78
            $refWebContext = $parameter->getAttributes(AbstractWebContextParam::class, ReflectionAttribute::IS_INSTANCEOF);
79 1
            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...
80
                $webParam = $refWebContext[0]->newInstance();
81
                $default = $this->getDefault($parameter);
82 1
                $param = new AssistedWebContextParam($webParam, $default);
83 1
                $names[$parameter->name] = $param;
84
                continue;
85
            }
86 1
87
            // #[Input]
88
            $inputAttribute = $parameter->getAttributes(Input::class, ReflectionAttribute::IS_INSTANCEOF);
89
            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...
90
                $names[$parameter->name] = new InputParam($this->inputQuery, $parameter);
91
                continue;
92
            }
93
94 72
            // #[InputFile]
95
            $inputFileAttributes = $parameter->getAttributes(InputFile::class, ReflectionAttribute::IS_INSTANCEOF);
96 72
            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...
97 72
                $this->setInputFileParam($parameter, $inputFileAttributes, $names);
98 65
                continue;
99 4
            }
100
101 4
            $valueParams[$parameter->name] = $parameter;
102
        }
103 64
104 4
        $names = $this->getNames($names, $valueParams);
105 4
106
        return $names;
107 4
    }
108
109 62
    /**
110
     * @param array<Assisted|object|ResourceParam> $annotations
111
     *
112 72
     * @return array<string, ParamInterface>
113
     */
114
    private function getAssistedNames(array $annotations): array
115 4
    {
116
        $names = [];
117 4
        foreach ($annotations as $annotation) {
118
            if ($annotation instanceof ResourceParam) {
119
                $names[$annotation->param] = new AssistedResourceParam($annotation);
120 62
            }
121
122 62
            if (! ($annotation instanceof Assisted)) {
123
                continue;
124
            }
125
126
            // @codeCoverageIgnoreStart
127
            $names = $this->setAssistedAnnotation($names, $annotation); // BC for annotation
128
            // @codeCoverageIgnoreEnd
129
        }
130
131
        return $names;
132
    }
133
134
    /**
135
     * @param array<object> $annotations
136
     *
137
     * @return array<string, AbstractWebContextParam>
138
     */
139
    private function getWebContext(array $annotations): array
140
    {
141
        $webcontext = [];
142
        foreach ($annotations as $annotation) {
143
            if (! ($annotation instanceof AbstractWebContextParam)) {
144
                continue;
145
            }
146
147
            $webcontext[$annotation->param] = $annotation;
148
        }
149
150
        return $webcontext;
151
    }
152
153
    /**
154
     * @param array<string, ParamInterface> $names
155
     *
156
     * @return array<string, ParamInterface>
157
     *
158
     * @codeCoverageIgnore BC for annotation
159
     */
160
    private function setAssistedAnnotation(array $names, Assisted $assisted): array
161
    {
162
        foreach ($assisted->values as $assistedParam) {
163
            $names[$assistedParam] = new AssistedParam();
164
        }
165
166
        return $names;
167
    }
168
169
    /**
170
     * @param ReflectionParameter[]                  $parameters
171
     * @param array<string, ParamInterface>          $assistedNames
172
     * @param array<string, AbstractWebContextParam> $webcontext
173
     *
174
     * @return (AssistedWebContextParam|ParamInterface)[]
175
     * @psalm-return array<string, AssistedWebContextParam|ParamInterface>
176
     */
177
    private function addNamedParams(array $parameters, array $assistedNames, array $webcontext): array
178
    {
179
        $names = [];
180
        foreach ($parameters as $parameter) {
181
            $name = $parameter->name;
182
            if (isset($assistedNames[$name])) {
183
                $names[$name] = $assistedNames[$parameter->name];
184
185
                continue;
186
            }
187
188
            if (isset($webcontext[$name])) {
189
                $default = $this->getDefault($parameter);
190
                $names[$name] = new AssistedWebContextParam($webcontext[$name], $default);
191
192
                continue;
193
            }
194
195
            $names[$name] = $this->getParam($parameter);
196
        }
197
198
        return $names;
199
    }
200
201
    /**
202
     * @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...
203
     * @param array<string, ParamInterface>         $names
204
     */
205
    private function setInputFileParam(ReflectionParameter $parameter, array $inputFileAttributes, array &$names): void
206
    {
207
        $type = $parameter->getType();
208
        $isArray = $type instanceof ReflectionNamedType && $type->isBuiltin() && $type->getName() === 'array';
209
        if ($isArray) {
210
            $names[$parameter->name] = new InputFormsParam($this->factory, $parameter, $inputFileAttributes);
211
212
            return;
213
        }
214
215
        $names[$parameter->name] = new InputFormParam($this->factory, $parameter, $inputFileAttributes);
216
    }
217
218
    /** @psalm-return DefaultParam<mixed>|NoDefaultParam */
219
    private function getDefault(ReflectionParameter $parameter): DefaultParam|NoDefaultParam
220
    {
221
        return $parameter->isDefaultValueAvailable() === true ? new DefaultParam($parameter->getDefaultValue()) : new NoDefaultParam();
222
    }
223
224
    /**
225
     * @param array<string, ParamInterface>      $names
226
     * @param array<string, ReflectionParameter> $valueParams
227
     *
228
     * @return array<string, ParamInterface>
229
     */
230
    private function getNames(array $names, array $valueParams): array
231
    {
232
        // if there is more than single attributes
233
        if ($names) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $names of type array<string,BEAR\Resource\ParamInterface> 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...
234
            foreach ($valueParams as $paramName => $valueParam) {
235
                $names[$paramName] = $this->getParam($valueParam);
236
            }
237
        }
238
239
        return $names;
240
    }
241
242
    /**
243
     * @return ClassParam|OptionalParam|RequiredParam
244
     * @psalm-return ClassParam|OptionalParam<mixed>|RequiredParam
245
     */
246
    private function getParam(ReflectionParameter $parameter): ParamInterface
247
    {
248
        $type = $parameter->getType();
249
        if ($type instanceof ReflectionNamedType && ! $type->isBuiltin()) {
250
            return new ClassParam($type, $parameter);
251
        }
252
253
        return $parameter->isDefaultValueAvailable() === true ? new OptionalParam($parameter->getDefaultValue()) : new RequiredParam();
254
    }
255
}
256