Completed
Push — 2.1 ( c952e8...98ed49 )
by Carsten
10:00
created

UrlManager::getHostInfo()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3.0123

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
ccs 8
cts 9
cp 0.8889
cc 3
eloc 8
nc 3
nop 0
crap 3.0123
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\web;
9
10
use Yii;
11
use yii\base\Component;
12
use yii\base\InvalidConfigException;
13
use yii\caching\Cache;
14
15
/**
16
 * UrlManager handles HTTP request parsing and creation of URLs based on a set of rules.
17
 *
18
 * UrlManager is configured as an application component in [[\yii\base\Application]] by default.
19
 * You can access that instance via `Yii::$app->urlManager`.
20
 *
21
 * You can modify its configuration by adding an array to your application config under `components`
22
 * as it is shown in the following example:
23
 *
24
 * ```php
25
 * 'urlManager' => [
26
 *     'enablePrettyUrl' => true,
27
 *     'rules' => [
28
 *         // your rules go here
29
 *     ],
30
 *     // ...
31
 * ]
32
 * ```
33
 *
34
 * @property string $baseUrl The base URL that is used by [[createUrl()]] to prepend to created URLs.
35
 * @property string $hostInfo The host info (e.g. "http://www.example.com") that is used by
36
 * [[createAbsoluteUrl()]] to prepend to created URLs.
37
 * @property string $scriptUrl The entry script URL that is used by [[createUrl()]] to prepend to created
38
 * URLs.
39
 *
40
 * @author Qiang Xue <[email protected]>
41
 * @since 2.0
42
 */
