Passed
Push — 8.0 ( 229d2e...c06b7d )
by liu
02:44
created

Rule::parseRule()   B

Complexity

Conditions 10
Paths 72

Size

Total Lines 48
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 20.0091

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 26
c 1
b 0
f 0
nc 72
nop 6
dl 0
loc 48
ccs 15
cts 28
cp 0.5356
crap 20.0091
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\middleware\AllowCrossDomain;
18
use think\middleware\CheckRequestCache;
19
use think\middleware\FormTokenCheck;
20
use think\Request;
21
use think\Route;
22
use think\route\dispatch\Callback as CallbackDispatch;
23
use think\route\dispatch\Controller as ControllerDispatch;
24
25
/**
26
 * 路由规则基础类
27
 */
28
abstract class Rule
29
{
30
    /**
31
     * 路由标识
32
     * @var string
33
     */
34
    protected $name;
35
36
    /**
37
     * 所在域名
38
     * @var string
39
     */
40
    protected $domain;
41
42
    /**
43
     * 路由对象
44
     * @var Route
45
     */
46
    protected $router;
47
48
    /**
49
     * 路由所属分组
50
     * @var RuleGroup
51
     */
52
    protected $parent;
53
54
    /**
55
     * 路由规则
56
     * @var mixed
57
     */
58
    protected $rule;
59
60
    /**
61
     * 路由地址
62
     * @var string|Closure
63
     */
64
    protected $route;
65
66
    /**
67
     * 请求类型
68
     * @var string
69
     */
70
    protected $method = '*';
71
72
    /**
73
     * 路由变量
74
     * @var array
75
     */
76
    protected $vars = [];
77
78
    /**
79
     * 路由参数
80
     * @var array
81
     */
82
    protected $option = [];
83
84
    /**
85
     * 路由变量规则
86
     * @var array
87
     */
88
    protected $pattern = [];
89
90
    /**
91
     * 预定义变量规则
92
     * @var array
93
     */
94
    protected $regex = [
95
        'int'       => '\d+',
96
        'float'     => '\d+\.\d+',
97
        'alpha'     => '[A-Za-z]+',
98
        'alphaNum'  => '[A-Za-z0-9]+',
99
        'alphaDash' => '[A-Za-z0-9\-\_]+',
100
    ];
101
102
    /**
103
     * 需要和分组合并的路由参数
104
     * @var array
105
     */
106
    protected $mergeOptions = ['model', 'append', 'middleware'];
107
108
    abstract public function check(Request $request, string $url, bool $completeMatch = false);
109
110
    /**
111
     * 设置路由参数
112
     * @access public
113
     * @param  array $option 参数
114
     * @return $this
115
     */
116
    public function option(array $option)
117
    {
118
        $this->option = array_merge($this->option, $option);
119
120
        return $this;
121
    }
122
123
    /**
124
     * 设置单个路由参数
125
     * @access public
126
     * @param  string $name  参数名
127
     * @param  mixed  $value 值
128
     * @return $this
129
     */
130 27
    public function setOption(string $name, $value)
131
    {
132 27
        $this->option[$name] = $value;
133
134 27
        return $this;
135
    }
136
137
    /**
138
     * 注册变量规则
139
     * @access public
140
     * @param  array $regex 变量规则
141
     * @return $this
142
     */
143
    public function regex(array $regex)
144
    {
145
        $this->regex = array_merge($this->regex, $regex);
146
147
        return $this;
148
    }
149
150
    /**
151
     * 注册变量规则
152
     * @access public
153
     * @param  array $pattern 变量规则
154
     * @return $this
155
     */
156
    public function pattern(array $pattern)
157
    {
158
        $this->pattern = array_merge($this->pattern, $pattern);
159
160
        return $this;
161
    }
162
163
    /**
164
     * 设置标识
165
     * @access public
166
     * @param  string $name 标识名
167
     * @return $this
168
     */
169
    public function name(string $name)
170
    {
171
        $this->name = $name;
172
173
        return $this;
174
    }
175
176
    /**
177
     * 获取路由对象
178
     * @access public
179
     * @return Route
180
     */
181 3
    public function getRouter(): Route
182
    {
183 3
        return $this->router;
184
    }
185
186
    /**
187
     * 获取Name
188
     * @access public
189
     * @return string
190
     */
191
    public function getName(): string
192
    {
193
        return $this->name ?: '';
194
    }
195
196
    /**
197
     * 获取当前路由规则
198
     * @access public
199
     * @return mixed
200
     */
201 9
    public function getRule()
202
    {
203 9
        return $this->rule;
204
    }
205
206
    /**
207
     * 获取当前路由地址
208
     * @access public
209
     * @return mixed
210
     */
211 27
    public function getRoute()
212
    {
213 27
        return $this->route;
214
    }
215
216
    /**
217
     * 获取当前路由的变量
218
     * @access public
219
     * @return array
220
     */
221 3
    public function getVars(): array
222
    {
223 3
        return $this->vars;
224
    }
225
226
    /**
227
     * 获取Parent对象
228
     * @access public
229
     * @return $this|null
230
     */
231
    public function getParent()
232
    {
233
        return $this->parent;
234
    }
235
236
    /**
237
     * 获取路由所在域名
238
     * @access public
239
     * @return string
240
     */
241 9
    public function getDomain(): string
242
    {
243 9
        return $this->domain ?: $this->parent->getDomain();
244
    }
245
246
    /**
247
     * 获取路由参数
248
     * @access public
249
     * @param  string $name 变量名
250
     * @return mixed
251
     */
252 27
    public function config(string $name = '')
253
    {
254 27
        return $this->router->config($name);
255
    }
256
257
    /**
258
     * 获取变量规则定义
259
     * @access public
260
     * @param  string $name 变量名
261
     * @return mixed
262
     */
263 24
    public function getPattern(string $name = '')
264
    {
265 24
        $pattern = $this->pattern;
266
267 24
        if ($this->parent) {
268 24
            $pattern = array_merge($this->parent->getPattern(), $pattern);
269
        }
270
271 24
        if ('' === $name) {
272 24
            return $pattern;
273
        }
274
275
        return $pattern[$name] ?? null;
276
    }
277
278
    /**
279
     * 获取路由参数定义
280
     * @access public
281
     * @param  string $name 参数名
282
     * @param  mixed  $default 默认值
283
     * @return mixed
284
     */
285 27
    public function getOption(string $name = '', $default = null)
286
    {
287 27
        $option = $this->option;
288
289 27
        if ($this->parent) {
290 24
            $parentOption = $this->parent->getOption();
291
292
            // 合并分组参数
293 24
            foreach ($this->mergeOptions as $item) {
294 24
                if (isset($parentOption[$item]) && isset($option[$item])) {
295
                    $option[$item] = array_merge($parentOption[$item], $option[$item]);
296
                }
297
            }
298
299 24
            $option = array_merge($parentOption, $option);
300
        }
301
302 27
        if ('' === $name) {
303 27
            return $option;
304
        }
305
306 9
        return $option[$name] ?? $default;
307
    }
308
309
    /**
310
     * 获取当前路由的请求类型
311
     * @access public
312
     * @return string
313
     */
314 24
    public function getMethod(): string
315
    {
316 24
        return strtolower($this->method);
317
    }
318
319
    /**
320
     * 设置路由请求类型
321
     * @access public
322
     * @param  string $method 请求类型
323
     * @return $this
324
     */
325
    public function method(string $method)
326
    {
327
        return $this->setOption('method', strtolower($method));
328
    }
329
330
    /**
331
     * 检查后缀
332
     * @access public
333
     * @param  string $ext URL后缀
334
     * @return $this
335
     */
336
    public function ext(string $ext = '')
337
    {
338
        return $this->setOption('ext', $ext);
339
    }
340
341
    /**
342
     * 检查禁止后缀
343
     * @access public
344
     * @param  string $ext URL后缀
345
     * @return $this
346
     */
347
    public function denyExt(string $ext = '')
348
    {
349
        return $this->setOption('deny_ext', $ext);
350
    }
351
352
    /**
353
     * 检查域名
354
     * @access public
355
     * @param  string $domain 域名
356
     * @return $this
357
     */
358
    public function domain(string $domain)
359
    {
360
        $this->domain = $domain;
361
        return $this->setOption('domain', $domain);
362
    }
363
364
    /**
365
     * 是否区分大小写
366
     * @access public
367
     * @param  bool $case 是否区分
368
     * @return $this
369
     */
370
    public function caseUrl(bool $case)
371
    {
372
        return $this->setOption('case_sensitive', $case);
373
    }
374
375
    /**
376
     * 设置参数过滤检查
377
     * @access public
378
     * @param  array $filter 参数过滤
379
     * @return $this
380
     */
381
    public function filter(array $filter)
382
    {
383
        $this->option['filter'] = $filter;
384
385
        return $this;
386
    }
387
388
    /**
389
     * 绑定模型
390
     * @access public
391
     * @param  array|string|Closure $var  路由变量名 多个使用 & 分割
392
     * @param  string|Closure       $model 绑定模型类
393
     * @param  bool                  $exception 是否抛出异常
394
     * @return $this
395
     */
396
    public function model(array | string | Closure $var, string | Closure $model = null, bool $exception = true)
397
    {
398
        if ($var instanceof Closure) {
0 ignored issues
show
introduced by
$var is never a sub-type of Closure.
Loading history...
399
            $this->option['model'][] = $var;
400
        } elseif (is_array($var)) {
0 ignored issues
show
introduced by
The condition is_array($var) is always true.
Loading history...
401
            $this->option['model'] = $var;
402
        } elseif (is_null($model)) {
403
            $this->option['model']['id'] = [$var, true];
404
        } else {
405
            $this->option['model'][$var] = [$model, $exception];
406
        }
407
408
        return $this;
409
    }
410
411
    /**
412
     * 附加路由隐式参数
413
     * @access public
414
     * @param  array $append 追加参数
415
     * @return $this
416
     */
417
    public function append(array $append = [])
418
    {
419
        $this->option['append'] = $append;
420
421
        return $this;
422
    }
423
424
    /**
425
     * 绑定验证
426
     * @access public
427
     * @param  mixed  $validate 验证器类
428
     * @param  string $scene 验证场景
429
     * @param  array  $message 验证提示
430
     * @param  bool   $batch 批量验证
431
     * @return $this
432
     */
433
    public function validate($validate, string $scene = null, array $message = [], bool $batch = false)
434
    {
435
        $this->option['validate'] = [$validate, $scene, $message, $batch];
436
437
        return $this;
438
    }
439
440
    /**
441
     * 指定路由中间件
442
     * @access public
443
     * @param string|array|Closure $middleware 中间件
444
     * @param mixed $params 参数
445
     * @return $this
446
     */
447 3
    public function middleware(string | array | Closure $middleware, ...$params)
448
    {
449 3
        if (empty($params) && is_array($middleware)) {
450
            $this->option['middleware'] = $middleware;
451
        } else {
452 3
            foreach ((array) $middleware as $item) {
453 3
                $this->option['middleware'][] = [$item, $params];
454
            }
455
        }
456
457 3
        return $this;
458
    }
459
460
    /**
461
     * 允许跨域
462
     * @access public
463
     * @param  array $header 自定义Header
464
     * @return $this
465
     */
466 3
    public function allowCrossDomain(array $header = [])
467
    {
468 3
        return $this->middleware(AllowCrossDomain::class, $header);
469
    }
470
471
    /**
472
     * 表单令牌验证
473
     * @access public
474
     * @param  string $token 表单令牌token名称
475
     * @return $this
476
     */
477
    public function token(string $token = '__token__')
478
    {
479
        return $this->middleware(FormTokenCheck::class, $token);
480
    }
481
482
    /**
483
     * 设置路由缓存
484
     * @access public
485
     * @param  array|string|int $cache 缓存
486
     * @return $this
487
     */
488
    public function cache(array | string | int $cache)
489
    {
490
        return $this->middleware(CheckRequestCache::class, $cache);
491
    }
492
493
    /**
494
     * 检查URL分隔符
495
     * @access public
496
     * @param  string $depr URL分隔符
497
     * @return $this
498
     */
499
    public function depr(string $depr)
500
    {
501
        return $this->setOption('param_depr', $depr);
502
    }
503
504
    /**
505
     * 设置需要合并的路由参数
506
     * @access public
507
     * @param  array $option 路由参数
508
     * @return $this
509
     */
510
    public function mergeOptions(array $option = [])
511
    {
512
        $this->mergeOptions = array_merge($this->mergeOptions, $option);
513
        return $this;
514
    }
515
516
    /**
517
     * 检查是否为HTTPS请求
518
     * @access public
519
     * @param  bool $https 是否为HTTPS
520
     * @return $this
521
     */
522
    public function https(bool $https = true)
523
    {
524
        return $this->setOption('https', $https);
525
    }
526
527
    /**
528
     * 检查是否为JSON请求
529
     * @access public
530
     * @param  bool $json 是否为JSON
531
     * @return $this
532
     */
533
    public function json(bool $json = true)
534
    {
535
        return $this->setOption('json', $json);
536
    }
537
538
    /**
539
     * 检查是否为AJAX请求
540
     * @access public
541
     * @param  bool $ajax 是否为AJAX
542
     * @return $this
543
     */
544
    public function ajax(bool $ajax = true)
545
    {
546
        return $this->setOption('ajax', $ajax);
547
    }
548
549
    /**
550
     * 检查是否为PJAX请求
551
     * @access public
552
     * @param  bool $pjax 是否为PJAX
553
     * @return $this
554
     */
555
    public function pjax(bool $pjax = true)
556
    {
557
        return $this->setOption('pjax', $pjax);
558
    }
559
560
    /**
561
     * 路由到一个模板地址 需要额外传入的模板变量
562
     * @access public
563
     * @param  array $view 视图
564
     * @return $this
565
     */
566
    public function view(array $view = [])
567
    {
568
        return $this->setOption('view', $view);
569
    }
570
571
    /**
572
     * 通过闭包检查路由是否匹配
573
     * @access public
574
     * @param  callable $match 闭包
575
     * @return $this
576
     */
577
    public function match(callable $match)
578
    {
579
        return $this->setOption('match', $match);
580
    }
581
582
    /**
583
     * 设置路由完整匹配
584
     * @access public
585
     * @param  bool $match 是否完整匹配
586
     * @return $this
587
     */
588
    public function completeMatch(bool $match = true)
589
    {
590
        return $this->setOption('complete_match', $match);
591
    }
592
593
    /**
594
     * 是否去除URL最后的斜线
595
     * @access public
596
     * @param  bool $remove 是否去除最后斜线
597
     * @return $this
598
     */
599 27
    public function removeSlash(bool $remove = true)
600
    {
601 27
        return $this->setOption('remove_slash', $remove);
602
    }
603
604
    /**
605
     * 设置路由规则全局有效
606
     * @access public
607
     * @return $this
608
     */
609
    public function crossDomainRule()
610
    {
611
        $this->router->setCrossDomainRule($this);
612
        return $this;
613
    }
614
615
    /**
616
     * 解析匹配到的规则路由
617
     * @access public
618
     * @param  Request $request 请求对象
619
     * @param  string  $rule 路由规则
620
     * @param  mixed   $route 路由地址
621
     * @param  string  $url URL地址
622
     * @param  array   $option 路由参数
623
     * @param  array   $matches 匹配的变量
624
     * @return Dispatch
625
     */
626 24
    public function parseRule(Request $request, string $rule, $route, string $url, array $option = [], array $matches = []): Dispatch
627
    {
628 24
        if (is_string($route) && isset($option['prefix'])) {
629
            // 路由地址前缀
630
            $route = $option['prefix'] . $route;
631
        }
632
633
        // 替换路由地址中的变量
634 24
        $extraParams = true;
635 24
        $search      = $replace      = [];
636 24
        $depr        = $this->config('pathinfo_depr');
637 24
        foreach ($matches as $key => $value) {
638
            $search[]  = '<' . $key . '>';
639
            $replace[] = $value;
640
641
            $search[]  = ':' . $key;
642
            $replace[] = $value;
643
644
            if (str_contains($value, $depr)) {
0 ignored issues
show
Bug introduced by
It seems like $depr can also be of type null; however, parameter $needle of str_contains() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

644
            if (str_contains($value, /** @scrutinizer ignore-type */ $depr)) {
Loading history...
645
                $extraParams = false;
646
            }
647
        }
648
649 24
        if (is_string($route)) {
650 9
            $route = str_replace($search, $replace, $route);
651
        }
652
653
        // 解析额外参数
654 24
        if ($extraParams) {
655 24
            $count = substr_count($rule, '/');
656 24
            $url   = array_slice(explode('|', $url), $count + 1);
657 24
            $this->parseUrlParams(implode('|', $url), $matches);
658
        }
659
660 24
        foreach ($matches as $key => &$val) {
661
            if (isset($this->pattern[$key]) && in_array($this->pattern[$key], ['\d+', 'int', 'float'])) {
662
                $val = match ($this->pattern[$key]) {
663
                    'int', '\d+' => (int) $val,
664
                    'float'      => (float) $val,
665
                    default      => $val,
666
                };
667
            }
668
        }
669
670 24
        $this->vars = $matches;
671
672
        // 发起路由调度
673 24
        return $this->dispatch($request, $route, $option);
674
    }
675
676
    /**
677
     * 发起路由调度
678
     * @access protected
679
     * @param  Request $request Request对象
680
     * @param  mixed   $route  路由地址
681
     * @param  array   $option 路由参数
682
     * @return Dispatch
683
     */
684 24
    protected function dispatch(Request $request, $route, array $option): Dispatch
685
    {
686 24
        if (isset($option['dispatcher']) && is_subclass_of($option['dispatcher'], Dispatch::class)) {
687
            // 指定分组的调度处理对象
688
            $result = new $option['dispatcher']($request, $this, $route, $this->vars);
689 24
        } elseif (is_subclass_of($route, Dispatch::class)) {
690
            $result = new $route($request, $this, $route, $this->vars);
691 24
        } elseif ($route instanceof Closure) {
692
            // 执行闭包
693 15
            $result = new CallbackDispatch($request, $this, $route, $this->vars);
694 9
        } elseif (is_array($route)) {
695
            // 路由到类的方法
696
            $result = $this->dispatchMethod($request, $route);
697 9
        } elseif (str_contains($route, '@') || str_contains($route, '::') || str_contains($route, '\\')) {
698
            // 路由到类的方法
699
            $route  = str_replace('::', '@', $route);
700
            $result = $this->dispatchMethod($request, $route);
701
        } else {
702
            // 路由到控制器/操作
703 9
            $result = $this->dispatchController($request, $route);
704
        }
705
706 24
        return $result;
707
    }
708
709
    /**
710
     * 调度到类的方法
711
     * @access protected
712
     * @param  Request $request Request对象
713
     * @param  string|array  $route 路由地址
714
     * @return CallbackDispatch
715
     */
716
    protected function dispatchMethod(Request $request, string | array $route): CallbackDispatch
717
    {
718
        if (is_string($route)) {
0 ignored issues
show
introduced by
The condition is_string($route) is always false.
Loading history...
719
            $path = $this->parseUrlPath($route);
720
721
            $route  = str_replace('/', '@', implode('/', $path));
722
            $method = str_contains($route, '@') ? explode('@', $route) : $route;
723
        } else {
724
            $method = $route;
725
        }
726
727
        return new CallbackDispatch($request, $this, $method, $this->vars);
728
    }
729
730
    /**
731
     * 调度到控制器方法 规则:模块/控制器/操作
732
     * @access protected
733
     * @param  Request $request Request对象
734
     * @param  string  $route 路由地址
735
     * @return ControllerDispatch
736
     */
737 9
    protected function dispatchController(Request $request, string $route): ControllerDispatch
738
    {
739 9
        $path = $this->parseUrlPath($route);
740
741 9
        $action     = array_pop($path);
742 9
        $controller = !empty($path) ? array_pop($path) : null;
743
744
        // 路由到模块/控制器/操作
745 9
        return new ControllerDispatch($request, $this, [$controller, $action], $this->vars);
746
    }
747
748
    /**
749
     * 路由检查
750
     * @access protected
751
     * @param  array   $option 路由参数
752
     * @param  Request $request Request对象
753
     * @return bool
754
     */
755 27
    protected function checkOption(array $option, Request $request): bool
756
    {
757
        // 检查当前路由是否匹配
758 27
        if (isset($option['match']) && is_callable($option['match'])) {
759
            if (false === $option['match']($this, $request)) {
760
                return false;
761
            }
762
        }
763
764
        // 请求类型检测
765 27
        if (!empty($option['method'])) {
766
            if (is_string($option['method']) && false === stripos($option['method'], $request->method())) {
767
                return false;
768
            }
769
        }
770
771
        // AJAX PJAX 请求检查
772 27
        foreach (['ajax', 'pjax', 'json'] as $item) {
773 27
            if (isset($option[$item])) {
774
                $call = 'is' . $item;
775
                if ($option[$item] && !$request->$call() || !$option[$item] && $request->$call()) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($option[$item] && ! $re...m] && $request->$call(), Probably Intended Meaning: $option[$item] && (! $re...] && $request->$call())
Loading history...
776
                    return false;
777
                }
778
            }
779
        }
780
781
        // 伪静态后缀检测
782 27
        if ($request->url() != '/' && ((isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|'))
783 27
            || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')))) {
784
            return false;
785
        }
786
787
        // 域名检查
788 27
        if ((isset($option['domain']) && !in_array($option['domain'], [$request->host(true), $request->subDomain()]))) {
789
            return false;
790
        }
791
792
        // HTTPS检查
793 27
        if ((isset($option['https']) && $option['https'] && !$request->isSsl())
794 27
            || (isset($option['https']) && !$option['https'] && $request->isSsl())
795
        ) {
796
            return false;
797
        }
798
799
        // 请求参数检查
800 27
        if (isset($option['filter'])) {
801
            foreach ($option['filter'] as $name => $value) {
802
                if ($request->param($name, '') != $value) {
803
                    return false;
804
                }
805
            }
806
        }
807
808 27
        return true;
809
    }
810
811
    /**
812
     * 解析URL地址中的参数Request对象
813
     * @access protected
814
     * @param  string $rule 路由规则
815
     * @param  array  $var 变量
816
     * @return void
817
     */
818 24
    protected function parseUrlParams(string $url, array &$var = []): void
819
    {
820 24
        if ($url) {
821
            preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) {
822
                $var[$match[1]] = strip_tags($match[2]);
823
            }, $url);
824
        }
825
    }
