Completed
Push — 6.0 ( 40b14a...7136ae )
by liu
02:52
created

Rule::token()   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
// +----------------------------------------------------------------------
1 ignored issue
show
Coding Style introduced by
You must use "/**" style comments for a file comment
Loading history...
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2019 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 think\Container;
16
use think\Request;
17
use think\Response;
18
use think\Route;
19
use think\route\dispatch\Callback as CallbackDispatch;
20
use think\route\dispatch\Controller as ControllerDispatch;
21
use think\route\dispatch\Redirect as RedirectDispatch;
22
use think\route\dispatch\Response as ResponseDispatch;
23
use think\route\dispatch\View as ViewDispatch;
24
25
/**
26
 * 路由规则基础类
27
 */
5 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
28
abstract class Rule
29
{
30
    /**
31
     * 路由标识
32
     * @var string
33
     */
34
    protected $name;
35
36
    /**
37
     * 路由对象
38
     * @var Route
39
     */
40
    protected $router;
41
42
    /**
43
     * 路由所属分组
44
     * @var RuleGroup
45
     */
46
    protected $parent;
47
48
    /**
49
     * 路由规则
50
     * @var mixed
51
     */
52
    protected $rule;
53
54
    /**
55
     * 路由地址
56
     * @var string|\Closure
57
     */
58
    protected $route;
59
60
    /**
61
     * 请求类型
62
     * @var string
63
     */
64
    protected $method;
65
66
    /**
67
     * 路由变量
68
     * @var array
69
     */
70
    protected $vars = [];
71
72
    /**
73
     * 路由参数
74
     * @var array
75
     */
76
    protected $option = [];
77
78
    /**
79
     * 路由变量规则
80
     * @var array
81
     */
82
    protected $pattern = [];
83
84
    /**
85
     * 需要和分组合并的路由参数
86
     * @var array
87
     */
88
    protected $mergeOptions = ['after', 'model', 'append', 'middleware'];
89
90
    abstract public function check(Request $request, string $url, bool $completeMatch = false);
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function check()
Loading history...
91
92
    /**
93
     * 设置路由参数
94
     * @access public
95
     * @param  array $option 参数
96
     * @return $this
97
     */
98
    public function option(array $option)
99
    {
100
        $this->option = array_merge($this->option, $option);
101
102
        return $this;
103
    }
104
105
    /**
106
     * 设置单个路由参数
107
     * @access public
108
     * @param  string $name  参数名
109
     * @param  mixed  $value 值
110
     * @return $this
111
     */
112
    public function setOption(string $name, $value)
113
    {
114
        $this->option[$name] = $value;
115
116
        return $this;
117
    }
118
119
    /**
120
     * 注册变量规则
121
     * @access public
122
     * @param  array $pattern 变量规则
123
     * @return $this
124
     */
125
    public function pattern(array $pattern)
126
    {
127
        $this->pattern = array_merge($this->pattern, $pattern);
128
129
        return $this;
130
    }
131
132
    /**
133
     * 设置标识
134
     * @access public
135
     * @param  string $name 标识名
136
     * @return $this
137
     */
138
    public function name(string $name)
139
    {
140
        $this->name = $name;
141
142
        return $this;
143
    }
144
145
    /**
146
     * 获取路由对象
147
     * @access public
148
     * @return Route
149
     */
150
    public function getRouter(): Route
151
    {
152
        return $this->router;
153
    }
154
155
    /**
156
     * 获取Name
157
     * @access public
158
     * @return string
159
     */
160
    public function getName(): string
161
    {
162
        return $this->name;
163
    }
164
165
    /**
166
     * 获取当前路由规则
167
     * @access public
168
     * @return mixed
169
     */
170
    public function getRule()
171
    {
172
        return $this->rule;
173
    }
174
175
    /**
176
     * 获取当前路由地址
177
     * @access public
178
     * @return mixed
179
     */
180
    public function getRoute()
181
    {
182
        return $this->route;
183
    }
184
185
    /**
186
     * 获取当前路由的变量
187
     * @access public
188
     * @return array
189
     */
190
    public function getVars(): array
191
    {
192
        return $this->vars;
193
    }
194
195
    /**
196
     * 获取Parent对象
197
     * @access public
198
     * @return $this|null
199
     */
200
    public function getParent()
201
    {
202
        return $this->parent;
203
    }
204
205
    /**
206
     * 获取路由所在域名
207
     * @access public
208
     * @return string
209
     */
210
    public function getDomain(): string
211
    {
212
        return $this->parent->getDomain();
213
    }
214
215
    /**
216
     * 获取路由参数
217
     * @access public
218
     * @param  string $name 变量名
219
     * @return mixed
220
     */
221
    public function config(string $name = '')
222
    {
223
        return $this->router->config($name);
224
    }
225
226
    /**
227
     * 获取变量规则定义
228
     * @access public
229
     * @param  string $name 变量名
230
     * @return mixed
231
     */
232
    public function getPattern(string $name = '')
233
    {
234
        if ('' === $name) {
235
            return $this->pattern;
236
        }
237
238
        return $this->pattern[$name] ?? null;
239
    }
240
241
    /**
242
     * 获取路由参数定义
243
     * @access public
244
     * @param  string $name 参数名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
245
     * @param  mixed  $default 默认值
246
     * @return mixed
247
     */
248
    public function getOption(string $name = '', $default = null)
249
    {
250
        if ('' === $name) {
251
            return $this->option;
252
        }
253
254
        return $this->option[$name] ?? $default;
255
    }
256
257
    /**
258
     * 获取当前路由的请求类型
259
     * @access public
260
     * @return string
261
     */
262
    public function getMethod(): string
263
    {
264
        return strtolower($this->method);
265
    }
266
267
    /**
268
     * 设置路由请求类型
269
     * @access public
270
     * @param  string $method 请求类型
271
     * @return $this
272
     */
273
    public function method(string $method)
274
    {
275
        return $this->setOption('method', strtolower($method));
276
    }
277
278
    /**
279
     * 检查后缀
280
     * @access public
281
     * @param  string $ext URL后缀
282
     * @return $this
283
     */
284
    public function ext(string $ext = '')
285
    {
286
        return $this->setOption('ext', $ext);
287
    }
288
289
    /**
290
     * 检查禁止后缀
291
     * @access public
292
     * @param  string $ext URL后缀
293
     * @return $this
294
     */
295
    public function denyExt(string $ext = '')
296
    {
297
        return $this->setOption('deny_ext', $ext);
298
    }
299
300
    /**
301
     * 检查域名
302
     * @access public
303
     * @param  string $domain 域名
304
     * @return $this
305
     */
306
    public function domain(string $domain)
307
    {
308
        return $this->setOption('domain', $domain);
309
    }
310
311
    /**
312
     * 设置参数过滤检查
313
     * @access public
314
     * @param  array $filter 参数过滤
315
     * @return $this
316
     */
317
    public function filter(array $filter)
318
    {
319
        $this->option['filter'] = $filter;
320
321
        return $this;
322
    }
323
324
    /**
325
     * 绑定模型
326
     * @access public
327
     * @param  array|string|\Closure $var  路由变量名 多个使用 & 分割
0 ignored issues
show
Coding Style introduced by
Expected 7 spaces after parameter name; 2 found
Loading history...
328
     * @param  string|\Closure       $model 绑定模型类
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
329
     * @param  bool                  $exception 是否抛出异常
330
     * @return $this
331
     */
332
    public function model($var, $model = null, bool $exception = true)
333
    {
334
        if ($var instanceof \Closure) {
335
            $this->option['model'][] = $var;
336
        } elseif (is_array($var)) {
337
            $this->option['model'] = $var;
338
        } elseif (is_null($model)) {
339
            $this->option['model']['id'] = [$var, true];
340
        } else {
341
            $this->option['model'][$var] = [$model, $exception];
342
        }
343
344
        return $this;
345
    }
346
347
    /**
348
     * 附加路由隐式参数
349
     * @access public
350
     * @param  array $append 追加参数
351
     * @return $this
352
     */
353
    public function append(array $append = [])
354
    {
355
        $this->option['append'] = $append;
356
357
        return $this;
358
    }
359
360
    /**
361
     * 绑定验证
362
     * @access public
363
     * @param  mixed  $validate 验证器类
364
     * @param  string $scene 验证场景
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
365
     * @param  array  $message 验证提示
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
366
     * @param  bool   $batch 批量验证
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
367
     * @return $this
368
     */
369
    public function validate($validate, string $scene = null, array $message = [], bool $batch = false)
370
    {
371
        $this->option['validate'] = [$validate, $scene, $message, $batch];
372
373
        return $this;
374
    }
375
376
    /**
377
     * 指定路由中间件
378
     * @access public
379
     * @param  string|array|\Closure $middleware 中间件
380
     * @param  mixed                 $param 参数
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 1 found
Loading history...
381
     * @return $this
382
     */
383
    public function middleware($middleware, $param = null)
384
    {
385
        if (is_null($param) && is_array($middleware)) {
386
            $this->option['middleware'] = $middleware;
387
        } else {
388
            foreach ((array) $middleware as $item) {
389
                $this->option['middleware'][] = [$item, $param];
390
            }
391
        }
392
393
        return $this;
394
    }
395
396
    /**
397
     * 允许跨域
398
     * @access public
399
     * @param  array $header 自定义Header
400
     * @return $this
401
     */
402
    public function allowCrossDomain(array $header = [])
403
    {
404
        return $this->middleware('\think\middleware\AllowCrossDomain', $header);
405
    }
406
407
    /**
408
     * 表单令牌验证
409
     * @access public
410
     * @param  string $token 表单令牌token名称
411
     * @return $this
412
     */
413
    public function token(string $token = '__token__')
414
    {
415
        return $this->middleware('\think\middleware\FormTokenCheck', $token);
416
    }
417
418
    /**
419
     * 设置路由缓存
420
     * @access public
421
     * @param  array|string $cache 缓存
422
     * @return $this
423
     */
424
    public function cache($cache)
425
    {
426
        return $this->setOption('cache', $cache);
427
    }
428
429
    /**
430
     * 检查URL分隔符
431
     * @access public
432
     * @param  string $depr URL分隔符
433
     * @return $this
434
     */
435
    public function depr(string $depr)
436
    {
437
        return $this->setOption('param_depr', $depr);
438
    }
439
440
    /**
441
     * 设置需要合并的路由参数
442
     * @access public
443
     * @param  array $option 路由参数
444
     * @return $this
445
     */
446
    public function mergeOptions(array $option = [])
447
    {
448
        $this->mergeOptions = array_merge($this->mergeOptions, $option);
449
        return $this;
450
    }
451
452
    /**
453
     * 检查是否为HTTPS请求
454
     * @access public
455
     * @param  bool $https 是否为HTTPS
456
     * @return $this
457
     */
458
    public function https(bool $https = true)
459
    {
460
        return $this->setOption('https', $https);
461
    }
462
463
    /**
464
     * 检查是否为JSON请求
465
     * @access public
466
     * @param  bool $json 是否为JSON
467
     * @return $this
468
     */
469
    public function json(bool $json = true)
470
    {
471
        return $this->setOption('json', $json);
472
    }
473
474
    /**
475
     * 检查是否为AJAX请求
476
     * @access public
477
     * @param  bool $ajax 是否为AJAX
478
     * @return $this
479
     */
480
    public function ajax(bool $ajax = true)
481
    {
482
        return $this->setOption('ajax', $ajax);
483
    }
484
485
    /**
486
     * 检查是否为PJAX请求
487
     * @access public
488
     * @param  bool $pjax 是否为PJAX
489
     * @return $this
490
     */
491
    public function pjax(bool $pjax = true)
492
    {
493
        return $this->setOption('pjax', $pjax);
494
    }
495
496
    /**
497
     * 当前路由到一个模板地址 当使用数组的时候可以传入模板变量
498
     * @access public
499
     * @param  bool|array $view 视图
500
     * @return $this
501
     */
502
    public function view($view = true)
503
    {
504
        return $this->setOption('view', $view);
505
    }
506
507
    /**
508
     * 当前路由为重定向
509
     * @access public
510
     * @param  bool $redirect 是否为重定向
511
     * @return $this
512
     */
513
    public function redirect(bool $redirect = true)
514
    {
515
        return $this->setOption('redirect', $redirect);
516
    }
517
518
    /**
519
     * 设置status
520
     * @access public
521
     * @param  int $status 状态码
522
     * @return $this
523
     */
524
    public function status(int $status)
525
    {
526
        return $this->setOption('status', $status);
527
    }
528
529
    /**
530
     * 设置路由完整匹配
531
     * @access public
532
     * @param  bool $match 是否完整匹配
533
     * @return $this
534
     */
535
    public function completeMatch(bool $match = true)
536
    {
537
        return $this->setOption('complete_match', $match);
538
    }
539
540
    /**
541
     * 是否去除URL最后的斜线
542
     * @access public
543
     * @param  bool $remove 是否去除最后斜线
544
     * @return $this
545
     */
546
    public function removeSlash(bool $remove = true)
547
    {
548
        return $this->setOption('remove_slash', $remove);
549
    }
550
551
    /**
552
     * 设置路由规则全局有效
553
     * @access public
554
     * @return $this
555
     */
556
    public function crossDomainRule()
557
    {
558
        if ($this instanceof RuleGroup) {
559
            $method = '*';
560
        } else {
561
            $method = $this->method;
562
        }
563
564
        $this->router->setCrossDomainRule($this, $method);
565
566
        return $this;
567
    }
568
569
    /**
570
     * 合并分组参数
571
     * @access public
572
     * @return array
573
     */
574
    public function mergeGroupOptions(): array
575
    {
576
        $parentOption = $this->parent->getOption();
577
        // 合并分组参数
578
        foreach ($this->mergeOptions as $item) {
579
            if (isset($parentOption[$item]) && isset($this->option[$item])) {
580
                $this->option[$item] = array_merge($parentOption[$item], $this->option[$item]);
581
            }
582
        }
583
584
        $this->option = array_merge($parentOption, $this->option);
585
586
        return $this->option;
587
    }
588
589
    /**
590
     * 解析匹配到的规则路由
591
     * @access public
592
     * @param  Request $request 请求对象
593
     * @param  string  $rule 路由规则
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
594
     * @param  mixed   $route 路由地址
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
595
     * @param  string  $url URL地址
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
596
     * @param  array   $option 路由参数
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
597
     * @param  array   $matches 匹配的变量
598
     * @return Dispatch
599
     */
600
    public function parseRule(Request $request, string $rule, $route, string $url, array $option = [], array $matches = []): Dispatch
601
    {
602
        if (is_string($route) && isset($option['prefix'])) {
603
            // 路由地址前缀
604
            $route = $option['prefix'] . $route;
605
        }
606
607
        // 替换路由地址中的变量
608
        if (is_string($route) && !empty($matches)) {
609
            $search = $replace = [];
610
611
            foreach ($matches as $key => $value) {
612
                $search[]  = '<' . $key . '>';
613
                $replace[] = $value;
614
615
                $search[]  = ':' . $key;
616
                $replace[] = $value;
617
            }
618
619
            $route = str_replace($search, $replace, $route);
620
        }
621
622
        // 解析额外参数
623
        $count = substr_count($rule, '/');
624
        $url   = array_slice(explode('|', $url), $count + 1);
625
        $this->parseUrlParams(implode('|', $url), $matches);
626
627
        $request->setRoute($matches);
628
629
        // 发起路由调度
630
        return $this->dispatch($request, $route, $option);
631
    }
632
633
    /**
634
     * 发起路由调度
635
     * @access protected
636
     * @param  Request $request Request对象
637
     * @param  mixed   $route  路由地址
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 2 found
Loading history...
638
     * @param  array   $option 路由参数
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
639
     * @return Dispatch
640
     */
641
    protected function dispatch(Request $request, $route, array $option): Dispatch
642
    {
643
        if ($route instanceof Dispatch) {
644
            $result = $route;
645
        } elseif ($route instanceof \Closure) {
646
            // 执行闭包
647
            $result = new CallbackDispatch($request, $this, $route);
648
        } elseif ($route instanceof Response) {
649
            $result = new ResponseDispatch($request, $this, $route);
650
        } elseif (isset($option['view']) && false !== $option['view']) {
651
            $result = new ViewDispatch($request, $this, $route, is_array($option['view']) ? $option['view'] : []);
652
        } elseif (!empty($option['redirect']) || 0 === strpos($route, '/') || strpos($route, '://')) {
653
            // 路由到重定向地址
654
            $result = new RedirectDispatch($request, $this, $route, [], $option['status'] ?? 301);
655
        } elseif (false !== strpos($route, '\\')) {
656
            // 路由到类的方法
657
            $result = $this->dispatchMethod($request, $route);
658
        } else {
659
            // 路由到控制器/操作
660
            $result = $this->dispatchController($request, $route);
661
        }
662
663
        return $result;
664
    }
665
666
    /**
667
     * 解析URL地址为 模块/控制器/操作
668
     * @access protected
669
     * @param  Request $request Request对象
670
     * @param  string  $route 路由地址
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
671
     * @return CallbackDispatch
672
     */
673
    protected function dispatchMethod(Request $request, string $route): CallbackDispatch
674
    {
675
        list($path, $var) = $this->parseUrlPath($route);
676
677
        $route  = str_replace('/', '@', implode('/', $path));
678
        $method = strpos($route, '@') ? explode('@', $route) : $route;
679
680
        return new CallbackDispatch($request, $this, $method, $var);
681
    }
682
683
    /**
684
     * 解析URL地址为 模块/控制器/操作
685
     * @access protected
686
     * @param  Request $request Request对象
687
     * @param  string  $route 路由地址
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
688
     * @return ControllerDispatch
689
     */
690
    protected function dispatchController(Request $request, string $route): ControllerDispatch
691
    {
692
        list($path, $var) = $this->parseUrlPath($route);
693
694
        $action     = array_pop($path);
695
        $controller = !empty($path) ? array_pop($path) : null;
696
697
        // 路由到模块/控制器/操作
698
        return new ControllerDispatch($request, $this, [$controller, $action], $var);
699
    }
700
701
    /**
702
     * 路由检查
703
     * @access protected
704
     * @param  array   $option 路由参数
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
705
     * @param  Request $request Request对象
706
     * @return bool
707
     */
708
    protected function checkOption(array $option, Request $request): bool
709
    {
710
        // 请求类型检测
711
        if (!empty($option['method'])) {
712
            if (is_string($option['method']) && false === stripos($option['method'], $request->method())) {
713
                return false;
714
            }
715
        }
716
717
        // AJAX PJAX 请求检查
718
        foreach (['ajax', 'pjax', 'json'] as $item) {
719
            if (isset($option[$item])) {
720
                $call = 'is' . $item;
721
                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...
722
                    return false;
723
                }
724
            }
725
        }
726
727
        // 伪静态后缀检测
728
        if ($request->url() != '/' && ((isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|'))
729
            || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')))) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
730
            return false;
731
        }
732
733
        // 域名检查
734
        if ((isset($option['domain']) && !in_array($option['domain'], [$request->host(true), $request->subDomain()]))) {
735
            return false;
736
        }
737
738
        // HTTPS检查
739
        if ((isset($option['https']) && $option['https'] && !$request->isSsl())
740
            || (isset($option['https']) && !$option['https'] && $request->isSsl())) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
741
            return false;
742
        }
743
744
        // 请求参数检查
745
        if (isset($option['filter'])) {
746
            foreach ($option['filter'] as $name => $value) {
747
                if ($request->param($name, '', null) != $value) {
748
                    return false;
749
                }
750
            }
751
        }
752
753
        return true;
754
    }
755
756
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $url should have a doc-comment as per coding-style.
Loading history...
757
     * 解析URL地址中的参数Request对象
758
     * @access protected
759
     * @param  string $rule 路由规则
0 ignored issues
show
Coding Style introduced by
Doc comment for parameter $rule does not match actual variable name $url
Loading history...
760
     * @param  array  $var 变量
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
761
     * @return void
762
     */
763
    protected function parseUrlParams(string $url, array &$var = []): void
764
    {
765
        if ($url) {
766
            preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
767
                $var[$match[1]] = strip_tags($match[2]);
768
            }, $url);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
769
        }
