Completed
Push — 6.0 ( caeff7...3b655b )
by liu
03:11
created

RouteBuild::buildDirRoute()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 31
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 15
nc 10
nop 4
dl 0
loc 31
ccs 0
cts 16
cp 0
crap 30
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;
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 RouteBuild extends Command
1 ignored issue
show
Coding Style introduced by
Missing doc comment for class RouteBuild
Loading history...
21
{
22
    protected function configure()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function configure()
Loading history...
23
    {
24
        $this->setName('route:build')
25
            ->addArgument('app', Argument::OPTIONAL, 'Build app route cache .')
26
            ->setDescription('Build Annotation route rule.');
27
    }
28
29
    protected function execute(Input $input, Output $output)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function execute()
Loading history...
30
    {
31
        $app = $input->getArgument('app');
32
33
        if (empty($app) && !is_dir($this->app->getBasePath() . 'controller')) {
34
            $output->writeln('<error>Miss app name!</error>');
35
            return false;
36
        }
37
38
        $this->buildRoute($app);
39
        $output->writeln('<info>Succeed!</info>');
40
    }
41
42
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $app should have a doc-comment as per coding-style.
Loading history...
43
     * 根据注释自动生成路由规则
44
     * @access protected
45
     * @return string
46
     */
47
    protected function buildRoute(string $app = null): string
48
    {
49
        if ($app) {
50
            $path      = $this->app->getBasePath() . $app . DIRECTORY_SEPARATOR;
51
            $namespace = 'app\\' . $app;
52
        } else {
53
            $path      = $this->app->getBasePath();
54
            $namespace = 'app';
55
        }
56
57
        $content = '<?php ' . PHP_EOL . '// 根据注解自动生成的路由规则' . PHP_EOL . 'use think\\facade\\Route;' . PHP_EOL;
58
59
        $layer  = $this->app->config->get('route.controller_layer');
60
        $suffix = $this->app->config->get('route.controller_suffix');
61
62
        $content .= $this->buildDirRoute($path . $layer . DIRECTORY_SEPARATOR, $namespace, $suffix, $layer);
0 ignored issues
show
Bug introduced by
It seems like $suffix can also be of type array and array and null; however, parameter $suffix of think\console\command\RouteBuild::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

62
        $content .= $this->buildDirRoute($path . $layer . DIRECTORY_SEPARATOR, $namespace, /** @scrutinizer ignore-type */ $suffix, $layer);
Loading history...
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

62
        $content .= $this->buildDirRoute($path . /** @scrutinizer ignore-type */ $layer . DIRECTORY_SEPARATOR, $namespace, $suffix, $layer);
Loading history...
Bug introduced by
It seems like $layer can also be of type array and array and null; however, parameter $layer of think\console\command\RouteBuild::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

62
        $content .= $this->buildDirRoute($path . $layer . DIRECTORY_SEPARATOR, $namespace, $suffix, /** @scrutinizer ignore-type */ $layer);
Loading history...
63
64
        $filename = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'build_route.php';
65
        file_put_contents($filename, $content);
66
67
        return $filename;
68
    }
69
70
    /**
71
     * 生成子目录控制器类的路由规则
72
     * @access protected
73
     * @param  string $path  控制器目录
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 2 found
Loading history...
74
     * @param  string $namespace 应用命名空间
75
     * @param  bool   $suffix 类库后缀
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
76
     * @param  string $layer 控制器层目录名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
77
     * @return string
78
     */
79
    protected function buildDirRoute(string $path, string $namespace, bool $suffix, string $layer): string
80
    {
81
        $content     = '';
82
        $controllers = glob($path . '*.php');
83
84
        foreach ($controllers as $controller) {
85
            $controller = basename($controller, '.php');
86
87
            if ($suffix) {
88
                // 控制器后缀
89
                $controller = substr($controller, 0, -10);
90
            }
91
92
            $class = new \ReflectionClass($namespace . '\\' . $layer . '\\' . $controller);
93
94
            if (strpos($layer, '\\')) {
95
                // 多级控制器
96
                $level      = str_replace(DIRECTORY_SEPARATOR, '.', substr($layer, 11));
97
                $controller = $level . '.' . $controller;
98
            }
99
100
            $content .= $this->getControllerRoute($class, $controller);
101
        }
102
103
        $subDir = glob($path . '*', GLOB_ONLYDIR);
104
105
        foreach ($subDir as $dir) {
106
            $content .= $this->buildDirRoute($dir . DIRECTORY_SEPARATOR, $namespace, $suffix, $layer . '\\' . basename($dir));
107
        }
108
109
        return $content;
110
    }
111
112
    /**
113
     * 生成控制器类的路由规则
114
     * @access protected
115
     * @param  ReflectionClass  $class        控制器反射对象
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 8 found
Loading history...
116
     * @param  string           $controller   控制器名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
117
     * @return string
118
     */
119
    protected function getControllerRoute(ReflectionClass $class, string $controller): string
120
    {
121
        $content = '';
122
        $comment = $class->getDocComment() ?: '';
123
124
        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

124
        if (false !== strpos(/** @scrutinizer ignore-type */ $comment, '@route(')) {
Loading history...
125
            $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\Ro...ld::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

125
            $comment = $this->parseRouteComment(/** @scrutinizer ignore-type */ $comment);
Loading history...
126
            $comment = preg_replace('/route\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::resource(\1,\'' . $controller . '\')', $comment);
127
            $content .= PHP_EOL . $comment;
128
        } elseif (false !== strpos($comment, '@alias(')) {
129
            $comment = $this->parseRouteComment($comment, '@alias(');
130
            $comment = preg_replace('/alias\(\s?([\'\"][\-\_\/\w]+[\'\"])\s?\)/is', 'Route::alias(\1,\'' . $controller . '\')', $comment);
131
            $content .= PHP_EOL . $comment;
132
        }
133
134
        $methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);
135
136
        foreach ($methods as $method) {
137
            $comment = $this->getMethodRouteComment($controller, $method);
138
            if ($comment) {
139
                $content .= PHP_EOL . $comment;
140
            }
141
        }
142
143
        return $content;
144
    }
