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

Menu::renderNormalMenu()   F

Complexity

Conditions 35
Paths > 20000

Size

Total Lines 136

Duplication

Lines 53
Ratio 38.97 %

Importance

Changes 0
Metric Value
dl 53
loc 136
rs 0
c 0
b 0
f 0
cc 35
nc 124472
nop 9

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\Navigation;
17
18
use \RecursiveIteratorIterator;
19
use \Zend\Navigation\AbstractContainer;
20
use \Zend\Navigation\Page\AbstractPage;
21
use \Zend\View\Exception;
22
23
/**
24
 *
25
 * Helper for recursively rendering 'Bootstrap' compatible multi-level menus
26
 *
27
 */
28
class Menu extends \Zend\View\Helper\Navigation\Menu
29
{
30
	use \UIComponents\View\Helper\Traits\ComponentClassnamesTrait;
31
	use \UIComponents\View\Helper\Traits\ComponentAttributesTrait;
32
33
    /**
34
     * default CSS class to use for li elements
35
     *
36
     * @var string
37
     */
38
    protected $defaultLiClass = '';
39
40
    /**
41
     * CSS class to use for the ul sub-menu element
42
     *
43
     * @var string
44
     */
45
    protected $subUlClass = 'dropdown-menu';
46
47
    /**
48
     * CSS class to use for the 1. level (NOT root level!) ul sub-menu element
49
     *
50
     * @var string
51
     */
52
    protected $subUlClassLevel1 = 'dropdown-menu';
53
54
    /**
55
     * CSS class to use for the active li sub-menu element
56
     *
57
     * @var string
58
     */
59
    protected $subLiClass = 'dropdown-submenu';
60
61
    /**
62
     * CSS class to use for the active li sub-menu element
63
     *
64
     * @var string
65
     */
66
    protected $subLiClassLevel0 = 'dropdown';
67
68
    /**
69
     * CSS class prefix to use for the menu element's icon class
70
     *
71
     * @var string
72
     */
73
    protected $iconPrefixClass = 'icon-';
74
75
    /**
76
     * HREF string to use for the sub-menu toggle element's HREF attribute, 
77
     * to override current page's href/'htmlify' setting
78
     *
79
     * @var string
80
     */
81
    protected $hrefSubToggleOverride = null;
82
83
    /**
84
     * Partial view script to use for rendering menu link/item
85
     *
86
     * @var string|array
87
     */
88
    protected $htmlifyPartial = null;
89
    
90
    
91
92
    /**
93
     * View helper entry point:
94
     * Retrieves helper and optionally sets container to operate on
95
     *
96
     * @param    AbstractContainer $container [optional] container to operate on
97
     * @return self
98
     */
99
    public function __invoke($container = null)
100
    {
101
        if (null !== $container) {
102
            $this->setContainer($container);
103
        }
104
105
        return $this;
106
    }
107
    
108
    /**
109
     * Returns the navigation container helper operates on by default
110
     *
111
     * Implements {@link HelperInterface::getContainer()}.
112
     *
113
     * If no container is set, a new container will be instantiated and
114
     * stored in the helper.
115
     *
116
     * @return Navigation\AbstractContainer    navigation container
117
     */
118
    public function getContainer()
119
    {
120
        if (null === $this->container) {
121
            $this->container = new \UIComponents\Navigation\Navigation();
122
        }
123
124
        return $this->container;
125
    }
126
127
    /**
128
     * Renders helper
129
     *
130
     * Renders a HTML 'ul' for the given $container. If $container is not given,
131
     * the container registered in the helper will be used.
132
     *
133
     * Available $options:
134
     *
135
     *
136
     * @param    AbstractContainer $container [optional] container to create menu from.
137
     *                                        Default is to use the container retrieved
138
     *                                        from {@link getContainer()}.
139
     * @param    array             $options    [optional] options for controlling rendering
140
     * @return string
141
     */
142
    public function renderMenu($container = null, array $options = [])
143
    {
144
        $this->parseContainer($container);
145
        if (null === $container) {
146
            $container = $this->getContainer();
147
        }
148
149
150
        $options = $this->normalizeOptions($options);
151
152
        if ($options['onlyActiveBranch'] && !$options['renderParents']) {
153
            $html = $this->renderDeepestMenu(
154
                $container,
0 ignored issues
show
Bug introduced by
It seems like $container defined by parameter $container on line 142 can also be of type string; however, Zend\View\Helper\Navigat...nu::renderDeepestMenu() does only seem to accept object<Zend\Navigation\AbstractContainer>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
155
                $options['ulClass'],
156
                $options['indent'],
157
                $options['minDepth'],
158
                $options['maxDepth'],
159
                $options['escapeLabels'],
160
                $options['addClassToListItem'],
161
                $options['liActiveClass']
162
            );
163
        } else {
164
            $html = $this->renderNormalMenu(
165
                $container,
0 ignored issues
show
Bug introduced by
It seems like $container defined by parameter $container on line 142 can also be of type string; however, UIComponents\View\Helper...enu::renderNormalMenu() does only seem to accept object<Zend\Navigation\AbstractContainer>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
166
                $options['ulClass'],
167
                $options['indent'],
168
                $options['minDepth'],
169
                $options['maxDepth'],
170
                $options['onlyActiveBranch'],
171
                $options['escapeLabels'],
172
                $options['addClassToListItem'],
173
                $options['liActiveClass']
174
            );
175
        }
176
177
        return $html;
178
    }
179
180
    /**
181
     * Renders a normal menu (called from {@link renderMenu()})
182
     *
183
     * @param    AbstractContainer $container            container to render
184
     * @param    string            $ulClass            CSS class for first UL
185
     * @param    string            $indent             initial indentation
186
     * @param    int|null            $minDepth            minimum depth
187
     * @param    int|null            $maxDepth            maximum depth
188
     * @param    bool                $onlyActive         render only active branch?
189
     * @param    bool                $escapeLabels        Whether or not to escape the labels
190
     * @param    bool                $addClassToListItem Whether or not page class applied to <li> element
191
     * @param    string            $liActiveClass        CSS class for active LI
192
     * @return string
193
     */
194
    protected function renderNormalMenu(
195
        AbstractContainer $container,
196
        $ulClass,
197
        $indent,
198
        $minDepth,
199
        $maxDepth,
200
        $onlyActive,
201
        $escapeLabels,
202
        $addClassToListItem,
203
        $liActiveClass
204
    ) {
205
        $html = '';
206
207
        // find deepest active
208
        $found = $this->findActive($container, $minDepth, $maxDepth);
0 ignored issues
show
Documentation introduced by
$container is of type object<Zend\Navigation\AbstractContainer>, but the function expects a object<UIComponents\View...AbstractContainer>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
209
        /* @var $escaper \Zend\View\Helper\EscapeHtmlAttr */
210
        $escaper = $this->view->plugin('escapeHtmlAttr');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Zend\View\Renderer\RendererInterface as the method plugin() does only exist in the following implementations of said interface: Zend\View\Renderer\PhpRenderer.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
211
212 View Code Duplication
        if ($found) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $found of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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...
213
            $foundPage    = $found['page'];
214
            $foundDepth = $found['depth'];
215
        } else {
216
            $foundPage = null;
217
        }
218
219
        // create iterator
220
        $iterator = new RecursiveIteratorIterator(
221
            $container,
222
            RecursiveIteratorIterator::SELF_FIRST
223
        );
224
        if (is_int($maxDepth)) {
225
            $iterator->setMaxDepth($maxDepth);
226
        }
227
228
        // iterate container
229
        $prevDepth = -1;
230
        foreach ($iterator as $page) {
231
            $depth = $iterator->getDepth();
232
            $page->set('level', $depth);
233
            $isActive = $page->isActive(true);
234 View Code Duplication
            if ($depth < $minDepth || !$this->accept($page)) {
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...
235
                // page is below minDepth or not accepted by acl/visibility
236
                continue;
237
            } elseif ($onlyActive && !$isActive) {
238
                // page is not active itself, but might be in the active branch
239
                $accept = false;
240
                if ($foundPage) {
241
                    if ($foundPage->hasPage($page)) {
242
                        // accept if page is a direct child of the active page
243
                        $accept = true;
244
                    } elseif ($foundPage->getParent()->hasPage($page)) {
245
                        // page is a sibling of the active page...
246
                        if (!$foundPage->hasPages(!$this->renderInvisible) ||
247
                            is_int($maxDepth) && $foundDepth + 1 > $maxDepth) {
0 ignored issues
show
Bug introduced by
The variable $foundDepth does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
248
                            // accept if active page has no children, or the
249
                            // children are too deep to be rendered
250
                            $accept = true;
251
                        }
252
                    }
253
                }
254
255
                if (!$accept) {
256
                    continue;
257
                }
258
            }
259
260
            // make sure indentation is correct
261
            $depth -= $minDepth;
262
            $myIndent = $indent . str_repeat('    ', $depth);
263
            $attributes = $this->getAttributes();
264
            if ($depth > $prevDepth) {
265
                // start new ul tag
266
                $ulClass = '' . 
267
                    ($depth == 0 ? $this->getUlClass() : 
268
                            ($depth == 1 ? $this->getSubUlClassLevel1() : $this->getSubUlClass())
269
                    ) . 
270
                    ' level_' . $depth . 
271
                '';
272 View Code Duplication
                if ($ulClass && $depth ==    0) {
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...
273
                    $ulClass = ' class="' . $escaper($ulClass) . '"';
274
                } else {
275
                    $ulClass = ' class="' . $escaper($ulClass) . '"';
276
                }
277
                $html .= $myIndent . '<ul' . $ulClass . ' '.($depth == 0 ? $this->htmlAttribs($attributes) : '').'>' . PHP_EOL;
278
            } elseif ($prevDepth > $depth) {
279
                // close li/ul tags until we're at current depth
280 View Code Duplication
                for ($i = $prevDepth; $i > $depth; $i--) {
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...
281
                    $ind = $indent . str_repeat('        ', $i);
282
                    $html .= $ind . '    </li>' . PHP_EOL;
283
                    $html .= $ind . '</ul>' . PHP_EOL;
284
                }
285
                // close previous li tag
286
                $html .= $myIndent . '    </li>' . PHP_EOL;
287
            } else {
288
                // close previous li tag
289
                $html .= $myIndent . '    </li>' . PHP_EOL;
290
            }
291
292
            // render li tag and page
293
            $liClasses = [];
294
            // Is page active?
295
            if ($isActive) {
296
                $liClasses[] = $liActiveClass;
297
            }
298
            if (!empty($this->getDefaultLiClass())) {
299
                $liClasses[] = $this->getDefaultLiClass();
300
            }
301
            $isBelowMaxLevel = ($maxDepth > $depth) || ($maxDepth === null) || ($maxDepth === false);
302 View Code Duplication
            if (!empty($page->pages) && $isBelowMaxLevel) {
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...
303
                $liClasses[] = ($depth == 0 ? $this->getSubLiClassLevel0() : $this->getSubLiClass());
304
            }
305
            // Add CSS class from page to <li>
306
            if ($addClassToListItem && $page->getClass()) {
307
                $liClasses[] = $page->getClass();
308
            }
309
            $liClass = empty($liClasses) ? '' : ' class="' . $escaper(implode(' ', $liClasses)) . '"';
310
311
            $html .= $myIndent . '    <li' . $liClass . '>' . PHP_EOL
312
                . $myIndent . '        ' . $this->htmlify($page, $escapeLabels, $addClassToListItem) . PHP_EOL;
313
314
            // store as previous depth for next iteration
315
            $prevDepth = $depth;
316
        }
317
318 View Code Duplication
        if ($html) {
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...
319
            // done iterating container; close open ul/li tags
320
            for ($i = $prevDepth+1; $i > 0; $i--) {
321
                $myIndent = $indent . str_repeat('        ', $i-1);
322
                $html .= $myIndent . '    </li>' . PHP_EOL
323
                    . $myIndent . '</ul>' . PHP_EOL;
324
            }
325
            $html = rtrim($html, PHP_EOL);
326
        }
327
328
        return $html;
329
    }
