Completed
Push — 1.x ( b209ea...8cf922 )
by Akihito
14s queued 11s
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
    /** @var Reader */
22
    private $reader;
23
24
    public function __construct(Reader $reader)
25 107
    {
26
        $this->reader = $reader;
27 107
    }
28 107
29 107
    /**
30
     * {@inheritdoc}
31 75
     */
32
    public function __invoke(callable $callable): array
33 75
    {
34
        /** @var array{0:object, 1:string} $callable */
35
        $method = new ReflectionMethod($callable[0], $callable[1]);
36 75
        $paramMetas = false;
37 75
        if (PHP_VERSION_ID >= 80000) {
38 75
            $paramMetas = $this->getAttributeParamMetas($method);
39 8
        }
40
41 72
        if (! $paramMetas) {
42 72
            $paramMetas = $this->getAnnotationParamMetas($method);
43 72
        }
44 72
45 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...
46 72
    }
47 72
48
    /**
49 72
     * @return array<string, AssistedWebContextParam|ParamInterface>
50
     */
51
    private function getAnnotationParamMetas(ReflectionMethod $method)
52 72
    {
53
        $parameters = $method->getParameters();
54 72
        $annotations = $this->reader->getMethodAnnotations($method);
55 72
        $assistedNames = $this->getAssistedNames($annotations);
56 35
        $webContext = $this->getWebContext($annotations);
57 3
58
        return $this->addNamedParams($parameters, $assistedNames, $webContext);
59 35
    }
60 35
61
    /**
62
     * @return array<string, ParamInterface>
63
     *
64 72
     * @psalm-suppress TooManyTemplateParams $refAttribute
65
     */
66
    private function getAttributeParamMetas(ReflectionMethod $method): array
67 72
    {
68
        $parameters = $method->getParameters();
69 72
        $names = $valueParams = [];
70 72
        foreach ($parameters as $parameter) {
71 35
            /** @var array<ReflectionAttribute<RequestParamInterface>> $refAttribute */
72 35
            $refAttribute = $parameter->getAttributes(RequestParamInterface::class, ReflectionAttribute::IS_INSTANCEOF);
73
            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...
74
                /** @var ?ResourceParam $resourceParam */
75
                $resourceParam = $refAttribute[0]->newInstance();
76 72
                if ($resourceParam instanceof ResourceParam) {
77
                    $names[$parameter->name] = new AssistedResourceParam($resourceParam);
78
                    continue;
79 1
                }
80
            }
81
82 1
            /** @var array<ReflectionAttribute<AbstractWebContextParam>> $refWebContext */
83 1
            $refWebContext = $parameter->getAttributes(AbstractWebContextParam::class, ReflectionAttribute::IS_INSTANCEOF);
84
            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...
85
                /** @var AbstractWebContextParam $webParam */
86 1
                $webParam = $refWebContext[0]->newInstance();
87
                /** @psalm-var scalar $defaultValue */
88
                $defaultValue = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
89
                $param = new AssistedWebContextParam($webParam, new DefaultParam($defaultValue));
90
                $names[$parameter->name] = $param;
91
                continue;
92
            }
93
94 72
            $valueParams[$parameter->name] = $parameter;
95
        }
96 72
97 72
        $names = $this->getNames($names, $valueParams);
98 65
99 4
        return $names;
100
    }
101 4
102
    /**
103 64
     * @param array<Assisted|object|ResourceParam> $annotations
104 4
     *
105 4
     * @return array<string, ParamInterface>
106
     */
107 4
    private function getAssistedNames(array $annotations): array
108
    {
109 62
        $names = [];
110
        foreach ($annotations as $annotation) {
111
            if ($annotation instanceof ResourceParam) {
112 72
                $names[$annotation->param] = new AssistedResourceParam($annotation);
113
            }
114
115 4
            if (! ($annotation instanceof Assisted)) {
116
                continue;
117 4
            }
118
119
            $names = $this->setAssistedAnnotation($names, $annotation);
120 62
        }
121
122 62
        return $names;
123
    }
124
125
    /**
126
     * @param array<object> $annotations
127
     *
128
     * @return array<string, AbstractWebContextParam>
129
     */
130
    private function getWebContext(array $annotations): array
131
    {
132
        $webcontext = [];
133
        foreach ($annotations as $annotation) {
134
            if (! ($annotation instanceof AbstractWebContextParam)) {
135
                continue;
136
            }
137
138
            $webcontext[$annotation->param] = $annotation;
139
        }
140
141
        return $webcontext;
142
    }
143
144
    /**
145
     * @param array<string, ParamInterface> $names
146
     *
147
     * @return array<string, ParamInterface>
148
     */
149
    private function setAssistedAnnotation(array $names, Assisted $assisted): array
150
    {
151
        foreach ($assisted->values as $assistedParam) {
152
            $names[$assistedParam] = new AssistedParam();
153
        }
154
155
        return $names;
156
    }
157
158
    /**
159
     * @param ReflectionParameter[]                  $parameters
160
     * @param array<string, ParamInterface>          $assistedNames
161
     * @param array<string, AbstractWebContextParam> $webcontext
162
     *
163
     * @return (AssistedWebContextParam|ParamInterface)[]
164
     * @psalm-return array<string, AssistedWebContextParam|ParamInterface>
165
     */
166
    private function addNamedParams(array $parameters, array $assistedNames, array $webcontext): array
167
    {
168
        $names = [];
169
        foreach ($parameters as $parameter) {
170
            $name = $parameter->name;
171
            if (isset($assistedNames[$name])) {
172
                $names[$name] = $assistedNames[$parameter->name];
173
174
                continue;
175
            }
176
177
            if (isset($webcontext[$name])) {
178
                $default = $this->getDefault($parameter);
179
                $names[$name] = new AssistedWebContextParam($webcontext[$name], $default);
180
181
                continue;
182
            }
183
184
            $names[$name] = $this->getParam($parameter);
185
        }
186
187
        return $names;
188
    }
189
190
    /**
191
     * @return DefaultParam|NoDefaultParam
192
     * @psalm-return DefaultParam<mixed>|NoDefaultParam
193
     */
194
    private function getDefault(ReflectionParameter $parameter)
195
    {
196
        return $parameter->isDefaultValueAvailable() === true ? new DefaultParam($parameter->getDefaultValue()) : new NoDefaultParam();
197
    }
198
199
    /**
200
     * @param array<string, AssistedResourceParam|AssistedWebContextParam> $names
201
     * @param array<string, ReflectionParameter>                           $valueParams
202
     *
203
     * @return array<string, ParamInterface>
204
     */
205
    private function getNames(array $names, array $valueParams): array
206
    {
207
        // if there is more than single attributes
208
        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...
209
            foreach ($valueParams as $paramName => $valueParam) {
210
                $names[$paramName] = $this->getParam($valueParam);
211
            }
212
        }
213
214
        return $names;
215
    }
216
217
    /**
218
     * @return ClassParam|OptionalParam|RequiredParam
219
     * @psalm-return ClassParam|OptionalParam<mixed>|RequiredParam
220
     */
221
    private function getParam(ReflectionParameter $parameter): ParamInterface
222
    {
223
        $type = $parameter->getType();
224
        if ($type instanceof ReflectionNamedType && ! $type->isBuiltin()) {
225
            return new ClassParam($type, $parameter);
226
        }
227
228
        return $parameter->isDefaultValueAvailable() === true ? new OptionalParam($parameter->getDefaultValue()) : new RequiredParam();
229
    }
230
}
231