Passed
Pull Request — 1.x (#273)
by Akihito
04:06 queued 02:09
created

NamedParamMetas::getNames()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
nc 2
nop 2
dl 0
loc 10
ccs 0
cts 0
cp 0
crap 12
rs 10
c 1
b 0
f 0
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 Doctrine\Common\Annotations\Reader;
10
use Ray\Di\Di\Assisted;
11
use Ray\WebContextParam\Annotation\AbstractWebContextParam;
12
use ReflectionAttribute;
13
use ReflectionMethod;
14
use ReflectionNamedType;
15
use ReflectionParameter;
16
17
use const PHP_VERSION_ID;
18
19
final class NamedParamMetas implements NamedParamMetasInterface
20
{
21
    private Reader $reader;
22
23
    public function __construct(Reader $reader)
24
    {
25 107
        $this->reader = $reader;
26
    }
27 107
28 107
    /**
29 107
     * {@inheritdoc}
30
     */
31 75
    public function __invoke(callable $callable): array
32
    {
33 75
        /** @var array{0:object, 1:string} $callable */
34
        $method = new ReflectionMethod($callable[0], $callable[1]);
35
        $paramMetas = false;
36 75
        if (PHP_VERSION_ID >= 80000) {
37 75
            $paramMetas = $this->getAttributeParamMetas($method);
38 75
        }
39 8
40
        if (! $paramMetas) {
41 72
            $paramMetas = $this->getAnnotationParamMetas($method);
42 72
        }
43 72
44 72
        return $paramMetas;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $paramMetas could return the type false which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
45 72
    }
46 72
47 72
    /**
48
     * @return array<string, AssistedWebContextParam|ParamInterface>
49 72
     */
50
    private function getAnnotationParamMetas(ReflectionMethod $method)
51
    {
52 72
        $parameters = $method->getParameters();
53
        $annotations = $this->reader->getMethodAnnotations($method);
54 72
        $assistedNames = $this->getAssistedNames($annotations);
55 72
        $webContext = $this->getWebContext($annotations);
56 35
57 3
        return $this->addNamedParams($parameters, $assistedNames, $webContext);
58
    }
59 35
60 35
    /**
61
     * @return array<string, ParamInterface>
62
     *
63
     * @psalm-suppress TooManyTemplateParams $refAttribute
64 72
     */
65
    private function getAttributeParamMetas(ReflectionMethod $method): array
66
    {
67 72
        $parameters = $method->getParameters();
68
        $names = $valueParams = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $valueParams is dead and can be removed.
Loading history...
69 72
        foreach ($parameters as $parameter) {
70 72
            /** @var array<ReflectionAttribute<RequestParamInterface>> $refAttribute */
71 35
            $refAttribute = $parameter->getAttributes(RequestParamInterface::class, ReflectionAttribute::IS_INSTANCEOF);
72 35
            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...
73
                /** @var ?ResourceParam $resourceParam */
74
                $resourceParam = $refAttribute[0]->newInstance();
75
                if ($resourceParam instanceof ResourceParam) {
76 72
                    $names[$parameter->name] = new AssistedResourceParam($resourceParam);
77
                    continue;
78
                }
79 1
            }
80
81
            /** @var array<ReflectionAttribute<AbstractWebContextParam>> $refWebContext */
82 1
            $refWebContext = $parameter->getAttributes(AbstractWebContextParam::class, ReflectionAttribute::IS_INSTANCEOF);
83 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...
84
                /** @var AbstractWebContextParam $webParam */
85
                $webParam = $refWebContext[0]->newInstance();
86 1
                /** @psalm-var scalar $defaultValue */
87
                $defaultValue = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
88
                $param = new AssistedWebContextParam($webParam, new DefaultParam($defaultValue));
89
                $names[$parameter->name] = $param;
90
                continue;
91
            }
92
93
            $names[$parameter->name] = $parameter;
94 72
        }
95
96 72
        $names = $this->convertParam($names);
97 72
98 65
        return $names;
99 4
    }
100
101 4
    /**
102
     * @param array<Assisted|object|ResourceParam> $annotations
103 64
     *
104 4
     * @return array<string, ParamInterface>
105 4
     */
106
    private function getAssistedNames(array $annotations): array
