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

InputParamMeta::get()   B

Complexity

Conditions 7
Paths 17

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 25
c 1
b 0
f 0
nc 17
nop 1
dl 0
loc 43
rs 8.5866
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource\Options;
6
7
use BEAR\Resource\Input\InputAttributeIterator;
8
use ReflectionClass;
9
use ReflectionMethod;
10
use ReflectionNamedType;
11
use ReflectionParameter;
12
use function array_merge;
13
use function class_exists;
14
use function in_array;
15
16
/**
17
 * Extracts metadata from Input attribute parameters
18
 */
19
final class InputParamMeta implements InputParamMetaInterface
20
{
21
    private InputAttributeIterator $inputIterator;
22
    private OptionsMethodDocBolck $docBlock;
23
    private OptionsMethodRequest $methodRequest;
24
25
    public function __construct()
26
    {
27
        $this->inputIterator = new InputAttributeIterator();
28
        $this->docBlock = new OptionsMethodDocBolck();
29
        $this->methodRequest = new OptionsMethodRequest();
30
    }
31
32
    /**
33
     * {@inheritDoc}
34
     */
35
    public function get(ReflectionMethod $method): array
36
    {
37
        if (! $this->inputIterator->hasInputParameters($method)) {
38
            return [];
39
        }
40
41
        [, $methodParamDocs] = ($this->docBlock)($method);
42
43
        $allParameters = [];
44
        $allRequired = [];
45
46
        foreach (($this->inputIterator)($method) as $paramName => $param) {
47
            $type = $param->getType();
48
            if (! $type instanceof ReflectionNamedType) {
49
                continue;
50
            }
51
52
            $className = $type->getName();
53
            if (! class_exists($className)) {
54
                continue;
55
            }
56
57
            $groupDescription = $methodParamDocs[$paramName]['description'] ?? null;
58
            [$flatParams, $required] = $this->flattenInputClass(
59
                $className,
60
                $paramName,
61
                $groupDescription,
62
            );
63
64
            $allParameters = array_merge($allParameters, $flatParams);
65
            $allRequired = array_merge($allRequired, $required);
66
        }
67
68
        $result = [];
69
        if (! empty($allParameters)) {
70
            $result['parameters'] = $allParameters;
71
        }
72
73
        if (! empty($allRequired)) {
74
            $result['required'] = $allRequired;
75
        }
76
77
        return $result;
78
    }
79
80
    /**
81
     * Flatten Input class into query parameters
82
     *
83
     * @param class-string $className
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
84
     *
85
     * @return array{0: array<string, array<string, mixed>>, 1: array<int, string>}
86
     */
87
    private function flattenInputClass(
88
        string $className,
89
        string $group,
90
        string|null $groupDescription,
91
    ): array {
92
        $refClass = new ReflectionClass($className);
93
        $constructor = $refClass->getConstructor();
94
95
        if (! $constructor) {
96
            return [[], []];
97
        }
98
99
        // Use OptionsMethodRequest to handle parameter processing
100
        [, $constructorParamDocs] = ($this->docBlock)($constructor);
101
        $paramMeta = ($this->methodRequest)($constructor, $constructorParamDocs, []);
102
103
        $parameters = $paramMeta['parameters'] ?? [];
104
        $required = $paramMeta['required'] ?? [];
105
106
        // Separate regular parameters and nested Input parameters
107
        $finalParameters = [];
108
        $finalRequired = [];
109
110
        foreach ($constructor->getParameters() as $param) {
111
            $paramName = $param->getName();
112
113
            if ($this->isNestedInputParameter($param)) {
114
                $nestedDescription = $constructorParamDocs[$paramName]['description'] ?? null;
115
                [$nestedParams, $nestedRequired] = $this->processNestedParameter(
116
                    $param,
117
                    $nestedDescription,
118
                );
119
                $finalParameters = array_merge($finalParameters, $nestedParams);
120
                $finalRequired = array_merge($finalRequired, $nestedRequired);
121
            } elseif (isset($parameters[$paramName])) {
122
                // Add group information to regular parameters
123
                $parameters[$paramName]['group'] = $group;
124
                if ($groupDescription) {
125
                    $parameters[$paramName]['group_description'] = $groupDescription;
126
                }
127
128
                $finalParameters[$paramName] = $parameters[$paramName];
129
130
                if (in_array($paramName, $required, true)) {
131
                    $finalRequired[] = $paramName;
132
                }
133
            }
134
        }
135
136
        return [$finalParameters, $finalRequired];
137
    }
138
139
    private function isNestedInputParameter(ReflectionParameter $param): bool
140
    {
141
        return $this->inputIterator->getInputAttribute($param) !== null;
142
    }
143
144
    /** @return array{0: array<string, array<string, mixed>>, 1: array<int, string>} */
145
    private function processNestedParameter(
146
        ReflectionParameter $param,
147
        string|null $nestedDescription,
148
    ): array {
149
        $type = $param->getType();
150
        if (! ($type instanceof ReflectionNamedType) || ! class_exists($type->getName())) {
151
            return [[], []];
152
        }
153
154
        return $this->flattenInputClass(
155
            $type->getName(),
156
            $param->getName(),
157
            $nestedDescription,
158
        );
159
    }
160
}
161