Completed
Push — master ( a45428...13e731 )
by Rasmus
11s queued 10s
created

InputRenderer::errorSummary()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 2.0011

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 14
cts 15
cp 0.9333
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 14
nc 2
nop 0
crap 2.0011
1
<?php
2
3
namespace mindplay\kissform;
4
5
use mindplay\kissform\Facets\FieldInterface;
6
use RuntimeException;
7
8
/**
9
 * This class renders and populates input elements for use in forms,
10
 * by consuming property-information provided by {@see Field} objects,
11
 * and populating them with state from an {@see InputModel}.
12
 *
13
 * Conventions for method-names in this class:
14
 *
15
 *   * `get_()` and `is_()` methods provide raw information about Fields
16
 * 
17
 *   * `render_()` methods delegate rendering to {@see Field::renderInput} implementations.
18
 * 
19
 *   * `_For()` methods (such as `inputFor()`) render low-level HTML tags (with state) for Fields
20
 * 
21
 *   * Verb methods like `visit`, `merge` and `escape` perform various relevant actions
22
 * 
23
 *   * Noun methods like `tag`, `attrs` and `label` create low-level HTML tags
24
 *
25
 */
26
class InputRenderer
27
{
28
    /**
29
     * @var string HTML encoding charset
30
     */
31
    public $encoding = 'UTF-8';
32
33
    /**
34
     * @var bool if true, use long form XHTML for value-less attributes (e.g. disabled="disabled")
35
     *
36
     * @see attrs()
37
     */
38
    public $xhtml = false;
39
40
    /**
41
     * @var InputModel input model
42
     */
43
    public $model;
44
45
    /**
46
     * @var string|string[]|null form element collection name(s)
47
     */
48
    public $collection_name;
49
50
    /**
51
     * @var string|null form element id-attribute prefix (or null, to bypass id-attribute generation)
52
     */
53
    public $id_prefix;
54
55
    /**
56
     * @var string CSS class name applied to all form controls
57
     *
58
     * @see inputFor()
59
     */
60
    public $input_class = 'form-control';
61
62
    /**
63
     * @var string CSS class name added to labels
64
     *
65
     * @see labelFor()
66
     */
67
    public $label_class = 'control-label';
68
69
    /**
70
     * @var string suffix to append to all labels (e.g. ":")
71
     *
72
     * @see labelFor()
73
     */
74
    public $label_suffix = '';
75
76
    /**
77
     * @var string CSS class-name added to required fields
78
     *
79
     * @see groupFor()
80
     */
81
    public $required_class = 'required';
82
83
    /**
84
     * @var string CSS class-name added to fields with error state
85
     *
86
     * @see groupFor()
87
     */
88
    public $error_class = 'has-error';
89
90
    /**
91
     * @var string group tag name (e.g. "div", "fieldset", etc.; defaults to "div")
92
     *
93
     * @see groupFor()
94
     * @see endGroup()
95
     */
96
    public $group_tag = 'div';
97
98
    /**
99
     * @var array default attributes to be added to opening control-group tags
100
     *
101
     * @see groupFor()
102
     */
103
    public $group_attrs = ['class' => 'form-group'];
104
105
    /**
106
     * @var string error summary CSS class-name
107
     *
108
     * @see errorSummary()
109
     */
110
    public $error_summary_class = "error-summary";
111
112
    /**
113
     * @var string[] map where Field name => label override
114
     */
115
    protected $labels = [];
116
117
    /**
118
     * @var string[] map where Field name => placeholder override
119
     */
120
    protected $placeholders = [];
121
122
    /**
123
     * @var bool[] map where Field name => required flag
124
     */
125
    protected $required = [];
126
127
    /**
128
     * @var string[] list of void elements
129
     *
130
     * @see tag()
131
     *
132
     * @link http://www.w3.org/TR/html-markup/syntax.html#void-elements
133
     */
134
    private static $void_elements = [
135
        'area'    => true,
136
        'base'    => true,
137
        'br'      => true,
138
        'col'     => true,
139
        'command' => true,
140
        'embed'   => true,
141
        'hr'      => true,
142
        'img'     => true,
143
        'input'   => true,
144
        'keygen'  => true,
145
        'link'    => true,
146
        'meta'    => true,
147
        'param'   => true,
148
        'source'  => true,
149
        'track'   => true,
150
        'wbr'     => true,
151
    ];
152
153
    /**
154
     * @param InputModel|array|null $model           input model, or (possibly nested) input array (e.g. $_GET or $_POST)
155
     * @param string|string[]|null  $collection_name collection name(s) for inputs, e.g. 'myform' or ['myform', '123'] etc.
156
     * @param string|null           $id_prefix       base id for inputs, e.g. 'myform' or 'myform-123', etc.
157
     */
158 27
    public function __construct($model = null, $collection_name = null, $id_prefix = "form")
159
    {
160 27
        $this->model = InputModel::create($model);
161 27
        $this->collection_name = $collection_name;
162 27
        $this->id_prefix = $id_prefix === null
163 1
            ? ($collection_name === null
164 1
                ? null
165 1
                : implode('-', (array) $this->collection_name))
166 27
            : $id_prefix;
167 27
    }
168
169
    /**
170
     * @param FieldInterface $field
171
     *
172
     * @return string
173
     *
174
     * @see Field::getLabel()
175
     */
176 3
    public function getLabel(FieldInterface $field)
177
    {
178 3
        return array_key_exists($field->getName(), $this->labels)
179 1
            ? $this->labels[$field->getName()]
180 3
            : $field->getLabel();
181
    }
182
183
    /**
184
     * Override the label defined by the Field
185
     *
186
     * @param FieldInterface $field
187
     * @param string         $label
188
     */
189 1
    public function setLabel(FieldInterface $field, $label)
190
    {
191 1
        $this->labels[$field->getName()] = $label;
192 1
    }
193
194
    /**
195
     * @param FieldInterface $field
196
     *
197
     * @return string
198
     *
199
     * @see Field::getPlaceholder()
200
     */
201 13
    public function getPlaceholder(FieldInterface $field)
202
    {
203 13
        return array_key_exists($field->getName(), $this->placeholders)
204 1
            ? $this->placeholders[$field->getName()]
205 13
            : $field->getPlaceholder();
206
    }
207
208
    /**
209
     * Override the placeholder label defined by the Field
210
     *
211
     * @param FieldInterface $field
212
     * @param string         $placeholder
213
     */
214 1
    public function setPlaceholder(FieldInterface $field, $placeholder)
215
    {
216 1
        $this->placeholders[$field->getName()] = $placeholder;
217 1
    }
218
219
    /**
220
     * @param FieldInterface $field
221
     *
222
     * @return string|null computed name-attribute
223
     */
224 19
    public function getName(FieldInterface $field)
225
    {
226 19
        $names = (array) $this->collection_name;
227 19
        $names[] = $field->getName();
228
229 19
        return $names[0] . (count($names) > 1 ? '[' . implode('][', array_slice($names, 1)) . ']' : '');
230
    }
231
232
    /**
233
     * @param FieldInterface $field
234
     * @param string|null    $suffix
235
     *
236
     * @return string|null computed id-attribute
237
     */
238 18
    public function getId(FieldInterface $field, $suffix = null)
239
    {
240 18
        return $this->id_prefix
241 18
            ? $this->id_prefix . '-' . $field->getName() . ($suffix ? "-{$suffix}" : '')
242 18
            : null;
243
    }
244
245
    /**
246
     * Conditionally create (or add) CSS class-names for Field status, e.g.
247
     * {@see $required_class} for {@see Field::$required} and {@see $error_class}
248
     * if the {@see $model} contains an error.
249
     *
250
     * @param FieldInterface $field
251
     *
252
     * @return array map of HTML attributes (with additonial classes)
253
     *
254
     * @see $required_class
255
     * @see $error_class
256
     */
257 4
    public function getAttrs(FieldInterface $field)
258
    {
259 4
        $classes = [];
260
261 4
        if ($this->required_class !== null && $this->isRequired($field)) {
262 3
            $classes[] = $this->required_class;
263
        }
264
265 4
        if ($this->error_class !== null && $this->model->hasError($field)) {
266 2
            $classes[] = $this->error_class;
267
        }
268
269 4
        return ['class' => $classes];
270
    }
271
272
    /**
273
     * @param FieldInterface $field
274
     *
275
     * @return bool true, if the given Field is required
276
     */
277 13
    public function isRequired(FieldInterface $field)
278
    {
279 13
        return array_key_exists($field->getName(), $this->required)
280 1
            ? $this->required[$field->getName()]
281 13
            : $field->isRequired();
282
    }
283
284
    /**
285
     * Override the required flag defined by the Field
286
     *
287
     * @param FieldInterface $field
288
     * @param bool           $required
289
     */
290 1
    public function setRequired(FieldInterface $field, $required = true)
291
    {
292 1
        $this->required[$field->getName()] = (bool) $required;
293 1
    }
294
295
    /**
296
     * Build an HTML input for a given Field.
297
     *
298
     * @param FieldInterface $field
299
     * @param array          $attr
300
     *
301
     * @return string
302
     *
303
     * @throws RuntimeException if the given Field cannot be rendered
304
     */
305 19
    public function render(FieldInterface $field, array $attr = [])
306
    {
307 19
        return $field->renderInput($this, $this->model, $attr);
308
    }
309
310
    /**
311
     * Builds an HTML group containing a label and rendered input for a given Field.
312
     *
313
     * @param FieldInterface $field
314
     * @param string|null    $label      label text (optional)
315
     * @param array          $input_attr map of HTML attributes for the input (optional)
316
     * @param array          $group_attr map of HTML attributes for the group (optional)
317
     *
318
     * @return string
319
     */
320 1
    public function renderGroup(FieldInterface $field, $label = null, array $input_attr = [], $group_attr = [])
321
    {
322
        return
323 1
            $this->groupFor($field, $group_attr)
324 1
            . $this->labelFor($field, $label)
325 1
            . $this->render($field, $input_attr)
326 1
            . $this->endGroup();
327
    }
328
329
    /**
330
     * Builds an HTML div with state-classes, containing a rendered input for a given Field.
331
     *
332
     * @param FieldInterface $field
333
     * @param array          $input_attr attributes for the generated input
334
     * @param array          $div_attr   attributes for the wrapper div
335
     *
336
     * @return string HTML
337
     */
338 2
    public function renderDiv(FieldInterface $field, array $input_attr = [], $div_attr = [])
339
    {
340 2
        return $this->divFor($field, $this->render($field, $input_attr), $div_attr);
341
    }
342
343
    /**
344
     * Visit a given Field - temporarily swaps out {@see $model}, {@see $name_prefix}
345
     * and {@see $id_prefix} and merges any changes made to the model while calling
346
     * the given function.
347
     *
348
     * @param FieldInterface|int|string $field Field instance, or an integer index, or string key
349
     * @param callable                  $func  function (InputModel $model): mixed
350
     *
351
     * @return mixed
352
     */
353 2
    public function visit($field, $func)
354
    {
355 2
        $model = $this->model;
356 2
        $name_prefix = $this->collection_name;
357 2
        $id_prefix = $this->id_prefix;
358
359 2
        $key = $field instanceof FieldInterface
360 2
            ? $field->getName()
361 2
            : (string) $field;
362
363 2
        $this->model = InputModel::create(@$model->input[$key], $model->getError($key));
0 ignored issues
show
Bug introduced by
It seems like $model->getError($key) targeting mindplay\kissform\InputModel::getError() can also be of type string; however, mindplay\kissform\InputModel::create() does only seem to accept array<integer,string>|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
364 2
        $this->collection_name = array_merge((array) $this->collection_name, [$key]);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge((array) $thi...tion_name, array($key)) of type array is incompatible with the declared type string|array<integer,string>|null of property $collection_name.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
365 2
        $this->id_prefix = $this->id_prefix
366 2
            ? $this->id_prefix . '-' . $key
367
            : null;
368
369 2
        call_user_func($func, $this->model);
370
371 2
        if ($this->model->input !== []) {
372 2
            $model->input[$key] = $this->model->input;
373
        } else {
374 2
            unset($model->input[$key]);
375
        }
376
377 2
        if ($this->model->hasErrors()) {
378 1
            $model->setError($key, $this->model->getErrors());
379
        }
380
381 2
        $this->model = $model;
382 2
        $this->collection_name = $name_prefix;
383 2
        $this->id_prefix = $id_prefix;
384 2
    }
385
386
    /**
387
     * Merge any number of attribute maps, with the latter overwriting the first, and
388
     * with special handling for the class-attribute.
389
     *
390
     * @param array ...$attr
391
     *
392
     * @return array
393
     */
394 20
    public function mergeAttrs()
395
    {
396 20
        $maps = func_get_args();
397
398 20
        $result = [];
399
400 20
        foreach ($maps as $map) {
401 20
            if (isset($map['class'])) {
402 18
                if (isset($result['class'])) {
403 7
                    $map['class'] = array_merge((array) $result['class'], (array) $map['class']);
404
                }
405
            }
406
407 20
            $result = array_merge($result, $map);
408
        }
409
410 20
        return $result;
411
    }
412
413
    /**
414
     * Encode plain text as HTML
415
     *
416
     * @param string $text  plain text
417
     * @param int    $flags encoding flags (optional, see htmlspecialchars)
418
     *
419
     * @return string escaped HTML
420
     *
421
     * @see softEscape()
422
     */
423 25
    public function escape($text, $flags = ENT_COMPAT)
424
    {
425 25
        return htmlspecialchars($text, $flags, $this->encoding, true);
426
    }
427
428
    /**
429
     * Encode plain text as HTML, while attempting to avoid double-encoding
430
     *
431
     * @param string $text  plain text
432
     * @param int    $flags encoding flags (optional, see htmlspecialchars)
433
     *
434
     * @return string escaped HTML
435
     *
436
     * @see escape()
437
     */
438 5
    public function softEscape($text, $flags = ENT_COMPAT)
439
    {
440 5
        return htmlspecialchars($text, $flags, $this->encoding, false);
441
    }
442
443
    /**
444
     * Build an opening and closing HTML tag (or a self-closing tag) - examples:
445
     *
446
     *     echo $renderer->tag('input', array('type' => 'text'));  => <input type="text"/>
447
     *
448
     *     echo $renderer->tag('div', array(), 'Foo &amp; Bar');   => <div>Foo &amp; Bar</div>
449
     *
450
     *     echo $renderer->tag('script', array(), '');             => <script></script>
451
     *
452
     * @param string      $name HTML tag name
453
     * @param array       $attr map of HTML attributes
454
     * @param string|null $html inner HTML, or NULL to build a self-closing tag
455
     *
456
     * @return string
457
     *
458
     * @see openTag()
459
     */
460 24
    public function tag($name, array $attr = [], $html = null)
461
    {
462 24
        return $html === null && isset(self::$void_elements[$name])
463 18
            ? '<' . $name . $this->attrs($attr) . '/>'
464 24
            : '<' . $name . $this->attrs($attr) . '>' . $html . '</' . $name . '>';
465
    }
466
467
    /**
468
     * Build an open HTML tag; remember to close the tag.
469
     *
470
     * Note that there is no closeTag() equivalent, as this wouldn't help with anything
471
     * and would actually require more code than e.g. a simple literal `</div>`
472
     *
473
     * @param string $name HTML tag name
474
     * @param array  $attr map of HTML attributes
475
     *
476
     * @return string
477
     *
478
     * @see tag()
479
     */
480 3
    public function openTag($name, array $attr = [])
481
    {
482 3
        return '<' . $name . $this->attrs($attr) . '>';
483
    }
484
485
    /**
486
     * Build HTML attributes for use inside an HTML (or XML) tag.
487
     *
488
     * Includes a leading space, since this is usually used inside a tag, e.g.:
489
     *
490
     *     <div<?= $form->attrs(array('class' => 'foo')) ?>>...</div>
491
     *
492
     * Accepts strings, or arrays of strings, as attribute-values - arrays will
493
     * be folded using space as a separator, e.g. useful for the class-attribute.
494
     *
495
     * Attributes containing NULL, FALSE or an empty array() are ignored.
496
     *
497
     * Attributes containing TRUE are rendered as value-less attributes.
498
     *
499
     * @param array $attr map where attribute-name => attribute value(s)
500
     * @param bool  $sort true, to sort attributes by name; otherwise false (sorting is enabled by default)
501
     *
502
     * @return string
503
     */
504 25
    public function attrs(array $attr, $sort = true)
505
    {
506 25
        if ($sort) {
507 25
            ksort($attr);
508
        }
509
510 25
        $html = '';
511
512 25
        foreach ($attr as $name => $value) {
513 25
            if (is_array($value)) {
514 8
                $value = count($value)
515 7
                    ? implode(' ', $value) // fold multi-value attribute (e.g. class-names)
516 8
                    : null; // filter empty array
517
            }
518
519 25
            if ($value === null || $value === false) {
520 18
                continue; // skip NULL and FALSE attributes
521
            }
522
523 25
            if ($value === true) {
524 9
                $html .= $this->xhtml ?
525 1
                    ' ' . $name . '="' . $name . '"' // e.g. disabled="disabled" (as required for XHTML)
526 9
                    : ' ' . $name; // value-less HTML attribute
527
            } else {
528 25
                $html .= ' ' . $name . '="' . $this->escape($value) . '"';
529
            }
530
        }
531
532 25
        return $html;
533
    }
534
535
    /**
536
     * Builds an HTML <input> tag
537
     *
538
     * @param string $type
539
     * @param string $name
0 ignored issues
show
Documentation introduced by
Should the type for parameter $name not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
540
     * @param string $value
0 ignored issues
show
Documentation introduced by
Should the type for parameter $value not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
541
     * @param array  $attr map of HTML attributes
542
     *
543
     * @return string
544
     *
545
     * @see inputFor()
546
     */
547 1
    public function input($type, $name = null, $value= null, $attr = [])
548
    {
549 1
        return $this->tag(
550 1
            'input',
551 1
            $this->mergeAttrs(
552
                [
553 1
                    'type'  => $type,
554 1
                    'name'  => $name,
555 1
                    'value' => $value,
556
                ],
557 1
                $attr
558
            )
559
        );
560
    }
561
562
    /**
563
     * Build an HTML5 input-tag for a given Field
564
     *
565
     * @param FieldInterface $field
566
     * @param string         $type HTML5 input type-attribute (e.g. "text", "password", etc.)
567
     * @param array          $attr map of HTML attributes
568
     *
569
     * @return string
570
     */
571 12
    public function inputFor(FieldInterface $field, $type, array $attr = [])
572
    {
573 12
        return $this->tag(
574 12
            'input',
575 12
            $this->mergeAttrs(
576
                [
577 12
                    'name'        => $this->getName($field),
578 12
                    'id'          => $this->getId($field),
579 12
                    'class'       => $this->input_class,
580 12
                    'value'       => $this->model->getInput($field),
581 12
                    'type'        => $type,
582 12
                    'required'    => $this->isRequired($field),
583 12
                    'placeholder' => @$attr['placeholder'] ?: $this->getPlaceholder($field),
584
                ],
585 12
                $attr
586
            )
587
        );
588
    }
589
590
    /**
591
     * Build an HTML opening tag for an input group
592
     *
593
     * Call {@see endGroup()} to create the matching closing tag.
594
     *
595
     * @param array $attr optional map of HTML attributes
596
     *
597
     * @return string
598
     *
599
     * @see groupFor()
600
     */
601 1
    public function group($attr = [])
602
    {
603 1
        return $this->openTag(
604 1
            $this->group_tag,
605 1
            $this->mergeAttrs($this->group_attrs, $attr)
606
        );
607
    }
608
609
    /**
610
     * Build an HTML opening tag for an input group, with CSS classes added for
611
     * {@see Field::$required} and error state, as needed.
612
     *
613
     * Call {@see endGroup()} to create the matching closing tag.
614
     *
615
     * @param FieldInterface $field
616
     * @param array          $attr map of HTML attributes (optional)
617
     *
618
     * @return string
619
     *
620
     * @see $group_tag
621
     * @see $group_attrs
622
     * @see $required_class
623
     * @see $error_class
624
     * @see endGroup()
625
     */
626 2
    public function groupFor(FieldInterface $field, array $attr = [])
627
    {
628 2
        return $this->openTag(
629 2
            $this->group_tag,
630 2
            $this->mergeAttrs($this->group_attrs, $this->getAttrs($field), $attr)
631
        );
632
    }
633
634
    /**
635
     * Returns the matching closing tag for a {@see group()} or {@see groupFor()} tag.
636
     *
637
     * @return string
638
     *
639
     * @see groupFor()
640
     * @see $group_tag
641
     */
642 2
    public function endGroup()
643
    {
644 2
        return "</{$this->group_tag}>";
645
    }
646
647
    /**
648
     * Builds an HTML div with state-classes, containing the given HTML.
649
     *
650
     * @param FieldInterface $field
651
     * @param string         $html inner HTML for the generated div
652
     * @param array          $attr additional attributes for the div
653
     *
654
     * @return string HTML
655
     */
656 2
    public function divFor(FieldInterface $field, $html, array $attr = [])
657
    {
658 2
        return $this->tag('div', $this->mergeAttrs($this->getAttrs($field), $attr), $html);
659
    }
660
661
    /**
662
     * Build a `<label for="id" />` tag
663
     *
664
     * @param string $for   target element ID
665
     * @param string $label label text
666
     * @param array  $attr  map of HTML attributes
667
     *
668
     * @return string
669
     *
670
     * @see labelFor()
671
     */
672 3
    public function label($for, $label, $attr = [])
673
    {
674 3
        return $this->tag(
675 3
            'label',
676 3
            $this->mergeAttrs(
677
                [
678 3
                    'for' => $for,
679 3
                    'class' => $this->label_class
680
                ],
681 3
                $attr
682
            ),
683 3
            $this->softEscape($label . $this->label_suffix)
684
        );
685
    }
686
687
    /**
688
     * Build an HTML `<label for="id" />` tag
689
     *
690
     * @param FieldInterface $field
691
     * @param string|null    $label label text (optional)
692
     * @param array          $attr  map of HTML attributes
693
     *
694
     * @return string
695
     *
696
     * @see Field::getLabel()
697
     *
698
     * @throws RuntimeException if a label cannot be produced
699
     */
700 2
    public function labelFor(FieldInterface $field, $label = null, array $attr = [])
0 ignored issues
show
Unused Code introduced by
The parameter $attr is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
701
    {
702 2
        $id = $this->getId($field);
703
704 2
        if ($id === null) {
705 1
            throw new RuntimeException("cannot produce a label when FormHelper::\$id_prefix is NULL");
706
        }
707
708 2
        if ($label === null) {
709 2
            $label = $this->getLabel($field);
710
711 2
            if ($label === null) {
712 1
                throw new RuntimeException("the given Field has no defined label");
713
            }
714
        }
715
716 2
        return $this->label($id, $label);
717
    }
718
719
    /**
720
     * Build an HTML error summary, if there are any errors. (otherwise, returns an empty string.)
721
     *
722
     * Typically this is used at the end of a form (above the submit button) to call
723
     * attention to the fact that some inputs failed validation - this is meaningful
724
     * on long forms with many inputs, where the user might not immediately notice
725
     * a single input that has been highlighted with an error state.
726
     *
727
     * @return string error summary (or an empty string, if the current model contains no errors)
728
     */
729 1
    public function errorSummary()
730
    {
731 1
        if (! $this->model->hasErrors()) {
732
            return "";
733
        }
734
735 1
        $errors = implode(
736 1
            "",
737 1
            array_map(
738 1
                function ($str) {
739 1
                    return "<li>" . $this->escape($str) . "</li>";
740 1
                },
741 1
                $this->model->getErrors()
742
            )
743
        );
744
745 1
        return $this->tag(
746 1
            "div",
747
            [
748 1
                "role" => "alert",
749 1
                "class" => $this->error_summary_class
750
            ],
751 1
            "<ul>{$errors}</ul>"
752
        );
753
    }
754
}
755