Passed
Pull Request — master (#160)
by Wilmer
10:36
created

Field   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 539
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 150
c 1
b 0
f 0
dl 0
loc 539
ccs 131
cts 131
cp 1
rs 9.36
wmc 38

26 Methods

Rating   Name   Duplication   Size   Complexity  
A hidden() 0 7 1
A run() 0 33 6
A file() 0 4 1
A range() 0 4 1
A checkbox() 0 12 2
A label() 0 11 2
A build() 0 17 2
A url() 0 4 1
A email() 0 4 1
A error() 0 11 2
A dateTimeLocal() 0 4 1
A date() 0 4 1
A radiolist() 0 4 1
A checkboxList() 0 4 1
A telephone() 0 4 1
A image() 0 10 1
A radio() 0 12 2
A select() 0 4 1
A text() 0 4 1
A dateTime() 0 4 1
A password() 0 4 1
A textArea() 0 4 1
A resetButton() 0 10 1
A number() 0 4 1
A hint() 0 15 3
A submitButton() 0 12 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Form\Widget;
6
7
use Yiisoft\Definitions\Exception\InvalidConfigException;
8
use Yiisoft\Form\Widget\Attribute\FieldAttributes;
9
use Yiisoft\Html\Html;
10
use Yiisoft\Html\Tag\Div;
11
12
use function strtr;
13
14
/**
15
 * Renders the field widget along with label and hint tag (if any) according to template.
16
 *
17
 * @psalm-suppress MissingConstructor
18
 */
19
final class Field extends AbstractWidget
20
{
21
    use FieldAttributes;
22
23
    public const TYPE_CHECKBOX = Checkbox::class;
24
    public const TYPE_CHECKBOX_LIST = CheckboxList::class;
25
    public const TYPE_DATE = Date::class;
26
    public const TYPE_DATE_TIME = DateTime::class;
27
    public const TYPE_DATE_TIME_LOCAL = DateTimeLocal::class;
28
    public const TYPE_EMAIL = Email::class;
29
    public const TYPE_ERROR = Error::class;
30
    public const TYPE_FILE = File::class;
31
    public const TYPE_HIDDEN = Hidden::class;
32
    public const TYPE_HINT = Hint::class;
33
    public const TYPE_LABEL = Label::class;
34
    public const TYPE_NUMBER = Number::class;
35
    public const TYPE_PASSWORD = Password::class;
36
    public const TYPE_RADIO = Radio::class;
37
    public const TYPE_RADIO_LIST = RadioList::class;
38
    public const TYPE_RANGE = Range::class;
39
    public const TYPE_SELECT = Select::class;
40
    public const TYPE_SUBMIT_BUTTON = SubmitButton::class;
41
    public const TYPE_TELEPHONE = Telephone::class;
42
    public const TYPE_TEXT = Text::class;
43
    public const TYPE_TEXT_AREA = TextArea::class;
44
    public const TYPE_URL = Url::class;
45
    private const HAS_LENGTH_TYPES = [
46
        self::TYPE_EMAIL,
47
        self::TYPE_PASSWORD,
48
        self::TYPE_TELEPHONE,
49
        self::TYPE_TEXT,
50
        self::TYPE_TEXT_AREA,
51
        self::TYPE_URL,
52
    ];
53
    private const MATCH_REGULAR_EXPRESSION_TYPES = [
54
        self::TYPE_EMAIL,
55
        self::TYPE_PASSWORD,
56
        self::TYPE_TELEPHONE,
57
        self::TYPE_TEXT,
58
        self::TYPE_TEXT_AREA,
59
        self::TYPE_URL,
60
    ];
61
    private const NO_PLACEHOLDER_TYPES = [
62
        self::TYPE_CHECKBOX,
63
        self::TYPE_HIDDEN,
64
        self::TYPE_RADIO,
65
        self::TYPE_SELECT,
66
    ];
67
    private const NUMBER_TYPES = [
68
        self::TYPE_NUMBER,
69
        self::TYPE_RANGE,
70
    ];
71
72
    /**
73
     * Renders a checkbox.
74
     *
75
     * This method will generate the `checked` tag attribute according to the model attribute value.
76
     *
77
     * @param array $attributes the HTML attributes for the widget.
78
     * @param array $config the configuration array for widget factory.
79
     *
80
     * @return static the field widget instance.
81
     */
82 12
    public function checkbox(array $attributes = [], array $config = []): self
83
    {
84 12
        $new = clone $this;
85
86
        /** @var array */
87 12
        $enclosedByLabel = $config['enclosedByLabel()'] ?? [true];
88
89 12
        if ($enclosedByLabel === [true]) {
90 11
            $new->parts['{label}'] = '';
91
        }
92
93 12
        return $new->build(self::TYPE_CHECKBOX, $attributes, $config);
94
    }
95
96
    /**
97
     * Renders a list of checkboxes.
98
     *
99
     * A checkbox list allows multiple selection, As a result, the corresponding submitted value is an array.
100
     * The selection of the checkbox list is taken from the value of the model attribute.
101
     *
102
     * @param array $attributes the HTML attributes for the widget.
103
     * @param array $config the configuration array for widget factory.
104
     *
105
     * @return static the field widget instance.
106
     */
107 11
    public function checkboxList(array $attributes = [], array $config = []): self
108
    {
109 11
        $new = clone $this;
110 11
        return $new->build(self::TYPE_CHECKBOX_LIST, $attributes, $config);
111
    }
112
113
    /**
114
     * Renders a date widget.
115
     *
116
     * @param array $attributes the HTML attributes for the date widget.
117
     * @param array $config the configuration array for widget factory.
118
     *
119
     * @return static the field widget instance.
120
     */
121 6
    public function date(array $attributes = [], array $config = []): self
122
    {
123 6
        $new = clone $this;
124 6
        return $new->build(self::TYPE_DATE, $attributes, $config);
125
    }
126
127
    /**
128
     * Renders a date time widget.
129
     *
130
     * @param array $attributes the HTML attributes for the date widget.
131
     * @param array $config the configuration array for widget factory.
132
     *
133
     * @return static the field widget instance.
134
     */
135 6
    public function dateTime(array $attributes = [], array $config = []): self
136
    {
137 6
        $new = clone $this;
138 6
        return $new->build(self::TYPE_DATE_TIME, $attributes, $config);
139
    }
140
141
    /**
142
     * Renders a date time local widget.
143
     *
144
     * @param array $attributes the HTML attributes for the date widget.
145
     * @param array $config the configuration array for widget factory.
146
     *
147
     * @return static the field widget instance.
148
     */
149 6
    public function dateTimeLocal(array $attributes = [], array $config = []): self
150
    {
151 6
        $new = clone $this;
152 6
        return $new->build(self::TYPE_DATE_TIME_LOCAL, $attributes, $config);
153
    }
154
155
    /**
156
     * Renders a email widget.
157
     *
158
     * @param array $attributes the HTML attributes for the date widget.
159
     * @param array $config the configuration array for widget factory.
160
     *
161
     * @return static the field widget instance.
162
     */
163 11
    public function email(array $attributes = [], array $config = []): self
164
    {
165 11
        $new = clone $this;
166 11
        return $new->build(self::TYPE_EMAIL, $attributes, $config);
167
    }
168
169
    /**
170
     * Generates a tag that contains the first validation error of {@see attribute}.
171
     *
172
     * Note that even if there is no validation error, this method will still return an empty error tag.
173
     *
174
     * @param array $attributes the HTML attributes for the widget.
175
     * @param array $config the configuration array for widget factory.
176
     *
177
     * @return static the field widget instance.
178
     */
179 170
    public function error(array $attributes = [], array $config = []): self
180
    {
181 170
        $new = clone $this;
182
183 170
        $errorClass = $new->errorsClass[$new->type] ?? $new->errorClass;
184
185 170
        if ($errorClass !== '') {
186 11
            Html::addCssClass($attributes, $errorClass);
187
        }
188
189 170
        return $new->build(self::TYPE_ERROR, $attributes, $config, '{error}');
190
    }
191
192
    /**
193
     * Renders a file widget.
194
     *
195
     * @param array $attributes the HTML attributes for the date widget.
196
     * @param array $config the configuration array for widget factory.
197
     *
198
     * @return static the field widget instance.
199
     */
200 6
    public function file(array $attributes = [], array $config = []): self
201
    {
202 6
        $new = clone $this;
203 6
        return $new->build(self::TYPE_FILE, $attributes, $config);
204
    }
205
206
    /**
207
     * Renders a hidden widget.
208
     *
209
     * @param array $attributes the HTML attributes for the date widget.
210
     * @param array $config the configuration array for widget factory.
211
     *
212
     * @return static the field widget instance.
213
     */
214 2
    public function hidden(array $attributes = [], array $config = []): self
215
    {
216 2
        $new = clone $this;
217 2
        $new->parts['{label}'] = '';
218 2
        $new->parts['{hint}'] = '';
219 2
        $new->parts['{error}'] = '';
220 2
        return $new->build(self::TYPE_HIDDEN, $attributes, $config);
221
    }
222
223
    /**
224
     * Renders the hint tag.
225
     *
226
     * @param array $attributes the HTML attributes for the widget.
227
     * @param array $config the configuration array for widget factory.
228
     *
229
     * @return static the field widget instance.
230
     */
231 171
    public function hint(array $attributes = [], array $config = []): self
232
    {
233 171
        $new = clone $this;
234
235 171
        if ($new->ariaDescribedBy === true) {
236 1
            $attributes['id'] = $new->getId();
237
        }
238
239 171
        $hintClass = $new->hintsClass[$new->type] ?? $new->hintClass;
240
241 171
        if ($hintClass !== '') {
242 11
            Html::addCssClass($attributes, $hintClass);
243
        }
244
245 171
        return $new->build(self::TYPE_HINT, $attributes, $config, '{hint}');
246
    }
247
248
    /**
249
     * Renders a image widget.
250
     *
251
     * @param array $attributes the HTML attributes for the widget.
252
     * @param array $config the configuration array for widget factory.
253
     *
254
     * @throws InvalidConfigException
255
     *
256
     * @return static the field object itself.
257
     */
258 9
    public function image(array $attributes = [], array $config = []): self
259
    {
260 9
        $new = clone $this;
261
262 9
        $new->parts['{error}'] = '';
263 9
        $new->parts['{hint}'] = '';
264 9
        $new->parts['{label}'] = '';
265 9
        $new->parts['{input}'] = Image::widget($config)->attributes($attributes)->render();
266
267 9
        return $new;
268
    }
269
270
    /**
271
     * Generates a label tag.
272
     *
273
     * @param array $attributes the HTML attributes for the widget.
274
     * @param array $config the configuration array for widget factory.
275
     *
276
     * @return static the field widget instance.
277
     */
278 158
    public function label(array $attributes = [], array $config = []): self
279
    {
280 158
        $new = clone $this;
281
282 158
        $labelClass = $new->labelsClass[$new->type] ?? $new->labelClass;
283
284 158
        if ($labelClass !== '') {
285 3
            Html::addCssClass($attributes, $labelClass);
286
        }
287
288 158
        return $new->build(self::TYPE_LABEL, $attributes, $config, '{label}');
289
    }
290
291
    /**
292
     * Renders a number widget.
293
     *
294
     * @param array $attributes the HTML attributes for the widget.
295
     * @param array $config the configuration array for widget factory.
296
     *
297
     * @return static the field object itself.
298
     */
299 8
    public function number(array $attributes = [], array $config = []): self
300
    {
301 8
        $new = clone $this;
302 8
        return $new->build(self::TYPE_NUMBER, $attributes, $config);
303
    }
304
305
    /**
306
     * Renders a password widget.
307
     *
308
     * @param array $attributes the HTML attributes for the widget.
309
     * @param array $config the configuration array for widget factory.
310
     *
311
     * @return static the field object itself.
312
     */
313 11
    public function password(array $attributes = [], array $config = []): self
314
    {
315 11
        $new = clone $this;
316 11
        return $new->build(self::TYPE_PASSWORD, $attributes, $config);
317
    }
318
319
    /**
320
     * Renders a radio widget.
321
     *
322
     * @param array $attributes the HTML attributes for the widget.
323
     * @param array $config the configuration array for widget factory.
324
     *
325
     * @return static the field object itself.
326
     */
327 10
    public function radio(array $attributes = [], array $config = []): self
328
    {
329 10
        $new = clone $this;
330
331
        /** @var array */
332 10
        $enclosedByLabel = $config['enclosedByLabel()'] ?? [true];
333
334 10
        if ($enclosedByLabel === [true]) {
335 9
            $new->parts['{label}'] = '';
336
        }
337
338 10
        return $new->build(self::TYPE_RADIO, $attributes, $config);
339
    }
340
341
    /**
342
     * Renders a radio list widget.
343
     *
344
     * @param array $attributes the HTML attributes for the widget.
345
     * @param array $config the configuration array for widget factory.
346
     *
347
     * @return static the field object itself.
348
     */
349 15
    public function radiolist(array $attributes = [], array $config = []): self
350
    {
351 15
        $new = clone $this;
352 15
        return $new->build(self::TYPE_RADIO_LIST, $attributes, $config);
353
    }
354
355
    /**
356
     * Renders a range widget.
357
     *
358
     * @param array $attributes the HTML attributes for the widget.
359
     * @param array $config the configuration array for widget factory.
360
     *
361
     * @return static the field object itself.
362
     */
363 10
    public function range(array $attributes = [], array $config = []): self
364
    {
365 10
        $new = clone $this;
366 10
        return $new->build(self::TYPE_RANGE, $attributes, $config);
367
    }
368
369
    /**
370
     * Renders a reset button widget.
371
     *
372
     * @param array $attributes the HTML attributes for the widget.
373
     * @param array $config the configuration array for widget factory.
374
     *
375
     * @throws InvalidConfigException
376
     *
377
     * @return static the field object itself.
378
     */
379 6
    public function resetButton(array $attributes = [], array $config = []): self
380
    {
381 6
        $new = clone $this;
382
383 6
        $new->parts['{error}'] = '';
384 6
        $new->parts['{hint}'] = '';
385 6
        $new->parts['{label}'] = '';
386 6
        $new->parts['{input}'] = ResetButton::widget($config)->attributes($attributes)->render();
387
388 6
        return $new;
389
    }
390
391
    /**
392
     * Renders a select widget.
393
     *
394
     * @param array $attributes the HTML attributes for the widget.
395
     * @param array $config the configuration array for widget factory.
396
     *
397
     * @return static the field object itself.
398
     */
399 10
    public function select(array $attributes = [], array $config = []): self
400
    {
401 10
        $new = clone $this;
402 10
        return $new->build(self::TYPE_SELECT, $attributes, $config);
403
    }
404
405
    /**
406
     * Renders a submit button widget.
407
     *
408
     * @param array $attributes the HTML attributes for the widget.
409
     * @param array $config the configuration array for widget factory.
410
     *
411
     * @throws InvalidConfigException
412
     *
413
     * @return static the field object itself.
414
     */
415 6
    public function submitButton(array $attributes = [], array $config = []): self
416
    {
417 6
        $new = clone $this;
418
419 6
        $new->type = self::TYPE_SUBMIT_BUTTON;
420
421 6
        $new->parts['{error}'] = '';
422 6
        $new->parts['{hint}'] = '';
423 6
        $new->parts['{label}'] = '';
424 6
        $new->parts['{input}'] = SubmitButton::widget($config)->attributes($attributes)->render();
425
426 6
        return $new;
427
    }
428
429
    /**
430
     * Renders a text widget.
431
     *
432
     * @param array $attributes the HTML attributes for the widget.
433
     * @param array $config the configuration array for widget factory.
434
     *
435
     * @return static the field widget instance.
436
     */
437 8
    public function telephone(array $attributes = [], array $config = []): self
438
    {
439 8
        $new = clone $this;
440 8
        return $new->build(self::TYPE_TELEPHONE, $attributes, $config);
441
    }
442
443
    /**
444
     * Renders a text widget.
445
     *
446
     * @param array $attributes the HTML attributes for the widget.
447
     * @param array $config the configuration array for widget factory.
448
     *
449
     * @return static the field widget instance.
450
     */
451 39
    public function text(array $attributes = [], array $config = []): self
452
    {
453 39
        $new = clone $this;
454 39
        return $new->build(self::TYPE_TEXT, $attributes, $config);
455
    }
456
457
    /**
458
     * Renders a text area widget.
459
     *
460
     * @param array $attributes the HTML attributes for the widget.
461
     * @param array $config the configuration array for widget factory.
462
     *
463
     * @return static the field widget instance.
464
     */
465 10
    public function textArea(array $attributes = [], array $config = []): self
466
    {
467 10
        $new = clone $this;
468 10
        return $new->build(self::TYPE_TEXT_AREA, $attributes, $config);
469
    }
470
471
    /**
472
     * Renders a url widget.
473
     *
474
     * @param array $attributes the HTML attributes for the widget.
475
     * @param array $config the configuration array for widget factory.
476
     *
477
     * @return static the field widget instance.
478
     */
479 9
    public function url(array $attributes = [], array $config = []): self
480
    {
481 9
        $new = clone $this;
482 9
        return $new->build(self::TYPE_URL, $attributes, $config);
483
    }
484
485
    /**
486
     * Renders the whole field.
487
     *
488
     * This method will generate the label, input tag and hint tag (if any), and assemble them into HTML according to
489
     * {@see template}.
490
     *
491
     * If (not set), the default methods will be called to generate the label and input tag, and use them as the
492
     * content.
493
     *
494
     * @return string the rendering result.
495
     */
496 194
    protected function run(): string
497
    {
498 194
        $new = clone $this;
499
500 194
        $div = Div::tag();
501
502 194
        if (!isset($new->parts['{input}'])) {
503 30
            $new->type = self::TYPE_TEXT;
504 30
            $new = $new->text();
505
        }
506
507 193
        if (!isset($new->parts['{label}'])) {
508 146
            $new = $new->label();
509
        }
510
511 193
        if (!isset($new->parts['{hint}'])) {
512 165
            $new = $new->hint();
513
        }
514
515 193
        if (!isset($new->parts['{error}'])) {
516 165
            $new = $new->error();
517
        }
518
519 193
        $containerClass = $new->containersClass[$new->type] ?? $new->containerClass;
520
521 193
        if ($containerClass !== '') {
522 4
            $div = $div->class($containerClass);
523
        }
524
525 193
        $template = $new->templates[$new->type] ?? $new->template;
526 193
        $content = preg_replace('/^\h*\v+/m', '', trim(strtr($template, $new->parts)));
527
528 193
        return $div->content(PHP_EOL . $content . PHP_EOL)->encode(false)->render();
529
    }
530
531
    /**
532
     * Build input tag field.
533
     *
534
     * @param string $type the type of tag.
535
     * @param array $attributes the HTML attributes for the tag.
536
     * @param array $config the configuration array for widget factory.
537
     * @param string $parts the parts of tag.
538
     *
539
     * @return static the field widget instance.
540
     */
541 190
    private function build(
542
        string $type,
543
        array $attributes = [],
544
        array $config = [],
545
        string $parts = '{input}'
546
    ): self {
547 190
        $new = clone $this;
548
549 190
        if ($parts === '{input}') {
550 189
            $new->type = $type;
551 189
            $attributes = $new->setInputAttributes($type, $attributes);
552
        }
553
554
        /** @var AbstractWidget */
555 190
        $type = $type::widget($config);
556 190
        $new->parts[$parts] = $type->for($new->getFormModel(), $new->getAttribute())->attributes($attributes)->render();
557 172
        return $new;
558
    }
559
}
560