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