Passed
Push — master ( 700b0f...aafb0a )
by Björn
18:25 queued 10s
created

Languagemenu   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 526
Duplicated Lines 4.75 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
wmc 59
lcom 2
cbo 3
dl 25
loc 526
rs 4.08
c 0
b 0
f 0

35 Methods

Rating   Name   Duplication   Size   Complexity  
A setDetector() 0 5 1
A getDetector() 0 11 3
A setUlClass() 0 5 1
A getUlClass() 0 4 1
A setTitleMethod() 0 7 1
A getTitleMethod() 0 4 1
A setTitleInCurrentLocale() 0 5 1
A getTitleInCurrentLocale() 0 4 1
A setLabelMethod() 0 7 1
A getLabelMethod() 0 4 1
A setLabelInCurrentLocale() 0 5 1
A getLabelInCurrentLocale() 0 4 1
A setOmitCurrent() 0 5 1
A omitCurrent() 0 4 1
A getDefaultLiClass() 0 3 1
A setDefaultLiClass() 0 4 1
A getSubUlClass() 0 3 1
A setSubUlClass() 0 4 1
A getSubUlClassLevel1() 0 3 1
A setSubUlClassLevel1() 0 4 1
A getSubLiClass() 0 3 1
A setSubLiClass() 0 4 1
A getSubLiClassLevel0() 0 3 1
A setSubLiClassLevel0() 0 4 1
A getIconPrefixClass() 0 3 1
A setIconPrefixClass() 0 4 1
A getHrefSubToggleOverride() 0 3 1
A setHrefSubToggleOverride() 0 4 1
A setHtmlifyPartial() 8 8 4
A getHtmlifyPartial() 0 4 1
A __invoke() 0 5 1
A render() 17 17 3
C buildComponent() 0 59 15
A checkLocaleMethod() 0 21 2
A getLocaleProperty() 0 12 3

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

1
<?php
2
/**
3
 * BB's Zend Framework 2 Components
4
 * 
5
 * UI Components
6
 *
7
 * @package     [MyApplication]
8
 * @subpackage  BB's Zend Framework 2 Components
9
 * @subpackage  UI Components
10
 * @author      Björn Bartels <[email protected]>
11
 * @link        https://gitlab.bjoernbartels.earth/groups/zf2
12
 * @license     http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
13
 * @copyright   copyright (c) 2016 Björn Bartels <[email protected]>
14
 */
