MenuItem   F
last analyzed

Complexity

Total Complexity 70

Size/Duplication

Total Lines 621
Duplicated Lines 6.28 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 54.27%

Importance

Changes 0
Metric Value
wmc 70
lcom 1
cbo 4
dl 39
loc 621
ccs 89
cts 164
cp 0.5427
rs 2.779
c 0
b 0
f 0

39 Methods

Rating   Name   Duplication   Size   Complexity  
A child() 0 6 1
A dropdown() 21 21 2
A route() 9 16 2
A url() 9 14 2
A add() 0 8 1
A addDivider() 0 8 1
A divider() 0 4 1
A addHeader() 0 11 1
A header() 0 4 1
A getChilds() 0 8 2
A __construct() 0 5 1
A setIconAttribute() 0 13 2
A getRandomName() 0 4 1
A make() 0 6 1
A fill() 0 8 3
A getUrl() 0 12 3
A getRequest() 0 4 1
A getIcon() 0 11 4
A getProperties() 0 4 1
A getAttributes() 0 8 2
A isDivider() 0 4 1
A isHeader() 0 4 1
A is() 0 4 1
A hasSubMenu() 0 4 1
A hasChilds() 0 4 1
A hasActiveOnChild() 0 8 3
B getActiveStateFromChilds() 0 21 9
A inactive() 0 14 3
A getActiveAttribute() 0 4 1
A getInactiveAttribute() 0 4 1
A isActive() 0 22 5
A hasRoute() 0 4 1
A getActiveStateFromRoute() 0 4 1
A getActiveStateFromUrl() 0 4 1
A order() 0 6 1
A hideWhen() 0 6 1
A hidden() 0 8 2
A toArray() 0 4 1
A __get() 0 4 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
3
namespace Nwidart\Menus;
4
5
use Closure;
6
use Collective\Html\HtmlFacade as HTML;
7
use Illuminate\Contracts\Support\Arrayable as ArrayableContract;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Facades\Request;
10
use Illuminate\Support\Str;
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
 */
