Passed
Push — 6.0 ( 97220f...d9fa6d )
by liu
02:25
created

Url::parseUrl()   C

Complexity

Conditions 12
Paths 53

Size

Total Lines 51
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 32
nc 53
nop 2
dl 0
loc 51
rs 6.9666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
// +----------------------------------------------------------------------
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;
14
15
class Url
1 ignored issue
show
Coding Style introduced by
Missing class doc comment
Loading history...
16
{
17
    /**
18
     * 绑定检查
19
     * @var bool
20
     */
21
    protected $bindCheck;
22
23
    /**
24
     * 显示域名
25
     * @var bool
26
     */
27
    protected $showDomain;
28
29
    /**
30
     * 应用对象
31
     * @var App
32
     */
33
    protected $app;
34
35
    /**
36
     * 配置参数
37
     * @var array
38
     */
39
    protected $config = [];
40
41
    public function __construct(App $app, array $config = [])
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
42
    {
43
        $this->app    = $app;
44
        $this->config = $config;
45
46
        if (is_file($app->getRuntimePath() . 'route.php')) {
47
            // 读取路由映射文件
48
            $app->route->import(include $app->getRuntimePath() . 'route.php');
49
        }
50
    }
51
52
    /**
53
     * 初始化
54
     * @access public
55
     * @param  array $config 配置信息
56
     * @return void
57
     */
58
    public function init(array $config = [])
59
    {
60
        $this->config = array_merge($this->config, array_change_key_case($config));
61
    }
62
63
    public static function __make(App $app, Route $route)
1 ignored issue
show
Coding Style introduced by
Method name "Url::__make" is invalid; only PHP magic methods should be prefixed with a double underscore
Loading history...
Coding Style introduced by
Missing function doc comment
Loading history...
64
    {
65
        return new static($app, $route->config());
66
    }
67
68
    /**
69
     * URL生成 支持路由反射
70
     * @access public
71
     * @param  string       $url 路由地址
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
72
     * @param  array|string $vars 参数 ['a'=>'val1', 'b'=>'val2']
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
73
     * @param  string|bool  $suffix 伪静态后缀,默认为true表示获取配置值
74
     * @param  bool|string  $domain 是否显示域名 或者直接传入域名
75
     * @return string
76
     */
77
    public function build(string $url = '', $vars = [], $suffix = true, $domain = false): string
78
    {
79
        // 解析URL
80
        if (0 === strpos($url, '[') && $pos = strpos($url, ']')) {
81
            // [name] 表示使用路由命名标识生成URL
82
            $name = substr($url, 1, $pos - 1);
83
            $url  = 'name' . substr($url, $pos + 1);
84
        }
85
86
        if (false === strpos($url, '://') && 0 !== strpos($url, '/')) {
87
            $info = parse_url($url);
88
            $url  = !empty($info['path']) ? $info['path'] : '';
89
90
            if (isset($info['fragment'])) {
91
                // 解析锚点
92
                $anchor = $info['fragment'];
93
94
                if (false !== strpos($anchor, '?')) {
95
                    // 解析参数
96
                    list($anchor, $info['query']) = explode('?', $anchor, 2);
97
                }
98
99
                if (false !== strpos($anchor, '@')) {
100
                    // 解析域名
101
                    list($anchor, $domain) = explode('@', $anchor, 2);
102
                }
103
            } elseif (strpos($url, '@') && false === strpos($url, '\\')) {
104
                // 解析域名
105
                list($url, $domain) = explode('@', $url, 2);
106
            }
107
        }
108
109
        $this->showDomain = false === $domain ? false : true;
110
111
        // 解析参数
112
        if (is_string($vars)) {
113
            // aaa=1&bbb=2 转换成数组
114
            parse_str($vars, $vars);
0 ignored issues
show
Bug introduced by
$vars of type string is incompatible with the type array|null expected by parameter $arr of parse_str(). ( Ignorable by Annotation )

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

114
            parse_str($vars, /** @scrutinizer ignore-type */ $vars);
Loading history...
115
        }
116
117
        if ($url) {
118
            $checkName   = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '');
119
            $checkDomain = $domain && is_string($domain) ? $domain : null;
120
121
            $rule = $this->app->route->getName($checkName, $checkDomain);
122
123
            if (is_null($rule) && isset($info['query'])) {
0 ignored issues
show
introduced by
The condition is_null($rule) is always false.
Loading history...
124
                $rule = $this->app->route->getName($url, $checkDomain);
125
                // 解析地址里面参数 合并到vars
126
                parse_str($info['query'], $params);
127
                $vars = array_merge($params, $vars);
128
                unset($info['query']);
129
            }
130
        }
131
132
        if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) {
133
            // 匹配路由命名标识
134
            $url = $match[0];
135
136
            if (!empty($match[1])) {
137
                $domain = $match[1];
138
            }
139
140
            if (!is_null($match[2])) {
141
                $suffix = $match[2];
142
            }
143
144
            if ($this->app->request->app() && !$this->app->http->isBindDomain()) {
145
                $url = $this->app->request->app() . '/' . $url;
146
            }
147
        } elseif (!empty($rule) && isset($name)) {
148
            throw new \InvalidArgumentException('route name not exists:' . $name);
149
        } else {
150
            // 检测URL绑定
151
            if (!$this->bindCheck) {
152
                $bind = $this->app->route->getDomainBind($domain && is_string($domain) ? $domain : null);
153
154
                if ($bind && 0 === strpos($url, $bind)) {
155
                    $url = substr($url, strlen($bind) + 1);
156
                } else {
157
                    $binds = $this->app->route->getBind();
158
159
                    foreach ($binds as $key => $val) {
160
                        if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) {
161
                            $url    = substr($url, strlen($val) + 1);
162
                            $domain = $key;
163
                            break;
164
                        }
165
                    }
166
                }
167
            }
