Passed
Pull Request — master (#192)
by Alexander
03:40
created

Select::generateInput()   B

Complexity

Conditions 10
Paths 5

Size

Total Lines 33
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 10.1167

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 21
nc 5
nop 0
dl 0
loc 33
ccs 17
cts 19
cp 0.8947
crap 10.1167
rs 7.6666
c 1
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Form\Field;
6
7
use InvalidArgumentException;
8
use Stringable;
9
use Yiisoft\Form\Field\Base\InputField;
10
use Yiisoft\Form\Field\Base\ValidationClass\ValidationClassInterface;
11
use Yiisoft\Form\Field\Base\ValidationClass\ValidationClassTrait;
12
use Yiisoft\Html\Tag\Optgroup;
13
use Yiisoft\Html\Tag\Option;
14
use Yiisoft\Html\Tag\Select as SelectTag;
15
16
/**
17
 * A control for selecting amongst a set of options.
18
 *
19
 * @link https://html.spec.whatwg.org/multipage/form-elements.html#the-select-element
20
 */
21
final class Select extends InputField implements ValidationClassInterface
22
{
23
    use ValidationClassTrait;
24
25
    private SelectTag $select;
26
27 5
    public function __construct()
28
    {
29 5
        $this->select = SelectTag::tag();
30
    }
31
32 1
    public function items(Optgroup|Option ...$items): self
33
    {
34 1
        $new = clone $this;
35 1
        $new->select = $this->select->items(...$items);
36 1
        return $new;
37
    }
38
39 1
    public function options(Option ...$options): self
40
    {
41 1
        return $this->items(...$options);
42
    }
43
44
    /**
45
     * @param array $data Options data. The array keys are option values, and the array values are the corresponding
46
     * option labels. The array can also be nested (i.e. some array values are arrays too). For each sub-array,
47
     * an option group will be generated whose label is the key associated with the sub-array.
48
     *
49
     * Example:
50
     * ```php
51
     * [
52
     *     '1' => 'Santiago',
53
     *     '2' => 'Concepcion',
54
     *     '3' => 'Chillan',
55
     *     '4' => 'Moscow'
56
     *     '5' => 'San Petersburg',
57
     *     '6' => 'Novosibirsk',
58
     *     '7' => 'Ekaterinburg'
59
     * ];
60
     * ```
61
     *
62
     * Example with options groups:
63
     * ```php
64
     * [
65
     *     '1' => [
66
     *         '1' => 'Santiago',
67
     *         '2' => 'Concepcion',
68
     *         '3' => 'Chillan',
69
     *     ],
70
     *     '2' => [
71
     *         '4' => 'Moscow',
72
     *         '5' => 'San Petersburg',
73
     *         '6' => 'Novosibirsk',
74
     *         '7' => 'Ekaterinburg'
75
     *     ],
76
     * ];
77
     * ```
78
     * @param bool $encode Whether option content should be HTML-encoded.
79
     * @param array[] $optionsAttributes Array of option attribute sets indexed by option values from {@see $data}.
80
     * @param array[] $groupsAttributes Array of group attribute sets indexed by group labels from {@see $data}.
81
     *
82
     * @psalm-param array<array-key, string|array<array-key,string>> $data
83
     *
84
     * @return self
85
     */
86 5
    public function optionsData(
87
        array $data,
88
        bool $encode = true,
89
        array $optionsAttributes = [],
90
        array $groupsAttributes = []
91
    ): self {
92 5
        $new = clone $this;
93 5
        $new->select = $this->select->optionsData($data, $encode, $optionsAttributes, $groupsAttributes);
94 5
        return $new;
95
    }
96
97
    /**
98
     * @param bool $disabled Whether select input is disabled.
99
     *
100
     * @link https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-disabled
101
     */
102 1
    public function disabled(bool $disabled = true): self
103
    {
104 1
        $new = clone $this;
105 1
        $new->inputTagAttributes['disabled'] = $disabled;
106 1
        return $new;
107
    }
108
109
    /**
110
     * Identifies the element (or elements) that describes the object.
111
     *
112
     * @link https://w3c.github.io/aria/#aria-describedby
113
     */
114
    public function ariaDescribedBy(string $value): self
115
    {
116
        $new = clone $this;
117
        $new->inputTagAttributes['aria-describedby'] = $value;
118
        return $new;
119
    }
120
121
    /**
122
     * Defines a string value that labels the current element.
123
     *
124
     * @link https://w3c.github.io/aria/#aria-label
125
     */
126
    public function ariaLabel(string $value): self
127
    {
128
        $new = clone $this;
129
        $new->inputTagAttributes['aria-label'] = $value;
130
        return $new;
131
    }
132
133
    /**
134
     * Focus on the control (put cursor into it) when the page loads. Only one form element could be in focus
135
     * at the same time.
136
     *
137
     * @link https://html.spec.whatwg.org/multipage/interaction.html#attr-fe-autofocus
138
     */
139
    public function autofocus(bool $value = true): self
140
    {
141
        $new = clone $this;
142
        $new->inputTagAttributes['autofocus'] = $value;
143
        return $new;
144
    }
145
146
    /**
147
     * The `tabindex` attribute indicates that its element can be focused, and where it participates in sequential
148
     * keyboard navigation (usually with the Tab key, hence the name).
149
     *
150
     * It accepts an integer as a value, with different results depending on the integer's value:
151
     *
152
     * - A negative value (usually `tabindex="-1"`) means that the element is not reachable via sequential keyboard
153
     *   navigation, but could be focused with Javascript or visually. It's mostly useful to create accessible widgets
154
     *   with JavaScript.
155
     * - `tabindex="0"` means that the element should be focusable in sequential keyboard navigation, but its order is
156
     *   defined by the document's source order.
157
     * - A positive value means the element should be focusable in sequential keyboard navigation, with its order
158
     *   defined by the value of the number. That is, `tabindex="4"` is focused before `tabindex="5"`, but after
159
     *   `tabindex="3"`.
160
     *
161
     * @link https://html.spec.whatwg.org/multipage/interaction.html#attr-tabindex
162
     */
163
    public function tabIndex(?int $value): self
164
    {
165
        $new = clone $this;
166
        $new->inputTagAttributes['tabindex'] = $value;
167
        return $new;
168
    }
169
170
    /**
171
     * @param bool $value Whether the user is to be allowed to select zero or more options.
172
     *
173
     * @link https://html.spec.whatwg.org/multipage/form-elements.html#attr-select-multiple
174
     */
175 2
    public function multiple(bool $value = true): self
176
    {
177 2
        $new = clone $this;
178 2
        $new->inputTagAttributes['multiple'] = $value;
179 2
        return $new;
180
    }
181
182
    /**
183
     * @param string|null $text Text of the option that has dummy value and is rendered as an invitation to select
184
     * a value.
185
     */
186 1
    public function prompt(?string $text): self
187
    {
188 1
        $new = clone $this;
189 1
        $new->select = $this->select->prompt($text);
190 1
        return $new;
191
    }
192
193
    /**
194
     * @param Option|null $option Option that has dummy value and is rendered as an invitation to select a value.
195
     */
196 1
    public function promptOption(?Option $option): self
197
    {
198 1
        $new = clone $this;
199 1
        $new->select = $this->select->promptOption($option);
200 1
        return $new;
201
    }
202
203
    /**
204
     * A boolean attribute. When specified, the element is required.
205
     *
206
     * @param bool $value Whether the control is required for form submission.
207
     *
208
     * @link https://html.spec.whatwg.org/multipage/form-elements.html#attr-select-required
209
     */
210 1
    public function required(bool $value = true): self
211
    {
212 1
        $new = clone $this;
213 1
        $new->inputTagAttributes['required'] = $value;
214 1
        return $new;
215
    }
216
217
    /**
218
     * The size of the control.
219
     *
220
     * @param int $value The number of options to show to the user.
221
     *
222
     * @link https://html.spec.whatwg.org/multipage/form-elements.html#attr-select-size
223
     */
224 1
    public function size(int $value): self
225
    {
226 1
        $new = clone $this;
227 1
        $new->inputTagAttributes['size'] = $value;
228 1
        return $new;
229
    }
230
231 1
    public function unselectValue(bool|float|int|string|Stringable|null $value): self
232
    {
233 1
        $new = clone $this;
234 1
        $new->select = $this->select->unselectValue($value);
235 1
        return $new;
236
    }
237
238 4
    protected function generateInput(): string
239
    {
240 4
        $value = $this->getAttributeValue();
241 4
        $multiple = (bool) ($this->inputTagAttributes['multiple'] ?? false);
242
243 4
        if ($multiple) {
244
            /** @var mixed $value */
245 1
            $value ??= [];
246 1
            if (!is_iterable($value)) {
247
                throw new InvalidArgumentException(
248
                    'Select field with multiple option requires iterable or null value.'
249
                );
250
            }
251
        } else {
252 3
            if (!is_bool($value)
253 3
                && !is_string($value)
254 3
                && !is_numeric($value)
255 3
                && $value !== null
256 3
                && (!is_object($value) || !method_exists($value, '__toString'))
257
            ) {
258
                throw new InvalidArgumentException(
259
                    'Non-multiple Select field requires a string, numeric, bool, Stringable or null value.'
260
                );
261
            }
262 3
            $value = $value === null ? [] : [$value];
263
        }
264
        /** @psalm-var iterable<int, Stringable|scalar> $value */
265
266 4
        return $this->select
267 4
            ->attributes($this->inputTagAttributes)
268 4
            ->name($this->getInputName())
269 4
            ->values($value)
270 4
            ->render();
271
    }
272
273 4
    protected function prepareContainerTagAttributes(array &$attributes): void
274
    {
275 4
        if ($this->hasFormModelAndAttribute()) {
276 4
            $this->addValidationClassToTagAttributes(
277
                $attributes,
278 4
                $this->getFormModel(),
279 4
                $this->getAttributeName(),
280
            );
281
        }
282
    }
283
}
284