770
    }
771
772
    /**
773
     * 解析URL的pathinfo参数和变量
774
     * @access public
775
     * @param  string $url URL地址
776
     * @return array
777
     */
778
    public function parseUrlPath(string $url): array
779
    {
780
        // 分隔符替换 确保路由定义使用统一的分隔符
781
        $url = str_replace('|', '/', $url);
782
        $url = trim($url, '/');
783
        $var = [];
784
785
        if (false !== strpos($url, '?')) {
786
            // [控制器/操作?]参数1=值1&参数2=值2...
787
            $info = parse_url($url);
788
            $path = explode('/', $info['path']);
789
            parse_str($info['query'], $var);
790
        } elseif (strpos($url, '/')) {
791
            // [控制器/操作]
792
            $path = explode('/', $url);
793
        } elseif (false !== strpos($url, '=')) {
794
            // 参数1=值1&参数2=值2...
795
            parse_str($url, $var);
796
            $path = [];
797
        } else {
798
            $path = [$url];
799
        }
800
801
        return [$path, $var];
802
    }
803
804
    /**
805
     * 生成路由的正则规则
806
     * @access protected
807
     * @param  string $rule 路由规则
0 ignored issues
show
Coding Style introduced by
Expected 10 spaces after parameter name; 1 found
Loading history...
808
     * @param  array  $match 匹配的变量
0 ignored issues
show
Coding Style introduced by
Expected 9 spaces after parameter name; 1 found
Loading history...
809
     * @param  array  $pattern   路由变量规则
0 ignored issues
show
Coding Style introduced by
Expected 7 spaces after parameter name; 3 found
Loading history...
810
     * @param  array  $option    路由参数
0 ignored issues
show
Coding Style introduced by
Expected 8 spaces after parameter name; 4 found
Loading history...
811
     * @param  bool   $completeMatch   路由是否完全匹配
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
812
     * @param  string $suffix   路由正则变量后缀
0 ignored issues
show
Coding Style introduced by
Expected 8 spaces after parameter name; 3 found
Loading history...
813
     * @return string
814
     */
