Passed
Push — 8.0 ( 495b3f...e5cbfb )
by liu
02:07
created

RuleGroup::check()   B

Complexity

Conditions 11
Paths 61

Size

Total Lines 49
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 12.8905

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 11
eloc 24
c 4
b 0
f 0
nc 61
nop 3
dl 0
loc 49
ccs 18
cts 24
cp 0.75
crap 12.8905
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Closure;
16
use think\Container;
17
use think\Exception;
18
use think\Request;
19
use think\Route;
20
21
/**
22
 * 路由分组类
23
 */
24
class RuleGroup extends Rule
25
{
26
    /**
27
     * 分组路由(包括子分组)
28
     * @var Rule[]
29
     */
30
    protected $rules = [];
31
32
    /**
33
     * MISS路由
34
     * @var RuleItem
35
     */
36
    protected $miss;
37
38
    /**
39
     * 完整名称
40
     * @var string
41
     */
42
    protected $fullName;
43
44
    /**
45
     * 分组别名
46
     * @var string
47
     */
48
    protected $alias;
49
50
    /**
51
     * 是否已经解析
52
     * @var bool
53
     */
54
    protected $hasParsed;
55
56
    /**
57
     * 架构函数
58
     * @access public
59
     * @param  Route     $router 路由对象
60
     * @param  RuleGroup $parent 上级对象
61
     * @param  string    $name   分组名称
62
     * @param  mixed     $rule   分组路由
63
     * @param  bool      $lazy   延迟解析
64
     */
65 3
    public function __construct(Route $router, RuleGroup $parent = null, string $name = '', $rule = null, bool $lazy = false)
66
    {
67 3
        $this->router = $router;
68 3
        $this->parent = $parent;
69 3
        $this->rule   = $rule;
70 3
        $this->name   = trim($name, '/');
71
72 3
        $this->setFullName();
73
74 3
        if ($this->parent) {
75 3
            $this->domain = $this->parent->getDomain();
76 3
            $this->parent->addRuleItem($this);
77
        }
78
79 3
        if (!$lazy) {
80 3
            $this->parseGroupRule($rule);
81
        }
82
    }
83
84
    /**
85
     * 设置分组的路由规则
86
     * @access public
87
     * @return void
88
     */
89 3
    protected function setFullName(): void
90
    {
91 3
        if (str_contains($this->name, ':')) {
92
            $this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name);
93
        }
94
95 3
        if ($this->parent && $this->parent->getFullName()) {
96
            $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : '');
97
        } else {
98 3
            $this->fullName = $this->name;
99
        }
100
101 3
        if ($this->name) {
102
            $this->router->getRuleName()->setGroup($this->name, $this);
103
        }
104
    }
105
106
    /**
107
     * 获取所属域名
108
     * @access public
109
     * @return string
110
     */
111 12
    public function getDomain(): string
112
    {
113 12
        return $this->domain ?: '-';
114
    }
115
116
    /**
117
     * 获取分组别名
118
     * @access public
119
     * @return string
120
     */
121
    public function getAlias(): string
122
    {
123
        return $this->alias ?: '';
124
    }
125
126
    /**
127
     * 检测分组路由
128
     * @access public
129
     * @param  Request $request       请求对象
130
     * @param  string  $url           访问地址
131
     * @param  bool    $completeMatch 路由是否完全匹配
132
     * @return Dispatch|false
133
     */
134 27
    public function check(Request $request, string $url, bool $completeMatch = false)
135
    {
136
        // 检查分组有效性
137 27
        if (!$this->checkOption($this->option, $request) || !$this->checkUrl($url)) {
138
            return false;
139
        }
140
141
        // 解析分组路由
142 27
        if (!$this->hasParsed) {
143 24
            $this->parseGroupRule($this->rule);
144
        }
145
146
        // 获取当前路由规则
147 27
        $method = strtolower($request->method());
148 27
        $rules  = $this->getRules($method);
149 27
        $option = $this->getOption();
150
151 27
        if (isset($option['complete_match'])) {
152
            $completeMatch = $option['complete_match'];
153
        }
154
155 27
        if (!empty($option['merge_rule_regex'])) {
156
            // 合并路由正则规则进行路由匹配检查
157
            $result = $this->checkMergeRuleRegex($request, $rules, $url, $completeMatch);
158
159
            if (false !== $result) {
160
                return $result;
161
            }
162
        }
163
164
        // 检查分组路由
165 27
        foreach ($rules as $item) {
166 24
            $result = $item->check($request, $url, $completeMatch);
167
168 24
            if (false !== $result) {
169 24
                return $result;
170
            }
171
        }
172
173 6
        if (!empty($option['dispatcher'])) {
174
            $result = $this->parseRule($request, '', $option['dispatcher'], $url, $option);
175 6
        } elseif ($miss = $this->getMissRule($method)) {
176
            // 未匹配所有路由的路由规则处理
177 3
            $result = $this->parseRule($request, '', $miss->getRoute(), $url, $miss->getOption());
178
        } else {
179 3
            $result = false;
180
        }
181
182 6
        return $result;
183
    }