330
331
    /**
332
     * Finds the deepest active page in the given container
333
     *
334
     * @param  Navigation\AbstractContainer $container  container to search
335
     * @param  int|null             $minDepth   [optional] minimum depth
336
     *                                          required for page to be
337
     *                                          valid. Default is to use
338
     *                                          {@link getMinDepth()}. A
339
     *                                          null value means no minimum
340
     *                                          depth required.
341
     * @param  int|null             $maxDepth   [optional] maximum depth
342
     *                                          a page can have to be
343
     *                                          valid. Default is to use
344
     *                                          {@link getMaxDepth()}. A
345
     *                                          null value means no maximum
346
     *                                          depth required.
347
     * @return array                            an associative array with
348
     *                                          the values 'depth' and
349
     *                                          'page', or an empty array
350
     *                                          if not found
351
     */
352
    public function findActive($container = null, $minDepth = null, $maxDepth = -1)
353
    {
354
    	if ( null == $container ) {
355
    		$container = $this->getContainer();
356
    	}
357
    	return parent::findActive($container, $minDepth, $maxDepth);
358
    }
359
    
360
    /**
361
     * Returns an HTML string containing an 'a' element for the given page if
362
     * the page's href is not empty, and a 'span' element if it is empty
363
     *
364
     * Overrides {@link AbstractHelper::htmlify()}.
365
     *
366
     * @param    AbstractPage $page                page to generate HTML for
367
     * @param    bool         $escapeLabel        Whether or not to escape the label
368
     * @param    bool         $addClassToListItem Whether or not to add the page class to the list item
369
     * @return string
370
     */
