Passed
Push — 8.0 ( a85345...fadba0 )
by liu
02:39
created

Rule::token()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
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\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
     *
166
     * @access public
167
     * @param  string $name 变量名
168
     * @param  mixed  $rule 变量规则
169
     * @return $this
170
     */
171
    public function when(string | array $name, $rule = null)
172
    {
173
        if (is_array($name)) {
0 ignored issues
show
introduced by
The condition is_array($name) is always true.
Loading history...
174
            $this->option['var_rule'] = $name;
175
        } else {
176
            $this->option['var_rule'][$name] = $rule;
177
        }
178
179
        return $this;
180
    }
181
182
    /**
183
     * 设置标识
184
     * @access public
185
     * @param  string $name 标识名
186
     * @return $this
187
     */
188
    public function name(string $name)
189
    {
190
        $this->name = $name;
191
192
        return $this;
193
    }
194
195
    /**
196
     * 获取路由对象
197
     * @access public
198
     * @return Route
199
     */
200
    public function getRouter(): Route
201
    {
202
        return $this->router;
203
    }
204
205
    /**
206
     * 获取Name
207
     * @access public
208
     * @return string
209
     */
210
    public function getName(): string
211
    {
212
        return $this->name ?: '';
213
    }
214
215
    /**
216
     * 获取当前路由规则
217
     * @access public
218
     * @return mixed
219
     */
220 9
    public function getRule()
221
    {
222 9
        return $this->rule;
223
    }
224
225
    /**
226
     * 获取当前路由地址
227
     * @access public
228
     * @return mixed
229
     */
230 27
    public function getRoute()
231
    {
232 27
        return $this->route;
233
    }
234
235
    /**
236
     * 获取当前路由的变量
237
     * @access public
238
     * @return array
239
     */
240 3
    public function getVars(): array
241
    {
242 3
        return $this->vars;
243
    }
244
245
    /**
246
     * 获取Parent对象
247
     * @access public
248
     * @return $this|null
249
     */
250
    public function getParent()
251
    {
252
        return $this->parent;
253
    }
254
255
    /**
256
     * 获取路由所在域名
257
     * @access public
258
     * @return string
259
     */
260 9
    public function getDomain(): string
261
    {
262 9
        return $this->domain ?: $this->parent->getDomain();
263
    }
264
265
    /**
266
     * 获取路由参数
267
     * @access public
268
     * @param  string $name 变量名
269
     * @return mixed
270
     */
271 27
    public function config(string $name = '')
272
    {
273 27
        return $this->router->config($name);
274
    }
275
276
    /**
277
     * 获取变量规则定义
278
     * @access public
279
     * @param  string $name 变量名
280
     * @return mixed
281
     */
282 24
    public function getPattern(string $name = '')
283
    {
284 24
        $pattern = $this->pattern;
285
286 24
        if ($this->parent) {
287 24
            $pattern = array_merge($this->parent->getPattern(), $pattern);
288
        }
289
290 24
        if ('' === $name) {
291 24
            return $pattern;
292
        }
293
294
        return $pattern[$name] ?? null;
295
    }
296
297
    /**
298
     * 获取路由参数定义
299
     * @access public
300
     * @param  string $name 参数名
301
     * @param  mixed  $default 默认值
302
     * @return mixed
303
     */
304 27
    public function getOption(string $name = '', $default = null)
305
    {
306 27
        $option = $this->option;
307
308 27
        if ($this->parent) {
309 24
            $parentOption = $this->parent->getOption();
310
311
            // 合并分组参数
312 24
            foreach ($this->mergeOptions as $item) {
313 24
                if (isset($parentOption[$item]) && isset($option[$item])) {
314
                    $option[$item] = array_merge($parentOption[$item], $option[$item]);
315
                }
316
            }
317
318 24
            $option = array_merge($parentOption, $option);
319
        }
320
321 27
        if ('' === $name) {
322 27
            return $option;
323
        }
324
325 9
        return $option[$name] ?? $default;
326
    }
327
328
    /**
329
     * 获取当前路由的请求类型
330
     * @access public
331
     * @return string
332
     */
333 24
    public function getMethod(): string
334
    {
335 24
        return strtolower($this->method);
336
    }
337
338
    /**
339
     * 设置路由请求类型
340
     * @access public
341
     * @param  string $method 请求类型
342
     * @return $this
343
     */
344
    public function method(string $method)
345
    {
346
        return $this->setOption('method', strtolower($method));
347
    }
348
349
    /**
350
     * 检查后缀
351
     * @access public
352
     * @param  string $ext URL后缀
353
     * @return $this
354
     */
355
    public function ext(string $ext = '')
356
    {
357
        return $this->setOption('ext', $ext);
358
    }
359
360
    /**
361
     * 检查禁止后缀
362
     * @access public
363
     * @param  string $ext URL后缀
364
     * @return $this
365
     */
