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

Rule::checkOption()   F

Complexity

Conditions 33
Paths 127

Size

Total Lines 63
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 215.8676

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 33
eloc 28
c 3
b 0
f 0
nc 127
nop 2
dl 0
loc 63
ccs 13
cts 29
cp 0.4483
crap 215.8676
rs 3.9416

How to fix   Long Method    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\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