184
185
    /**
186
     * 分组URL匹配检查
187
     * @access protected
188
     * @param  string $url URL
189
     * @return bool
190
     */
191 27
    protected function checkUrl(string $url): bool
192
    {
193 27
        if ($this->fullName) {
194
            $pos = strpos($this->fullName, '<');
195
196
            if (false !== $pos) {
197
                $str = substr($this->fullName, 0, $pos);
198
            } else {
199
                $str = $this->fullName;
200
            }
201
202
            if ($str && 0 !== stripos(str_replace('|', '/', $url), $str)) {
203
                return false;
204
            }
205
        }
206
207 27
        return true;
208
    }
209
210
    /**
211
     * 设置路由分组别名
212
     * @access public
213
     * @param  string $alias 路由分组别名
214
     * @return $this
215
     */
216
    public function alias(string $alias)
217
    {
218
        $this->alias = $alias;
219
        $this->router->getRuleName()->setGroup($alias, $this);
220
221
        return $this;
222
    }
223
224
    /**
225
     * 解析分组和域名的路由规则及绑定
226
     * @access public
227
     * @param  mixed $rule 路由规则
228
     * @return void
229
     */
230 27
    public function parseGroupRule($rule): void
231
    {
232 27
        if (is_string($rule) && is_subclass_of($rule, Dispatch::class)) {
233
            $this->dispatcher($rule);
234
            return;
235
        }
236
237 27
        $origin = $this->router->getGroup();
238 27
        $this->router->setGroup($this);
239
240 27
        if ($rule instanceof Closure) {
241 6
            Container::getInstance()->invokeFunction($rule);
242 24
        } elseif (is_string($rule) && $rule) {
243
            $this->router->bind($rule, $this->domain);
244
        }
245
246 27
        $this->router->setGroup($origin);
247 27
        $this->hasParsed = true;
248
    }
249
250
    /**
251
     * 检测分组路由
252
     * @access public
253
     * @param  Request $request       请求对象
254
     * @param  array   $rules         路由规则
255
     * @param  string  $url           访问地址
256
     * @param  bool    $completeMatch 路由是否完全匹配
257
     * @return Dispatch|false
258
     */
259
    protected function checkMergeRuleRegex(Request $request, array &$rules, string $url, bool $completeMatch)
260
    {
261
        $depr  = $this->config('pathinfo_depr');
262
        $url   = $depr . str_replace('|', $depr, $url);
263
        $regex = [];
264
        $items = [];
265
266
        foreach ($rules as $key => $val) {
267
            $item = $val[1];
268
            if ($item instanceof RuleItem) {
269
                $rule = $depr . str_replace('/', $depr, $item->getRule());
270
                if ($depr == $rule && $depr != $url) {
271
                    unset($rules[$key]);
272
                    continue;
273
                }
274
275
                $complete = $item->getOption('complete_match', $completeMatch);
276
277
                if (!str_contains($rule, '<')) {
278
                    if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) {
279
                        return $item->checkRule($request, $url, []);
280
                    }
281
282
                    unset($rules[$key]);
283
                    continue;
284
                }
285
286
                $slash = preg_quote('/-' . $depr, '/');
287
288
                if ($matchRule = preg_split('/[' . $slash . ']<\w+\??>/', $rule, 2)) {
289
                    if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) {
290
                        unset($rules[$key]);
291
                        continue;
292
                    }
293
                }
294
295
                if (preg_match_all('/[' . $slash . ']?<?\w+\??>?/', $rule, $matches)) {
296
                    unset($rules[$key]);
297
                    $pattern = array_merge($this->getPattern(), $item->getPattern());
298
                    $option  = array_merge($this->getOption(), $item->getOption());
299
300
                    $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key);
301
                    $items[$key] = $item;
302
                }
303
            }
304
        }
305
306
        if (empty($regex)) {
307
            return false;
308
        }
309
310
        try {
311
            $result = preg_match('~^(?:' . implode('|', $regex) . ')~u', $url, $match);
312
        } catch (\Exception $e) {
313
            throw new Exception('route pattern error');
314
        }
315
316
        if ($result) {
317
            $var = [];
318
            foreach ($match as $key => $val) {
319
                if (is_string($key) && '' !== $val) {
320
                    [$name, $pos] = explode('_THINK_', $key);
321
322
                    $var[$name] = $val;
323
                }
324
            }
325
326
            if (!isset($pos)) {
327
                foreach ($regex as $key => $item) {
328
                    if (str_starts_with(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) {
329
                        $pos = $key;
330
                        break;
331
                    }
332
                }
333
            }
334
335
            $rule  = $items[$pos]->getRule();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pos does not seem to be defined for all execution paths leading up to this point.
Loading history...
336
            $array = $this->router->getRule($rule);
337
338
            foreach ($array as $item) {
339
                if (in_array($item->getMethod(), ['*', strtolower($request->method())])) {
340
                    $result = $item->checkRule($request, $url, $var);
341
342
                    if (false !== $result) {
343
                        return $result;
344
                    }
345
                }
346
            }
347
        }
348
349
        return false;
350
    }
