Passed
Push — 8.0 ( fd65e0...efbc40 )
by liu
02:49
created

Rule::checkOption()   D

Complexity

Conditions 29
Paths 79

Size

Total Lines 54
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 147.2512

Importance

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

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