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

MenuItem   F

Complexity

Total Complexity 68

Size/Duplication

Total Lines 527
Duplicated Lines 0 %

Test Coverage

Coverage 60.42%

Importance

Changes 0
Metric Value
eloc 126
dl 0
loc 527
ccs 87
cts 144
cp 0.6042
rs 2.96
c 0
b 0
f 0
wmc 68

36 Methods

Rating   Name   Duplication   Size   Complexity  
A getRequest() 0 3 1
A getChildren() 0 7 2
A route() 0 15 2
A getIcon() 0 10 4
A child() 0 5 1
A getBadge() 0 6 2
A getChilds() 0 3 1
A addBadge() 0 11 1
A addDivider() 0 7 1
A getUrl() 0 11 3
A addHeader() 0 10 1
A getProperties() 0 3 1
A dropdown() 0 20 2
A fill() 0 5 3
A url() 0 13 2
A divider() 0 3 1
A __construct() 0 4 1
A header() 0 3 1
A hasRoute() 0 3 1
A inactive() 0 13 3
A hasChilds() 0 3 1
A hasActiveOnChild() 0 7 3
A getActiveStateFromRoute() 0 3 1
B getActiveStateFromChildren() 0 20 9
A getActiveStateFromUrl() 0 3 1
A hasSubMenu() 0 3 1
A getAttributes() 0 18 5
A getActiveAttribute() 0 3 1
A isActive() 0 21 5
A isDivider() 0 3 1
A isHeader() 0 3 1
A hasBadge() 0 3 1
A getActiveStateFromChilds() 0 3 1
A order() 0 5 1
A getInactiveAttribute() 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 AllowDynamicProperties;
0 ignored issues
show
Bug introduced by
The type AllowDynamicProperties was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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

238
            return collect(/** @scrutinizer ignore-type */ $this->childs)->sortBy('order')->all();
Loading history...
239
        }
240 9
241
        return $this->childs;
242 9
    }
243
244 9
    /**
245
     * Get url.
246
     *
247
     * @return string
248
     */
249
    public function getUrl(): string
250
    {
251
        if ($this->route !== null) {
252
            return route($this->route[0], $this->route[1]);
253
        }
254 1
255
        if (empty($this->url)) {
256 1
            return url('/#');
257
        }
258 1
259
        return url($this->url);
260 1
    }
261
262
    /**
263
     * Get request url.
264
     *
265
     * @return string
266
     */
267
    public function getRequest(): string
268
    {
269
        return ltrim(str_replace(url('/'), '', $this->getUrl()), '/');
270 1
    }
271
272 1
    /**
273
     * @return string
274
     */
275
    public function getBadge(): string
276
    {
277
        if ($this->hasBadge()) {
278
            extract($this->badge);
279
280
            return '<span class="' . $type . '">' . $text . '</span>';
281
        }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 277 is false. This is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
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 (is_bool($inactive)) {
0 ignored issues
show
introduced by
The condition is_bool($inactive) is always false.
Loading history...
447
            return $inactive;
448
        }
449
450
        if ($inactive instanceof \Closure) {
0 ignored issues
show
introduced by
$inactive is never a sub-type of Closure.
Loading history...
451
            return call_user_func($inactive);
452
        }
453
454
        return false;
455
    }
456
457
    /**
458
     * Get active attribute.
459
     *
460
     * @return string
461
     */
462
    public function getActiveAttribute(): string
463
    {
464
        return Arr::get($this->attributes, 'active');
0 ignored issues
show
Bug Best Practice introduced by
The expression return Illuminate\Suppor...->attributes, 'active') could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
465
    }
466
467
    /**
468
     * Get inactive attribute.
469
     *
470
     * @return string
471
     */
472
    public function getInactiveAttribute(): string
473
    {
474
        return Arr::get($this->attributes, 'inactive');
0 ignored issues
show
Bug Best Practice introduced by
The expression return Illuminate\Suppor...attributes, 'inactive') could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
475
    }
476
477
    /**
478
     * Get active state for current item.
479
     *
480
     * @return mixed
481
     */
482
    public function isActive(): mixed
483
    {
484
        if ($this->inactive()) {
485
            return false;
486
        }
487
488
        $active = $this->getActiveAttribute();
489
490
        if (is_bool($active)) {
0 ignored issues
show
introduced by
The condition is_bool($active) is always false.
Loading history...
491
            return $active;
492
        }
493
494
        if ($active instanceof \Closure) {
0 ignored issues
show
introduced by
$active is never a sub-type of Closure.
Loading history...
495
            return call_user_func($active);
496
        }
497
498
        if ($this->hasRoute()) {
499
            return $this->getActiveStateFromRoute();
500
        }
501
502
        return $this->getActiveStateFromUrl();
503
    }
504
505
    /**
506
     * Determine the current item using route.
507
     *
508
     * @return bool
509
     */
510
    protected function hasRoute(): bool
511
    {
512
        return !empty($this->route);
513
    }
514
515
    /**
516
     * Get active status using route.
517
     *
518
     * @return bool
519
     */
520
    protected function getActiveStateFromRoute(): bool
521
    {
522
        return Request::is(str_replace(url('/') . '/', '', $this->getUrl()));
523
    }
524
525
    /**
526
     * Get active status using request url.
527
     *
528
     * @return bool
529
     */
530
    protected function getActiveStateFromUrl(): bool
531
    {
532
        return Request::is($this->url);
533
    }
534
535
    /**
536
     * Set order value.
537
     *
538
     * @param  int $order
539
     * @return self
540
     */
541
    public function order(int $order): self
542
    {
543
        $this->order = $order;
544
545
        return $this;
546
    }
547
548
    public function hasBadge(): bool
549
    {
550
        return !empty($this->badge);
551
    }
552
}
553