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

InputParamMeta::flattenInputClass()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 47
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

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