Completed
Pull Request — master (#309)
by
unknown
02:22
created

Route   C

Complexity

Total Complexity 59

Size/Duplication

Total Lines 320
Duplicated Lines 1.25 %

Coupling/Cohesion

Components 2
Dependencies 14

Importance

Changes 0
Metric Value
wmc 59
lcom 2
cbo 14
dl 4
loc 320
rs 5.849
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
C addNew() 4 31 7
A remove() 0 13 3
A getRoutePrefix() 0 7 3
A getPermissionName() 0 8 2
B getRoutes() 0 57 7
B getAppRoutes() 0 21 6
B getRouteRecursive() 0 24 5
C getControllerFiles() 0 30 12
A getControllerActions() 0 15 2
C getActionRoutes() 0 23 8
A invalidate() 0 6 2
A setDefaultRule() 0 6 2

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

1
<?php
2
3
namespace mdm\admin\models;
4
5
use Exception;
6
use mdm\admin\components\Configs;
7
use mdm\admin\components\Helper;
8
use mdm\admin\components\RouteRule;
9
use Yii;
10
use yii\caching\TagDependency;
11
use yii\helpers\VarDumper;
12
13
/**
14
 * Description of Route
15
 *
16
 * @author Misbahul D Munir <[email protected]>
17
 * @since 1.0
18
 */
19
class Route extends \yii\base\Object
20
{
21
    const CACHE_TAG = 'mdm.admin.route';
22
23
    const PREFIX_ADVANCED = '@';
24
    const PREFIX_BASIC = '/';
25
26
    private $_routePrefix;
27
28
    /**
29
     * Assign or remove items
30
     * @param array $routes
31
     * @return array
32
     */
33
    public function addNew($routes)
34
    {
35
        $manager = Configs::authManager();
36
        foreach ($routes as $route) {
37
            try {
38
                $r = explode('&', $route);
39
                $item = $manager->createPermission($this->getPermissionName($route));
40
                if (count($r) > 1) {
41
                    $action = '/' . trim($r[0], '/');
42
                    if (($itemAction = $manager->getPermission($action)) === null) {
43
                        $itemAction = $manager->createPermission($action);
44
                        $manager->add($itemAction);
45
                    }
46
                    unset($r[0]);
47 View Code Duplication
                    foreach ($r as $part) {
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...
48
                        $part = explode('=', $part);
49
                        $item->data['params'][$part[0]] = isset($part[1]) ? $part[1] : '';
50
                    }
51
                    $this->setDefaultRule();
52
                    $item->ruleName = RouteRule::RULE_NAME;
53
                    $manager->add($item);
54
                    $manager->addChild($item, $itemAction);
55
                } else {
56
                    $manager->add($item);
57
                }
58
            } catch (Exception $exc) {
59
                Yii::error($exc->getMessage(), __METHOD__);
60
            }
61
        }
62
        Helper::invalidate();
63
    }
64
65
    /**
66
     * Assign or remove items
67
     * @param array $routes
68
     * @return array
69
     */
70
    public function remove($routes)
71
    {
72
        $manager = Configs::authManager();
73
        foreach ($routes as $route) {
74
            try {
75
                $item = $manager->createPermission($this->getPermissionName($route));
76
                $manager->remove($item);
77
            } catch (Exception $exc) {
78
                Yii::error($exc->getMessage(), __METHOD__);
79
            }
80
        }
81
        Helper::invalidate();
82
    }
83
84
    /**
85
     * Returns route prefix depending on the configuration.
86
     * @return string Route prefix
87
     */
88
    public function getRoutePrefix()
89
    {
90
        if (!$this->_routePrefix) {
91
            $this->_routePrefix = Configs::instance()->advanced ? self::PREFIX_ADVANCED : self::PREFIX_BASIC;
92
        }
93
        return $this->_routePrefix;
94
    }
95
96
    /**
97
     * Returns the correct permission name depending on the configuration.
98
     * @param  string $route Route
99
     * @return string        Permission name
100
     */
101
    public function getPermissionName($route)
102
    {
103
        if (self::PREFIX_BASIC == $this->routePrefix) {
0 ignored issues
show
Bug introduced by
The property routePrefix does not seem to exist. Did you mean _routePrefix?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
104
            return self::PREFIX_BASIC . trim($route, self::PREFIX_BASIC);
105
        } else {
106
            return self::PREFIX_ADVANCED . ltrim(trim($route, self::PREFIX_BASIC), self::PREFIX_ADVANCED);
107
        }
108
    }
109
110
    /**
111
     * Get available and assigned routes
112
     * @return array
113
     */
114
    public function getRoutes()
115
    {
116
        $manager = Configs::authManager();
117
        // Get advanced configuration
118
        $advanced = Configs::instance()->advanced;
119
        if ($advanced) {
120
            // Use advanced route scheme.
121
            // Set advanced route prefix.
122
            $this->_routePrefix = self::PREFIX_ADVANCED;
123
            // Create empty routes array.
124
            $routes = [];
125
            // Save original app.
126
            $yiiApp = Yii::$app;
127
            // Step through each configured application
128
            foreach ($advanced as $id => $configPaths) {
129
                // Force correct id string.
130
                $id = $this->routePrefix . ltrim(trim($id), $this->routePrefix);
0 ignored issues
show
Bug introduced by
The property routePrefix does not seem to exist. Did you mean _routePrefix?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
131
                // Create empty config array.
132
                $config = [];
133
                // Assemble configuration for current app.
134
                foreach ($configPaths as $configPath) {
135
                    // Merge every new configuration with the old config array.
136
                    $config = yii\helpers\ArrayHelper::merge($config, require (Yii::getAlias($configPath)));
137
                }
138
                // Create new app using the config array.
139
                $app = new yii\web\Application($config);
140
                // Get all the routes of the newly created app.
141
                $r = $this->getAppRoutes($app);
142
                // Dump new app
143
                unset($app);
144
                // Prepend the app id to all routes.
145
                foreach ($r as $route) {
146
                    $routes[$id . $route] = $id . $route;
147
                }
148
                // Switch back to original app.
149
                Yii::$app = $yiiApp;
150
            }
151
        } else {
152
            // Use basic route scheme.
153
            // Set basic route prefix
154
            $this->_routePrefix = self::PREFIX_BASIC;
155
            // Get basic app routes.
156
            $routes = $this->getAppRoutes();
157
        }
158
        $exists = [];
159
        foreach (array_keys($manager->getPermissions()) as $name) {
160
            if ($name[0] !== $this->routePrefix) {
0 ignored issues
show
Bug introduced by
The property routePrefix does not seem to exist. Did you mean _routePrefix?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
161
                continue;
162
            }
163
            $exists[] = $name;
164
            unset($routes[$name]);
165
        }
166
        return [
167
            'available' => array_keys($routes),
168
            'assigned' => $exists,
169
        ];
170
    }
171
172
    /**
173
     * Get list of application routes
174
     * @return array
175
     */
176
    public function getAppRoutes($module = null)
177
    {
178
        if ($module === null) {
179
            $module = Yii::$app;
180
        } elseif (is_string($module)) {
181
            $module = Yii::$app->getModule($module);
182
        }
183
        $key = [__METHOD__, $module->getUniqueId()];
184
        $cache = Configs::instance()->cache;
185
        if ($cache === null || ($result = $cache->get($key)) === false) {
186
            $result = [];
187
            $this->getRouteRecursive($module, $result);
188
            if ($cache !== null) {
189
                $cache->set($key, $result, Configs::instance()->cacheDuration, new TagDependency([
190
                    'tags' => self::CACHE_TAG,
191
                ]));
192
            }
193
        }
194
195
        return $result;
196
    }
197
198
    /**
199
     * Get route(s) recursive
200
     * @param \yii\base\Module $module
201
     * @param array $result
202
     */
203
    protected function getRouteRecursive($module, &$result)
204
    {
205
        $token = "Get Route of '" . get_class($module) . "' with id '" . $module->uniqueId . "'";
206
        Yii::beginProfile($token, __METHOD__);
207
        try {
208
            foreach ($module->getModules() as $id => $child) {
209
                if (($child = $module->getModule($id)) !== null) {
210
                    $this->getRouteRecursive($child, $result);
211
                }
212
            }
213
214
            foreach ($module->controllerMap as $id => $type) {
215
                $this->getControllerActions($type, $id, $module, $result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by parameter $result on line 203 can also be of type array; however, mdm\admin\models\Route::getControllerActions() 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...
216
            }
217
218
            $namespace = trim($module->controllerNamespace, '\\') . '\\';
219
            $this->getControllerFiles($module, $namespace, '', $result);
220
            $all = '/' . ltrim($module->uniqueId . '/*', '/');
221
            $result[$all] = $all;
222
        } catch (\Exception $exc) {
223
            Yii::error($exc->getMessage(), __METHOD__);
224
        }
225
        Yii::endProfile($token, __METHOD__);
226
    }
227
228
    /**
229
     * Get list controller under module
230
     * @param \yii\base\Module $module
231
     * @param string $namespace
232
     * @param string $prefix
233
     * @param mixed $result
234
     * @return mixed
235
     */
236
    protected function getControllerFiles($module, $namespace, $prefix, &$result)
237
    {
238
        $path = Yii::getAlias('@' . str_replace('\\', '/', $namespace), false);
239
        $token = "Get controllers from '$path'";
240
        Yii::beginProfile($token, __METHOD__);
241
        try {
242
            if (!is_dir($path)) {
243
                return;
244
            }
245
            foreach (scandir($path) as $file) {
246
                if ($file == '.' || $file == '..') {
247
                    continue;
248
                }
249
                if (is_dir($path . '/' . $file) && preg_match('%^[a-z0-9_/]+$%i', $file . '/')) {
250
                    $this->getControllerFiles($module, $namespace . $file . '\\', $prefix . $file . '/', $result);
251
                } elseif (strcmp(substr($file, -14), 'Controller.php') === 0) {
252
                    $baseName = substr(basename($file), 0, -14);
253
                    $name = strtolower(preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $baseName));
254
                    $id = ltrim(str_replace(' ', '-', $name), '-');
255
                    $className = $namespace . $baseName . 'Controller';
256
                    if (strpos($className, '-') === false && class_exists($className) && is_subclass_of($className, 'yii\base\Controller')) {
257
                        $this->getControllerActions($className, $prefix . $id, $module, $result);
258
                    }
259
                }
260
            }
261
        } catch (\Exception $exc) {
262
            Yii::error($exc->getMessage(), __METHOD__);
263
        }
264
        Yii::endProfile($token, __METHOD__);
265
    }
266
267
    /**
268
     * Get list action of controller
269
     * @param mixed $type
270
     * @param string $id
271
     * @param \yii\base\Module $module
272
     * @param string $result
273
     */
274
    protected function getControllerActions($type, $id, $module, &$result)
275
    {
276
        $token = "Create controller with cofig=" . VarDumper::dumpAsString($type) . " and id='$id'";
277
        Yii::beginProfile($token, __METHOD__);
278
        try {
279
            /* @var $controller \yii\base\Controller */
280
            $controller = Yii::createObject($type, [$id, $module]);
0 ignored issues
show
Documentation introduced by
$type is of type *, but the function expects a callable.

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...
281
            $this->getActionRoutes($controller, $result);
0 ignored issues
show
Documentation introduced by
$result is of type string, but the function expects a array.

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...
282
            $all = "/{$controller->uniqueId}/*";
283
            $result[$all] = $all;
284
        } catch (\Exception $exc) {
285
            Yii::error($exc->getMessage(), __METHOD__);
286
        }
287
        Yii::endProfile($token, __METHOD__);
288
    }
289
290
    /**
291
     * Get route of action
292
     * @param \yii\base\Controller $controller
293
     * @param array $result all controller action.
294
     */
295
    protected function getActionRoutes($controller, &$result)
296
    {
297
        $token = "Get actions of controller '" . $controller->uniqueId . "'";
298
        Yii::beginProfile($token, __METHOD__);
299
        try {
300
            $prefix = '/' . $controller->uniqueId . '/';
301
            foreach ($controller->actions() as $id => $value) {
302
                $result[$prefix . $id] = $prefix . $id;
303
            }
304
            $class = new \ReflectionClass($controller);
305
            foreach ($class->getMethods() as $method) {
306
                $name = $method->getName();
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
307
                if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0 && $name !== 'actions') {
308
                    $name = strtolower(preg_replace('/(?<![A-Z])[A-Z]/', ' \0', substr($name, 6)));
309
                    $id = $prefix . ltrim(str_replace(' ', '-', $name), '-');
310
                    $result[$id] = $id;
311
                }
312
            }
313
        } catch (\Exception $exc) {
314
            Yii::error($exc->getMessage(), __METHOD__);
315
        }
316
        Yii::endProfile($token, __METHOD__);
317
    }
318
319
    /**
320
     * Ivalidate cache
321
     */
322
    public static function invalidate()
323
    {
324
        if (Configs::cache() !== null) {
325
            TagDependency::invalidate(Configs::cache(), self::CACHE_TAG);
326
        }
327
    }
328
329
    /**
330
     * Set default rule of parameterize route.
331
     */
332
    protected function setDefaultRule()
333
    {
334
        if (Configs::authManager()->getRule(RouteRule::RULE_NAME) === null) {
335
            Configs::authManager()->add(new RouteRule());
336
        }
337
    }
338
}
339