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\InputAttributeIterator;
8
use ReflectionClass;
9
use ReflectionMethod;
10
use ReflectionNamedType;
11
12
use function array_merge;
13
use function class_exists;
14
use function is_array;
15
use function is_bool;
16
use function is_numeric;
17
use function is_string;
18
19
/**
20
 * Extracts metadata from Input attribute parameters
21
 */
22
final class InputParamMeta implements InputParamMetaInterface
23
{
24
    private InputAttributeIterator $inputIterator;
25
    private OptionsMethodDocBolck $docBlock;
26
27
    public function __construct()
28
    {
29
        $this->inputIterator = new InputAttributeIterator();
30
        $this->docBlock = new OptionsMethodDocBolck();
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
        // Get constructor documentation using OptionsMethodDocBolck
101
        // This already handles type conversion (int -> integer) and description extraction
102
        [, $constructorParamDocs] = ($this->docBlock)($constructor);
103
104
        $parameters = [];
105
        $required = [];
106
107
        foreach ($constructor->getParameters() as $param) {
108
            $paramName = $param->getName();
109
110
            // Check if this is a nested Input parameter
111
            if ($this->inputIterator->getInputAttribute($param) !== null) {
112
                $type = $param->getType();
113
                if ($type instanceof ReflectionNamedType && class_exists($type->getName())) {
114
                    $nestedDescription = null;
115
                    if (isset($constructorParamDocs[$paramName]['description'])) {
116
                        $nestedDescription = $constructorParamDocs[$paramName]['description'];
117
                    }
118
119
                    [$nestedParams, $nestedRequired] = $this->flattenInputClass(
120
                        $type->getName(),
121
                        $paramName,
122
                        $nestedDescription,
123
                    );
124
125
                    $parameters = array_merge($parameters, $nestedParams);
126
                    $required = array_merge($required, $nestedRequired);
127
                    continue;
128
                }
129
            }
130
131
            // Start with phpdoc information (already has type conversion)
132
            $paramMeta = [];
133
            if (isset($constructorParamDocs[$paramName])) {
134
                $paramMeta = $constructorParamDocs[$paramName];
135
            }
136
137
            // Override with reflection type if phpdoc doesn't have type
138
            if (! isset($paramMeta['type'])) {
139
                $type = $param->getType();
140
                if ($type instanceof ReflectionNamedType) {
141
                    $typeName = $type->getName();
142
                    // Apply same conversion as OptionsMethodDocBolck
143
                    $paramMeta['type'] = $typeName === 'int' ? 'integer' : $typeName;
144
                }
145
            }
146
147
            // Add default value
148
            if ($param->isDefaultValueAvailable()) {
149
                /** @var mixed $defaultValue */
150
                $defaultValue = $param->getDefaultValue();
151
                if (is_string($defaultValue)) {
152
                    $paramMeta['default'] = $defaultValue;
153
                } elseif (is_bool($defaultValue)) {
154
                    $paramMeta['default'] = $defaultValue ? 'true' : 'false';
155
                } elseif (is_numeric($defaultValue)) {
156
                    $paramMeta['default'] = (string) $defaultValue;
157
                } elseif (is_array($defaultValue)) {
158
                    $paramMeta['default'] = '[]';
159
                }
160
            }
161
162
            // Add group information
163
            $paramMeta['group'] = $group;
164
            if ($groupDescription) {
165
                $paramMeta['group_description'] = $groupDescription;
166
            }
167
168
            $parameters[$paramName] = $paramMeta;
169
170
            // Check if required
171
            if ($param->isDefaultValueAvailable() || $param->allowsNull()) {
172
                continue;
173
            }
174
175
            $required[] = $paramName;
176
        }
177
178
        return [$parameters, $required];
179
    }
180
}
181