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

InputParamMeta::flattenInputClass()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 49
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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