826
827
    /**
828
     * 解析URL的pathinfo参数
829
     * @access public
830
     * @param  string $url URL地址
831
     * @return array
832
     */
833 12
    public function parseUrlPath(string $url): array
834
    {
835
        // 分隔符替换 确保路由定义使用统一的分隔符
836 12
        $url = str_replace('|', '/', $url);
837 12
        $url = trim($url, '/');
838
839 12
        if (str_contains($url, '/')) {
840
            // [控制器/操作]
841 9
            $path = explode('/', $url);
842
        } else {
843 3
            $path = [$url];
844
        }
845
846 12
        return $path;
847
    }
848
849
    /**
850
     * 生成路由的正则规则
851
     * @access protected
852
     * @param  string $rule 路由规则
853
     * @param  array  $match 匹配的变量
854
     * @param  array  $pattern   路由变量规则
855
     * @param  array  $option    路由参数
856
     * @param  bool   $completeMatch   路由是否完全匹配
857
     * @param  string $suffix   路由正则变量后缀
858
     * @return string
859
     */
860
    protected function buildRuleRegex(string $rule, array $match, array $pattern = [], array $option = [], bool $completeMatch = false, string $suffix = ''): string
861
    {
862
        foreach ($match as $name) {
863
            $value = $this->buildNameRegex($name, $pattern, $suffix);
864
            if ($value) {
865
                $origin[]  = $name;
866
                $replace[] = $value;
867
            }
868
        }
869
870
        // 是否区分 / 地址访问
871
        if ('/' != $rule) {
872
            if (!empty($option['remove_slash'])) {
873
                $rule = rtrim($rule, '/');
874
            } elseif (str_ends_with($rule, '/')) {
875
                $rule     = rtrim($rule, '/');
876
                $hasSlash = true;
877
            }
878
        }
879
880
        $regex = isset($replace) ? str_replace($origin, $replace, $rule) : $rule;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $origin does not seem to be defined for all execution paths leading up to this point.
Loading history...
881
        $regex = str_replace([')?/', ')?-'], [')/', ')-'], $regex);
882
883
        if (isset($hasSlash)) {
884
            $regex .= '/';
885
        }
886
887
        return $regex . ($completeMatch ? '$' : '');
888
    }
