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

AbstractHelper   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 368
Duplicated Lines 5.43 %

Coupling/Cohesion

Components 2
Dependencies 10

Importance

Changes 0
Metric Value
wmc 60
lcom 2
cbo 10
dl 20
loc 368
rs 3.6
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __call() 0 7 1
A __toString() 0 4 1
A render() 17 17 3
A isAllowed() 0 5 1
A htmlify() 0 20 1
A normalizeId() 0 7 1
A buildComponent() 3 22 4
D _createElement() 0 40 34
A getTagname() 0 3 1
A setTagname() 0 6 2
A getHeader() 0 3 1
A setHeader() 0 6 2
A getFooter() 0 3 1
A setFooter() 0 6 2
A getContent() 0 3 1
A setContent() 0 4 1
A getDOMDoc() 0 6 2
A setDOMDoc() 0 4 1

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 AbstractHelper 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 AbstractHelper, 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;
17
18
use RecursiveIteratorIterator;
19
use Zend\EventManager\EventManager;
20
use Zend\EventManager\EventManagerAwareInterface;
21
use Zend\EventManager\EventManagerInterface;
22
use Zend\I18n\Translator\TranslatorAwareInterface;
23
use Zend\Navigation\Page\AbstractPage;
24
use Zend\Navigation\AbstractContainer;
25
use Zend\Permissions\Acl;
26
use Zend\ServiceManager\ServiceLocatorAwareInterface;
27
use Zend\ServiceManager\ServiceLocatorInterface;
28
use Zend\View;
29
use Zend\View\Exception;
30
use \UIComponents\Translator\TranslatorAwareInterfaceTrait;
31
use \UIComponents\View\Helper\Traits\ComponentClassnamesTrait;
32
use \UIComponents\View\Helper\Traits\ComponentAttributesTrait;
33
use \UIComponents\View\Helper\Traits\ComponentServiceManagersTrait;
34
use \UIComponents\View\Helper\Traits\ComponentAclTrait;
35
use \UIComponents\View\Helper\Traits\ComponentNavigationTrait;
36
37
/**
38
 * Base class for navigational helpers
39
 */
40
abstract class AbstractHelper extends \Zend\View\Helper\AbstractHtmlElement implements
41
    EventManagerAwareInterface,
42
    HelperInterface,
43
    ServiceLocatorAwareInterface,
44
    TranslatorAwareInterface
