GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( a1207e...fad361 )
by Sebastian
02:01
created

Menu   F

Complexity

Total Complexity 72

Size/Duplication

Total Lines 703
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 0
Metric Value
wmc 72
lcom 1
cbo 13
dl 0
loc 703
rs 3.8095
c 0
b 0
f 0

42 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A new() 0 4 1
A build() 0 4 2
A fill() 0 10 3
A add() 0 10 2
A addIf() 0 8 2
A link() 0 4 1
A empty() 0 4 1
A linkIf() 0 8 2
A html() 0 4 1
A htmlIf() 0 8 2
A submenu() 0 9 1
A submenuIf() 0 8 2
A parseSubmenuArgs() 0 8 2
A createSubmenuMenu() 0 10 2
A createSubmenuHeader() 0 8 2
A each() 0 14 3
A registerFilter() 0 6 1
A applyFilter() 0 10 2
A applyToAll() 0 7 1
A wrap() 0 6 1
A isActive() 0 10 3
A setActive() 0 12 3
A setActiveFromUrl() 0 12 1
A setActiveFromCallable() 0 20 3
A setActiveClass() 0 6 1
A addItemClass() 0 8 1
A setItemAttribute() 0 8 1
A addItemParentClass() 0 8 1
A setItemParentAttribute() 0 8 1
A setWrapperTag() 0 6 1
A withoutWrapperTag() 0 6 1
A setParentTag() 0 6 1
A withoutParentTag() 0 6 1
A setActiveClassOnLink() 0 6 1
A setActiveClassOnParent() 0 6 1
A if() 0 4 2
A blueprint() 0 9 1
A render() 0 18 4
C renderItem() 0 24 7
A count() 0 4 1
A __toString() 0 4 1

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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
namespace Spatie\Menu;
4
5
use Countable;
6
use Spatie\Menu\Html\Tag;
7
use Spatie\Menu\Html\Attributes;
8
use Spatie\Menu\Helpers\Reflection;
9
use Spatie\Menu\Traits\Conditions as ConditionsTrait;
10
use Spatie\Menu\Traits\HasTextAttributes as HasAttributesTrait;
11
use Spatie\Menu\Traits\HasHtmlAttributes as HasHtmlAttributesTrait;
12
use Spatie\Menu\Traits\HasParentAttributes as HasParentAttributesTrait;
13
14
class Menu implements Item, Countable, HasHtmlAttributes, HasParentAttributes
15
{
16
    use HasHtmlAttributesTrait, HasParentAttributesTrait, ConditionsTrait, HasAttributesTrait;
17
18
    /** @var array */
19
    protected $items = [];
20
21
    /** @var array */
22
    protected $filters = [];
23
24
    /** @var string */
25
    protected $prepend, $append = '';
0 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
26
27
    /** @var array */
28
    protected $wrap = [];
29
30
    /** @var string */
31
    protected $activeClass = 'active';
32
33
    /** @var string */
34
    protected $wrapperTagName = 'ul';
35
36
    /** @var bool */
37
    protected $parentTagName = 'li';
38
39
    /** @var bool */
40
    protected $activeClassOnParent = true;
41
42
    /** @var bool */
43
    protected $activeClassOnLink = false;
44
45
    /** @var \Spatie\Menu\Html\Attributes */
46
    protected $htmlAttributes, $parentAttributes;
0 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
47
48
    protected function __construct(Item ...$items)
49
    {
50
        $this->items = $items;
51
52
        $this->htmlAttributes = new Attributes();
53
        $this->parentAttributes = new Attributes();
54
    }
55
56
    /**
57
     * Create a new menu, optionally prefilled with items.
58
     *
59
     * @param array $items
60
     *
61
     * @return static
62
     */
63
    public static function new($items = [])
64
    {
65
        return new static(...array_values($items));
66
    }
67
68
    /**
69
     * Build a new menu from an array. The callback receives a menu instance as
70
     * the accumulator, the array item as the second parameter, and the item's
71
     * key as the third.
72
     *
73
     * @param array|\Iterator $items
74
     * @param callable $callback
75
     * @param \Spatie\Menu\Menu|null $initial
76
     *
77
     * @return static
78
     */
79
    public static function build($items, callable $callback, self $initial = null)
80
    {
81
        return ($initial ?: static::new())->fill($items, $callback);
82
    }
83
84
    /**
85
     * Fill a menu from an array. The callback receives a menu instance as
86
     * the accumulator, the array item as the second parameter, and the item's
87
     * key as the third.
88
     *
89
     * @param array|\Iterator $items
90
     * @param callable $callback
91
     *
92
     * @return static
93
     */
94
    public function fill($items, callable $callback)
95
    {
96
        $menu = $this;
97
98
        foreach ($items as $key => $item) {
99
            $menu = $callback($menu, $item, $key) ?: $menu;
100
        }
101
102
        return $menu;
103
    }
104
105
    /**
106
     * Add an item to the menu. This also applies all registered filters to the
107
     * item.
108
     *
109
     * @param \Spatie\Menu\Item $item
110
     *
111
     * @return $this
112
     */
113
    public function add(Item $item)
114
    {
115
        foreach ($this->filters as $filter) {
116
            $this->applyFilter($filter, $item);
117
        }
118
119
        $this->items[] = $item;
120
121
        return $this;
122
    }
123
124
    /**
125
     * Add an item to the menu if a (non-strict) condition is met.
126
     *
127
     * @param bool              $condition
128
     * @param \Spatie\Menu\Item $item
129
     *
130
     * @return $this
131
     */
132
    public function addIf($condition, Item $item)
133
    {
134
        if ($this->resolveCondition($condition)) {
135
            $this->add($item);
136
        }
137
138
        return $this;
139
    }
140
141
    /**
142
     * Shortcut function to add a plain link to the menu.
143
     *
144
     * @param string $url
145
     * @param string $text
146
     *
147
     * @return $this
148
     */
149
    public function link(string $url, string $text)
150
    {
151
        return $this->add(Link::to($url, $text));
152
    }
153
154
    /**
155
     * Shortcut function to add an empty item to the menu.
156
     *
157
     * @return $this
158
     */
159
    public function empty()
160
    {
161
        return $this->add(Html::empty());
162
    }
163
164
    /**
165
     * Add a link to the menu if a (non-strict) condition is met.
166
     *
167
     * @param bool   $condition
168
     * @param string $url
169
     * @param string $text
170
     *
171
     * @return $this
172
     */
173
    public function linkIf($condition, string $url, string $text)
174
    {
175
        if ($this->resolveCondition($condition)) {
176
            $this->link($url, $text);
177
        }
178
179
        return $this;
180
    }
181
182
    /**
183
     * Shortcut function to add raw html to the menu.
184
     *
185
     * @param string $html
186
     * @param array  $parentAttributes
187
     *
188
     * @return $this
189
     */
190
    public function html(string $html, array $parentAttributes = [])
191
    {
192
        return $this->add(Html::raw($html)->setParentAttributes($parentAttributes));
193
    }
194
195
    /**
196
     * Add a chunk of html if a (non-strict) condition is met.
197
     *
198
     * @param bool   $condition
199
     * @param string $html
200
     * @param array  $parentAttributes
201
     *
202
     * @return $this
203
     */
204
    public function htmlIf($condition, string $html, array $parentAttributes = [])
205
    {
206
        if ($this->resolveCondition($condition)) {
207
            $this->html($html, $parentAttributes);
208
        }
209
210
        return $this;
211
    }
212
213
    /**
214
     * @param callable|\Spatie\Menu\Menu|\Spatie\Menu\Item $header
215
     * @param callable|\Spatie\Menu\Menu|null $menu
216
     *
217
     * @return $this
218
     */
219
    public function submenu($header, $menu = null)
0 ignored issues
show
Unused Code introduced by
The parameter $header 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...
Unused Code introduced by
The parameter $menu 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...
220
    {
221
        list($header, $menu) = $this->parseSubmenuArgs(func_get_args());
222
223
        $menu = $this->createSubmenuMenu($menu);
224
        $header = $this->createSubmenuHeader($header);
225
226
        return $this->add($menu->prependIf($header, $header));
0 ignored issues
show
Documentation introduced by
$header is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
227
    }
228
229
    /**
230
     * @param bool $condition
231
     * @param callable|\Spatie\Menu\Menu|\Spatie\Menu\Item $header
232
     * @param callable|\Spatie\Menu\Menu|null $menu
233
     *
234
     * @return $this
235
     */
236
    public function submenuIf($condition, $header, $menu = null)
237
    {
238
        if ($condition) {
239
            $this->submenu($header, $menu);
240
        }
241
242
        return $this;
243
    }
244
245
    protected function parseSubmenuArgs($args): array
246
    {
247
        if (count($args) === 1) {
248
            return ['', $args[0]];
249
        }
250
251
        return [$args[0], $args[1]];
252
    }
253
254
    /**
255
     * @param \Spatie\Menu\Menu|callable $menu
256
     *
257
     * @return \Spatie\Menu\Menu
258
     */
259
    protected function createSubmenuMenu($menu): self
260
    {
261
        if (is_callable($menu)) {
262
            $transformer = $menu;
263
            $menu = $this->blueprint();
264
            $transformer($menu);
265
        }
266
267
        return $menu;
268
    }
269
270
    /**
271
     * @param \Spatie\Menu\Item|string $header
272
     *
273
     * @return string
274
     */
275
    protected function createSubmenuHeader($header): string
276
    {
277
        if ($header instanceof Item) {
278
            $header = $header->render();
279
        }
280
281
        return $header;
282
    }
283
284
    /**
285
     * Iterate over all the items and apply a callback. If you typehint the
286
     * item parameter in the callable, it wil only be applied to items of that
287
     * type.
288
     *
289
     * @param callable $callable
290
     *
291
     * @return $this
292
     */
293
    public function each(callable $callable)
294
    {
295
        $type = Reflection::firstParameterType($callable);
296
297
        foreach ($this->items as $item) {
298
            if (! Reflection::itemMatchesType($item, $type)) {
299
                continue;
300
            }
301
302
            $callable($item);
303
        }
304
305
        return $this;
306
    }
307
308
    /**
309
     * Register a filter to the menu. When an item is added, all filters will be
310
     * applied to the item. If you typehint the item parameter in the callable, it
311
     * will only be applied to items of that type.
312
     *
313
     * @param callable $callable
314
     *
315
     * @return $this
316
     */
317
    public function registerFilter(callable $callable)
318
    {
319
        $this->filters[] = $callable;
320
321
        return $this;
322
    }
323
324
    /**
325
     * Apply a filter to an item. Returns the result of the filter.
326
     *
327
     * @param callable          $filter
328
     * @param \Spatie\Menu\Item $item
329
     */
330
    protected function applyFilter(callable $filter, Item $item)
331
    {
332
        $type = Reflection::firstParameterType($filter);
333
334
        if (! Reflection::itemMatchesType($item, $type)) {
335
            return;
336
        }
337
338
        $filter($item);
339
    }
340
341
    /**
342
     * Apply a callable to all existing items, and register it as a filter so it
343
     * will get applied to all new items too. If you typehint the item parameter
344
     * in the callable, it wil only be applied to items of that type.
345
     *
346
     * @param callable $callable
347
     *
348
     * @return $this
349
     */
350
    public function applyToAll(callable $callable)
351
    {
352
        $this->each($callable);
353
        $this->registerFilter($callable);
354
355
        return $this;
356
    }
357
358
    /**
359
     * Wrap the entire menu in an html element. This is another level of
360
     * wrapping above the `wrapperTag`.
361
     *
362
     * @param string $element
363
     * @param array $attributes
364
     *
365
     * @return $this
366
     */
367
    public function wrap(string $element, $attributes = [])
368
    {
369
        $this->wrap = [$element, $attributes];
370
371
        return $this;
372
    }
373
374
    /**
375
     * Determine whether the menu is active.
376
     *
377
     * @return bool
378
     */
379
    public function isActive(): bool
380
    {
381
        foreach ($this->items as $item) {
382
            if ($item->isActive()) {
383
                return true;
384
            }
385
        }
386
387
        return false;
388
    }
389
390
    /**
391
     * Set multiple items in the menu as active based on a callable that filters
392
     * through items. If you typehint the item parameter in the callable, it will
393
     * only be applied to items of that type.
394
     *
395
     * @param callable|string $urlOrCallable
396
     * @param string          $root
397
     *
398
     * @return $this
399
     */
400
    public function setActive($urlOrCallable, string $root = '/')
401
    {
402
        if (is_string($urlOrCallable)) {
403
            return $this->setActiveFromUrl($urlOrCallable, $root);
404
        }
405
406
        if (is_callable($urlOrCallable)) {
407
            return $this->setActiveFromCallable($urlOrCallable);
408
        }
409
410
        throw new \InvalidArgumentException('`setActive` requires a pattern or a callable');
411
    }
412
413
    /**
414
     * Set all relevant children active based on the current request's URL.
415
     *
416
     * /, /about, /contact => request to /about will set the about link active.
417
     *
418
     * /en, /en/about, /en/contact => request to /en won't set /en active if the
419
     *                                request root is set to /en.
420
     *
421
     * @param string $url  The current request url.
422
     * @param string $root If the link's URL is an exact match with the request
423
     *                     root, the link won't be set active. This behavior is
424
     *                     to avoid having home links active on every request.
425
     *
426
     * @return $this
427
     */
428
    public function setActiveFromUrl(string $url, string $root = '/')
429
    {
430
        $this->applyToAll(function (Menu $menu) use ($url, $root) {
431
            $menu->setActiveFromUrl($url, $root);
432
        });
433
434
        $this->applyToAll(function (Activatable $item) use ($url, $root) {
435
            $item->determineActiveForUrl($url, $root);
436
        });
437
438
        return $this;
439
    }
440
441
    /**
442
     * @param callable $callable
443
     *
444
     * @return $this
445
     */
446
    public function setActiveFromCallable(callable $callable)
447
    {
448
        $this->applyToAll(function (Menu $menu) use ($callable) {
449
            $menu->setActiveFromCallable($callable);
450
        });
451
452
        $type = Reflection::firstParameterType($callable);
453
454
        $this->applyToAll(function (Activatable $item) use ($callable, $type) {
455
            if (! Reflection::itemMatchesType($item, $type)) {
0 ignored issues
show
Documentation introduced by
$item is of type object<Spatie\Menu\Activatable>, but the function expects a object<Spatie\Menu\Item>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
456
                return;
457
            }
458
459
            if ($callable($item)) {
460
                $item->setActive();
461
            }
462
        });
463
464
        return $this;
465
    }
466
467
    /**
468
     * Set the class name that will be used on active items for this menu.
469
     *
470
     * @param string $class
471
     *
472
     * @return $this
473
     */
474
    public function setActiveClass(string $class)
475
    {
476
        $this->activeClass = $class;
477
478
        return $this;
479
    }
480
481
    /**
482
     * Add a class to all items in the menu.
483
     *
484
     * @param string $class
485
     *
486
     * @return $this
487
     */
488
    public function addItemClass(string $class)
489
    {
490
        $this->applyToAll(function (HasHtmlAttributes $link) use ($class) {
491
            $link->addClass($class);
492
        });
493
494
        return $this;
495
    }
496
497
    /**
498
     * Set an attribute on all items in the menu.
499
     *
500
     * @param string $attribute
501
     * @param string $value
502
     *
503
     * @return $this
504
     */
505
    public function setItemAttribute(string $attribute, string $value = '')
506
    {
507
        $this->applyToAll(function (HasHtmlAttributes $link) use ($attribute, $value) {
508
            $link->setAttribute($attribute, $value);
509
        });
510
511
        return $this;
512
    }
513
514
    /**
515
     * Add a parent class to all items in the menu.
516
     *
517
     * @param string $class
518
     *
519
     * @return $this
520
     */
521
    public function addItemParentClass(string $class)
522
    {
523
        $this->applyToAll(function (HasParentAttributes $item) use ($class) {
524
            $item->addParentClass($class);
525
        });
526
527
        return $this;
528
    }
529
530
    /**
531
     * Add a parent attribute to all items in the menu.
532
     *
533
     * @param string $attribute
534
     * @param string $value
535
     *
536
     * @return $this
537
     */
538
    public function setItemParentAttribute(string $attribute, string $value = '')
539
    {
540
        $this->applyToAll(function (HasParentAttributes $item) use ($attribute, $value) {
541
            $item->setParentAttribute($attribute, $value);
542
        });
543
544
        return $this;
545
    }
546
547
    /**
548
     * Set tag for items wrapper.
549
     *
550
     * @param string|null $wrapperTagName
551
     * @return $this
552
     */
553
    public function setWrapperTag($wrapperTagName = null)
554
    {
555
        $this->wrapperTagName = $wrapperTagName;
556
557
        return $this;
558
    }
559
560
    /**
561
     * Set tag for items wrapper.
562
     *
563
     * @param string|null $wrapperTagName
0 ignored issues
show
Bug introduced by
There is no parameter named $wrapperTagName. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
564
     * @return $this
565
     */
566
    public function withoutWrapperTag()
567
    {
568
        $this->wrapperTagName = null;
569
570
        return $this;
571
    }
572
573
    /**
574
     * Set the parent tag name.
575
     *
576
     * @param string|null $parentTagName
577
     * @return $this
578
     */
579
    public function setParentTag($parentTagName = null)
580
    {
581
        $this->parentTagName = $parentTagName;
0 ignored issues
show
Documentation Bug introduced by
It seems like $parentTagName can also be of type string. However, the property $parentTagName is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
582
583
        return $this;
584
    }
585
586
    /**
587
     * Render items without a parent tag.
588
     *
589
     * @return $this
590
     */
591
    public function withoutParentTag()
592
    {
593
        $this->parentTagName = null;
594
595
        return $this;
596
    }
597
598
    /**
599
     * Set whether active class should (also) be on link.
600
     *
601
     * @param $activeClassOnLink
602
     * @return $this
603
     */
604
    public function setActiveClassOnLink(bool $activeClassOnLink = true)
605
    {
606
        $this->activeClassOnLink = $activeClassOnLink;
607
608
        return $this;
609
    }
610
611
    /**
612
     * Set whether active class should (also) be on parent.
613
     *
614
     * @param $activeClassOnParent
615
     * @return $this
616
     */
617
    public function setActiveClassOnParent(bool $activeClassOnParent = true)
618
    {
619
        $this->activeClassOnParent = $activeClassOnParent;
620
621
        return $this;
622
    }
623
624
    /**
625
     * @param bool $condition
626
     * @param callable $callable
627
     *
628
     * @return $this
629
     */
630
    public function if(bool $condition, callable $callable)
0 ignored issues
show
Coding Style introduced by
Possible parse error: non-abstract method defined as abstract
Loading history...
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
631
    {
632
        return $condition ? $callable($this) : $this;
633
    }
634
635
    /**
636
     * Create a empty blueprint of the menu (copies `filters` and `activeClass`).
637
     *
638
     * @return static
639
     */
640
    public function blueprint()
641
    {
642
        $clone = new static();
643
644
        $clone->filters = $this->filters;
645
        $clone->activeClass = $this->activeClass;
646
647
        return $clone;
648
    }
649
650
    /**
651
     * Render the menu.
652
     *
653
     * @return string
654
     */
655
    public function render(): string
656
    {
657
        $tag = $this->wrapperTagName
658
            ? new Tag($this->wrapperTagName, $this->htmlAttributes)
659
            : null;
660
661
        $contents = array_map([$this, 'renderItem'], $this->items);
662
663
        $wrappedContents = $tag ? $tag->withContents($contents) : implode('', $contents);
664
665
        $menu = $this->prepend.$wrappedContents.$this->append;
666
667
        if (! empty($this->wrap)) {
668
            return Tag::make($this->wrap[0], new Attributes($this->wrap[1]))->withContents($menu);
669
        }
670
671
        return $menu;
672
    }
673
674
    protected function renderItem(Item $item): string
675
    {
676
        $attributes = new Attributes();
677
678
        if ($item->isActive()) {
679
            if ($this->activeClassOnParent) {
680
                $attributes->addClass($this->activeClass);
681
            }
682
683
            if ($this->activeClassOnLink && $item instanceof HasHtmlAttributes) {
684
                $item->addClass($this->activeClass);
685
            }
686
        }
687
688
        if ($item instanceof HasParentAttributes) {
689
            $attributes->setAttributes($item->parentAttributes());
690
        }
691
692
        if (! $this->parentTagName) {
693
            return $item->render();
694
        }
695
696
        return Tag::make($this->parentTagName, $attributes)->withContents($item->render());
0 ignored issues
show
Documentation introduced by
$this->parentTagName is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
697
    }
698
699
    /**
700
     * The amount of items in the menu.
701
     *
702
     * @return int
703
     */
704
    public function count(): int
705
    {
706
        return count($this->items);
707
    }
708
709
    /**
710
     * @return string
711
     */
712
    public function __toString(): string
713
    {
714
        return $this->render();
715
    }
716
}
717