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.
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 */*
<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
.
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
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" ) }
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).
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.
Mapping needs to be cached
@Chris - they are not a problem if you cache the data. Without cache would be silly.
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???
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
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.