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

Rule::checkOption()   D

Complexity

Conditions 32
Paths 91

Size

Total Lines 58
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 223.0383

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 32
eloc 27
c 3
b 0
f 0
nc 91
nop 2
dl 0
loc 58
ccs 12
cts 28
cp 0.4286
crap 223.0383
rs 4.1666

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
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