Using search engine and user friendly URLs

You are viewing revision #4 of this wiki article.
This version may not be up to date with the latest version.
You may want to view the differences to the latest version or see the changes made in this revision.

« previous (#3)next (#5) »

  1. Configuring CUrlManager
  2. Hiding Entry Script from URL
  3. Keeping DRY (Don't Repeat Yourself)

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.

Asssume 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 */*
&lt;IfModule mod_rewrite.c&gt;
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
&lt;/IfModule&gt;

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.

Links

Chinese version

19 0
31 followers
Viewed: 105 168 times
Version: Unknown (update)
Category: Tutorials
Tags: SEO, URL
Written by: qiang
Last updated by: Yang He
Created on: Oct 23, 2009
Last updated: 12 years ago
Update Article

Revisions

View all history

Related Articles