23
class MenuItem implements ArrayableContract
24
{
25
    /**
26
     * Array properties.
27
     *
28
     * @var array
29
     */
30
    protected $properties = [];
31
32
    /**
33
     * The child collections for current menu item.
34
     *
35
     * @var array
36
     */
37
    protected $childs = array();
38
39
    /**
40
     * The fillable attribute.
41
     *
42
     * @var array
43
     */
44
    protected $fillable = array(
45
        'url',
46
        'route',
47
        'title',
48
        'name',
49
        'icon',
50
        'parent',
51
        'attributes',
52
        'active',
53
        'order',
54
        'hideWhen',
55
    );
56
57
    /**
58
     * The hideWhen callback.
59
     *
60
     * @var Closure
61
     */
62
    protected $hideWhen;
63
64
    /**
65
     * Constructor.
66
     *
67
     * @param array $properties
68
     */
69 24
    public function __construct($properties = array())
70
    {
71 24
        $this->properties = $properties;
72 24
        $this->fill($properties);
73 24
    }
74
75
    /**
76
     * Set the icon property when the icon is defined in the link attributes.
77
     *
78
     * @param array $properties
79
     *
80
     * @return array
81
     */
82 24
    protected static function setIconAttribute(array $properties)
83
    {
84 24
        $icon = Arr::get($properties, 'attributes.icon');
85 24
        if (!is_null($icon)) {
86 1
            $properties['icon'] = $icon;
87
88 1
            Arr::forget($properties, 'attributes.icon');
89
90 1
            return $properties;
91
        }
92
93 23
        return $properties;
94
    }
95
96
    /**
97
     * Get random name.
98
     *
99
     * @param array $attributes
100
     *
101
     * @return string
102
     */
103
    protected static function getRandomName(array $attributes)
104
    {
105
        return substr(md5(Arr::get($attributes, 'title', Str::random(6))), 0, 5);
106
    }
107
108
    /**
109
     * Create new static instance.
110
     *
111
     * @param array $properties
112
     *
113
     * @return static
114
     */
115 24
    public static function make(array $properties)
116
    {
117 24
        $properties = self::setIconAttribute($properties);
118
119 24
        return new static($properties);
120
    }
121
122
    /**
123
     * Fill the attributes.
124
     *
125
     * @param array $attributes
126
     */
127 24
    public function fill($attributes)
128
    {
129 24
        foreach ($attributes as $key => $value) {
130 23
            if (in_array($key, $this->fillable)) {
131 23
                $this->{$key} = $value;
132
            }
133
        }
134 24
    }
135
136
    /**
137
     * Create new menu child item using array.
138
     *
139
     * @param $attributes
140
     *
141
     * @return $this
142
     */
143 2
    public function child($attributes)
144
    {
145 2
        $this->childs[] = static::make($attributes);
146
147 2
        return $this;
148
    }
149
150
    /**
151
     * Register new child menu with dropdown.
152
     *
153
     * @param $title
154
     * @param callable $callback
155
     *
156
     * @return $this
157
     */
158 9 View Code Duplication
    public function dropdown($title, \Closure $callback, $order = 0, array $attributes = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
159
    {
160 9
        $properties = compact('title', 'order', 'attributes');
161
162 9
        if (func_num_args() === 3) {
163
            $arguments = func_get_args();
164
165
            $title = Arr::get($arguments, 0);
166
            $attributes = Arr::get($arguments, 2);
167
168
            $properties = compact('title', 'attributes');
169
        }
170
171 9
        $child = static::make($properties);
172
173 9
        call_user_func($callback, $child);
174
175 9
        $this->childs[] = $child;
176
177 9
        return $child;
178
    }
179
180
    /**
181
     * Create new menu item and set the action to route.
182
     *
183
     * @param $route
184
     * @param $title
185
     * @param array $parameters
186
     * @param array $attributes
187
     *
188
     * @return MenuItem
189
     */
190 3
    public function route($route, $title, $parameters = array(), $order = 0, $attributes = array())
191
    {
192 3 View Code Duplication
        if (func_num_args() === 4) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193
            $arguments = func_get_args();
194
195
            return $this->add([
196
                'route' => [Arr::get($arguments, 0), Arr::get($arguments, 2)],
197
                'title' => Arr::get($arguments, 1),
198
                'attributes' => Arr::get($arguments, 3),
199
            ]);
200
        }
201
202 3
        $route = array($route, $parameters);
203
204 3
        return $this->add(compact('route', 'title', 'order', 'attributes'));
205
    }
206
207
    /**
208
     * Create new menu item  and set the action to url.
209
     *
210
     * @param $url
211
     * @param $title
212
     * @param array $attributes
213
     *
214
     * @return MenuItem
215
     */
216 6
    public function url($url, $title, $order = 0, $attributes = array())
217
    {
218 6 View Code Duplication
        if (func_num_args() === 3) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
219
            $arguments = func_get_args();
220
221
            return $this->add([
222
                'url' => Arr::get($arguments, 0),
223
                'title' => Arr::get($arguments, 1),
224
                'attributes' => Arr::get($arguments, 2),
225
            ]);
226
        }
227
228 6
        return $this->add(compact('url', 'title', 'order', 'attributes'));
229
    }
230
231
    /**
232
     * Add new child item.
233
     *
234
     * @param array $properties
235
     *
236
     * @return $this
237
     */
238 9
    public function add(array $properties)
239
    {
240 9
        $item = static::make($properties);
241
242 9
        $this->childs[] = $item;
243
244 9
        return $item;
245
    }
246
247
    /**
248
     * Add new divider.
249
     *
250
     * @param int $order
251
     *
252
     * @return self
253
     */
254 1
    public function addDivider($order = null)
255
    {
256 1
        $item = static::make(array('name' => 'divider', 'order' => $order));
257
258 1
        $this->childs[] = $item;
259
260 1
        return $item;
261
    }
262
263
    /**
264
     * Alias method instead "addDivider".
265
     *
266
     * @param int $order
267
     *
268
     * @return MenuItem
269
     */
270 1
    public function divider($order = null)
