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

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

6 7
The concept of *URL management* in Yii is fairly simple: the application simply uses 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 make site-wide changes to URLs merely by
editing a single configuration file, without ever touching any application code.
8

9
Internal Routes
Alexander Makarov committed
10
---------------
11

12 13 14 15 16 17 18 19 20
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 the *controller ID*, while `index` is 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` represents a blog module, the module's `post` 
controller, and the `index` action.
21 22 23 24

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

25 26
The most important rule for creating URLs in your site is to always do so through the URL manager. The URL manager is a built-in application component fittingly named `urlManager`. This component is accessible from both web and console applications via
`\Yii::$app->urlManager`. The component makes available two methods for creating URLs:
27

28 29
- `createUrl($params)`
- `createAbsoluteUrl($params, $schema = null)`
30

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

Both methods can be passed parameters used to further customize the URL, such as appending values to pass along as part of the request.
37

38
Some examples of these two methods:
39 40

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

49 50
The exact format of the resuting URL depends on how the URL manager is configured. The above
examples could also output:
51 52 53

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

59 60
In order to simplify URL creation, Yii has the [[yii\helpers\Url]] helper. Assuming the current URL is `/index.php?r=management/default/users&id=10`, the following
shows how the `Url` helper works:
61 62

```php
63 64
use yii\helpers\Url;

65 66 67
// currently active route
// /index.php?r=management/default/users
echo Url::to('');
68

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

73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
// 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();
97 98 99

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

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

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

```php
namespace app\controllers;

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

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

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

126 127 128
Customizing URLs
----------------

129
By default, Yii uses a query string format for URLs, such as `/index.php?r=news/view&id=100`. In order to make URLs
130
human-friendly (i.e., more legible), you need to configure the `urlManager` component in the application's configuration
131
file. Enabling "pretty" URLs will convert the query string format to a directory-based format: `/index.php/news/view?id=100`.
132 133 134


Disabling the `showScriptName` parameter further customizes the URL such that `index.php` will be omitted. Here's the relevant part of
135
the application's configuration file:
136 137 138 139

```php
<?php
return [
140 141 142 143 144 145 146
    // ...
    'components' => [
        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
        ],
    ],
147 148 149
];
```

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

153
### Named Parameters
154

Aris Karageorgos committed
155
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:
156 157 158 159 160

```
<ParameterName:ParameterPattern>
```

Aris Karageorgos committed
161
`ParameterName` is a name of a `GET` parameter, and the optional `ParameterPattern` is the regular expression that should
162
be used to match the value of the `GET` parameter. When `ParameterPattern` is omitted, it means the parameter
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.

166
Let's use some examples to explain how URL rules work. Asusuming that the rule set consists of three rules:
167 168 169

```php
[
170 171 172
    'posts'=>'post/list',
    'post/<id:\d+>'=>'post/read',
    'post/<year:\d{4}>/<title>'=>'post/read',
173 174 175
]
```

176 177 178
- 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
179
  `/index.php/post/2008/a%20sample%20post`. The third rule is applied.
180
- Calling `Url::toRoute('post/read')` generates `/index.php/post/read`. None of the rules is applied; convention is used instead.
181

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

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

190 191
As mentioned earlier, the other purpose of URL rules is to parse the requesting URLs. Naturally, this is the inverse 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 to the route `post/read` with the `GET` parameter `['id' => 100]` (accessible via
192
`Yii::$app->request->get('id')`).
193 194 195

### Parameterizing Routes

196 197
Rules may also make use of named parameters as part of a route. Named parameters allow a rule to be applied to multiple routes based
on matching criteria. Named parameters may also help reduce the number of rules needed for an application, and thus improve the overall
198 199
performance.

200
The following example rules illustrate how to parameterize routes with named parameters:
201 202 203

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

210
In the above example, two named parameters are found in the route part of the rules: `controller` and `action`. The former matches a controller ID that's either "post" or "comment", while the latter matches an action ID that could be "create", "update", or "delete". You may name the parameters differently as long as they do not conflict with any GET parameters that may appear in your URLs.
211

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

215
### Parameterizing Hostnames
216

Aris Karageorgos committed
217 218
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
219
`http://admin.example.com/en/profile` may be parsed into GET parameters `user=admin` and `lang=en`. On the other hand,
220
rules with hostnames may also be used to create URLs with parameterized hostnames.
221

222
In order to use parameterized hostnames, simply declare the URL rules while including the host info:
223 224 225

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

230 231
In the above example, the first segment of the hostname is treated as the "user" parameter while the first segment
of the pat is treated as the "lang" parameter. The rule corresponds to the `user/profile` route.
232

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

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

### Faking URL Suffix

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

253
### Handling REST requests
254

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

261 262 263 264

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

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

### Strict URL parsing

Aris Karageorgos committed
269
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.
270 271 272 273

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

Creating your own rule classes
------------------------------
285

286
[[yii\web\UrlRule]] class is used for both parsing URL into parameters and creating URL based on parameters. Despite
Aris Karageorgos committed
287
the fact that default implementation is flexible enough for the majority of projects, there are situations when using
288 289 290 291
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.

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

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

302
            // ...
303

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

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
314
namespace app\components;
315

Alexander Makarov committed
316
use yii\web\UrlRule;
317 318 319

class CarUrlRule extends UrlRule
{
320 321
    public $connectionID = 'db';

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

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
    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
    }
352 353 354 355 356 357 358 359 360
}
```

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.