runtime-url-handling.md 14.5 KB
Newer Older
1 2
URL Parsing and Generation
==========================
Alexander Makarov committed
3

4
> Note: This section is under development.
Qiang Xue committed
5

Aris Karageorgos committed
6
The concept of URL management in Yii is fairly simple. URL management is based on the premise that the application uses
Alexander Makarov committed
7
internal routes and parameters everywhere. The framework itself will then translate routes into URLs, and vice versa, according to the URL manager's configuration. This approach allows you to change site-wide URLs merely by
Aris Karageorgos committed
8
editing a single configuration file, without ever touching the application code.
9

Aris Karageorgos committed
10
Internal routes
Alexander Makarov committed
11
---------------
12

Aris Karageorgos committed
13 14 15 16 17
When implementing an application using Yii, you'll deal with internal routes, often referred to as routes and parameters.
Each controller and controller action has a corresponding internal route such as `site/index` or `user/create`.
In the first example, `site` is referred to as the *controller ID* while `index` is referred to as the *action ID*. In the
second example, `user` is the controller ID and `create` is the action ID. If the controller belongs to a *module*, the
internal route is prefixed with the module ID. For example `blog/post/index` for a blog module (with `post` being the
18
controller ID and `index` being the action ID).
19 20 21 22

Creating URLs
-------------

Aris Karageorgos committed
23
The most important rule for creating URLs in your site is to always do so using the URL manager. The URL manager is a built-in application component named `urlManager`. This component is accessible from both web and console applications via
24
`\Yii::$app->urlManager`. The component makes available the two following URL creation methods:
25

26 27
- `createUrl($params)`
- `createAbsoluteUrl($params, $schema = null)`
28

Alexander Makarov committed
29 30
The `createUrl` method creates an URL relative to the application root, such as `/index.php/site/index/`.
The `createAbsoluteUrl` method creates an URL prefixed with the proper protocol and hostname:
31
`http://www.example.com/index.php/site/index`. The former is suitable for internal application URLs, while the latter
Alexander Makarov committed
32 33
is used when you need to create URLs for external resources, such as connecting to third party services, sending email,
generating RSS feeds etc.
34 35 36 37

Some examples:

```php
38
echo \Yii::$app->urlManager->createUrl(['site/page', 'id' => 'about']);
39
// /index.php/site/page/id/about/
40
echo \Yii::$app->urlManager->createUrl(['date-time/fast-forward', 'id' => 105])
Mark committed
41
// /index.php?r=date-time/fast-forward&id=105
42
echo \Yii::$app->urlManager->createAbsoluteUrl('blog/post/index');
43
// http://www.example.com/index.php/blog/post/index/
44 45
```

Aris Karageorgos committed
46
The exact format of the URL depends on how the URL manager is configured. The above
47
examples may also output:
48 49 50

* `/site/page/id/about/`
* `/index.php?r=site/page&id=about`
Mark committed
51
* `/index.php?r=date-time/fast-forward&id=105`
Mark committed
52
* `/index.php/date-time/fast-forward?id=105`
53 54 55
* `http://www.example.com/blog/post/index/`
* `http://www.example.com/index.php?r=blog/post/index`

56 57
In order to simplify URL creation there is [[yii\helpers\Url]] helper. Assuming we're at `/index.php?r=management/default/users&id=10` the following
is how `Url` helper works:
58 59

```php
60 61
use yii\helpers\Url;

62 63 64
// currently active route
// /index.php?r=management/default/users
echo Url::to('');
65

66
// same controller, different action
Alexander Makarov committed
67
// /index.php?r=management/default/page&id=contact
68
echo Url::toRoute(['page', 'id' => 'contact']);
69 70


71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
// same module, different controller and action
// /index.php?r=management/post/index
echo Url::toRoute('post/index');

// absolute route no matter what controller is making this call
// /index.php?r=site/index
echo Url::toRoute('/site/index');

// url for the case sensitive action `actionHiTech` of the current controller
// /index.php?r=management/default/hi-tech
echo Url::toRoute('hi-tech');

// url for action the case sensitive controller, `DateTimeController::actionFastForward`
// /index.php?r=date-time/fast-forward&id=105
echo Url::toRoute(['/date-time/fast-forward', 'id' => 105]);

// get URL from alias
// http://google.com/
Yii::setAlias('@google', 'http://google.com/');
echo Url::to('@google');

// get home URL
// /index.php?r=site/index
echo Url::home();
95 96 97

Url::remember(); // save URL to be used later
Url::previous(); // get previously saved URL
98 99 100
```

> **Tip**: In order to generate URL with a hashtag, for example `/index.php?r=site/page&id=100#title`, you need to
101
  specify the parameter named `#` using `Url::to(['post/read', 'id' => 100, '#' => 'title'])`.
