Completed
Push — 6.0 ( 88124b...2e07d8 )
by liu
02:03
created

RuleGroup::check()   C

Complexity

Conditions 12
Paths 89

Size

Total Lines 49
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 12.7571

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 24
c 1
b 0
f 0
nc 89
nop 3
dl 0
loc 49
ccs 19
cts 23
cp 0.8261
crap 12.7571
rs 6.9666

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