NavBar::begin()   F
last analyzed

Complexity

Conditions 12
Paths 384

Size

Total Lines 79
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 53
CRAP Score 12

Importance

Changes 0
Metric Value
cc 12
eloc 49
nc 384
nop 0
dl 0
loc 79
ccs 53
cts 53
cp 1
crap 12
rs 3.8333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bootstrap5;
6
7
use Stringable;
8
use Yiisoft\Arrays\ArrayHelper;
9
use Yiisoft\Html\Html;
10
use Yiisoft\Html\Tag\Base\Tag;
11
12
use function array_merge;
13
14
/**
15
 * NavBar renders a navbar HTML component.
16
 *
17
 * Any content enclosed between the {@see begin()} and {@see end()} calls of NavBar is treated as the content of the
18
 * navbar. You may use widgets such as {@see Nav} or {@see \Yiisoft\Widget\Menu} to build up such content. For example,
19
 *
20
 * ```php
21
 *    if ($user->getId() !== null) {
22
 *        $menuItems = [
23
 *            [
24
 *                'label' => 'About',
25
 *                'url' => '/about',
26
 *            ],
27
 *            [
28
 *                'label' => 'Contact',
29
 *                'url' => '/contact',
30
 *            ],
31
 *            [
32
 *                'label' => 'Logout' . ' ' . '(' . $user->getUsername() . ')',
33
 *                'url' => '/logout'
34
 *            ],
35
 *        ];
36
 *    } else {
37
 *        $menuItems = [
38
 *            [
39
 *                'label' => 'About',
40
 *                'url' => '/about',
41
 *            ],
42
 *            [
43
 *                'label' => 'Contact',
44
 *                'url' => '/contact',
45
 *            ],
46
 *            [
47
 *                'label' => 'Login',
48
 *                'url' => '/login',
49
 *            ],
50
 *        ];
51
 *    }
52
 *
53
 *    <?= NavBar::widget()
54
 *        ->brandText('My Application Basic')
55
 *        ->brandUrl('/')
56
 *        ->options([
57
 *            'class' => 'navbar navbar-dark bg-dark navbar-expand-lg text-white',
58
 *        ])
59
 *        ->begin();
60
 *
61
 *        echo Nav::widget()
62
 *            ->currentPath($currentPath)
63
 *            ->items($menuItems)
64
 *            ->options([
65
 *                'class' => 'navbar-nav float-right ml-auto'
66
 *            ]);
67
 *
68
 *    echo NavBar::end(); ?>
69
 * ```
70
 * Note: $currentPath it must be injected from each controller to the main controller.
71
 *
72
 * SiteController.php
73
 *
74
 * ```php
75
 *
76
 *    public function index(ServerRequestInterface $request): ResponseInterface
77
 *    {
78
 *        $response = $this->responseFactory->createResponse();
79
 *        $currentPath = $request
80
 *            ->getUri()
81
 *            ->getPath();
82
 *        $output = $this->render('index', ['currentPath' => $currentPath]);
83
 *        $response
84
 *            ->getBody()
85
 *            ->write($output);
86
 *
87
 *        return $response;
88
 *    }
89
 * ```
90
 *
91
 * Controller.php
92
 *
93
 * ```php
94
 *    private function renderContent($content, array $parameters = []): string
95
 *    {
96
 *        $user = $this->user->getIdentity();
97
 *        $layout = $this->findLayoutFile($this->layout);
98
 *
99
 *        if ($layout !== null) {
100
 *            return $this->view->renderFile(
101
 *                $layout,
102
 *                    [
103
 *                        'aliases' => $this->aliases,
104
 *                        'content' => $content,
105
 *                        'user' => $user,
106
 *                        'params' => $this->params,
107
 *                        'currentPath' => !isset($parameters['currentPath']) ?: $parameters['currentPath']
108
 *                    ],
109
 *                $this
110
 *            );
111
 *        }
112
 *
113
 *        return $content;
114
 *    }
115
 * ```
116
 */
