Passed
Pull Request — 1.x (#321)
by Akihito
03:22 queued 57s
created

InputParamMeta   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 157
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 77
c 1
b 0
f 0
dl 0
loc 157
rs 10
wmc 28

3 Methods

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