NamedParamMetas   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 206
Duplicated Lines 0 %

Importance

Changes 11
Bugs 1 Features 0
Metric Value
eloc 74
c 11
b 1
f 0
dl 0
loc 206
rs 9.84
wmc 32

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getDefault() 0 3 2
A getAssistedNames() 0 18 4
A getNames() 0 10 3
A __invoke() 0 11 2
A setAssistedAnnotation() 0 7 2
A getAttributeParamMetas() 0 30 5
A getWebContext() 0 12 3
A getAnnotationParamMetas() 0 8 1
A getParam() 0 20 6
A addNamedParams() 0 22 4
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) {
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...
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) {
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...
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) {
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...
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