Menu   D
last analyzed

Complexity

Total Complexity 59

Size/Duplication

Total Lines 703
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 59
eloc 211
c 3
b 0
f 0
dl 0
loc 703
ccs 122
cts 122
cp 1
rs 4.08

38 Methods

Rating   Name   Duplication   Size   Complexity  
A dropdownContainerClass() 0 6 1
A activateItems() 0 6 1
A activeClass() 0 6 1
A afterAttributes() 0 6 1
A dropdownContainerTag() 0 6 1
A attributes() 0 6 1
A dropdownDefinitions() 0 6 1
A firstItemClass() 0 6 1
A afterClass() 0 6 1
A beforeClass() 0 6 1
A disabledClass() 0 6 1
A beforeTag() 0 6 1
A class() 0 6 1
A container() 0 6 1
A afterContent() 0 6 1
A currentPath() 0 6 1
A beforeAttributes() 0 6 1
A afterTag() 0 6 1
A beforeContent() 0 6 1
A iconContainerAttributes() 0 6 1
A linkClass() 0 6 1
A itemsTag() 0 6 1
A linkAttributes() 0 6 1
A itemsContainerAttributes() 0 6 1
A tagName() 0 6 1
A renderAfterContent() 0 10 2
A itemsContainerClass() 0 6 1
A template() 0 6 1
A lastItemClass() 0 6 1
A linkTag() 0 6 1
A itemsContainer() 0 6 1
A items() 0 6 1
A renderBeforeContent() 0 9 2
A renderDropdown() 0 26 3
A renderItem() 0 28 6
B renderItems() 0 43 9
A renderMenu() 0 25 4
A render() 0 28 2

How to fix   Complexity   

Complex Class

Complex classes like Menu often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Menu, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Widgets;
6
7
use InvalidArgumentException;
8
use Stringable;
9
use Yiisoft\Definitions\Exception\CircularReferenceException;
10
use Yiisoft\Definitions\Exception\InvalidConfigException;
11
use Yiisoft\Definitions\Exception\NotInstantiableException;
12
use Yiisoft\Factory\NotFoundException;
13
use Yiisoft\Html\Html;
14
use Yiisoft\Widget\Widget;
15
16
use function array_merge;
17
use function count;
18
use function implode;
19
use function strtr;
20
use function trim;
21
22
/**
23
 * Menu displays a multi-level menu using nested HTML lists.
24
 *
25
 * The {@see Menu::items()} method specifies the possible items in the menu.
26
 * A menu item can contain sub-items which specify the sub-menu under that menu item.
27
 *
28
 * Menu checks the current route and request parameters to toggle certain menu items with active state.
29
 *
30
 * Note that Menu only renders the HTML tags about the menu. It does not do any styling.
31
 * You are responsible to provide CSS styles to make it look like a real menu.
32
 *
33
 * The following example shows how to use Menu:
34
 *
35
 * ```php
36
 * <?= Menu::Widget()
37
 *     ->items([
38
 *         ['label' => 'Login', 'link' => 'site/login', 'visible' => true],
39
 *     ]);
40
 * ?>
41
 * ```
42
 */