43
class UrlManager extends Component
44
{
45
    /**
46
     * @var boolean whether to enable pretty URLs. Instead of putting all parameters in the query
47
     * string part of a URL, pretty URLs allow using path info to represent some of the parameters
48
     * and can thus produce more user-friendly URLs, such as "/news/Yii-is-released", instead of
49
     * "/index.php?r=news%2Fview&id=100".
50
     */
51
    public $enablePrettyUrl = false;
52
    /**
53
     * @var boolean whether to enable strict parsing. If strict parsing is enabled, the incoming
54
     * requested URL must match at least one of the [[rules]] in order to be treated as a valid request.
55
     * Otherwise, the path info part of the request will be treated as the requested route.
56
     * This property is used only when [[enablePrettyUrl]] is true.
57
     */
58
    public $enableStrictParsing = false;
59
    /**
60
     * @var array the rules for creating and parsing URLs when [[enablePrettyUrl]] is true.
61
     * This property is used only if [[enablePrettyUrl]] is true. Each element in the array
62
     * is the configuration array for creating a single URL rule. The configuration will
63
     * be merged with [[ruleConfig]] first before it is used for creating the rule object.
64
     *
65
     * A special shortcut format can be used if a rule only specifies [[UrlRule::pattern|pattern]]
66
     * and [[UrlRule::route|route]]: `'pattern' => 'route'`. That is, instead of using a configuration
67
     * array, one can use the key to represent the pattern and the value the corresponding route.
68
     * For example, `'post/<id:\d+>' => 'post/view'`.
69
     *
70
     * For RESTful routing the mentioned shortcut format also allows you to specify the
71
     * [[UrlRule::verb|HTTP verb]] that the rule should apply for.
72
     * You can do that  by prepending it to the pattern, separated by space.
73
     * For example, `'PUT post/<id:\d+>' => 'post/update'`.
74
     * You may specify multiple verbs by separating them with comma
75
     * like this: `'POST,PUT post/index' => 'post/create'`.
76
     * The supported verbs in the shortcut format are: GET, HEAD, POST, PUT, PATCH and DELETE.
77
     * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way
78
     * so you normally would not specify a verb for normal GET request.
79
     *
80
     * Here is an example configuration for RESTful CRUD controller:
81
     *
82
     * ```php
83
     * [
84
     *     'dashboard' => 'site/index',
85
     *
86
     *     'POST <controller:[\w-]+>s' => '<controller>/create',
87
     *     '<controller:[\w-]+>s' => '<controller>/index',
88
     *
89
     *     'PUT <controller:[\w-]+>/<id:\d+>'    => '<controller>/update',
90
     *     'DELETE <controller:[\w-]+>/<id:\d+>' => '<controller>/delete',
91
     *     '<controller:[\w-]+>/<id:\d+>'        => '<controller>/view',
92
     * ];
93
     * ```
94
     *
95
     * Note that if you modify this property after the UrlManager object is created, make sure
96
     * you populate the array with rule objects instead of rule configurations.
97
     */
98
    public $rules = [];
99
    /**
100
     * @var string the URL suffix used when in 'path' format.
101
     * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
102
     * This property is used only if [[enablePrettyUrl]] is true.
103
     */
104
    public $suffix;
105
    /**
106
     * @var boolean whether to show entry script name in the constructed URL. Defaults to true.
107
     * This property is used only if [[enablePrettyUrl]] is true.
108
     */
109
    public $showScriptName = true;
110
    /**
111
     * @var string the GET parameter name for route. This property is used only if [[enablePrettyUrl]] is false.
112
     */
113
    public $routeParam = 'r';
114
    /**
115
     * @var Cache|string the cache object or the application component ID of the cache object.
116
     * Compiled URL rules will be cached through this cache object, if it is available.
117
     *
118
     * After the UrlManager object is created, if you want to change this property,
119
     * you should only assign it with a cache object.
120
     * Set this property to false if you do not want to cache the URL rules.
121
     */
122
    public $cache = 'cache';
123
    /**
124
     * @var array the default configuration of URL rules. Individual rule configurations
125
     * specified via [[rules]] will take precedence when the same property of the rule is configured.
126
     */
127
    public $ruleConfig = ['class' => UrlRule::class];
128
129
    /**
130
     * @var string the cache key for cached rules
131
     * @since 2.0.8
132
     */
133
    protected $cacheKey = __CLASS__;
134
135
    private $_baseUrl;
136
    private $_scriptUrl;
137
    private $_hostInfo;
138
    private $_ruleCache;
139
140
141
    /**
142
     * Initializes UrlManager.
143
     */
144 31
    public function init()
145
    {
146 31
        parent::init();
147
148 31
        if (!$this->enablePrettyUrl || empty($this->rules)) {
149 26
            return;
150
        }
151 7
        if (is_string($this->cache)) {
152 1
            $this->cache = Yii::$app->get($this->cache, false);
153 1
        }
154 7
        if ($this->cache instanceof Cache) {
155
            $cacheKey = $this->cacheKey;
156
            $hash = md5(json_encode($this->rules));
157
            if (($data = $this->cache->get($cacheKey)) !== false && isset($data[1]) && $data[1] === $hash) {
158
                $this->rules = $data[0];
159
            } else {
160
                $this->rules = $this->buildRules($this->rules);
161
                $this->cache->set($cacheKey, [$this->rules, $hash]);
162
            }
163
        } else {
164 7
            $this->rules = $this->buildRules($this->rules);
165
        }
166 7
    }
167
168
    /**
169
     * Adds additional URL rules.
170
     *
171
     * This method will call [[buildRules()]] to parse the given rule declarations and then append or insert
172
     * them to the existing [[rules]].
173
     *
174
     * Note that if [[enablePrettyUrl]] is false, this method will do nothing.
175
     *
176
     * @param array $rules the new rules to be added. Each array element represents a single rule declaration.
177
     * Please refer to [[rules]] for the acceptable rule format.
178
     * @param boolean $append whether to add the new rules by appending them to the end of the existing rules.
179
     */
180
    public function addRules($rules, $append = true)
181
    {
182
        if (!$this->enablePrettyUrl) {
183
            return;
184
        }
185
        $rules = $this->buildRules($rules);
186
        if ($append) {
187
            $this->rules = array_merge($this->rules, $rules);
188
        } else {
189
            $this->rules = array_merge($rules, $this->rules);
190
        }
191
    }
192
193
    /**
194
     * Builds URL rule objects from the given rule declarations.
195
     * @param array $rules the rule declarations. Each array element represents a single rule declaration.
196
     * Please refer to [[rules]] for the acceptable rule formats.
197
     * @return UrlRuleInterface[] the rule objects built from the given rule declarations
198
     * @throws InvalidConfigException if a rule declaration is invalid
199
     */
200 7
    protected function buildRules($rules)
201
    {
202 7
        $compiledRules = [];
203 7
        $verbs = 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS';
204 7
        foreach ($rules as $key => $rule) {
205 7
            if (is_string($rule)) {
206 6
                $rule = ['route' => $rule];
207 6
                if (preg_match("/^((?:($verbs),)*($verbs))\\s+(.*)$/", $key, $matches)) {
208 1
                    $rule['verb'] = explode(',', $matches[1]);
209
                    // rules that do not apply for GET requests should not be use to create urls
210 1
                    if (!in_array('GET', $rule['verb'])) {
211 1
                        $rule['mode'] = UrlRule::PARSING_ONLY;
212 1
                    }
213 1
                    $key = $matches[4];
214 1
                }
215 6
                $rule['pattern'] = $key;
216 6
            }
217 7
            if (is_array($rule)) {
218 7
                $rule = Yii::createObject(array_merge($this->ruleConfig, $rule));
219 7
            }
220 7
            if (!$rule instanceof UrlRuleInterface) {
221
                throw new InvalidConfigException('URL rule class must implement UrlRuleInterface.');
222
            }
223 7
            $compiledRules[] = $rule;
224 7
        }
225 7
        return $compiledRules;
226
    }
227
228
    /**
229
     * Parses the user request.
230
     * @param Request $request the request component
231
     * @return array|boolean the route and the associated parameters. The latter is always empty
232
     * if [[enablePrettyUrl]] is false. False is returned if the current request cannot be successfully parsed.
233
     */
234 4
    public function parseRequest($request)
235
    {
236 4
        if ($this->enablePrettyUrl) {
237 4
            $pathInfo = $request->getPathInfo();
238
            /* @var $rule UrlRule */
239 4
            foreach ($this->rules as $rule) {
240 3
                if (($result = $rule->parseRequest($this, $request)) !== false) {
241 3
                    return $result;
242
                }
243 4
            }
244
245 2
            if ($this->enableStrictParsing) {
246 1
                return false;
247
            }
248
249 2
            Yii::trace('No matching URL rules. Using default URL parsing logic.', __METHOD__);
250
251
            // Ensure, that $pathInfo does not end with more than one slash.
252 2
            if (strlen($pathInfo) > 1 && substr_compare($pathInfo, '//', -2, 2) === 0) {
253 1
                return false;
254
            }
255
256 2
            $suffix = (string) $this->suffix;
257 2
            if ($suffix !== '' && $pathInfo !== '') {
258 2
                $n = strlen($this->suffix);
259 2
                if (substr_compare($pathInfo, $this->suffix, -$n, $n) === 0) {
260 2
                    $pathInfo = substr($pathInfo, 0, -$n);
261 2
                    if ($pathInfo === '') {
262
                        // suffix alone is not allowed
263
                        return false;
264
                    }
265 2
                } else {
266
                    // suffix doesn't match
267 1
                    return false;
268
                }
269 2
            }
270
271 2
            return [$pathInfo, []];
272
        } else {
273 1
            Yii::trace('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__);
274 1
            $route = $request->getQueryParam($this->routeParam, '');
275 1
            if (is_array($route)) {
276 1
                $route = '';
277 1
            }
278
279 1
            return [(string) $route, []];
280
        }
281
    }
282
283
    /**
284
     * Creates a URL using the given route and query parameters.
285
     *
286
     * You may specify the route as a string, e.g., `site/index`. You may also use an array
287
     * if you want to specify additional query parameters for the URL being created. The
288
     * array format must be:
289
     *
290
     * ```php
291
     * // generates: /index.php?r=site%2Findex&param1=value1&param2=value2
292
     * ['site/index', 'param1' => 'value1', 'param2' => 'value2']
293
     * ```
294
     *
295
     * If you want to create a URL with an anchor, you can use the array format with a `#` parameter.
296
     * For example,
297
     *
298
     * ```php
299
     * // generates: /index.php?r=site%2Findex&param1=value1#name
300
     * ['site/index', 'param1' => 'value1', '#' => 'name']
301
     * ```
302
     *
303
     * The URL created is a relative one. Use [[createAbsoluteUrl()]] to create an absolute URL.
304
     *
305
     * Note that unlike [[\yii\helpers\Url::toRoute()]], this method always treats the given route
306
     * as an absolute route.
307
     *
308
     * @param string|array $params use a string to represent a route (e.g. `site/index`),
309
     * or an array to represent a route with query parameters (e.g. `['site/index', 'param1' => 'value1']`).
310
     * @return string the created URL
311
     */
312 20
    public function createUrl($params)
313
    {
314 20
        $params = (array) $params;
315 20
        $anchor = isset($params['#']) ? '#' . $params['#'] : '';
316 20
        unset($params['#'], $params[$this->routeParam]);
317
318 20
        $route = trim($params[0], '/');
319 20
        unset($params[0]);
320
321 20
        $baseUrl = $this->showScriptName || !$this->enablePrettyUrl ? $this->getScriptUrl() : $this->getBaseUrl();
322
323 20
        if ($this->enablePrettyUrl) {
324 5
            $cacheKey = $route . '?';
325 5
            foreach ($params as $key => $value) {
326 4
                if ($value !== null) {
327 4
                    $cacheKey .= $key . '&';
328 4
                }
329 5
            }
330
331 5
            $url = $this->getUrlFromCache($cacheKey, $route, $params);
332
333 5
            if ($url === false) {
334 5
                $cacheable = true;
335 5
                foreach ($this->rules as $rule) {
336
                    /* @var $rule UrlRule */
337 5
                    if (!empty($rule->defaults) && $rule->mode !== UrlRule::PARSING_ONLY) {
338
                        // if there is a rule with default values involved, the matching result may not be cached
339 1
                        $cacheable = false;
340 1
                    }
341 5
                    if (($url = $rule->createUrl($this, $route, $params)) !== false) {
342 4
                        if ($cacheable) {
343 4
                            $this->setRuleToCache($cacheKey, $rule);
344 4
                        }
345 4
                        break;
346
                    }
347 5
                }
348 5
            }
349
350 5
            if ($url !== false) {
351 4
                if (strpos($url, '://') !== false) {
352 2
                    if ($baseUrl !== '' && ($pos = strpos($url, '/', 8)) !== false) {
353 2
                        return substr($url, 0, $pos) . $baseUrl . substr($url, $pos) . $anchor;
354
                    } else {
355
                        return $url . $baseUrl . $anchor;
356
                    }
357
                } else {
358 3
                    return "$baseUrl/{$url}{$anchor}";
359
                }
360
            }
361
362 2
            if ($this->suffix !== null) {
363 1
                $route .= $this->suffix;
364 1
            }
365 2
            if (!empty($params) && ($query = http_build_query($params)) !== '') {
366 2
                $route .= '?' . $query;
367 2
            }
368
369 2
            return "$baseUrl/{$route}{$anchor}";
370
        } else {
371 16
            $url = "$baseUrl?{$this->routeParam}=" . urlencode($route);
372 16
            if (!empty($params) && ($query = http_build_query($params)) !== '') {
373 14
                $url .= '&' . $query;
374 14
            }
375
376 16
            return $url . $anchor;
377
        }
378
    }
379
380
    /**
381
     * Get URL from internal cache if exists
382
     * @param string $cacheKey generated cache key to store data.
383
     * @param string $route the route (e.g. `site/index`).
384
     * @param array $params rule params.
385
     * @return boolean|string the created URL
386
     * @see createUrl()
387
     * @since 2.0.8
388
     */
389 5
    protected function getUrlFromCache($cacheKey, $route, $params)
390
    {
391 5
        if (!empty($this->_ruleCache[$cacheKey])) {
392 2
            foreach ($this->_ruleCache[$cacheKey] as $rule) {
393
                /* @var $rule UrlRule */
394 2
                if (($url = $rule->createUrl($this, $route, $params)) !== false) {
395 2
                    return $url;
396
                }
397
            }
398
        } else {
399 5
            $this->_ruleCache[$cacheKey] = [];
400
        }
401 5
        return false;
402
    }
403
404
    /**
405
     * Store rule (e.g. [[UrlRule]]) to internal cache
406
     * @param $cacheKey
407
     * @param UrlRuleInterface $rule
408
     * @since 2.0.8
409
     */
410 4
    protected function setRuleToCache($cacheKey, UrlRuleInterface $rule)
411
    {
412 4
        $this->_ruleCache[$cacheKey][] = $rule;
413 4
    }
414
415
    /**
416
     * Creates an absolute URL using the given route and query parameters.
417
     *
418
     * This method prepends the URL created by [[createUrl()]] with the [[hostInfo]].
419
     *
420
     * Note that unlike [[\yii\helpers\Url::toRoute()]], this method always treats the given route
421
     * as an absolute route.
422
     *
423
     * @param string|array $params use a string to represent a route (e.g. `site/index`),
424
     * or an array to represent a route with query parameters (e.g. `['site/index', 'param1' => 'value1']`).
425
     * @param string $scheme the scheme to use for the url (either `http` or `https`). If not specified
426
     * the scheme of the current request will be used.
427
     * @return string the created URL
428
     * @see createUrl()
429
     */
430 6
    public function createAbsoluteUrl($params, $scheme = null)
431
    {
432 6
        $params = (array) $params;
433 6
        $url = $this->createUrl($params);
434 6
        if (strpos($url, '://') === false) {
435 5
            $url = $this->getHostInfo() . $url;
436 5
        }
437 6
        if (is_string($scheme) && ($pos = strpos($url, '://')) !== false) {
438 4
            $url = $scheme . substr($url, $pos);
439 4
        }
440
441 6
        return $url;
442
    }
443
444
    /**
445
     * Returns the base URL that is used by [[createUrl()]] to prepend to created URLs.
446
     * It defaults to [[Request::baseUrl]].
447
     * This is mainly used when [[enablePrettyUrl]] is true and [[showScriptName]] is false.
448
     * @return string the base URL that is used by [[createUrl()]] to prepend to created URLs.
449
     * @throws InvalidConfigException if running in console application and [[baseUrl]] is not configured.
450
     */
451 3
    public function getBaseUrl()
452
    {
453 3
        if ($this->_baseUrl === null) {
454 1
            $request = Yii::$app->getRequest();
455 1
            if ($request instanceof Request) {
456 1
                $this->_baseUrl = $request->getBaseUrl();
457 1
            } else {
458
                throw new InvalidConfigException('Please configure UrlManager::baseUrl correctly as you are running a console application.');
459
            }
460 1
        }
461
462 3
        return $this->_baseUrl;
463
    }
464
465
    /**
466
     * Sets the base URL that is used by [[createUrl()]] to prepend to created URLs.
467
     * This is mainly used when [[enablePrettyUrl]] is true and [[showScriptName]] is false.
468
     * @param string $value the base URL that is used by [[createUrl()]] to prepend to created URLs.
469
     */
470 11
    public function setBaseUrl($value)
471
    {
472 11
        $this->_baseUrl = $value === null ? null : rtrim($value, '/');
473 11
    }
474
475
    /**
476
     * Returns the entry script URL that is used by [[createUrl()]] to prepend to created URLs.
477
     * It defaults to [[Request::scriptUrl]].
478
     * This is mainly used when [[enablePrettyUrl]] is false or [[showScriptName]] is true.
479
     * @return string the entry script URL that is used by [[createUrl()]] to prepend to created URLs.
480
     * @throws InvalidConfigException if running in console application and [[scriptUrl]] is not configured.
481
     */
482 19
    public function getScriptUrl()
483
    {
484 19
        if ($this->_scriptUrl === null) {
485 4
            $request = Yii::$app->getRequest();
486 4
            if ($request instanceof Request) {
487 4
                $this->_scriptUrl = $request->getScriptUrl();
488 4
            } else {
489
                throw new InvalidConfigException('Please configure UrlManager::scriptUrl correctly as you are running a console application.');
490
            }
491 4
        }
492
493 19
        return $this->_scriptUrl;
494
    }
495
496
    /**
497
     * Sets the entry script URL that is used by [[createUrl()]] to prepend to created URLs.
498
     * This is mainly used when [[enablePrettyUrl]] is false or [[showScriptName]] is true.
499
     * @param string $value the entry script URL that is used by [[createUrl()]] to prepend to created URLs.
500
     */
501 17
    public function setScriptUrl($value)
502
    {
503 17
        $this->_scriptUrl = $value;
504 17
    }
505
506
    /**
507
     * Returns the host info that is used by [[createAbsoluteUrl()]] to prepend to created URLs.
508
     * @return string the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend to created URLs.
509
     * @throws InvalidConfigException if running in console application and [[hostInfo]] is not configured.
510
     */
511 7
    public function getHostInfo()
512
    {
513 7
        if ($this->_hostInfo === null) {
514 1
            $request = Yii::$app->getRequest();
515 1
            if ($request instanceof \yii\web\Request) {
516 1
                $this->_hostInfo = $request->getHostInfo();
517 1
            } else {
518
                throw new InvalidConfigException('Please configure UrlManager::hostInfo correctly as you are running a console application.');
519
            }
520 1
        }
521
522 7
        return $this->_hostInfo;
523
    }
524
525
    /**
526
     * Sets the host info that is used by [[createAbsoluteUrl()]] to prepend to created URLs.
527
     * @param string $value the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend to created URLs.
528
     */
529 8
    public function setHostInfo($value)
530
    {
531 8
        $this->_hostInfo = $value === null ? null : rtrim($value, '/');
532 8
    }
533
}
534