Passed
Push — 8.0 ( 5fce0a...4a7956 )
by liu
02:32
created

Rule::name()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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