815
    protected function buildRuleRegex(string $rule, array $match, array $pattern = [], array $option = [], bool $completeMatch = false, string $suffix = ''): string
816
    {
817
        foreach ($match as $name) {
818
            $replace[] = $this->buildNameRegex($name, $pattern, $suffix);
819
        }
820
821
        // 是否区分 / 地址访问
822
        if ('/' != $rule) {
823
            if (!empty($option['remove_slash'])) {
824
                $rule = rtrim($rule, '/');
825
            } elseif (substr($rule, -1) == '/') {
826
                $rule     = rtrim($rule, '/');
827
                $hasSlash = true;
828
            }
829
        }
830
831
        $regex = str_replace(array_unique($match), array_unique($replace), $rule);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $replace seems to be defined by a foreach iteration on line 817. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
832
        $regex = str_replace([')?/', ')/', ')?-', ')-', '\\\\/'], [')\/', ')\/', ')\-', ')\-', '\/'], $regex);
833
834
        if (isset($hasSlash)) {
835
            $regex .= '\/';
836
        }
837
838
        return $regex . ($completeMatch ? '$' : '');
839
    }
840
841
    /**
842
     * 生成路由变量的正则规则
843
     * @access protected
844
     * @param  string $name    路由变量
845
     * @param  array  $pattern 变量规则
846
     * @param  string $suffix  路由正则变量后缀
847
     * @return string
848
     */