102

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
There's also `Url::canonical()` method that allows you to generate
[canonical URL](https://en.wikipedia.org/wiki/Canonical_link_element) for the currently executing action.
The method ignores all action parameters except ones passed via action arguments:

```php
namespace app\controllers;

use yii\web\Controller;
use yii\helpers\Url;

class CanonicalController extends Controller
{
    public function actionTest($page)
    {
        echo Url::canonical();
    }
}
```

When accessed as `/index.php?r=canonical/test&page=hello&number=42` canonical URL will be `/index.php?r=canonical/test&page=hello`.

124 125 126
Customizing URLs
----------------

127
By default, Yii uses a query string format for URLs, such as `/index.php?r=news/view&id=100`. In order to make URLs
Aris Karageorgos committed
128
human-friendly i.e., more readable, you need to configure the `urlManager` component in the application's configuration
129 130 131
file. Enabling "pretty" URLs will convert the query string format to a directory-based format: `/index.php/news/view?id=100`.
Disabling the `showScriptName` parameter means that `index.php` will not be part of the URLs. Here's the relevant part of
the application's configuration file:
132 133 134 135

```php
<?php
return [
136 137 138 139 140 141 142
    // ...
    'components' => [
        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
        ],
    ],
143 144 145
];
```

146
Note that this configuration will only work if the web server has been properly configured for Yii, see
Carsten Brandt committed
147
[installation](start-installation.md#recommended-apache-configuration).
148 149 150

### Named parameters

Aris Karageorgos committed
151
A rule can be associated with a few `GET` parameters. These `GET` parameters appear in the rule's pattern as special tokens in the following format:
152 153 154 155 156

```
<ParameterName:ParameterPattern>
```

Aris Karageorgos committed
157 158
`ParameterName` is a name of a `GET` parameter, and the optional `ParameterPattern` is the regular expression that should
be used to match the value of the `GET` parameter. In case `ParameterPattern` is omitted, it means the parameter
159 160 161 162 163 164 165
should match any characters except `/`. When creating a URL, these parameter tokens will be replaced with the
corresponding parameter values; when parsing a URL, the corresponding GET parameters will be populated with the parsed results.

Let's use some examples to explain how URL rules work. We assume that our rule set consists of three rules:

```php
[
166 167 168
    'posts'=>'post/list',
    'post/<id:\d+>'=>'post/read',
    'post/<year:\d{4}>/<title>'=>'post/read',
169 170 171
]
```

172 173 174
- Calling `Url::toRoute('post/list')` generates `/index.php/posts`. The first rule is applied.
- Calling `Url::toRoute(['post/read', 'id' => 100])` generates `/index.php/post/100`. The second rule is applied.
- Calling `Url::toRoute(['post/read', 'year' => 2008, 'title' => 'a sample post'])` generates
175
  `/index.php/post/2008/a%20sample%20post`. The third rule is applied.
176
- Calling `Url::toRoute('post/read')` generates `/index.php/post/read`. None of the rules is applied, convention is used
177 178
  instead.

Aris Karageorgos committed
179 180
In summary, when using `createUrl` to generate a URL, the route and the `GET` parameters passed to the method are used to
decide which URL rule to be applied. If every parameter associated with a rule can be found in the `GET` parameters passed
181 182
to `createUrl`, and if the route of the rule also matches the route parameter, the rule will be used to generate the URL.

183 184 185
If the `GET` parameters passed to `Url::toRoute` are more than those required by a rule, the additional parameters will
appear in the query string. For example, if we call `Url::toRoute(['post/read', 'id' => 100, 'year' => 2008])`, we will
obtain `/index.php/post/100?year=2008`.
186

187 188 189 190
As we mentioned earlier, the other purpose of URL rules is to parse the requesting URLs. Naturally, this is an inverse
process of URL creation. For example, when a user requests for `/index.php/post/100`, the second rule in the above example
will apply, which resolves in the route `post/read` and the `GET` parameter `['id' => 100]` (accessible via
`Yii::$app->request->get('id')`).
191 192 193 194 195 196 197 198 199 200 201

### Parameterizing Routes

We may reference named parameters in the route part of a rule. This allows a rule to be applied to multiple routes based
on matching criteria. It may also help reduce the number of rules needed for an application, and thus improve the overall
performance.

We use the following example rules to illustrate how to parameterize routes with named parameters:

```php
[
202 203 204
    '<controller:(post|comment)>/<id:\d+>/<action:(create|update|delete)>' => '<controller>/<action>',
    '<controller:(post|comment)>/<id:\d+>' => '<controller>/read',
    '<controller:(post|comment)>s' => '<controller>/list',
205 206 207
]
```

Aris Karageorgos committed
208
In the above example, we use two named parameters in the route part of the rules: `controller` and `action`. The former matches a controller ID to be either post or comment, while the latter matches an action ID to be create, update or delete. You may name the parameters differently as long as they do not conflict with GET parameters that may appear in URLs.
209

Aris Karageorgos committed
210 211
Using the above rules, the URL `/index.php/post/123/create` will be parsed as the route `post/create` with `GET` parameter
`id=123`. Given the route `comment/list` and `GET` parameter `page=2`, we can create a URL `/index.php/comments?page=2`.
212 213 214

### Parameterizing  hostnames

Aris Karageorgos committed
215 216
It is also possible to include hostnames in the rules for parsing and creating URLs. One may extract part of the hostname
to be a `GET` parameter. This is especially useful for handling subdomains. For example, the URL
217 218 219 220 221 222 223
`http://admin.example.com/en/profile` may be parsed into GET parameters `user=admin` and `lang=en`. On the other hand,
rules with hostname may also be used to create URLs with parameterized hostnames.

In order to use parameterized hostnames, simply declare URL rules with host info, e.g.:

```php
[
224
    'http://<user:\w+>.example.com/<lang:\w+>/profile' => 'user/profile',
225 226 227
]
```

Aris Karageorgos committed
228 229
In the above example the first segment of the hostname is treated as the user parameter while the first segment
of the path info is treated as the lang parameter. The rule corresponds to the `user/profile` route.
230

231
Note that [[yii\web\UrlManager::showScriptName]] will not take effect when a URL is being created using a rule with a parameterized hostname.
232

Aris Karageorgos committed
233 234 235
Also note that any rule with a parameterized hostname should NOT contain the subfolder if the application is under
a subfolder of the Web root. For example, if the application is under `http://www.example.com/sandbox/blog`, then we
should still use the same URL rule as described above without the subfolder `sandbox/blog`.
236 237 238 239 240 241

### Faking URL Suffix

```php
<?php
return [
242 243 244 245 246 247
    // ...
    'components' => [
        'urlManager' => [
            'suffix' => '.html',
        ],
    ],
248 249 250
];
```

251
### Handling REST requests
252

253
TBD:
MarsuBoss committed
254
- RESTful routing: [[yii\filters\VerbFilter]], [[yii\web\UrlManager::$rules]]
255
- Json API:
256 257
  - response: [[yii\web\Response::format]]
  - request: [[yii\web\Request::$parsers]], [[yii\web\JsonParser]]
258

259 260 261 262

URL parsing
-----------

Aris Karageorgos committed
263
Complimentary to creating URLs Yii also handles transforming custom URLs back into internal routes and parameters.
264 265 266

### Strict URL parsing

Aris Karageorgos committed
267
By default if there's no custom rule for a URL and the URL matches the default format such as `/site/page`, Yii tries to run the corresponding controller's action. This behavior can be disabled so if there's no custom rule match, a 404 not found error will be produced immediately.
268 269 270 271

```php
<?php
return [
272 273 274 275 276 277
    // ...
    'components' => [
        'urlManager' => [
            'enableStrictParsing' => true,
        ],
    ],
278 279 280 281 282
];
```

Creating your own rule classes
------------------------------
283

284
[[yii\web\UrlRule]] class is used for both parsing URL into parameters and creating URL based on parameters. Despite
Aris Karageorgos committed
285
the fact that default implementation is flexible enough for the majority of projects, there are situations when using
286 287 288 289
your own rule class is the best choice. For example, in a car dealer website, we may want to support the URL format like
`/Manufacturer/Model`, where `Manufacturer` and `Model` must both match some data in a database table. The default rule
class will not work because it mostly relies on statically declared regular expressions which have no database knowledge.

290
We can write a new URL rule class by extending from [[yii\web\UrlRule]] and use it in one or multiple URL rules. Using
291 292 293 294 295
the above car dealer website as an example, we may declare the following URL rules in application config:

```php
// ...
'components' => [
296 297 298
    'urlManager' => [
        'rules' => [
            '<action:(login|logout|about)>' => 'site/<action>',
299

300
            // ...
301

302 303 304
            ['class' => 'app\components\CarUrlRule', 'connectionID' => 'db', /* ... */],
        ],
    ],
305 306 307 308 309 310 311
],
```

In the above, we use the custom URL rule class `CarUrlRule` to handle
the URL format `/Manufacturer/Model`. The class can be written like the following:

```php
Alexander Makarov committed
312
namespace app\components;
313

Alexander Makarov committed
314
use yii\web\UrlRule;
315 316 317

class CarUrlRule extends UrlRule
{
318 319
    public $connectionID = 'db';

320 321 322 323 324 325 326
    public function init()
    {
        if ($this->name === null) {
            $this->name = __CLASS__;
        }
    }

327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
    public function createUrl($manager, $route, $params)
    {
        if ($route === 'car/index') {
            if (isset($params['manufacturer'], $params['model'])) {
                return $params['manufacturer'] . '/' . $params['model'];
            } elseif (isset($params['manufacturer'])) {
                return $params['manufacturer'];
            }
        }
        return false;  // this rule does not apply
    }

    public function parseRequest($manager, $request)
    {
        $pathInfo = $request->getPathInfo();
        if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches)) {
            // check $matches[1] and $matches[3] to see
            // if they match a manufacturer and a model in the database
            // If so, set $params['manufacturer'] and/or $params['model']
            // and return ['car/index', $params]
        }
        return false;  // this rule does not apply
    }
350 351 352 353 354 355 356 357 358
}
```

Besides the above usage, custom URL rule classes can also be implemented
for many other purposes. For example, we can write a rule class to log the URL parsing
and creation requests. This may be useful during development stage. We can also
write a rule class to display a special 404 error page in case all other URL rules fail
to resolve the current request. Note that in this case, the rule of this special class
must be declared as the last rule.