Passed
Pull Request — master (#192)
by Alexander
05:47 queued 02:55
created

Select::size()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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