15
16
namespace UIComponents\View\Helper\Components;
17
18
use Zend\View\HelperPluginManager;
19
use Locale;
20
21
class Languagemenu extends \UIComponents\View\Helper\AbstractHelper
22
23
{
24
    
25
    /**
26
     * @var Detector $detector
27
     */
28
    protected $detector;
29
30
    /**
31
     * Set the class to be used on the list container
32
     *
33
     * @var string || null
34
     */
35
    protected $class;
36
37
    /**
38
     * Method used to construct a title for each item
39
     *
40
     * @var string || null
41
     */
42
    protected $titleMethod = 'displayLanguage';
43
44
    /**
45
     * Flag to specify specifies whether the title should be in the current locale
46
     *
47
     * @var boolean default false
48
     */
49
    protected $titleInCurrentLocale = false;
50
51
    /**
52
     * Method used to construct a label for each item
53
     *
54
     * @var string || null
55
     */
56
    protected $labelMethod = 'displayLanguage';
57
58
    /**
59
     * Flag to specify specifies whether the label should be in the current locale
60
     *
61
     * @var boolean default true
62
     */
63
    protected $labelInCurrentLocale = true;
64
65
    /**
66
     * Flag to specify the current locale should be omitted from the menu
67
     *
68
     * @var boolean default false
69
     */
70
    protected $omitCurrent = false;
71
72
    /**
73
     * default CSS class to use for li elements
74
     *
75
     * @var string
76
     */
77
    protected $defaultLiClass = '';
78
79
    /**
80
     * CSS class to use for the ul sub-menu element
81
     *
82
     * @var string
83
     */
84
    protected $subUlClass = 'dropdown-menu';
85
86
    /**
87
     * CSS class to use for the 1. level (NOT root level!) ul sub-menu element
88
     *
89
     * @var string
90
     */
91
    protected $subUlClassLevel1 = 'dropdown-menu';
92
93
    /**
94
     * CSS class to use for the active li sub-menu element
95
     *
96
     * @var string
97
     */
98
    protected $subLiClass = 'dropdown-submenu';
99
100
    /**
101
     * CSS class to use for the active li sub-menu element
102
     *
103
     * @var string
104
     */
105
    protected $subLiClassLevel0 = 'dropdown';
106
107
    /**
108
     * CSS class prefix to use for the menu element's icon class
109
     *
110
     * @var string
111
     */
112
    protected $iconPrefixClass = 'icon-';
113
114
    /**
115
     * HREF string to use for the sub-menu toggle element's HREF attribute, 
116
     * to override current page's href/'htmlify' setting
117
     *
118
     * @var string
119
     */
120
    protected $hrefSubToggleOverride = null;
121
122
    /**
123
     * Partial view script to use for rendering menu link/item
124
     *
125
     * @var string|array
126
     */
127
    protected $htmlifyPartial = null;
128
129
    /**
130
     * @param Detector $detector
131
     */
132
    public function setDetector($detector)
133
    {
134
        $this->detector = $detector;
135
        return $this;
136
    }
137
138
    /**
139
     * @return Detector $detector
140
     */
141
    public function getDetector()
142
    {
143
        if (!$this->detector) {
144
            $serviceLocator = $this->getServiceLocator();
145
            if ($serviceLocator instanceof HelperPluginManager) {
146
                $serviceLocator = $serviceLocator->getServiceLocator();
147
            }
148
            $this->detector = $serviceLocator->get('SlmLocale\Locale\Detector');
0 ignored issues
show
Documentation Bug introduced by
It seems like $serviceLocator->get('Sl...ale\\Locale\\Detector') can also be of type array. However, the property $detector is declared as type object<UIComponents\View...er\Components\Detector>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
149
        }
150
        return $this->detector;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->detector; of type object|array adds the type array to the return on line 150 which is incompatible with the return type documented by UIComponents\View\Helper...nguagemenu::getDetector of type UIComponents\View\Helper\Components\Detector.
Loading history...
151
    }
152
153
    /**
154
     * @param string $class
155
     */
156
    public function setUlClass($class)
157
    {
158
        $this->class = $class;
159
        return $this;
160
    }
161
162
    /**
163
     * @return string
164
     */
165
    public function getUlClass()
166
    {
167
        return $this->class;
168
    }
169
170
    /**
171
     * @param string $itemTitleMethod
0 ignored issues
show
Documentation introduced by
There is no parameter named $itemTitleMethod. Did you maybe mean $titleMethod?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
172
     */
173
    public function setTitleMethod($titleMethod)
174
    {
175
        $this->checkLocaleMethod($titleMethod);
176
177
        $this->titleMethod = $titleMethod;
178
        return $this;
179
    }
180
181
    /**
182
     * @return string
183
     */
184
    public function getTitleMethod()
185
    {
186
        return $this->titleMethod;
187
    }
188
189
    /**
190
     * @param boolean $flag
191
     */
192
    public function setTitleInCurrentLocale($flag)
193
    {
194
        $this->titleInCurrentLocale = (bool) $flag;
195
        return $this;
196
    }
197
198
    /**
199
     * @return boolean
200
     */
201
    public function getTitleInCurrentLocale()
202
    {
203
        return $this->titleInCurrentLocale;
204
    }
205
206
    /**
207
     * @param string $labelMethod
208
     */
209
    public function setLabelMethod($labelMethod)
210
    {
211
        $this->checkLocaleMethod($labelMethod);
212
213
        $this->labelMethod = $labelMethod;
214
        return $this;
215
    }
216
217
    /**
218
     * @return string
219
     */
220
    public function getLabelMethod()
221
    {
222
        return $this->labelMethod;
223
    }
224
225
    /**
226
     * @param boolean $flag
227
     */
228
    public function setLabelInCurrentLocale($flag)
229
    {
230
        $this->labelInCurrentLocale = (bool) $flag;
231
        return $this;
232
    }
233
234
    /**
235
     * @return boolean
236
     */
237
    public function getLabelInCurrentLocale()
238
    {
239
        return $this->labelInCurrentLocale;
240
    }
241
242
    /**
243
     * @param boolean $omitCurrent
244
     */
245
    public function setOmitCurrent($omitCurrent)
246
    {
247
        $this->omitCurrent = (bool) $omitCurrent;
248
        return $this;
249
    }
250
251
    /**
252
     * @return boolean
253
     */
254
    public function omitCurrent()
255
    {
256
        return $this->omitCurrent;
257
    }
258
    
259
    /**
260
     * @return the $defaultLiClass
261
     */
262
    public function getDefaultLiClass() {
263
        return $this->defaultLiClass;
264
    }
265
266
    /**
267
     * @param string $defaultLiClass
268
     */
269
    public function setDefaultLiClass($defaultLiClass) {
270
        $this->defaultLiClass = $defaultLiClass;
271
        return $this;
272
    }
273
274
    /**
275
     * @return the $subUlClass
276
     */
277
    public function getSubUlClass() {
278
        return $this->subUlClass;
279
    }
280
281
    /**
282
     * @param string $subUlClass
283
     */
284
    public function setSubUlClass($subUlClass) {
285
        $this->subUlClass = $subUlClass;
286
        return $this;
287
    }
288
289
    /**
290
     * @return the $subUlClassLevel1
291
     */
292
    public function getSubUlClassLevel1() {
293
        return $this->subUlClassLevel1;
294
    }
295
296
    /**
297
     * @param string $subUlClassLevel1
298
     */
299
    public function setSubUlClassLevel1($subUlClassLevel1) {
300
        $this->subUlClassLevel1 = $subUlClassLevel1;
301
        return $this;
302
    }
303
304
    /**
305
     * @return the $subLiClass
306
     */
307
    public function getSubLiClass() {
308
        return $this->subLiClass;
309
    }
310
311
    /**
312
     * @param string $subLiClass
313
     */
314
    public function setSubLiClass($subLiClass) {
315
        $this->subLiClass = $subLiClass;
316
        return $this;
317
    }
318
319
    /**
320
     * @return the $subLiClassLevel0
321
     */
322
    public function getSubLiClassLevel0() {
323
        return $this->subLiClassLevel0;
324
    }
325
    
326
    /**
327
     * @param string $subLiClassLevel0
328
     */
329
    public function setSubLiClassLevel0($subLiClassLevel0) {
330
        $this->subLiClassLevel0 = $subLiClassLevel0;
331
        return $this;
332
    }
333
    
334
    /**
335
     * @return the $iconPrefixClass
336
     */
337
    public function getIconPrefixClass() {
338
        return $this->iconPrefixClass;
339
    }
340
341
    /**
342
     * @param string $iconPrefixClass
343
     */
344
    public function setIconPrefixClass($iconPrefixClass) {
345
        $this->iconPrefixClass = $iconPrefixClass;
346
        return $this;
347
    }
348
    
349
    /**
350
     * @return the $hrefSubToggleOverride
351
     */
352
    public function getHrefSubToggleOverride() {
353
        return $this->hrefSubToggleOverride;
354
    }
355
356
    /**
357
     * @param string $hrefSubToggleOverride
358
     */
359
    public function setHrefSubToggleOverride($hrefSubToggleOverride) {
360
        $this->hrefSubToggleOverride = $hrefSubToggleOverride;
361
        return $this;
362
    }
363
364
    /**
365
     * Sets which partial view script to use for rendering menu
366
     *
367
     * @param    string|array $partial partial view script or null. If an array is
368
     *                                given, it is expected to contain two
369
     *                                values; the partial view script to use,
370
     *                                and the module where the script can be
371
     *                                found.
372
     * @return self
373
     */
374 View Code Duplication
    public function setHtmlifyPartial($partial)
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...
375
    {
376
        if (null === $partial || is_string($partial) || is_array($partial)) {
377
            $this->htmlifyPartial = $partial;
378
        }
379
    
380
        return $this;
381
    }
382
    
383
    /**
384
     * Returns partial view script to use for rendering menu
385
     *
386
     * @return string|array|null
387
     */
388
    public function getHtmlifyPartial()
389
    {
390
        return $this->htmlifyPartial;
391
    }
392
    
393
    /**
394
     * View helper entry point:
395
     * Retrieves helper and optionally sets component options to operate on
396
     *
397
     * @param  array|StdClass $options [optional] component options to operate on
398
     * @return self
399
     */
400
    public function __invoke($options = array())
401
    {
402
        parent::__invoke($options);
403
        return $this;
404
    }
405
    
406
    /**
407
     * render component
408
     *
409
     * @param boolean $output
410
     *
411
     * @return string
412
     */
413 View Code Duplication
    public function render($output = false)
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...
414
    {
415
        try {
416
             
417
            if ($output) {
418
                echo $this->buildComponent();
419
            }
420
            return $this->buildComponent();
421
                
422
        } catch (\Exception $e) {
423
             
424
            $msg = get_class($e) . ': ' . $e->getMessage() . "\n" . $e->getTraceAsString();
425
            trigger_error($msg, E_USER_ERROR);
426
            return '';
427
    
428
        }
429
    }
430
    
431
    /**
432
     * build markup
433
     *
434
     * @return string
435
     */
436
    public function buildComponent()
437
    {    
438
        if (!($detector = $this->getDetector())) {
439
            throw new \RuntimeException('To assemble an url, a detector is required');
440
        }
441
442
        $class = $this->getUlClass();
443
        $liclass = $this->getSubLiClassLevel0();
444
        $subulclass = $this->getSubUlClassLevel1();
445
        $iconprefixclass = $this->getIconPrefixClass();
446
447
        $list     = '';
448
        $current  = Locale::getDefault();
449
        foreach($detector->getSupported() as $locale) {
450
            if ($this->omitCurrent() && $current === $locale) {
451
                continue;
452
            }
453
454
            $titleLocale = $this->getTitleInCurrentLocale() ? $locale : $current;
455
            $labelLocale = $this->getLabelInCurrentLocale() ? $locale : $current;
456
457
            $url   = $this->getView()->localeUrl($locale);
0 ignored issues
show
Bug introduced by
The method localeUrl() does not seem to exist on object<Zend\View\Renderer\RendererInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
458
            $title = $this->getLocaleProperty($this->getTitleMethod(), $locale, $titleLocale);
0 ignored issues
show
Unused Code introduced by
$title is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
459
            $label = $this->getLocaleProperty($this->getLabelMethod(), $locale, $labelLocale);
460
            $primary = $this->getLocaleProperty('primaryLanguage', $locale, true);
461
            $displayName = $this->getLocaleProperty('displayName', $locale, $labelLocale);
462
463
            $item = sprintf(
464
                '<li><a href="%s" title="%s"%s>%s</a></li>' . "\n",
465
                $url,
466
                $displayName,
467
                ($current === $locale) ? ' class="active"' : '',
468
                (($iconprefixclass) ? '<span class="' . $iconprefixclass . $primary . '"></span> ' : '') . $label
469
            );
470
471
            $list .= $item;
472
        }
473
        $attributes = $this->getAttributes();
474
        $html  = 
475
            '<ul'.(($class) ? sprintf(' class="%s"', $class) : '').' '.($this->htmlAttribs($attributes)).'>'.
476
                '<li'.(($liclass) ? sprintf(' class="%s"', $liclass) : '').'>'.
477
                    '<a href="" class="'.(($liclass) ? $liclass.'-toggle' : '').'" data-toggle="'.(($liclass) ? $liclass : '').'" role="button" aria-haspopup="true" aria-expanded="false" title="'.Locale::getDisplayName(null).'">'.
478
                        '<span class="'.(($iconprefixclass) ? $iconprefixclass : '').Locale::getPrimaryLanguage(null).'"></span> '.
479
                        ''.Locale::getDisplayLanguage(null). // ' - '.Locale::getDefault().' - '.Locale::getPrimaryLanguage(null).''.
480
                        '<span class="caret"></span>'.
481
                    '</a>'.
482
                       sprintf(
483
                        '<ul%s>%s</ul>',
484
                        ($subulclass) ? sprintf(' class="%s"', $subulclass) : '',
485
                        $list
486
                       ).
487
                   '</li>'.
488
            '</ul>'
489
        ;
490
491
        return $html;
492
        
493
        return '<h2>'.__CLASS__.'</h2>';
0 ignored issues
show
Unused Code introduced by
return '<h2>' . __CLASS__ . '</h2>'; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
494
    }
495
    
496
    /**
497
     * Check whether method part of the Locale class is
498
     *
499
     * @param  string $method Method to check
500
     * @throws RuntimeException If method is not part of locale
501
     * @return true
502
     */
503
    protected function checkLocaleMethod($method)
504
    {
505
        $options = array(
506
                'displayLanguage',
507
                'displayName',
508
                'displayRegion',
509
                'displayScript',
510
                'displayVariant',
511
                'primaryLanguage',
512
                'region',
513
                'script'
514
        );
515
    
516
        if (!in_array($method, $options)) {
517
            throw new RuntimeException(sprintf(
518
                    'Unknown method "%s" for Locale, expecting one of these: %s.',
519
                    $method,
520
                    implode(', ', $options)
521
            ));
522
        }
523
    }
524
    
525
    /**
526
     * Retrieves a value by property from Locale
527
     *
528
     * @param $property
529
     * @param $locale
530
     * @param bool $in_locale
531
     * @return mixed
532
     */
533
    protected function getLocaleProperty($property, $locale, $in_locale = false)
534
    {
535
        $callback = sprintf('\Locale::get%s', ucfirst($property));
536
    
537
        $args = array($locale);
538
    
539
        if ($in_locale && !in_array($property, array('primaryLanguage', 'region', 'script'))) {
540
            $args[] = $in_locale;
541
        }
542
    
543
        return call_user_func_array($callback, $args);
544
    }
545
    
546
}