Passed
Pull Request — master (#57)
by Kyle
08:10
created

MenuItem   F

Complexity

Total Complexity 72

Size/Duplication

Total Lines 534
Duplicated Lines 0 %

Test Coverage

Coverage 60.42%

Importance

Changes 0
Metric Value
eloc 131
dl 0
loc 534
ccs 87
cts 144
cp 0.6042
rs 2.64
c 0
b 0
f 0
wmc 72

36 Methods

Rating   Name   Duplication   Size   Complexity  
A getIcon() 0 10 4
A getProperties() 0 3 1
A hasChilds() 0 3 1
A hasActiveOnChild() 0 7 3
B getActiveStateFromChildren() 0 20 9
A hasSubMenu() 0 3 1
A getAttributes() 0 18 5
A isDivider() 0 3 1
A isHeader() 0 3 1
A getActiveStateFromChilds() 0 3 1
A is() 0 3 1
A getRequest() 0 3 1
A hasRoute() 0 3 1
A inactive() 0 16 5
A getChildren() 0 7 2
A route() 0 15 2
A child() 0 5 1
A getActiveStateFromRoute() 0 3 1
A getBadge() 0 8 2
A getChilds() 0 3 1
A addBadge() 0 11 1
A getActiveStateFromUrl() 0 3 1
A addDivider() 0 7 1
A getUrl() 0 11 3
A addHeader() 0 10 1
A getActiveAttribute() 0 3 1
B isActive() 0 24 7
A dropdown() 0 20 2
A fill() 0 5 3
A hasBadge() 0 3 1
A url() 0 13 2
A divider() 0 3 1
A __construct() 0 4 1
A order() 0 5 1
A getInactiveAttribute() 0 3 1
A header() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like MenuItem 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 MenuItem, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace KyleMassacre\Menus;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Support\Facades\Request;
7
use KyleMassacre\Menus\Contracts\MenuItemContract;
8
use KyleMassacre\Menus\Traits\CanHide;
9
use Spatie\Html\Facades\Html as HTML;
10
11
/**
12
 * @property string url
13
 * @property string route
14
 * @property string title
15
 * @property string name
16
 * @property string icon
17
 * @property int parent
18
 * @property array attributes
19
 * @property bool active
20
 * @property int order
21
 * @property array badge
22
 */
23
class MenuItem extends MenuItemContract
24
{
25
    use CanHide;
26
27
    /**
28
     * Constructor.
29
     *
30
     * @param array $properties
31
     */
32
    public function __construct(array $properties = array())
33
    {
34
        $this->properties = $properties;
35
        $this->fill($properties);
36
    }
37
38
    /**
39
     * Fill the attributes.
40
     *
41
     * @param array $attributes
42
     */
43
    public function fill(array $attributes): void
44
    {
45
        foreach ($attributes as $key => $value) {
46
            if (in_array($key, $this->fillable)) {
47
                $this->{$key} = $value;
48
            }
49
        }
50
    }
51
52
    /**
53
     * Create new menu child item using array.
54
     *
55
     * @param array $attributes
56
     *
57
     * @return self
58
     */
59
    public function child(array $attributes): self
60
    {
61
        $this->childs[] = static::make($attributes);
62
63
        return $this;
64
    }
65
66
    /**
67
     * Register new child menu with dropdown.
68
     *
69 24
     * @param $title
70
     * @param \Closure $callback
71 24
     * @param int $order
72 24
     * @param array $attributes
73 24
     * @return $this
74
     */
75
    public function dropdown(string $title, \Closure $callback, int $order = 0, array $attributes = array()): static
76
    {
77
        $properties = compact('title', 'order', 'attributes');
78
79
        if (func_num_args() === 3) {
80
            $arguments = func_get_args();
81
82 24
            $title = Arr::get($arguments, 0);
83
            $attributes = Arr::get($arguments, 2);
84 24
85 24
            $properties = compact('title', 'attributes');
86 1
        }
87
88 1
        $child = static::make($properties);
89
90 1
        call_user_func($callback, $child);
91
92
        $this->childs[] = $child;
93 23
94
        return $child;
95
    }
96
97
    /**
98
     * Create new menu item and set the action to route.
99
     *
100
     * @param $route
101
     * @param $title
102
     * @param array $parameters
103
     * @param array $attributes
104
     *
105
     * @return MenuItemContract
106
     */
107
    public function route($route, $title, array $parameters = array(), $order = 0, array $attributes = array()): static
108
    {
109
        if (func_num_args() === 4) {
110
            $arguments = func_get_args();
111
112
            return $this->add([
113
                'route' => [Arr::get($arguments, 0), Arr::get($arguments, 2)],
114
                'title' => Arr::get($arguments, 1),
115 24
                'attributes' => Arr::get($arguments, 3),
116
            ]);
117 24
        }
118
119 24
        $route = array($route, $parameters);
120
121
        return $this->add(compact('route', 'title', 'order', 'attributes'));
122
    }
123
124
    /**
125
     * Create new menu item  and set the action to url.
126
     *
127 24
     * @param $url
128
     * @param $title
129 24
     * @param array $attributes
130 23
     *
131 23
     * @return static
132
     */
133
    public function url($url, $title, $order = 0, array $attributes = array()): static
134 24
    {
135
        if (func_num_args() === 3) {
136
            $arguments = func_get_args();
137
138
            return $this->add([
139
                'url' => Arr::get($arguments, 0),
140
                'title' => Arr::get($arguments, 1),
141
                'attributes' => Arr::get($arguments, 2),
142
            ]);
143 2
        }
144
145 2
        return $this->add(compact('url', 'title', 'order', 'attributes'));
146
    }
147 2
148
    /**
149
     * Add new divider.
150
     *
151
     * @param int $order
152
     *
153
     * @return self
154
     */
155
    public function addDivider($order = null): static
156
    {
157
        $item = static::make(array('name' => 'divider', 'order' => $order));
158 9
159
        $this->childs[] = $item;
160 9
161
        return $item;
162 9
    }
163
164
    /**
165
     * Alias method instead "addDivider".
166
     *
167
     * @param int $order
168
     *
169
     * @return MenuItem
170
     */
171 9
    public function divider($order = null): static
172
    {
173 9
        return $this->addDivider($order);
174
    }
175 9
176
    /**
177 9
     * Add dropdown header.
178
     *
179
     * @param $title
180
     *
181
     * @return $this
182
     */
183
    public function addHeader($title): static
184
    {
185
        $item = static::make(array(
186
            'name' => 'header',
187
            'title' => $title,
188
        ));
189
190 3
        $this->childs[] = $item;
191
192 3
        return $item;
193
    }
194
195
    /**
196
     * Same with "addHeader" method.
197
     *
198
     * @param $title
199
     *
200
     * @return $this
201
     */
202 3
    public function header($title): static
203
    {
204 3
        return $this->addHeader($title);
205
    }
206
207
    public function addBadge(string $type, $text): static
208
    {
209
        $properties = array(
210
            'type' => $type,
211
            'text' => $text,
212
            'name' => 'badge',
213
        );
214
        $item = static::make($properties);
215
        $this->badge = $properties;
216 6
217
        return $item;
218 6
    }
219
220
    /**
221
     * @deprecated See `getChildren`
222
     * @return array
223
     */
224
    public function getChilds(): array
225
    {
226
        return $this->getChildren();
227
    }
228 6
229
    /**
230
     * Get childs.
231
     *
232
     * @return array
233
     */
234
    public function getChildren(): array
235
    {
236
        if (config('menus.ordering')) {
237
            return collect($this->childs)->sortBy('order')->all();
0 ignored issues
show
Bug introduced by
$this->childs of type array is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

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

237
            return collect(/** @scrutinizer ignore-type */ $this->childs)->sortBy('order')->all();
Loading history...
238 9
        }
239
240 9
        return $this->childs;
241
    }
242 9
243
    /**
244 9
     * Get url.
245
     *
246
     * @return string
247
     */
248
    public function getUrl(): string
249
    {
250
        if ($this->route !== null) {
251
            return route($this->route[0], $this->route[1]);
252
        }
253
254 1
        if (empty($this->url)) {
255
            return url('/#');
256 1
        }
257
258 1
        return url($this->url);
259
    }
260 1
261
    /**
262
     * Get request url.
263
     *
264
     * @return string
265
     */
266
    public function getRequest(): string
267
    {
268
        return ltrim(str_replace(url('/'), '', $this->getUrl()), '/');
269
    }
270 1
271
    /**
272 1
     * @return string
273
     */
274
    public function getBadge(): string
275
    {
276
        if ($this->hasBadge()) {
277
            extract($this->badge);
278
279
            return '<span class="' . $type . '">' . $text . '</span>';
280
        }
281
        return '';
282 2
    }
283
284 2
    /**
285 2
     * Get icon.
286 2
     *
287
     * @param string|null $default
288
     *
289 2
     * @return string|null
290
     */
291 2
    public function getIcon(string $default = null): ?string
292
    {
293
        if ($this->icon !== null && $this->icon !== '') {
294
            return '<i class="' . $this->icon . '"></i>';
295
        }
296
        if ($default === null) {
297
            return $default;
298
        }
299
300
        return '<i class="' . $default . '"></i>';
301 2
    }
302
303 2
    /**
304
     * Get properties.
305
     *
306
     * @return array
307
     */
308
    public function getProperties(): array
309
    {
310
        return $this->properties;
311 10
    }
312
313 10
    /**
314 1
     * Get HTML attribute data.
315
     *
316
     * @return string
317 9
     */
318
    public function getAttributes(): string
319
    {
320
        $attributes = $this->attributes ?: [];
321
322
        Arr::forget($attributes, ['active', 'icon']);
323
324
        $attributeString = collect($attributes)
325 3
            ->map(function ($value, $key) {
326
                if (is_bool($value)) {
327 3
                    return $value ? $key : '';
328 1
                }
329
330
                return $key . '="' . e($value) . '"';
331 2
            })
332
            ->filter()
333
            ->implode(' ');
334
335 2
        return $attributeString ? ' ' . $attributeString : '';
336
    }
337
338
    /**
339
     * Check is the current item divider.
340
     *
341
     * @return bool
342
     */
343 1
    public function isDivider(): bool
344
    {
345 1
        return $this->is('divider');
346
    }
347
348
    /**
349
     * Check is the current item divider.
350
     *
351
     * @return bool
352
     */
353
    public function isHeader(): bool
354
    {
355 3
        return $this->is('header');
356
    }
357 3
358 1
    /**
359
     * Check is the current item divider.
360 2
     *
361 1
     * @param $name
362
     *
363
     * @return bool
364 1
     */
365
    public function is($name): bool
366
    {
367
        return $this->name == $name;
368
    }
369
370
    /**
371
     * Check is the current item has sub menu .
372 2
     *
373
     * @return bool
374 2
     */
375
    public function hasSubMenu(): bool
376
    {
377
        return !empty($this->childs);
378
    }
379
380
    /**
381
     * @deprecated Same with hasSubMenu.
382 1
     *
383
     * @return bool
384 1
     */
385
    public function hasChilds(): bool
386 1
    {
387
        return $this->hasSubMenu();
388 1
    }
389
390
    /**
391
     * Check the active state for current menu.
392
     *
393
     * @return mixed
394
     */
395
    public function hasActiveOnChild(): mixed
396 1
    {
397
        if ($this->inactive()) {
398 1
            return false;
399
        }
400
401
        return $this->hasSubMenu() && $this->getActiveStateFromChildren();
402
    }
403
404
    public function getActiveStateFromChildren(): bool
405
    {
406 1
        foreach ($this->getChildren() as $child) {
407
            if ($child->inactive()) {
408 1
                continue;
409
            }
410
            if ($child->hasChilds()) {
411
                if ($child->getActiveStateFromChilds()) {
412
                    return true;
413
                }
414
            } elseif ($child->isActive()) {
415
                return true;
416
            } elseif ($child->hasRoute() && $child->getActiveStateFromRoute()) {
417
                return true;
418 2
            } elseif ($child->getActiveStateFromUrl()) {
419
                return true;
420 2
            }
421
        }
422
423
        return false;
424
    }
425
426
    /**
427
     * @deprecated See `getActiveStateFromChildren()`
428 1
     * Get active state from child menu items.
429
     *
430 1
     * @return bool
431
     */
432
    public function getActiveStateFromChilds(): bool
433
    {
434
        return $this->getActiveStateFromChildren();
435
    }
436
437
    /**
438 1
     * Get inactive state.
439
     *
440 1
     * @return bool
441
     */
442
    public function inactive(): bool
443
    {
444
        $inactive = $this->getInactiveAttribute();
445
446
        if ($inactive === '1' || $inactive === '0') {
447
            return (bool)$inactive;
448
        }
449
        if (is_bool($inactive)) {
0 ignored issues
show
introduced by
The condition is_bool($inactive) is always false.
Loading history...
450
            return $inactive;
451
        }
452
453
        if ($inactive instanceof \Closure) {
0 ignored issues
show
introduced by
$inactive is never a sub-type of Closure.
Loading history...
454
            return call_user_func($inactive);
455
        }
456
457
        return false;
458
    }
459
460
    /**
461
     * Get active attribute.
462
     *
463
     * @return string
464
     */
465
    public function getActiveAttribute(): string
466
    {
467
        return Arr::get($this->attributes, 'active', '');
468
    }
469
470
    /**
471
     * Get inactive attribute.
472
     *
473
     * @return string
474
     */
475
    public function getInactiveAttribute(): string
476
    {
477
        return Arr::get($this->attributes, 'inactive', '');
478
    }
479
480
    /**
481
     * Get active state for current item.
482
     *
483
     * @return mixed
484
     */
485
    public function isActive(): mixed
486
    {
487
        if ($this->inactive()) {
488
            return false;
489
        }
490
491
        $active = $this->getActiveAttribute();
492
493
        if ($active === '1' || $active === '0') {
494
            return (bool)$active;
495
        }
496
        if (is_bool($active)) {
0 ignored issues
show
introduced by
The condition is_bool($active) is always false.
Loading history...
497
            return $active;
498
        }
499
500
        if ($active instanceof \Closure) {
0 ignored issues
show
introduced by
$active is never a sub-type of Closure.
Loading history...
501
            return call_user_func($active);
502
        }
503
504
        if ($this->hasRoute()) {
505
            return $this->getActiveStateFromRoute();
506
        }
507
508
        return $this->getActiveStateFromUrl();
509
    }
510
511
    /**
512
     * Determine the current item using route.
513
     *
514
     * @return bool
515
     */
516
    protected function hasRoute(): bool
517
    {
518
        return !empty($this->route);
519
    }
520
521
    /**
522
     * Get active status using route.
523
     *
524
     * @return bool
525
     */
526
    protected function getActiveStateFromRoute(): bool
527
    {
528
        return Request::is(str_replace(url('/') . '/', '', $this->getUrl()));
529
    }
530
531
    /**
532
     * Get active status using request url.
533
     *
534
     * @return bool
535
     */
536
    protected function getActiveStateFromUrl(): bool
537
    {
538
        return Request::is($this->url);
539
    }
540
541
    /**
542
     * Set order value.
543
     *
544
     * @param  int $order
545
     * @return self
546
     */
547
    public function order(int $order): self
548
    {
549
        $this->order = $order;
550
551
        return $this;
552
    }
553
554
    public function hasBadge(): bool
555
    {
556
        return !empty($this->badge);
557
    }
558
}
559