Completed
Push — 2.1 ( 1e24d9...76e6a2 )
by Dmitry
66:20 queued 62:56
created

UrlManager::parseRequest()   C

Complexity

Conditions 12
Paths 29

Size

Total Lines 52
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 12.004

Importance

Changes 0
Metric Value
dl 0
loc 52
ccs 32
cts 33
cp 0.9697
rs 5.9842
c 0
b 0
f 0
cc 12
eloc 31
nc 29
nop 1
crap 12.004

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