Passed
Push — 6.0 ( b392ff...41ffa0 )
by liu
02:33
created

Route::buildDirRoute()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 31
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 15
nc 10
nop 4
dl 0
loc 31
rs 9.4555
c 0
b 0
f 0
1
<?php
2
// +----------------------------------------------------------------------
1 ignored issue
show
Coding Style introduced by
You must use "/**" style comments for a file comment
Loading history...
3
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: yunwuxin <[email protected]>
10
// +----------------------------------------------------------------------
11
namespace think\console\command\optimize;
12
13
use ReflectionClass;
14
use ReflectionMethod;
15
use think\console\Command;
16
use think\console\Input;
17
use think\console\input\Argument;
18
use think\console\Output;
19
20
class Route extends Command
1 ignored issue
show
Coding Style introduced by
Missing class doc comment
Loading history...
21
{
22
    protected function configure()
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
23
    {
24
        $this->setName('optimize:route')
25
            ->addArgument('app', Argument::OPTIONAL, 'Build app route cache .')
26
            ->setDescription('Build route cache.');
27
    }
28
29
    protected function execute(Input $input, Output $output)
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
30
    {
31
        $app = $input->getArgument('app');
32
33
        if ($app) {
34
            $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . $app . DIRECTORY_SEPARATOR;
0 ignored issues
show
Bug introduced by
Are you sure $app of type mixed|think\console\input\Argument can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

34
            $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . /** @scrutinizer ignore-type */ $app . DIRECTORY_SEPARATOR;
Loading history...
35
        } else {
36
            $path = $this->app->getRuntimePath();
37
        }
38
39
        $filename = $path . 'route.php';
40
        if (is_file($filename)) {
41
            unlink($filename);
42
        }
43
44
        file_put_contents($filename, $this->buildRouteCache($app));
45
        $output->writeln('<info>Succeed!</info>');
46
    }
47
48
    protected function buildRouteCache(string $app = null): string
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
49
    {
50
        $this->app->route->clear();
51
        $this->app->route->lazy(false);
52
53
        // 路由检测
54
        $path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR . ($app ? $app . DIRECTORY_SEPARATOR : '');
55
56
        $files = is_dir($path) ? scandir($path) : [];
57
58
        foreach ($files as $file) {
59
            if (strpos($file, '.php')) {
60
                include $path . $file;
61
            }
62
        }
63
64
        if ($this->app->config->get('route.route_annotation')) {
65
            include $this->buildRoute();
66
        }
67
68
        $content = '<?php ' . PHP_EOL . 'return ';
69
        $content .= '\think\App::unserialize(\'' . \think\App::serialize($this->app->route->getName()) . '\');';
70
        return $content;
71
    }
72
73
    /**
74
     * 根据注释自动生成路由规则
75
     * @access protected
76
     * @return string
77
     */
78
    protected function buildRoute(): string
79
    {
80
        $namespace = $this->app->getNameSpace();
81
        $content   = '<?php ' . PHP_EOL . '//根据 Annotation 自动生成的路由规则' . PHP_EOL . 'use think\\facade\\Route;' . PHP_EOL;
82
83
        $layer  = $this->app->config->get('route.controller_layer');
84
        $suffix = $this->app->config->get('route.controller_suffix');
85
86
        $path = $this->app->getAppPath() . $layer . DIRECTORY_SEPARATOR;
0 ignored issues
show
Bug introduced by
Are you sure $layer of type array|mixed|null can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

86
        $path = $this->app->getAppPath() . /** @scrutinizer ignore-type */ $layer . DIRECTORY_SEPARATOR;
Loading history...
87
        $content .= $this->buildDirRoute($path, $namespace, $suffix, $layer);
0 ignored issues
show
Bug introduced by
It seems like $layer can also be of type array and array and null; however, parameter $layer of think\console\command\op...\Route::buildDirRoute() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

87
        $content .= $this->buildDirRoute($path, $namespace, $suffix, /** @scrutinizer ignore-type */ $layer);
Loading history...
Bug introduced by
It seems like $suffix can also be of type array and array and null; however, parameter $suffix of think\console\command\op...\Route::buildDirRoute() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

87
        $content .= $this->buildDirRoute($path, $namespace, /** @scrutinizer ignore-type */ $suffix, $layer);
Loading history...
88
89
        $filename = $this->app->getRuntimePath() . 'build_route.php';
90
        file_put_contents($filename, $content);
91
92
        return $filename;
93
    }
94
95
    /**
96
     * 生成子目录控制器类的路由规则
97
     * @access protected
98
     * @param  string $path  控制器目录
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 2 found
Loading history...
99
     * @param  string $namespace 应用命名空间
100
     * @param  bool   $suffix 类库后缀
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
101
     * @param  string $layer 控制器层目录名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
102
     * @return string
103
     */
104
    protected function buildDirRoute(string $path, string $namespace, bool $suffix, string $layer): string
105
    {
106
        $content     = '';
107
        $controllers = glob($path . '*.php');
108
109
        foreach ($controllers as $controller) {
110
            $controller = basename($controller, '.php');
111
112
            if ($suffix) {
113
                // 控制器后缀
114
                $controller = substr($controller, 0, -10);
115
            }
116
117
            $class = new \ReflectionClass($namespace . '\\' . $layer . '\\' . $controller);
118
119
            if (strpos($layer, '\\')) {
120
                // 多级控制器
121
                $level      = str_replace(DIRECTORY_SEPARATOR, '.', substr($layer, 11));
122
                $controller = $level . '.' . $controller;
123
            }
124
125
            $content .= $this->getControllerRoute($class, $controller);
126
        }
127
128
        $subDir = glob($path . '*', GLOB_ONLYDIR);
129
130
        foreach ($subDir as $dir) {
131
            $content .= $this->buildDirRoute($dir . DIRECTORY_SEPARATOR, $namespace, $suffix, $layer . '\\' . basename($dir));
132
        }
133
134
        return $content;
135
    }
136
137
    /**
138
     * 生成控制器类的路由规则
139
     * @access protected
140
     * @param  ReflectionClass  $class        控制器反射对象
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 8 found
Loading history...
141
     * @param  string           $controller   控制器名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
142
     * @return string
143
     */
144
    protected function getControllerRoute(ReflectionClass $class, string $controller): string
145
    {
146
        $content = '';
147
        $comment = $class->getDocComment() ?: '';
148
149
        if (false !== strpos($comment, '@route(')) {
0 ignored issues
show
Bug introduced by
It seems like $comment can also be of type true; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

149
        if (false !== strpos(/** @scrutinizer ignore-type */ $comment, '@route(')) {
Loading history...
150
            $comment = $this->parseRouteComment($comment);
0 ignored issues
show
Bug introduced by
It seems like $comment can also be of type true; however, parameter $comment of think\console\command\op...te::parseRouteComment() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

150
            $comment = $this->parseRouteComment(/** @scrutinizer ignore-type */ $comment);
Loading history...
151
            $comment = preg_replace('/route\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::resource(\1,\'' . $controller . '\')', $comment);
152
            $content .= PHP_EOL . $comment;
153
        } elseif (false !== strpos($comment, '@alias(')) {
154
            $comment = $this->parseRouteComment($comment, '@alias(');
155
            $comment = preg_replace('/alias\(\s?([\'\"][\-\_\/\w]+[\'\"])\s?\)/is', 'Route::alias(\1,\'' . $controller . '\')', $comment);
156
            $content .= PHP_EOL . $comment;
157
        }
158
159
        $methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);
160
161
        foreach ($methods as $method) {
162
            $comment = $this->getMethodRouteComment($controller, $method);
163
            if ($comment) {
164
                $content .= PHP_EOL . $comment;
165
            }
166
        }
167
168
        return $content;
169
    }
170
171
    /**
172
     * 解析路由注释
173
     * @access protected
174
     * @param  string $comment
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
175
     * @param  string $tag
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
176
     * @return string
177
     */
178
    protected function parseRouteComment(string $comment, string $tag = '@route('): string
179
    {
180
        $comment = substr($comment, 3, -2);
181
        $comment = explode(PHP_EOL, substr(strstr(trim($comment), $tag), 1));
182
        $comment = array_map(function ($item) {return trim(trim($item), ' \t*');}, $comment);
0 ignored issues
show
Coding Style introduced by
Opening brace must be the last content on the line
Loading history...
Coding Style introduced by
Closing brace must be on a line by itself
Loading history...
183
184
        if (count($comment) > 1) {
185
            $key     = array_search('', $comment);
186
            $comment = array_slice($comment, 0, false === $key ? 1 : $key);
0 ignored issues
show
Bug introduced by
It seems like false === $key ? 1 : $key can also be of type string; however, parameter $length of array_slice() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

186
            $comment = array_slice($comment, 0, /** @scrutinizer ignore-type */ false === $key ? 1 : $key);
Loading history...
187
        }
188
189
        $comment = implode(PHP_EOL . "\t", $comment) . ';';
190
191
        if (strpos($comment, '{')) {
192
            $comment = preg_replace_callback('/\{\s?.*?\s?\}/s', function ($matches) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
193
                return false !== strpos($matches[0], '"') ? '[' . substr(var_export(json_decode($matches[0], true), true), 7, -1) . ']' : $matches[0];
194
            }, $comment);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
195
        }
196
        return $comment;
197
    }