371
    public function htmlify(AbstractPage $page, $escapeLabel = true, $addClassToListItem = false)
372
    {
373
        $partial = $this->getHtmlifyPartial();
374
        if ($partial) {
375
            return $this->renderHtmlifyPartial($page, $escapeLabel, $addClassToListItem, $partial);
376
        }
377
        // get attribs for element
378
        $attribs = [
379
                'id'     => $page->getId(),
380
                'title'    => $this->translate($page->getTitle(), $page->getTextDomain()),
381
        ];
382
        $classnames = array();
383
        if ( $addClassToListItem === false ) {
384
            $class = $page->getClass();
385
            if (!empty($class)) {
386
                $classnames[] = $page->getClass();
387
            }
388
        }
389
        $maxDepth = $this->getMaxDepth();
390
        $depth = $page->get('level');
391
        $isBelowMaxLevel = ($maxDepth > $depth) || ($maxDepth === null) || ($maxDepth === false);
392
        if ( !empty($page->pages) && $isBelowMaxLevel ) {
393
            $classnames[] = 'dropdown-toggle';
394
            $attribs['data-toggle'] = (($depth == 0) ? $this->getSubLiClassLevel0() : $this->getSubLiClass());
395
        }
396
        $attribs['class'] = implode(" ", $classnames);
397
        
398
        // does page have a href?
399
        $href = (
400
            !empty($page->pages) && !empty($this->getHrefSubToggleOverride()) ?
401
                $this->getHrefSubToggleOverride() : $page->getHref()
402
        );
403
        $element = 'a';
404
        if ($href) {
405
            $attribs['href'] = $href;
406
            $attribs['target'] = $page->getTarget();
407
        } else {
408
            $attribs['href'] = '#';
409
        }
410
        
411
        $html    = '<' . $element . $this->htmlAttribs($attribs) . '>';
412
        $html .= ($page->get('icon') ? '<span class="' . $this->getIconPrefixClass() . '' . $page->get('icon') . '"></span> ' : '' );
413
        $label = $this->translate($page->getLabel(), $page->getTextDomain());
414
        if ($escapeLabel === true) {
415
            /** @var \Zend\View\Helper\EscapeHtml $escaper */
416
            $escaper = $this->view->plugin('escapeHtml');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Zend\View\Renderer\RendererInterface as the method plugin() does only exist in the following implementations of said interface: Zend\View\Renderer\PhpRenderer.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
417
            $html .= $escaper($label);
418
        } else {
419
            $html .= $label;
420
        }
421
        $html .= '</' . $element . '>';
422
    
423
        return $html;
424
    }
