Passed
Push — 8.0 ( adfc0c...00de83 )
by liu
02:15
created

RuleGroup::check()   B

Complexity

Conditions 10
Paths 45

Size

Total Lines 47
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 11.1743

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 10
eloc 22
c 5
b 0
f 0
nc 45
nop 3
dl 0
loc 47
ccs 17
cts 22
cp 0.7727
crap 11.1743
rs 7.6666

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 ($miss = $this->getMissRule($method)) {
174
            // 未匹配所有路由的路由规则处理
175 3
            $result = $miss->parseRule($request, '', $miss->getRoute(), $url, $miss->getOption());
176
        } else {
177 3
            $result = false;
178
        }
179
180 6
        return $result;
181
    }
182
183
    /**
184
     * 分组URL匹配检查
185
     * @access protected
186
     * @param  string $url URL
187
     * @return bool
188
     */
189 27
    protected function checkUrl(string $url): bool
190
    {
191 27
        if ($this->fullName) {
192
            $pos = strpos($this->fullName, '<');
193
194
            if (false !== $pos) {
195
                $str = substr($this->fullName, 0, $pos);
196
            } else {
197
                $str = $this->fullName;
198
            }
199
200
            if ($str && 0 !== stripos(str_replace('|', '/', $url), $str)) {
201
                return false;
202
            }
203
        }
204
205 27
        return true;
206
    }
207
208
    /**
209
     * 设置路由分组别名
210
     * @access public
211
     * @param  string $alias 路由分组别名
212
     * @return $this
213
     */
214
    public function alias(string $alias)
215
    {
216
        $this->alias = $alias;
217
        $this->router->getRuleName()->setGroup($alias, $this);
218
219
        return $this;
220
    }
221
222
    /**
223
     * 解析分组和域名的路由规则及绑定
224
     * @access public
225
     * @param  mixed $rule 路由规则
226
     * @return void
227
     */
228 27
    public function parseGroupRule($rule): void
229
    {
230 27
        if (is_string($rule) && is_subclass_of($rule, Dispatch::class)) {
231
            $this->dispatcher($rule);
232
            return;
233
        }
234
235 27
        $origin = $this->router->getGroup();
236 27
        $this->router->setGroup($this);
237
238 27
        if ($rule instanceof Closure) {
239 6
            Container::getInstance()->invokeFunction($rule);
240 24
        } elseif (is_string($rule) && $rule) {
241
            $this->router->bind($rule, $this->domain);
242
        }
243
244 27
        $this->router->setGroup($origin);
245 27
        $this->hasParsed = true;
246
    }
247
248
    /**
249
     * 检测分组路由
250
     * @access public
251
     * @param  Request $request       请求对象
252
     * @param  array   $rules         路由规则
253
     * @param  string  $url           访问地址
254
     * @param  bool    $completeMatch 路由是否完全匹配
255
     * @return Dispatch|false
256
     */
257
    protected function checkMergeRuleRegex(Request $request, array &$rules, string $url, bool $completeMatch)
258
    {
259
        $depr  = $this->config('pathinfo_depr');
260
        $url   = $depr . str_replace('|', $depr, $url);
261
        $regex = [];
262
        $items = [];
263
264
        foreach ($rules as $key => $item) {
265
            if ($item instanceof RuleItem) {
266
                $rule = $depr . str_replace('/', $depr, $item->getRule());
267
                if ($depr == $rule && $depr != $url) {
268
                    unset($rules[$key]);
269
                    continue;
270
                }
271
272
                $complete = $item->getOption('complete_match', $completeMatch);
273
274
                if (!str_contains($rule, '<')) {
275
                    if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) {
276
                        return $item->checkRule($request, $url, []);
277
                    }
278
279
                    unset($rules[$key]);
280
                    continue;
281
                }
282
283
                $slash = preg_quote('/-' . $depr, '/');
284
285
                if ($matchRule = preg_split('/[' . $slash . ']<\w+\??>/', $rule, 2)) {
286
                    if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) {
287
                        unset($rules[$key]);
288
                        continue;
289
                    }
290
                }
291
292
                if (preg_match_all('/[' . $slash . ']?<?\w+\??>?/', $rule, $matches)) {
293
                    unset($rules[$key]);
294
                    $pattern = array_merge($this->getPattern(), $item->getPattern());
295
                    $option  = array_merge($this->getOption(), $item->getOption());
296
297
                    $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key);
298
                    $items[$key] = $item;
299
                }
300
            }
301
        }
302
303
        if (empty($regex)) {
304
            return false;
305
        }
306
307
        try {
308
            $result = preg_match('~^(?:' . implode('|', $regex) . ')~u', $url, $match);
309
        } catch (\Exception $e) {
310
            throw new Exception('route pattern error');
311
        }
