Passed
Pull Request — 1.x (#339)
by Akihito
04:42 queued 02:25
created

OptionsMethods::getInMap()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
nc 2
nop 1
dl 0
loc 14
ccs 0
cts 0
cp 0
crap 6
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource;
6
7
use BEAR\Resource\Annotation\Embed;
8
use BEAR\Resource\Annotation\JsonSchema;
9
use BEAR\Resource\Annotation\Link;
10
use Ray\Aop\ReflectionMethod;
11
use Ray\Di\Di\Named;
12
use Ray\WebContextParam\Annotation\AbstractWebContextParam;
13
use Ray\WebContextParam\Annotation\CookieParam;
14
use Ray\WebContextParam\Annotation\EnvParam;
15
use Ray\WebContextParam\Annotation\FilesParam;
16
use Ray\WebContextParam\Annotation\FormParam;
17
use Ray\WebContextParam\Annotation\QueryParam;
18
use Ray\WebContextParam\Annotation\ServerParam;
19
20
use function assert;
21
use function class_exists;
22
use function file_exists;
23
use function file_get_contents;
24
use function json_decode;
25
26
use const JSON_THROW_ON_ERROR;
27
28
/**
29
 * @psalm-import-type Body from Types
30
 * @psalm-import-type InsMap from Types
31
 * @psalm-import-type OptionsMethodsResponse from Types
32
 * @psalm-import-type OptionsResponse from Types
33
 */
34
final readonly class OptionsMethods
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_READONLY, expecting T_CLASS on line 34 at column 6
Loading history...
35
{
36
    /**
37
     * Constants for annotation name and "in" name
38
     */
39
    private const WEB_CONTEXT_NAME = [
40
        CookieParam::class => 'cookie',
41
        EnvParam::class => 'env',
42 99
        FormParam::class => 'formData',
43
        QueryParam::class => 'query',
44 99
        ServerParam::class => 'server',
45 99
        FilesParam::class => 'files',
46 99
    ];
47
48 7
    public function __construct(
49
        #[Named('json_schema_dir')]
50 7
        private string $schemaDir = '',
51 7
    ) {
52 7
    }
53 7
54 7
    /** @return OptionsMethodsResponse */
55 7
    public function __invoke(ResourceObject $ro, string $requestMethod): array
56 7
    {
57 1
        $method = new ReflectionMethod($ro::class, 'on' . $requestMethod);
58
        $ins = $this->getInMap($method);
59
        [$doc, $paramDoc] = (new OptionsMethodDocBolck())($method);
60 6
        $methodOption = $doc;
61
        $paramMetas = (new OptionsMethodRequest())($method, $paramDoc, $ins);
62
        $schema = $this->getJsonSchema($method);
63 7
        $request = ! empty($paramMetas) ? ['request' => $paramMetas] : [];
64
        $methodOption += $request;
65 7
        if (! empty($schema)) {
66 7
            $methodOption += ['schema' => $schema];
67 7
        }
68 5
69 5
        $extras = $this->getMethodExtras($method);
70
        if (! empty($extras)) {
71
            $methodOption += $extras;
72
        }
73 7
74
        return $methodOption; // @phpstan-ignore-line
75
    }
76 7
77
    /**
78 7
     * @return (Embed|Link)[][]
79 7
     * @psalm-return array{links?: non-empty-list<Link>, embed?: non-empty-list<Embed>}
80 5
     * @phpstan-return (Embed|Link)[][]
81
     */
82 2
    private function getMethodExtras(ReflectionMethod $method): array
83 2
    {
84 1
        $extras = [];
85
        $annotations = $method->getAnnotations();
86
        foreach ($annotations as $annotation) {
87 1
            if ($annotation instanceof Link) {
88
                $extras['links'][] = $annotation;
89
            }
90
91
            if (! ($annotation instanceof Embed)) {
92
                continue;
93
            }
94
95
            $extras['embed'][] = $annotation;
96
        }
97
98
        return $extras;
99
    }
100
101
    /** @return InsMap */
102
    private function getInMap(ReflectionMethod $method): array
103
    {
104
        $ins = [];
105
        bc_for_annotation: {
106
            // @codeCoverageIgnoreStart
107
            $annotations = $method->getAnnotations();
108
            $ins = $this->getInsFromMethodAnnotations($annotations, $ins);
109
        if ($ins) {
110
            return $ins;
111
        }
112
            // @codeCoverageIgnoreEnd
113
        }
114
115
        return $this->getInsFromParameterAttributes($method, $ins);
116
    }
117
118
    /** @return Body */
119
    private function getJsonSchema(ReflectionMethod $method): array
120
    {
121
        $schema = $method->getAnnotation(JsonSchema::class);
122
        if (! $schema instanceof JsonSchema) {
123
            return [];
124
        }
125
126
        $schemaFile = $this->schemaDir . '/' . $schema->schema;
127
        if (! file_exists($schemaFile)) {
128
            return [];
129
        }
130
131
        return (array) json_decode((string) file_get_contents($schemaFile), null, 512, JSON_THROW_ON_ERROR);
132
    }
133
134
    /**
135
     * @param array<object> $annotations
136
     * @param InsMap        $ins
137
     *
138
     * @return InsMap
139
     *
140
     * @codeCoverageIgnore BC for annotation
141
     * @psalm-suppress MixedReturnTypeCoercion
142
     * @psalm-suppress MixedArrayOffset
143
     * @psalm-suppress UndefinedPropertyFetch
144
     */
145
    public function getInsFromMethodAnnotations(array $annotations, array $ins): array
146
    {
147
        foreach ($annotations as $annotation) {
148
            if (! ($annotation instanceof AbstractWebContextParam)) {
149
                continue;
150
            }
151
152
            $class = $annotation::class;
153
            assert(class_exists($class));
154
            /** @var array-key $webKey */
155
            assert($class === CookieParam::class || $class === EnvParam::class || $class === FormParam::class || $class === QueryParam::class || $class === ServerParam::class || $class === FilesParam::class);
156
            $webKey = self::WEB_CONTEXT_NAME[$class];
157
158
            $ins[$annotation->param] = $webKey;
159
        }
160
161
        return $ins;
162
    }
163
164
    /**
165
     * @param InsMap $ins
166
     *
167
     * @return InsMap
168
     *
169
     * @psalm-suppress InvalidArrayOffset, MixedAssignment, MixedReturnTypeCoercion
170
     */
171
    public function getInsFromParameterAttributes(ReflectionMethod $method, array $ins): array
172
    {
173
        $parameters = $method->getParameters();
174
        foreach ($parameters as $parameter) {
175
            $attributes = $parameter->getAttributes();
176
            foreach ($attributes as $attribute) {
177
                $instance = $attribute->newInstance();
178
                if (! ($instance instanceof AbstractWebContextParam)) {
179
                    continue;
180
                }
181
182
                /** @var array-key $class */
183
                /** @phpstan-ignore varTag.nativeType */
184
                $class = $instance::class;
185
                $ins[$parameter->name] = self::WEB_CONTEXT_NAME[$class];
186
            }
187
        }
188
189
        return $ins;
190
    }
191
}
192