Passed
Push — 8.0 ( f6550d...d13e09 )
by liu
02:43
created

RuleGroup::check()   B

Complexity

Conditions 11
Paths 61

Size

Total Lines 50
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 12.8905

Importance

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