# 路由
_路由(Routing)_ 是 Pagekit 解決的一個中心問題。當瀏覽器點擊一個 URL,框架會確認哪些操作會被調用。
[toc=2]
## 控制器
在 Pagekit 中創建路由的最常見方式是定義一個 _控制器(controller)_。控制器可以在處理請求、設置路由和渲染視圖時發出響應。
### 注冊控制器
可以在[模塊配置](224131)中注冊控制器。使用 `routes` 屬性將控制器添加到路由上。
```php
'routes' => [
'/hello' => [
'name' => '@hello/admin',
'controller' => [
'Pagekit\\Hello\\Controller\\HelloController'
]
]
],
```
### 基本結構
以 `@Route("/hello")` 注釋的類,使得控制器被_添加_到 `http://example.com/hello/`。意味著它會響應這個 URL 及其子級 URL的所有請求,例如 `http://example.com/hello/settings`.
```php
namespace Pagekit\Hello\Controller;
/**
* @Route("/hello")
*/
class HelloController
{
public function indexAction()
{
// ...
}
public function settingsAction()
{
// ...
}
}
```
默認地,你的擴展(或主題)會被啟動,并且一系列默認路由會被自動生成。可以使用[開發者工具欄](224147) 瀏覽剛才注冊的路由(以及所有核心路由)。
這里說明如何理解路由:
|路由 | 例子 | 描述|
|------|------------------|---------------------------------------------------|
|Name |`@hello/hello/settings`| 路由的名稱可用于生成 URL(必須是唯一的)|
|URI |`/hello/settings`| 在瀏覽器中訪問此路由的路徑|
|Action|`Pagekit\Hello\Controller\DefaultController::settingsAction`| 即將被調用的控制器操作|
默認地,路由會是 `http://example.com/<extension>/<controller>/<action>` 這樣的形式。特殊操作 `indexAction`,不會附加到 `.../index` 上,而是附加到 `.../`。自定義路由的高級選項當然也是可用的,在下文中會看到。
**Note** 在你的應用程序中,如果一個路由不是唯一的,真正被使用的是首先添加的那一個。由于這是框架內部的行為,說不定什么時候就會改變,所以不應該依賴它,要確保你的路由是唯一的。
## 注釋/Annotations
許多的控制器行為是由注釋信息來確定類和方法的。下表是關于各種注釋的描述:
|注釋 | 描述|
|---------- | -------------------------------------------------------------|
|`@Route` | 被附加操作或整個控制器的路由|
|`@Request` | 處理從 http 請求傳遞到方法的參數|
|`@Access` | 檢查用戶權限|
**Note** 只有以兩個星號開頭的多行注釋會生效,一個星號的不會。
```
// will NOT work:
/* @Route("/hello") */
// will work:
/** @Route("/hello") */
// will work:
/**
* @Route("/hello")
*/
```
### @Route
定義即將被附加控制器(或控制器操作)的路由。它可以注釋到類和方法。
默認地,名為 `greetAction` 的方法會在類的路由下,會作為 `/greet` 的形式來附加。要添加自定義路由,可以為方法添加任意數目的附加路由。路由還可以包含即將傳遞到方法的動態參數。
```php
/**
* @Route("/greet", name="@hello/greet/world")
* @Route("/greet/{name}", name="@hello/greet/name")
*/
public function greetAction($name = 'World')
{
// ...
}
```
可以指定參數來滿足特定的需求(例如,將值限制為數字)。你可以命名一個路線,這樣你可以從代碼參考。在方法定義中,使用PHP參數的默認值,使參數可選。
路由可用綁定特定的 HTTP 方法, (e.g. `GET` or `POST`)。這對 RESTfil API 特別有用。
```php
/**
* @Route("/view/{id}", name="@hello/view/id", requirements={"id"="\d+"}, methods="GET")
*/
public function viewAction($id = 0)
{
// ...
}
```
**Note** 了解更多詳情和例子,查看[Symfony 文檔](http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/routing.html) 的 `@Route` 注釋。
### @Request
可以指定通過請求傳遞的數據類型,并將其匹配到要傳遞到目標方法的參數上。
數組將 _name_ 映射到 _type_。 _name_ 請求數據中的鍵。_type_ 可以是 `int`, `string`, `array` 或者其他高級類型比如整數數組的 `int[]`。如果沒有指定類型,則默認假設是 `string` 。
鍵的順序將決定傳遞到方法的參數的順序。方法頭部的參數名稱可以是任意形式的。
```php
/**
* @Request({"id": "int", "title", "config": "array"}, csrf=true)
*/
public function saveAction($id, $title, $config)
{
// ...
}
```
還可以檢查 token 來防范 [CSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)。添加 `csrf=true` 到請求方法的注釋里,并在要提交表單到這個方法的視圖中添加 `@token` 調用。
查看 `Pagekit\Filter\FilterManager` 中包含所有可用過濾器的列表。某些過濾器有附加的選項,比如 `pregreplace`。
```php
/**
* @Request({"folders":"pregreplace[]"}, options={"folders" = {"pattern":"/[^a-z0-9_-]/i"}})
*/
public function deleteFolders($folders)
{
// ...
}
```
### @Access
可以指定訪問特定方法或整個控制器的用戶權限。
控制器應該具體到前端頁面或管理面板。到目前為止,我們看到了前端頁面的控制器。管理控制器只能被擁有 `Access admin area` 權限的用戶訪問。還有,該控制器的所有路由的 URL 都以 `admin/` 開頭。最后,會在管理界面視圖中渲染,而不是默認的主題中。
```php
/**
* @Access(admin=true)
*/
class SettingsController
{
// ...
}
```
現在,只有擁有 `Access admin area` 權限的用戶可以訪問此控制器的操作。如果想要進一步限制只允許某些用戶進行特定的操作(比如管理用戶等),你可以為單個控制器操作添加限制條件。
在`extension.php` (或 `theme.php`)文件中定義權限,并以你希望的方式組合它們。控制器級的訪問限制會與單個操作上的訪問限制組合。因此你可以為控制器設置一個基礎的_最小_訪問等級并限制某些操作,比如行政管理操作,則需要更多具體權限。
```php
/**
* @Access("hello: manage users")
*/
public function saveAction()
{
// ...
}
```
當然,即使控制器并非管理區域的控制器,你還是可以使用這些限制。還可以在單個控制器操作上檢查管理權限。
```php
/**
* @Access("hello: edit article", admin=true)
*/
public function editAction()
{
// ...
}
```
## 生成 URL
使用URL 服務,為路由生成 URL。
```php
$this['url']->route('@hello/default/index') // '/hello/default/index'
$this['url']->route('@hello/default/index', true) // 'http://example.com/hello/default/index'
$this['url']->route('@hello/view/id', ['id' => 23]) // '/hello/view/23'
```
## 鏈接
Pagekit 的路由可以用內部的路由語法描述。鏈接由路由的名稱(e.g. '@hello/name')和可選的 GET 參數(e.g. '@hello/name?name=World')組成。它將實際的路由 URI 與路由本身分離。