Passed
Push — 8.0 ( 790d0e...d6540b )
by liu
11:17
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
     * @return $this
464
     */
465
    public function withoutMiddleware()
466
    {
467
        $this->option['without_middleware'] = true;
468
469
        return $this;
470
    }
471
472
    /**
473
     * 允许跨域
474
     * @access public
475
     * @param  array $header 自定义Header
476
     * @return $this
477
     */
478 3
    public function allowCrossDomain(array $header = [])
479
    {
480 3
        return $this->middleware(AllowCrossDomain::class, $header);
481
    }
482
483
    /**
484
     * 表单令牌验证
485
     * @access public
486
     * @param  string $token 表单令牌token名称
487
     * @return $this
488
     */
489
    public function token(string $token = '__token__')
490
    {
491
        return $this->middleware(FormTokenCheck::class, $token);
492
    }
493
494
    /**
495
     * 设置路由缓存
496
     * @access public
497
     * @param  array|string|int $cache 缓存
498
     * @return $this
499
     */
500
    public function cache(array | string | int $cache)
501
    {
502
        return $this->middleware(CheckRequestCache::class, $cache);
503
    }
504
505
    /**
506
     * 检查URL分隔符
507
     * @access public
508
     * @param  string $depr URL分隔符
509
     * @return $this
510
     */
511
    public function depr(string $depr)
512
    {
513
        return $this->setOption('param_depr', $depr);
514
    }
515
516
    /**
517
     * 设置需要合并的路由参数
518
     * @access public
519
     * @param  array $option 路由参数
520
     * @return $this
521
     */
522
    public function mergeOptions(array $option = [])
523
    {
524
        $this->mergeOptions = array_merge($this->mergeOptions, $option);
525
        return $this;
526
    }
527
528
    /**
529
     * 检查是否为HTTPS请求
530
     * @access public
531
     * @param  bool $https 是否为HTTPS
532
     * @return $this
533
     */
534
    public function https(bool $https = true)
535
    {
536
        return $this->setOption('https', $https);
537
    }
538
539
    /**
540
     * 检查是否为JSON请求
541
     * @access public
542
     * @param  bool $json 是否为JSON
543
     * @return $this
544
     */
545
    public function json(bool $json = true)
546
    {
547
        return $this->setOption('json', $json);
548
    }
549
550
    /**
551
     * 检查是否为AJAX请求
552
     * @access public
553
     * @param  bool $ajax 是否为AJAX
554
     * @return $this
555
     */
556
    public function ajax(bool $ajax = true)
557
    {
558
        return $this->setOption('ajax', $ajax);
559
    }
560
561
    /**
562
     * 检查是否为PJAX请求
563
     * @access public
564
     * @param  bool $pjax 是否为PJAX
565
     * @return $this
566
     */
567
    public function pjax(bool $pjax = true)
568
    {
569
        return $this->setOption('pjax', $pjax);
570
    }
571
572
    /**
573
     * 路由到一个模板地址 需要额外传入的模板变量
574
     * @access public
575
     * @param  array $view 视图
576
     * @return $this
577
     */
578
    public function view(array $view = [])
579
    {
580
        return $this->setOption('view', $view);
581
    }
582
583
    /**
584
     * 通过闭包检查路由是否匹配
585
     * @access public
586
     * @param  callable $match 闭包
587
     * @return $this
588
     */
589
    public function match(callable $match)
590
    {
591
        return $this->setOption('match', $match);
592
    }
593
594
    /**
595
     * 设置路由完整匹配
596
     * @access public
597
     * @param  bool $match 是否完整匹配
598
     * @return $this
599
     */
600
    public function completeMatch(bool $match = true)
601
    {
602
        return $this->setOption('complete_match', $match);
603
    }
604
605
    /**
606
     * 是否去除URL最后的斜线
607
     * @access public
608
     * @param  bool $remove 是否去除最后斜线
609
     * @return $this
610
     */
611 27
    public function removeSlash(bool $remove = true)
612
    {
613 27
        return $this->setOption('remove_slash', $remove);
614
    }
615
616
    /**
617
     * 设置路由规则全局有效
618
     * @access public
619
     * @return $this
620
     */
621
    public function crossDomainRule()
622
    {
623
        $this->router->setCrossDomainRule($this);
624
        return $this;
625
    }
626
627
    /**
628
     * 解析匹配到的规则路由
629
     * @access public
630
     * @param  Request $request 请求对象
631
     * @param  string  $rule 路由规则
632
     * @param  mixed   $route 路由地址
633
     * @param  string  $url URL地址
634
     * @param  array   $option 路由参数
635
     * @param  array   $matches 匹配的变量
636
     * @return Dispatch
637
     */
638 24
    public function parseRule(Request $request, string $rule, $route, string $url, array $option = [], array $matches = []): Dispatch
639
    {
640 24
        if (is_string($route) && isset($option['prefix'])) {
641
            // 路由地址前缀
642
            $route = $option['prefix'] . $route;
643
        }
644
645
        // 替换路由地址中的变量
646 24
        $extraParams = true;
647 24
        $search      = $replace      = [];
648 24
        $depr        = $this->config('pathinfo_depr');
649 24
        foreach ($matches as $key => $value) {
650
            $search[]  = '<' . $key . '>';
651
            $replace[] = $value;
652
653
            $search[]  = ':' . $key;
654
            $replace[] = $value;
655
656
            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

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