Passed
Push — 1.x ( 90de9d...e2e7a5 )
by Akihito
05:28 queued 03:04
created

NamedParamMetas   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 238
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 10
Bugs 0 Features 0
Metric Value
eloc 83
dl 0
loc 238
ccs 51
cts 51
cp 1
rs 9.44
c 10
b 0
f 0
wmc 37
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\InputQuery\Attribute\Input;
12
use Ray\InputQuery\Attribute\InputFile;
13
use Ray\InputQuery\FileUploadFactoryInterface;
14
use Ray\InputQuery\InputQueryInterface;
15
use Ray\WebContextParam\Annotation\AbstractWebContextParam;
16
use ReflectionAttribute;
17
use ReflectionNamedType;
18
use ReflectionParameter;
19
20
/**
21
 * @psalm-import-type ParamMap from Types
22
 * @psalm-import-type WebContextParamMap from Types
23
 * @psalm-import-type ReflectionParameterMap from Types
24
 * @psalm-import-type ObjectList from Types
25 107
 */
26
final readonly class NamedParamMetas implements NamedParamMetasInterface
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_READONLY, expecting T_CLASS on line 26 at column 6
Loading history...
27 107
{
28 107
    /** @param InputQueryInterface<object> $inputQuery */
29 107
    public function __construct(
30
        private InputQueryInterface $inputQuery,
31 75
        private FileUploadFactoryInterface $factory,
32
    ) {
33 75
    }
34
35
    /**
36 75
     * {@inheritDoc}
37 75
     */
38 75
    #[Override]
39 8
    public function __invoke(callable $callable): array
40
    {
41 72
        // callable is [object, string] but native type doesn't allow array access
42 72
        /** @psalm-suppress InvalidArrayAccess, MixedArgument */
43 72
        /** @var array{0:object, 1:string} $callable */
44 72
        $method = new ReflectionMethod($callable[0], $callable[1]); // @phpstan-ignore-line
45 72
46 72
        return $this->getAttributeParamMetas($method);
47 72
    }
48
49 72
    /**
50
     * @return ParamMap
51
     *
52 72
     * @psalm-suppress TooManyTemplateParams $refAttribute
53
     * @psalm-suppress PossiblyInvalidArrayAssignment
54 72
     */
55 72
    private function getAttributeParamMetas(ReflectionMethod $method): array
56 35
    {
57 3
        $parameters = $method->getParameters();
58
        $names = $valueParams = [];
59 35
60 35
        // Check method-level ResourceParam attributes
61
        $methodResourceParams = $method->getAttributes(ResourceParam::class);
62
        foreach ($methodResourceParams as $methodAttr) {
63
            $resourceParam = $methodAttr->newInstance();
64 72
            $names[$resourceParam->param] = new AssistedResourceParam($resourceParam);
65
        }
66
67 72
        foreach ($parameters as $parameter) {
68
            // Skip if already set by method-level attribute
69 72
            if (isset($names[$parameter->name])) {
70 72
                continue;
71 35
            }
72 35
73
            $refAttribute = $parameter->getAttributes(RequestParamInterface::class, ReflectionAttribute::IS_INSTANCEOF);
74
            if ($refAttribute) {
75
                /** @var ?ResourceParam $resourceParam */
76 72
                $resourceParam = $refAttribute[0]->newInstance();
77
                if ($resourceParam instanceof ResourceParam) {
78
                    $names[$parameter->name] = new AssistedResourceParam($resourceParam);
79 1
                    continue;
80
                }
81
            }
82 1
83 1
            $refWebContext = $parameter->getAttributes(AbstractWebContextParam::class, ReflectionAttribute::IS_INSTANCEOF);
84
            if ($refWebContext) {
85
                $webParam = $refWebContext[0]->newInstance();
86 1
                $default = $this->getDefault($parameter);
87
                $param = new AssistedWebContextParam($webParam, $default);
88
                $names[$parameter->name] = $param;
89
                continue;
90
            }
91
92
            // #[Input]
93
            $inputAttribute = $parameter->getAttributes(Input::class, ReflectionAttribute::IS_INSTANCEOF);
94 72
            if ($inputAttribute) {
95
                $names[$parameter->name] = new InputParam($this->inputQuery, $parameter);
96 72
                continue;
97 72
            }
98 65
99 4
            // #[InputFile]
100
            $inputFileAttributes = $parameter->getAttributes(InputFile::class, ReflectionAttribute::IS_INSTANCEOF);
101 4
            if ($inputFileAttributes) {
102
                $this->setInputFileParam($parameter, $inputFileAttributes, $names);
103 64
                continue;
104 4
            }
105 4
106
            $valueParams[$parameter->name] = $parameter;
107 4
        }
108
109 62
        $names = $this->getNames($names, $valueParams);
110
111
        return $names;
112 72
    }
113
114
    /**
115 4
     * @param array<ReflectionAttribute<InputFile>> $inputFileAttributes
116
     * @param ParamMap                              $names
117 4
     */
118
    private function setInputFileParam(ReflectionParameter $parameter, array $inputFileAttributes, array &$names): void
119
    {
120 62
        $type = $parameter->getType();
121
        $isArray = $type instanceof ReflectionNamedType && $type->isBuiltin() && $type->getName() === 'array';
122 62
        if ($isArray) {
123
            $names[$parameter->name] = new InputFormsParam($this->factory, $parameter, $inputFileAttributes);
124
125
            return;
126
        }
127
128
        $names[$parameter->name] = new InputFormParam($this->factory, $parameter, $inputFileAttributes);
129
    }
130
131
    /** @psalm-return DefaultParam<mixed>|NoDefaultParam */
132
    private function getDefault(ReflectionParameter $parameter): DefaultParam|NoDefaultParam
133
    {
134
        return $parameter->isDefaultValueAvailable() === true ? new DefaultParam($parameter->getDefaultValue()) : new NoDefaultParam();
135
    }
136
137
    /**
138
     * @param ParamMap               $names
139
     * @param ReflectionParameterMap $valueParams
140
     *
141
     * @return ParamMap
142
     */
143
    private function getNames(array $names, array $valueParams): array
144
    {
145
        foreach ($valueParams as $paramName => $valueParam) {
146
            $names[$paramName] = $this->getParam($valueParam);
147
        }
148
149
        return $names;
150
    }
151
152
    /**
153
     * @return ClassParam|OptionalParam|RequiredParam
154
     * @psalm-return ClassParam|OptionalParam<mixed>|RequiredParam
155
     */
156
    private function getParam(ReflectionParameter $parameter): ParamInterface
157
    {
158
        $type = $parameter->getType();
159
        if ($type instanceof ReflectionNamedType && ! $type->isBuiltin()) {
160
            return new ClassParam($type, $parameter);
161
        }
162
163
        return $parameter->isDefaultValueAvailable() === true ? new OptionalParam($parameter->getDefaultValue()) : new RequiredParam();
164
    }
165
}
166