Completed
Push — master ( cbc143...48298c )
by Misbahul D
06:23 queued 04:21
created

Helper   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 234
Duplicated Lines 5.13 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 55
c 1
b 1
f 0
lcom 1
cbo 8
dl 12
loc 234
rs 6.8

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getRegisteredRoutes() 0 13 4
C getDefaultRoutes() 0 27 8
C getRoutesByUser() 0 24 7
C checkRoute() 12 38 12
B normalizeRoute() 0 19 6
A filter() 0 7 2
B filterActionColumn() 0 15 5
A invalidate() 0 6 2
B filterRecursive() 0 20 9

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

1
<?php
2
3
namespace mdm\admin\components;
4
5
use mdm\admin\models\Route;
6
use Yii;
7
use yii\caching\TagDependency;
8
use yii\helpers\ArrayHelper;
9
use yii\web\User;
10
11
/**
12
 * Description of Helper
13
 *
14
 * @author Misbahul D Munir <[email protected]>
15
 * @since 2.3
16
 */
17
class Helper
18
{
19
    private static $_userRoutes = [];
20
    private static $_defaultRoutes;
21
    private static $_routes;
22
23
    public static function getRegisteredRoutes()
24
    {
25
        if (self::$_routes === null) {
26
            self::$_routes = [];
27
            $manager = Configs::authManager();
28
            foreach ($manager->getPermissions() as $item) {
29
                if ($item->name[0] === '/') {
30
                    self::$_routes[$item->name] = $item->name;
31
                }
32
            }
33
        }
34
        return self::$_routes;
35
    }
36
37
    /**
38
     * Get assigned routes by default roles
39
     * @return array
40
     */
41
    protected static function getDefaultRoutes()
42
    {
43
        if (self::$_defaultRoutes === null) {
44
            $manager = Configs::authManager();
45
            $roles = $manager->defaultRoles;
0 ignored issues
show
Bug introduced by
Accessing defaultRoles on the interface yii\rbac\ManagerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
46
            $cache = Configs::cache();
47
            if ($cache && ($routes = $cache->get($roles)) !== false) {
48
                self::$_defaultRoutes = $routes;
49
            } else {
50
                $permissions = self::$_defaultRoutes = [];
51
                foreach ($roles as $role) {
52
                    $permissions = array_merge($permissions, $manager->getPermissionsByRole($role));
53
                }
54
                foreach ($permissions as $item) {
55
                    if ($item->name[0] === '/') {
56
                        self::$_defaultRoutes[$item->name] = true;
57
                    }
58
                }
59
                if ($cache) {
60
                    $cache->set($roles, self::$_defaultRoutes, Configs::cacheDuration(), new TagDependency([
61
                        'tags' => Configs::CACHE_TAG,
62
                    ]));
63
                }
64
            }
65
        }
66
        return self::$_defaultRoutes;
67
    }
68
69
    /**
70
     * Get assigned routes of user.
71
     * @param integer $userId
72
     * @return array
73
     */
74
    public static function getRoutesByUser($userId)
75
    {
76
        if (!isset(self::$_userRoutes[$userId])) {
77
            $cache = Configs::cache();
78
            if ($cache && ($routes = $cache->get([__METHOD__, $userId])) !== false) {
79
                self::$_userRoutes[$userId] = $routes;
80
            } else {
81
                $routes = static::getDefaultRoutes();
82
                $manager = Configs::authManager();
83
                foreach ($manager->getPermissionsByUser($userId) as $item) {
84
                    if ($item->name[0] === '/') {
85
                        $routes[$item->name] = true;
86
                    }
87
                }
88
                self::$_userRoutes[$userId] = $routes;
89
                if ($cache) {
90
                    $cache->set([__METHOD__, $userId], $routes, Configs::cacheDuration(), new TagDependency([
91
                        'tags' => Configs::CACHE_TAG,
92
                    ]));
93
                }
94
            }
95
        }
96
        return self::$_userRoutes[$userId];
97
    }
98
99
    /**
100
     * Check access route for user.
101
     * @param string|array $route
102
     * @param integer|User $user
103
     * @return boolean
104
     */
105
    public static function checkRoute($route, $params = [], $user = null)
106
    {
107
        $config = Configs::instance();
108
        $r = static::normalizeRoute($route, $config->advanced);
0 ignored issues
show
Bug introduced by
It seems like $route defined by parameter $route on line 105 can also be of type array; however, mdm\admin\components\Helper::normalizeRoute() does only seem to accept string, 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...
109
        if ($config->onlyRegisteredRoute && !isset(static::getRegisteredRoutes()[$r])) {
110
            return true;
111
        }
112
113
        if ($user === null) {
114
            $user = Yii::$app->getUser();
0 ignored issues
show
Bug introduced by
The method getUser does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
115
        }
116
        $userId = $user instanceof User ? $user->getId() : $user;
117
118
        if ($config->strict) {
119
            if ($user->can($r, $params)) {
120
                return true;
121
            }
122 View Code Duplication
            while (($pos = strrpos($r, '/')) > 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...
123
                $r = substr($r, 0, $pos);
124
                if ($user->can($r . '/*', $params)) {
125
                    return true;
126
                }
127
            }
128
            return $user->can('/*', $params);
129
        } else {
130
            $routes = static::getRoutesByUser($userId);
131
            if (isset($routes[$r])) {
132
                return true;
133
            }
134 View Code Duplication
            while (($pos = strrpos($r, '/')) > 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...
135
                $r = substr($r, 0, $pos);
136
                if (isset($routes[$r . '/*'])) {
137
                    return true;
138
                }
139
            }
140
            return isset($routes['/*']);
141
        }
142
    }
143
144
    /**
145
     * Normalize route
146
     * @param  string  $route    Plain route string
147
     * @param  boolean|array $advanced Array containing the advanced configuration. Defaults to false.
148
     * @return string            Normalized route string
149
     */
150
    protected static function normalizeRoute($route, $advanced = false)
151
    {
152
        if ($route === '') {
153
            $normalized = '/' . Yii::$app->controller->getRoute();
154
        } elseif (strncmp($route, '/', 1) === 0) {
155
            $normalized = $route;
156
        } elseif (strpos($route, '/') === false) {
157
            $normalized = '/' . Yii::$app->controller->getUniqueId() . '/' . $route;
158
        } elseif (($mid = Yii::$app->controller->module->getUniqueId()) !== '') {
159
            $normalized = '/' . $mid . '/' . $route;
160
        } else {
161
            $normalized = '/' . $route;
162
        }
163
        // Prefix @app-id to route.
164
        if ($advanced) {
165
            $normalized = Route::PREFIX_ADVANCED . Yii::$app->id . $normalized;
166
        }
167
        return $normalized;
168
    }
169
170
    /**
171
     * Filter menu items
172
     * @param array $items
173
     * @param integer|User $user
174
     */
175
    public static function filter($items, $user = null)
176
    {
177
        if ($user === null) {
178
            $user = Yii::$app->getUser();
0 ignored issues
show
Bug introduced by
The method getUser does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
179
        }
180
        return static::filterRecursive($items, $user);
181
    }
182
183
    /**
184
     * Filter menu recursive
185
     * @param array $items
186
     * @param integer|User $user
187
     * @return array
188
     */
189
    protected static function filterRecursive($items, $user)
190
    {
191
        $result = [];
192
        foreach ($items as $i => $item) {
193
            $url = ArrayHelper::getValue($item, 'url', '#');
194
            $allow = is_array($url) ? static::checkRoute($url[0], array_slice($url, 1), $user) : true;
195
196
            if (isset($item['items']) && is_array($item['items'])) {
197
                $subItems = self::filterRecursive($item['items'], $user);
198
                if (count($subItems)) {
199
                    $allow = true;
200
                }
201
                $item['items'] = $subItems;
202
            }
203
            if ($allow && !($url == '#' && empty($item['items']))) {
204
                $result[$i] = $item;
205
            }
206
        }
207
        return $result;
208
    }
209
210
    /**
211
     * Filter action column button. Use with [[yii\grid\GridView]]
212
     * ```php
213
     * 'columns' => [
214
     *     ...
215
     *     [
216
     *         'class' => 'yii\grid\ActionColumn',
217
     *         'template' => Helper::filterActionColumn(['view','update','activate'])
218
     *     ]
219
     * ],
220
     * ```
221
     * @param array|string $buttons
222
     * @param integer|User $user
223
     * @return string
224
     */
225
    public static function filterActionColumn($buttons = [], $user = null)
226
    {
227
        if (is_array($buttons)) {
228
            $result = [];
229
            foreach ($buttons as $button) {
230
                if (static::checkRoute($button, [], $user)) {
231
                    $result[] = "{{$button}}";
232
                }
233
            }
234
            return implode(' ', $result);
235
        }
236
        return preg_replace_callback('/\\{([\w\-\/]+)\\}/', function ($matches) use ($user) {
237
            return static::checkRoute($matches[1], [], $user) ? "{{$matches[1]}}" : '';
238
        }, $buttons);
239
    }
240
241
    /**
242
     * Use to invalidate cache.
243
     */
244
    public static function invalidate()
245
    {
246
        if (Configs::cache() !== null) {
247
            TagDependency::invalidate(Configs::cache(), Configs::CACHE_TAG);
248
        }
249
    }
250
}
251