Completed
Branch 6.0 (d30585)
by yun
04:17
created

RuleGroup::setFullName()   A

Complexity

Conditions 6
Paths 12

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 6.3949

Importance

Changes 0
Metric Value
cc 6
nc 12
nop 0
dl 0
loc 16
rs 9.1111
c 0
b 0
f 0
ccs 7
cts 9
cp 0.7778
crap 6.3949
1
<?php
2
// +----------------------------------------------------------------------
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 Closure;
16
use think\Container;
17
use think\Exception;
18
use think\Request;
19
use think\Response;
20
use think\Route;
21
use think\route\dispatch\Response as ResponseDispatch;
22
23
/**
24
 * 路由分组类
25
 */
26
class RuleGroup extends Rule
27
{
28
    /**
29
     * 分组路由(包括子分组)
30
     * @var array
31
     */
32
    protected $rules = [];
33
34
    /**
35
     * 分组路由规则
36
     * @var mixed
37
     */
38
    protected $rule;
39
40
    /**
41
     * MISS路由
42
     * @var RuleItem
43
     */
44
    protected $miss;
45
46
    /**
47
     * 完整名称
48
     * @var string
49
     */
50
    protected $fullName;
51
52
    /**
53
     * 分组别名
54
     * @var string
55
     */
56
    protected $alias;
57
58
    /**
59
     * 架构函数
60
     * @access public
61
     * @param  Route     $router 路由对象
62
     * @param  RuleGroup $parent 上级对象
63
     * @param  string    $name   分组名称
64
     * @param  mixed     $rule   分组路由
65
     */
66 6
    public function __construct(Route $router, RuleGroup $parent = null, string $name = '', $rule = null)
67
    {
68 6
        $this->router = $router;
69 6
        $this->parent = $parent;
70 6
        $this->rule   = $rule;
71 6
        $this->name   = trim($name, '/');
72
73 6
        $this->setFullName();
74
75 6
        if ($this->parent) {
76 6
            $this->domain = $this->parent->getDomain();
77 6
            $this->parent->addRuleItem($this);
78
        }
79
80 6
        if ($router->isTest()) {
81
            $this->lazy(false);
82
        }
83 6
    }
84
85
    /**
86
     * 设置分组的路由规则
87
     * @access public
88
     * @return void
89
     */
90 6
    protected function setFullName(): void
91
    {
92 6
        if (false !== strpos($this->name, ':')) {
93
            $this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name);
94
        }
95
96 6
        if ($this->parent && $this->parent->getFullName()) {
97
            $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : '');
98
        } else {
99 6
            $this->fullName = $this->name;
100
        }
101
102 6
        if ($this->name) {
103 3
            $this->router->getRuleName()->setGroup($this->name, $this);
104
        }
105 6
    }
106
107
    /**
108
     * 获取所属域名
109
     * @access public
110
     * @return string
111
     */
112 6
    public function getDomain(): string
113
    {
114 6
        return $this->domain ?: '-';
115
    }
116
117
    /**
118
     * 获取分组别名
119
     * @access public
120
     * @return string
121
     */
122
    public function getAlias(): string
123
    {
124
        return $this->alias ?: '';
125
    }
126
127
    /**
128
     * 检测分组路由
129
     * @access public
130
     * @param  Request $request       请求对象
131
     * @param  string  $url           访问地址
132
     * @param  bool    $completeMatch 路由是否完全匹配
133
     * @return Dispatch|false
134
     */
135 18
    public function check(Request $request, string $url, bool $completeMatch = false)
136
    {
137
        // 检查分组有效性
138 18
        if (!$this->checkOption($this->option, $request) || !$this->checkUrl($url)) {
139
            return false;
140
        }
141
142
        // 解析分组路由
143 18
        if ($this instanceof Resource) {
144 3
            $this->buildResourceRule();
0 ignored issues
show
Documentation Bug introduced by
The method buildResourceRule does not exist on object<think\route\RuleGroup>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
145 18
        } elseif ($this->rule instanceof Response) {
146
            return new ResponseDispatch($request, $this, $this->rule);
147
        } else {
148 18
            $this->parseGroupRule($this->rule);
149
        }
150
151
        // 获取当前路由规则
152 18
        $method = strtolower($request->method());
153 18
        $rules  = $this->getRules($method);
154
155 18
        if ($this->parent) {
156
            // 合并分组参数
157 6
            $this->mergeGroupOptions();
158
            // 合并分组变量规则
159 6
            $this->pattern = array_merge($this->parent->getPattern(), $this->pattern);
160
        }
161
162 18
        if (isset($this->option['complete_match'])) {
163 3
            $completeMatch = $this->option['complete_match'];
164
        }
165
166 18
        if (!empty($this->option['merge_rule_regex'])) {
167
            // 合并路由正则规则进行路由匹配检查
168
            $result = $this->checkMergeRuleRegex($request, $rules, $url, $completeMatch);
169
170
            if (false !== $result) {
171
                return $result;
172
            }
173
        }
174
175
        // 检查分组路由
176 18
        foreach ($rules as $key => $item) {
177 18
            $result = $item[1]->check($request, $url, $completeMatch);
178
179 18
            if (false !== $result) {
180 18
                return $result;
181
            }
182
        }
183
184 3
        if ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) {
185
            // 未匹配所有路由的路由规则处理
186
            $result = $this->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->mergeGroupOptions());
