Passed
Push — 8.0 ( 1ced43...5fce0a )
by liu
02:43
created

Rule::dispatchController()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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

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