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 ( 70e2ff...8a2e1b )
by Sebastian
02:30
created

Menu   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 475
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8
Metric Value
wmc 50
lcom 1
cbo 8
dl 0
loc 475
rs 8.6206

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A new() 0 4 1
A add() 0 10 2
A addIf() 0 8 2
A link() 0 4 1
A linkIf() 0 8 2
A html() 0 4 1
A htmlIf() 0 8 2
A each() 0 14 3
A registerFilter() 0 6 1
A applyFilter() 0 10 2
A applyToAll() 0 7 1
A prefixLinks() 0 6 1
A prepend() 0 6 1
A prependIf() 0 8 2
A append() 0 6 1
A appendIf() 0 8 2
A isActive() 0 10 3
A setActive() 0 12 3
C setActiveFromUrl() 0 49 10
A setActiveFromCallable() 0 21 3
A setActiveClass() 0 6 1
A render() 0 16 2
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\HtmlElement\HtmlElement;
7
use Spatie\Menu\Traits\HtmlAttributes;
8
use Spatie\Menu\Traits\ParentAttributes;
9
10
class Menu implements Item, Countable
11
{
12
    use HtmlAttributes, ParentAttributes;
13
14
    /** @var array */
15
    protected $items = [];
16
17
    /** @var string */
18
    protected $prepend = '';
19
20
    /** @var string */
21
    protected $append = '';
22
23
    /** @var array */
24
    protected $filters = [];
25
26
    /** @var string */
27
    protected $activeClass = 'active';
28
29
    /**
30
     * @param \Spatie\Menu\Item[] ...$items
31
     */
32
    protected function __construct(Item ...$items)
33
    {
34
        $this->items = $items;
35
36
        $this->initializeHtmlAttributes();
37
        $this->initializeParentAttributes();
38
    }
39
40
    /**
41
     * Create a new menu, optionally prefilled with items.
42
     *
43
     * @param array $items
44
     *
45
     * @return static
46
     */
47
    public static function new(array $items = [])
48
    {
49
        return new static(...array_values($items));
50
    }
51
52
    /**
53
     * Add an item to the menu. This also applies all registered filters to the
54
     * item.
55
     *
56
     * @param \Spatie\Menu\Item $item
57
     *
58
     * @return $this
59
     */
60
    public function add(Item $item)
61
    {
62
        foreach ($this->filters as $filter) {
63
            $this->applyFilter($filter, $item);
64
        }
65
66
        $this->items[] = $item;
67
68
        return $this;
69
    }
70
71
    /**
72
     * Add an item to the menu if a (non-strict) condition is met.
73
     *
74
     * @param bool $condition
75
     * @param \Spatie\Menu\Item $item
76
     *
77
     * @return $this
78
     */
79
    public function addIf($condition, Item $item)
80
    {
81
        if ($condition) {
82
            $this->add($item);
83
        }
84
85
        return $this;
86
    }
87
88
    /**
89
     * Shortcut function to add a plain link to the menu.
90
     *
91
     * @param string $url
92
     * @param string $text
93
     *
94
     * @return $this
95
     */
96
    public function link(string $url, string $text)
97
    {
98
        return $this->add(Link::to($url, $text));
99
    }
100
101
    /**
102
     * Add a link to the menu if a (non-strict) condition is met.
103
     *
104
     * @param bool $condition
105
     * @param string $url
106
     * @param string $text
107
     *
108
     * @return $this
109
     */
110
    public function linkIf($condition, string $url, string $text)
111
    {
112
        if ($condition) {
113
            $this->link($url, $text);
114
        }
115
116
        return $this;
117
    }
118
119
    /**
120
     * Shortcut function to add raw html to the menu.
121
     *
122
     * @param string $html
123
     *
124
     * @return $this
125
     */
126
    public function html(string $html)
127
    {
128
        return $this->add(Html::raw($html));
129
    }
130
131
    /**
132
     * Add a chunk of html if a (non-strict) condition is met.
133
     *
134
     * @param bool $condition
135
     * @param string $html
136
     *
137
     * @return $this
138
     */
139
    public function htmlIf($condition, string $html)
140
    {
141
        if ($condition) {
142
            $this->html($html);
143
        }
144
145
        return $this;
146
    }
147
148
    /**
149
     * Iterate over all the items and apply a callback. If you typehint the
150
     * item parameter in the callable, it wil only be applied to items of that
151
     * type.
152
     *
153
     * @param callable $callable
154
     *
155
     * @return $this
156
     */
157
    public function each(callable $callable)
158
    {
159
        $type = first_parameter_type($callable);
160
161
        foreach ($this->items as $item) {
162
            if (!item_matches_type($item, $type)) {
163
                continue;
164
            }
165
166
            $callable($item);
167
        }
168
169
        return $this;
170
    }
171
172
    /**
173
     * Register a filter to the menu. When an item is added, all filters will be
174
     * applied to the item. If you typehint the item parameter in the callable, it
175
     * will only be applied to items of that type.
176
     *
177
     * @param callable $callable
178
     *
179
     * @return $this
180
     */
181
    public function registerFilter(callable $callable)
182
    {
183
        $this->filters[] = $callable;
184
185
        return $this;
186
    }
187
188
    /**
189
     * Apply a filter to an item. Returns the result of the filter.
190
     *
191
     * @param callable $filter
192
     * @param \Spatie\Menu\Item $item
193
     */
194
    protected function applyFilter(callable $filter, Item $item)
195
    {
196
        $type = first_parameter_type($filter);
197
198
        if (!item_matches_type($item, $type)) {
199
            return;
200
        }
201
202
        $filter($item);
203
    }
204
205
    /**
206
     * Apply a callable to all existing items, and register it as a filter so it
207
     * will get applied to all new items too. If you typehint the item parameter
208
     * in the callable, it wil only be applied to items of that type.
209
     *
210
     * @param callable $callable
211
     *
212
     * @return $this
213
     */
214
    public function applyToAll(callable $callable)
215
    {
216
        $this->each($callable);
217
        $this->registerFilter($callable);
218
219
        return $this;
220
    }
221
222
    /**
223
     * Prefix all the links in the menu.
224
     *
225
     * @param string $prefix
226
     *
227
     * @return $this
228
     */
229
    public function prefixLinks(string $prefix)
230
    {
231
        return $this->applyToAll(function (Link $link) use ($prefix) {
232
            $link->prefix($prefix);
233
        });
234
    }
235
236
    /**
237
     * Prepend the menu with a string of html on render.
238
     *
239
     * @param string $prepend
240
     *
241
     * @return $this
242
     */
243
    public function prepend(string $prepend)
244
    {
245
        $this->prepend = $prepend;
246
247
        return $this;
248
    }
249
250
    /**
251
     * Prepend the menu with a string of html on render if a certain condition is
252
     * met.
253
     *
254
     * @param bool $condition
255
     * @param string $prepend
256
     *
257
     * @return $this
258
     */
259
    public function prependIf(bool $condition, string $prepend)
260
    {
261
        if ($condition) {
262
            return $this->prepend($prepend);
263
        }
264
265
        return $this;
266
    }
267
268
    /**
269
     * Append a string of html to the menu on render.
270
     *
271
     * @param string $append
272
     *
273
     * @return $this
274
     */
275
    public function append(string $append)
276
    {
277
        $this->append = $append;
278
279
        return $this;
280
    }
281
282
    /**
283
     * Append the menu with a string of html on render if a certain condition is
284
     * met.
285
     *
286
     * @param bool $condition
287
     * @param string $append
288
     *
289
     * @return static
290
     */
291
    public function appendIf(bool $condition, string $append)
292
    {
293
        if ($condition) {
294
            return $this->append($append);
295
        }
296
297
        return $this;
298
    }
299
300
    /**
301
     * Determine whether the menu is active.
302
     *
303
     * @return bool
304
     */
305
    public function isActive() : bool
306
    {
307
        foreach ($this->items as $item) {
308
            if ($item->isActive()) {
309
                return true;
310
            }
311
        }
312
313
        return false;
314
    }
315
316
    /**
317
     * Set multiple items in the menu as active based on a callable that filters
318
     * through items. If you typehint the item parameter in the callable, it will
319
     * only be applied to items of that type.
320
     *
321
     * @param callable|string $urlOrCallable
322
     * @param string $root
323
     *
324
     * @return $this
325
     */
326
    public function setActive($urlOrCallable, string $root = '/')
327
    {
328
        if (is_string($urlOrCallable)) {
329
            return $this->setActiveFromUrl($urlOrCallable, $root);
330
        }
331
332
        if (is_callable($urlOrCallable)) {
333
            return $this->setActiveFromCallable($urlOrCallable);
334
        }
335
336
        throw new \InvalidArgumentException('`setActive` requires a pattern or a callable');
337
    }
338
339
    /**
340
     * Set all relevant children active based on the current request's URL.
341
     *
342
     * /, /about, /contact => request to /about will set the about link active.
343
     *
344
     * /en, /en/about, /en/contact => request to /en won't set /en active if the
345
     *                                request root is set to /en.
346
     *
347
     * @param string $url The current request url.
348
     * @param string $root If the link's URL is an exact match with the request
349
     *                     root, the link won't be set active. This behavior is
350
     *                     to avoid having home links active on every request.
351
     *
352
     * @return $this
353
     */
354
    public function setActiveFromUrl(string $url, string $root = '/')
355
    {
356
        $this->applyToAll(function (Menu $menu) use ($url, $root) {
357
            $menu->setActiveFromUrl($url, $root);
358
        });
359
360
        $requestUrl = url_parts($url);
361
        $requestRoot = strip_trailing_slashes($root, '/');
362
363
        $this->applyToAll(function ($item) use ($requestUrl, $requestRoot) {
364
365
            // Not using a magic typehint since we need to do two instance checks
366
            if (! $item instanceof HasUrl || ! $item instanceof Activatable) {
367
                return;
368
            }
369
370
            $url = url_parts($item->getUrl());
371
372
            // If the menu item is on a different host it can't be active.
373
            if ($url['host'] !== '' && $url['host'] !== $requestUrl['host']) {
374
                return;
375
            }
376
377
            // If the request url or the link url is on the root, only set exact matches active.
378
            if (
379
                $requestUrl['path'] === $requestRoot ||
380
                $url['path'] === $requestRoot
381
            ) {
382
                if ($url['path'] === $requestUrl['path']) {
383
                    $item->setActive();
384
                }
385
386
                return;
387
            }
388
389
            // If the request path is empty and it isn't the root, there's most likely a
390
            // configuration error, and the item isn't active.
391
            if (empty($url['path'])) {
392
                return;
393
            }
394
395
            // The menu item is active if it's path starts with the request path.
396
            if (strpos($url['path'], $requestUrl['path']) === 0) {
397
                $item->setActive();
398
            };
399
        });
400
401
        return $this;
402
    }
403
404
    /**
405
     * @param callable $callable
406
     *
407
     * @return $this
408
     */
409
    public function setActiveFromCallable(callable $callable)
410
    {
411
        $this->applyToAll(function (Menu $menu) use ($callable) {
412
            $menu->setActiveFromCallable($callable);
413
        });
414
415
        $type = first_parameter_type($callable);
416
417
        $this->applyToAll(function (Activatable $item) use ($callable, $type) {
418
419
            if (!item_matches_type($item, $type)) {
420
                return;
421
            }
422
423
            if ($callable($item)) {
424
                $item->setActive();
425
            }
426
        });
427
428
        return $this;
429
    }
430
431
    /**
432
     * Set the class name that will be used on active items for this menu.
433
     *
434
     * @param string $class
435
     *
436
     * @return $this
437
     */
438
    public function setActiveClass(string $class)
439
    {
440
        $this->activeClass = $class;
441
442
        return $this;
443
    }
444
445
    /**
446
     * Render the menu.
447
     *
448
     * @return string
449
     */
450
    public function render() : string
451
    {
452
        $contents = HtmlElement::render(
453
            'ul',
454
            $this->htmlAttributes->toArray(),
455
            array_map(function (Item $item) {
456
                return HtmlElement::render(
457
                    $item->isActive() ? "li.{$this->activeClass}" : 'li',
458
                    $item->getParentAttributes(),
459
                    $item->render()
460
                );
461
            }, $this->items)
462
        );
463
464
        return "{$this->prepend}{$contents}{$this->append}";
465
    }
466
467
    /**
468
     * The amount of items in the menu.
469
     *
470
     * @return int
471
     */
472
    public function count() : int
473
    {
474
        return count($this->items);
475
    }
476
477
    /**
478
     * @return string
479
     */
480
    public function __toString() : string
481
    {
482
        return $this->render();
483
    }
484
}
485