Passed
Pull Request — master (#57)
by Kyle
06:09
created

MenuItem   F

Complexity

Total Complexity 71

Size/Duplication

Total Lines 620
Duplicated Lines 0 %

Test Coverage

Coverage 54.27%

Importance

Changes 0
Metric Value
eloc 146
dl 0
loc 620
ccs 89
cts 164
cp 0.5427
rs 2.7199
c 0
b 0
f 0
wmc 71

40 Methods

Rating   Name   Duplication   Size   Complexity  
A getRequest() 0 3 1
A hasRoute() 0 3 1
A inactive() 0 13 3
A add() 0 7 1
A hasChilds() 0 3 1
A route() 0 15 2
A getIcon() 0 10 4
A setIconAttribute() 0 12 2
A child() 0 5 1
A hasActiveOnChild() 0 7 3
A getActiveStateFromRoute() 0 3 1
A getBadge() 0 6 2
A getChilds() 0 7 2
A addBadge() 0 11 1
A getActiveStateFromUrl() 0 3 1
A toArray() 0 3 1
A addDivider() 0 7 1
A hasSubMenu() 0 3 1
A getUrl() 0 11 3
A getAttributes() 0 7 2
A addHeader() 0 10 1
A make() 0 5 1
A getActiveAttribute() 0 3 1
A getProperties() 0 3 1
A isActive() 0 21 5
A dropdown() 0 20 2
A isDivider() 0 3 1
A getRandomName() 0 3 1
A isHeader() 0 3 1
A fill() 0 5 3
A __get() 0 3 2
A hasBadge() 0 3 1
A url() 0 13 2
A divider() 0 3 1
A __construct() 0 4 1
B getActiveStateFromChilds() 0 20 9
A order() 0 5 1
A getInactiveAttribute() 0 3 1
A header() 0 3 1
A is() 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 Collective\Html\HtmlFacade as HTML;
6
use Illuminate\Contracts\Support\Arrayable as ArrayableContract;
7
use Illuminate\Support\Arr;
8
use Illuminate\Support\Facades\Request;
9
use Illuminate\Support\Str;
10
use KyleMassacre\Menus\Traits\CanHide;
11
12
/**
13
 * @property string url
14
 * @property string route
15
 * @property string title
16
 * @property string name
17
 * @property string icon
18
 * @property int parent
19
 * @property array attributes
20
 * @property bool active
21
 * @property int order
22
 * @property array badge
23
 */
24
class MenuItem implements ArrayableContract
25
{
26
    use CanHide;
27
    /**
28
     * Array properties.
29
     *
30
     * @var array
31
     */
32
    protected $properties = [];
33
34
    /**
35
     * The child collections for current menu item.
36
     *
37
     * @var array
38
     */
39
    protected $childs = array();
40
41
    /**
42
     * The fillable attribute.
43
     *
44
     * @var array
45
     */
46
    protected $fillable = array(
47
        'url',
48
        'route',
49
        'title',
50
        'name',
51
        'icon',
52
        'parent',
53
        'attributes',
54
        'active',
55
        'order',
56
        'hideWhen',
57
        'badge',
58
        'class',
59
    );
60
61
    /**
62
     * Constructor.
63
     *
64
     * @param array $properties
65
     */
66
    public function __construct($properties = array())
67
    {
68
        $this->properties = $properties;
69 24
        $this->fill($properties);
70
    }
71 24
72 24
    /**
73 24
     * Set the icon property when the icon is defined in the link attributes.
74
     *
75
     * @param array $properties
76
     *
77
     * @return array
78
     */
79
    protected static function setIconAttribute(array $properties)
80
    {
81
        $icon = Arr::get($properties, 'attributes.icon');
82 24
        if (!is_null($icon)) {
83
            $properties['icon'] = $icon;
84 24
85 24
            Arr::forget($properties, 'attributes.icon');
86 1
87
            return $properties;
88 1
        }
89
90 1
        return $properties;
91
    }
92
93 23
    /**
94
     * Get random name.
95
     *
96
     * @param array $attributes
97
     *
98
     * @return string
99
     */
100
    protected static function getRandomName(array $attributes)
101
    {
102
        return substr(md5(Arr::get($attributes, 'title', Str::random(6))), 0, 5);
103
    }
104
105
    /**
106
     * Create new static instance.
107
     *
108
     * @param array $properties
109
     *
110
     * @return static
111
     */
112
    public static function make(array $properties)
113
    {
114
        $properties = self::setIconAttribute($properties);
115 24
116
        return new static($properties);
117 24
    }
118
119 24
    /**
120
     * Fill the attributes.
121
     *
122
     * @param array $attributes
123
     */
124
    public function fill($attributes)
125
    {
126
        foreach ($attributes as $key => $value) {
127 24
            if (in_array($key, $this->fillable)) {
128
                $this->{$key} = $value;
129 24
            }
130 23
        }
131 23
    }
132
133
    /**
134 24
     * Create new menu child item using array.
135
     *
136
     * @param $attributes
137
     *
138
     * @return $this
139
     */
140
    public function child($attributes)
141
    {
142
        $this->childs[] = static::make($attributes);
143 2
144
        return $this;
145 2
    }
146
147 2
    /**
148
     * Register new child menu with dropdown.
149
     *
150
     * @param $title
151
     * @param callable $callback
152
     *
153
     * @return $this
154
     */
155
    public function dropdown($title, \Closure $callback, $order = 0, array $attributes = array())
156
    {
157
        $properties = compact('title', 'order', 'attributes');
158 9
159
        if (func_num_args() === 3) {
160 9
            $arguments = func_get_args();
161
162 9
            $title = Arr::get($arguments, 0);
163
            $attributes = Arr::get($arguments, 2);
164
165
            $properties = compact('title', 'attributes');
166
        }
167
168
        $child = static::make($properties);
169
170
        call_user_func($callback, $child);
171 9
172
        $this->childs[] = $child;
173 9
174
        return $child;
175 9
    }
176
177 9
    /**
178
     * Create new menu item and set the action to route.
179
     *
180
     * @param $route
181
     * @param $title
182
     * @param array $parameters
183
     * @param array $attributes
184
     *
185
     * @return MenuItem
186
     */
187
    public function route($route, $title, $parameters = array(), $order = 0, $attributes = array())
188
    {
189
        if (func_num_args() === 4) {
190 3
            $arguments = func_get_args();
191
192 3
            return $this->add([
193
                'route' => [Arr::get($arguments, 0), Arr::get($arguments, 2)],
194
                'title' => Arr::get($arguments, 1),
195
                'attributes' => Arr::get($arguments, 3),
196
            ]);
197
        }
198
199
        $route = array($route, $parameters);
200
201
        return $this->add(compact('route', 'title', 'order', 'attributes'));
202 3
    }
203
204 3
    /**
205
     * Create new menu item  and set the action to url.
206
     *
207
     * @param $url
208
     * @param $title
209
     * @param array $attributes
210
     *
211
     * @return MenuItem
212
     */
213
    public function url($url, $title, $order = 0, $attributes = array())
214
    {
215
        if (func_num_args() === 3) {
216 6
            $arguments = func_get_args();
217
218 6
            return $this->add([
219
                'url' => Arr::get($arguments, 0),
220
                'title' => Arr::get($arguments, 1),
221
                'attributes' => Arr::get($arguments, 2),
222
            ]);
223
        }
224
225
        return $this->add(compact('url', 'title', 'order', 'attributes'));
226
    }
227
228 6
    /**
229
     * Add new child item.
230
     *
231
     * @param array $properties
232
     *
233
     * @return $this
234
     */
235
    public function add(array $properties)
236
    {
237
        $item = static::make($properties);
238 9
239
        $this->childs[] = $item;
240 9
241
        return $item;
242 9
    }
243
244 9
    /**
245
     * Add new divider.
246
     *
247
     * @param int $order
248
     *
249
     * @return self
250
     */
251
    public function addDivider($order = null)
252
    {
253
        $item = static::make(array('name' => 'divider', 'order' => $order));
254 1
255
        $this->childs[] = $item;
256 1
257
        return $item;
258 1
    }
259
260 1
    /**
261
     * Alias method instead "addDivider".
262
     *
263
     * @param int $order
264
     *
265
     * @return MenuItem
266
     */
267
    public function divider($order = null)
268
    {
269
        return $this->addDivider($order);
270 1
    }
271
272 1
    /**
273
     * Add dropdown header.
274
     *
275
     * @param $title
276
     *
277
     * @return $this
278
     */
279
    public function addHeader($title)
280
    {
281
        $item = static::make(array(
282 2
            'name' => 'header',
283
            'title' => $title,
284 2
        ));
285 2
286 2
        $this->childs[] = $item;
287
288
        return $item;
289 2
    }
290
291 2
    /**
292
     * Same with "addHeader" method.
293
     *
294
     * @param $title
295
     *
296
     * @return $this
297
     */
298
    public function header($title)
299
    {
300
        return $this->addHeader($title);
301 2
    }
302
303 2
    public function addBadge(string $type, $text)
304
    {
305
        $properties = array(
306
            'type' => $type,
307
            'text' => $text,
308
            'name' => 'badge',
309
        );
310
        $item = static::make($properties);
311 10
        $this->badge = $properties;
312
313 10
        return $item;
314 1
    }
315
316
    /**
317 9
     * Get childs.
318
     *
319
     * @return array
320
     */
321
    public function getChilds()
322
    {
323
        if (config('menus.ordering')) {
324
            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

324
            return collect(/** @scrutinizer ignore-type */ $this->childs)->sortBy('order')->all();
Loading history...
325 3
        }
326
327 3
        return $this->childs;
328 1
    }
329
330
    /**
331 2
     * Get url.
332
     *
333
     * @return string
334
     */
335 2
    public function getUrl()
336
    {
337
        if ($this->route !== null) {
338
            return route($this->route[0], $this->route[1]);
339
        }
340
341
        if (empty($this->url)) {
342
            return url('/#');
343 1
        }
344
345 1
        return url($this->url);
346
    }
347
348
    /**
349
     * Get request url.
350
     *
351
     * @return string
352
     */
353
    public function getRequest()
354
    {
355 3
        return ltrim(str_replace(url('/'), '', $this->getUrl()), '/');
356
    }
357 3
358 1
    /**
359
     * @param string $type
360 2
     * @param $text
361 1
     */
362
    public function getBadge()
363
    {
364 1
        if($this->hasBadge()) {
365
            extract($this->badge);
366
367
            return '<span class="' . $type . '">' . $text . '</span>';
368
        }
369
        //dd('badge is null');
370
    }
371
372 2
    /**
373
     * Get icon.
374 2
     *
375
     * @param null|string $default
376
     *
377
     * @return string
378
     */
379
    public function getIcon($default = null)
380
    {
381
        if ($this->icon !== null && $this->icon !== '') {
382 1
            return '<i class="' . $this->icon . '"></i>';
383
        }
384 1
        if ($default === null) {
385
            return $default;
386 1
        }
387
388 1
        return '<i class="' . $default . '"></i>';
389
    }
390
391
    /**
392
     * Get properties.
393
     *
394
     * @return array
395
     */
396 1
    public function getProperties()
397
    {
398 1
        return $this->properties;
399
    }
400
401
    /**
402
     * Get HTML attribute data.
403
     *
404
     * @return mixed
405
     */
406 1
    public function getAttributes()
407
    {
408 1
        $attributes = $this->attributes ? $this->attributes : [];
409
410
        Arr::forget($attributes, ['active', 'icon']);
411
412
        return HTML::attributes($attributes);
413
    }
414
415
    /**
416
     * Check is the current item divider.
417
     *
418 2
     * @return bool
419
     */
420 2
    public function isDivider()
421
    {
422
        return $this->is('divider');
423
    }
424
425
    /**
426
     * Check is the current item divider.
427
     *
428 1
     * @return bool
429
     */
430 1
    public function isHeader()
431
    {
432
        return $this->is('header');
433
    }
434
435
    /**
436
     * Check is the current item divider.
437
     *
438 1
     * @param $name
439
     *
440 1
     * @return bool
441
     */
442
    public function is($name)
443
    {
444
        return $this->name == $name;
445
    }
446
447
    /**
448
     * Check is the current item has sub menu .
449
     *
450
     * @return bool
451
     */
452
    public function hasSubMenu()
453
    {
454
        return !empty($this->childs);
455
    }
456
457
    /**
458
     * Same with hasSubMenu.
459
     *
460
     * @return bool
461
     */
462
    public function hasChilds()
463
    {
464
        return $this->hasSubMenu();
465
    }
466
467
    /**
468
     * Check the active state for current menu.
469
     *
470
     * @return mixed
471
     */
472
    public function hasActiveOnChild()
473
    {
474
        if ($this->inactive()) {
475
            return false;
476
        }
477
478
        return $this->hasChilds() ? $this->getActiveStateFromChilds() : false;
479
    }
480
481
    /**
482
     * Get active state from child menu items.
483
     *
484
     * @return bool
485
     */
486
    public function getActiveStateFromChilds()
487
    {
488
        foreach ($this->getChilds() as $child) {
489
            if ($child->inactive()) {
490
                continue;
491
            }
492
            if ($child->hasChilds()) {
493
                if ($child->getActiveStateFromChilds()) {
494
                    return true;
495
                }
496
            } elseif ($child->isActive()) {
497
                return true;
498
            } elseif ($child->hasRoute() && $child->getActiveStateFromRoute()) {
499
                return true;
500
            } elseif ($child->getActiveStateFromUrl()) {
501
                return true;
502
            }
503
        }
504
505
        return false;
506
    }
507
508
    /**
509
     * Get inactive state.
510
     *
511
     * @return bool
512
     */
513
    public function inactive()
514
    {
515
        $inactive = $this->getInactiveAttribute();
516
517
        if (is_bool($inactive)) {
0 ignored issues
show
introduced by
The condition is_bool($inactive) is always false.
Loading history...
518
            return $inactive;
519
        }
520
521
        if ($inactive instanceof \Closure) {
0 ignored issues
show
introduced by
$inactive is never a sub-type of Closure.
Loading history...
522
            return call_user_func($inactive);
523
        }
524
525
        return false;
526
    }
527
528
    /**
529
     * Get active attribute.
530
     *
531
     * @return string
532
     */
533
    public function getActiveAttribute()
534
    {
535
        return Arr::get($this->attributes, 'active');
536
    }
537
538
    /**
539
     * Get inactive attribute.
540
     *
541
     * @return string
542
     */
543
    public function getInactiveAttribute()
544
    {
545
        return Arr::get($this->attributes, 'inactive');
546
    }
547
548
    /**
549
     * Get active state for current item.
550
     *
551
     * @return mixed
552
     */
553
    public function isActive()
554
    {
555
        if ($this->inactive()) {
556
            return false;
557
        }
558
559
        $active = $this->getActiveAttribute();
560
561
        if (is_bool($active)) {
0 ignored issues
show
introduced by
The condition is_bool($active) is always false.
Loading history...
562
            return $active;
563
        }
564
565
        if ($active instanceof \Closure) {
0 ignored issues
show
introduced by
$active is never a sub-type of Closure.
Loading history...
566
            return call_user_func($active);
567
        }
568
569
        if ($this->hasRoute()) {
570
            return $this->getActiveStateFromRoute();
571
        }
572
573
        return $this->getActiveStateFromUrl();
574
    }
575
576
    /**
577
     * Determine the current item using route.
578
     *
579
     * @return bool
580
     */
581
    protected function hasRoute()
582
    {
583
        return !empty($this->route);
584
    }
585
586
    /**
587
     * Get active status using route.
588
     *
589
     * @return bool
590
     */
591
    protected function getActiveStateFromRoute()
592
    {
593
        return Request::is(str_replace(url('/') . '/', '', $this->getUrl()));
594
    }
595
596
    /**
597
     * Get active status using request url.
598
     *
599
     * @return bool
600
     */
601
    protected function getActiveStateFromUrl()
602
    {
603
        return Request::is($this->url);
604
    }
605
606
    /**
607
     * Set order value.
608
     *
609
     * @param  int $order
610
     * @return self
611
     */
612
    public function order($order)
613
    {
614
        $this->order = $order;
615
616
        return $this;
617
    }
618
619
    public function hasBadge()
620
    {
621
        return !empty($this->badge);
622
    }
623
624
    /**
625
     * Get the instance as an array.
626
     *
627
     * @return array
628
     */
629
    public function toArray()
630
    {
631
        return $this->getProperties();
632
    }
633
634
    /**
635
     * Get property.
636
     *
637
     * @param string $key
638
     *
639 4
     * @return string|null
640
     */
641 4
    public function __get($key)
642
    {
643
        return isset($this->$key) ? $this->$key : null;
644
    }
645
}
646