Completed
Push — 1.x ( 8452d6...11fc5a )
by Akihito
15s queued 12s
created

OptionsMethods   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 147
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 6
Bugs 0 Features 1
Metric Value
eloc 62
dl 0
loc 147
ccs 29
cts 29
cp 1
rs 10
c 6
b 0
f 1
wmc 21

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getInsFromParameterAttributes() 0 18 4
A getMethodExtras() 0 17 4
A getInsFromMethodAnnotations() 0 13 3
A __invoke() 0 20 4
A getJsonSchema() 0 13 3
A getInMap() 0 13 2
A __construct() 0 4 1
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 Doctrine\Common\Annotations\Reader;
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
use ReflectionMethod;
20
21
use function assert;
22
use function class_exists;
23
use function file_exists;
24
use function file_get_contents;
25
use function json_decode;
26
27
use const JSON_THROW_ON_ERROR;
28
29
final class OptionsMethods
30
{
31
    /**
32
     * Constants for annotation name and "in" name
33
     */
34
    private const WEB_CONTEXT_NAME = [
35
        CookieParam::class => 'cookie',
36
        EnvParam::class => 'env',
37
        FormParam::class => 'formData',
38
        QueryParam::class => 'query',
39
        ServerParam::class => 'server',
40
        FilesParam::class => 'files',
41
    ];
42 99
43
    public function __construct(
44 99
        private Reader $reader,
45 99
        #[Named('json_schema_dir')] private string $schemaDir = '',
46 99
    ) {
47
    }
48 7
49
    /**
50 7
     * return array{summary?: string, description?: string, request: array, links: array, embed: array}
51 7
     *
52 7
     * @return array<int|string, array<mixed>|string>
53 7
     */
54 7
    public function __invoke(ResourceObject $ro, string $requestMethod): array
55 7
    {
56 7
        $method = new ReflectionMethod($ro::class, 'on' . $requestMethod);
57 1
        $ins = $this->getInMap($method);
58
        [$doc, $paramDoc] = (new OptionsMethodDocBolck())($method);
59
        $methodOption = $doc;
60 6
        $paramMetas = (new OptionsMethodRequest())($method, $paramDoc, $ins);
61
        $schema = $this->getJsonSchema($method);
62
        $request = $paramMetas ? ['request' => $paramMetas] : [];
63 7
        $methodOption += $request;
64
        if (! empty($schema)) {
65 7
            $methodOption += ['schema' => $schema];
66 7
        }
67 7
68 5
        $extras = $this->getMethodExtras($method);
69 5
        if (! empty($extras)) {
70
            $methodOption += $extras;
71
        }
72
73 7
        return $methodOption;
74
    }
75
76 7
    /**
77
     * @return (Embed|Link)[][]
78 7
     * @psalm-return array{links?: non-empty-list<Link>, embed?: non-empty-list<Embed>}
79 7
     * @phpstan-return (Embed|Link)[][]
80 5
     */
81
    private function getMethodExtras(ReflectionMethod $method): array
82 2
    {
83 2
        $extras = [];
84 1
        $annotations = $this->reader->getMethodAnnotations($method);
85
        foreach ($annotations as $annotation) {
86
            if ($annotation instanceof Link) {
87 1
                $extras['links'][] = $annotation;
88
            }
89
90
            if (! ($annotation instanceof Embed)) {
91
                continue;
92
            }
93
94
            $extras['embed'][] = $annotation;
95
        }
96
97
        return $extras;
98
    }
99
100
    /** @return array<string, string> */
101
    private function getInMap(ReflectionMethod $method): array
102
    {
103
        $ins = [];
104
        $annotations = $this->reader->getMethodAnnotations($method);
105
        $ins = $this->getInsFromMethodAnnotations($annotations, $ins);
106
        if ($ins) {
107
            return $ins;
108
        }
109
110
        /** @var array<string, string> $insParam */
111
        $insParam = $this->getInsFromParameterAttributes($method, $ins);
112
113
        return $insParam;
114
    }
115
116
    /** @return array<string, mixed> */
117
    private function getJsonSchema(ReflectionMethod $method): array
118
    {
119
        $schema = $this->reader->getMethodAnnotation($method, JsonSchema::class);
120
        if (! $schema instanceof JsonSchema) {
121
            return [];
122
        }
123
124
        $schemaFile = $this->schemaDir . '/' . $schema->schema;
125
        if (! file_exists($schemaFile)) {
126
            return [];
127
        }
128
129
        return (array) json_decode((string) file_get_contents($schemaFile), null, 512, JSON_THROW_ON_ERROR);
130
    }
131
132
    /**
133
     * @param array<object>         $annotations
134
     * @param array<string, string> $ins
135
     *
136
     * @return array<string, string>
137
     */
138
    public function getInsFromMethodAnnotations(array $annotations, array $ins): array
139
    {
140
        foreach ($annotations as $annotation) {
141
            if (! ($annotation instanceof AbstractWebContextParam)) {
142
                continue;
143
            }
144
145
            $class = $annotation::class;
146
            assert(class_exists($class));
147
            $ins[$annotation->param] = self::WEB_CONTEXT_NAME[$class];
148
        }
149
150
        return $ins;
151
    }
152
153
    /**
154
     * @param array<string, string> $ins
155
     *
156
     * @return array<string, string>
157
     */
158
    public function getInsFromParameterAttributes(ReflectionMethod $method, array $ins): array|null
159
    {
160
        $parameters = $method->getParameters();
161
        foreach ($parameters as $parameter) {
162
            $attributes = $parameter->getAttributes();
163
            foreach ($attributes as $attribute) {
164
                $instance = $attribute->newInstance();
165
                if (! ($instance instanceof AbstractWebContextParam)) {
166
                    continue;
167
                }
168
169
                $class = $instance::class;
170
                assert(class_exists($class));
171
                $ins[$parameter->name] = self::WEB_CONTEXT_NAME[$class];
172
            }
173
        }
174
175
        return $ins;
176
    }
177
}
178