849
    protected function buildNameRegex(string $name, array $pattern, string $suffix): string
850
    {
851
        $optional = '';
852
        $slash    = substr($name, 0, 1);
853
854
        if (in_array($slash, ['/', '-'])) {
855
            $prefix = '\\' . $slash;
856
            $name   = substr($name, 1);
857
            $slash  = substr($name, 0, 1);
858
        } else {
859
            $prefix = '';
860
        }
861
862
        if ('<' != $slash) {
863
            return $prefix . preg_quote($name, '/');
864
        }
865
866
        if (strpos($name, '?')) {
867
            $name     = substr($name, 1, -2);
868
            $optional = '?';
869
        } elseif (strpos($name, '>')) {
870
            $name = substr($name, 1, -1);
871
        }
872
873
        if (isset($pattern[$name])) {
874
            $nameRule = $pattern[$name];
875
            if (0 === strpos($nameRule, '/') && '/' == substr($nameRule, -1)) {
876
                $nameRule = substr($nameRule, 1, -1);
877
            }
878
        } else {
879
            $nameRule = $this->router->config('default_route_pattern');
880
        }
881
882
        return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional;
883
    }
884
885
    /**
886
     * 分析路由规则中的变量
887
     * @access protected
888
     * @param  string $rule 路由规则
889
     * @return array
890
     */