889
890
    /**
891
     * 生成路由变量的正则规则
892
     * @access protected
893
     * @param  string $name    路由变量
894
     * @param  array  $pattern 变量规则
895
     * @param  string $suffix  路由正则变量后缀
896
     * @return string
897
     */
898
    protected function buildNameRegex(string $name, array $pattern, string $suffix): string
899
    {
900
        $optional = '';
901
        $slash    = substr($name, 0, 1);
902
903
        if (in_array($slash, ['/', '-'])) {
904
            $prefix = $slash;
905
            $name   = substr($name, 1);
906
            $slash  = substr($name, 0, 1);
907
        } else {
908
            $prefix = '';
909
        }
910
911
        if ('<' != $slash) {
912
            return '';
913
        }
914
915
        if (str_contains($name, '?')) {
916
            $name     = substr($name, 1, -2);
917
            $optional = '?';
918
        } elseif (str_contains($name, '>')) {
919
            $name = substr($name, 1, -1);
920
        }
921
922
        if (isset($pattern[$name])) {
923
            $nameRule = $pattern[$name];
924
            if (isset($this->regex[$nameRule])) {
925
                $nameRule = $this->regex[$nameRule];
926
            }
927
928
            if (str_starts_with($nameRule, '/') && str_ends_with($nameRule, '/')) {
929
                $nameRule = substr($nameRule, 1, -1);
930
            }
931
        } else {
932
            $nameRule = $this->config('default_route_pattern');
933
        }
934
935
        return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional;
936
    }
937
938
    /**
939
     * 设置路由参数
940
     * @access public
941
     * @param  string $method 方法名
942
     * @param  array  $args   调用参数
943
     * @return $this
944
     */
945
    public function __call($method, $args)
946
    {
947
        if (count($args) > 1) {
948
            $args[0] = $args;
949
        }
950
        array_unshift($args, $method);
951
952
        return call_user_func_array([$this, 'setOption'], $args);
953
    }
954
955
    public function __sleep()
956
    {
957
        return ['name', 'rule', 'route', 'method', 'vars', 'option', 'pattern'];
958
    }
959
960
    public function __wakeup()
961
    {
962
        $this->router = Container::pull('route');
963
    }
964
965
    public function __debugInfo()
966
    {
967
        return [
968
            'name'    => $this->name,
969
            'rule'    => $this->rule,
970
            'route'   => $this->route,
971
            'method'  => $this->method,
972
            'vars'    => $this->vars,
973
            'option'  => $this->option,
974
            'pattern' => $this->pattern,
975
        ];
976
    }
977
}
978