Passed
Push — 8.0 ( 4448c2...81f206 )
by liu
02:14
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 6
    public function __construct(Route $router, ?RuleGroup $parent = null, string $name = '', $rule = null, bool $lazy = false)
72
    {
73 6
        $this->router = $router;
74 6
        $this->parent = $parent;
75 6
        $this->rule   = $rule;
76 6
        $this->name   = trim($name, '/');
77
78 6
        $this->setFullName();
79
80 6
        if ($this->parent) {
81 3
            $this->domain = $this->parent->getDomain();
82 3
            $this->parent->addRuleItem($this);
83
        }
84
85 6
        if (!$lazy) {
86 6
            $this->parseGroupRule($rule);
87
        }
88
    }
89
90
    /**
91
     * 设置分组的路由规则
92
     * @access public
93
     * @return void
94
     */
95 6
    protected function setFullName(): void
96
    {
97 6
        if (str_contains($this->name, ':')) {
98
            $this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name);
99
        }
100
101 6
        if ($this->parent && $this->parent->getFullName()) {
102
            $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : '');
103
        } else {
104 6
            $this->fullName = $this->name;
105
        }
106
107 6
        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
     * 注册分组默认URL解析路由
360
     * @access public
361
     * @param  array     $option 解析规则
362
     * @return RuleItem
363
     */
364 3
    public function autoUrl(array $option = []): RuleItem
365
    {
366 3
        if (!empty($option)) {
367
            [$rule, $route] = $option;
368
        } else {
369 3
            $name  = $this->getfullName();
370 3
            $layer = $name ? $name . '/' : '';
371 3
            $rule  = $layer . '[:controller]/[:action]';
372 3
            $route = $layer . ':controller/:action';
373
        }
374
375 3
        $ruleItem = new UrlRuleItem($this->router, new RuleGroup($this->router), '_default_route_', $rule, $route);
376
377 3
        $ruleItem = $ruleItem->default([
378 3
            'controller' => $this->config('default_controller'),
379 3
            'action'     => $this->config('default_action'),
380 3
        ])->pattern([
381 3
            'controller' => '[A-Za-z0-9\.\_]+',
382 3
            'action'     => '[A-Za-z0-9\_]+',
383 3
        ])->option($this->getOption());
384
385 3
        $this->urlDispatch = $ruleItem;
386 3
        return $ruleItem;
387
    }
388
389
    /**
390
     * 注册MISS路由
391
     * @access public
392
     * @param  string|Closure $route  路由地址
393
     * @param  string         $method 请求类型
394
     * @return RuleItem
395
     */
396 27
    public function miss(string | Closure $route, string $method = '*'): RuleItem
397
    {
398
        // 创建路由规则实例
399 27
        $method   = strtolower($method);
400 27
        $ruleItem = new RuleItem($this->router, $this, null, '', $route, $method);
401
402 27
        $this->miss[$method] = $ruleItem->setMiss();
403
404 27
        return $ruleItem;
405
    }
406
407
    /**
408
     * 添加分组下的MISS路由
409
     * @access public
410
     * @param  string $method 请求类型
411
     * @return RuleItem|null
412
     */
413 6
    public function getMissRule(string $method = '*'): ?RuleItem
414
    {
415 6
        if (isset($this->miss[$method])) {
416 3
            $miss = $this->miss[$method];
417 3
        } elseif (isset($this->miss['*'])) {
418
            $miss = $this->miss['*'];
419
        }
420 6
        return $miss ?? null;
421
    }
422
423
    /**
424
     * 添加分组下的路由规则
425
     * @access public
426
     * @param  string $rule   路由规则
427
     * @param  mixed  $route  路由地址
428
     * @param  string $method 请求类型
429
     * @return RuleItem
430
     */
431 24
    public function addRule(string $rule, $route = null, string $method = '*'): RuleItem
432
    {
433
        // 读取路由标识
434 24
        if (is_string($route)) {
435 9
            $name = $route;
436
        } else {
437 15
            $name = null;
438
        }
439
440 24
        $method = strtolower($method);
441
442 24
        if ('' === $rule || '/' === $rule) {
443 3
            $rule .= '$';
444
        }
445
446
        // 创建路由规则实例
447 24
        $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method);
448
449 24
        $this->addRuleItem($ruleItem);
450
451 24
        return $ruleItem;
452
    }
453
454
    /**
455
     * 注册分组下的路由规则
456
     * @access public
457
     * @param  Rule   $rule   路由规则
458
     * @return $this
459
     */
460 24
    public function addRuleItem(Rule $rule)
461
    {
462 24
        $this->rules[] = $rule;
463 24
        return $this;
464
    }
465
466
    /**
467
     * 设置分组的路由前缀
468
     * @access public
469
     * @param  string $prefix 路由前缀
470
     * @return $this
471
     */
472
    public function prefix(string $prefix)
473
    {
474
        if ($this->parent && $this->parent->getOption('prefix')) {
475
            $prefix = $this->parent->getOption('prefix') . $prefix;
476
        }
477
478
        return $this->setOption('prefix', $prefix);
479
    }
480
481
    /**
482
     * 合并分组的路由规则正则
483
     * @access public
484
     * @param  bool $merge 是否合并
485
     * @return $this
486
     */
487 6
    public function mergeRuleRegex(bool $merge = true)
488
    {
489 6
        return $this->setOption('merge_rule_regex', $merge);
490
    }
491
492
    /**
493
     * 设置分组的Dispatch调度
494
     * @access public
495
     * @param  string $dispatch 调度类
496
     * @return $this
497
     */
498
    public function dispatcher(string $dispatch)
499
    {
500
        return $this->setOption('dispatcher', $dispatch);
501
    }
502
503
    /**
504
     * 获取完整分组Name
505
     * @access public
506
     * @return string
507
     */
508 27
    public function getFullName(): string
509
    {
510 27
        return $this->fullName ?: '';
511
    }
512
513
    /**
514
     * 获取分组的路由规则
515
     * @access public
516
     * @param  string $method 请求类型
517
     * @return array
518
     */
519 27
    public function getRules(string $method = ''): array
520
    {
521 27
        if ('' === $method) {
522
            return $this->rules;
523
        }
524
525 27
        return array_filter($this->rules, function ($item) use ($method) {
526 24
            $ruleMethod = $item->getMethod();
527 24
            return '*' == $ruleMethod || str_contains($ruleMethod, $method);
528 27
        });
529
    }
530
531
    /**
532
     * 清空分组下的路由规则
533
     * @access public
534
     * @return void
535
     */
536
    public function clear(): void
537
    {
538
        $this->rules = [];
539
    }
540
}
541