# URL路由
路由是URL和控制器操作之間的雙向轉換。 雙向意味著我們可以確定控制器URL鏈接到什么,反之亦然:生成給定方法的URL。 本章包含學到幾點:
* 如何定義路由和創建鏈接
* 關于SEO重定向的幾個注釋
* 如何調試定義的路由
* 如何創建自己的路由器
# 什么是路由?
路由是URL和應用程序請求之間的雙向轉換。
* Nette\Http\IRequest (includes URL) → Nette\Application\Request
* Nette\Application\Request → absolute URL
由于雙向路由,您不必將網址硬編碼到模板中,您只需鏈接到控制器的操作,框架會為您生成網址:
~~~
{* creates a link to presenter 'Product' and action 'detail' *}
<a n:href="Product:detail $productId">product detail</a>
~~~
這個以前實例已有說明了。可以詳細了解如何創建鏈接(MVC應用程序和控制器這一單里)。
路由是一個單獨的應用程序層。 這允許您非常有效地操作與URL結構,而不需要修改應用程序本身。 隨時更改路線很簡單,同時保留原始地址,并自動重定向到新版本。
### SimpleRouter
所需的URL格式由路由器設置。 最簡單的路由器實現是SimpleRouter。 它可以在不需要特定的URL格式時使用,當mod_rewrite(或替代)不可用或者我們根本不想為用戶友好的URL打擾。
生成的地址將如下所示:
~~~
http://example.com/?presenter=Product&action=detail&id=123
~~~
這個是原來自動生成URL。
SimpleRouter構造函數的第一個參數是默認的Presenter方法,如果我們打開例如。 http://example.com/,
無需其他參數。
~~~
// 默認為presenter'Homepage'和'default'方法
$router = new Nette\Application\Routers\SimpleRouter('Homepage:default');
~~~
第二個構造函數參數是可選的,用于傳遞附加標志(對于單向路由只有SimpleRouter :: ONE_WAY)。
配置應用程序以使用SimpleRouter的推薦方法是使用配置文件(例如config.neon):
~~~
services:
application.router: Nette\Application\Routers\SimpleRouter('Homepage:default')
~~~
這一小節就是教你默認打開URL方法的設置。比如我有一個后臺AdminPresenter.php控制器,里面有一個index方法,我想打開網址主頁就是后臺中的index方法,那你就要在config.neon配置里加入
~~~
services:
application.router: Nette\Application\Routers\SimpleRouter('Admin:index')
~~~
## 路由:更漂亮的網址
人類友好的URL(也更酷和更漂亮)更容易記住,并幫助SEO。 Nette框架保持當前的趨勢,并完全滿足開發人員的愿望。
~~~
所有請求必須由index.php文件處理。 這可以通過例如 通過使用Apache模塊mod_rewrite或Nginx的try_files指令(請參閱如何配置服務器的漂亮的URL)。
~~~
Route類能夠創建幾乎任何格式的地址一個。 讓我們從一個簡單的例子開始,生成以下漂亮的URL中Product:default與id = 123:
~~~
http://example.com/product/detail/123
~~~
Product:控制器前面這部份名稱
detail:方法名稱
123:參數
這個以前實例也說明了。
以下代碼段創建一個Route對象,將路徑掩碼作為第一個參數,并在第二個參數中指定默認方法。 我們可以使用第三個參數傳遞額外的標志。
使用router/RouterFactory.php,這個文件就是設置路由的。
~~~
// 默認為presenter控制器中的default方法。
$route = new Route('<presenter>/<action>[/<id>]', 'Homepage:default');
// 或者使用數組寫入
$route = new Route('<presenter>/<action>[/<id>]', [
'presenter' => 'Homepage',
'action' => 'default'
]);
~~~
此路由可供所有控制器和方法使用。 接受諸如/ article / edit / 10或/ catalog / list的路徑,因為id部分包含在方括號中,這表示它是可選的。
由于其他參數(控制器和方法)具有默認值(Homepage和default),因此它們也是可選的。 如果它們的值與默認值相同,則會在生成URL時跳過這些值。 鏈接到Product:default只生成http://example.com/product
并鏈接到Homepage:default只生成http://example.com/.
以上說的這個delault也是一種默認值。它也是可以不用寫出來,比如:我有一個控制器DemoPresenter.php里面有一個default方法,打開這個方法鏈接時我們可以用
http://example.com/demo
后面的default可以不用寫進去。
### 路徑掩碼
最簡單的路徑掩碼只包括靜態URL和目標控制器操作。
~~~
$route = new Route('products', 'Products:default');
~~~
然而,大多數真實面具包含一些參數。 參數括在尖括號中(例如<year>),并傳遞給目標控制器。
~~~
$route = new Route('history/<year>', 'History:view');
~~~
掩碼還可以包含傳統的GET參數(在問號后查詢)。 在路徑掩碼的這部分中既不支持驗證表達式也不支持更復雜的結構,但可以設置什么鍵屬于哪個變量:
~~~
// 在我們的應用程序中使用GET參數“cat”作為“categoryId”
$route = new Route('<presenter>/<action> ? id=<productId> & cat=<categoryId>', ...);
~~~
~~~
問號前的參數稱為路徑參數,問號后的參數稱為查詢參數。
~~~
掩碼不僅可以描述相對于應用程序文檔根(web根)的路徑,而且可以包含相對于服務器文檔根(以單個斜杠開始)或具有域的絕對路徑(以雙斜線開始)的路徑。
~~~
// 相對于應用程序文檔根目錄(www目錄)
$route = new Route('<presenter>/<action>', ...);
// 相對于服務器文檔根
$route = new Route('/<presenter>/<action>', ...);
// 絕對路徑包括主機名
$route = new Route('//<subdomain>.example.com/<presenter>/<action>', ...);
~~~
絕對路徑掩碼可以利用以下變量:
* %tld%=頂級域名,例如 com或org
* %sld%=第二級域,例如 在example.com返回示例
* %domain%=第二級域,例如 example.com
* %basePath%
~~~
$route = new Route('//www.%domain%/%basePath%/<presenter>/<action>', ...);
$route = new Route('//www.%sld%.%tld/%basePath%/<presenter>/<action', ...);
~~~
路線收集
因為我們通常定義多個路由,我們將它們包裝到一個RouteList中
~~~
use Nette\Application\Routers\RouteList;
use Nette\Application\Routers\Route;
$router = new RouteList();
$router[] = new Route('rss.xml', 'Feed:rss');
$router[] = new Route('article/<id>', 'Article:view');
$router[] = new Route('<presenter>/<action>[/<id>]', 'Homepage:default');
~~~
與其他框架不同,Nette不需要命名路由。
~~~
重要的是,按照從上到下的順序評估的路由順序。 這里的經驗法則是,路由從最頂部的特定位置到最底部的最模糊的位置。
~~~
請記住,大量的路由會對應用程序速度產生負面影響,主要是在生成鏈接時。 值得保持路線盡可能簡單。
如果沒有找到路由,將拋出BadRequestException,這對用戶顯示為404 Not Found。
### 路由器工廠
推薦的配置應用路由器的方法是寫一個工廠(例如位于app / router / RouterFactory.php中)并將其注冊到配置文件(例如位于app / config / config.neon中)中的系統DI容器。
**File app/router/RouterFactory.php:**
~~~
<?php
namespace App;
use Nette\Application\Routers\RouteList,
Nette\Application\Routers\Route;
class RouterFactory
{
/**
* @return \Nette\Application\IRouter
*/
public function createRouter()
{
$router = new RouteList();
$router[] = new Route('<presenter>/<action>', 'Homepage:default');
return $router;
}
}
~~~
@return注釋是重要的,并且是讓代碼工作所必需的。
**File app/config/config.neon:**
~~~
services:
routerFactory: App\RouterFactory
router: @routerFactory::createRouter
~~~
### 默認值
每個參數可能在掩碼中定義了默認值:
~~~
$route = new Route('<presenter=Homepage>/<action=default>/<id=>');
~~~
或使用數組:
~~~
$route = new Route('<presenter>/<action>/<id>', [
'presenter' => 'Homepage',
'action' => 'default',
'id' => NULL,
]);
//等于以下復雜符號
$route = new Route('<presenter>/<action>/<id>', [
'presenter' => [
Route::VALUE => 'Homepage',
],
'action' => [
Route::VALUE => 'default',
],
'id' => [
Route::VALUE => NULL,
],
]);
~~~
<presenter>和<action>的默認值也可以寫為第二個構造函數參數中的字符串。
~~~
$route = new Route('<presenter>/<action>/<id=>', 'Homepage:default');
~~~
## 正則表達式
每個參數可能已經定義了需要匹配的正則表達式。 在匹配和生成URL時檢查此正則表達式。 例如,讓我們將id設置為只有數字,使用\ d + regexp:
~~~
//regexp可以直接在路徑掩碼中定義參數名
$route = new Route('<presenter>/<action>[/<id \d+>]', 'Homepage:default');
// 上面代碼也可以寫成以下
$route = new Route('<presenter>/<action>[/<id>]', [
'presenter' => 'Homepage',
'action' => 'default',
'id' => [
Route::PATTERN => '\d+',
],
]);
~~~
意思這個URL后面的ID必須是數字,如果不是數字就會出錯。這個ID是根據你這個正則表達式來確點。
~~~
路徑參數的默認驗證表達式為[^ /] +,表示除斜杠外的所有字符。 如果參數也應該與斜杠匹配,我們可以將正則表達式設置為.+。
~~~
## 可選序列
方括號表示掩碼的可選部分。 隱藏的任何部分可以設置為可選,包括那些包含參數的部分:
~~~
$route = new Route('[<lang [a-z]{2}>/]<name>', 'Article:view');
接受的URL: 參數:
// Accepted URLs: Parameters:
// /en/download action => view, lang => en, name => download
// /download action => view, lang => NULL, name => download
~~~
顯然,如果參數在可選序列內,它也是可選的,默認為NULL。 序列應該定義它的周圍環境,在這種情況下,必須跟隨一個參數,如果設置的斜杠。 該技術可以用于例如可選的語言子域:
~~~
$route = new Route('//[<lang=en>.]%domain%/<presenter>/<action>', ...);
~~~
序列可以自由嵌套和組合:
~~~
$route = new Route(
'[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page=0>]',
'Homepage:default'
);
//接受的網址:
// /cs/hello
// /en-us/hello
// /hello
// /hello/page-12
~~~
網址生成器嘗試保持網址盡可能短。 這就是為什么index [.html]路由生成為/ index。 可以通過在表示相應可選序列的最左方括號之后寫入感嘆號來反轉此行為:
~~~
// 可以 both /hello和 /hello.html, generates /hello
$route = new Route('<name>[.html]');
// 可以 both /hello和 /hello.html, generates /hello.html
$route = new Route('<name>[!.html]');
~~~
沒有方括號的可選參數(即具有默認值的參數)的行為如下所示:
~~~
$route = new Route('<presenter=Homepage>/<action=default>/<id=>');
// 等于:
$route = new Route('[<presenter=Homepage>/[<action=default>/[<id>]]]');
~~~
如果我們想改變最右邊斜線的生成方式,那就是代替**/ homepage /** 得到** / homepage**,我們可以調整路線:
~~~
$route = new Route('[<presenter=Homepage>[/<action=default>[/<id>]]]');
~~~
## 標志
路由默認行為可以通過幾個標志來改變,它們作為第三個構造函數參數傳遞。
ONE_WAY標記 - 單一路線通常用于保留舊網址功能,當應用程序被重寫時。 Route :: ONE_WAY標記不用于生成URL的路由。
~~~
// 舊URL /product-info?id=123,新 URL /product/123
$router[] = new Route('product-info', 'Product:detail', Route::ONE_WAY);
$router[] = new Route('product/<id>', 'Product:detail');
~~~
### HTTPS
我們需要配置您的服務器以使用HTTPS協議。
通過使用代碼為301的永久重定向,在我們的應用程序的根目錄中使用.htaccess文件,可以實現為已經完善的應用程序轉發所有地址。
~~~
<IfModule mod_rewrite.c>
RewriteEngine On
...
RewriteCond %{HTTPS} off
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
...
</IfModule>
~~~
路由生成具有加載頁面的同一協議的URL。 如果我們需要,使某些地址使用其他協議,我們在路由的掩碼中使用它們。
~~~
// 轉發到HTTP協議
$route = new Route('http://%host%/<presenter>/<action>','Homepage:default');
// 轉發到HTTPS協議
$route = new Route('https://%host%/<presenter>/<action>','Admin:default');
~~~
## 過濾器和翻譯
這是一個好的做法是用英語編寫源代碼,但如果你需要你的應用程序在不同的環境中運行怎么辦? 簡單的路線如:
~~~
$route = new Route('<presenter>/<action>/<id>', [
'presenter' => 'Homepage',
'action' => 'default',
'id' => NULL,
]);
~~~
將生成英語網址,例如/ product / detail / 123,/ cart或/ catalog / view。 如果我們想翻譯這些URL,我們可以使用在Route :: FILTER_TABLE鍵下定義的字典。 所以:
~~~
$route = new Route('<presenter>/<action>/<id>', [
'presenter' => [
Route::VALUE => 'Homepage', // default value
Route::FILTER_TABLE => [
// translated string in URL => presenter
'produkt' => 'Product',
'einkaufswagen' => 'Cart',
'katalog' => 'Catalog',
],
],
'action' => [
Route::VALUE => 'default',
Route::FILTER_TABLE => [
'sehen' => 'view',
],
],
'id' => NULL,
]);
~~~
Route :: FILTER_TABLE下的多個鍵可以具有相同的值。 這就是別名的創建方式。 最后一個值是規范的(用于生成鏈接)。
字典可以應用于任何路徑參數。 如果未找到翻譯,則使用原始(非翻譯)值。 該路線默認接受翻譯(例如/ einkaufswagen)和原始(例如/cart)網址。 如果您只想接受翻譯的URL,您需要添加Route :: FILTER_STRICT => TRUE到路由定義。
除了將字典設置為數組之外,還可以設置輸入和輸出過濾器。 輸入和輸出過濾器分別是在Route :: FILTER_IN和Route :: FILTER_OUT鍵下定義的回調。
~~~
$route = new Route('<presenter=Homepage>/<action=default>', [
'action' => [
Route::FILTER_IN => function($action) {
return strrev($action);
},
Route::FILTER_OUT => function($action) {
return strrev($action);
},
],
]);
~~~
輸入過濾器接受來自URL的值,并應返回將傳遞給控制器的值。 輸出過濾器接受來自presenter的值,并應返回將在URL中使用的值。 如果任何這些過濾器無法過濾值(通常是因為它無效),它應該返回NULL。
## 全局過濾器
除了特定參數的過濾器之外,還可以定義全局過濾器,它接受具有所有參數的關聯數組,并返回具有過濾參數的數組。 全局過濾器在NULL鍵下定義。
~~~
$route = new Route('<presenter>/<action>', [
'presenter' => 'Homepage',
'action' => 'default',
NULL => [
Route::FILTER_IN => function(array $params) {
// ...
return $params;
},
Route::FILTER_OUT => function(array $params) {
// ...
return $params;
},
],
]);
~~~
您可以使用全局過濾器根據另一個參數的值過濾某些參數,例如 根據<lang>翻譯<presenter>和<action>。
## Foo參數
Foo參數基本上是未命名的參數,允許您匹配正則表達式。 以下路由匹配/ index,/index.html,/index.htm和/index.php:
~~~
$route = new Route('index<? \.html?|\.php|>', 'Homepage:default');
~~~
還可以顯式定義將用于URL生成的字符串(類似于為實參設置默認值)。 字符串必須直接放在問號后面。 以下路由類似于上一個,但生成/index.html而不是/ index,因為字符串.html被設置為“默認值”。
~~~
$route = new Route('index<?.html \.html?|\.php|>', 'Homepage:default');
~~~
## 模塊
在Nette中,我們可以將控制器分成模塊。 因此,我們需要在路由中使用這些模塊。 我們可以在Route類中使用module參數:
~~~
new Route('admin/<presenter>/<action>', [
'module' => 'Admin'
]);
~~~
如果我們有更多的路由,我們想在一個模塊中組合在一起,我們可以使用RouteList與第一個參數:
~~~
$adminRouter = new RouteList('Admin');
$adminRouter[] = new Route('admin/<presenter>/<action>');
~~~
### 路由調試器
首先使用路線似乎有點神奇。 這就是為什么你會欣賞路由調試器的價值。 這是一個調試器欄面板,它提供了路由器獲得的所有參數的列表和所有定義的路由的列表。 它還顯示了您當前在哪個控制器和方法。

如果應用程序在調試模式下運行,默認情況下將啟用路由調試器。 不過,您可以在配置文件中禁用它:
~~~
routing:
debugger: off # on by default
~~~
### SEO和規范化
框架增加SEO(搜索引擎優化),因為它阻止多個網址鏈接到不同的內容(沒有適當的重定向)。 如果多個地址鏈接到同一目標(/ index和/index.html),框架選擇第一個(它是規范的),并重定向另一個與它的HTTP代碼301.感謝你的頁面不會 在搜索引擎上有重復,它們的排名不會分裂。
這整個過程稱為規范化。 默認(規范)URL是一個路由器生成的,即集合中的第一個路由不返回NULL并且沒有ONE_WAY標志。
規范化由Presenter完成,默認情況下它已打開。 您可以通過將Presenter :: $ autoCanonicalize設置為FALSE來禁用它,例如 在startup()。
Ajax和POST請求不會重定向,因為用戶將遭受數據丟失,或者它不會產生額外的SEO值。
### 模塊化
如果我們想在我們的應用程序中添加具有自己的路由的單獨的模塊,例如討論論壇,我們可以在其他地方定義路由,可能在createRoutes()方法中:
~~~
class Forum
{
static function createRoutes($router, $prefix)
{
$router[] = new Route($prefix . 'index.php', 'Forum:Homepage:default');
$router[] = new Route($prefix . 'admin.php', 'Forum:Admin:default');
...
}
}
~~~
“Routing-in”的論壇到現有的應用程序可以像調用這個方法一樣簡單:(bootstrap.php)
~~~
$router = new RouteList;
//我們的路線
...
// 添加論壇模塊
Forum::createRoutes($router, '//forum.example.com/');
$container->addService('router', $router);
~~~
### 自定義路由器
如果這些提供的路由不符合您的需要,您可以創建自己的路由器,并將其添加到路由器集合。 路由器只是一個IRouter的實現,它有兩種方法:
~~~
use Nette\Application\Request as AppRequest;
use Nette\Http\IRequest as HttpRequest;
use Nette\Http\Url;
class MyRouter implements Nette\Application\IRouter
{
function match(HttpRequest $httpRequest)
{
// ...
}
function constructUrl(AppRequest $appRequest, Url $refUrl)
{
// ...
}
}
~~~
方法匹配處理一個HttpRequest(它不僅僅提供一個Url)到一個內部Nette \ Application \ Request,它包含演示者名稱及其參數。 如果無法處理HTTP請求,則應返回NULL。
方法constructUrl從應用程序請求生成絕對URL,可能使用來自$ refUrl參數的信息。
定制路由器的可能性是無限的,例如可以實現基于數據庫記錄的路由器。
- Nette簡介
- 快速開始
- 入門
- 主頁
- 顯示文章詳細頁
- 文章評論
- 創建和編輯帖子
- 權限驗證
- 程序員指南
- MVC應用程序和控制器
- URL路由
- Tracy - PHP調試器
- 調試器擴展
- 增強PHP語言
- HTTP請求和響應
- 數據庫
- 數據庫:ActiveRow
- 數據庫和表
- Sessions
- 用戶授權和權限
- 配置
- 依賴注入
- 獲取依賴關系
- DI容器擴展
- 組件
- 字符串處理
- 數組處理
- HTML元素
- 使用URL
- 表單
- 驗證器
- 模板
- AJAX & Snippets
- 發送電子郵件
- 圖像操作
- 緩存
- 本土化
- Nette Tester - 單元測試
- 與Travis CI的持續集成
- 分頁
- 自動加載
- 文件搜索:Finder
- 原子操作