271
    {
272 1
        return $this->addDivider($order);
273
    }
274
275
    /**
276
     * Add dropdown header.
277
     *
278
     * @param $title
279
     *
280
     * @return $this
281
     */
282 2
    public function addHeader($title)
283
    {
284 2
        $item = static::make(array(
285 2
            'name' => 'header',
286 2
            'title' => $title,
287
        ));
288
289 2
        $this->childs[] = $item;
290
291 2
        return $item;
292
    }
293
294
    /**
295
     * Same with "addHeader" method.
296
     *
297
     * @param $title
298
     *
299
     * @return $this
300
     */
301 2
    public function header($title)
302
    {
303 2
        return $this->addHeader($title);
304
    }
305
306
    /**
307
     * Get childs.
308
     *
309
     * @return array
310
     */
311 10
    public function getChilds()
312
    {
313 10
        if (config('menus.ordering')) {
314 1
            return collect($this->childs)->sortBy('order')->all();
315
        }
316
317 9
        return $this->childs;
318
    }
319
320
    /**
321
     * Get url.
322
     *
323
     * @return string
324
     */
325 3
    public function getUrl()
326
    {
327 3
        if ($this->route !== null) {
328 1
            return route($this->route[0], $this->route[1]);
329
        }
330
331 2
        if (empty($this->url)) {
332
            return url('/#');
333
        }
334
335 2
        return url($this->url);
336
    }
337
338
    /**
339
     * Get request url.
340
     *
341
     * @return string
342
     */
343 1
    public function getRequest()
344
    {
345 1
        return ltrim(str_replace(url('/'), '', $this->getUrl()), '/');
346
    }
347
348
    /**
349
     * Get icon.
350
     *
351
     * @param null|string $default
352
     *
353
     * @return string
354
     */
355 3
    public function getIcon($default = null)
356
    {
357 3
        if ($this->icon !== null && $this->icon !== '') {
358 1
            return '<i class="' . $this->icon . '"></i>';
359
        }
360 2
        if ($default === null) {
361 1
            return $default;
362
        }
363
364 1
        return '<i class="' . $default . '"></i>';
365
    }
366
367
    /**
368
     * Get properties.
369
     *
370
     * @return array
371
     */
372 2
    public function getProperties()
373
    {
374 2
        return $this->properties;
375
    }
376
377
    /**
378
     * Get HTML attribute data.
379
     *
380
     * @return mixed
381
     */
382 1
    public function getAttributes()
383
    {
384 1
        $attributes = $this->attributes ? $this->attributes : [];
385
386 1
        Arr::forget($attributes, ['active', 'icon']);
387
388 1
        return HTML::attributes($attributes);
389
    }
390
391
    /**
392
     * Check is the current item divider.
393
     *
394
     * @return bool
395
     */
396 1
    public function isDivider()
397
    {
398 1
        return $this->is('divider');
399
    }
400
401
    /**
402
     * Check is the current item divider.
403
     *
404
     * @return bool
405
     */
406 1
    public function isHeader()
407
    {
408 1
        return $this->is('header');
409
    }
410
411
    /**
412
     * Check is the current item divider.
413
     *
414
     * @param $name
415
     *
416
     * @return bool
417
     */
418 2
    public function is($name)
419
    {
420 2
        return $this->name == $name;
421
    }
422
423
    /**
424
     * Check is the current item has sub menu .
425
     *
426
     * @return bool
427
     */
428 1
    public function hasSubMenu()
429
    {
430 1
        return !empty($this->childs);
431
    }
432
433
    /**
434
     * Same with hasSubMenu.
435
     *
436
     * @return bool
437
     */
438 1
    public function hasChilds()
439
    {
440 1
        return $this->hasSubMenu();
441
    }
442
443
    /**
444
     * Check the active state for current menu.
445
     *
446
     * @return mixed
447
     */
448
    public function hasActiveOnChild()
449
    {
450
        if ($this->inactive()) {
451
            return false;
452
        }
453
454
        return $this->hasChilds() ? $this->getActiveStateFromChilds() : false;
455
    }