312
313
        if ($result) {
314
            $var = [];
315
            foreach ($match as $key => $val) {
316
                if (is_string($key) && '' !== $val) {
317
                    [$name, $pos] = explode('_THINK_', $key);
318
319
                    $var[$name] = $val;
320
                }
321
            }
322
323
            if (!isset($pos)) {
324
                foreach ($regex as $key => $item) {
325
                    if (str_starts_with(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) {
326
                        $pos = $key;
327
                        break;
328
                    }
329
                }
330
            }
331
332
            $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...
333
            $array = $this->router->getRule($rule);
334
335
            foreach ($array as $item) {
336
                if (in_array($item->getMethod(), ['*', strtolower($request->method())])) {
337
                    $result = $item->checkRule($request, $url, $var);
338
339
                    if (false !== $result) {
340
                        return $result;
341
                    }
342
                }
343
            }
344
        }
345
346
        return false;
347
    }
348
349
    /**
350
     * 注册MISS路由
351
     * @access public
352
     * @param  string|Closure $route  路由地址
353
     * @param  string         $method 请求类型
354
     * @return RuleItem
355
     */
356 27
    public function miss(string | Closure $route, string $method = '*'): RuleItem
357
    {
358
        // 创建路由规则实例
359 27
        $method   = strtolower($method);
360 27
        $ruleItem = new RuleItem($this->router, $this, null, '', $route, $method);
361
362 27
        $this->miss[$method] = $ruleItem->setMiss();
363
364 27
        return $ruleItem;
365
    }
366
367
    /**
368
     * 添加分组下的MISS路由
369
     * @access public
370
     * @param  string $method 请求类型
371
     * @return RuleItem|null
372
     */
373 6
    public function getMissRule(string $method = '*'): ?RuleItem
374
    {
375 6
        if (isset($this->miss[$method])) {
376 3
            $miss = $this->miss[$method];
377 3
        } elseif (isset($this->miss['*'])) {
378
            $miss = $this->miss['*'];
379
        } else {
380 3
            return null;
381
        }
382 3
        return $miss;
383
    }
384
385
    /**
386
     * 添加分组下的路由规则
387
     * @access public
388
     * @param  string $rule   路由规则
389
     * @param  mixed  $route  路由地址
390
     * @param  string $method 请求类型
391
     * @return RuleItem
392
     */
393 24
    public function addRule(string $rule, $route = null, string $method = '*'): RuleItem
394
    {
395
        // 读取路由标识
396 24
        if (is_string($route)) {
397 9
            $name = $route;
398
        } else {
399 15
            $name = null;
400
        }
401
402 24
        $method = strtolower($method);
403
404 24
        if ('' === $rule || '/' === $rule) {
405 3
            $rule .= '$';
406
        }
407
408
        // 创建路由规则实例
409 24
        $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method);
410
411 24
        $this->addRuleItem($ruleItem);
412
413 24
        return $ruleItem;
414
    }
415
416
    /**
417
     * 注册分组下的路由规则
418
     * @access public
419
     * @param  Rule   $rule   路由规则
420
     * @return $this
421
     */
422 24
    public function addRuleItem(Rule $rule)
423
    {
424 24
        $this->rules[] = $rule;
425 24
        return $this;
426
    }
427
428
    /**
429
     * 设置分组的路由前缀
430
     * @access public
431
     * @param  string $prefix 路由前缀
432
     * @return $this
433
     */
434
    public function prefix(string $prefix)
435
    {
436
        if ($this->parent && $this->parent->getOption('prefix')) {
437
            $prefix = $this->parent->getOption('prefix') . $prefix;
438
        }
439
440
        return $this->setOption('prefix', $prefix);
441
    }
442
443
    /**
444
     * 合并分组的路由规则正则
445
     * @access public
446
     * @param  bool $merge 是否合并
447
     * @return $this
448
     */
449 6
    public function mergeRuleRegex(bool $merge = true)
450
    {
451 6
        return $this->setOption('merge_rule_regex', $merge);
452
    }
453
454
    /**
455
     * 设置分组的Dispatch调度
456
     * @access public
457
     * @param  string $dispatch 调度类
458
     * @return $this
459
     */
460
    public function dispatcher(string $dispatch)
461
    {
462
        return $this->setOption('dispatcher', $dispatch);
463
    }
464
465
    /**
466
     * 获取完整分组Name
467
     * @access public
468
     * @return string
469
     */
470 27
    public function getFullName(): string
471
    {
472 27
        return $this->fullName ?: '';
473
    }
474
475
    /**
476
     * 获取分组的路由规则
477
     * @access public
478
     * @param  string $method 请求类型
479
     * @return array
480
     */
481 27
    public function getRules(string $method = ''): array
482
    {
483 27
        if ('' === $method) {
484
            return $this->rules;
485
        }
486
487 27
        return array_filter($this->rules, function ($item) use ($method) {
488 24
            $ruleMethod = $item->getMethod();
489 24
            return '*' == $ruleMethod || str_contains($ruleMethod, $method);
490 27
        });
491
    }
492
493
    /**
494
     * 清空分组下的路由规则
495
     * @access public
496
     * @return void
497
     */
498
    public function clear(): void
499
    {
500
        $this->rules = [];
501
    }
502
}
503