45
{
46
    
47
    use TranslatorAwareInterfaceTrait;
48
    use ComponentClassnamesTrait;
49
    use ComponentAttributesTrait;
50
    use ComponentServiceManagersTrait;
51
    use ComponentAclTrait;
52
    use ComponentNavigationTrait;
53
    
54
    //
55
    // component related vars/properties/providers/services...
56
    //
57
    
58
    /**
59
     * component's tag-name
60
     *
61
     * @var string
62
     */
63
    protected $tagname = 'div';
64
    
65
    
66
    /**
67
     * component's header
68
     *
69
     * @var mixed
70
     */
71
    protected $header = null;
72
    
73
    /**
74
     * component's footer
75
     *
76
     * @var mixed
77
     */
78
    protected $footer = null;
79
    
80
    /**
81
     * component's main content
82
     *
83
     * @var string|array
84
     */
85
    protected $content = '';
86
    
87
    /**
88
     * component's size attributes
89
     *
90
     * @var string|array
91
     */
92
    protected $size = '';
93
    
94
    
95
96
    /**
97
     * DOM object container needed for element creation with php's default DOM method
98
     * 
99
     * @var \DOMDocument
100
     */
101
    protected $_DOMDoc = null;
102
    
103
    
104
105
   /**
106
     * Magic overload: Proxy calls to the navigation container
107
     *
108
     * @param    string $method    method name in container
109
     * @param    array    $arguments rguments to pass
110
     * @return mixed
111
     * @throws Navigation\Exception\ExceptionInterface
112
     */
113
    public function __call($method, array $arguments = [])
114
    {
115
        return call_user_func_array(
116
            [$this->getContainer(), $method],
117
            $arguments
118
        );
119
    }
120
121
    /**
122
     * Magic overload: Proxy to {@link render()}.
123
     *
124
     * This method will trigger an E_USER_ERROR if rendering the helper causes
125
     * an exception to be thrown.
126
     *
127
     * Implements {@link HelperInterface::__toString()}.
128
     *
129
     * @return string
130
     */
131
    public function __toString()
132
    {
133
        return $this->render();
134
    }
135
136
    /**
137
     * render component
138
     * 
139
     * @param boolean $output
140
     * 
141
     * @return string
142
     */
143 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...
144
    {    
145
        try {
146
            
147
            if ($output) {
148
                echo $this->buildComponent();
149
            }
150
            return $this->buildComponent();
151
            
152
        } catch (\Exception $e) {
153
            
154
            $msg = get_class($e) . ': ' . $e->getMessage() . "\n" . $e->getTraceAsString();
155
            trigger_error($msg, E_USER_ERROR);
156
            return '';
157
            
158
        }
159
    }
160
161
    /**
162
     * Determines whether a page should be allowed given certain parameters
163
     *
164
     * @param    array    $params
165
     * @return    bool
166
     */
167
    protected function isAllowed($params)
168
    {
169
        $results = $this->getEventManager()->trigger(__FUNCTION__, $this, $params);
170
        return $results->last();
171
    }
172
173
    // Util methods:
174
175
    /**
176
     * Returns an HTML string containing an 'a' element for the given page
177
     *
178
     * @param    AbstractPage $page    page to generate HTML for
179
     * @return string                HTML string (<a href="…">Label</a>)
180
     */
181
    public function htmlify(AbstractPage $page)
182
    {
183
        $label = $this->translate($page->getLabel(), $page->getTextDomain());
184
        $title = $this->translate($page->getTitle(), $page->getTextDomain());
185
186
        // get attribs for anchor element
187
        $attribs = [
188
            'id'     => $page->getId(),
189
            'title'    => $title,
190
            'class'    => $page->getClass(),
191
            'href'    => $page->getHref(),
192
            'target' => $page->getTarget()
193
        ];
194
195
        /** @var \Zend\View\Helper\EscapeHtml $escaper */
196
        $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...
197
        $label    = $escaper($label);
198
199
        return '<a' . $this->htmlAttribs($attribs) . '>' . $label . '</a>';
200
    }
201
202
    /**
203
     * Normalize an ID
204
     *
205
     * Overrides {@link View\Helper\AbstractHtmlElement::normalizeId()}.
206
     *
207
     * @param    string $value
208
     * @return string
209
     */
210
    protected function normalizeId($value)
211
    {
212
        $prefix = get_class($this);
213
        $prefix = strtolower(trim(substr($prefix, strrpos($prefix, '\\')), '\\'));
214
215
        return $prefix . '-' . $value;
216
    }
217
218
    //
219
    // component related methods
220
    //
221
    
222
    /**
223
     * @return string the assemled component rendered to HTML
224
     */
225
    public function buildComponent() {
226
        $html = ' '.__CLASS__.' ';
0 ignored issues
show
Unused Code introduced by
$html 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...
227
        if ( empty($this->getTagname()) ) {
228
            return '';
229
        }
230
        
231
        $header = $this->getHeader();
232
        
233
        $footer = $this->getFooter();
234
        
235
        $body = $this->getContent();
236
        
237
        $content = (array($header, $body, $footer));
238 View Code Duplication
        if ( is_array($body) && !isset($body["tagname"])) {
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...
239
            $content = array_merge(array($header), ($body), array($footer));
240
        }
241
        
242
        $component = $this->_createElement($this->getTagname(), $this->getClassnames(), (array)$this->getAttributes(), $content);
243
244
        return $component;
245
246
    } 
247
    
248
    /**
249
     * create the component markup
250
     * 
251
     * @param string $tagname 
252
     * @param string $classnames 
253
     * @param array $attributes 
254
     * @param string|mixed $content 
255
     * 
256
     * @return string the component markup
257
     */
258
    public function _createElement($tagname = 'div', $classnames = '', $attributes = array(), $content = '') {
259
        $html = '';
260
        $html .= '<'.$tagname.''.((isset($classnames) && !empty($classnames)) ? ' class="'.$classnames.'"' : '').' '.$this->htmlAttribs($attributes).'>';
261
        if (!empty($content)) {
262
            if ( $content instanceof AbstractHelper ) {
263
                $html .= $content->render();
264
            } else if ( is_array($content) && isset($content['tagname']) ) {
265
                $html .= $this->_createElement(
266
                    $content['tagname'],
267
                    (isset($content['classnames']) && !empty($content['classnames']) ? $content['classnames'] : (isset($content['class']) && !empty($content['class']) ? $content['class'] : '')),
268
                    (isset($content['attributes']) && !empty($content['attributes']) ? $content['attributes'] : (isset($content['attr']) && !empty($content['attr']) ? $content['attr'] : '')),
269
                    (isset($content['content']) && !empty($content['content']) ? $content['content'] : '')
270
                );
271
            } else if ( is_array($content) ) {
272
                foreach ($content as $key => $item) {
273
                    if ( $item instanceof AbstractHelper ) {
274
                        $html .= $item->render();
275
                    } else if ( is_array($item) && isset($item['tagname']) ) {
276
                        $html .= $this->_createElement(
277
                            $item['tagname'],
278
                            (isset($item['classnames']) && !empty($item['classnames']) ? $item['classnames'] : (isset($item['class']) && !empty($item['class']) ? $item['class'] : '')),
279
                            (array)(isset($item['attributes']) && !empty($item['attributes']) ? $item['attributes'] : (isset($item['attr']) && !empty($item['attr']) ? $item['attr'] : '')),
280
                            (isset($item['content']) && !empty($item['content']) ? $item['content'] : '')
281
                        );
282
                    } else if ( is_array($item) ) {
283
                        foreach ($content as $key => $value) {
284
                            $html .= (string)$value;
285
                        }
286
                    } else {
287
                        $html .= $item;
288
                    }
289
                }
290
            } else {
291
                $html .= $content;
292
            }
293
        }
294
        $html .= '</'.$tagname.'>';
295
        
296
        return $html;
297
    } 
298
    
299
    
300
    //
301
    // component related getters/setters
302
    //
303
    
304
    /**
305
     * get main tag-name
306
     * 
307
     * @return string the $tagname
308
     */
309
    public function getTagname() {
310
        return $this->tagname;
311
    }
312
313
    /**
314
     * set main tag-name
315
     * 
316
     * @param string $tagname
317
     */
318
    public function setTagname($tagname) {
319
        if ( null !== $tagname ) {
320
            $this->tagname = $tagname;
321
        }
322
        return $this;
323
    }
324
325
    /**
326
     * get the element header
327
     * 
328
     * @return mixed the $header
329
     */
330
    public function getHeader() {
331
        return $this->header;
332
    }
333
334
    /**
335
     * set the element header
336
     * 
337
     * @param mixed $header
338
     */
339
    public function setHeader($header) {
340
        if ( null !== $header ) {
341
            $this->header = $header;
342
        }
343
        return $this;
344
    }
345
346
    /**
347
     * get the element footer
348
     * 
349
     * @return mixed the $footer
350
     */
351
    public function getFooter() {
352
        return $this->footer;
353
    }
354
355
    /**
356
     * set the element footer
357
     * 
358
     * @param mixed $footer
359
     */
360
    public function setFooter($footer = null) {
361
        if ( null !== $footer ) {
362
            $this->footer = $footer;
363
        }
364
        return $this;
365
    }
366
367
    /**
368
     * get the element content
369
     * @return the $content
370
     */
371
    public function getContent() {
372
        return $this->content;
373
    }
374
375
    /**
376
     * set the element content
377
     * 
378
     * @param string|array $content
379
     */
380
    public function setContent($content = '') {
381
        $this->content = $content;
382
        return $this;
383
    }
384
    
385
    /**
386
     * get DOM object
387
     * 
388
     * @return the $_DOMDoc
389
     */
390
    public function getDOMDoc() {
391
        if ( ! $this->_DOMDoc instanceof \DOMDocument ) {
392
            $this->_DOMDoc = new \DOMDocument();
393
        }
394
        return $this->_DOMDoc;
395
    }
396
397
    /**
398
     * set DOM object
399
     * 
400
     * @param \DOMDocument $_DOMDoc
401
     */
402
    public function setDOMDoc(\DOMDocument $_DOMDoc) {
403
        $this->_DOMDoc = $_DOMDoc;
404
        return $this;
405
    }
406
    
407
}
408