路由
當您的應用程式收到請求時,它會呼叫控制器動作以產生回應。路由設定定義了每個傳入 URL 要執行的動作。它也提供了其他有用的功能,例如產生 SEO 友善的 URL (例如 /read/intro-to-symfony
而不是 index.php?article_id=57
)。
建立路由
路由可以使用 YAML、XML、PHP 或屬性來設定。所有格式都提供相同的功能和效能,因此請選擇您喜歡的格式。Symfony 建議使用屬性,因為將路由和控制器放在同一個地方很方便。
以屬性建立路由
PHP 屬性允許在與這些路由關聯的控制器程式碼旁邊定義路由。屬性在 PHP 8 和更高版本中是原生的,因此您可以立即使用它們。
在使用它們之前,您需要為您的專案新增一些設定。如果您的專案使用 Symfony Flex,則已為您建立此檔案。否則,請手動建立以下檔案
1 2 3 4 5 6 7 8 9 10
# config/routes/attributes.yaml
controllers:
resource:
path: ../../src/Controller/
namespace: App\Controller
type: attribute
kernel:
resource: App\Kernel
type: attribute
此設定告訴 Symfony 尋找在宣告於 App\Controller
命名空間且儲存在遵循 PSR-4 標準的 src/Controller/
目錄中的類別上定義為屬性的路由。核心也可以作為控制器,這對於將 Symfony 用作微框架的小型應用程式特別有用。
假設您想在您的應用程式中為 /blog
URL 定義路由。為此,請建立如下的控制器類別
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog', name: 'blog_list')]
public function list(): Response
{
// ...
}
}
此設定定義了一個名為 blog_list
的路由,當使用者請求 /blog
URL 時會比對。當比對發生時,應用程式會執行 BlogController
類別的 list()
方法。
注意
在比對路由時,URL 的查詢字串不會被考慮。在本範例中,諸如 /blog?foo=bar
和 /blog?foo=bar&bar=foo
之類的 URL 也會比對到 blog_list
路由。
警告
如果您在同一個檔案中定義多個 PHP 類別,Symfony 只會載入第一個類別的路由,而忽略所有其他路由。
路由名稱 (blog_list
) 目前並不重要,但在稍後產生 URL 時將至關重要。您只需要記住,每個路由名稱在應用程式中都必須是唯一的。
在 YAML、XML 或 PHP 檔案中建立路由
除了在控制器類別中定義路由之外,您還可以在單獨的 YAML、XML 或 PHP 檔案中定義它們。主要優點是它們不需要任何額外的依賴關係。主要的缺點是當您檢查某些控制器動作的路由時,必須使用多個檔案。
以下範例示範如何在 YAML/XML/PHP 中定義一個名為 blog_list
的路由,該路由將 /blog
URL 與 BlogController
的 list()
動作關聯起來
1 2 3 4 5 6 7 8 9
# config/routes.yaml
blog_list:
path: /blog
# the controller value has the format 'controller_class::method_name'
controller: App\Controller\BlogController::list
# if the action is implemented as the __invoke() method of the
# controller class, you can skip the '::method_name' part:
# controller: App\Controller\BlogController
注意
預設情況下,Symfony 會載入以 YAML 和 PHP 格式定義的路由。如果您以 XML 格式定義路由,則需要更新 src/Kernel.php 檔案。
比對 HTTP 方法
預設情況下,路由會比對任何 HTTP 動詞 (GET
、POST
、PUT
等)。使用 methods
選項來限制每個路由應回應的動詞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// src/Controller/BlogApiController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogApiController extends AbstractController
{
#[Route('/api/posts/{id}', methods: ['GET', 'HEAD'])]
public function show(int $id): Response
{
// ... return a JSON response with the post
}
#[Route('/api/posts/{id}', methods: ['PUT'])]
public function edit(int $id): Response
{
// ... edit a post
}
}
提示
HTML 表單僅支援 GET
和 POST
方法。如果您從 HTML 表單使用不同的方法呼叫路由,請新增一個名為 _method
的隱藏欄位,並指定要使用的方法 (例如 <input type="hidden" name="_method" value="PUT">
)。如果您使用 Symfony Forms 建立表單,當 framework.http_method_override 選項為 true
時,這會自動為您完成。
比對表達式
如果您需要根據一些任意的比對邏輯來比對某些路由,請使用 condition
選項
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\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(): Response
{
// ...
}
#[Route(
'/posts/{id}',
name: 'post_show',
// expressions can retrieve route parameter values using the "params" variable
condition: "params['id'] < 1000"
)]
public function showPost(int $id): Response
{
// ... return a JSON response with the post
}
}
condition
選項的值是一個使用任何有效的表達式語言語法的表達式,並且可以使用 Symfony 建立的任何這些變數
context
- RequestContext 的一個實例,它保存了關於正在比對的路由的最基本資訊。
request
- 代表當前請求的 Symfony Request 物件。
params
- 當前路由的已比對路由參數陣列。
您也可以使用以下函數
env(字串 $name)
- 使用環境變數處理器傳回變數的值
service(字串 $alias)
-
傳回路由條件服務。
首先,將
#[AsRoutingConditionService]
屬性或routing.condition_service
標籤新增到您想要在路由條件中使用的服務1 2 3 4 5 6 7 8 9 10 11
use Symfony\Bundle\FrameworkBundle\Routing\Attribute\AsRoutingConditionService; use Symfony\Component\HttpFoundation\Request; #[AsRoutingConditionService(alias: 'route_checker')] class RouteChecker { public function check(Request $request): bool { // ... } }
然後,使用
service()
函數在條件內參照該服務1 2 3 4
// Controller (using an alias): #[Route(condition: "service('route_checker').check(request)")] // Or without alias: #[Route(condition: "service('App\\\Service\\\RouteChecker').check(request)")]
在幕後,表達式會編譯成原始 PHP。因此,使用 condition
鍵不會造成額外的開銷,除了執行底層 PHP 所需的時間。
警告
產生 URL 時,條件不會被考慮在內 (這將在本文稍後說明)。
偵錯路由
隨著您的應用程式成長,您最終將擁有大量的路由。Symfony 包含一些命令來協助您偵錯路由問題。首先,debug:router
命令會以 Symfony 評估它們的相同順序,列出您的所有應用程式路由
1 2 3 4 5 6 7 8 9 10 11 12
$ 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}
---------------- ------- ------- ----- --------------------------------------------
將某些路由的名稱 (或部分名稱) 傳遞給此引數以列印路由詳細資訊
1 2 3 4 5 6 7 8 9 10 11
$ 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 |
+-------------+---------------------------------------------------------+
提示
使用 --show-aliases
選項來顯示給定路由的所有可用別名。
另一個命令稱為 router:match
,它會顯示哪個路由將比對給定的 URL。它有助於找出為什麼某些 URL 沒有執行您預期的控制器動作
1 2 3
$ 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}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
// ...
#[Route('/blog/{slug}', name: 'blog_show')]
public function show(string $slug): Response
{
// $slug will equal the dynamic part of the URL
// e.g. at /blog/yay-routing, then $slug='yay-routing'
// ...
}
}
變數部分的名稱 ({slug}
在本範例中) 用於建立一個 PHP 變數,其中儲存該路由內容並傳遞給控制器。如果使用者造訪 /blog/my-first-post
URL,Symfony 會執行 BlogController
類別中的 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
,則兩個路由都將比對,而 Symfony 將使用首先定義的路由。為了修正此問題,請使用 requirements
選項為 {page}
參數新增一些驗證
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])]
public function list(int $page): Response
{
// ...
}
#[Route('/blog/{slug}', name: 'blog_show')]
public function show($slug): Response
{
// ...
}
}
requirements
選項定義了路由參數必須比對的 PHP 正則表達式,整個路由才能比對。在本範例中,\d+
是一個正則表達式,它比對任何長度的數字。現在
URL | 路由 | 參數 |
---|---|---|
/blog/2 |
blog_list |
$page = 2 |
/blog/my-first-post |
blog_show |
$slug = my-first-post |
提示
Requirement 列舉包含常用正則表達式常數的集合,例如數字、日期和 UUID,可以用作路由參數需求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Requirement\Requirement;
class BlogController extends AbstractController
{
#[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => Requirement::DIGITS])]
public function list(int $page): Response
{
// ...
}
}
提示
路由需求 (以及路由路徑) 可以包含設定參數,這對於定義一次複雜的正則表達式並在多個路由中重複使用它們很有用。
提示
參數也支援 PCRE Unicode 屬性,這些屬性是比對通用字元類型的跳脫序列。例如,\p{Lu}
比對任何語言中的任何大寫字元,\p{Greek}
比對任何希臘字元等等。
注意
在路由參數中使用正則表達式時,您可以將 utf8
路由選項設定為 true
,使任何 .
字元比對任何 UTF-8 字元,而不僅僅是一個位元組。
如果您願意,可以使用語法 {parameter_name<requirements>}
將需求內嵌到每個參數中。此功能使設定更簡潔,但當需求複雜時,可能會降低路由可讀性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog/{page<\d+>}', name: 'blog_list')]
public function list(int $page): Response
{
// ...
}
}
選用參數
在先前的範例中,blog_list
的 URL 是 /blog/{page}
。如果使用者造訪 /blog/1
,它將比對。但是如果他們造訪 /blog
,它將不會比對。一旦您將參數新增到路由,它就必須具有值。
您可以透過為 {page}
參數新增預設值,使 blog_list
在使用者造訪 /blog
時再次比對。當使用屬性時,預設值在控制器動作的引數中定義。在其他設定格式中,它們使用 defaults
選項定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])]
public function list(int $page = 1): Response
{
// ...
}
}
現在,當使用者造訪 /blog
時,blog_list
路由將比對,而 $page
將預設為值 1
。
警告
您可以有多個選用參數 (例如 /blog/{slug}/{page}
),但選用參數之後的所有內容都必須是選用的。例如,/{page}/blog
是一個有效的路徑,但 page
將始終是必需的 (即 /blog
將不比對此路由)。
如果您想始終在產生的 URL 中包含一些預設值 (例如,在先前的範例中強制產生 /blog/1
而不是 /blog
),請在參數名稱前新增 !
字元:/blog/{!page}
與需求一樣,預設值也可以使用語法 {parameter_name?default_value}
內嵌在每個參數中。此功能與內嵌需求相容,因此您可以將兩者都內嵌在單個參數中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog/{page<\d+>?1}', name: 'blog_list')]
public function list(int $page): Response
{
// ...
}
}
提示
若要為任何參數提供 null
預設值,請在 ?
字元後不新增任何內容 (例如 /blog/{page?}
)。如果您這樣做,請不要忘記更新相關控制器引數的類型,以允許傳遞 null
值 (例如,將 int $page
替換為 ?int $page
)。
優先順序參數
Symfony 按照路由定義的順序評估路由。如果路由的路徑比對許多不同的模式,則可能會阻止其他路由被比對。在 YAML 和 XML 中,您可以在設定檔中上下移動路由定義,以控制其優先順序。在定義為 PHP 屬性的路由中,這要困難得多,因此您可以在這些路由中設定選用的 priority
參數來控制其優先順序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
/**
* This route has a greedy pattern and is defined first.
*/
#[Route('/blog/{slug}', name: 'blog_show')]
public function show(string $slug): Response
{
// ...
}
/**
* This route could not be matched without defining a higher priority than 0.
*/
#[Route('/blog/list', name: 'blog_list', priority: 2)]
public function list(): Response
{
// ...
}
}
priority
參數預期一個整數值。優先順序較高的路由會在優先順序較低的路由之前排序。未定義時的預設值為 0
。
參數轉換
常見的路由需求是將儲存在某些參數中的值 (例如,充當使用者 ID 的整數) 轉換為另一個值 (例如,代表使用者的物件)。此功能稱為「參數轉換器」。
現在,保留先前的路由設定,但變更控制器動作的引數。不要使用 string $slug
,而是新增 BlogPost $post
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// src/Controller/BlogController.php
namespace App\Controller;
use App\Entity\BlogPost;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
// ...
#[Route('/blog/{slug}', name: 'blog_show')]
public function show(BlogPost $post): Response
{
// $post is the object whose slug matches the routing parameter
// ...
}
}
如果您的控制器引數包含物件的類型提示 (在本例中為 BlogPost
),則「參數轉換器」會發出資料庫請求,以使用請求參數 (在本例中為 slug
) 尋找物件。如果找不到物件,Symfony 會自動產生 404 回應。
請查看 Doctrine 參數轉換文件,以了解可用於自訂用於從路由參數中提取物件的資料庫查詢的 #[MapEntity]
屬性。
枚舉支援參數
您可以使用 PHP 枚舉支援作為路由參數,因為 Symfony 會自動將它們轉換為其純量值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// src/Controller/OrderController.php
namespace App\Controller;
use App\Enum\OrderStatusEnum;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class OrderController extends AbstractController
{
#[Route('/orders/list/{status}', name: 'list_orders_by_status')]
public function list(OrderStatusEnum $status = OrderStatusEnum::Paid): Response
{
// ...
}
}
特殊參數
除了您自己的參數之外,路由還可以包含 Symfony 建立的以下任何特殊參數
_controller
- 此參數用於確定在路由比對時執行哪個控制器和動作。
_format
- 比對的值用於設定 Request 物件的「請求格式」。這用於設定回應的
Content-Type
等項目 (例如,json
格式會轉換為Content-Type
application/json
)。 _fragment
- 用於設定片段識別碼,它是 URL 的選用最後一部分,以
#
字元開頭,用於識別文件的某一部分。 _locale
- 用於在請求上設定語系。
您可以將這些屬性 (除了 _fragment
) 同時包含在個別路由和路由匯入中。Symfony 定義了一些具有相同名稱 (除了前導底線) 的特殊屬性,因此您可以更輕鬆地定義它們
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// src/Controller/ArticleController.php
namespace App\Controller;
// ...
class ArticleController extends AbstractController
{
#[Route(
path: '/articles/{_locale}/search.{_format}',
locale: 'en',
format: 'html',
requirements: [
'_locale' => 'en|fr',
'_format' => 'html|xml',
],
)]
public function search(): Response
{
}
}
額外參數
在路由的 defaults
選項中,您可以選擇性地定義未包含在路由設定中的參數。這對於將額外的引數傳遞給路由的控制器很有用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog/{page}', name: 'blog_index', defaults: ['page' => 1, 'title' => 'Hello world!'])]
public function index(int $page, string $title): Response
{
// ...
}
}
路由參數中的斜線字元
路由參數可以包含除 /
斜線字元以外的任何值,因為它是用於分隔 URL 不同部分的字元。例如,如果 /share/{token}
路由中的 token
值包含 /
字元,則此路由將不會比對。
一個可能的解決方案是變更參數需求以使其更寬鬆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class DefaultController extends AbstractController
{
#[Route('/share/{token}', name: 'share', requirements: ['token' => '.+'])]
public function share($token): Response
{
// ...
}
}
注意
如果路由定義了多個參數,並且您將此寬鬆的正則表達式應用於所有參數,則可能會得到意外的結果。例如,如果路由定義為 /share/{path}/{token}
,並且 path
和 token
都接受 /
,則 token
將僅取得最後一部分,而其餘部分由 path
比對。
注意
如果路由包含特殊的 {_format}
參數,則不應對允許斜線的參數使用 .+
需求。例如,如果模式為 /share/{token}.{_format}
且 {token}
允許任何字元,則 /share/foo/bar.json
URL 將把 foo/bar.json
視為 token,而格式將為空。這可以透過將 .+
需求替換為 [^.]+
來解決,以允許除點以外的任何字元。
路由別名
路由別名允許您為同一個路由設定多個名稱
1 2 3
# config/routes.yaml
new_route_name:
alias: original_route_name
在此範例中,應用程式可以使用 original_route_name
和 new_route_name
這兩個路由,並產生相同的結果。
棄用路由別名
如果某些路由別名不應再被使用(因為它已過時,或者您決定不再維護它),您可以宣告其定義為已棄用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
new_route_name:
alias: original_route_name
# this outputs the following generic deprecation message:
# Since acme/package 1.2: The "new_route_name" route alias is deprecated. You should stop using it, as it will be removed in the future.
deprecated:
package: 'acme/package'
version: '1.2'
# you can also define a custom deprecation message (%alias_id% placeholder is available)
deprecated:
package: 'acme/package'
version: '1.2'
message: 'The "%alias_id%" route alias is deprecated. Do not use it anymore.'
在此範例中,每次使用 new_route_name
別名時,都會觸發棄用警告,建議您停止使用該別名。
此訊息實際上是一個訊息範本,它會將 %alias_id%
佔位符的出現次數替換為路由別名名稱。您必須在您的範本中至少包含一次 %alias_id%
佔位符。
路由群組和前綴
一群路由共用某些選項是很常見的(例如,所有與部落格相關的路由都以 /blog
開頭)。這就是 Symfony 包含共用路由設定功能的原因。
當以屬性定義路由時,請將通用設定放在控制器類別的 #[Route]
屬性中。在其他路由格式中,匯入路由時請使用選項來定義通用設定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/blog', requirements: ['_locale' => 'en|es|fr'], name: 'blog_')]
class BlogController extends AbstractController
{
#[Route('/{_locale}', name: 'index')]
public function index(): Response
{
// ...
}
#[Route('/{_locale}/posts/{slug}', name: 'show')]
public function show(string $slug): Response
{
// ...
}
}
警告
exclude
選項僅在 resource
值為 glob 字串時才有效。如果您使用常規字串(例如 '../src/Controller'
),則 exclude
值將被忽略。
在此範例中,index()
動作的路由將被稱為 blog_index
,其 URL 將為 /blog/{_locale}
。show()
動作的路由將被稱為 blog_show
,其 URL 將為 /blog/{_locale}/posts/{slug}
。這兩個路由也都會驗證 _locale
參數是否符合類別屬性中定義的正規表示式。
注意
如果任何前綴路由定義了空路徑,Symfony 會在其後添加一個尾部斜線。在先前的範例中,以 /blog
為前綴的空路徑將產生 /blog/
URL。如果您想避免這種行為,請將 trailing_slash_on_root
選項設定為 false
(當使用 PHP 屬性時,此選項不可用)
1 2 3 4 5 6 7
# config/routes/attributes.yaml
controllers:
resource: '../../src/Controller/'
type: attribute
prefix: '/blog'
trailing_slash_on_root: false
# ...
另請參閱
Symfony 可以從不同來源匯入路由,您甚至可以建立自己的路由載入器。
取得路由名稱和參數
Symfony 建立的 Request
物件將所有路由設定(例如名稱和參數)儲存在「請求屬性」中。您可以在控制器中透過 Request
物件取得此資訊
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog', name: 'blog_list')]
public function list(Request $request): Response
{
$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();
// ...
}
}
在服務中,您可以透過注入 RequestStack 服務來取得此資訊。在範本中,使用 Twig 全域應用程式變數來取得目前的路由名稱 (app.current_route
) 及其參數 (app.current_route_parameters
)。
特殊路由
Symfony 定義了一些特殊的控制器,用於從路由設定中渲染範本和重新導向到其他路由,因此您不必建立控制器動作。
直接從路由重新導向至 URL 和路由
使用 RedirectController
重新導向到其他路由和 URL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
# config/routes.yaml
doc_shortcut:
path: /doc
controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController
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
# add this to remove all original route attributes when redirecting
ignoreAttributes: true
# or specify which attributes to ignore:
# ignoreAttributes: ['offset', 'limit']
legacy_doc:
path: /legacy/doc
controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController
defaults:
# this value can be an absolute path or an absolute URL
path: 'https://legacy.example.com/doc'
permanent: true
提示
Symfony 也提供了一些實用工具,用於在控制器內部重新導向
重新導向帶有尾部斜線的 URL
從歷史上看,URL 一直遵循 UNIX 約定,為目錄添加尾部斜線(例如 https://example.com/foo/
),並移除它們以指向檔案(https://example.com/foo
)。雖然為這兩個 URL 提供不同的內容是可以的,但如今常見的做法是將這兩個 URL 視為相同的 URL 並在它們之間重新導向。
Symfony 遵循此邏輯在帶有和不帶有尾部斜線的 URL 之間重新導向(但僅適用於 GET
和 HEAD
請求)
路由 URL | 如果請求的 URL 是 /foo |
如果請求的 URL 是 /foo/ |
---|---|---|
/foo |
它匹配(200 狀態回應) |
它會進行 301 重新導向到 /foo |
/foo/ |
它會進行 301 重新導向到 /foo/ |
它匹配(200 狀態回應) |
子網域路由
路由可以設定 host
選項,以要求傳入請求的 HTTP 主機符合某些特定值。在以下範例中,這兩個路由都匹配相同的路徑 (/
),但其中一個路由僅回應特定的主機名稱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// src/Controller/MainController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class MainController extends AbstractController
{
#[Route('/', name: 'mobile_homepage', host: 'm.example.com')]
public function mobileHomepage(): Response
{
// ...
}
#[Route('/', name: 'homepage')]
public function homepage(): Response
{
// ...
}
}
host
選項的值可以包含參數(這在多租戶應用程式中很有用),並且這些參數也可以使用 requirements
進行驗證
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// src/Controller/MainController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class MainController extends AbstractController
{
#[Route(
'/',
name: 'mobile_homepage',
host: '{subdomain}.example.com',
defaults: ['subdomain' => 'm'],
requirements: ['subdomain' => 'm|mobile'],
)]
public function mobileHomepage(): Response
{
// ...
}
#[Route('/', name: 'homepage')]
public function homepage(): Response
{
// ...
}
}
在上面的範例中,subdomain
參數定義了一個預設值,因為否則每次使用這些路由產生 URL 時,您都需要包含子網域值。
提示
當匯入路由以使它們都要求該主機名稱時,您也可以設定 host
選項。
注意
當使用子網域路由時,您必須在功能測試中設定 Host
HTTP 標頭,否則路由將不匹配
1 2 3 4 5 6 7 8 9
$crawler = $client->request(
'GET',
'/',
[],
[],
['HTTP_HOST' => 'm.example.com']
// or get the value from some configuration parameter:
// ['HTTP_HOST' => 'm.'.$client->getContainer()->getParameter('domain')]
);
提示
您也可以在 host
選項中使用內聯預設值和需求格式:{subdomain<m|mobile>?m}.example.com
本地化路由 (i18n)
如果您的應用程式被翻譯成多種語言,則每個路由可以為每個翻譯語系定義不同的 URL。這避免了重複路由的需求,也減少了潛在的錯誤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Controller/CompanyController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class CompanyController extends AbstractController
{
#[Route(path: [
'en' => '/about-us',
'nl' => '/over-ons'
], name: 'about_us')]
public function about(): Response
{
// ...
}
}
注意
當使用 PHP 屬性進行本地化路由時,您必須使用 path
具名參數來指定路徑陣列。
當本地化路由匹配時,Symfony 會在整個請求期間自動使用相同的語系。
提示
當應用程式使用完整的「語言 + 區域」語系(例如 fr_FR
、fr_BE
)時,如果所有相關語系中的 URL 都相同,則路由可以僅使用語言部分(例如 fr
)以避免重複相同的 URL。
國際化應用程式的常見要求是以語系為所有路由添加前綴。這可以透過為每個語系定義不同的前綴來完成(如果您願意,也可以為您的預設語系設定空前綴)
1 2 3 4 5 6 7
# config/routes/attributes.yaml
controllers:
resource: '../../src/Controller/'
type: attribute
prefix:
en: '' # don't prefix URLs for English, the default locale
nl: '/nl'
注意
如果正在匯入的路由在其自身的定義中包含特殊的 _locale 參數,Symfony 將僅針對該語系匯入它,而不會針對其他已設定的語系前綴匯入。
例如,如果路由在其定義中包含 locale: 'en'
,並且它正在以 en
(前綴:空) 和 nl
(前綴:/nl
) 語系匯入,則該路由將僅在 en
語系中可用,而不在 nl
語系中可用。
另一個常見的要求是根據語系將網站託管在不同的網域上。這可以透過為每個語系定義不同的主機來完成。
1 2 3 4 5 6 7
# config/routes/attributes.yaml
controllers:
resource: '../../src/Controller/'
type: attribute
host:
en: 'www.example.com'
nl: 'www.example.nl'
無狀態路由
有時,當 HTTP 回應應該被快取時,確保可以發生這種情況非常重要。但是,只要在請求期間啟動會話,Symfony 就會將回應轉換為私有的不可快取回應。
有關詳細資訊,請參閱 HTTP 快取。
路由可以設定 stateless
布林值選項,以宣告在匹配請求時不應使用會話
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// src/Controller/MainController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;
class MainController extends AbstractController
{
#[Route('/', name: 'homepage', stateless: true)]
public function homepage(): Response
{
// ...
}
}
現在,如果使用了會話,應用程式將根據您的 kernel.debug
參數報告它
enabled
:將拋出 UnexpectedSessionUsageException 異常disabled
:將記錄警告
這將幫助您了解並希望修正應用程式中意外的行為。
產生 URL
路由系統是雙向的
- 它們將 URL 與控制器關聯(如前幾節所述);
- 它們為給定的路由產生 URL。
從路由產生 URL 允許您不必在 HTML 範本中手動編寫 <a href="...">
值。此外,如果某些路由的 URL 發生變更,您只需更新路由設定,所有連結都將被更新。
要產生 URL,您需要指定路由的名稱(例如 blog_show
)和路由定義的參數值(例如 slug = my-blog-post
)。
因此,每個路由都有一個內部名稱,該名稱在應用程式中必須是唯一的。如果您沒有使用 name
選項明確設定路由名稱,Symfony 會根據控制器和動作產生一個自動名稱。
如果目標類別具有新增路由的 __invoke()
方法且目標類別僅新增了一個路由,則 Symfony 會根據 FQCN 宣告路由別名。Symfony 還會為每個僅定義一個路由的方法自動新增別名。考慮以下類別
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// src/Controller/MainController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;
final class MainController extends AbstractController
{
#[Route('/', name: 'homepage')]
public function homepage(): Response
{
// ...
}
}
Symfony 將新增一個名為 App\Controller\MainController::homepage
的路由別名。
在控制器中產生 URL
如果您的控制器繼承自 AbstractController,請使用 generateUrl()
輔助方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class BlogController extends AbstractController
{
#[Route('/blog', name: 'blog_list')]
public function list(): Response
{
// 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->getUserIdentifier(),
]);
// 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']);
// ...
}
}
注意
如果您將一些不屬於路由定義的參數傳遞給 generateUrl()
方法,它們將作為查詢字串包含在產生的 URL 中
1 2 3
$this->generateUrl('blog', ['page' => 2, 'category' => 'Symfony']);
// the 'blog' route only defines the 'page' parameter; the generated URL is:
// /blog/2?category=Symfony
警告
雖然物件在用作佔位符時會轉換為字串,但當用作額外參數時不會轉換。因此,如果您要傳遞物件(例如 Uuid)作為額外參數的值,則需要明確地將其轉換為字串
1
$this->generateUrl('blog', ['uuid' => (string) $entity->getUuid()]);
如果您的控制器沒有繼承自 AbstractController
,您需要在您的控制器中獲取服務並遵循下一節的指示。
在服務中產生 URL
將 router
Symfony 服務注入到您自己的服務中,並使用其 generate()
方法。當使用服務自動裝配時,您只需要在服務建構子中新增一個引數,並使用 UrlGeneratorInterface 類別進行類型提示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
// src/Service/SomeService.php
namespace App\Service;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class SomeService
{
public function __construct(
private UrlGeneratorInterface $urlGenerator,
) {
}
public function someMethod(): void
{
// ...
// generate a URL with no route arguments
$signUpPage = $this->urlGenerator->generate('sign_up');
// generate a URL with route arguments
$userProfilePage = $this->urlGenerator->generate('user_profile', [
'username' => $user->getUserIdentifier(),
]);
// generated URLs are "absolute paths" by default. Pass a third optional
// argument to generate different URLs (e.g. an "absolute URL")
$signUpPage = $this->urlGenerator->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->urlGenerator->generate('sign_up', ['_locale' => 'nl']);
}
}
在樣板中產生 URL
請閱讀關於 Symfony 範本的主要文章中建立頁面之間連結的章節。
在 JavaScript 中產生 URL
如果您的 JavaScript 程式碼包含在 Twig 範本中,您可以使用 path()
和 url()
Twig 函數來產生 URL,並將它們儲存在 JavaScript 變數中。escape()
過濾器是必要的,以逸出任何非 JavaScript 安全的值
1 2 3
<script>
const route = "{{ path('blog_show', {slug: 'my-blog-post'})|escape('js') }}";
</script>
如果您需要動態產生 URL,或者您正在使用純 JavaScript 程式碼,則此解決方案不起作用。在這些情況下,請考慮使用 FOSJsRoutingBundle。
在命令中產生 URL
在命令中產生 URL 的方式與在服務中產生 URL相同。唯一的區別是命令不是在 HTTP 上下文中執行的。因此,如果您產生絕對 URL,您將獲得 https://127.0.0.1/
作為主機名稱,而不是您的真實主機名稱。
解決方案是設定 default_uri
選項,以定義命令在產生 URL 時使用的「請求上下文」
1 2 3 4 5
# config/packages/routing.yaml
framework:
router:
# ...
default_uri: 'https://example.org/my/path/'
現在,當您在命令中產生 URL 時,您將獲得預期的結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
// src/Command/SomeCommand.php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
// ...
class SomeCommand extends Command
{
public function __construct(private UrlGeneratorInterface $urlGenerator)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
// generate a URL with no route arguments
$signUpPage = $this->urlGenerator->generate('sign_up');
// generate a URL with route arguments
$userProfilePage = $this->urlGenerator->generate('user_profile', [
'username' => $user->getUserIdentifier(),
]);
// by default, generated URLs are "absolute paths". Pass a third optional
// argument to generate different URIs (e.g. an "absolute URL")
$signUpPage = $this->urlGenerator->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->urlGenerator->generate('sign_up', ['_locale' => 'nl']);
// ...
}
}
注意
預設情況下,為 Web 資源產生的 URL 使用相同的 default_uri
值,但您可以使用 asset.request_context.base_path
和 asset.request_context.secure
容器參數來變更它。
檢查路由是否存在
在高動態應用程式中,可能需要在使用路由產生 URL 之前檢查路由是否存在。在這些情況下,請勿使用 getRouteCollection() 方法,因為這會重新產生路由快取並減慢應用程式的速度。
相反,請嘗試產生 URL 並捕獲在路由不存在時拋出的 RouteNotFoundException。
1 2 3 4 5 6 7 8 9
use Symfony\Component\Routing\Exception\RouteNotFoundException;
// ...
try {
$url = $this->router->generate($routeName, $routeParameters);
} catch (RouteNotFoundException $e) {
// the route is not defined...
}
強制在產生的 URL 上使用 HTTPS
注意
如果您的伺服器在終止 SSL 的代理伺服器後面執行,請確保設定 Symfony 以在代理伺服器後面工作
方案的設定僅用於非 HTTP 請求。schemes
選項與不正確的代理伺服器設定結合使用將導致重新導向迴圈。
預設情況下,產生的 URL 使用與當前請求相同的 HTTP 方案。在控制台命令中,由於沒有 HTTP 請求,URL 預設使用 http
。您可以針對每個命令(透過路由器的 getContext()
方法)或使用這些設定參數全域變更此設定
1 2 3 4
# config/services.yaml
parameters:
router.request_context.scheme: 'https'
asset.request_context.secure: true
在控制台命令之外,使用 schemes
選項來明確定義每個路由的方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Controller/SecurityController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class SecurityController extends AbstractController
{
#[Route('/login', name: 'login', schemes: ['https'])]
public function login(): Response
{
// ...
}
}
為 login
路由產生的 URL 將始終使用 HTTPS。這表示當使用 path()
Twig 函數產生 URL 時,如果原始請求的 HTTP 方案與路由使用的方案不同,您可能會獲得絕對 URL 而不是相對 URL
1 2 3 4 5 6
{# 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') }}
方案需求也針對傳入請求強制執行。如果您嘗試使用 HTTP 存取 /login
URL,您將自動重新導向到相同的 URL,但使用 HTTPS 方案。
如果您想強制一組路由使用 HTTPS,您可以在匯入它們時定義預設方案。以下範例強制所有定義為屬性的路由使用 HTTPS
1 2 3 4 5
# config/routes/attributes.yaml
controllers:
resource: '../../src/Controller/'
type: attribute
schemes: [https]
注意
Security 組件提供了透過 requires_channel
設定強制執行 HTTP 或 HTTPS 的另一種方法。
簽署 URI
簽名 URI 是一種包含雜湊值的 URI,該雜湊值取決於 URI 的內容。這樣,您稍後可以透過重新計算其雜湊值並將其與 URI 中包含的雜湊值進行比較來檢查簽名 URI 的完整性。
Symfony 透過 UriSigner 服務提供了一個實用工具來簽署 URI,您可以將其注入到您的服務或控制器中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
// src/Service/SomeService.php
namespace App\Service;
use Symfony\Component\HttpFoundation\UriSigner;
class SomeService
{
public function __construct(
private UriSigner $uriSigner,
) {
}
public function someMethod(): void
{
// ...
// generate a URL yourself or get it somehow...
$url = 'https://example.com/foo/bar?sort=desc';
// sign the URL (it adds a query parameter called '_hash')
$signedUrl = $this->uriSigner->sign($url);
// $url = 'https://example.com/foo/bar?sort=desc&_hash=e4a21b9'
// check the URL signature
$uriSignatureIsValid = $this->uriSigner->check($signedUrl);
// $uriSignatureIsValid = true
// if you have access to the current Request object, you can use this
// other method to pass the entire Request object instead of the URI:
$uriSignatureIsValid = $this->uriSigner->checkRequest($request);
}
}
出於安全原因,常見的做法是使簽名 URI 在一段時間後過期(例如,當使用它們來重設使用者憑證時)。預設情況下,簽名 URI 不會過期,但您可以使用 sign() 的 $expiration
引數來定義到期日期/時間
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
// src/Service/SomeService.php
namespace App\Service;
use Symfony\Component\HttpFoundation\UriSigner;
class SomeService
{
public function __construct(
private UriSigner $uriSigner,
) {
}
public function someMethod(): void
{
// ...
// generate a URL yourself or get it somehow...
$url = 'https://example.com/foo/bar?sort=desc';
// sign the URL with an explicit expiration date
$signedUrl = $this->uriSigner->sign($url, new \DateTimeImmutable('2050-01-01'));
// $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=2524608000&_hash=e4a21b9'
// if you pass a \DateInterval, it will be added from now to get the expiration date
$signedUrl = $this->uriSigner->sign($url, new \DateInterval('PT10S')); // valid for 10 seconds from now
// $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=1712414278&_hash=e4a21b9'
// you can also use a timestamp in seconds
$signedUrl = $this->uriSigner->sign($url, 4070908800); // timestamp for the date 2099-01-01
// $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=4070908800&_hash=e4a21b9'
}
}
注意
到期日期/時間透過 _expiration
查詢參數包含在簽名 URI 中。
7.1
為簽名 URI 新增到期日期的功能是在 Symfony 7.1 中引入的。
注意
產生的 URI 雜湊可能包含 /
和 +
字元,這可能會導致某些用戶端出現問題。如果您遇到此問題,請使用以下內容替換它們:strtr($hash, ['/' => '_', '+' => '-'])
。
疑難排解
以下是您在使用路由時可能會看到的一些常見錯誤
1 2
Controller "App\\Controller\\BlogController::show()" requires that you
provide a value for the "$slug" argument.
當您的控制器方法具有引數時(例如 $slug
),就會發生這種情況
1 2 3 4
public function show(string $slug): Response
{
// ...
}
但您的路由路徑沒有 {slug}
參數(例如,它是 /blog/show
)。將 {slug}
新增到您的路由路徑:/blog/show/{slug}
或為引數提供預設值(即 $slug = null
)。
1 2
Some mandatory parameters are missing ("slug") to generate a URL for route
"blog_show".
這表示您正在嘗試為 blog_show
路由產生 URL,但您沒有傳遞 slug
值(這是必需的,因為它在路由路徑中具有 {slug}
參數)。要解決此問題,請在產生路由時傳遞 slug
值
1
$this->generateUrl('blog_show', ['slug' => 'slug-value']);
或在 Twig 中
1
{{ path('blog_show', {slug: 'slug-value'}) }}