Completed
Push — 1.x ( 246016...0c7587 )
by Akihito
03:54 queued 03:52
created

NamedParamMetas::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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