366
    public function denyExt(string $ext = '')
367
    {
368
        return $this->setOption('deny_ext', $ext);
369
    }
370
371
    /**
372
     * 检查域名
373
     * @access public
374
     * @param  string $domain 域名
375
     * @return $this
376
     */
377
    public function domain(string $domain)
378
    {
379
        $this->domain = $domain;
380
        return $this->setOption('domain', $domain);
381
    }
382
383
    /**
384
     * 是否区分大小写
385
     * @access public
386
     * @param  bool $case 是否区分
387
     * @return $this
388
     */
389
    public function caseUrl(bool $case)
390
    {
391
        return $this->setOption('case_sensitive', $case);
392
    }
393
394
    /**
395
     * 设置参数过滤检查
396
     * @access public
397
     * @param  array $filter 参数过滤
398
     * @return $this
399
     */
400
    public function filter(array $filter)
401
    {
402
        $this->option['filter'] = $filter;
403
404
        return $this;
405
    }
406
407
    /**
408
     * 设置路由变量默认值
409
     * @access public
410
     * @param  array $default 可选路由变量默认值
411
     * @return $this
412
     */
413
    public function default(array $default)
414
    {
415
        $this->option['default'] = $default;
416
417
        return $this;
418
    }
419
420
    /**
421
     * 设置路由自动注册中间件
422
     * @access public
423
     * @param  bool $auto 
424
     * @return $this
425
     */
426
    public function autoMiddleware(bool $auto = true)
427
    {
428
        $this->option['auto_middleware'] = $auto;
429
430
        return $this;
431
    }
432
433
    /**
434
     * 绑定模型
435
     * @access public
436
     * @param  array|string|Closure $var  路由变量名 多个使用 & 分割
437
     * @param  string|Closure|null  $model 绑定模型类
438
     * @param  bool                 $exception 是否抛出异常
439
     * @return $this
440
     */
441
    public function model(array | string | Closure $var, string | Closure | null $model = null, bool $exception = true)
442
    {
443
        if ($var instanceof Closure) {
0 ignored issues
show
introduced by
$var is never a sub-type of Closure.
Loading history...
444
            $this->option['model'][] = $var;
445
        } elseif (is_array($var)) {
0 ignored issues
show
introduced by
The condition is_array($var) is always true.
Loading history...
446
            $this->option['model'] = $var;
447
        } elseif (is_null($model)) {
448
            $this->option['model']['id'] = [$var, true];
449
        } else {
450
            $this->option['model'][$var] = [$model, $exception];
451
        }
452
453
        return $this;
454
    }
455
456
    /**
457
     * 附加路由隐式参数
458
     * @access public
459
     * @param  array $append 追加参数
460
     * @return $this
461
     */
462
    public function append(array $append = [])
463
    {
464
        $this->option['append'] = $append;
465
466
        return $this;
467
    }
468
469
    /**
470
     * 绑定验证
471
     * @access public
472
     * @param  mixed        $validate 验证器类
473
     * @param  string|array $scene 验证场景
474
     * @param  array        $message 验证提示
475
     * @param  bool         $batch 批量验证
476
     * @return $this
477
     */
478
    public function validate($validate, string | array $scene = '', array $message = [], bool $batch = false)
479
    {
480
        $this->option['validate'] = [$validate, $scene, $message, $batch];
481
482
        return $this;
483
    }
484
485
    /**
486
     * 指定路由中间件
487
     * @access public
488
     * @param string|array|Closure $middleware 中间件
489
     * @param mixed $params 参数
490
     * @return $this
491
     */
492 3
    public function middleware(string | array | Closure $middleware, ...$params)
493
    {
494 3
        if (empty($params) && is_array($middleware)) {
495
            $this->option['middleware'] = $middleware;
496
        } else {
497 3
            foreach ((array) $middleware as $item) {
498 3
                $this->option['middleware'][] = [$item, $params];
499
            }
500
        }
501
502 3
        return $this;
503
    }
504
505
    /**
506
     * 设置不使用的中间件 留空则为全部不用
507
     * @access public
508
     * @param array $middleware 中间件
509
     * @return $this
510
     */
511
    public function withoutMiddleware(array $middleware = [])
512
    {
513
        $this->option['without_middleware'] = $middleware;
514
515
        return $this;
516
    }
517
518
    /**
519
     * 允许跨域
520
     * @access public
521
     * @param  array $header 自定义Header
522
     * @return $this
523
     */
524 3
    public function allowCrossDomain(array $header = [])
525
    {
526 3
        return $this->middleware(AllowCrossDomain::class, $header);
527
    }
528
529
    /**
530
     * 表单令牌验证
531
     * @access public
532
     * @param  string $token 表单令牌token名称
533
     * @return $this
534
     */