456
457
    /**
458
     * Get active state from child menu items.
459
     *
460
     * @return bool
461
     */
462
    public function getActiveStateFromChilds()
463
    {
464
        foreach ($this->getChilds() as $child) {
465
            if ($child->inactive()) {
466
                continue;
467
            }
468
            if ($child->hasChilds()) {
469
                if ($child->getActiveStateFromChilds()) {
470
                    return true;
471
                }
472
            } elseif ($child->isActive()) {
473
                return true;
474
            } elseif ($child->hasRoute() && $child->getActiveStateFromRoute()) {
475
                return true;
476
            } elseif ($child->getActiveStateFromUrl()) {
477
                return true;
478
            }
479
        }
480
481
        return false;
482
    }
483
484
    /**
485
     * Get inactive state.
486
     *
487
     * @return bool
488
     */
489
    public function inactive()
490
    {
491
        $inactive = $this->getInactiveAttribute();
492
493
        if (is_bool($inactive)) {
494
            return $inactive;
495
        }
496
497
        if ($inactive instanceof \Closure) {
498
            return call_user_func($inactive);
499
        }
500
501
        return false;
502
    }
503
504
    /**
505
     * Get active attribute.
506
     *
507
     * @return string
508
     */
509
    public function getActiveAttribute()
510
    {
511
        return Arr::get($this->attributes, 'active');
512
    }
513
514
    /**
515
     * Get inactive attribute.
516
     *
517
     * @return string
518
     */
519
    public function getInactiveAttribute()
520
    {
521
        return Arr::get($this->attributes, 'inactive');
522
    }
523
524
    /**
525
     * Get active state for current item.
526
     *
527
     * @return mixed
528
     */
529
    public function isActive()
530
    {
531
        if ($this->inactive()) {
532
            return false;
533
        }
534
535
        $active = $this->getActiveAttribute();
536
537
        if (is_bool($active)) {
538
            return $active;
539
        }
540
541
        if ($active instanceof \Closure) {
542
            return call_user_func($active);
543
        }
544
545
        if ($this->hasRoute()) {
546
            return $this->getActiveStateFromRoute();
547
        }
548
549
        return $this->getActiveStateFromUrl();
550
    }
551
552
    /**
553
     * Determine the current item using route.
554
     *
555
     * @return bool
556
     */
557
    protected function hasRoute()
558
    {
559
        return !empty($this->route);
560
    }
561
562
    /**
563
     * Get active status using route.
564
     *
565
     * @return bool
566
     */
567
    protected function getActiveStateFromRoute()
568
    {
569
        return Request::is(str_replace(url('/') . '/', '', $this->getUrl()));
570
    }
571
572
    /**
573
     * Get active status using request url.
574
     *
575
     * @return bool
576
     */
577
    protected function getActiveStateFromUrl()
578
    {
579
        return Request::is($this->url);
580
    }
581
582
    /**
583
     * Set order value.
584
     *
585
     * @param  int $order
586
     * @return self
587
     */
588
    public function order($order)
589
    {
590
        $this->order = $order;
591
592
        return $this;
593
    }
594
595
    /**
596
     * Set hide condition for current menu item.
597
     *
598
     * @param  Closure
599
     * @return boolean
600
     */
601
    public function hideWhen(Closure $callback)
602
    {
603
        $this->hideWhen = $callback;
604
605
        return $this;
606
    }
607
608
    /**
609
     * Determine whether the menu item is hidden.
610
     *
611
     * @return boolean
612
     */
613
    public function hidden()
614
    {
615
        if (is_null($this->hideWhen)) {
616
            return false;
617
        }
618
619
        return call_user_func($this->hideWhen) == true;
620
    }
621
622
    /**
623
     * Get the instance as an array.
624
     *
625
     * @return array
626
     */
627
    public function toArray()
628
    {
629
        return $this->getProperties();
630
    }
631
632
    /**
633
     * Get property.
634
     *
635
     * @param string $key
636
     *
637
     * @return string|null
638
     */
639 4
    public function __get($key)
640
    {
641 4
        return isset($this->$key) ? $this->$key : null;
642
    }
643
}
644