Passed
Push — 8.0 ( 229d2e...c06b7d )
by liu
02:44
created

Rule::ajax()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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