Yii 1.1: Using search engine and user friendly URLs

35 followers

The Definitive Guide introduces the fundamentals of managing URLs in a Yii application. In this tutorial, we introduce a practical technique that can quickly turn your application into using search-engine-friendly URLs.

Assume we have an application that mainly consists of CRUD operations for several object types. For example, in the blog demo, we need CRUD operations for Post, Comment and User. Using Post as an example, our goal is to implement its CRUD operations with the following URLs:

  • reading a post: http://example.com/post/99/this+is+a+sample+post
  • listing posts: http://example.com/post
  • listing posts with pagination and sorting: http://example.com/post?page=2&sort=title
  • creating a post: http://example.com/post/create
  • updating a post: http://example.com/post/update?id=99
  • deleting a post: http://examplecom/post/delete?id=99

The above URLs are all very short and readable. For the "reading a post" URL, we append the post title to the URL because it is preferred by search engines which expect URLs themselves to provide some useful information when indexing the corresponding pages. The rest of the pages are less important for search engines because they provide less information. For this reason, we put all parameters in the query string part instead of path info.

Configuring CUrlManager

In order to accomplish the above URL formats, we need to configure the CUrlManager component in the application configuration as follows,

return array(
    'components'=>array(
        'urlManager'=>array(
            'urlFormat'=>'path',
            'showScriptName'=>false,
            'rules'=>array(
                '<controller:\w+>'=>'<controller>/list',
                '<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
                '<controller:\w+>/<id:\d+>/<title>'=>'<controller>/view',
                '<controller:\w+>/<id:\d+>'=>'<controller>/view',
            ),
        ),
    ),
);

In the above configuration, we first specify to use the path URL format which turns the usual query-string-based URLs into path-info-based ones (e.g. from /index.php?r=post/list to /index.php/post/list.) We then state that the entry script name should be hidden from the URLs. Last, we specify a list of URL formatting rules based on our earlier description.

Let's explain a bit more about the URL formatting rules.

In the first rule, we specify that if the path info of an incoming URL is a single word (e.g. post), then it should be treated as a controller ID and the corresponding route should be the controller ID with the list action. In our example, this means if the path info is post, it should be treated as the post/list route; if the path info is comment, it would be comment/list; and so on. On the other hand, when we create a URL by calling $controller->createUrl('post/list'), this rule would apply and we should obtain a URL /post. If we pass in additional GET parameters when creating the URL, they will appear in the query string part (e.g. /post?page=99, which may be generated by a pager widget).

In the second rule, we specify that if the path info of an incoming URL consists of two words separated by a slash, then they form the needed the route. For example, if the path info post/create, then this would also be the route to execute the request (i.e., the post controller and the create action). When we create a URL by calling $controller->createUrl('post/create'), this rule would apply and we should obtain a URL post/create. Any additional GET parameters would be appended to the query string part of the URL (e.g. /post/update?id=99).

The last two rules are for the reading URLs. One rule is for reading URLs with the title parameter, and one without.

Hiding Entry Script from URL

We need one more step in order to remove index.php from our URLs, i.e., configuring the Web server. For Apache HTTP server, as described in the Definitive Guide, we need to place a file named .htaccess under the directory containing the entry script. The file should have the following content:

