Understanding Routes in the Yii Framework

February 18, 2013
The Yii Book If you like my writing on the Yii framework, you'll love "The Yii Book"!

A topic critical to controllers, although not dictated within the actual controller code are routes. Routes are how URLs map to the controller and action to be invoked. Chapter 3, “A Manual for Your Yii Site,” introduced the basic concept and Chapter 4 explained how to configure the “urlManager” component to change how routes are formatted. Let’s now look at the topic in greater detail.

This is an excerpt from Chapter 7, “Working with Controllers,” of “The Yii Book“.

Path vs. Get

URLs in Yii are going to be in one of two formats:

http://www.example.com/index.php?r=ControllerID/ActionID

or

http://www.example.com/index.php/ControllerID/ActionID/

The first format is the default, and is called the “get” format, as values are passed as if they were standard GET variables (because, well, they are). The second format is the “path” format, in which the values appear as if they are part of the path (i.e., as if they map to directories on the file system). As explained in Chapter 4, this format is enabled by uncommenting the appropriate part of the configuration file:

# protected/config/main.php
'components'=>array(
    'urlManager'=>array(
        'urlFormat'=>'path',
    // Etc.

This is fairly basic stuff and is probably well ingrained to you by now. The important part of this concept is how you define the rules for dictating the paths.

Note that, for simplicity sake, I’m going to assume you’re using the path format from here on out. Further, the rules apply to the “path” part of the URL: that after the schema (e.g., http or https) and the domain (e.g., www.example.com/). So in the rest of this explanation, I’ll generally begin my demonstration URLs without the assumed http://www.example.com/.

{TIP} You can actually make your rules apply to the whole URL, including the domain and/or subdomain. See the class docs for the CurlManager class for an example.

Route Rules

These are the default rules set in the configuration file:

'rules'=>array(
    '<controller:\w+>/<id:\d+>' => '/view',
    '<controller:\w+>/<action:\w+>/<id:\d+>' => '/',
    '<controller:\w+>/<action:\w+>' => '/',
),

What exactly is going on there and, more importantly, how do you understand these rules enough for you to be able to implement your own?

The “urlManager” rules apply both when reading in a URL and when creating a URL (e.g., as a link). The rules serve two purposes: identifying the route–the controller and action–and reading in or passing along any parameters.

Each rule uses a name=>value pair to associate a value with a route. For example, here’s a simple rule: 'home' => 'site/index'. With that rule in place, the URL http://www.example.com/home will call the “index” action of the “site” controller. And similarly, if you go to create a URL to the “site/index” route, Yii will set that URL as http://www.example.com/home.

Obviously hardcoding literal strings to routes has limited appeal. To make your rules more flexible, apply regular expressions and named placeholders as the values. That syntax is: . You do need to understand regular expressions to follow this approach.

As an example, let’s return to the last rule defined by default in the configuration file:

'<controller:\w+>/<action:\w+>' => '/',

The very first part of that is . That means the rule is looking for what’s called a regular expression “word”, represented by \w+. In plainer English, that particular regular expression looks for a string of one or more alphanumeric characters. Once found, the rule labels that combination as controller.

Next, the rule looks for a literal slash.

Next, the rule looks for another “word”: \w+. Once found, the rule labels that combination as action.

The labels–the named placeholders–can then be used to establish the route. (Think of this like back referencing in regular expressions, if you’re familiar with that concept.)

This rule therefore equates a URL of anyword1/anyword2 with the “anyword1/anyword2” route. Hopefully, that literal association makes sense, but remember that this is a flexible literal association, unlike the hardcoded one shown earlier.

{TIP} You may have an easier time understanding routes and regular expressions if you’re familiar with using Apache’s mod_rewrite tool, which is somewhat similar.

Handling Parameters

Once you understand the concepts I’ve covered thus far, there’s one new twist to introduce: parameters. Many actions will require them, such as the “view” and “update” actions that need to accept the ID value of the record being viewed or updated. Those particular values will always be integers, you can use the \d+ regular expression pattern to match them. That’s what’s going on in the first rule:

'<controller:\w+>/<id:\d+>' => '/view',

That rule looks for a “word”, followed by a slash, followed by one or more digits. The digits part is a named parameter, labelled “id”. So page/42 is associated with the route “page/view”. But what about the 42? That value is not part of the actual route: it’s neither a controller nor an action. Where does it go? Well, it will be passed as a parameter to the action:

# protected/controllers/PageController.php
public function actionView($id) {
    // Etc.

For Yii to execute the “view” action, it invokes that method, passing along the parameter. So in this particular case, the route will be “page/view” but the actionView() method of the “page” controller will also be able to use $id, which will have a value of 42. There’s one little hitch…

Normally, it does not matter what names you give your parameters in a function:

function test($x) {}
$z = 23;
test($z); // No problem.
$x = 42;
test($x); // Still fine.

However, when you’ve identified a parameter in a rule that’s not part of the route, it will only be passed to an action if that action’s parameter is named the same. The earlier example code works, but this action definition with that same rule will throw an exception:

# protected/controllers/PageController.php
public function actionView($x) {
    // Etc.

{NOTE} Placeholders, as I’m calling them, and parameters are created in a rule in exactly the same way, I’m just using two different terms to distinguish between matches that will be used in routes (i.e., placeholders) and those passed to action methods (i.e., parameters).

Taking this a step further, let’s say you want to support two parameters. For example, you have a user verification process, wherein the user clicks a link in an email that takes them back to the site to verify the account. The link should pass two values along in the URL to uniquely identify the user: a number and a hash (a string of characters). The rule to catch that could be:

# protected/config/main.php
'verify/<x:\d+>/<y:\w+>' => 'user/verify',

That rule says that the literal word “verify”, followed by a slash, followed by a digit, followed by another slash, followed by a regular expression “word” should be routed to “user/verify”. With the named parameters, the actionVerify() method should be written to accept two parameters, $x and $y:

# protected/controllers/UserController.php
public function actionVerify($x, $y) {
    // Etc.

Strange as it may seem, you can put those parameters in either order, and it will still work. You can even write the method with just one or no parameters and the the action can still be invoked without error (although the action would not be passed both parameters in that case). Really, the only thing you can’t do is define the method to take parameters with different names than those in the rule.

As one more example of this, to really hammer the point home, let’s say you want a way to view a user’s profile by name: user/myUserName, user/yourUserName, etc. That rule could be:

# protected/config/main.php
'user/<username:\w+>' => 'user/view',

That rule associates the “user/view” route with that value, and creates a named parameter of “username”. (Note that the regular expression pattern for matching the actual username will depend upon what characters you allow in a username.)

Now for the action, which would presumably retrieve the user’s profile from the database using the username:

# protected/controllers/UserController.php
public function actionView($username) {
    // Etc.

Looking at the last of the default rules created by yiic, the “edit” and “update” actions are handled by this one:

'<controller:\w+>/<action:\w+>/<id:\d+>' => '/',

That rule would catch page/edit/42 or page/delete/42 (as well as page/view/42). And, again, each action should be defined so that it takes a parameter specifically named $id.

Understand that the rules are tested in order from top down, the first rule that constitutes a match will be equated to its route. Also, from both a comprehension standpoint, and for better performance, you should try to have as few rules as possible.

Case Sensitivity

Before getting into a couple more examples, I should clarify that routing rules are case-sensitive by default. This means that although the regular expression \w+ will match Post, post, or pOsT, only post will match a controller in your application.

As explained previously, the controller ID is the name of the controller class minus the “Controller” part, with the first letter in lowercase. Thus, SiteController becomes “site”, but SomeTypeController would become “someType”. The same is true for actions, except you start by dropping the initial “action” part.

{TIP} Routes can be made to be case-insensitive by setting the CUrlManager class’s caseSenstive property to false in your configuration file. Still, I think it best to treat them as case-sensitive.

Creating URLs

As I said at the beginning of this discussion, the routing rules come into play when parsing URLs into routes and also when creating URLs based upon routes. You should always have Yii create URLs to any page that gets run through the bootstrap file. This is done using either the createUrl() or the createAbsoluteUrl() method of the CController class.

Within a controller, or within its view files, you can access it via $this. Note that both methods just create and return a URL, not an HTML link!

The first argument to both is a string indicating the route. The current controller and action are assumed, so $this->createUrl('') returns the URL for the current page.

If you just provide an action ID, you’ll get the URL for that action of the current controller:

# protected/controllers/PageController.php
public function actionDummy() {
    $url = $this->createUrl('index'); // page/index

If you provide a route, the URL will be to that route:

# protected/controllers/PageController.php
public function actionDummy() {
    $url = $this->createUrl('user/index'); // user/index

If the URL expects named parameters to be passed, add those in an array as the second argument:

# protected/controllers/PageController.php
public function actionDummy() {
    $url = $this->createUrl('view', array('id' => 42)); // page/view id=42

To really hammer home the point, for that URL to work, the actionView() method of the controller should be written to accept an $id parameter, which presumably also matches the corresponding rule.

You can also create URLs through Yii::app():

$url = Yii::app()->createUrl('page/view', array('id' => 42));

The main difference between the two versions of the createUrl() method is that the CController version can be used without referencing a controller ID, whereas the Yii::app() version (i.e., that defined in CApplication) requires a controller ID be provided.