187
        } else {
188 3
            $result = false;
189
        }
190
191 3
        return $result;
192
    }
193
194
    /**
195
     * 分组URL匹配检查
196
     * @access protected
197
     * @param  string $url URL
198
     * @return bool
199
     */
200 18
    protected function checkUrl(string $url): bool
201
    {
202 18
        if ($this->fullName) {
203 3
            $pos = strpos($this->fullName, '<');
204
205 3
            if (false !== $pos) {
206
                $str = substr($this->fullName, 0, $pos);
207
            } else {
208 3
                $str = $this->fullName;
209
            }
210
211 3
            if ($str && 0 !== stripos(str_replace('|', '/', $url), $str)) {
212
                return false;
213
            }
214
        }
215
216 18
        return true;
217
    }
218
219
    /**
220
     * 设置路由分组别名
221
     * @access public
222
     * @param  string $alias 路由分组别名
223
     * @return $this
224
     */
225
    public function alias(string $alias)
226
    {
227
        $this->alias = $alias;
228
        $this->router->getRuleName()->setGroup($alias, $this);
229
230
        return $this;
231
    }
232
233
    /**
234
     * 延迟解析分组的路由规则
235
     * @access public
236
     * @param  bool $lazy 路由是否延迟解析
237
     * @return $this
238
     */
239 6
    public function lazy(bool $lazy = true)
240
    {
241 6
        if (!$lazy) {
242 6
            $this->parseGroupRule($this->rule);
243 6
            $this->rule = null;
244
        }
245
246 6
        return $this;
247
    }
248
249
    /**
250
     * 解析分组和域名的路由规则及绑定
251
     * @access public
252
     * @param  mixed $rule 路由规则
253
     * @return void
254
     */
255 18
    public function parseGroupRule($rule): void
256
    {
257 18
        $origin = $this->router->getGroup();
258 18
        $this->router->setGroup($this);
259
260 18
        if ($rule instanceof \Closure) {
261 6
            Container::getInstance()->invokeFunction($rule);
262 18
        } elseif (is_string($rule) && $rule) {
263
            $this->router->bind($rule, $this->domain);
264
        }
265
266 18
        $this->router->setGroup($origin);
267 18
    }
268
269
    /**
270
     * 检测分组路由
271
     * @access public
272
     * @param  Request $request       请求对象
273
     * @param  array   $rules         路由规则
274
     * @param  string  $url           访问地址
275
     * @param  bool    $completeMatch 路由是否完全匹配
276
     * @return Dispatch|false
277
     */
278
    protected function checkMergeRuleRegex(Request $request, array &$rules, string $url, bool $completeMatch)
279
    {
280
        $depr  = $this->router->config('pathinfo_depr');
281
        $url   = $depr . str_replace('|', $depr, $url);
282
        $regex = [];
283
        $items = [];
284
285
        foreach ($rules as $key => $val) {
286
            $item = $val[1];
287
            if ($item instanceof RuleItem) {
288
                $rule = $depr . str_replace('/', $depr, $item->getRule());
289
                if ($depr == $rule && $depr != $url) {
290
                    unset($rules[$key]);
291
                    continue;
292
                }
293
294
                $complete = $item->getOption('complete_match', $completeMatch);
295
296
                if (false === strpos($rule, '<')) {
297
                    if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) {
298
                        return $item->checkRule($request, $url, []);
299
                    }
300
301
                    unset($rules[$key]);
302
                    continue;
303
                }
304
305
                $slash = preg_quote('/-' . $depr, '/');
306
307
                if ($matchRule = preg_split('/[' . $slash . ']<\w+\??>/', $rule, 2)) {
308
                    if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) {
309
                        unset($rules[$key]);
310
                        continue;
311
                    }
312
                }
313
314
                if (preg_match_all('/[' . $slash . ']?<?\w+\??>?/', $rule, $matches)) {
315
                    unset($rules[$key]);
316
                    $pattern = array_merge($this->getPattern(), $item->getPattern());
317
                    $option  = array_merge($this->getOption(), $item->getOption());
318
319
                    $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key);
320
                    $items[$key] = $item;
321
                }
322
            }
323
        }
324
325
        if (empty($regex)) {
326
            return false;
327
        }
328
329
        try {
330
            $result = preg_match('/^(?:' . implode('|', $regex) . ')/u', $url, $match);
331
        } catch (\Exception $e) {
332
            throw new Exception('route pattern error');
333
        }
