Passed
Pull Request — 5.1 (#1748)
by guanguans
09:27
created

Rule   F

Complexity

Total Complexity 162

Size/Duplication

Total Lines 1096
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 280
dl 0
loc 1096
rs 2
c 0
b 0
f 0
wmc 162

61 Methods

Rating   Name   Duplication   Size   Complexity  
A mobile() 0 3 1
A view() 0 3 1
A dispatchMethod() 0 8 2
A model() 0 13 4
A method() 0 3 1
A removeSlash() 0 3 1
A option() 0 9 2
B buildRuleRegex() 0 24 7
A allowCrossDomain() 0 11 4
A getParent() 0 3 1
A parseRule() 0 33 6
A depr() 0 3 1
A getRouter() 0 3 1
A response() 0 4 1
A parseUrlPath() 0 24 4
A mergeExtraVars() 0 3 1
A redirect() 0 3 1
A append() 0 9 2
C dispatch() 0 24 12
A before() 0 3 1
A crossDomainRule() 0 11 2
A parseUrlParams() 0 9 3
A completeMatch() 0 3 1
A mergeGroupOptions() 0 16 5
A dispatchController() 0 11 3
A mergeOptions() 0 4 1
A getConfig() 0 3 1
A getMethod() 0 3 1
A __debugInfo() 0 6 1
A cache() 0 3 1
A after() 0 3 1
A doAfter() 0 3 1
A __wakeup() 0 3 1
A getVars() 0 3 1
A header() 0 9 2
A ajax() 0 3 1
A middleware() 0 11 4
A __sleep() 0 3 1
A getRule() 0 3 1
A __call() 0 8 2
A getDomain() 0 3 1
A checkBefore() 0 9 3
A pattern() 0 9 2
B dispatchModule() 0 20 7
A pjax() 0 3 1
D checkOption() 0 45 26
A filter() 0 9 2
A getRoute() 0 3 1
A checkCrossDomain() 0 18 4
A getPattern() 0 7 3
A getOption() 0 7 3
A parseVar() 0 21 5
A name() 0 5 1
A vars() 0 5 1
A ext() 0 3 1
A denyExt() 0 3 1
A getName() 0 3 1
B buildNameRegex() 0 34 8
A domain() 0 3 1
A https() 0 3 1
A validate() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like Rule often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Rule, and based on these observations, apply Extract Interface, too.

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~2018 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
12
namespace think\route;
13
14
use think\Container;
15
use think\Request;
16
use think\Response;
17
use think\route\dispatch\Callback as CallbackDispatch;
18
use think\route\dispatch\Controller as ControllerDispatch;
19
use think\route\dispatch\Module as ModuleDispatch;
20
use think\route\dispatch\Redirect as RedirectDispatch;
21
use think\route\dispatch\Response as ResponseDispatch;
22
use think\route\dispatch\View as ViewDispatch;
23
24
abstract class Rule
1 ignored issue
show
Coding Style introduced by
Missing class doc comment
Loading history...
25
{
26
    /**
27
     * 路由标识
28
     * @var string
29
     */
30
    protected $name;
31
32
    /**
33
     * 路由对象
34
     * @var Route
0 ignored issues
show
Bug introduced by
The type think\route\Route was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
35
     */
36
    protected $router;
37
38
    /**
39
     * 路由所属分组
40
     * @var RuleGroup
41
     */
42
    protected $parent;
43
44
    /**
45
     * 路由规则
46
     * @var mixed
47
     */
48
    protected $rule;
49
50
    /**
51
     * 路由地址
52
     * @var string|\Closure
53
     */
54
    protected $route;
55
56
    /**
57
     * 请求类型
58
     * @var string
59
     */
60
    protected $method;
61
62
    /**
63
     * 路由变量
64
     * @var array
65
     */
66
    protected $vars = [];
67
68
    /**
69
     * 路由参数
70
     * @var array
71
     */
72
    protected $option = [];
73
74
    /**
75
     * 路由变量规则
76
     * @var array
77
     */
78
    protected $pattern = [];
79
80
    /**
81
     * 需要和分组合并的路由参数
82
     * @var array
83
     */
84
    protected $mergeOptions = ['after', 'model', 'header', 'response', 'append', 'middleware'];
85
86
    /**
87
     * 是否需要后置操作
88
     * @var bool
89
     */
90
    protected $doAfter;
91
92
    /**
93
     * 是否锁定参数
94
     * @var bool
95
     */
96
    protected $lockOption = false;
97
98
    abstract public function check($request, $url, $completeMatch = false);
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
99
100
    /**
101
     * 获取Name
102
     * @access public
103
     * @return string
104
     */
105
    public function getName()
106
    {
107
        return $this->name;
108
    }
109
110
    /**
111
     * 获取当前路由规则
112
     * @access public
113
     * @return string
114
     */
115
    public function getRule()
116
    {
117
        return $this->rule;
118
    }
119
120
    /**
121
     * 获取当前路由地址
122
     * @access public
123
     * @return mixed
124
     */
125
    public function getRoute()
126
    {
127
        return $this->route;
128
    }
129
130
    /**
131
     * 获取当前路由的请求类型
132
     * @access public
133
     * @return string
134
     */
135
    public function getMethod()
136
    {
137
        return strtolower($this->method);
138
    }
139
140
    /**
141
     * 获取当前路由的变量
142
     * @access public
143
     * @return array
144
     */
145
    public function getVars()
146
    {
147
        return $this->vars;
148
    }
149
150
    /**
151
     * 获取路由对象
152
     * @access public
153
     * @return Route
154
     */
155
    public function getRouter()
156
    {
157
        return $this->router;
158
    }
159
160
    /**
161
     * 路由是否有后置操作
162
     * @access public
163
     * @return bool
164
     */
165
    public function doAfter()
166
    {
167
        return $this->doAfter;
168
    }
169
170
    /**
171
     * 获取路由分组
172
     * @access public
173
     * @return RuleGroup|null
174
     */
175
    public function getParent()
176
    {
177
        return $this->parent;
178
    }
179
180
    /**
181
     * 获取路由所在域名
182
     * @access public
183
     * @return string
184
     */
185
    public function getDomain()
186
    {
187
        return $this->parent->getDomain();
188
    }
189
190
    /**
191
     * 获取变量规则定义
192
     * @access public
193
     * @param  string  $name 变量名
194
     * @return mixed
195
     */
196
    public function getPattern($name = '')
197
    {
198
        if ('' === $name) {
199
            return $this->pattern;
200
        }
201
202
        return isset($this->pattern[$name]) ? $this->pattern[$name] : null;
203
    }
204
205
    /**
206
     * 获取路由参数
207
     * @access public
208
     * @param  string  $name 变量名
209
     * @return mixed
210
     */
211
    public function getConfig($name = '')
212
    {
213
        return $this->router->config($name);
214
    }
215
216
    /**
217
     * 获取路由参数定义
218
     * @access public
219
     * @param  string  $name 参数名
220
     * @return mixed
221
     */
222
    public function getOption($name = '')
223
    {
224
        if ('' === $name) {
225
            return $this->option;
226
        }
227
228
        return isset($this->option[$name]) ? $this->option[$name] : null;
229
    }
230
231
    /**
232
     * 注册路由参数
233
     * @access public
234
     * @param  string|array  $name  参数名
235
     * @param  mixed         $value 值
236
     * @return $this
237
     */
238
    public function option($name, $value = '')
239
    {
240
        if (is_array($name)) {
241
            $this->option = array_merge($this->option, $name);
242
        } else {
243
            $this->option[$name] = $value;
244
        }
245
246
        return $this;
247
    }
248
249
    /**
250
     * 注册变量规则
251
     * @access public
252
     * @param  string|array  $name 变量名
253
     * @param  string        $rule 变量规则
254
     * @return $this
255
     */
256
    public function pattern($name, $rule = '')
257
    {
258
        if (is_array($name)) {
259
            $this->pattern = array_merge($this->pattern, $name);
260
        } else {
261
            $this->pattern[$name] = $rule;
262
        }
263
264
        return $this;
265
    }
266
267
    /**
268
     * 设置标识
269
     * @access public
270
     * @param  string  $name 标识名
271
     * @return $this
272
     */
273
    public function name($name)
274
    {
275
        $this->name = $name;
276
277
        return $this;
278
    }
279
280
    /**
281
     * 设置变量
282
     * @access public
283
     * @param  array  $vars 变量
284
     * @return $this
285
     */
286
    public function vars($vars)
287
    {
288
        $this->vars = $vars;
289
290
        return $this;
291
    }
292
293
    /**
294
     * 设置路由请求类型
295
     * @access public
296
     * @param  string     $method
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
297
     * @return $this
298
     */
299
    public function method($method)
300
    {
301
        return $this->option('method', strtolower($method));
302
    }
303
304
    /**
305
     * 设置路由前置行为
306
     * @access public
307
     * @param  array|\Closure    $before
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
308
     * @return $this
309
     */
310
    public function before($before)
311
    {
312
        return $this->option('before', $before);
313
    }
314
315
    /**
316
     * 设置路由后置行为
317
     * @access public
318
     * @param  array|\Closure     $after
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
319
     * @return $this
320
     */
321
    public function after($after)
322
    {
323
        return $this->option('after', $after);
324
    }
325
326
    /**
327
     * 检查后缀
328
     * @access public
329
     * @param  string     $ext
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
330
     * @return $this
331
     */
332
    public function ext($ext = '')
333
    {
334
        return $this->option('ext', $ext);
335
    }
336
337
    /**
338
     * 检查禁止后缀
339
     * @access public
340
     * @param  string     $ext
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
341
     * @return $this
342
     */
343
    public function denyExt($ext = '')
344
    {
345
        return $this->option('deny_ext', $ext);
346
    }
347
348
    /**
349
     * 检查域名
350
     * @access public
351
     * @param  string     $domain
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
352
     * @return $this
353
     */
354
    public function domain($domain)
355
    {
356
        return $this->option('domain', $domain);
357
    }
358
359
    /**
360
     * 设置参数过滤检查
361
     * @access public
362
     * @param  string|array     $name
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
363
     * @param  mixed            $value
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
364
     * @return $this
365
     */
366
    public function filter($name, $value = null)
367
    {
368
        if (is_array($name)) {
369
            $this->option['filter'] = $name;
370
        } else {
371
            $this->option['filter'][$name] = $value;
372
        }
373
374
        return $this;
375
    }
376
377
    /**
378
     * 绑定模型
379
     * @access public
380
     * @param  array|string      $var  路由变量名 多个使用 & 分割
0 ignored issues
show
Coding Style introduced by
Expected 7 spaces after parameter name; 2 found
Loading history...
381
     * @param  string|\Closure   $model 绑定模型类
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
382
     * @param  bool              $exception 是否抛出异常
383
     * @return $this
384
     */
385
    public function model($var, $model = null, $exception = true)
386
    {
387
        if ($var instanceof \Closure) {
0 ignored issues
show
introduced by
$var is never a sub-type of Closure.
Loading history...
388
            $this->option['model'][] = $var;
389
        } elseif (is_array($var)) {
390
            $this->option['model'] = $var;
391
        } elseif (is_null($model)) {
392
            $this->option['model']['id'] = [$var, true];
393
        } else {
394
            $this->option['model'][$var] = [$model, $exception];
395
        }
396
397
        return $this;
398
    }
399
400
    /**
401
     * 附加路由隐式参数
402
     * @access public
403
     * @param  array     $append
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
404
     * @return $this
405
     */
406
    public function append(array $append = [])
407
    {
408
        if (isset($this->option['append'])) {
409
            $this->option['append'] = array_merge($this->option['append'], $append);
410
        } else {
411
            $this->option['append'] = $append;
412
        }
413
414
        return $this;
415
    }
416
417
    /**
418
     * 绑定验证
419
     * @access public
420
     * @param  mixed    $validate 验证器类
421
     * @param  string   $scene 验证场景
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
422
     * @param  array    $message 验证提示
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
423
     * @param  bool     $batch 批量验证
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
424
     * @return $this
425
     */
426
    public function validate($validate, $scene = null, $message = [], $batch = false)
427
    {
428
        $this->option['validate'] = [$validate, $scene, $message, $batch];
429
430
        return $this;
431
    }
432
433
    /**
434
     * 绑定Response对象
435
     * @access public
436
     * @param  mixed     $response
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
437
     * @return $this
438
     */
439
    public function response($response)
440
    {
441
        $this->option['response'][] = $response;
442
        return $this;
443
    }
444
445
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $header should have a doc-comment as per coding-style.
Loading history...
446
     * 设置Response Header信息
447
     * @access public
448
     * @param  string|array $name  参数名
0 ignored issues
show
Coding Style introduced by
Doc comment for parameter $name does not match actual variable name $header
Loading history...
449
     * @param  string       $value 参数值
450
     * @return $this
451
     */
452
    public function header($header, $value = null)
453
    {
454
        if (is_array($header)) {
455
            $this->option['header'] = $header;
456
        } else {
457
            $this->option['header'][$header] = $value;
458
        }
459
460
        return $this;
461
    }
462
463
    /**
464
     * 指定路由中间件
465
     * @access public
466
     * @param  string|array|\Closure    $middleware
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
467
     * @param  mixed                    $param
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
468
     * @return $this
469
     */
470
    public function middleware($middleware, $param = null)
471
    {
472
        if (is_null($param) && is_array($middleware)) {
473
            $this->option['middleware'] = $middleware;
474
        } else {
475
            foreach ((array) $middleware as $item) {
476
                $this->option['middleware'][] = [$item, $param];
477
            }
478
        }
479
480
        return $this;
481
    }
482
483
    /**
484
     * 设置路由缓存
485
     * @access public
486
     * @param  array|string     $cache
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
487
     * @return $this
488
     */
489
    public function cache($cache)
490
    {
491
        return $this->option('cache', $cache);
492
    }
493
494
    /**
495
     * 检查URL分隔符
496
     * @access public
497
     * @param  bool     $depr
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
498
     * @return $this
499
     */
500
    public function depr($depr)
501
    {
502
        return $this->option('param_depr', $depr);
503
    }
504
505
    /**
506
     * 是否合并额外参数
507
     * @access public
508
     * @param  bool     $merge
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
509
     * @return $this
510
     */
511
    public function mergeExtraVars($merge = true)
512
    {
513
        return $this->option('merge_extra_vars', $merge);
514
    }
515
516
    /**
517
     * 设置需要合并的路由参数
518
     * @access public
519
     * @param  array     $option
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
520
     * @return $this
521
     */
522
    public function mergeOptions($option = [])
523
    {
524
        $this->mergeOptions = array_merge($this->mergeOptions, $option);
525
        return $this;
526
    }
527
528
    /**
529
     * 检查是否为HTTPS请求
530
     * @access public
531
     * @param  bool     $https
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
532
     * @return $this
533
     */
534
    public function https($https = true)
535
    {
536
        return $this->option('https', $https);
537
    }
538
539
    /**
540
     * 检查是否为AJAX请求
541
     * @access public
542
     * @param  bool     $ajax
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
543
     * @return $this
544
     */
545
    public function ajax($ajax = true)
546
    {
547
        return $this->option('ajax', $ajax);
548
    }
549
550
    /**
551
     * 检查是否为PJAX请求
552
     * @access public
553
     * @param  bool     $pjax
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
554
     * @return $this
555
     */
556
    public function pjax($pjax = true)
557
    {
558
        return $this->option('pjax', $pjax);
559
    }
560
561
    /**
562
     * 检查是否为手机访问
563
     * @access public
564
     * @param  bool     $mobile
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
565
     * @return $this
566
     */
567
    public function mobile($mobile = true)
568
    {
569
        return $this->option('mobile', $mobile);
570
    }
571
572
    /**
573
     * 当前路由到一个模板地址 当使用数组的时候可以传入模板变量
574
     * @access public
575
     * @param  bool|array     $view
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
576
     * @return $this
577
     */
578
    public function view($view = true)
579
    {
580
        return $this->option('view', $view);
581
    }
582
583
    /**
584
     * 当前路由为重定向
585
     * @access public
586
     * @param  bool   $redirect 是否为重定向
587
     * @return $this
588
     */
589
    public function redirect($redirect = true)
590
    {
591
        return $this->option('redirect', $redirect);
592
    }
593
594
    /**
595
     * 设置路由完整匹配
596
     * @access public
597
     * @param  bool     $match
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
598
     * @return $this
599
     */
600
    public function completeMatch($match = true)
601
    {
602
        return $this->option('complete_match', $match);
603
    }
604
605
    /**
606
     * 是否去除URL最后的斜线
607
     * @access public
608
     * @param  bool     $remove
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
609
     * @return $this
610
     */
611
    public function removeSlash($remove = true)
612
    {
613
        return $this->option('remove_slash', $remove);
614
    }
615
616
    /**
617
     * 设置是否允许跨域
618
     * @access public
619
     * @param  bool     $allow
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
620
     * @param  array    $header
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
621
     * @return $this
622
     */
623
    public function allowCrossDomain($allow = true, $header = [])
624
    {
625
        if (!empty($header)) {
626
            $this->header($header);
627
        }
628
629
        if ($allow && $this->parent) {
630
            $this->parent->addRuleItem($this, 'options');
631
        }
632
633
        return $this->option('cross_domain', $allow);
634
    }
635
636
    /**
637
     * 检查OPTIONS请求
638
     * @access public
639
     * @param  Request     $request
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
640
     * @return Dispatch|void
641
     */
642
    protected function checkCrossDomain($request)
643
    {
644
        if (!empty($this->option['cross_domain'])) {
645
646
            $header = [
647
                'Access-Control-Allow-Origin'  => '*',
648
                'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE',
649
                'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With',
650
            ];
651
652
            if (!empty($this->option['header'])) {
653
                $header = array_merge($header, $this->option['header']);
654
            }
655
656
            $this->option['header'] = $header;
657
658
            if ($request->method(true) == 'OPTIONS') {
659
                return new ResponseDispatch($request, $this, Response::create()->code(204)->header($header));
660
            }
661
        }
662
    }
663
664
    /**
665
     * 设置路由规则全局有效
666
     * @access public
667
     * @return $this
668
     */
669
    public function crossDomainRule()
670
    {
671
        if ($this instanceof RuleGroup) {
672
            $method = '*';
673
        } else {
674
            $method = $this->method;
675
        }
676
677
        $this->router->setCrossDomainRule($this, $method);
678
679
        return $this;
680
    }
681
682
    /**
683
     * 合并分组参数
684
     * @access public
685
     * @return array
686
     */
687
    public function mergeGroupOptions()
688
    {
689
        if (!$this->lockOption) {
690
            $parentOption = $this->parent->getOption();
691
            // 合并分组参数
692
            foreach ($this->mergeOptions as $item) {
693
                if (isset($parentOption[$item]) && isset($this->option[$item])) {
694
                    $this->option[$item] = array_merge($parentOption[$item], $this->option[$item]);
695
                }
696
            }
697
698
            $this->option     = array_merge($parentOption, $this->option);
699
            $this->lockOption = true;
700
        }
701
702
        return $this->option;
703
    }
704
705
    /**
706
     * 解析匹配到的规则路由
707
     * @access public
708
     * @param  Request   $request 请求对象
709
     * @param  string    $rule 路由规则
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
710
     * @param  string    $route 路由地址
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
711
     * @param  string    $url URL地址
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
712
     * @param  array     $option 路由参数
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
713
     * @param  array     $matches 匹配的变量
714
     * @return Dispatch
715
     */
716
    public function parseRule($request, $rule, $route, $url, $option = [], $matches = [])
717
    {
718
        if (is_string($route) && isset($option['prefix'])) {
719
            // 路由地址前缀
720
            $route = $option['prefix'] . $route;
721
        }
722
723
        // 替换路由地址中的变量
724
        if (is_string($route) && !empty($matches)) {
725
            $search = $replace = [];
726
727
            foreach ($matches as $key => $value) {
728
                $search[]  = '<' . $key . '>';
729
                $replace[] = $value;
730
731
                $search[]  = ':' . $key;
732
                $replace[] = $value;
733
            }
734
735
            $route = str_replace($search, $replace, $route);
736
        }
737
738
        // 解析额外参数
739
        $count = substr_count($rule, '/');
740
        $url   = array_slice(explode('|', $url), $count + 1);
741
        $this->parseUrlParams($request, implode('|', $url), $matches);
742
743
        $this->vars    = $matches;
744
        $this->option  = $option;
745
        $this->doAfter = true;
746
747
        // 发起路由调度
748
        return $this->dispatch($request, $route, $option);
749
    }
750
751
    /**
752
     * 检查路由前置行为
753
     * @access protected
754
     * @param  mixed   $before 前置行为
755
     * @return mixed
756
     */
757
    protected function checkBefore($before)
758
    {
759
        $hook = Container::get('hook');
760
761
        foreach ((array) $before as $behavior) {
762
            $result = $hook->exec($behavior);
763
764
            if (false === $result) {
765
                return false;
766
            }
767
        }
768
    }
769
770
    /**
771
     * 发起路由调度
772
     * @access protected
773
     * @param  Request   $request Request对象
774
     * @param  mixed     $route  路由地址
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 2 found
Loading history...
775
     * @param  array     $option 路由参数
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
776
     * @return Dispatch
777
     */
778
    protected function dispatch($request, $route, $option)
779
    {
780
        if ($route instanceof \Closure) {
781
            // 执行闭包
782
            $result = new CallbackDispatch($request, $this, $route);
783
        } elseif ($route instanceof Response) {
784
            $result = new ResponseDispatch($request, $this, $route);
785
        } elseif (isset($option['view']) && false !== $option['view']) {
786
            $result = new ViewDispatch($request, $this, $route, is_array($option['view']) ? $option['view'] : []);
787
        } elseif (!empty($option['redirect']) || 0 === strpos($route, '/') || strpos($route, '://')) {
788
            // 路由到重定向地址
789
            $result = new RedirectDispatch($request, $this, $route, [], isset($option['status']) ? $option['status'] : 301);
790
        } elseif (false !== strpos($route, '\\')) {
791
            // 路由到方法
792
            $result = $this->dispatchMethod($request, $route);
793
        } elseif (0 === strpos($route, '@')) {
794
            // 路由到控制器
795
            $result = $this->dispatchController($request, substr($route, 1));
796
        } else {
797
            // 路由到模块/控制器/操作
798
            $result = $this->dispatchModule($request, $route);
799
        }
800
801
        return $result;
802
    }
803
804
    /**
805
     * 解析URL地址为 模块/控制器/操作
806
     * @access protected
807
     * @param  Request   $request Request对象
808
     * @param  string    $route 路由地址
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
809
     * @return CallbackDispatch
810
     */
811
    protected function dispatchMethod($request, $route)
812
    {
813
        list($path, $var) = $this->parseUrlPath($route);
814
815
        $route  = str_replace('/', '@', implode('/', $path));
816
        $method = strpos($route, '@') ? explode('@', $route) : $route;
817
818
        return new CallbackDispatch($request, $this, $method, $var);
819
    }
820
821
    /**
822
     * 解析URL地址为 模块/控制器/操作
823
     * @access protected
824
     * @param  Request   $request Request对象
825
     * @param  string    $route 路由地址
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
826
     * @return ControllerDispatch
827
     */
828
    protected function dispatchController($request, $route)
829
    {
830
        list($route, $var) = $this->parseUrlPath($route);
831
832
        $result = new ControllerDispatch($request, $this, implode('/', $route), $var);
833
834
        $request->setAction(array_pop($route));
835
        $request->setController($route ? array_pop($route) : $this->getConfig('default_controller'));
836
        $request->setModule($route ? array_pop($route) : $this->getConfig('default_module'));
837
838
        return $result;
839
    }
840
841
    /**
842
     * 解析URL地址为 模块/控制器/操作
843
     * @access protected
844
     * @param  Request   $request Request对象
845
     * @param  string    $route 路由地址
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
846
     * @return ModuleDispatch
847
     */
848
    protected function dispatchModule($request, $route)
849
    {
850
        list($path, $var) = $this->parseUrlPath($route);
851
852
        $action     = array_pop($path);
853
        $controller = !empty($path) ? array_pop($path) : null;
854
        $module     = $this->getConfig('app_multi_module') && !empty($path) ? array_pop($path) : null;
855
        $method     = $request->method();
856
857
        if ($this->getConfig('use_action_prefix') && $this->router->getMethodPrefix($method)) {
858
            $prefix = $this->router->getMethodPrefix($method);
859
            // 操作方法前缀支持
860
            $action = 0 !== strpos($action, $prefix) ? $prefix . $action : $action;
861
        }
862
863
        // 设置当前请求的路由变量
864
        $request->setRouteVars($var);
865
866
        // 路由到模块/控制器/操作
867
        return new ModuleDispatch($request, $this, [$module, $controller, $action], ['convert' => false]);
868
    }
869
870
    /**
871
     * 路由检查
872
     * @access protected
873
     * @param  array     $option 路由参数
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
874
     * @param  Request   $request Request对象
875
     * @return bool
876
     */
877
    protected function checkOption($option, Request $request)
878
    {
879
        // 请求类型检测
880
        if (!empty($option['method'])) {
881
            if (is_string($option['method']) && false === stripos($option['method'], $request->method())) {
882
                return false;
883
            }
884
        }
885
886
        // AJAX PJAX 请求检查
887
        foreach (['ajax', 'pjax', 'mobile'] as $item) {
888
            if (isset($option[$item])) {
889
                $call = 'is' . $item;
890
                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...
891
                    return false;
892
                }
893
            }
894
        }
895
896
        // 伪静态后缀检测
897
        if ($request->url() != '/' && ((isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|'))
898
            || (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...
899
            return false;
900
        }
901
902
        // 域名检查
903
        if ((isset($option['domain']) && !in_array($option['domain'], [$request->host(true), $request->subDomain()]))) {
904
            return false;
905
        }
906
907
        // HTTPS检查
908
        if ((isset($option['https']) && $option['https'] && !$request->isSsl())
909
            || (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...
910
            return false;
911
        }
912
913
        // 请求参数检查
914
        if (isset($option['filter'])) {
915
            foreach ($option['filter'] as $name => $value) {
916
                if ($request->param($name, '', null) != $value) {
917
                    return false;
918
                }
919
            }
920
        }
921
        return true;
922
    }
923
924
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $url should have a doc-comment as per coding-style.
Loading history...
925
     * 解析URL地址中的参数Request对象
926
     * @access protected
927
     * @param  Request   $request
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
928
     * @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...
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
929
     * @param  array     $var 变量
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
930
     * @return void
931
     */
932
    protected function parseUrlParams($request, $url, &$var = [])
933
    {
934
        if ($url) {
935
            if ($this->getConfig('url_param_type')) {
936
                $var += explode('|', $url);
937
            } else {
938
                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...
939
                    $var[$match[1]] = strip_tags($match[2]);
940
                }, $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...
941
            }
942
        }
943
    }
944
945
    /**
946
     * 解析URL的pathinfo参数和变量
947
     * @access public
948
     * @param  string    $url URL地址
949
     * @return array
950
     */
951
    public function parseUrlPath($url)
952
    {
953
        // 分隔符替换 确保路由定义使用统一的分隔符
954
        $url = str_replace('|', '/', $url);
955
        $url = trim($url, '/');
956
        $var = [];
957
958
        if (false !== strpos($url, '?')) {
959
            // [模块/控制器/操作?]参数1=值1&参数2=值2...
960
            $info = parse_url($url);
961
            $path = explode('/', $info['path']);
962
            parse_str($info['query'], $var);
963
        } elseif (strpos($url, '/')) {
964
            // [模块/控制器/操作]
965
            $path = explode('/', $url);
966
        } elseif (false !== strpos($url, '=')) {
967
            // 参数1=值1&参数2=值2...
968
            $path = [];
969
            parse_str($url, $var);
970
        } else {
971
            $path = [$url];
972
        }
973
974
        return [$path, $var];
975
    }
976
977
    /**
978
     * 生成路由的正则规则
979
     * @access protected
980
     * @param  string    $rule 路由规则
0 ignored issues
show
Coding Style introduced by
Expected 10 spaces after parameter name; 1 found
Loading history...
981
     * @param  array     $match 匹配的变量
0 ignored issues
show
Coding Style introduced by
Expected 9 spaces after parameter name; 1 found
Loading history...
982
     * @param  array     $pattern   路由变量规则
0 ignored issues
show
Coding Style introduced by
Expected 7 spaces after parameter name; 3 found
Loading history...
983
     * @param  array     $option    路由参数
0 ignored issues
show
Coding Style introduced by
Expected 8 spaces after parameter name; 4 found
Loading history...
984
     * @param  bool      $completeMatch   路由是否完全匹配
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
985
     * @param  string    $suffix   路由正则变量后缀
0 ignored issues
show
Coding Style introduced by
Expected 8 spaces after parameter name; 3 found
Loading history...
986
     * @return string
987
     */
988
    protected function buildRuleRegex($rule, $match, $pattern = [], $option = [], $completeMatch = false, $suffix = '')
989
    {
990
        foreach ($match as $name) {
991
            $replace[] = $this->buildNameRegex($name, $pattern, $suffix);
0 ignored issues
show
Bug introduced by
$pattern of type array is incompatible with the type string expected by parameter $pattern of think\route\Rule::buildNameRegex(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

991
            $replace[] = $this->buildNameRegex($name, /** @scrutinizer ignore-type */ $pattern, $suffix);
Loading history...
992
        }
993
994
        // 是否区分 / 地址访问
995
        if ('/' != $rule) {
996
            if (!empty($option['remove_slash'])) {
997
                $rule = rtrim($rule, '/');
998
            } elseif (substr($rule, -1) == '/') {
999
                $rule     = rtrim($rule, '/');
1000
                $hasSlash = true;
1001
            }
1002
        }
1003
1004
        $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 990. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1005
        $regex = str_replace([')?/', ')/', ')?-', ')-', '\\\\/'], [')\/', ')\/', ')\-', ')\-', '\/'], $regex);
1006
1007
        if (isset($hasSlash)) {
1008
            $regex .= '\/';
1009
        }
1010
1011
        return $regex . ($completeMatch ? '$' : '');
1012
    }
1013
1014
    /**
1015
     * 生成路由变量的正则规则
1016
     * @access protected
1017
     * @param  string    $name      路由变量
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 6 found
Loading history...
1018
     * @param  string    $pattern   变量规则
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
1019
     * @param  string    $suffix    路由正则变量后缀
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 4 found
Loading history...
1020
     * @return string
1021
     */
1022
    protected function buildNameRegex($name, $pattern, $suffix)
1023
    {
1024
        $optional = '';
1025
        $slash    = substr($name, 0, 1);
1026
1027
        if (in_array($slash, ['/', '-'])) {
1028
            $prefix = '\\' . $slash;
1029
            $name   = substr($name, 1);
1030
            $slash  = substr($name, 0, 1);
1031
        } else {
1032
            $prefix = '';
1033
        }
1034
1035
        if ('<' != $slash) {
1036
            return $prefix . preg_quote($name, '/');
1037
        }
1038
1039
        if (strpos($name, '?')) {
1040
            $name     = substr($name, 1, -2);
1041
            $optional = '?';
1042
        } elseif (strpos($name, '>')) {
1043
            $name = substr($name, 1, -1);
1044
        }
1045
1046
        if (isset($pattern[$name])) {
1047
            $nameRule = $pattern[$name];
1048
            if (0 === strpos($nameRule, '/') && '/' == substr($nameRule, -1)) {
1049
                $nameRule = substr($nameRule, 1, -1);
1050
            }
1051
        } else {
1052
            $nameRule = $this->getConfig('default_route_pattern');
1053
        }
1054
1055
        return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional;
1056
    }
1057
1058
    /**
1059
     * 分析路由规则中的变量
1060
     * @access protected
1061
     * @param  string    $rule 路由规则
1062
     * @return array
1063
     */
1064
    protected function parseVar($rule)
1065
    {
1066
        // 提取路由规则中的变量
1067
        $var = [];
1068
1069
        if (preg_match_all('/<\w+\??>/', $rule, $matches)) {
1070
            foreach ($matches[0] as $name) {
1071
                $optional = false;
1072
1073
                if (strpos($name, '?')) {
1074
                    $name     = substr($name, 1, -2);
1075
                    $optional = true;
1076
                } else {
1077
                    $name = substr($name, 1, -1);
1078
                }
1079
1080
                $var[$name] = $optional ? 2 : 1;
1081
            }
1082
        }
1083
1084
        return $var;
1085
    }
1086
1087
    /**
1088
     * 设置路由参数
1089
     * @access public
1090
     * @param  string    $method     方法名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 5 found
Loading history...
1091
     * @param  array     $args       调用参数
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 7 found
Loading history...
1092
     * @return $this
1093
     */
1094
    public function __call($method, $args)
1095
    {
1096
        if (count($args) > 1) {
1097
            $args[0] = $args;
1098
        }
1099
        array_unshift($args, $method);
1100
1101
        return call_user_func_array([$this, 'option'], $args);
1102
    }
1103
1104
    public function __sleep()
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
1105
    {
1106
        return ['name', 'rule', 'route', 'method', 'vars', 'option', 'pattern', 'doAfter'];
1107
    }
1108
1109
    public function __wakeup()
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
1110
    {
1111
        $this->router = Container::get('route');
1112
    }
1113
1114
    public function __debugInfo()
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
1115
    {
1116
        $data = get_object_vars($this);
1117
        unset($data['parent'], $data['router'], $data['route']);
1118
1119
        return $data;
1120
    }
1121
}
1122