107 4
    {
108
        $names = [];
109 62
        foreach ($annotations as $annotation) {
110
            if ($annotation instanceof ResourceParam) {
111
                $names[$annotation->param] = new AssistedResourceParam($annotation);
112 72
            }
113
114
            if (! ($annotation instanceof Assisted)) {
115 4
                continue;
116
            }
117 4
118
            $names = $this->setAssistedAnnotation($names, $annotation);
119
        }
120 62
121
        return $names;
122 62
    }
123
124
    /**
125
     * @param array<object> $annotations
126
     *
127
     * @return array<string, AbstractWebContextParam>
128
     */
129
    private function getWebContext(array $annotations): array
130
    {
131
        $webcontext = [];
132
        foreach ($annotations as $annotation) {
133
            if (! ($annotation instanceof AbstractWebContextParam)) {
134
                continue;
135
            }
136
137
            $webcontext[$annotation->param] = $annotation;
138
        }
139
140
        return $webcontext;
141
    }
142
143
    /**
144
     * @param array<string, ParamInterface> $names
145
     *
146
     * @return array<string, ParamInterface>
147
     */
148
    private function setAssistedAnnotation(array $names, Assisted $assisted): array
149
    {
150
        foreach ($assisted->values as $assistedParam) {
151
            $names[$assistedParam] = new AssistedParam();
152
        }
153
154
        return $names;
155
    }
156
157
    /**
158
     * @param ReflectionParameter[]                  $parameters
159
     * @param array<string, ParamInterface>          $assistedNames
160
     * @param array<string, AbstractWebContextParam> $webcontext
161
     *
162
     * @return (AssistedWebContextParam|ParamInterface)[]
163
     * @psalm-return array<string, AssistedWebContextParam|ParamInterface>
164
     */
165
    private function addNamedParams(array $parameters, array $assistedNames, array $webcontext): array
166
    {
167
        $names = [];
168
        foreach ($parameters as $parameter) {
169
            $name = $parameter->name;
170
            if (isset($assistedNames[$name])) {
171
                $names[$name] = $assistedNames[$parameter->name];
172
173
                continue;
174
            }
175
176
            if (isset($webcontext[$name])) {
177
                $default = $this->getDefault($parameter);
178
                $names[$name] = new AssistedWebContextParam($webcontext[$name], $default);
179
180
                continue;
181
            }
182
183
            $names[$name] = $this->getParam($parameter);
184
        }
185
186
        return $names;
187
    }
188
189
    /**
190
     * @return DefaultParam|NoDefaultParam
191
     * @psalm-return DefaultParam<mixed>|NoDefaultParam
192
     */
193
    private function getDefault(ReflectionParameter $parameter)
194
    {
195
        return $parameter->isDefaultValueAvailable() === true ? new DefaultParam($parameter->getDefaultValue()) : new NoDefaultParam();
196
    }
197
198
    /**
199
     * @param array<string, AssistedResourceParam|AssistedWebContextParam> $names
200
     * @param array<string, ReflectionParameter>                           $valueParams
201
     *
202
     * @return array<string, ParamInterface>
203
     */
204
    private function convertParam(array $names): array
205
    {
206
        // if there is more than single attributes
207
        if ($names) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $names of type array<string,BEAR\Resour...ssistedWebContextParam> 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...
208
            foreach ($names as $paramName => $maybeParam) {
209
                if (! ($maybeParam instanceof ReflectionParameter)) {
210
                    continue;
211
                }
212
213
                $names[$paramName] = $this->getParam($maybeParam);
214
            }
215
        }
216
217
        return $names;
218
    }
219
220
    /**
221
     * @return ClassParam|OptionalParam|RequiredParam
222
     * @psalm-return ClassParam|OptionalParam<mixed>|RequiredParam
223
     */
224
    private function getParam(ReflectionParameter $parameter): ParamInterface
225
    {
226
        $type = $parameter->getType();
227
        if ($type instanceof ReflectionNamedType && ! $type->isBuiltin()) {
228
            return new ClassParam($type, $parameter);
229
        }
230
231
        return $parameter->isDefaultValueAvailable() === true ? new OptionalParam($parameter->getDefaultValue()) : new RequiredParam();
232
    }
233
}
234