Select::generateInput()   B
last analyzed

Complexity

Conditions 10
Paths 5

Size

Total Lines 38
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 10

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 24
c 1
b 0
f 0
nc 5
nop 0
dl 0
loc 38
ccs 27
cts 27
cp 1
crap 10
rs 7.6666

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