Passed
Push — 8.0 ( cd7e8a...0aac54 )
by liu
11:57 queued 09:21
created

Controller::init()   B

Complexity

Conditions 8
Paths 96

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 8.2518

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 8
eloc 19
c 3
b 0
f 0
nc 96
nop 1
dl 0
loc 30
ccs 16
cts 19
cp 0.8421
crap 8.2518
rs 8.4444
1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: liu21st <[email protected]>
10
// +----------------------------------------------------------------------
11
declare (strict_types = 1);
12
13
namespace think\route\dispatch;
14
15
use ReflectionClass;
16
use ReflectionException;
17
use ReflectionMethod;
18
use think\App;
19
use think\exception\ClassNotFoundException;
20
use think\exception\HttpException;
21
use think\helper\Str;
22
use think\route\Dispatch;
23
24
/**
25
 * Controller Dispatcher
26
 */
27
class Controller extends Dispatch
28
{
29
    /**
30
     * 控制器名
31
     * @var string
32
     */
33
    protected $controller;
34
35
    /**
36
     * 操作名
37
     * @var string
38
     */
39
    protected $actionName;
40
41 12
    public function init(App $app)
42
    {
43 12
        parent::init($app);
44
45 12
        $path = $this->dispatch;
46 12
        if (is_string($path)) {
47 3
            $path = explode('/', $path);
48
        }
49
50 12
        $action     = !empty($path) ? array_pop($path) : $this->rule->config('default_action');
51 12
        $controller = !empty($path) ? array_pop($path) : $this->rule->config('default_controller');
52 12
        $layer      = !empty($path) ? implode('/', $path) : '';
53
54
        // 获取控制器名和分层(目录)名
55 12
        if (str_contains($controller, '.')) {
0 ignored issues
show
Bug introduced by
It seems like $controller can also be of type null; however, parameter $haystack of str_contains() 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

55
        if (str_contains(/** @scrutinizer ignore-type */ $controller, '.')) {
Loading history...
56
            $pos        = strrpos($controller, '.');
0 ignored issues
show
Bug introduced by
It seems like $controller can also be of type null; however, parameter $haystack of strrpos() 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

56
            $pos        = strrpos(/** @scrutinizer ignore-type */ $controller, '.');
Loading history...
57
            $layer      = ($layer ? $layer . '.' : '') . substr($controller, 0, $pos);
0 ignored issues
show
Bug introduced by
It seems like $controller can also be of type null; however, parameter $string of substr() 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

57
            $layer      = ($layer ? $layer . '.' : '') . substr(/** @scrutinizer ignore-type */ $controller, 0, $pos);
Loading history...
58
            $controller = Str::studly(substr($controller, $pos + 1));
59
        } else {
60 12
            $controller = Str::studly($controller);
0 ignored issues
show
Bug introduced by
It seems like $controller can also be of type null; however, parameter $value of think\helper\Str::studly() 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

60
            $controller = Str::studly(/** @scrutinizer ignore-type */ $controller);
Loading history...
61
        }
62
63 12
        $this->actionName = $action;
64 12
        $this->controller = ($layer ? $layer . '.' : '') . $controller;
65
66
        // 设置当前请求的控制器、操作
67 12
        $this->request
68 12
            ->setLayer($layer)
69 12
            ->setController($this->controller)
70 12
            ->setAction($this->actionName);
0 ignored issues
show
Bug introduced by
It seems like $this->actionName can also be of type null; however, parameter $action of think\Request::setAction() 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

70
            ->setAction(/** @scrutinizer ignore-type */ $this->actionName);
Loading history...
71
    }
72
73 12
    public function exec()
74
    {
75
        try {
76
            // 实例化控制器
77 12
            $instance = $this->controller($this->controller);
78
        } catch (ClassNotFoundException $e) {
79
            throw new HttpException(404, 'controller not exists:' . $e->getClass());
80
        }
81
82
        // 注册控制器中间件
83 12
        $this->registerControllerMiddleware($instance);
84
85 12
        return $this->app->middleware->pipeline('controller')
86 12
            ->send($this->request)
87 12
            ->then(function () use ($instance) {
88
                // 获取当前操作名
89 12
                $suffix = $this->rule->config('action_suffix');
90 12
                $action = $this->actionName . $suffix;
91
92 12
                if (is_callable([$instance, $action])) {
93 12
                    $vars = array_merge($this->request->get(), $this->param);
0 ignored issues
show
Bug introduced by
$this->request->get() of type null|object is incompatible with the type array expected by parameter $arrays of array_merge(). ( Ignorable by Annotation )

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

93
                    $vars = array_merge(/** @scrutinizer ignore-type */ $this->request->get(), $this->param);
Loading history...
94
                    try {
95 12
                        $reflect = new ReflectionMethod($instance, $action);
96
                        // 严格获取当前操作方法名
97 3
                        $actionName = $reflect->getName();
98 3
                        if ($suffix) {
99
                            $actionName = substr($actionName, 0, -strlen($suffix));
100
                        }
101
102 3
                        $this->request->setAction($actionName);
103 9
                    } catch (ReflectionException $e) {
104 9
                        $reflect = new ReflectionMethod($instance, '__call');
105 9
                        $vars    = [$action, $vars];
106 10
                        $this->request->setAction($action);
107
                    }
108
                } else {
109
                    // 操作不存在
110
                    throw new HttpException(404, 'method not exists:' . $instance::class . '->' . $action . '()');
111
                }
112
113 12
                $data = $this->app->invokeReflectMethod($instance, $reflect, $vars);
114
115 12
                return $this->autoResponse($data);
116 12
            });
117
    }
118
119 3
    protected function parseActions($actions)
120
    {
121 3
        return array_map(function ($item) {
122 3
            return strtolower($item);
123 3
        }, is_string($actions) ? explode(",", $actions) : $actions);
124
    }
125
126
    /**
127
     * 使用反射机制注册控制器中间件
128
     * @access public
129
     * @param object $controller 控制器实例
130
     * @return void
131
     */
132 12
    protected function registerControllerMiddleware($controller): void
133
    {
134 12
        $class = new ReflectionClass($controller);
135
136 12
        if ($class->hasProperty('middleware')) {
137 6
            $reflectionProperty = $class->getProperty('middleware');
138 6
            $reflectionProperty->setAccessible(true);
139
140 6
            $middlewares = $reflectionProperty->getValue($controller);
141 6
            $action      = $this->request->action(true);
142
143 6
            foreach ($middlewares as $key => $val) {
144 3
                if (!is_int($key)) {
145 3
                    $middleware = $key;
146 3
                    $options    = $val;
147 3
                } elseif (isset($val['middleware'])) {
148 3
                    $middleware = $val['middleware'];
149 3
                    $options    = $val['options'] ?? [];
150
                } else {
151 3
                    $middleware = $val;
152 3
                    $options    = [];
153
                }
154
155 3
                if (isset($options['only']) && !in_array($action, $this->parseActions($options['only']))) {
156
                    continue;
157 3
                } elseif (isset($options['except']) && in_array($action, $this->parseActions($options['except']))) {
158 3
                    continue;
159
                }
160
161 3
                if (is_string($middleware) && str_contains($middleware, ':')) {
162 3
                    $middleware = explode(':', $middleware);
163 3
                    if (count($middleware) > 1) {
164 3
                        $middleware = [$middleware[0], array_slice($middleware, 1)];
165
                    }
166
                }
167
168 3
                $this->app->middleware->controller($middleware);
169
            }
170
        }
171
    }
172
173
    /**
174
     * 实例化访问控制器
175
     * @access public
176
     * @param string $name 资源地址
177
     * @return object
178
     * @throws ClassNotFoundException
179
     */
180 12
    public function controller(string $name)
181
    {
182 12
        $suffix = $this->rule->config('controller_suffix') ? 'Controller' : '';
183
184 12
        $controllerLayer = $this->rule->config('controller_layer') ?: 'controller';
185 12
        $emptyController = $this->rule->config('empty_controller') ?: 'Error';
186
187 12
        $class = $this->app->parseClass($controllerLayer, $name . $suffix);
188
189 12
        if (class_exists($class)) {
190 9
            return $this->app->make($class, [], true);
191 3
        } elseif ($emptyController && class_exists($emptyClass = $this->app->parseClass($controllerLayer, $emptyController . $suffix))) {
192 3
            return $this->app->make($emptyClass, [], true);
193
        }
194
195
        throw new ClassNotFoundException('class not exists:' . $class, $class);
196
    }
197
}
198