Passed
Push — 5.2 ( ed7db6...79cd5f )
by liu
02:29
created

Url::root()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
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;
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 App
26
     */
27
    protected $app;
28
29
    /**
30
     * 配置参数
31
     * @var array
32
     */
33
    protected $config = [];
34
35
    public function __construct(App $app, array $config = [])
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
36
    {
37
        $this->app    = $app;
38
        $this->config = $config;
39
40
        if (is_file($app->getRuntimePath() . 'route.php')) {
41
            // 读取路由映射文件
42
            $app->route->import(include $app->getRuntimePath() . 'route.php');
43
        }
44
    }
45
46
    /**
47
     * 初始化
48
     * @access public
49
     * @param  array $config 配置信息
50
     * @return void
51
     */
52
    public function init(array $config = [])
53
    {
54
        $this->config = array_merge($this->config, array_change_key_case($config));
55
    }
56
57
    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...
58
    {
59
        return new static($app, $route->config());
60
    }
61
62
    /**
63
     * URL生成 支持路由反射
64
     * @access public
65
     * @param  string       $url 路由地址
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
66
     * @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...
67
     * @param  string|bool  $suffix 伪静态后缀,默认为true表示获取配置值
68
     * @param  bool|string  $domain 是否显示域名 或者直接传入域名
69
     * @return string
70
     */
71
    public function build(string $url = '', $vars = [], $suffix = true, $domain = false): string
72
    {
73
        // 解析URL
74
        if (0 === strpos($url, '[') && $pos = strpos($url, ']')) {
75
            // [name] 表示使用路由命名标识生成URL
76
            $name = substr($url, 1, $pos - 1);
77
            $url  = 'name' . substr($url, $pos + 1);
78
        }
79
80
        if (false === strpos($url, '://') && 0 !== strpos($url, '/')) {
81
            $info = parse_url($url);
82
            $url  = !empty($info['path']) ? $info['path'] : '';
83
84
            if (isset($info['fragment'])) {
85
                // 解析锚点
86
                $anchor = $info['fragment'];
87
88
                if (false !== strpos($anchor, '?')) {
89
                    // 解析参数
90
                    list($anchor, $info['query']) = explode('?', $anchor, 2);
91
                }
92
93
                if (false !== strpos($anchor, '@')) {
94
                    // 解析域名
95
                    list($anchor, $domain) = explode('@', $anchor, 2);
96
                }
97
            } elseif (strpos($url, '@') && false === strpos($url, '\\')) {
98
                // 解析域名
99
                list($url, $domain) = explode('@', $url, 2);
100
            }
101
        }
102
103
        // 解析参数
104
        if (is_string($vars)) {
105
            // aaa=1&bbb=2 转换成数组
106
            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

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