425
426
    /**
427
     * Renders the given $page by invoking the partial view helper
428
     *
429
     * The container will simply be passed on as a model to the view script
430
     * as-is, and will be available in the partial script as 'container', e.g.
431
     * <code>echo 'Number of pages: ', count($this->container);</code>.
432
     *
433
     * @param    string|array    $partial    [optional] partial view script to use.
434
     *                                    Default is to use the partial
435
     *                                    registered in the helper. If an array
436
     *                                    is given, it is expected to contain two
437
     *                                    values; the partial view script to use,
438
     *                                    and the module where the script can be
439
     *                                    found.
440
     * @return string
441
     * @throws Exception\RuntimeException if no partial provided
442
     * @throws Exception\InvalidArgumentException if partial is invalid array
443
     */
444
    public function renderHtmlifyPartial(AbstractPage $page, $escapeLabel = true, $addClassToListItem = false, $partial = null)
445
    {
446
        if (null === $partial) {
447
            $partial = $this->getPartial();
448
        }
449
    
450
        if (empty($partial)) {
451
            throw new Exception\RuntimeException(
452
                    'Unable to render menu: No partial view script provided'
453
                    );
454
        }
455
        $model = [
456
            'page' => $page,
457
            'escapeLabel' => $escapeLabel,
458
            'addClassToListItem' => $addClassToListItem,
459
            'menu' => (clone $this),
460
            
461
        ];
462
    
463
        /** @var \Zend\View\Helper\Partial $partialHelper */
464
        $partialHelper = $this->view->plugin('partial');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Zend\View\Renderer\RendererInterface as the method plugin() does only exist in the following implementations of said interface: Zend\View\Renderer\PhpRenderer.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
465
    
466
        if (is_array($partial)) {
467
            if (count($partial) != 2) {
468
                throw new Exception\InvalidArgumentException(
469
                        'Unable to render menu: A view partial supplied as '
470
                        .    'an array must contain two values: partial view '
471
                        .    'script and module where script can be found'
472
                        );
473
            }
474
    
475
            return $partialHelper($partial[0], $model);
476
        }
477
    
478
        return $partialHelper($partial, $model);
479
    }
