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