Alert   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 521
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 158
c 1
b 0
f 0
dl 0
loc 521
rs 8.96
ccs 161
cts 161
cp 1
wmc 43

35 Methods

Rating   Name   Duplication   Size   Complexity  
A body() 0 6 1
A bodyContainerAttributes() 0 6 1
A bodyContainerClass() 0 6 1
A headerClass() 0 6 1
A headerTag() 0 10 2
A class() 0 6 1
A headerContainerClass() 0 6 1
A buttonLabel() 0 6 1
A renderHeaderContainer() 0 11 3
A bodyAttributes() 0 6 1
A renderBodyContainer() 0 11 2
A renderBody() 0 5 2
A iconClass() 0 6 1
A id() 0 6 1
A renderIcon() 0 9 1
A iconContainerClass() 0 6 1
A bodyClass() 0 6 1
A renderHeader() 0 3 1
A headerContainer() 0 6 1
A attributes() 0 6 1
A layoutBody() 0 6 1
A layoutHeader() 0 6 1
A renderButton() 0 9 1
A buttonOnClick() 0 6 1
A buttonClass() 0 6 1
A header() 0 6 1
A iconText() 0 6 1
A buttonAttributes() 0 6 1
A headerContainerAttributes() 0 6 1
A headerAttributes() 0 6 1
A iconAttributes() 0 6 1
A iconContainerAttributes() 0 6 1
A bodyContainer() 0 6 1
A bodyTag() 0 10 2
A render() 0 24 3

How to fix   Complexity   

Complex Class

Complex classes like Alert 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 Alert, 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 Yiisoft\Html\Html;
9
use Yiisoft\Html\Tag\Button;
10
use Yiisoft\Html\Tag\Div;
11
use Yiisoft\Html\Tag\I;
12
use Yiisoft\Widget\Widget;
13
14
use function array_key_exists;
15
use function strtr;
16
use function trim;
17
18
/**
19
 * Alert renders an alert component.
20
 *
21
 * @link https://getbootstrap.com/docs/5.0/components/alerts/
22
 * @link https://bulma.io/documentation/elements/notification/
23
 * @link https://tailwindui.com/components/application-ui/feedback/alerts
24
 */
