owned this note
owned this note
Published
Linked with GitHub
rounting
===
當應用程式收到請求(request)時,它會執行控制器(controller)操作以生成響應(response)。
路由配置可以定義對每個傳入 URL 所要執行的動作。它還提供其他有用的功能,例如生成對 SEO 友好的 URL(例如 ```/read/intro-to-symfony```代替```index.php?article_id=57```)。
擁有靈活性是重要的。你把頁面的URL從 ```/blog``` 改為 ```/news``` 時需要做些什麼?你需要追踪並更新多少連結,才能做出這種改變?如果你使用Symfony的路由,改變起來會容易許多。
Symfony路由器允許你定義富有創造性的```url```,再將其映射(map)到程式的不同區域。透過此段落,你可以做到:
- 創造複雜的路由,將其映射到控制器
- 在模板和控制器中生成URL
- 從Bundle中(或從其他地方)加載路由資源
- 對路由debug
## 創建routes
可以在 YAML、XML、PHP 或使用註釋(annotattions)中配置路由。所有格式都提供相同的功能和性能,因此可以選擇你最喜歡的。Symfony 推薦使用註釋(可以參考[symfony best pratice](https://symfony.com/doc/4.3/best_practices.html#best-practice-controller-annotations)), 因為將路由和控制器放在同一個地方很方便。
### 創建路由作為註釋
可以在你的應用程式中執行一次以下指令,用以支援註釋
```php=
composer require annotation
```
除了安裝所需的依賴向外,此指令還會創建以下配置文件
```php=
# congfig/routes.yaml
controllers:
resource: '../src/Controller'
type: annotation
```
此配置告訴 Symfony 存儲在``src/Controller/```目錄中的任何 PHP class中查找被定義為註釋的路由。
假設您要為```/blog```應用程式中的URL定義路由。為此,請創建一個如下所示的控制器class:
```php=
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
/**
* @Route("/blog", name="blog_list")
*/
public function list()
{
// ...
}
}
```
此配置定義了一個稱為```blog_list```match的路由,該路由在用戶請求```/blog```URL時match。當match成功時,應用程式會執行```BlogController``` class中的 ```list()```方法
>Note
match路由時不考慮 URL 的查詢字串。在這個例子中,URLs 像是```/blog?foo=bar```或者```/blog?foo=bar&bar=foo```也將match```blog_list```路由。
路由名稱(```blog_list```) 現在並不重要,但在以後生成URL時將是必不可少的。要記住,每個路由名稱在應用程序中必須是唯一的。
### 在 YAML、XML 或 PHP 文件中創建路由
您可以在單獨的 YAML、XML 或 PHP 文件中定義它們,而不是在控制器class中定義路由。
- 優點是它們不需要任何額外的依賴
- 缺點是在檢查某些控制器操作的路由時必須處理多個文件。
以下範例顯示如何在 PHP([YAML、XML](https://symfony.com/doc/4.3/routing.html)) 中定義一個名為```blog_list```的路由,該路由 將```/blogURL``` 與BlogController的```list()```做相連的操作:
```php=
// config/routes.php
use App\Controller\BlogController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return function (RoutingConfigurator $routes) {
$routes->add('blog_list', '/blog')
// the controller value has the format [controller_class, method_name]
->controller([BlogController::class, 'list'])
// if the action is implemented as the __invoke() method of the
// controller class, you can skip the ', method_name]' part:
// ->controller([BlogController::class])
;
};
```
match HTTP 方法
預設情況下,路由match任何HTTP的操作(GET,POST,PUT等)使用methods選項來限制每個路由應該做出響應的操作:
```php=
// src/Controller/BlogApiController.php
namespace App\Controller;
// ...
class BlogApiController extends AbstractController
{
/**
* @Route("/api/posts/{id}", methods={"GET","HEAD"})
*/
public function show(int $id)
{
// ... return a JSON response with the post
}
/**
* @Route("/api/posts/{id}", methods={"PUT"})
*/
public function edit(int $id)
{
// ... edit a post
}
}
```
:::success
Tip
HTML表單只支持GET和POST方法。如果您使用與 HTML 表單不同的方法呼叫路由,請添加一個_method的方法呼叫隱藏字段 (例如```<input type="hidden" name="_method" value="PUT"/>```)。如果您使用Symfony Forms的方法創建表單,這會自動為您完成。
:::
### match 表達式
在你需要路由配對基於任意matching logic時使用```condition```選項
```php=
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends AbstractController
{
/**
* @Route(
* "/contact",
* name="contact",
* condition="context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'"
* )
*
* expressions can also include config parameters:
* condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'"
*/
public function contact()
{
// ...
}
}
```
```condition```選項的值是任意有效的```ExpressionLanguage表達式```, 並且可以使用 Symfony 創建這些變量:
```context```
- RequestContext 的一個實例,它保存有關正在matched路由的最基本信息。
```request```
- 代表當前請求Symfony的請求對象。
在背後的運作機制,表達式被編譯成原始PHP。正因為如此,使用condition key不會導致超出底層 PHP 執行時間的額外成本花費。
:::danger
在生成 URL 時不考慮```condition```稍後會解釋)。
:::
### debug路由
隨著應用程式的擴增,最終將擁有很多路由。Symfony 包含一些命令來幫助您debug路由。該```debug:router```指令列出所有可以透過symfony評估在相同的應用程式中你所應用的路由
```php=
php bin/console debug:router
---------------- ------- ------- ----- --------------------------------------------
Name Method Scheme Host Path
---------------- ------- ------- ----- --------------------------------------------
homepage ANY ANY ANY /
contact GET ANY ANY /contact
contact_process POST ANY ANY /contact
article_show ANY ANY ANY /articles/{_locale}/{year}/{title}.{_format}
blog ANY ANY ANY /blog/{page}
blog_show ANY ANY ANY /blog/{slug}
---------------- ------- ------- ----- --------------------------------------------
```
將某個路由的名稱(或名稱的一部分)傳遞給此參數以顯示路由詳細訊息:
```php=
php bin/console debug:router app_lucky_number
+-------------+---------------------------------------------------------+
| Property | Value |
+-------------+---------------------------------------------------------+
| Route Name | app_lucky_number |
| Path | /lucky/number/{max} |
| ... | ... |
| Options | compiler_class: Symfony\Component\Routing\RouteCompiler |
| | utf8: true |
+-------------+---------------------------------------------------------+
```
另一個指令被稱為```router:match```,它顯示哪個路由將match到給定的 URL。這對於找出非期望被執行的URL的控制器action很有用:
```php=
php bin/console router:match /lucky/number/8
[OK] Route "app_lucky_number" matches
```
## 路由參數
前面的範例定義了 URL 從不改變的路由(例如```/blog```)。但是,定義某些部分路由是很常見的。例如,顯示一些文章的 URL 可能包含標題或 slug(例如```/blog/my-first-post```或```/blog/all-about-symfony```)。
在 Symfony 路由中,可變部分為```{ ... }```涵蓋並且它們必須有一個唯一的名稱。例如,顯示文章內容的路由定義為```/blog/{slug}```:
```php=
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
// ...
/**
* @Route("/blog/{slug}", name="blog_show")
*/
public function show(string $slug)
{
// $slug will equal the dynamic part of the URL
// e.g. at /blog/yay-routing, then $slug='yay-routing'
// ...
}
}
```
變量部分的名稱(在範例中的```{slug}```)用於創建一個 PHP 變量,其中存儲路由內容並傳遞給控制器。
如果用戶訪問```/blog/my-first-postURL```,Symfony會執行```BlogController```class中的```show()```方法,並將```$slug = 'my-first-post' ```參數傳遞給該```show()```方法。
路由可以定義任意數量的參數,但每個參數在每條路由上只能使用一次(例如```/blog/posts-about-{category}/page/{pageNumber}```)。
### 參數驗證
假設您的應用程式有一個```blog_show```路由 (URL:```/blog/{slug}```)和一個```blog_list```路由 (URL:``` /blog/{page}```)。鑑於路由參數接受任何值,因此無法區分兩條路由。
如果用戶請求```/blog/my-first-post```,兩條路由都會match,Symfony將使用第一個被定義的路由。
要解決此問題,使用```requirements```選項來向```{page}```參數添加驗證:
```php=
Copy
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
/**
* @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"})
*/
public function list(int $page)
{
// ...
}
/**
* @Route("/blog/{slug}", name="blog_show")
*/
public function show($slug)
{
// ...
}
}
```
該```requirements```選項定義了PHP正則表達式,路由參數必須match成功才能match整個路由。在這個例子中,```\d+```是一個amtch任意長度數字的正則表達式。
>|網址|路由|參數|
>|---|---|---|
>|```/blog/2```|```blog_list```|```$page=2```|
>|```/blog/my-first-post```|```blog_show```|```$slug = my-first-post```|
>note
>在路由參數中使用正則表達式時,您可以將```utf8```路由選項設置true作為任意```.```字符match任何 UTF-8 字符,而不僅僅是單個byte。
如果您願意,可以在每個參數的內部使用語法```{parameter_name<requirements>}```。
該特性使配置更加簡潔,但在需求復雜時會降低路由可讀性:
```php=
Copy
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
/**
* @Route("/blog/{page<\d+>}", name="blog_list")
*/
public function list(int $page)
{
// ...
}
}
```
### 可選參數
在前面的範例中,的 URL```blog_list```是```/blog/{page}```。如果用戶訪問```/blog/1```,它將match成功。但是如果他們訪問```/blog```,它將不會match成功。
一旦向路由添加參數,它就必須有一個值。
可以透過為```{page}```參數添加預設值讓用戶在拜訪```/blog```時能再次成功match到```blog_list```
使用註釋時,參數的預設值會在控制器的action中被定義。
在其他配置格式中,它們使用以下選項定義```defaults```:
```php=
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
/**
* @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"})
*/
public function list(int $page = 1)
{
// ...
}
}
```
現在,當用戶訪問```/blog```時,```blog_list```路由將match```$page```預設值設定為```1```。
:::danger
您可以有多個可選參數(例如```/blog/{slug}/{page}```),但可選參數之後的所有內容都必須是可選的。例如,```/{page}/blog```是一個有效的路徑,但```page```將始終是必需的(即```/blog```不會match此路由)。
:::
如果您想在生成的 URL 中始終包含一些預設值(在前一個範例中,強制生成```/blog/1```而不是```/blog```),請在參數名稱之前添加```!```符號:```/blog/{!page}```
與```requirements```一樣,也可以使用語法```{parameter_name?default_value}```在每個參數inline使用預設值。此功能與inlinerequirements兼容,因此您可以在單個參數中inline兩者:
```php=
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
/**
* @Route("/blog/{page<\d+>?1}", name="blog_list")
*/
public function list(int $page)
{
// ...
}
}
```
:::success
要為任何參數提供預設值```null```,請在```?```符號後不添加任何內容 (例如```/blog/{page?}```)。
:::
### 參數轉換
一個常見的路由需求是將存儲在某個參數中的值(例如作為用戶 ID 的整數)轉換為另一個值(例如代表用戶的對象)。此功能稱為“參數轉換器”,僅在使用註解定義路由時可用。
如果你之前沒有執行過這個命令,現在執行它來添加對註釋和“參數轉換器”的支援:
```
composer require annotations
```
現在,保留之前的路由配置,但更改控制器操作的參數。而不是```string $slug```,添加```BlogPost $post```:
```php=
// src/Controller/BlogController.php
namespace App\Controller;
use App\Entity\BlogPost;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
// ...
/**
* @Route("/blog/{slug}", name="blog_show")
*/
public function show(BlogPost $post)
{
// $post is the object whose slug matches the routing parameter
// ...
}
}
```
如果您的控制器參數包含對物件的型態提示(範例中的```BlogPost```),“參數轉換器”使用請求參數(本範例中的```slug```)來讓資料庫請求找到物件。
如果沒有找到物件,Symfony 會自動生成 404 response。
閱讀完整的[參數轉換器](https://symfony.com/bundles/SensioFrameworkExtraBundle/current/annotations/converters.html)以了解 Symfony 提供的轉換器以及如何配置它們。
### 特殊參數
除了您自己的參數之外,路由還可以包含以下任何由 Symfony 創建的特殊參數:
- _controller
- 該參數用於確定路由match時執行哪個控制器和action。
- _format
- match的值用於設置發出請求物件的“請求格式(request format)”。這用於諸如設置```Content-Type```響應之類的事情(例如,```json```格式轉換為 ```application/json```的```Content-Type```)。
- _fragment
- 用於設置片段識別子(fragment identifier),它是 URL 選向最後一部分,以```#```符號開頭,用於標識文件的一部分。
- _locale
- 用於設置locale的請求。
您可以每個路由導入以上的屬性(除了 ```_fragment```)。
Symfony 定義了一些具有相同名稱的特殊屬性(前面下底線的屬性除外),因此您可以更輕鬆地定義它們:
```php=
// src/Controller/ArticleController.php
namespace App\Controller;
// ...
class ArticleController extends AbstractController
{
/**
* @Route(
* "/articles/{_locale}/search.{_format}",
* locale="en",
* format="html",
* requirements={
* "_locale": "en|fr",
* "_format": "html|xml",
* }
* )
*/
public function search()
{
}
}
```
### 額外參數
在```defaults```路由選項中,您可以選擇定義不包含在路由配置中的參數。
這對於將額外的參數傳遞給路由中的控制器會有很用:
```php=
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Component\Routing\Annotation\Route;
class BlogController
{
/**
* @Route("/blog/{page}", name="blog_index", defaults={"page": 1, "title": "Hello world!"})
*/
public function index(int $page, string $title)
{
// ...
}
}
```
### 路由參數中的斜線符號
路由參數可以包含除了```/```符號以外的任何值,因為```/```用於分隔 URL 的不同部分。
舉例來說,如果路由```/share/{token}```其中的```token```包含```/```符號,則此路由將不會有match。
一種可能的解決方案是將參數要求更改得更為寬鬆:
```php=
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController
{
/**
* @Route("/share/{token}", name="share", requirements={"token"=".+"})
*/
public function share($token)
{
// ...
}
}
```
>note
如果路由定義了多個參數,並且將這個寬鬆的正則表達式應用於所有參數,結果將不如預期。例如,如果路由定義是```/share/{path}/{token}```,path則將包含其內容和token,並且```token```將為空。
>note
如果路由包含特殊```{_format}```參數,則不應使用```.+```允許```/```的參數要求。如果pattern是```/share/{token}.{_format}```並且```{token}```允許任何字符,則```/share/foo/bar.json```URL 將視為```foo/bar.json```token,format將為空。這可以通過將```.+```要求替換```[^.]+```為允許除點以外的任何字符來解決。
## 路由群組和前綴(prefixes)
一組路由共享一些選項是很常見的(例如所有與blog相關的路由都以```/blog```開頭)這就是為什麼 Symfony 包含一個共享路由配置的功能。
將路由定義為註解時,將通用配置放在```@Route```控制器class的註解中。在其他路由格式中,在定義普通的配置會使用某些選向來導入路由。
```php=
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/blog", requirements={"_locale": "en|es|fr"}, name="blog_")
*/
class BlogController
{
/**
* @Route("/{_locale}", name="index")
*/
public function index()
{
// ...
}
/**
* @Route("/{_locale}/posts/{slug}", name="show")
*/
public function show(Post $post)
{
// ...
}
}
```
在此範例中,```index()```action的路由會被稱為```blog_index```其URL會是```/blog/```
```show()```action的路由會被稱為```blog_show```其URL會是```/blog/{_locale}/posts/{slug}```
這兩個路由還將驗證```_locale```參數是否與class註釋中定義的正則表達式match。
>note
Symfony 可以從不同來源導入路由 ,你甚至可以創建自己的路由加載器。
## 獲取路由名稱和參數
```uest```ymfony所創建,並且出輸出到所有路由配置(包含名稱及參數)的"request parameters"
你可以透過控制器在```request```物件中取得資訊
```php=
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
/**
* @Route("/blog", name="blog_list")
*/
public function list(Request $request)
{
// ...
$routeName = $request->attributes->get('_route');
$routeParameters = $request->attributes->get('_route_params');
// use this to get all the available attributes (not only routing ones):
$allAttributes = $request->attributes->all();
}
}
```
也可以從注入```request_stack```此服務來[獲得](https://symfony.com/doc/4.3/service_container/request.html)在服務當中的request物件。
在模板中,使用Twig 全域app [變量](https://symfony.com/doc/4.3/templates.html#twig-app-variable)來獲取請求及其屬性:
```php=
{% set route_name = app.request.attributes.get('_route') %}
{% set route_parameters = app.request.attributes.get('_route_params') %}
{# use this to get all the available attributes (not only routing ones) #}
{% set all_attributes = app.request.attributes.all %}
```
## 特殊 route
Symfony 定義了一些特殊的控制器來呈現模板並從路由配置重定向到其他路由,因此您不必創建控制器操action。
### 透過路由重新導向到 URL 和路由
使用```RedirectController```重新導向到其他路由 ( ```redirectAction```) 和 URLs (```urlRedirectAction```):
```php=
# config/routes.yaml
doc_shortcut:
path: /doc
controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction
defaults:
route: 'doc_page'
# optionally you can define some arguments passed to the route
page: 'index'
version: 'current'
# redirections are temporary by default (code 302) but you can make them permanent (code 301)
permanent: true
# add this to keep the original query string parameters when redirecting
keepQueryParams: true
# add this to keep the HTTP method when redirecting. The redirect status changes
# * for temporary redirects, it uses the 307 status code instead of 302
# * for permanent redirects, it uses the 308 status code instead of 301
keepRequestMethod: true
legacy_doc:
path: /legacy/doc
controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction
defaults:
# this value can be an absolute path or an absolute URL
path: 'https://legacy.example.com/doc'
permanent: true
```
:::success
Symfony 還提供了一些實用功能來 [重新導向到控制器內部](https://symfony.com/doc/4.3/controller.html#controller-redirect)
:::
### 使用```/```重定向 URL
從歷史上看,URL 遵循 UNIX 原則,為目錄添加```/```(例如```https://example.com/foo/```)而且透過刪除它們以引用文件(```https://example.com/foo```)。
雖然為兩個 URL 提供不同的內容是可以的,但現在將兩個 URL 視為相同的 URL 並在它們之間重新導向是很常見的。
Symfony 遵循此邏輯在帶和不帶```/```的URL 之間重新導向(但僅限於```GET```和```HEAD```請求):
|路由網址| 如果請求的 URL 是 /foo| 如果請求的 URL 是 /foo/|
|---|---|---|
|```/foo```|match(```200```狀態響應)| 讓```301```重新導向到```/foo```|
|```/foo/``` |它讓```301```重新導向到```/foo/```| 它match(```200```狀態響應)|
## 在地化(localized)路由 (i18n)
如果您的應用程式被翻譯成多種語言,則每個路由可以為每個翻譯語言環境定義不同的 URL 。這避免了重複路由的需要,這也減少了潛在的錯誤:
```php=
// src/Controller/CompanyController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class CompanyController extends AbstractController
{
/**
* @Route({
* "en": "/about-us",
* "nl": "/over-ons"
* }, name="about_us")
*/
public function about()
{
// ...
}
}
```
當match在地化路由時,Symfony在整個請求期間會自動使用相同的語言環境。
:::success
當應用程式使用完整的“語言+地區”語言環境(例如fr_FR, fr_BE)時,如果所有相關語言環境中的 URL 都相同,則路由可以僅使用語言部分(例如fr)以避免重複相同的 URL。
:::
應用程式國際化的一個常見要求,是為所有路由添加區域設置前綴(prefix)。可以透過為每個語言環境定義不同的前綴來完成(如果您願意,還可以為預設語言環境設置一個空前綴):
```php=
# config/routes/annotations.yaml
controllers:
resource: '../src/Controller/'
type: annotation
prefix:
en: '' # don't prefix URLs for English, the default locale
nl: '/nl'
```
## 生成 URL
路由系統是雙向的:
1. 它們將 URL 與控制器相戶連接(如前幾節所述)
2. 它們為給定的路由生成 URL。從路由生成 URL 使您無需```<a href="..."> ```在 HTML 模板中手動寫入值。此外,如果某些路由的 URL 發生變化,您只需更新路由配置,所有link都會更新。
要生成 URL,需要指定路由的名稱(例如```blog_show```)和路由定義的參數值(例如 ```slug = my-blog-post```)。
因此,每個路由都有一個內部名稱,該名稱在應用程式中必須是唯一的。
如果您沒有使用該內部名稱來生成```name```的選項,Symfony 會根據控制器和action生成一個自動名稱。
### 在控制器中生成 URL
如果您的控制器從```AbstractController```擴展,請使用```generateUrl()```輔助系統:
```php=
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class BlogController extends AbstractController
{
/**
* @Route("/blog", name="blog_list")
*/
public function list()
{
// ...
// generate a URL with no route arguments
$signUpPage = $this->generateUrl('sign_up');
// generate a URL with route arguments
$userProfilePage = $this->generateUrl('user_profile', [
'username' => $user->getUsername(),
]);
// generated URLs are "absolute paths" by default. Pass a third optional
// argument to generate different URLs (e.g. an "absolute URL")
$signUpPage = $this->generateUrl('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
// when a route is localized, Symfony uses by default the current request locale
// pass a different '_locale' value if you want to set the locale explicitly
$signUpPageInDutch = $this->generateUrl('sign_up', ['_locale' => 'nl']);
}
}
```
</br>
>note
>如果您向該```generateUrl()```方法傳遞一些不屬於路由定義的參數,它們>將作為查詢字串包含在生成的 URL 中:
>```php=
>$this->generateUrl('blog', ['page' => 2, 'category' => >'Symfony']);
>// the 'blog' route only defines the 'page' parameter; the >generated URL is:
>// /blog/2?category=Symfony
>```
如果您的控制器沒有從```AbstractController```擴展,需要根據下一步的指示在你的控制器中獲取服務。
### 在服務中生成 URL
在你自己symfony的服務中注入```rounter```並且使用```generate()```。使用[服務自動裝配](https://symfony.com/doc/4.3/service_container/autowiring.html)時, 您只需要在服務構造函數中添加一個參數並使用```UrlGeneratorInterface```class對其進行類型提示(type-hint):
```php=
// src/Service/SomeService.php
namespace App\Service;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class SomeService
{
private $router;
public function __construct(UrlGeneratorInterface $router)
{
$this->router = $router;
}
public function someMethod()
{
// ...
// generate a URL with no route arguments
$signUpPage = $this->router->generate('sign_up');
// generate a URL with route arguments
$userProfilePage = $this->router->generate('user_profile', [
'username' => $user->getUsername(),
]);
// generated URLs are "absolute paths" by default. Pass a third optional
// argument to generate different URLs (e.g. an "absolute URL")
$signUpPage = $this->router->generate('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
// when a route is localized, Symfony uses by default the current request locale
// pass a different '_locale' value if you want to set the locale explicitly
$signUpPageInDutch = $this->router->generate('sign_up', ['_locale' => 'nl']);
}
}
```
### 在模板中生成 URL
在[模板文件](https://hackmd.io/Vfl3opHWQemzOuo2xLoMPw)中會介紹。
### 在 JavaScript 中生成 URL
如果您的 JavaScript code包含在 Twig 模板中,您可以使用 ```path()```和```url()``` Twig 函數來生成 URL 並將它們存儲在 JavaScript 變量中。
```escape()```函數來需要任意non-JavaScript-safe的值來轉譯:
```php=
<script>
const route = "{{ path('blog_show', {slug: 'my-blog-post'})|escape('js') }}";
</script>
```
如果需要動態生成 URL 或者使用純 JavaScript code,則此解決方案是沒有作用的。在這些情況下,請考慮使用[FOSJsRoutingBundle](https://github.com/FriendsOfSymfony/FOSJsRoutingBundle)。
### 在指令(commands)中生成 URL
在指令中生成 URL 與在服務中生成 URL 的工作方式相同。
唯一的差別是命令不在 HTTP 上下文中執行,因此它們無權訪問 HTTP 請求。
實際上,這意味著如果您生成絕對 URL,您將獲得```http://localhost/主機```名而不是您的真實主機名(host name)。
解決方案是在生成 URL 時透過指令配置"request context"。可以為所有指令全域配置此文件內容:
```php=
# config/services.yaml
parameters:
router.request_context.host: 'example.org'
router.request_context.base_url: 'my/path'
asset.request_context.base_path: '%router.request_context.base_url%'
```
此訊息也可以按指令配置:
```php=
// src/Command/SomeCommand.php
namespace App\Command;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
// ...
class SomeCommand extends Command
{
private $router;
public function __construct(RouterInterface $router)
{
parent::__construct();
$this->router = $router;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
// these values override any global configuration
$context = $this->router->getContext();
$context->setHost('example.com');
$context->setBaseUrl('my/path');
// generate a URL with no route arguments
$signUpPage = $this->router->generate('sign_up');
// generate a URL with route arguments
$userProfilePage = $this->router->generate('user_profile', [
'username' => $user->getUsername(),
]);
// generated URLs are "absolute paths" by default. Pass a third optional
// argument to generate different URLs (e.g. an "absolute URL")
$signUpPage = $this->router->generate('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
// when a route is localized, Symfony uses by default the current request locale
// pass a different '_locale' value if you want to set the locale explicitly
$signUpPageInDutch = $this->router->generate('sign_up', ['_locale' => 'nl']);
// ...
}
}
```
### 檢查路由是否存在
在高度動態的應用程式中,可能需要在使用路由生成 URL 之前檢查路由是否存在。
在這些情況下,不要使用```getRouteCollection()```方法,因為它會重新生成路由cahce並減慢應用程式的速度。
可以嘗試生成 URL 並捕獲路由不存在時拋出的 ```RouteNotFoundException```:
```php=
use Symfony\Component\Routing\Exception\RouteNotFoundException;
// ...
try {
$url = $this->router->generate($routeName, $routeParameters);
} catch (RouteNotFoundException $e) {
// the route is not defined...
}
```
### 在生成的 URL 上強制使用 HTTPS
預設情況下,生成的 URL 使用與當前請求相同的 HTTP 方案。
在沒有 HTTP 請求的控制台指令中,http預設使用 URL 。
您可以根據指令(透過路由器的```getContext()```方法)或使用以下配置參數全域更改此設置:
```php=
# config/services.yaml
parameters:
router.request_context.scheme: 'https'
asset.request_context.secure: true
```
在控制台指令之外,使用該```schemes```選項明確定義每個路由的方案:
```php=
// src/Controller/SecurityController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class SecurityController extends AbstractController
{
/**
* @Route("/login", name="login", schemes={"https"})
*/
public function login()
{
// ...
}
}
```
為login路由生成的 URL將始終使用 HTTPS。
這意味著在使用```path()``` Twig 函數生成 URL 時,如果原始請求的 HTTP scheme 與路由使用的 scheme 不同,您可能會得到絕對 URL 而不是相對 URL:
```=php
{# if the current scheme is HTTPS, generates a relative URL: /login #}
{{ path('login') }}
{# if the current scheme is HTTP, generates an absolute URL to change
the scheme: https://example.com/login #}
{{ path('login') }}
```
對傳入請求也強制執行要求。
如果嘗試使用```/login```URL訪問HTTP,您將自動重定向到相同的 URL,但使用 HTTPS 則不會。
如果要強制一組路由使用HTTPS,可以在導入時定義預設```scheme```。以下範例在定義為註釋的所有路由上強制使用 HTTPS:
```php=
# config/routes/annotations.yaml
controllers:
resource: '../src/Controller/'
type: annotation
defaults:
schemes: [https]
```
>note
>security component提供了[另一種](https://symfony.com/doc/4.3/security/force_https.html)透過```requires_channel```設置強制執行 HTTP 或 HTTPS的方法。
## 故障排除
以下是您在使用路由時可能會看到的一些常見錯誤:
控制器“App\Controller\BlogController::show()”要求您為“$slug”參數提供一個值。
當您的控制器方法有一個參數(例如```$slug```)時,就會發生這種情況:
```php=
public function show($slug)
{
// ...
}
```
但是,你的路由路徑並沒有```{slug}```參數(例如,它是 /blog/show)。
將```{slug}```添加到你的路由路徑:```/blog/show/{slug}```或給參數一個預設值(即```$slug = null```)。
</br>
缺少一些必需參數(“slug”)以生成路由“blog_show”的 URL。
這意味著您正在嘗試生成```blog_show```路由的 URL,但沒有傳遞```slug```值(這是必需的,因為```{slug}```在路由路徑中有一個參數)。
要解決此問題,請```slug```在生成路由時傳遞一個值:
```php=
$this->generateUrl('blog_show', ['slug' => 'slug-value']);
// or, in Twig
// {{ path('blog_show', {slug: 'slug-value'}) }}
```