535
    public function token(string $token = '__token__')
536
    {
537
        return $this->middleware(FormTokenCheck::class, $token);
538
    }
539
540
    /**
541
     * 设置路由缓存
542
     * @access public
543
     * @param  array|string|int $cache 缓存
544
     * @return $this
545
     */
546
    public function cache(array | string | int $cache)
547
    {
548
        return $this->middleware(CheckRequestCache::class, $cache);
549
    }
550
551
    /**
552
     * 检查URL分隔符
553
     * @access public
554
     * @param  string $depr URL分隔符
555
     * @return $this
556
     */
557
    public function depr(string $depr)
558
    {
559
        return $this->setOption('param_depr', $depr);
560
    }
561
562
    /**
563
     * 设置需要合并的路由参数
564
     * @access public
565
     * @param  array $option 路由参数
566
     * @return $this
567
     */
568
    public function mergeOptions(array $option = [])
569
    {
570
        $this->mergeOptions = array_merge($this->mergeOptions, $option);
571
        return $this;
572
    }
573
574
    /**
575
     * 检查是否为HTTPS请求
576
     * @access public
577
     * @param  bool $https 是否为HTTPS
578
     * @return $this
579
     */
580
    public function https(bool $https = true)
581
    {
582
        return $this->setOption('https', $https);
583
    }
584
585
    /**
586
     * 检查是否为JSON请求
587
     * @access public
588
     * @param  bool $json 是否为JSON
589
     * @return $this
590
     */
591
    public function json(bool $json = true)
592
    {
593
        return $this->setOption('json', $json);
594
    }
595
596
    /**
597
     * 检查是否为AJAX请求
598
     * @access public
599
     * @param  bool $ajax 是否为AJAX
600
     * @return $this
601
     */
602
    public function ajax(bool $ajax = true)
603
    {
604
        return $this->setOption('ajax', $ajax);
605
    }
606
607
    /**
608
     * 检查是否为PJAX请求
609
     * @access public
610
     * @param  bool $pjax 是否为PJAX
611
     * @return $this
612
     */
613
    public function pjax(bool $pjax = true)
614
    {
615
        return $this->setOption('pjax', $pjax);
616
    }
617
618
    /**
619
     * 路由到一个模板地址 需要额外传入的模板变量
620
     * @access public
621
     * @param  array $view 视图
622
     * @return $this
623
     */
624
    public function view(array $view = [])
625
    {
626
        return $this->setOption('view', $view);
627
    }
628
629
    /**
630
     * 通过闭包检查路由是否匹配
631
     * @access public
632
     * @param  callable $match 闭包
633
     * @return $this
634
     */
635
    public function match(callable $match)
636
    {
637
        return $this->setOption('match', $match);
638
    }
639
640
    /**
641
     * 设置路由完整匹配
642
     * @access public
643
     * @param  bool $match 是否完整匹配
644
     * @return $this
645
     */
646
    public function completeMatch(bool $match = true)
647
    {
648
        return $this->setOption('complete_match', $match);
649
    }
650
651
    /**
652
     * 是否去除URL最后的斜线
653
     * @access public
654
     * @param  bool $remove 是否去除最后斜线
655
     * @return $this
656
     */
657 27
    public function removeSlash(bool $remove = true)
658
    {
659 27
        return $this->setOption('remove_slash', $remove);
660
    }
661
662
    /**
663
     * 设置路由规则全局有效
664
     * @access public
665
     * @return $this
666
     */
667
    public function crossDomainRule()
668
    {
669
        $this->router->setCrossDomainRule($this);
670
        return $this;
671
    }
672
673
    /**
674
     * 解析匹配到的规则路由
675
     * @access public
676
     * @param  Request $request 请求对象
677
     * @param  string  $rule 路由规则
678
     * @param  mixed   $route 路由地址
679
     * @param  string  $url URL地址
680
     * @param  array   $option 路由参数
681
     * @param  array   $matches 匹配的变量
682
     * @return Dispatch
683
     */
684 24
    public function parseRule(Request $request, string $rule, $route, string $url, array $option = [], array $matches = []): Dispatch
685
    {
686 24
        if (is_string($route) && isset($option['prefix'])) {
687
            // 路由地址前缀
688
            $route = $option['prefix'] . $route;
689
        }
690
691
        // 替换路由地址中的变量
692 24
        $extraParams = true;
693 24
        $search      = $replace      = [];
694 24
        $depr        = $this->config('pathinfo_depr');
695
696 24
        foreach ($matches as $key => $value) {
697
            $search[]  = '<' . $key . '>';
698
            $replace[] = $value;
699
            $search[]  = '{' . $key . '}';
700
            $replace[] = $value;
701
            $search[]  = ':' . $key;
702
            $replace[] = $value;
703
704
            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

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