URL 網址管理
==============

網路應用程式完整的 URL 管理包括兩個方面：

1. When a user request comes in terms of a URL, the application needs to parse
it into understandable parameters.
2. The application needs to provide a way of creating URLs so that the
created URLs can be understood by the application.

1. 當使用者請求一個 URL，應用程式程式需要解析它變成可以理解的參數。
2. 應用程式程式需求提供一種創造 URL 的方法，以便建立的 URL 應用程式程式可以理解的。

對於Yii應用程式程式，這些透過 [CUrlManager] 輔助完成。


建立 URL
-------------

雖然URL可被寫死在控制器的視圖文件，但往往可以很靈活地動態建立它們：

~~~
[php]
$url=$this->createUrl($route,$params);
~~~

`$this` 指的是控制器實體；`$route` 指定請求的 [route](/doc/guide/basics.controller#route) 的要求；`$params` 列出了附加在網址中的 `GET` 參數。

預設情況下，URL 以 `get` 格式使用 [createUrl|CController::createUrl] 建立。例如，提供 `$route='post/read'` 和 `$params=array('id'=>100)`，我們將獲得以下網址：

~~~
/index.php?r=post/read&id=100
~~~

參數以一系列 `Name=Value` 通過符號串聯起來出現在請求字串，`r` 參數指的是請求的 [route](/doc/guide/basics.controller#route)。這種 URL 格式使用者友善不是很好，因為它需要一些非字字符。

我們可以使上述網址看起來更簡潔，更不言自明，通過採用所謂的 `path` 格式，省去查詢字串和把 GET 參數加到路徑訊息，作為網址的一部分：

~~~
/index.php/post/read/id/100
~~~

要更改 URL 格式，我們應該配置 [urlManager|CWebApplication::urlManager] 應用程式元件，以便 [createUrl|CController::createUrl] 可以自動切換到新格式和應用程式程式可以正確理解新的網址：

~~~
[php]
array(
	......
	'components'=>array(
		......
		'urlManager'=>array(
			'urlFormat'=>'path',
		),
	),
);
~~~

請注意，我們不需要指定的 [urlManager|CWebApplication::urlManager] 元件的類別，因為它在 [CWebApplication] 事先宣告為[CUrlManager]。

> 提示：此網址通過 [createUrl|CController::createUrl] 方法所產生的是一個相對地址。為了得到一個絕對的 URL ，我們可以用前綴 `Yii::app()->hostInfo`，或調用 [createAbsoluteUrl|CController::createAbsoluteUrl]。


使用者友善的 URL
------------------

當用 `path` 格式 URL，我們可以指定某些 URL 規則使我們的網址更使用者友善。例如，我們可以產生一個短短的 URL `/post/100`，而不是冗長 `/index.php/post/read/id/100`。網址建立和解析都是通過 [CUrlManager] 指定網址規則。

要指定的URL規則，我們必須設定 [urlManager|CWebApplication::urlManager] 應用程式元件的屬性 [rules|CUrlManager::rules]：

~~~
[php]
array(
	......
	'components'=>array(
		......
		'urlManager'=>array(
			'urlFormat'=>'path',
			'rules'=>array(
				'pattern1'=>'route1',
				'pattern2'=>'route2',
				'pattern3'=>'route3',
			),
		),
	),
);
~~~

這些規則被指定成一系列的路由模式配對陣列，每配對對應於一個單一的規則。路由的格式必須是有效的正則表達式，沒有分隔符和修飾語。它是用於匹配網址的路徑訊息部分。還有 [route](/doc/guide/basics.controller#route)應指向一個有效的路由控制器。

除此之外，一個規則可以指定客製化的選項。如下所示：

~~~
[php]
'pattern1'=>array('route1', 'urlSuffix'=>'.xml', 'caseSensitive'=>false)
~~~

上述，該陣列包含了一些客製化的選項。版本 1.1.0 後，有這些選項可供選擇：

   - [urlSuffix|CUrlRule::urlSuffix]: URL 規則的後綴。預設是 null，表示使用 [CUrlManager::urlSuffix] 的值。

   - [caseSensitive|CUrlRule::caseSensitive]: 是否大小寫敏感。預設是 null，表示使用 [CUrlManager::caseSensitive] 的值。

   - [defaultParams|CUrlRule::defaultParams]: 預設的 GET 參數 (name=>value) 規則。當這個規則被用來剖析傳入的需求時，這屬性的值將會被置入 $_GET。

   - [matchValue|CUrlRule::matchValue]: 當建立的 URL 時，GET 參數的值是否與規則的子樣式匹配。預設是空值，代表使用 [CUrlManager::matchValue] 的值。如果這個屬性是 false，代表一個規則會被用來建立 URL ，如果他的路由和給定的參數相互對應。如果這個屬性是 true，納給定的參數值必須和子模式的參數相對應。注意一點，將這個屬性設為 true 會降低效能。


### 使用命名的參數

一個規則可以和一些 GET 參數相關聯。這些 GET 參數會以特殊的符號格式出現在規則的樣式裡，如下所示：

~~~
&lt;ParamName:ParamPattern&gt;
~~~

`ParamName` 表示 GET 參數名字，可選項 `ParamPattern` 表示將用於匹配 GET 參數值的正則表達式。當產生一個 URL 時，這些參數符號將被相應的參數值替換；當解析一個網址時，相應的 GET 參數將通過解析結果來產生。

我們使用一些例子來解釋網址工作規則。我們假設我們的規則包括如下三個：

~~~
[php]
array(
	'posts'=>'post/list',
	'post/<id:\d+>'=>'post/read',
	'post/<year:\d{4}>/<title>'=>'post/read',
)
~~~

   - 調用 `$this->createUrl('post/list')` 產生 `/index.php/posts`。第一個規則適用。

   - 調用 `$this->createUrl('post/read',array('id'=>100))` 產生 `/index.php/post/100`。第二個規則適用。

   - 調用 `$this->createUrl('post/read',array('year'=>2008,'title'=>'a sample post'))` 產生 `/index.php/post/2008/a%20sample%20post`。第三個規則適用。

   - 調用 `$this->createUrl('post/read')` 產生 `/index.php/post/read`。請注意，沒有規則適用。

總之，當使用 [createUrl|CController::createUrl] 產生網址，路由和傳遞給該方法的 GET 參數被用來決定哪些網址規則適用。如果關聯規則中的每個參數可以在 GET 參數找到的，將被傳遞給 [createUrl|CController::createUrl]，如果路由的規則也匹配路由參數，規則將用來產生網址。

如果 GET 參數傳遞到[createUrl|CController::createUrl]是以上所要求的一項規則，其他參數將出現在查詢字串。例如，如果我們調用 `$this->createUrl('post/read',array('id'=>100,'year'=>2008))` ，我們將獲得 `/index.php/post/100?year=2008`。為了使這些額外參數出現在路徑訊息的一部分，我們應該給規則附加 `/*`。 因此，該規則 `post/<id:\d+>/*`，我們可以取得網址 `/index.php/post/100/year/2008`。

正如我們提到的，URL 規則的其他用途是解析請求網址。當然，這是 URL 產生的一個逆向過程。例如， 
當使用者請求 `/index.php/post/100`，上面例子的第二個規則將適用來解析路由 `post/read` 和 GET 參數 `array('id'=>100)`（可通過 `$_GET` 獲得）。

> 註：使用的URL規則將降低應用程式的性能。這是因為當解析請求的 URL，[ CUrlManager ]嘗試使用每個規則來匹配它，直到某個規則可以適用。因此，高流量網站應用程式應盡量減少其使用的 URL 規則。


### 參數化路由

我們或許會在一個規則的路由的部分參照命名參數。這將允許一個規則被用在許多路由匹配標準。他也會幫助減少一個應用程式需要的規則數量和增進整體的效能。

我們使用如下的範例規則來描述如何使用命名參數來參數化路由：

~~~
[php]
array(
	'<_c:(post|comment)>/<id:\d+>/<_a:(create|update|delete)>' => '<_c>/<_a>',
	'<_c:(post|comment)>/<id:\d+>' => '<_c>/read',
	'<_c:(post|comment)>s' => '<_c>/list',
)
~~~

如上所述，我們使用兩個命名參數在一個規則的路由部分：`_c` 和 `_a`。前者與控制器名稱 `post` 或 `comment` 配對，而後者與動作名稱 `create`、`update` 或 `delete`配對。你可以任意命名參數只要他們不與 URL 裡的 GET 參數產生衝突。

使用上述的規則，URL `/index.php/post/123/create` 會被解析成路由 `post/create` 帶著 GET 參數 `id=123`。和路由 `comment/list` 帶著 GET 參數 `page=2`，我們可以建立一個 URL `/index.php/comments?page=2`。


### 參數化主機名稱

包含主機名稱到規則裡來解析和建立 URL 也是可以的。可以擷取主機的部分名稱來當作 GET 參數。例如， URL `http://admin.example.com/en/profile` 可以被解析成 GET 參數 `user=admin` 和 `lang=en`。換句話說，帶有主機名稱的規則可以被用來建立參數化的主機名稱。

使用參數化的主機名稱，只需要簡單的宣告 URL 例如：

~~~
[php]
array(
	'http://<user:\w+>.example.com/<lang:\w+>/profile' => 'user/profile',
)
~~~

上述的範例說明了主機名稱的第一個區段會被當成 `user` 參數，路徑的第一個區段會被當成 `lang` 參數。這個規則 `user/profile` 路由相對應。

注意，當一個 URL 以參數化主機名稱建立時， [CUrlManager::showScriptName] 不會產生作用。

另外，如果一個應用程式在網站根目錄的子目錄下，一個參數化主機名稱規則不應該包含該子目錄。例如，如果一個應用程式是在 `http://www.example.com/sandbox/blog` 下，我們仍應使用上述相同的 URL 規則且不包含子目錄 `sandbox/blog`。

### 隱藏 `index.php`

還有一點，我們可以進一步清理我們的網址，即在 URL 中藏匿 `index.php` 入口腳本。這就要求我們配置網路伺服器，以及 [urlManager|CWebApplication::urlManager] 應用程式程式元件。

我們首先需要配置網路伺服器，這樣一個 URL 沒有入口腳本仍然可以處理入口腳本。如果是 [Apache HTTP server](http://httpd.apache.org/)，可以通過打開網址重寫引擎和指定一些重寫規則。這兩個操作可以在包含入口腳本的目錄下的 `.htaccess` 文件裡實現。下面是一個範例：

~~~
Options +FollowSymLinks
IndexIgnore */*
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
~~~

然後，我們設定 [urlManager|CWebApplication::urlManager] 元件的 [showScriptName|CUrlManager::showScriptName] 屬性為 `false`。

現在，如果我們調用 `$this->createUrl('post/read',array('id'=>100))`，我們將取得網址 `/post/100` 。更重要的是，這個URL可以被我們的網路應用程式程式正確解析。

### 偉造的 URL 後綴

我們還可以增加一些網址的後綴。例如，我們可以用 `/post/100.html` 來替代 `/post/100` 。這使得它看起來更像一個靜態網頁 URL。為了做到這一點，只需配置 [urlManager|CWebApplication::urlManager] 元件的[urlSuffix|CUrlManager::urlSuffix] 屬性為你所喜歡的後綴。


使用自定 URL 規則設置類別
-----------------------------

> 注意: Yii 從 1.1.8 版本起支援自定 URL 規則類別

預設情況下，每個 URL 規則都通過 [CUrlManager] 來宣告為一個 [CUrlRule] 物件，這個物件會解析當前請求並根據具體的規則來產生 URL。雖然 [CUrlRule]可以處理大部分 URL 格式，但在某些特殊情況下仍舊有改進餘地。

比如，在一個汽車銷售網站上，可能會需要支援類似 `/Manufacturer/Model` 這樣的 URL 格式，其中 `Manufacturer` 和 `Model` 都各自對應資料庫中的一個表。此時 [CUrlRule] 就無能為力了。

我們可以通過繼承 [CUrlRule] 的方式來創造一個新的 URL 規則類。並且使用這個類解析一個或者多個規則。以上面提到的汽車銷售網站為例，我們可以宣告下面的 URL 規則。

~~~
[php]
array(
	// 一個標準的 URL 規則，將 '/' 對應到 'site/index'
	'' => 'site/index',

	// 一個標準的 URL 規則，將 '/login' 對應到 'site/login', 等等
	'<action:(login|logout|about)>' => 'site/<action>',

	// 一個自定 URL 規則，用來處理 '/Manufacturer/Model'
	array(
	    'class' => 'application.components.CarUrlRule',
	    'connectionID' => 'db',
	),

	// 一個標準的 URL 規則，用來處理 'post/update' 等
	'<controller:\w+>/<action:\w+>' => '<controller>/<action>',
),
~~~

從以上可以看到，我們自定了一個 URL 規則類別 `CarUrlRule` 來處理類似 `/Manufacturer/Model` 這樣的 URL 規則。
這個類別可以這麼寫：

~~~
[php]
class CarUrlRule extends CBaseUrlRule
{
	public $connectionID = 'db';

	public function createUrl($manager,$route,$params,$ampersand)
	{
		if ($route==='car/index')
		{
			if (isset($params['manufacturer'], $params['model']))
				return $params['manufacturer'] . '/' . $params['model'];
			else if (isset($params['manufacturer']))
				return $params['manufacturer'];
		}
		return false;  // 這個規則沒套用
	}

	public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
	{
		if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches))
		{
			// 檢查 $matches[1] 和 $matches[3] 是否
			// 在資料庫中有匹配的 manufacturer model
			// 如果有，設定 $_GET['manufacturer'] 和/或 $_GET['model']
			// 並回傳 'car/index'
		}
		return false;  // 這個規則沒套用
	}
}
~~~

自定 URL 規則類別必須實現在 [CBaseUrlRule] 中定義的兩個介面。

* [CBaseUrlRule::createUrl()|createUrl()]
* [CBaseUrlRule::parseUrl()|parseUrl()]

除了這種典型用法，自定 URL 規則類別還可以有其他的用途。比如，我們可以寫一個規則類來記錄有關 URL 解析和 UEL 建立的請求。這對於正在開發中的網站來說很有用。我們還可以寫一個規則類來在其他 URL 規則都匹配失敗的時候顯示一個自定 404 頁面。注意，這種用法要求規則類別在所有其他規則的最後宣告。

<div class="revision">$Id$</div>