Passed
Push — 8.0 ( f7fb86...011e8e )
by liu
02:21
created

Dispatch   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 219
Duplicated Lines 0 %

Test Coverage

Coverage 40.22%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 85
dl 0
loc 219
ccs 37
cts 92
cp 0.4022
rs 9.44
c 6
b 0
f 0
wmc 37

13 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 6 1
A run() 0 4 1
B autoResponse() 0 23 9
A __construct() 0 2 1
B doRouteAfter() 0 32 7
B createBindModel() 0 35 8
A __wakeup() 0 4 1
A __sleep() 0 3 1
A getDispatch() 0 3 1
A getParam() 0 3 1
A __debugInfo() 0 6 1
A getActionBindVars() 0 7 1
A autoValidate() 0 24 4
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;
14
15
use Psr\Http\Message\ResponseInterface;
16
use think\App;
17
use think\Container;
18
use think\Request;
19
use think\Response;
20
use think\Validate;
21
22
/**
23
 * 路由调度基础类
24
 */
25
abstract class Dispatch
26
{
27
    /**
28
     * 应用对象
29
     * @var App
30
     */
31
    protected $app;
32
33 30
    public function __construct(protected Request $request, protected Rule $rule, protected $dispatch, protected array $param = [], protected array $option = [])
34
    {
35 30
    }
36
37 27
    public function init(App $app)
38
    {
39 27
        $this->app = $app;
40
41
        // 执行路由后置操作
42 27
        $this->doRouteAfter();
43
    }
44
45
    /**
46
     * 执行路由调度
47
     * @access public
48
     * @return Response
49
     */
50 30
    public function run(): Response
51
    {
52 30
        $data = $this->exec();
53 30
        return $this->autoResponse($data);
54
    }
55
56 30
    protected function autoResponse($data): Response
57
    {
58 30
        if ($data instanceof Response) {
59 21
            $response = $data;
60 24
        } elseif ($data instanceof ResponseInterface) {
61 3
            $response = Response::create((string) $data->getBody(), 'html', $data->getStatusCode());
62
63 3
            foreach ($data->getHeaders() as $header => $values) {
64 3
                $response->header([$header => implode(", ", $values)]);
65
            }
66 21
        } elseif (!is_null($data)) {
67
            // 默认自动识别响应输出类型
68 21
            $type     = $this->request->isJson() ? 'json' : 'html';
69 21
            $response = Response::create($data, $type);
70
        } else {
71
            $data = ob_get_clean();
72
73
            $content  = false === $data ? '' : $data;
74
            $status   = '' === $content && $this->request->isJson() ? 204 : 200;
75
            $response = Response::create($content, 'html', $status);
76
        }
77
78 30
        return $response;
79
    }
80
81
    /**
82
     * 检查路由后置操作
83
     * @access protected
84
     * @return void
85
     */
86 27
    protected function doRouteAfter(): void
87
    {
88 27
        $option = $this->option;
89
90
        // 添加中间件
91 27
        if (!empty($option['middleware'])) {
92 3
            if (isset($option['without_middleware'])) {
93
                $middleware = !empty($option['without_middleware']) ? array_diff($option['middleware'], $option['without_middleware']) : [];
94
            } else {
95 3
                $middleware = $option['middleware'];
96
            }
97 3
            $this->app->middleware->import($middleware, 'route');
98
        }
99
100 27
        if (!empty($option['append'])) {
101
            $this->param = array_merge($this->param, $option['append']);
102
        }
103
104
        // 绑定模型数据
105 27
        if (!empty($option['model'])) {
106
            $this->createBindModel($option['model'], $this->param);
107
        }
108
109
        // 记录当前请求的路由规则
110 27
        $this->request->setRule($this->rule);
111
112
        // 记录路由变量
113 27
        $this->request->setRoute($this->param);
114
115
        // 数据自动验证
116 27
        if (isset($option['validate'])) {
117
            $this->autoValidate($option['validate']);
118
        }
119
    }
120
121
    /**
122
     * 获取操作的绑定参数
123
     * @access protected
124
     * @return array
125
     */
126 27
    protected function getActionBindVars(): array
127
    {
128 27
        $bind = $this->rule->config('action_bind_param');
129 27
        return match ($bind) {
130 27
            'route' => $this->param,
131 27
            'param' => $this->request->param(),
132 27
            default => 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

132
            default => array_merge(/** @scrutinizer ignore-type */ $this->request->get(), $this->param),
Loading history...
133 27
        };
134
    }
135
136
    /**
137
     * 路由绑定模型实例
138
     * @access protected
139
     * @param array $bindModel 绑定模型
140
     * @param array $matches   路由变量
141
     * @return void
142
     */
143
    protected function createBindModel(array $bindModel, array $matches): void
144
    {
145
        foreach ($bindModel as $key => $val) {
146
            if ($val instanceof \Closure) {
147
                $result = $this->app->invokeFunction($val, $matches);
148
            } else {
149
                $fields = explode('&', $key);
150
151
                if (is_array($val)) {
152
                    [$model, $exception] = $val;
153
                } else {
154
                    $model     = $val;
155
                    $exception = true;
156
                }
157
158
                $where = [];
159
                $match = true;
160
161
                foreach ($fields as $field) {
162
                    if (!isset($matches[$field])) {
163
                        $match = false;
164
                        break;
165
                    } else {
166
                        $where[] = [$field, '=', $matches[$field]];
167
                    }
168
                }
169
170
                if ($match) {
171
                    $result = $model::where($where)->failException($exception)->find();
172
                }
173
            }
174
175
            if (!empty($result)) {
176
                // 注入容器
177
                $this->app->instance($result::class, $result);
178
            }
179
        }
180
    }
181
182
    /**
183
     * 验证数据
184
     * @access protected
185
     * @param array $option
186
     * @return void
187
     * @throws \think\exception\ValidateException
188
     */
189
    protected function autoValidate(array $option): void
190
    {
191
        [$validate, $scene, $message, $batch] = $option;
192
193
        if (is_array($validate)) {
194
            // 指定验证规则
195
            $v = new Validate();
196
            $v->rule($validate);
197
        } else {
198
            // 调用验证器
199
            $class = str_contains($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
200
201
            $v = new $class();
202
203
            if (!empty($scene)) {
204
                $v->scene($scene);
205
            }
206
        }
207
208
        /** @var Validate $v */
209
        $v->message($message)
210
            ->batch($batch)
211
            ->failException(true)
212
            ->check($this->request->param());
0 ignored issues
show
Bug introduced by
$this->request->param() of type null|object is incompatible with the type array expected by parameter $data of think\Validate::check(). ( Ignorable by Annotation )

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

212
            ->check(/** @scrutinizer ignore-type */ $this->request->param());
Loading history...
213
    }
214
215
    public function getDispatch()
216
    {
217
        return $this->dispatch;
218
    }
219
220
    public function getParam(): array
221
    {
222
        return $this->param;
223
    }
224
225
    abstract public function exec();
226
227
    public function __sleep()
228
    {
229
        return ['rule', 'dispatch', 'param', 'controller', 'actionName'];
230
    }
231
232
    public function __wakeup()
233
    {
234
        $this->app     = Container::pull('app');
235
        $this->request = $this->app->request;
236
    }
237
238
    public function __debugInfo()
239
    {
240
        return [
241
            'dispatch' => $this->dispatch,
242
            'param'    => $this->param,
243
            'rule'     => $this->rule,
244
        ];
245
    }
246
}
247