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

AbstractProxyHelper   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 375
Duplicated Lines 2.13 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 45
lcom 1
cbo 6
dl 8
loc 375
rs 8.8
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __invoke() 8 8 3
B __call() 0 20 6
A setContainer() 0 7 1
A getContainer() 0 8 2
A render() 0 5 1
B findHelper() 0 33 7
B inject() 0 26 10
A setDefaultProxy() 0 5 1
A getDefaultProxy() 0 4 1
A setInjectContainer() 0 5 1
A getInjectContainer() 0 4 1
A setInjectAcl() 0 5 1
A getInjectAcl() 0 4 1
A setInjectTranslator() 0 5 1
A getInjectTranslator() 0 4 1
A setPluginManager() 0 10 2
A getPluginManager() 0 8 2
A setView() 0 8 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 AbstractProxyHelper 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 AbstractProxyHelper, 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 Zend\ServiceManager\ServiceLocatorAwareInterface;
19
use Zend\View\Exception;
20
use Zend\View\Renderer\RendererInterface as Renderer;
21
22
/**
23
 * Proxy helper for retrieving navigational helpers and forwarding calls
24
 */
25
class AbstractProxyHelper extends AbstractHelper
26
{
27
    /**
28
     * View helper namespace
29
     *
30
     * @var string
31
     */
32
    const NS = 'UIComponents\View\Helper\Components';
33
34
    /**
35
     * Default proxy to use in {@link render()}
36
     *
37
     * @var string
38
     */
39
    protected $defaultProxy = 'element';
40
41
    /**
42
     * Indicates whether or not a given helper has been injected
43
     *
44
     * @var array
45
     */
46
    protected $injected = [];
47
48
    /**
49
     * Whether ACL should be injected when proxying
50
     *
51
     * @var bool
52
     */
53
    protected $injectAcl = true;
54
55
    /**
56
     * Whether container should be injected when proxying
57
     *
58
     * @var bool
59
     */
60
    protected $injectContainer = true;
61
62
    /**
63
     * Whether translator should be injected when proxying
64
     *
65
     * @var bool
66
     */
67
    protected $injectTranslator = true;
68
69
    /**
70
     * @var Navigation\PluginManager
71
     */
72
    protected $plugins;
73
74
    /**
75
     * AbstractContainer to operate on by default
76
     *
77
     * @var Navigation\AbstractContainer|Navigation\Navigation|\Zend\Navigation\AbstractContainer
78
     */
79
    protected $container;
80
81
    /**
82
     * Helper entry point
83
     *
84
     * @param  array $options option to operate on
85
     * @return Components
86
     */
87 View Code Duplication
    public function __invoke($options = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
88
    {
89
        if (isset($options['container']) && null !== $options['container']) {
90
            $this->setContainer($options['container']);
91
        }
92
93
        return ($this);
94
    }
95
96
    /**
97
     * Magic overload: Proxy to other navigation helpers or the container
98
     *
99
     * Examples of usage from a view script or layout:
100
     * <code>
101
     *   echo $this->Components()->Widget(...);
102
     * </code>
103
     *
104
     * @param  string $method             helper name or method name in container
105
     * @param  array  $arguments          [optional] arguments to pass
106
     * @throws \Zend\View\Exception\ExceptionInterface        if proxying to a helper, and the
107
     *                                    helper is not an instance of the
108
     *                                    interface specified in
109
     *                                    {@link findHelper()}
110
     * @throws \Zend\View\Exception\ExceptionInterface  if method does not exist in container
111
     * @return mixed                      returns what the proxied call returns
112
     */
113
    public function __call($method, array $arguments = [])
114
    {
115
        // check if call should proxy to another helper
116
        $helper = $this->findHelper($method, false);
117
        if ($helper) {
118
            //if ($helper instanceof ServiceLocatorAwareInterface && $this->getServiceLocator()) {
119
            if (method_exists($helper, "setServiceLocator") && $this->getServiceLocator()) {
120
                $helper->setServiceLocator($this->getServiceLocator());
121
            }
122
123
            if (method_exists($helper, "setTranslator") && $this->getTranslator()) {
124
                $helper->setTranslator($this->getTranslator());
125
            }
126
127
            return call_user_func_array($helper, $arguments);
128
        }
129
130
        // default behaviour: proxy call to container
131
        return parent::__call($method, $arguments);
132
    }
133
134
    /**
135
     * Sets navigation container the helper operates on by default
136
     *
137
     * Implements {@link HelperInterface::setContainer()}.
138
     *
139
     * @param  string|Navigation\AbstractContainer $container Default is null, meaning container will be reset.
140
     * @return AbstractHelper
141
     */
142
    public function setContainer($container = null)
143
    {
144
        $this->parseContainer($container);
145
        $this->container = $container;
0 ignored issues
show
Documentation Bug introduced by
It seems like $container can also be of type object<UIComponents\View...tion\AbstractContainer> or string. However, the property $container is declared as type object<UIComponents\View...tion\AbstractContainer>. 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...
146
147
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (UIComponents\View\Helper\AbstractProxyHelper) is incompatible with the return type of the parent method UIComponents\View\Helper...actHelper::setContainer of type UIComponents\View\Helper...omponentNavigationTrait.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
148
    }
149
150
    /**
151
     * Returns the navigation container helper operates on by default
152
     *
153
     * Implements {@link HelperInterface::getContainer()}.
154
     *
155
     * If no container is set, a new container will be instantiated and
156
     * stored in the helper.
157
     *
158
     * @return Navigation\AbstractContainer|\Zend\Navigation\AbstractContainer    navigation container
159
     */
160
    public function getContainer()
161
    {
162
        if (null === $this->container) {
163
            $this->container = new \UIComponents\Navigation\Navigation();
164
        }
165
166
        return $this->container;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->container; (UIComponents\View\Helper...ation\AbstractContainer) is incompatible with the return type declared by the interface UIComponents\View\Helper...Interface::getContainer of type Zend\Navigation\AbstractContainer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
167
    }
168
169
    /**
170
     * Renders helper
171
     *
172
     * @param  AbstractContainer $container
173
     * @return string
174
     * @throws Exception\RuntimeException
175
     */
176
    public function render($container = null)
177
    {
178
        return '';
179
        //return $this->findHelper($this->getDefaultProxy())->render($container);
180
    }
181
182
    /**
183
     * Returns the helper matching $proxy
184
     *
185
     * The helper must implement the interface
186
     * {@link UIComponents\View\Helper\Components\HelperInterface}.
187
     *
188
     * @param string $proxy  helper name
189
     * @param bool   $strict [optional] whether exceptions should be
190
     *                                  thrown if something goes
191
     *                                  wrong. Default is true.
192
     * @throws Exception\RuntimeException if $strict is true and helper cannot be found
193
     * @return boolean|\UIComponents\View\Helper\Components\HelperInterface  helper instance
194
     */
195
    public function findHelper($proxy, $strict = true)
196
    {
197
        $plugins = $this->getPluginManager();
198
        if (!$plugins->has($proxy)) {
199
            if ($strict) {
200
                throw new Exception\RuntimeException(sprintf(
201
                    'Failed to find plugin for %s',
202
                    $proxy
203
                ));
204
            }
205
            return false;
206
        }
207
208
        $helper    = $plugins->get($proxy);
209
210
        if ($helper && ($helper instanceof \Zend\View\Helper\Navigation\Menu)) {
211
212
            $container = $this->getContainer();
213
            $hash      = spl_object_hash($container) . spl_object_hash($helper);
214
            if (!isset($this->injected[$hash])) {
215
                $helper->setContainer();
216
                $this->inject($helper);
217
                $this->injected[$hash] = true;
218
            } else {
219
                if ($this->getInjectContainer()) {
220
                    $helper->setContainer($container);
221
                }
222
            }
223
224
        }
225
226
        return $helper;
227
    }
228
229
    /**
230
     * Injects container, ACL, and translator to the given $helper if this
231
     * helper is configured to do so
232
     *
233
     * @param  \Zend\View\Helper\AbstractHelper $helper helper instance
234
     * @return void
235
     */
236
    protected function inject(\Zend\View\Helper\AbstractHelper $helper)
237
    {
238
		if (
239
			($helper instanceof \UIComponents\View\Helper\AbstractHelper) ||
240
			($helper instanceof \UIComponents\View\Helper\Navigation\Menu)
241
		) {
242
	        if ($this->getInjectContainer() && !$helper->hasContainer()) {
243
	            $helper->setContainer($this->getContainer());
0 ignored issues
show
Bug introduced by
It seems like $this->getContainer() targeting UIComponents\View\Helper...yHelper::getContainer() can also be of type object<Zend\Navigation\AbstractContainer>; however, UIComponents\View\Helper...onTrait::setContainer() does only seem to accept string|object<UIComponen...AbstractContainer>|null, maybe add an additional type check?

This check looks at variables that 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...
244
	        }
245
246
	        if ($this->getInjectAcl()) {
247
	            if (!$helper->hasAcl()) {
248
	                $helper->setAcl($this->getAcl());
249
	            }
250
	            if (!$helper->hasRole()) {
251
	                $helper->setRole($this->getRole());
252
	            }
253
	        }
254
	        if ($this->getInjectTranslator() && !$helper->hasTranslator()) {
255
	            $helper->setTranslator(
256
	                $this->getTranslator(),
257
	                $this->getTranslatorTextDomain()
258
	            );
259
	        }
260
		}
261
    }
262
263
    /**
264
     * Sets the default proxy to use in {@link render()}
265
     *
266
     * @param  string $proxy default proxy
267
     * @return Bootstrap
268
     */
269
    public function setDefaultProxy($proxy)
270
    {
271
        $this->defaultProxy = (string) $proxy;
272
        return $this;
273
    }
274
275
    /**
276
     * Returns the default proxy to use in {@link render()}
277
     *
278
     * @return string
279
     */
280
    public function getDefaultProxy()
281
    {
282
        return $this->defaultProxy;
283
    }
284
285
    /**
286
     * Sets whether container should be injected when proxying
287
     *
288
     * @param  bool $injectContainer
289
     * @return Bootstrap
290
     */
291
    public function setInjectContainer($injectContainer = true)
292
    {
293
        $this->injectContainer = (bool) $injectContainer;
294
        return $this;
295
    }
296
297
    /**
298
     * Returns whether container should be injected when proxying
299
     *
300
     * @return bool
301
     */
302
    public function getInjectContainer()
303
    {
304
        return $this->injectContainer;
305
    }
306
307
    /**
308
     * Sets whether ACL should be injected when proxying
309
     *
310
     * @param  bool $injectAcl
311
     * @return NavigBootstrapation
312
     */
313
    public function setInjectAcl($injectAcl = true)
314
    {
315
        $this->injectAcl = (bool) $injectAcl;
316
        return $this;
317
    }
318
319
    /**
320
     * Returns whether ACL should be injected when proxying
321
     *
322
     * @return bool
323
     */
324
    public function getInjectAcl()
325
    {
326
        return $this->injectAcl;
327
    }
328
329
    /**
330
     * Sets whether translator should be injected when proxying
331
     *
332
     * @param  bool $injectTranslator
333
     * @return Bootstrap
334
     */
335
    public function setInjectTranslator($injectTranslator = true)
336
    {
337
        $this->injectTranslator = (bool) $injectTranslator;
338
        return $this;
339
    }
340
341
    /**
342
     * Returns whether translator should be injected when proxying
343
     *
344
     * @return bool
345
     */
346
    public function getInjectTranslator()
347
    {
348
        return $this->injectTranslator;
349
    }
350
351
    /**
352
     * Set manager for retrieving navigation helpers
353
     *
354
     * @param  Components\PluginManager $plugins
355
     * @return Components
356
     */
357
    public function setPluginManager(AbstractPluginManager $plugins)
358
    {
359
        $renderer = $this->getView();
360
        if ($renderer) {
361
            $plugins->setRenderer($renderer);
362
        }
363
        $this->plugins = $plugins;
0 ignored issues
show
Documentation Bug introduced by
It seems like $plugins of type object<UIComponents\View...\AbstractPluginManager> is incompatible with the declared type object<UIComponents\View...vigation\PluginManager> of property $plugins.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
364
365
        return $this;
366
    }
367
368
    /**
369
     * Retrieve plugin loader for navigation helpers
370
     *
371
     * Lazy-loads an instance of Navigation\HelperLoader if none currently
372
     * registered.
373
     *
374
     * @return Components\PluginManager
375
     */
376
    public function getPluginManager()
377
    {
378
        if (null === $this->plugins) {
379
            $this->setPluginManager(new Components\PluginManager());
380
        }
381
382
        return $this->plugins;
383
    }
384
385
    /**
386
     * Set the View object
387
     *
388
     * @param  Renderer $view
389
     * @return self
390
     */
391
    public function setView(Renderer $view)
392
    {
393
        parent::setView($view);
394
        if ($view && $this->plugins) {
395
            $this->plugins->setRenderer($view);
396
        }
397
        return $this;
398
    }
399
}
400