891
    protected function parseVar(string $rule): array
892
    {
893
        // 提取路由规则中的变量
894
        $var = [];
895
896
        if (preg_match_all('/<\w+\??>/', $rule, $matches)) {
897
            foreach ($matches[0] as $name) {
898
                $optional = false;
899
900
                if (strpos($name, '?')) {
901
                    $name     = substr($name, 1, -2);
902
                    $optional = true;
903
                } else {
904
                    $name = substr($name, 1, -1);
905
                }
906
907
                $var[$name] = $optional ? 2 : 1;
908
            }
909
        }
910
911
        return $var;
912
    }
913
914
    /**
915
     * 设置路由参数
916
     * @access public
917
     * @param  string $method 方法名
918
     * @param  array  $args   调用参数
919
     * @return $this
920
     */
921
    public function __call($method, $args)
922
    {
923
        if (count($args) > 1) {
924
            $args[0] = $args;
925
        }
926
        array_unshift($args, $method);
927
928
        return call_user_func_array([$this, 'setOption'], $args);
929
    }
930
931
    public function __sleep()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __sleep()
Loading history...
932
    {
933
        return ['name', 'rule', 'route', 'method', 'vars', 'option', 'pattern'];
934
    }
935
936
    public function __wakeup()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __wakeup()
Loading history...
937
    {
938
        $this->router = Container::pull('route');
939
    }
940
941
    public function __debugInfo()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __debugInfo()
Loading history...
942
    {
943
        return [
944
            'name'    => $this->name,
945
            'rule'    => $this->rule,
946
            'route'   => $this->route,
947
            'method'  => $this->method,
948
            'vars'    => $this->vars,
949
            'option'  => $this->option,
950
            'pattern' => $this->pattern,
951
        ];
952
    }
953
}
954