Options +FollowSymLinks
IndexIgnore */*
<IfModule mod_rewrite.c>
RewriteEngine on

# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

# otherwise forward it to index.php
RewriteRule . index.php
</IfModule>

Please consult the user reference if you are using a different Web server.

Keeping DRY (Don't Repeat Yourself)

We often need to insert URLs pointing to object detail pages. For example, in the object list page, we need to link to the detail page for every object; In an object detail page, we may also need to link to the detail page for its related objects.

We can certainly call createUrl() in all relevant view scripts. However, in order to keep DRY, a better place to call this method is in the model classes. In each model class, we can add a url property which returns the result of calling createUrl(). Then, whenever we need the URL to the detail page of an object, we can readily obtain it using the expression $object->url.

The above approach can be further improved. That is, instead of implementing the url property in every model class, we do it in a base model class and have all concrete model classes to extend from this base class. Below is such an attempt,

class ActiveRecord extends CActiveRecord
{
    public function getUrl()
    {
        $controller=get_class($this);
        $controller[0]=strtolower($controller[0]);
        $params=array('id'=>$this->id);
        // add the title parameter to the URL
        if($this->hasAttribute('title'))
            $params['title']=$this->title;
        return Yii::app()->urlManager->createUrl($controller.'/view', $params);
    }
}
 
// class Post extends ActiveRecord { ... }

We can save this base class in a file named ActiveRecord.php and put it under the directory protected/components so that when we extend this class to write new model classes, we do not need to explicitly include this class file, thanks to the Yii class autoloading feature.

With the above approach, we can achieve very flexible URL schemes while keeping our code clean. If some day we want to change the format of the detail page URL (e.g., besides title, we also want to add category information into the URL), we can immediately accomplish this by updating only the getUrl() method in the base class. If, a model has a very special format for its detail page URL, we can override the getUrl() method in this model class.

Finally, it may also be a good idea to implement in the model classes the methods that return other type of URLs. For example, we can implement the getListUrl() method in the base class similar to what we do for getUrl(). Then we can readily obtain the list page URL for Post model using the expression Post::model()->listUrl.

Total 7 comments

#18090 report it
A M B Nishanth at 2014/09/06 02:39am
URL Manager for Modules

Hai All,

Please tell how to set URL rule to access modules/controller/method/params OR modules/controller/method

Thanks in advance

#10981 report it
Ziggi at 2012/12/07 06:31pm
What does this mean? - Helooo !!!

Hey guys - this is Yii wiki - please, explain your ideas precisely!

What does this mean:

"The last two rules are for the reading URLs."

Can you explain exactly what the following rule if for:

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

Can you provide some exhaustive list of possible markups? For instance, I often see:

'<view:\w+>' => 'site/page'

What is this going to do actually???

The routing explaination provided here is completely minimalistic - where are routing rules explained in a more comprehensive manner???

#5017 report it
Backslider at 2011/09/06 08:13pm
Mapping needs to be cached

@Chris - they are not a problem if you cache the data. Without cache would be silly.

#5016 report it
Chris83 at 2011/09/06 07:02pm
About SEO

In my experience the best would be to have URLs such as: http://example.com/this+is+a+sample+post-99 Because then you can skip the table. URL mapping tables can be a PITA because of performance.

#3859 report it
Backslider at 2011/05/15 03:04am
Could be better

To my mind, a URL such as: http://example.com/post/99/this+is+a+sample+post is not so hot. It places the content three levels deep as far as search engines are concerned.

Far better to have a URL such as: http://example.com/this+is+a+sample+post

This can only be achieved using .htacces and a dedicated seo_urls table that contains the request URL, controller, action and ID (if action requires an ID).

#2787 report it
Rangel Reale at 2011/02/10 07:34am
Configuration for lighttpd

For lighttpd, use this configuration inside the "$HTTP["host"]" block:

$HTTP["host"] =~ "^(www\.)?example.com$" {

$HTTP["url"] =~ "^/protected/" { url.access-deny = ( "" ) }

url.rewrite-if-not-file = (
  "^/(.*)\.(.*)" => "/$0",
  "^/([^.]+)$" => "/index.php/$1",
  "^/$" => "/index.php"
)

}
#1213 report it
ptoly at 2009/10/23 03:55pm
No actionView

In my 1.09 version of Yii there is no actionView, the actual function is actionShow so configuring the CUrlManager did not work on my app until I did

return array(
    'components'=>array(
        'urlManager'=>array(
            'urlFormat'=>'path',
            'showScriptName'=>false,
            'rules'=>array(
                '<controller:\w+>'=>'/list',
                '<controller:\w+>/<action:\w+>'=>'/',
                '<controller:\w+>/<id:\d+>/'=>'/show',
                '<controller:\w+>/<id:\d+>'=>'/show',
            ),
        ),
    ),
);

Leave a comment

Please to leave your comment.

Write new article
  • Written by: qiang
  • Updated by: Yang He
  • Category: Tutorials
  • Yii Version: 1.1
  • Votes: +23 / -1
  • Viewed: 62,082 times
  • Created on: Oct 23, 2009
  • Last updated: Jun 30, 2012
  • Tags: SEO, URL