25
final class Alert extends Widget
26
{
27
    private array $attributes = [];
28
    private array $buttonAttributes = [];
29
    private string $buttonLabel = '&times;';
30
    private string $body = '';
31
    private array $bodyAttributes = [];
32
    /** @psalm-var non-empty-string */
33
    private ?string $bodyTag = 'span';
34
    private bool $bodyContainer = false;
35
    private array $bodyContainerAttributes = [];
36
    private string $header = '';
37
    private array $headerAttributes = [];
38
    private bool $headerContainer = false;
39
    private array $headerContainerAttributes = [];
40
    /** @psalm-var non-empty-string */
41
    private string $headerTag = 'span';
42
    private array $iconAttributes = [];
43
    private array $iconContainerAttributes = [];
44
    private string $iconText = '';
45
    private string $layoutHeader = '';
46
    private string $layoutBody = '{body}{button}';
47
48
    /**
49
     * Returns a new instance with the HTML attributes.
50
     *
51
     * @param array $valuesMap Attribute values indexed by attribute names.
52 1
     */
53
    public function attributes(array $valuesMap): self
54 1
    {
55 1
        $new = clone $this;
56 1
        $new->attributes = $valuesMap;
57
58
        return $new;
59
    }
60
61
    /**
62
     * Returns a new instance with changed message body.
63
     *
64
     * @param string $value The message body.
65
     */
66 21
    public function body(string $value): self
67
    {
68 21
        $new = clone $this;
69 21
        $new->body = $value;
70 21
71
        return $new;
72
    }
73
74
    /**
75
     * Returns a new instance with the HTML attributes for the message body tag.
76
     *
77
     * @param array $valuesMap Attribute values indexed by attribute names.
78
     *
79
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
80
     */
81
    public function bodyAttributes(array $valuesMap): self
82 1
    {
83
        $new = clone $this;
84 1
        $new->bodyAttributes = $valuesMap;
85 1
86 1
        return $new;
87
    }
88
89
    /**
90
     * Returns a new instance with CSS class for the message body tag.
91
     *
92
     * @param string $value The CSS class name.
93
     */
94
    public function bodyClass(string $value): self
95
    {
96 7
        $new = clone $this;
97
        Html::addCssClass($new->bodyAttributes, $value);
98 7
99 7
        return $new;
100 7
    }
101
102
    /**
103
     * Returns a new instance specifying when allows you to add an extra wrapper for the panel body.
104
     *
105
     * @param bool $value Whether to add an extra wrapper for the panel body.
106
     */
107
    public function bodyContainer(bool $value): self
108
    {
109
        $new = clone $this;
110 3
        $new->bodyContainer = $value;
111
112 3
        return $new;
113 1
    }
114
115
    /**
116 2
     * Returns a new instance with the HTML attributes for rendering extra message wrapper.
117 2
     *
118 2
     * @param array $valuesMap Attribute values indexed by attribute names.
119
     *
120
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
121
     */
122
    public function bodyContainerAttributes(array $valuesMap): self
123
    {
124
        $new = clone $this;
125
        $new->bodyContainerAttributes = $valuesMap;
126
127
        return $new;
128
    }
129
130 1
    /**
131
     * Returns a new instance with the CSS class for extra message wrapper.
132 1
     *
133 1
     * @param string $value The CSS class name.
134 1
     */
135
    public function bodyContainerClass(string $value): self
136
    {
137
        $new = clone $this;
138
        Html::addCssClass($new->bodyContainerAttributes, $value);
139
140
        return $new;
141
    }
142
143
    /**
144 5
     * Returns a new instance specifying when allows you to add an extra wrapper for the message body.
145
     *
146 5
     * @param string|null $tag The tag name.
147 5
     */
148 5
    public function bodyTag(?string $tag = null): self
149
    {
150
        if ($tag === '') {
151
            throw new InvalidArgumentException('Body tag must be a string and cannot be empty.');
152
        }
153
154
        $new = clone $this;
155
        $new->bodyTag = $tag;
156
157
        return $new;
158 6
    }
159
160 6
    /**
161 6
     * Returns a new instance with the HTML the attributes for rendering the button tag.
162 6
     *
163
     * The button is displayed in the header of the modal window. Clicking on the button will hide the modal.
164
     *
165
     * If {@see buttonEnabled} is `false`, no button will be rendered.
166
     *
167
     * The rest of the options will be rendered as the HTML attributes of the button tag.
168
     *
169
     * @param array $valuesMap Attribute values indexed by attribute names.
170
     *
171
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
172
     */
173
    public function buttonAttributes(array $valuesMap): self
174
    {
175
        $new = clone $this;
176
        $new->buttonAttributes = $valuesMap;
177
178
        return $new;
179
    }
180 4
181
    /**
182 4
     * Returns a new instance with the CSS class for the button.
183 4
     *
184 4
     * @param string $value The CSS class name.
185
     */
186
    public function buttonClass(string $value): self
187
    {
188
        $new = clone $this;
189
        Html::addCssClass($new->buttonAttributes, $value);
190
191
        return $new;
192
    }
193
194 13
    /**
195
     * Returns a new instance with the label for the button.
196 13
     *
197 13
     * @param string $value The label for the button.
198 13
     */
199
    public function buttonLabel(string $value = ''): self
200
    {
201
        $new = clone $this;
202
        $new->buttonLabel = $value;
203
204
        return $new;
205
    }
206
207
    /**
208 4
     * Returns a new instance with the `onclick` JavaScript for the button.
209
     *
210 4
     * @param string $value The `onclick` JavaScript for the button.
211 4
     */
212 4
    public function buttonOnClick(string $value): self
213
    {
214
        $new = clone $this;
215
        $new->buttonAttributes['onclick'] = $value;
216
217
        return $new;
218
    }
219
220
    /**
221
     * Returns a new instance with the CSS class for the widget.
222 7
     *
223
     * @param string $value The CSS class name.
224 7
     */
225 7
    public function class(string $value): self
226 7
    {
227
        $new = clone $this;
228
        Html::addCssClass($new->attributes, $value);
229
230
        return $new;
231
    }
232
233
    /**
234
     * Returns a new instance with the header content.
235
     *
236 12
     * @param string $value The header content in the message.
237
     */
238 12
    public function header(string $value): self
239 12
    {
240 12
        $new = clone $this;
241
        $new->header = $value;
242
243 19
        return $new;
244
    }
245 19
246 19
    /**
247 19
     * Returns a new instance with the HTML attributes for rendering the header content.
248
     *
249
     * @param array $valuesMap Attribute values indexed by attribute names.
250
     *
251
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
252
     */
253
    public function headerAttributes(array $valuesMap): self
254
    {
255
        $new = clone $this;
256
        $new->headerAttributes = $valuesMap;
257 4
258
        return $new;
259 4
    }
260 4
261 4
    /**
262
     * Returns a new instance with the CSS class for the header.
263
     *
264
     * @param string $value The CSS class name.
265
     */
266
    public function headerClass(string $value): self
267
    {
268
        $new = clone $this;
269
        Html::addCssClass($new->headerAttributes, $value);
270
271
        return $new;
272
    }
273 1
274
    /**
275 1
     * Returns a new instance specifying when allows you to add a div tag to the header extra wrapper.
276 1
     *
277 1
     * @param bool $value The value indicating whether to add a div tag to the header extra wrapper.
278
     */
279
    public function headerContainer(bool $value = true): self
280
    {
281
        $new = clone $this;
282
        $new->headerContainer = $value;
283
284
        return $new;
285
    }
286
287 2
    /**
288
     * Returns a new instance with the HTML attributes for rendering the header.
289 2
     *
290 2
     * @param array $valuesMap Attribute values indexed by attribute names.
291 2
     *
292
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
293
     */
294
    public function headerContainerAttributes(array $valuesMap): self
295
    {
296
        $new = clone $this;
297
        $new->headerContainerAttributes = $valuesMap;
298
299
        return $new;
300
    }
301 2
302
    /**
303 2
     * Returns a new instance with the CSS class for the header extra wrapper.
304 2
     *
305 2
     * @param string $value The CSS class name.
306
     */
307
    public function headerContainerClass(string $value): self
308
    {
309
        $new = clone $this;
310
        Html::addCssClass($new->headerContainerAttributes, $value);
311
312
        return $new;
313
    }
314
315 1
    /**
316
     * Returns a new instance with the tag name for the header.
317 1
     *
318 1
     * @param string $value The tag name for the header.
319 1
     *
320
     * @throws InvalidArgumentException
321
     */
322
    public function headerTag(string $value): self
323
    {
324
        if (empty($value)) {
325
            throw new InvalidArgumentException('Header tag must be a string and cannot be empty.');
326
        }
327
328
        $new = clone $this;
329
        $new->headerTag = $value;
330
331 1
        return $new;
332
    }
333 1
334 1
    /**
335 1
     * Returns a new instance with the HTML attributes for rendering the `<i>` tag for the icon.
336
     *
337
     * @param array $valuesMap Attribute values indexed by attribute names.
338
     *
339
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
340
     */
341
    public function iconAttributes(array $valuesMap): self
342
    {
343
        $new = clone $this;
344
        $new->iconAttributes = $valuesMap;
345
346
        return $new;
347 2
    }
348
349 2
    /**
350 1
     * Returns a new instance with the icon CSS class.
351
     *
352
     * @param string $value The icon CSS class.
353 1
     */
354 1
    public function iconClass(string $value): self
355 1
    {
356
        $new = clone $this;
357
        Html::addCssClass($new->iconAttributes, $value);
358
359
        return $new;
360
    }
361
362
    /**
363
     * Returns a new instance with the HTML attributes for rendering icon container.
364
     *
365
     * The rest of the options will be rendered as the HTML attributes of the icon container.
366
     *
367 2
     * @param array $valuesMap Attribute values indexed by attribute names.
368
     *
369 2
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
370 2
     */
371 2
    public function iconContainerAttributes(array $valuesMap): self
372
    {
373
        $new = clone $this;
374
        $new->iconContainerAttributes = $valuesMap;
375
376
        return $new;
377
    }
378
379
    /**
380
     * Returns a new instance with the CSS class for the icon container.
381 5
     *
382
     * @param string $value The CSS class name.
383 5
     */
384 5
    public function iconContainerClass(string $value): self
385 5
    {
386
        $new = clone $this;
387
        Html::addCssClass($new->iconContainerAttributes, $value);
388
389
        return $new;
390
    }
391
392
    /**
393
     * Returns a new instance with the icon text.
394
     *
395
     * @param string $value The icon text.
396
     */
397
    public function iconText(string $value): self
398
    {
399 1
        $new = clone $this;
400
        $new->iconText = $value;
401 1
402 1
        return $new;
403 1
    }
404
405
    /**
406
     * Returns a new instance with the specified Widget ID.
407
     *
408
     * @param string $value The id of the widget.
409
     */
410
    public function id(string $value): self
411
    {
412
        $new = clone $this;
413 2
        $new->attributes['id'] = $value;
414
415 2
        return $new;
416 2
    }
417 2
418
    /**
419
     * Returns a new instance with the config layout body.
420
     *
421
     * @param string $value The config layout body.
422
     */
423
    public function layoutBody(string $value): self
424
    {
425
        $new = clone $this;
426
        $new->layoutBody = $value;
427 3
428
        return $new;
429 3
    }
430 3
431 3
    /**
432
     * Returns a new instance with the config layout header.
433
     *
434
     * @param string $value The config layout header.
435
     */
436
    public function layoutHeader(string $value): self
437
    {
438
        $new = clone $this;
439
        $new->layoutHeader = $value;
440
441 8
        return $new;
442
    }
443 8
444 8
    public function render(): string
445 8
    {
446
        $div = Div::tag();
447
        $parts = [];
448
449
        if (!array_key_exists('id', $this->attributes)) {
450
            $div = $div->id(Html::generateId('alert-'));
451
        }
452
453
        $parts['{button}'] = $this->renderButton();
454
        $parts['{icon}'] = $this->renderIcon();
455 6
        $parts['{body}'] = $this->renderBody();
456
        $parts['{header}'] = $this->renderHeader();
457 6
458 6
        $contentAlert = $this->renderHeaderContainer($parts) . PHP_EOL . $this->renderBodyContainer($parts);
459 6
460
        return $this->body !== ''
461
            ? $div
462 21
                ->attribute('role', 'alert')
463
                ->addAttributes($this->attributes)
464 21
                ->content(PHP_EOL . trim($contentAlert) . PHP_EOL)
465 21
                ->encode(false)
466
                ->render()
467 21
            : '';
468 1
    }
469
470
    /**
471 21
     * Renders close button.
472 21
     */
473
    private function renderButton(): string
474
    {
475 21
        return PHP_EOL .
476 21
            Button::tag()
477
                ->attributes($this->buttonAttributes)
478
                ->content($this->buttonLabel)
479 21
                ->encode(false)
480 21
                ->type('button')
481
                ->render();
482
    }
483 21
484 21
    /**
485
     * Render icon.
486
     */
487 21
    private function renderIcon(): string
488
    {
489 21
        return PHP_EOL .
490
            Div::tag()
491 21
                ->attributes($this->iconContainerAttributes)
492 21
                ->content(I::tag()->attributes($this->iconAttributes)->content($this->iconText)->render())
493 21
                ->encode(false)
494 21
                ->render() .
495 21
            PHP_EOL;
496 21
    }
497
498
    /**
499
     * Render the alert message body.
500
     */
501
    private function renderBody(): string
502 21
    {
503
        return $this->bodyTag !== null
504 21
            ? Html::normalTag($this->bodyTag, $this->body, $this->bodyAttributes)->encode(false)->render()
505 21
            : $this->body;
506 21
    }
507 21
508 21
    /**
509 21
     * Render the header.
510 21
     */
511
    private function renderHeader(): string
512
    {
513
        return Html::normalTag($this->headerTag, $this->header, $this->headerAttributes)->encode(false)->render();
514
    }
515
516 21
    /**
517
     * Render the header container.
518 21
     */
519
    private function renderHeaderContainer(array $parts): string
520 21
    {
521 21
        $headerHtml = trim(strtr($this->layoutHeader, $parts));
522 21
523 21
        return $this->headerContainer && $headerHtml !== ''
524 21
            ? Div::tag()
525 21
                ->attributes($this->headerContainerAttributes)
526
                ->content(PHP_EOL . $headerHtml . PHP_EOL)
527
                ->encode(false)
528
                ->render()
529
            : $headerHtml;
530
    }
531 21
532
    /**
533 21
     * Render the panel body.
534 20
     */
535 20
    private function renderBodyContainer(array $parts): string
536 20
    {
537 20
        $bodyHtml = trim(strtr($this->layoutBody, $parts));
538 20
539
        return $this->bodyContainer
540 1
            ? Div::tag()
541
                ->attributes($this->bodyContainerAttributes)
542
                ->content(PHP_EOL . $bodyHtml . PHP_EOL)
543
                ->encode(false)
544
                ->render()
545
            : $bodyHtml;
546
    }
547
}
548