480
    
481
    /**
482
     * @return the $defaultLiClass
483
     */
484
    public function getDefaultLiClass() {
485
        return $this->defaultLiClass;
486
    }
487
488
    /**
489
     * @param string $defaultLiClass
490
     */
491
    public function setDefaultLiClass($defaultLiClass) {
492
        $this->defaultLiClass = $defaultLiClass;
493
        return $this;
494
    }
495
496
    /**
497
     * @return the $subUlClass
498
     */
499
    public function getSubUlClass() {
500
        return $this->subUlClass;
501
    }
502
503
    /**
504
     * @param string $subUlClass
505
     */
506
    public function setSubUlClass($subUlClass) {
507
        $this->subUlClass = $subUlClass;
508
        return $this;
509
    }
510
511
    /**
512
     * @return the $subUlClassLevel1
513
     */
514
    public function getSubUlClassLevel1() {
515
        return $this->subUlClassLevel1;
516
    }
517
518
    /**
519
     * @param string $subUlClassLevel1
520
     */
521
    public function setSubUlClassLevel1($subUlClassLevel1) {
522
        $this->subUlClassLevel1 = $subUlClassLevel1;
523
        return $this;
524
    }
525
526
    /**
527
     * @return the $subLiClass
528
     */
529
    public function getSubLiClass() {
530
        return $this->subLiClass;
531
    }
532
533
    /**
534
     * @param string $subLiClass
535
     */
536
    public function setSubLiClass($subLiClass) {
537
        $this->subLiClass = $subLiClass;
538
        return $this;
539
    }
540
541
    /**
542
     * @return the $subLiClassLevel0
543
     */
544
    public function getSubLiClassLevel0() {
545
        return $this->subLiClassLevel0;
546
    }
547
    
548
    /**
549
     * @param string $subLiClassLevel0
550
     */
551
    public function setSubLiClassLevel0($subLiClassLevel0) {
552
        $this->subLiClassLevel0 = $subLiClassLevel0;
553
        return $this;
554
    }
555
    
556
    /**
557
     * @return the $iconPrefixClass
558
     */
559
    public function getIconPrefixClass() {
560
        return $this->iconPrefixClass;
561
    }
562
563
    /**
564
     * @param string $iconPrefixClass
565
     */
566
    public function setIconPrefixClass($iconPrefixClass) {
567
        $this->iconPrefixClass = $iconPrefixClass;
568
        return $this;
569
    }
570
    
571
    /**
572
     * @return the $hrefSubToggleOverride
573
     */
574
    public function getHrefSubToggleOverride() {
575
        return $this->hrefSubToggleOverride;
576
    }
577
578
    /**
579
     * @param string $hrefSubToggleOverride
580
     */
581
    public function setHrefSubToggleOverride($hrefSubToggleOverride) {
582
        $this->hrefSubToggleOverride = $hrefSubToggleOverride;
583
        return $this;
584
    }
585
586
    /**
587
     * Sets which partial view script to use for rendering menu
588
     *
589
     * @param    string|array $partial partial view script or null. If an array is
590
     *                                given, it is expected to contain two
591
     *                                values; the partial view script to use,
592
     *                                and the module where the script can be
593
     *                                found.
594
     * @return self
595
     */
596 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...
597
    {
598
        if (null === $partial || is_string($partial) || is_array($partial)) {
599
            $this->htmlifyPartial = $partial;
600
        }
601
    
602
        return $this;
603
    }
604
    
605
    /**
606
     * Returns partial view script to use for rendering menu
607
     *
608
     * @return string|array|null
609
     */
610
    public function getHtmlifyPartial()
611
    {
612
        return $this->htmlifyPartial;
613
    }
614
    
615
    /**
616
     * Translate a message (for label, title, …)
617
     *
618
     * @param    string $message    ID of the message to translate
619
     * @param    string $textDomain Text domain (category name for the translations)
620
     * @return string             Translated message
621
     */
622
    public function translate($message, $textDomain = null)
623
    {
624
        return parent::translate($message, $textDomain);
625
    }
626
    
627
628
}