Passed
Push — 5.2 ( da0c84...54339d )
by liu
02:41
created

RuleGroup::mergeRuleRegex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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