198
199
    /**
200
     * 获取方法的路由注释
201
     * @access protected
202
     * @param  string             $controller 控制器名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
203
     * @param  ReflectionMethod   $reflectMethod
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
204
     * @return string|void
205
     */
206
    protected function getMethodRouteComment(string $controller, ReflectionMethod $reflectMethod)
207
    {
208
        $comment = $reflectMethod->getDocComment() ?: '';
209
210
        if (false !== strpos($comment, '@route(')) {
0 ignored issues
show
Bug introduced by
It seems like $comment can also be of type true; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

210
        if (false !== strpos(/** @scrutinizer ignore-type */ $comment, '@route(')) {
Loading history...
211
            $comment = $this->parseRouteComment($comment);
0 ignored issues
show
Bug introduced by
It seems like $comment can also be of type true; however, parameter $comment of think\console\command\op...te::parseRouteComment() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

211
            $comment = $this->parseRouteComment(/** @scrutinizer ignore-type */ $comment);
Loading history...
212
            $action  = $reflectMethod->getName();
213
214
            if ($suffix = $this->app->route->config('action_suffix')) {
215
                $action = substr($action, 0, -strlen($suffix));
216
            }
217
218
            $route   = $controller . '/' . $action;
219
            $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\,?\s?[\'\"]?(\w+?)[\'\"]?\s?\)/is', 'Route::\2(\1,\'' . $route . '\')', $comment);
220
            $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::rule(\1,\'' . $route . '\')', $comment);
221
222
            return $comment;
223
        }
224
    }
225
}
226