145
146
    /**
147
     * 解析路由注释
148
     * @access protected
149
     * @param  string $comment
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
150
     * @param  string $tag
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
151
     * @return string
152
     */
153
    protected function parseRouteComment(string $comment, string $tag = '@route('): string
154
    {
155
        $comment = substr($comment, 3, -2);
156
        $comment = explode(PHP_EOL, substr(strstr(trim($comment), $tag), 1));
157
        $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...
158
159
        if (count($comment) > 1) {
160
            $key     = array_search('', $comment);
161
            $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

161
            $comment = array_slice($comment, 0, /** @scrutinizer ignore-type */ false === $key ? 1 : $key);
Loading history...
162
        }
163
164
        $comment = implode(PHP_EOL . "\t", $comment) . ';';
165
166
        if (strpos($comment, '{')) {
167
            $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...
168
                return false !== strpos($matches[0], '"') ? '[' . substr(var_export(json_decode($matches[0], true), true), 7, -1) . ']' : $matches[0];
169
            }, $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...
170
        }
171
        return $comment;
172
    }
173
174
    /**
175
     * 获取方法的路由注释
176
     * @access protected
177
     * @param  string             $controller 控制器名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
178
     * @param  ReflectionMethod   $reflectMethod
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
179
     * @return string|void
180
     */
181
    protected function getMethodRouteComment(string $controller, ReflectionMethod $reflectMethod)
182
    {
183
        $comment = $reflectMethod->getDocComment() ?: '';
184
185
        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

185
        if (false !== strpos(/** @scrutinizer ignore-type */ $comment, '@route(')) {
Loading history...
186
            $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\Ro...ld::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

186
            $comment = $this->parseRouteComment(/** @scrutinizer ignore-type */ $comment);
Loading history...
187
            $action  = $reflectMethod->getName();
188
189
            if ($suffix = $this->app->route->config('action_suffix')) {
190
                $action = substr($action, 0, -strlen($suffix));
191
            }
192
193
            $route   = $controller . '/' . $action;
194
            $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\,?\s?[\'\"]?(\w+?)[\'\"]?\s?\)/is', 'Route::\2(\1,\'' . $route . '\')', $comment);
195
            $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::rule(\1,\'' . $route . '\')', $comment);
196
197
            return $comment;
198
        }
199
    }
200
}
201