168
169
            // 路由标识不存在 直接解析
170
            $url = $this->parseUrl($url, $domain);
171
172
            if (isset($info['query'])) {
173
                // 解析地址里面参数 合并到vars
174
                parse_str($info['query'], $params);
175
                $vars = array_merge($params, $vars);
176
            }
177
        }
178
179
        // 还原URL分隔符
180
        $depr = $this->config['pathinfo_depr'];
181
        $url  = str_replace('/', $depr, $url);
182
183
        $file = $this->app->request->baseFile();
184
        if ($file && 0 !== strpos($this->app->request->url(), $file)) {
185
            $file = str_replace('\\', '/', dirname($file));
186
        }
187
188
        $url = rtrim($file, '/') . '/' . $url;
189
190
        // URL后缀
191
        if ('/' == substr($url, -1) || '' == $url) {
192
            $suffix = '';
193
        } else {
194
            $suffix = $this->parseSuffix($suffix);
195
        }
196
197
        // 锚点
198
        $anchor = !empty($anchor) ? '#' . $anchor : '';
199
200
        // 参数组装
201
        if (!empty($vars)) {
202
            // 添加参数
203
            if ($this->config['url_common_param']) {
204
                $vars = http_build_query($vars);
205
                $url .= $suffix . '?' . $vars . $anchor;
206
            } else {
207
                foreach ($vars as $var => $val) {
208
                    if ('' !== $val) {
209
                        $url .= $depr . $var . $depr . urlencode((string) $val);
210
                    }
211
                }
212
213
                $url .= $suffix . $anchor;
214
            }
215
        } else {
216
            $url .= $suffix . $anchor;
217
        }
218
219
        // 检测域名
220
        $domain = $this->parseDomain($url, $domain);
221
222
        // URL组装
223
        $url = $domain . '/' . ltrim($url, '/');
224
225
        $this->bindCheck = false;
226
227
        return $url;
228
    }
229
230
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $domain should have a doc-comment as per coding-style.
Loading history...
231
     * 直接解析URL地址
232
     * @access public
233
     * @param  string $url URL
234
     * @return string
235
     */
236
    protected function parseUrl(string $url, &$domain): string
237
    {
238
        $request = $this->app->request;
239
240
        if (0 === strpos($url, '/')) {
241
            // 直接作为路由地址解析
242
            $url = substr($url, 1);
243
        } elseif (false !== strpos($url, '\\')) {
244
            // 解析到类
245
            $url = ltrim(str_replace('\\', '/', $url), '/');
246
        } elseif (0 === strpos($url, '@')) {
247
            // 解析到控制器
248
            $url = substr($url, 1);
249
        } else {
250
            // 解析到 应用/控制器/操作
251
            $app        = $request->app();
252
            $controller = $request->controller();
253
254
            if ('' == $url) {
255
                $action = $request->action();
256
            } else {
257
                $path       = explode('/', $url);
258
                $action     = array_pop($path);
259
                $controller = empty($path) ? $controller : array_pop($path);
260
                $app        = empty($path) ? $app : array_pop($path);
261
            }
262
263
            if ($this->config['url_convert']) {
264
                $action     = strtolower($action);
265
                $controller = App::parseName($controller);
266
            }
267
268
            $url = $controller . '/' . $action;
269
270
            if ($app) {
271
                $bind = $this->app->config->get('app.domain_bind', []);
272
                if ($key = array_search($app, $bind)) {
273
                    $domain = true === $domain ? $key : $domain;
274
                } else {
275
                    $map = $this->app->config->get('app.app_map', []);
276
277
                    if ($key = array_search($app, $map)) {
278
                        $url = $key . '/' . $url;
279
                    } else {
280
                        $url = $app . '/' . $url;
281
                    }
282
                }
283
            }
284
        }
285
286
        return $url;
287
    }