43
final class Menu extends Widget
44
{
45
    private array $afterAttributes = [];
46
    private string $afterContent = '';
47
    private string $afterTag = 'span';
48
    private string $activeClass = 'active';
49
    private bool $activateItems = true;
50
    private array $attributes = [];
51
    private array $beforeAttributes = [];
52
    private string $beforeContent = '';
53
    private string $beforeTag = 'span';
54
    private bool $container = true;
55
    private string $currentPath = '';
56
    private string $disabledClass = 'disabled';
57
    private bool $dropdownContainer = true;
58
    private array $dropdownContainerAttributes = [];
59
    private string $dropdownContainerTag = 'li';
60
    private array $dropdownDefinitions = [];
61
    private string $firstItemClass = '';
62
    private array $iconContainerAttributes = [];
63
    private array $items = [];
64
    private bool $itemsContainer = true;
65
    private array $itemsContainerAttributes = [];
66 2
    private string $itemsTag = 'li';
67
    private string $lastItemClass = '';
68 2
    private array $linkAttributes = [];
69 2
    private string $linkClass = '';
70 2
    private string $linkTag = 'a';
71
    private string $tagName = 'ul';
72
    private string $template = '{items}';
73
74
    /**
75
     * Return new instance with specified whether to activate parent menu items when one of the corresponding child menu
76
     * items is active.
77
     *
78
     * @param bool $value The value to be assigned to the activateItems property.
79
     */
80
    public function activateItems(bool $value): self
81 2
    {
82
        $new = clone $this;
83 2
        $new->activateItems = $value;
84 2
85 2
        return $new;
86
    }
87
88
    /**
89
     * Returns a new instance with the specified active CSS class.
90
     *
91
     * @param string $value The CSS class to be appended to the active menu item.
92
     */
93
    public function activeClass(string $value): self
94
    {
95 3
        $new = clone $this;
96
        $new->activeClass = $value;
97 3
98 3
        return $new;
99 3
    }
100
101
    /**
102
     * Returns a new instance with the specified after container attributes.
103
     *
104
     * @param array $valuesMap Attribute values indexed by attribute names.
105
     */
106
    public function afterAttributes(array $valuesMap): self
107
    {
108
        $new = clone $this;
109 4
        $new->afterAttributes = $valuesMap;
110
111 4
        return $new;
112 4
    }
113 4
114
    /**
115
     * Returns a new instance with the specified after container class.
116
     *
117
     * @param string $value The class name.
118
     */
119
    public function afterClass(string $value): self
120
    {
121 2
        $new = clone $this;
122
        Html::addCssClass($new->afterAttributes, $value);
123 2
124 2
        return $new;
125 2
    }
126
127
    /**
128
     * Returns a new instance with the specified after content.
129
     *
130
     * @param string|Stringable $content The content.
131
     */
132
    public function afterContent(string|Stringable $content): self
133
    {
134
        $new = clone $this;
135 2
        $new->afterContent = (string) $content;
136
137 2
        return $new;
138 2
    }
139 2
140
    /**
141
     * Returns a new instance with the specified after container tag.
142
     *
143
     * @param string $value The after container tag.
144
     */
145
    public function afterTag(string $value): self
146
    {
147
        $new = clone $this;
148
        $new->afterTag = $value;
149
150 2
        return $new;
151
    }
152 2
153 2
    /**
154 2
     * Returns a new instance with the HTML attributes. The following special options are recognized.
155
     *
156
     * @param array $valuesMap Attribute values indexed by attribute names.
157
     */
158
    public function attributes(array $valuesMap): self
159
    {
160
        $new = clone $this;
161
        $new->attributes = $valuesMap;
162
163
        return $new;
164
    }
165
166
    /**
167
     * Returns a new instance with the specified before container attributes.
168
     *
169
     * @param array $valuesMap Attribute values indexed by attribute names.
170
     */
171
    public function beforeAttributes(array $valuesMap): self
172
    {
173
        $new = clone $this;
174
        $new->beforeAttributes = $valuesMap;
175
176
        return $new;
177
    }
178
179
    /**
180
     * Returns a new instance with the specified before container class.
181
     *
182
     * @param string $value The before container class.
183
     */
184
    public function beforeClass(string $value): self
185
    {
186 16
        $new = clone $this;
187
        Html::addCssClass($new->beforeAttributes, $value);
188 16
189 16
        return $new;
190 16
    }
191
192
    /**
193
     * Returns a new instance with the specified before content.
194
     *
195
     * @param string|Stringable $value The content.
196
     */
197
    public function beforeContent(string|Stringable $value): self
198
    {
199
        $new = clone $this;
200
        $new->beforeContent = (string) $value;
201
202
        return $new;
203
    }
204
205
    /**
206
     * Returns a new instance with the specified before container tag.
207 2
     *
208
     * @param string $value The before container tag.
209 2
     */
210 2
    public function beforeTag(string $value): self
211 2
    {
212
        $new = clone $this;
213
        $new->beforeTag = $value;
214
215
        return $new;
216
    }
217
218
    /**
219
     * Returns a new instance with the specified the class `menu` widget.
220
     *
221
     * @param string $value The class `menu` widget.
222
     */
223
    public function class(string $value): self
224
    {
225 3
        $new = clone $this;
226
        Html::addCssClass($new->attributes, $value);
227 3
228 3
        return $new;
229 3
    }
230
231
    /**
232
     * Returns a new instance with the specified enable or disable the container widget.
233
     *
234
     * @param bool $value The container widget enable or disable, for default is `true`.
235
     */
236
    public function container(bool $value): self
237
    {
238
        $new = clone $this;
239 2
        $new->container = $value;
240
241 2
        return $new;
242 2
    }
243 2
244
    /**
245
     * Returns a new instance with the specified the current path.
246
     *
247
     * @param string $value The current path.
248
     */
249
    public function currentPath(string $value): self
250
    {
251
        $new = clone $this;
252
        $new->currentPath = $value;
253
254
        return $new;
255
    }
256 3
257
    /**
258 3
     * Returns a new instance with the specified disabled CSS class.
259 3
     *
260 3
     * @param string $value The CSS class to be appended to the disabled menu item.
261
     */
262
    public function disabledClass(string $value): self
263
    {
264
        $new = clone $this;
265
        $new->disabledClass = $value;
266
267
        return $new;
268
    }
269
270
    /**
271
     * Returns a new instance with the specified dropdown container class.
272
     *
273
     * @param string $value The dropdown container class.
274
     */
275
    public function dropdownContainerClass(string $value): self
276 2
    {
277
        $new = clone $this;
278 2
        Html::addCssClass($new->dropdownContainerAttributes, $value);
279 2
280 2
        return $new;
281
    }
282
283
    /**
284
     * Returns a new instance with the specified dropdown container tag.
285
     *
286
     * @param string $value The dropdown container tag.
287
     */
288
    public function dropdownContainerTag(string $value): self
289
    {
290 15
        $new = clone $this;
291
        $new->dropdownContainerTag = $value;
292 15
293
        return $new;
294 15
    }
295 2
296
    /**
297
     * Returns a new instance with the specified dropdown definition widget.
298 13
     *
299 13
     * @param array $valuesMap The dropdown definition widget.
300
     */
301 13
    public function dropdownDefinitions(array $valuesMap): self
302 1
    {
303 13
        $new = clone $this;
304
        $new->dropdownDefinitions = $valuesMap;
305
306
        return $new;
307
    }
308
309
    /**
310
     * Returns a new instance with the specified first item CSS class.
311
     *
312
     * @param string $value The CSS class that will be assigned to the first item in the main menu or each submenu.
313
     */
314
    public function firstItemClass(string $value): self
315
    {
316 13
        $new = clone $this;
317
        $new->firstItemClass = $value;
318 13
319 13
        return $new;
320
    }
321 13
322 13
    /**
323 13
     * Returns a new instance with the specified icon container attributes.
324 13
     *
325
     * @param array $valuesMap Attribute values indexed by attribute names.
326 13
     */
327 5
    public function iconContainerAttributes(array $valuesMap): self
328
    {
329
        $new = clone $this;
330 13
        $new->iconContainerAttributes = $valuesMap;
331 1
332
        return $new;
333
    }
334 13
335 1
    /**
336
     * List of items in the nav widget. Each array element represents a single menu item which can be either a string or
337
     * an array with the following structure:
338 13
     *
339
     * - label: string, required, the nav item label.
340 13
     * - active: bool, whether the item should be on active state or not.
341
     * - disabled: bool, whether the item should be on disabled state or not. For default `disabled` is false.
342 13
     * - encode: bool, whether the label should be HTML encoded or not. For default `encodeLabel` is true.
343 2
     * - items: array, optional, the item's submenu items. The structure is the same as for `items` option.
344 2
     * - itemsContainerAttributes: array, optional, the HTML attributes for the item's submenu container.
345 2
     * - link: string, the item's href. Defaults to "#". For default `link` is "#".
346
     * - linkAttributes: array, the HTML attributes of the item's link. For default `linkAttributes` is `[]`.
347
     * - icon: string, the item's icon. For default is ``.
348
     * - iconAttributes: array, the HTML attributes of the item's icon. For default `iconAttributes` is `[]`.
349 13
     * - iconClass: string, the item's icon CSS class. For default is ``.
350
     * - visible: bool, optional, whether this menu item is visible. Defaults to true.
351
     *
352 13
     * If a menu item is a string, it will be rendered directly without HTML encoding.
353
     *
354
     * @param array $valuesMap the list of items to be rendered.
355
     */
356
    public function items(array $valuesMap): self
357
    {
358
        $new = clone $this;
359
        $new->items = $valuesMap;
360
361
        return $new;
362
    }
363
364
    /**
365 13
     * Returns a new instance with the specified if enabled or disabled the items' container.
366
     *
367 13
     * @param bool $value The items container enable or disable, for default is `true`.
368 11
     */
369 11
    public function itemsContainer(bool $value): self
370 11
    {
371
        $new = clone $this;
372
        $new->itemsContainer = $value;
373
374 5
        return $new;
375 5
    }
376
377
    /**
378
     * Returns a new instance with the specified items' container attributes.
379
     *
380
     * @param array $valuesMap Attribute values indexed by attribute names.
381
     */
382
    public function itemsContainerAttributes(array $valuesMap): self
383
    {
384
        $new = clone $this;
385
        $new-> itemsContainerAttributes = $valuesMap;
386
387 15
        return $new;
388
    }
389 15
390 14
    /**
391 2
     * Returns a new instance with the specified items' container class.
392 2
     *
393
     * @param string $value The CSS class that will be assigned to the items' container.
394
     */
395 14
    public function itemsContainerClass(string $value): self
396 1
    {
397
        $new = clone $this;
398
        Html::addCssClass($new->itemsContainerAttributes, $value);
399 14
400 14
        return $new;
401 14
    }
402
403 14
    /**
404 4
     * Returns a new instance with the specified items tag.
405
     *
406 4
     * @param string $value The tag that will be used to wrap the items.
407 1
     */
408
    public function itemsTag(string $value): self
409 1
    {
410 1
        $new = clone $this;
411 1
        $new->itemsTag = $value;
412
413
        return $new;
414
    }
415
416 13
    /**
417
     * Returns a new instance with the specified last item CSS class.
418 12
     *
419 12
     * @param string $value The CSS class that will be assigned to the last item in the main menu or each submenu.
420
     */
421 2
    public function lastItemClass(string $value): self
422
    {
423 12
        $new = clone $this;
424
        $new->lastItemClass = $value;
425 4
426 1
        return $new;
427
    }
428
429 1
    /**
430
     * Returns a new instance with the specified link attributes.
431
     *
432 4
     * @param array $valuesMap Attribute values indexed by attribute names.
433 4
     */
434
    public function linkAttributes(array $valuesMap): self
435
    {
436
        $new = clone $this;
437 15
        $new->linkAttributes = $valuesMap;
438
439
        return $new;
440
    }
441
442
    /**
443
     * Returns a new instance with the specified link css class.
444
     *
445
     * @param string $value The CSS class that will be assigned to the link.
446
     */
447
    public function linkClass(string $value): self
448
    {
449 12
        $new = clone $this;
450
        $new->linkClass = $value;
451
452 12
        return $new;
453 12
    }
454 12
455 12
    /**
456
     * Returns a new instance with the specified link tag.
457
     *
458
     * @param string $value The tag that will be used to wrap the link.
459
     */
460
    public function linkTag(string $value): self
461
    {
462
        $new = clone $this;
463
        $new->linkTag = $value;
464
465
        return $new;
466
    }
467
468
    /**
469
     * Returns a new instance with the specified tag for rendering the menu.
470
     *
471
     * @param string $value The tag for rendering the menu.
472
     */
473
    public function tagName(string $value): self
474
    {
475
        $new = clone $this;
476
        $new->tagName = $value;
477
478
        return $new;
479
    }
480
481
    /**
482
     * Returns a new instance with the specified the template used to render the main menu.
483
     *
484
     * @param string $value The template used to render the main menu. In this template, the token `{items}` will be
485
     * replaced.
486
     */
487
    public function template(string $value): self
488
    {
489
        $new = clone $this;
490
        $new->template = $value;
491
492
        return $new;
493
    }
494
495
    /**
496
     * Renders the menu.
497
     *
498
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
499
     *
500
     * @return string The result of Widget execution to be outputted.
501
     */
502
    public function render(): string
503
    {
504
        if ($this->items === []) {
505
            return '';
506
        }
507
508
        /**
509
         * @psalm-var array<
510
         *   array-key,
511
         *   array{
512
         *     label: string,
513
         *     link: string,
514
         *     linkAttributes: array,
515
         *     active: bool,
516
         *     disabled: bool,
517
         *     visible: bool,
518
         *     items?: array
519
         *   }
520
         * > $items
521
         */
522
        $items = Helper\Normalizer::menu(
523
            $this->items,
524
            $this->currentPath,
525
            $this->activateItems,
526
            $this->iconContainerAttributes,
527
        );
528
529
        return $this->renderMenu($items);
530
    }
531
532
    private function renderAfterContent(): string
533
    {
534
        if ($this->afterTag === '') {
535
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
536
        }
537
538
        return PHP_EOL .
539
            Html::normalTag($this->afterTag, $this->afterContent, $this->afterAttributes)
540
                ->encode(false)
541
                ->render();
542
    }
543
544
    private function renderBeforeContent(): string
545
    {
546
        if ($this->beforeTag === '') {
547
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
548
        }
549
550
        return Html::normalTag($this->beforeTag, $this->beforeContent, $this->beforeAttributes)
551
            ->encode(false)
552
            ->render();
553
    }
554
555
    /**
556
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
557
     */
558
    private function renderDropdown(array $items): string
559
    {
560
        $dropdownDefinitions = $this->dropdownDefinitions;
561
562
        if ($dropdownDefinitions === []) {
563
            $dropdownDefinitions = [
564
                'container()' => [false],
565
                'dividerClass()' => ['dropdown-divider'],
566
                'toggleAttributes()' => [
567
                    ['aria-expanded' => 'false', 'data-bs-toggle' => 'dropdown', 'role' => 'button'],
568
                ],
569
                'toggleType()' => ['link'],
570
            ];
571
        }
572
573
        $dropdown = Dropdown::widget([], $dropdownDefinitions)->items($items)->render();
0 ignored issues
show
Bug introduced by
The method items() does not exist on Yiisoft\Widget\Widget. It seems like you code against a sub-type of Yiisoft\Widget\Widget such as Yiisoft\Yii\Widgets\Menu or Yiisoft\Yii\Widgets\Breadcrumbs or Yiisoft\Yii\Widgets\Dropdown. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

573
        $dropdown = Dropdown::widget([], $dropdownDefinitions)->/** @scrutinizer ignore-call */ items($items)->render();
Loading history...
574
575
        if ($this->dropdownContainerTag === '') {
576
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
577
        }
578
579
        return match ($this->dropdownContainer) {
580
            true => Html::normalTag($this->dropdownContainerTag, $dropdown, $this->dropdownContainerAttributes)
581
                ->encode(false)
582
                ->render(),
583
            false => $dropdown,
584
        };
585
    }
586
587
    /**
588
     * Renders the content of a menu item.
589
     *
590
     * Note that the container and the sub-menus are not rendered here.
591
     *
592
     * @param array $item The menu item to be rendered. Please refer to {@see items} to see what data might be in the
593
     * item.
594
     *
595
     * @return string The rendering result.
596
     *
597
     * @psalm-param array{
598
     *   label: string,
599
     *   link: string,
600
     *   linkAttributes: array,
601
     *   active: bool,
602
     *   disabled: bool,
603
     *   visible: bool,
604
     *   items?: array,
605
     *   itemsContainerAttributes?: array
606
     * } $item
607
     */
608
    private function renderItem(array $item): string
609
    {
610
        $linkAttributes = array_merge($item['linkAttributes'], $this->linkAttributes);
611
612
        if ($this->linkClass !== '') {
613
            Html::addCssClass($linkAttributes, $this->linkClass);
614
        }
615
616
        if ($item['active']) {
617
            $linkAttributes['aria-current'] = 'page';
618
            Html::addCssClass($linkAttributes, $this->activeClass);
619
        }
620
621
        if ($item['disabled']) {
622
            Html::addCssClass($linkAttributes, $this->disabledClass);
623
        }
624
625
        if ($item['link'] !== '') {
626
            $linkAttributes['href'] = $item['link'];
627
        }
628
629
        if ($this->linkTag === '') {
630
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
631
        }
632
633
        return match (isset($linkAttributes['href'])) {
634
            true => Html::normalTag($this->linkTag, $item['label'], $linkAttributes)->encode(false)->render(),
635
            false => $item['label'],
636
        };
637
    }
638
639
    /**
640
     * Recursively renders the menu items (without the container tag).
641
     *
642
     * @param array $items The menu items to be rendered recursively.
643
     *
644
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
645
     *
646
     * @psalm-param array<
647
     *   array-key,
648
     *   array{
649
     *     label: string,
650
     *     link: string,
651
     *     linkAttributes: array,
652
     *     active: bool,
653
     *     disabled: bool,
654
     *     visible: bool,
655
     *     items?: array,
656
     *     itemsContainerAttributes?: array
657
     *   }
658
     * > $items
659
     */
660
    private function renderItems(array $items): string
661
    {
662
        $lines = [];
663
        $n = count($items);
664
665
        foreach ($items as $i => $item) {
666
            if (isset($item['items'])) {
667
                $lines[] = strtr($this->template, ['{items}' => $this->renderDropdown([$item])]);
668
            } elseif ($item['visible']) {
669
                $itemsContainerAttributes = array_merge(
670
                    $this->itemsContainerAttributes,
671
                    $item['itemsContainerAttributes'] ?? [],
672
                );
673
674
                if ($i === 0 && $this->firstItemClass !== '') {
675
                    Html::addCssClass($itemsContainerAttributes, $this->firstItemClass);
676
                }
677
678
                if ($i === $n - 1 && $this->lastItemClass !== '') {
679
                    Html::addCssClass($itemsContainerAttributes, $this->lastItemClass);
680
                }
681
682
                $menu = $this->renderItem($item);
683
684
                if ($this->itemsTag === '') {
685
                    throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
686
                }
687
688
                $lines[] = match ($this->itemsContainer) {
689
                    false => $menu,
690
                    default => strtr(
691
                        $this->template,
692
                        [
693
                            '{items}' => Html::normalTag($this->itemsTag, $menu, $itemsContainerAttributes)
694
                                ->encode(false)
695
                                ->render(),
696
                        ],
697
                    ),
698
                };
699
            }
700
        }
701
702
        return PHP_EOL . implode(PHP_EOL, $lines);
703
    }
704
705
    /**
706
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
707
     *
708
     * @psalm-param array<
709
     *   array-key,
710
     *   array{
711
     *     label: string,
712
     *     link: string,
713
     *     linkAttributes: array,
714
     *     active: bool,
715
     *     disabled: bool,
716
     *     visible: bool,
717
     *     items?: array
718
     *   }
719
     * > $items
720
     */
721
    private function renderMenu(array $items): string
722
    {
723
        $afterContent = '';
724
        $attributes = $this->attributes;
725
        $beforeContent = '';
726
727
        $content = $this->renderItems($items) . PHP_EOL;
728
729
        if ($this->beforeContent !== '') {
730
            $beforeContent = $this->renderBeforeContent() . PHP_EOL;
731
        }
732
733
        if ($this->afterContent !== '') {
734
            $afterContent = $this->renderAfterContent();
735
        }
736
737
        if ($this->tagName === '') {
738
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
739
        }
740
741
        return match ($this->container) {
742
            false => $beforeContent . trim($content) . $afterContent,
743
            default => $beforeContent .
744
                Html::normalTag($this->tagName, $content, $attributes)->encode(false) .
745
                $afterContent,
746
        };
747
    }
748
}
749