351
352
    /**
353
     * 注册MISS路由
354
     * @access public
355
     * @param  string|Closure $route  路由地址
356
     * @param  string         $method 请求类型
357
     * @return RuleItem
358
     */
359 27
    public function miss(string|Closure $route, string $method = '*'): RuleItem
360
    {
361
        // 创建路由规则实例
362 27
        $method     =   strtolower($method);
363 27
        $ruleItem   =   new RuleItem($this->router, $this, null, '', $route, $method);
364
365 27
        $this->miss[$method] = $ruleItem->setMiss();
366
367 27
        return $ruleItem;
368
    }
369
370
    /**
371
     * 添加分组下的MISS路由
372
     * @access public
373
     * @param  string $method 请求类型
374
     * @return RuleItem|null
375
     */
376 6
    public function getMissRule(string $method = '*'): ?RuleItem
377
    {
378 6
        if (isset($this->miss[$method])) {
379 3
            $miss = $this->miss[$method];
380 3
        } elseif (isset($this->miss['*'])) {
381
            $miss = $this->miss['*'];
382
        } else {
383 3
            return null;
384
        }
385 3
        return $miss;
386
    }
387
388
    /**
389
     * 添加分组下的路由规则
390
     * @access public
391
     * @param  string $rule   路由规则
392
     * @param  mixed  $route  路由地址
393
     * @param  string $method 请求类型
394
     * @return RuleItem
395
     */
396 24
    public function addRule(string $rule, $route = null, string $method = '*'): RuleItem
397
    {
398
        // 读取路由标识
399 24
        if (is_string($route)) {
400 9
            $name = $route;
401
        } else {
402 15
            $name = null;
403
        }
404
405 24
        $method = strtolower($method);
406
407 24
        if ('' === $rule || '/' === $rule) {
408 3
            $rule .= '$';
409
        }
410
411
        // 创建路由规则实例
412 24
        $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method);
413
414 24
        $this->addRuleItem($ruleItem);
415
416 24
        return $ruleItem;
417
    }
418
419
    /**
420
     * 注册分组下的路由规则
421
     * @access public
422
     * @param  Rule   $rule   路由规则
423
     * @return $this
424
     */
425 24
    public function addRuleItem(Rule $rule)
426
    {
427 24
        $this->rules[] = $rule;
428 24
        return $this;
429
    }
430
431
    /**
432
     * 设置分组的路由前缀
433
     * @access public
434
     * @param  string $prefix 路由前缀
435
     * @return $this
436
     */
437
    public function prefix(string $prefix)
438
    {
439
        if ($this->parent && $this->parent->getOption('prefix')) {
440
            $prefix = $this->parent->getOption('prefix') . $prefix;
441
        }
442
443
        return $this->setOption('prefix', $prefix);
444
    }
445
446
    /**
447
     * 合并分组的路由规则正则
448
     * @access public
449
     * @param  bool $merge 是否合并
450
     * @return $this
451
     */
452 6
    public function mergeRuleRegex(bool $merge = true)
453
    {
454 6
        return $this->setOption('merge_rule_regex', $merge);
455
    }
456
457
    /**
458
     * 设置分组的Dispatch调度
459
     * @access public
460
     * @param  string $dispatch 调度类
461
     * @return $this
462
     */
463
    public function dispatcher(string $dispatch)
464
    {
465
        return $this->setOption('dispatcher', $dispatch);
466
    }
467
468
    /**
469
     * 获取完整分组Name
470
     * @access public
471
     * @return string
472
     */
473 27
    public function getFullName(): string
474
    {
475 27
        return $this->fullName ?: '';
476
    }
477
478
    /**
479
     * 获取分组的路由规则
480
     * @access public
481
     * @param  string $method 请求类型
482
     * @return array
483
     */
484 27
    public function getRules(string $method = ''): array
485
    {
486 27
        if ('' === $method) {
487
            return $this->rules;
488
        }
489
490 27
        return array_filter($this->rules, function ($item) use ($method) {
491 24
            $ruleMethod = $item->getMethod();
492 24
            return '*' == $ruleMethod || str_contains($ruleMethod, $method);
493 27
        });
494
    }
495
496
    /**
497
     * 清空分组下的路由规则
498
     * @access public
499
     * @return void
500
     */
501
    public function clear(): void
502
    {
503
        $this->rules = [];
504
    }
505
}
506