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

InputParamMeta::flattenInputClass()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 50
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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