334
335
        if ($result) {
336
            $var = [];
337
            foreach ($match as $key => $val) {
338
                if (is_string($key) && '' !== $val) {
339
                    [$name, $pos] = explode('_THINK_', $key);
0 ignored issues
show
Bug introduced by
The variable $name does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $pos seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
340
341
                    $var[$name] = $val;
342
                }
343
            }
344
345
            if (!isset($pos)) {
0 ignored issues
show
Bug introduced by
The variable $pos seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
346
                foreach ($regex as $key => $item) {
347
                    if (0 === strpos(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) {
348
                        $pos = $key;
349
                        break;
350
                    }
351
                }
352
            }
353
354
            $rule  = $items[$pos]->getRule();
0 ignored issues
show
Bug introduced by
The variable $pos does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
355
            $array = $this->router->getRule($rule);
356
357
            foreach ($array as $item) {
358
                if (in_array($item->getMethod(), ['*', strtolower($request->method())])) {
359
                    $result = $item->checkRule($request, $url, $var);
360
361
                    if (false !== $result) {
362
                        return $result;
363
                    }
364
                }
365
            }
366
        }
367
368
        return false;
369
    }
370
371
    /**
372
     * 获取分组的MISS路由
373
     * @access public
374
     * @return RuleItem|null
375
     */
376
    public function getMissRule():  ? RuleItem
377
    {
378
        return $this->miss;
379
    }
380
381
    /**
382
     * 注册MISS路由
383
     * @access public
384
     * @param  string|Closure $route  路由地址
385
     * @param  string         $method 请求类型
386
     * @return RuleItem
387
     */
388
    public function miss($route, string $method = '*') : RuleItem
389
    {
390
        // 创建路由规则实例
391
        $ruleItem = new RuleItem($this->router, $this, null, '', $route, strtolower($method));
392
393
        $ruleItem->setMiss();
394
        $this->miss = $ruleItem;
395
396
        return $ruleItem;
397
    }
398
399
    /**
400
     * 添加分组下的路由规则
401
     * @access public
402
     * @param  string $rule   路由规则
403
     * @param  mixed  $route  路由地址
404
     * @param  string $method 请求类型
405
     * @return RuleItem
406
     */
407 18
    public function addRule(string $rule, $route = null, string $method = '*'): RuleItem
408
    {
409
        // 读取路由标识
410 18
        if (is_string($route)) {
411 12
            $name = $route;
412
        } else {
413 9
            $name = null;
414
        }
415
416 18
        $method = strtolower($method);
417
418 18
        if ('' === $rule || '/' === $rule) {
419 3
            $rule .= '$';
420
        }
421
422
        // 创建路由规则实例
423 18
        $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method);
424
425 18
        $this->addRuleItem($ruleItem, $method);
426
427 18
        return $ruleItem;
428
    }
429
430
    /**
431
     * 注册分组下的路由规则
432
     * @access public
433
     * @param  Rule   $rule   路由规则
434
     * @param  string $method 请求类型
435
     * @return $this
436
     */
437 18
    public function addRuleItem(Rule $rule, string $method = '*')
438
    {
439 18
        if (strpos($method, '|')) {
440
            $rule->method($method);
441
            $method = '*';
442
        }
443
444 18
        $this->rules[] = [$method, $rule];
445
446 18
        if ($rule instanceof RuleItem && 'options' != $method) {
447 18
            $this->rules[] = ['options', $rule->setAutoOptions()];
448
        }
449
450 18
        return $this;
451
    }
452
453
    /**
454
     * 设置分组的路由前缀
455
     * @access public
456
     * @param  string $prefix 路由前缀
457
     * @return $this
458
     */
459
    public function prefix(string $prefix)
460
    {
461
        if ($this->parent && $this->parent->getOption('prefix')) {
462
            $prefix = $this->parent->getOption('prefix') . $prefix;
463
        }
464
465
        return $this->setOption('prefix', $prefix);
466
    }
467
468
    /**
469
     * 合并分组的路由规则正则
470
     * @access public
471
     * @param  bool $merge 是否合并
472
     * @return $this
473
     */
474 6
    public function mergeRuleRegex(bool $merge = true)
475
    {
476 6
        return $this->setOption('merge_rule_regex', $merge);
477
    }
478
479
    /**
480
     * 获取完整分组Name
481
     * @access public
482
     * @return string
483
     */
484 18
    public function getFullName(): string
485
    {
486 18
        return $this->fullName ?: '';
487
    }
488
489
    /**
490
     * 获取分组的路由规则
491
     * @access public
492
     * @param  string $method 请求类型
493
     * @return array
494
     */
495 18
    public function getRules(string $method = ''): array
496
    {
497 18
        if ('' === $method) {
498
            return $this->rules;
499
        }
500
501
        return array_filter($this->rules, function ($item) use ($method) {
502 18
            return $method == $item[0] || $item[0] == '*';
503 18
        });
504
    }
505
506
    /**
507
     * 清空分组下的路由规则
508
     * @access public
509
     * @return void
510
     */
511
    public function clear(): void
512
    {
513
        $this->rules = [];
514
    }
515
}
516