117
final class NavBar extends AbstractToggleWidget
118
{
119
    public const EXPAND_SM = 'navbar-expand-sm';
120
    public const EXPAND_MD = 'navbar-expand-md';
121
    public const EXPAND_LG = 'navbar-expand-lg';
122
    public const EXPAND_XL = 'navbar-expand-xl';
123
    public const EXPAND_XXL = 'navbar-expand-xxl';
124
125
    private array $collapseOptions = [];
126
    private ?string $brandText = null;
127
    private ?string $brandImage = null;
128
    private array $brandImageAttributes = [];
129
    private ?string $brandUrl = '/';
130
    private array $brandOptions = [];
131
    private string $screenReaderToggleText = 'Toggle navigation';
132
    protected string|Stringable $toggleLabel = '<span class="navbar-toggler-icon"></span>';
133
    private bool $renderInnerContainer = true;
134
    private array $innerContainerOptions = [];
135
    private array $options = [];
136
    private bool $encodeTags = false;
137
    private ?string $expandSize = self::EXPAND_LG;
138
    private Offcanvas|Collapse|null $widget = null;
139
    protected bool $renderToggle = false;
140
141 26
    public function getId(?string $suffix = '-navbar'): ?string
142
    {
143 26
        return $this->options['id'] ?? parent::getId($suffix);
144
    }
145
146 25
    protected function toggleComponent(): string
147
    {
148 25
        if ($this->widget instanceof Offcanvas) {
149 2
            return 'offcanvas';
150
        }
151
152 23
        return 'collapse';
153
    }
154
155
    /**
156
     * @throws \Yiisoft\Definitions\Exception\CircularReferenceException
157
     * @throws \Yiisoft\Definitions\Exception\InvalidConfigException
158
     * @throws \Yiisoft\Definitions\Exception\NotInstantiableException
159
     * @throws \Yiisoft\Factory\NotFoundException
160
     * @return string
161
     */
162 25
    public function begin(): string
163
    {
164
        /** Run Offcanvas|Collapse::begin before NavBar parent::begin for right stack order */
165 25
        if ($this->expandSize && $this->widget === null) {
166 21
            $collapseOptions = $this->collapseOptions;
167 21
            Html::addCssClass($collapseOptions, ['collapse' => 'collapse', 'widget' => 'navbar-collapse']);
168
169 21
            $this->widget = Collapse::widget()
170 21
                ->withOptions($collapseOptions)
0 ignored issues
show
Bug introduced by
The method withOptions() does not exist on Yiisoft\Widget\Widget. It seems like you code against a sub-type of Yiisoft\Widget\Widget such as Yiisoft\Yii\Bootstrap5\Collapse. ( Ignorable by Annotation )

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

170
                ->/** @scrutinizer ignore-call */ withOptions($collapseOptions)
Loading history...
171 21
                ->withBodyOptions([
172 21
                    'tag' => null,
173 21
                ]);
174
        }
175
176 25
        if ($this->widget) {
177 23
            [$tagName, $options, $encode] = $this->prepareToggleOptions();
178 23
            unset(
179 23
                $options['data-bs-target'],
180 23
                $options['data']['bs-target'],
181 23
                $options['aria-controls'],
182 23
                $options['aria']['controls'],
183 23
            );
184
185 23
            $widget = $this->widget
186 23
                ->withToggle(true)
187 23
                ->withToggleLabel($this->toggleLabel)
188 23
                ->withToggleOptions(
189 23
                    array_merge($options, [
190 23
                        'tag' => $tagName,
191 23
                        'encode' => $encode,
192 23
                    ])
193 23
                )
194 23
                ->begin();
195
        } else {
196 2
            $widget = '';
197
        }
198
199 25
        parent::begin();
200
201 25
        $options = $this->options;
202 25
        $options['id'] = $this->getId();
203 25
        $navTag = ArrayHelper::remove($options, 'tag', 'nav');
204 25
        $classNames = ['widget' => 'navbar'];
205
206 25
        if ($this->expandSize) {
207 22
            $classNames['size'] = $this->expandSize;
208
        }
209
210 25
        if ($this->theme) {
211 2
            $options['data-bs-theme'] = $this->theme;
212
213 2
            if ($this->theme === self::THEME_DARK) {
214 1
                $classNames['theme'] = 'navbar-dark';
215 1
            } elseif ($this->theme === self::THEME_LIGHT) {
216 1
                $classNames['theme'] = 'navbar-light';
217
            }
218
        }
219
220 25
        Html::addCssClass($options, $classNames);
221
222 25
        if (!isset($this->innerContainerOptions['class'])) {
223 23
            Html::addCssClass($this->innerContainerOptions, ['innerContainerOptions' => 'container']);
224
        }
225
226 25
        $htmlStart = Html::openTag($navTag, $options);
227
228 25
        if ($this->renderInnerContainer) {
229 24
            $htmlStart .= Html::openTag('div', $this->innerContainerOptions);
230
        }
231
232 25
        $htmlStart .= $this->renderBrand();
233
234 25
        if ($widget) {
235 23
            $htmlStart .= $widget;
236 2
        } elseif ($this->renderToggle) {
237 1
            $htmlStart .= $this->renderToggle();
238
        }
239
240 25
        return $htmlStart;
241
    }
242
243 25
    public function render(): string
244
    {
245 25
        $htmlRun = $this->widget ? $this->widget::end() : '';
246
247 25
        if ($this->renderInnerContainer) {
248 24
            $htmlRun .= Html::closeTag('div');
249
        }
250
251 25
        $htmlRun .= Html::closeTag($this->options['tag'] ?? 'nav');
252
253 25
        return $htmlRun;
254
    }
255
256
    /**
257
     * Set size before then content will be expanded
258
     */
259 9
    public function expandSize(?string $size): self
260
    {
261 9
        $new = clone $this;
262 9
        $new->expandSize = $size;
263
264 9
        return $new;
265
    }
266
267
    /**
268
     * The HTML attributes for the container tag. The following special options are recognized.
269
     *
270
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
271
     */
272 1
    public function collapseOptions(array $value): self
273
    {
274 1
        $new = clone $this;
275 1
        $new->collapseOptions = $value;
276
277 1
        return $new;
278
    }
279
280
    /**
281
     * Set/remove Offcanvas::widget or Collapse::widget
282
     */
283 5
    public function withWidget(Offcanvas|Collapse|null $widget): self
284
    {
285 5
        $new = clone $this;
286 5
        $new->widget = $widget;
287
288 5
        return $new;
289
    }
290
291
    /**
292
     * The text of the brand or empty if it's not used. Note that this is not HTML-encoded.
293
     *
294
     * @link https://getbootstrap.com/docs/5.0/components/navbar/#text
295
     */
296 7
    public function brandText(?string $value): self
297
    {
298 7
        $new = clone $this;
299 7
        $new->brandText = $value;
300
301 7
        return $new;
302
    }
303
304
    /**
305
     * Src of the brand image or empty if it's not used. Note that this param will override `$this->brandText` param.
306
     *
307
     * @link https://getbootstrap.com/docs/5.0/components/navbar/#image
308
     */
309 3
    public function brandImage(?string $value): self
310
    {
311 3
        $new = clone $this;
312 3
        $new->brandImage = $value;
313
314 3
        return $new;
315
    }
316
317
    /**
318
     * Set attributes for brandImage
319
     *
320
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
321
     */
322 1
    public function brandImageAttributes(array $attributes): self
323
    {
324 1
        $new = clone $this;
325 1
        $new->brandImageAttributes = $attributes;
326
327 1
        return $new;
328
    }
329
330
    /**
331
     * The URL for the brand's hyperlink tag and will be used for the "href" attribute of the brand link. Default value
332
     * is "/". You may set it to empty string if you want no link at all.
333
     *
334
     * @link https://getbootstrap.com/docs/5.0/components/navbar/#text
335
     */
336 6
    public function brandUrl(?string $value): self
337
    {
338 6
        $new = clone $this;
339 6
        $new->brandUrl = $value;
340
341 6
        return $new;
342
    }
343
344
    /**
345
     * The HTML attributes of the brand link.
346
     *
347
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
348
     */
349 1
    public function brandOptions(array $value): self
350
    {
351 1
        $new = clone $this;
352 1
        $new->brandOptions = $value;
353
354 1
        return $new;
355
    }
356
357
    /**
358
     * Text to show for screen readers for the button to toggle the navbar.
359
     */
360 1
    public function screenReaderToggleText(string $value): self
361
    {
362 1
        $new = clone $this;
363 1
        $new->screenReaderToggleText = $value;
364
365 1
        return $new;
366
    }
367
368
    /**
369
     * This for a 100% width navbar.
370
     */
371 1
    public function withoutRenderInnerContainer(): self
372
    {
373 1
        $new = clone $this;
374 1
        $new->renderInnerContainer = false;
375
376 1
        return $new;
377
    }
378
379
    /**
380
     * The HTML attributes of the inner container.
381
     *
382
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
383
     */
384 2
    public function innerContainerOptions(array $value): self
385
    {
386 2
        $new = clone $this;
387 2
        $new->innerContainerOptions = $value;
388
389 2
        return $new;
390
    }
391
392
    /**
393
     * The HTML attributes for the widget container tag. The following special options are recognized.
394
     *
395
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
396
     */
397 8
    public function options(array $value): self
398
    {
399 8
        $new = clone $this;
400 8
        $new->options = $value;
401
402 8
        return $new;
403
    }
404
405 25
    private function renderBrand(): string
406
    {
407 25
        if (empty($this->brandImage) && empty($this->brandText)) {
408 16
            return '';
409
        }
410
411 9
        $content = '';
412 9
        $options = $this->brandOptions;
413 9
        $encode = ArrayHelper::remove($options, 'encode', $this->encodeTags);
414
415 9
        Html::addCssClass($options, ['widget' => 'navbar-brand']);
416
417 9
        if (!empty($this->brandImage)) {
418 3
            $encode = false;
419 3
            $content = Html::img($this->brandImage)->addAttributes($this->brandImageAttributes);
420
        }
421
422 9
        if (!empty($this->brandText)) {
423 7
            $content .= $this->brandText;
424
        }
425
        /** @var string|Stringable $content */
426 9
        if (empty($this->brandUrl)) {
427 1
            $brand = Html::span($content, $options);
428
        } else {
429 8
            $brand = Html::a($content, $this->brandUrl, $options);
430
        }
431
432 9
        return $brand
433 9
            ->encode($encode)
434 9
            ->render();
435
    }
436
437 25
    protected function prepareToggleOptions(): array
438
    {
439 25
        [$tagName, $options] = parent::prepareToggleOptions();
440
441 25
        Html::addCssClass($options, ['widget' => 'navbar-toggler']);
442 25
        $options['aria-label'] = $this->screenReaderToggleText;
443
444 25
        return [$tagName, $options, $this->encodeTags];
445
    }
446
447
    /**
448
     * Renders collapsible toggle button.
449
     *
450
     * @return Tag the rendering toggle button.
451
     *
452
     * @link https://getbootstrap.com/docs/5.0/components/navbar/#toggler
453
     */
454 4
    public function renderToggle(): Tag
455
    {
456 4
        if ($this->widget) {
457 2
            return $this->widget->renderToggle();
458
        }
459
460 2
        return parent::renderToggle();
461
    }
462
}
463