Passed
Pull Request — 1.x (#321)
by Akihito
02:41
created

OptionsMethods::getInsFromParameterAttributes()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 13
nc 5
nop 2
dl 0
loc 22
rs 9.5222
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource\Options;
6
7
use BEAR\Resource\Annotation\Embed;
8
use BEAR\Resource\Annotation\JsonSchema;
9
use BEAR\Resource\Annotation\Link;
10
use BEAR\Resource\ResourceObject;
11
use Ray\Aop\ReflectionMethod;
12
use Ray\Di\Di\Named;
13
use Ray\WebContextParam\Annotation\AbstractWebContextParam;
14
use Ray\WebContextParam\Annotation\CookieParam;
15
use Ray\WebContextParam\Annotation\EnvParam;
16
use Ray\WebContextParam\Annotation\FilesParam;
17
use Ray\WebContextParam\Annotation\FormParam;
18
use Ray\WebContextParam\Annotation\QueryParam;
19
use Ray\WebContextParam\Annotation\ServerParam;
20
21
use function array_merge;
22
use function array_unique;
23
use function array_values;
24
use function file_exists;
25
use function file_get_contents;
26
use function json_decode;
27
28
use const JSON_THROW_ON_ERROR;
29
30
/**
31
 * @psalm-type WebContextKey = class-string<AbstractWebContextParam>
32
 * @psalm-type WebContextValue = 'cookie'|'env'|'formData'|'query'|'server'|'files'
33
 * @psalm-type OptionParamDoc = array{description?: string, embed?: mixed, links?: mixed, request?: mixed, schema?: mixed, summary?: string}
34
 * @psalm-import-type OptionMethodMeta from InputParamMetaInterface
35
 */
36
final class OptionsMethods
37
{
38
    /**
39
     * Constants for annotation name and "in" name
40
     */
41
    private const WEB_CONTEXT_NAME = [
42
        CookieParam::class => 'cookie',
43
        EnvParam::class => 'env',
44
        FormParam::class => 'formData',
45
        QueryParam::class => 'query',
46
        ServerParam::class => 'server',
47
        FilesParam::class => 'files',
48
    ];
49
50
    public function __construct(
51
        private readonly InputParamMetaInterface $inputParamMeta,
52
        #[Named('json_schema_dir')]
53
        private readonly string $schemaDir = '',
54
    ) {
55
    }
56
57
    /** @return OptionParamDoc */
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\Options\OptionParamDoc was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
58
    public function __invoke(ResourceObject $ro, string $requestMethod): array
59
    {
60
        $method = new ReflectionMethod($ro::class, 'on' . $requestMethod);
61
        $ins = $this->getInMap($method);
62
        [$doc, $paramDoc] = (new OptionsMethodDocBolck())($method);
63
        $methodOption = $doc;
64
        $paramMetas = (new OptionsMethodRequest())($method, $paramDoc, $ins);
65
        $inputMetas = $this->inputParamMeta->get($method);
66
        $paramMetas = $this->mergeParameterMetas($paramMetas, $inputMetas);
67
        $schema = $this->getJsonSchema($method);
68
        $request = $paramMetas ? ['request' => $paramMetas] : [];
69
        $methodOption += $request;
70
        if (! empty($schema)) {
71
            $methodOption += ['schema' => $schema];
72
        }
73
74
        $extras = $this->getMethodExtras($method);
75
        if (! empty($extras)) {
76
            $methodOption += $extras;
77
        }
78
79
        /** @var OptionParamDoc $methodOption */
80
        return $methodOption;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $methodOption returns the type BEAR\Resource\Options\OptionParamDoc which is incompatible with the type-hinted return array.
Loading history...
81
    }
82
83
    /**
84
     * @return (Embed|Link)[][]
85
     * @psalm-return array{links?: non-empty-list<Link>, embed?: non-empty-list<Embed>}
86
     * @phpstan-return (Embed|Link)[][]
87
     */
88
    private function getMethodExtras(ReflectionMethod $method): array
89
    {
90
        $extras = [];
91
        $annotations = $method->getAnnotations();
92
        foreach ($annotations as $annotation) {
93
            if ($annotation instanceof Link) {
94
                $extras['links'][] = $annotation;
95
            }
96
97
            if (! ($annotation instanceof Embed)) {
98
                continue;
99
            }
100
101
            $extras['embed'][] = $annotation;
102
        }
103
104
        return $extras;
105
    }
106
107
    /** @return array<string, string> */
108
    private function getInMap(ReflectionMethod $method): array
109
    {
110
        $ins = [];
111
        bc_for_annotation: {
112
            // @codeCoverageIgnoreStart
113
            $annotations = $method->getAnnotations();
114
            $ins = $this->getInsFromMethodAnnotations($annotations, $ins);
115
        if ($ins) {
116
            return $ins;
117
        }
118
            // @codeCoverageIgnoreEnd
119
        }
120
121
        /** @var array<string, string> $insParam */
122
        $insParam = $this->getInsFromParameterAttributes($method, $ins);
123
124
        return $insParam;
125
    }
126
127
    /** @return array<string, mixed> */
128
    private function getJsonSchema(ReflectionMethod $method): array
129
    {
130
        $schema = $method->getAnnotation(JsonSchema::class);
131
        if (! $schema instanceof JsonSchema) {
132
            return [];
133
        }
134
135
        $schemaFile = $this->schemaDir . '/' . $schema->schema;
136
        if (! file_exists($schemaFile)) {
137
            return [];
138
        }
139
140
        /** @var array<string, mixed> $schema */
141
        $schema = (array) json_decode((string) file_get_contents($schemaFile), null, 512, JSON_THROW_ON_ERROR);
142
143
        return $schema;
144
    }
145
146
    /**
147
     * @param array<object>         $annotations
148
     * @param array<string, string> $ins
149
     *
150
     * @return array<string, string>
151
     *
152
     * @codeCoverageIgnore BC for annotation
153
     */
154
    public function getInsFromMethodAnnotations(array $annotations, array $ins): array
155
    {
156
        foreach ($annotations as $annotation) {
157
            if (! ($annotation instanceof AbstractWebContextParam)) {
158
                continue;
159
            }
160
161
            $class = $annotation::class;
162
            if (! isset(self::WEB_CONTEXT_NAME[$class])) {
163
                continue;
164
            }
165
166
            $ins[$annotation->param] = self::WEB_CONTEXT_NAME[$class];
167
        }
168
169
        return $ins;
170
    }
171
172
    /**
173
     * @param array<string, string> $ins
174
     *
175
     * @return array<string, string>
176
     */
177
    public function getInsFromParameterAttributes(ReflectionMethod $method, array $ins): array|null
178
    {
179
        $parameters = $method->getParameters();
180
        foreach ($parameters as $parameter) {
181
            $attributes = $parameter->getAttributes();
182
            foreach ($attributes as $attribute) {
183
                $instance = $attribute->newInstance();
184
                if (! ($instance instanceof AbstractWebContextParam)) {
185
                    continue;
186
                }
187
188
                $class = $instance::class;
189
                if (! isset(self::WEB_CONTEXT_NAME[$class])) {
190
                    continue;
191
                }
192
193
                $webContextName = self::WEB_CONTEXT_NAME[$class];
194
                $ins[$parameter->name] = $webContextName;
195
            }
196
        }
197
198
        return $ins;
199
    }
200
201
    /**
202
     * Merge parameter metadata from OptionsMethodRequest and InputParamMeta
203
     *
204
     * @param OptionMethodMeta $regularParams
205
     * @param OptionMethodMeta $inputParams
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\Options\OptionMethodMeta was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
206
     *
207
     * @return OptionMethodMeta
208
     */
209
    private function mergeParameterMetas(array $regularParams, array $inputParams): array
210
    {
211
        $regularParameters = $regularParams['parameters'] ?? [];
212
        $inputParameters   = $inputParams['parameters'] ?? [];
213
214
        $regularRequired = $regularParams['required'] ?? [];
215
        $inputRequired   = $inputParams['required'] ?? [];
216
217
        $parameters = array_merge($regularParameters, $inputParameters);
218
        $required = array_values(array_unique(array_merge($regularRequired, $inputRequired)));
219
220
        if ($parameters === [] && $required === []) {
221
            return [];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array() returns the type array which is incompatible with the documented return type BEAR\Resource\Options\OptionMethodMeta.
Loading history...
222
        }
223
224
        return [
0 ignored issues
show
Bug Best Practice introduced by
The expression return array('parameters...required' => $required) returns the type array<string,array> which is incompatible with the documented return type BEAR\Resource\Options\OptionMethodMeta.
Loading history...
225
            'parameters' => $parameters,
226
            'required'   => $required,
227
        ];
228
    }
229
}
230