NamedParamMetas::getParam()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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