288
289
    /**
290
     * 检测域名
291
     * @access public
292
     * @param  string      $url URL
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
293
     * @param  string|true $domain 域名
294
     * @return string
295
     */
296
    protected function parseDomain(string &$url, $domain): string
297
    {
298
        if (!$domain) {
299
            return '';
300
        }
301
302
        $rootDomain = $this->app->request->rootDomain();
303
        if (true === $domain) {
304
            // 自动判断域名
305
            $domain  = $this->app->request->host();
306
            $domains = $this->app->route->getDomains();
307
308
            if (!empty($domains)) {
309
                $route_domain = array_keys($domains);
310
                foreach ($route_domain as $domain_prefix) {
311
                    if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== false) {
312
                        foreach ($domains as $key => $rule) {
313
                            $rule = is_array($rule) ? $rule[0] : $rule;
314
                            if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) {
315
                                $url    = ltrim($url, $rule);
316
                                $domain = $key;
317
318
                                // 生成对应子域名
319
                                if (!empty($rootDomain)) {
320
                                    $domain .= $rootDomain;
321
                                }
322
                                break;
323
                            } elseif (false !== strpos($key, '*')) {
324
                                if (!empty($rootDomain)) {
325
                                    $domain .= $rootDomain;
326
                                }
327
328
                                break;
329
                            }
330
                        }
331
                    }
332
                }
333
            }
334
        } elseif (false === strpos($domain, '.') && 0 !== strpos($domain, $rootDomain)) {
335
            $domain .= '.' . $rootDomain;
336
        }
337
338
        if (false !== strpos($domain, '://')) {
339
            $scheme = '';
340
        } else {
341
            $scheme = $this->app->request->isSsl() ? 'https://' : 'http://';
342
        }
343
344
        return $this->app->request->host() == $domain && !$this->showDomain ? '' : $scheme . $domain;
345
    }
346
347
    /**
348
     * 解析URL后缀
349
     * @access public
350
     * @param  string|bool $suffix 后缀
351
     * @return string
352
     */
353
    protected function parseSuffix($suffix): string
354
    {
355
        if ($suffix) {
356
            $suffix = true === $suffix ? $this->config['url_html_suffix'] : $suffix;
357
358
            if ($pos = strpos($suffix, '|')) {
359
                $suffix = substr($suffix, 0, $pos);
360
            }
361
        }
362
363
        return (empty($suffix) || 0 === strpos($suffix, '.')) ? (string) $suffix : '.' . $suffix;
364
    }
365
366
    /**
367
     * 匹配路由地址
368
     * @access public
369
     * @param  array $rule 路由规则
0 ignored issues
show
Coding Style introduced by
Expected 8 spaces after parameter name; 1 found
Loading history...
370
     * @param  array $vars 路由变量
0 ignored issues
show
Coding Style introduced by
Expected 8 spaces after parameter name; 1 found
Loading history...
371
     * @param  mixed $allowDomain 允许域名
372
     * @return array
373
     */
374
    public function getRuleUrl(array $rule, array &$vars = [], $allowDomain = ''): array
375
    {
376
        foreach ($rule as $item) {
377
            list($url, $pattern, $domain, $suffix) = $item;
378
379
            if (is_string($allowDomain) && $domain != $allowDomain) {
380
                continue;
381
            }
382
383
            if (!in_array($this->app->request->port(), [80, 443])) {
384
                $domain .= ':' . $this->app->request->port();
385
            }
386
387
            if (empty($pattern)) {
388
                return [rtrim($url, '?/-'), $domain, $suffix];
389
            }
390
391
            $type = $this->config['url_common_param'];
392
393
            foreach ($pattern as $key => $val) {
394
                if (isset($vars[$key])) {
395
                    $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? $vars[$key] : urlencode((string) $vars[$key]), $url);
396
                    unset($vars[$key]);
397
                    $url    = str_replace(['/?', '-?'], ['/', '-'], $url);
398
                    $result = [rtrim($url, '?/-'), $domain, $suffix];
399
                } elseif (2 == $val) {
400
                    $url    = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url);
401
                    $url    = str_replace(['/?', '-?'], ['/', '-'], $url);
402
                    $result = [rtrim($url, '?/-'), $domain, $suffix];
403
                } else {
404
                    break;
405
                }
406
            }
407
408
            if (isset($result)) {
409
                return $result;
410
            }
411
        }
412
413
        return [];
414
    }
415
}
416