Symfony4中文文件: 路由
路由
漂亮的URL是任何嚴謹的Web應用程式所必須的. 這意味著像 index.php?article_id=57
這樣醜陋的URL要被 /read/intro-to-symfony
所取代.
具有靈活性更加重要. 如果你需要將 /blog
更改為 /news
, 需要做些什麼? 你需要搜尋並更新多少連結才能做出這種改動? 如果你使用的是Symfony的路由, 更改將是很簡單的.
建立路由
路由是從URL到控制器的對映, 假如你想要一個路由完全匹配 /blog
和另外更多可匹配任何像 /blog/my-post
和 /blog/all-about-symfony
URL的動態路由.
路由可以在YAML, XML和PHP. 所有格式都提供相同的功能和效能, 因此可選擇你喜歡的格式. 如果你選擇PHP annotations, 請在你的應用程式中執行一次此命令以新增對它們的支援:
$ composer require annotations
現在你可以配置路由:
Annotations
// src/Controller/BlogController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class BlogController extends AbstractController { /** * Matches /blog exactly * * @Route("/blog", name="blog_list") */ public function list() { // ... } /** * Matches /blog/* * * @Route("/blog/{slug}", name="blog_show") */ public function show($slug) { // $slug will equal the dynamic part of the URL // e.g. at /blog/yay-routing, then $slug='yay-routing' // ... } }
YAML
# config/routes.yaml
blog_list:
path: /blog
controller: App\Controller\BlogController::list
blog_show:
path: /blog/{slug}
controller: App\Controller\BlogController::show
XML
<!-- config/routes.xml --> <?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> <route id="blog_list" controller="App\Controller\BlogController::list" path="/blog" > <!-- settings --> </route> <route id="blog_show" controller="App\Controller\BlogController::show" path="/blog/{slug}"> <!-- settings --> </route> </routes>
PHP
// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;
$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog', array(
'_controller' => [BlogController::class, 'list']
)));
$routes->add('blog_show', new Route('/blog/{slug}', array(
'_controller' => [BlogController::class, 'show']
)));
return $routes;
感謝這兩條路由:
- 如果使用者訪問
/blog
, 匹配第一條路由配置並且list()
將被執行; - 如果使用者訪問
/blog/*
, 匹配第二條路由配置並且show()
將被執行. 因為路由路徑是/blog/{slug}
, 所以$slug
變數傳遞給該值匹配的show()
. 例如, 如果使用者訪問/blog/yay-routing
, 那麼$slug
將等於yay-routing
.
每當路由路徑中有 {placeholder}
時, 該部分就成為萬用字元: 它將匹配任意值. 你的控制器現在也有一個名為 $placeholder
的引數 ( 萬用字元和引數名稱必須匹配 ).
每個路由還有一個內部名稱: blog_list
和 blog_show
. 這些可以是任意內容 ( 只要每個都是唯一的 ) 並且需要無任何特別含義. 稍後你將使用它們來生成URL.
其他格式的路由每個方法上面的 @Route 稱為 annotation. 如果你更願意使用YAML, XML或PHP配置路由, 那沒問題! 只需建立一個新的路由檔案 ( 例如
routes.xml
) , Symfony就會自動使用它.
本地化路由(i18n)
路由可以本地化地為每個區域提供唯一的路徑. Symfony提供了一種簡便的方式來宣告本地化路由而無重複.
Annotations
// src/Controller/CompanyController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class CompanyController extends AbstractController
{
/**
* @Route({
* "nl": "/over-ons",
* "en": "/about-us"
* }, name="about_us")
*/
public function about()
{
// ...
}
}
YAML
# config/routes.yaml
about_us:
path:
nl: /over-ons
en: /about-us
controller: App\Controller\CompanyController::about
XML
<!-- config/routes.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="about_us" controller="App\Controller\CompanyController::about">
<path locale="nl">/over-ons</path>
<path locale="en">/about-us</path>
</route>
</routes>
PHP
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;
return function (RoutingConfigurator $routes) {
$routes->add('about_us', ['nl' => '/over-ons', 'en' => '/about-us'])
->controller('App\Controller\CompanyController::about');
};
當本地化路由匹配時, Symfony會自動識別請求期間應使用哪個區域的路由設定. 以這種方式定義路由避免了對路由重複註冊的需要, 最小化了由定義不一致引起的任何錯誤的風險.
為所有路由新增字首是國際化應用程式的一個常見需求. 這樣可以通過為每個語言環境定義不同的路徑字首來完成 ( 如果願意, 可以為預設語言設定一個空字首 ):
YAML
# config/routes/annotations.yaml
controllers:
resource: '../../src/Controller/'
type: annotation
prefix:
en: '' # don't prefix URLs for English, the default locale
nl: '/nl'
新增 {萬用字元} 條件
想象一下, blog_list
路由將包含一個部落格主題的分頁列表, 其中包含 /blog/2
和 /blog/3
等第2頁和第3頁的URL. 如果你將路徑修改為 /blog/{page}
, 你將會遇到一個問題:
- blog_list:
/blog/{page}
將匹配/blog/*
; - blog_show:
/blog/{slug}
將仍然匹配/blog/*
;
當兩條路由匹配相同的URL時, 載入的第一條路由將勝利. 不幸的是, 這意味著 /blog/yay-routing
將匹配 blog_list
.
要解決此問題, 新增一個 {page}
萬用字元用來只匹配數字:
Annotations
// 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($page)
{
// ...
}
/**
* @Route("/blog/{slug}", name="blog_show")
*/
public function show($slug)
{
// ...
}
}
YAML
# config/routes.yaml
blog_list:
path: /blog/{page}
controller: App\Controller\BlogController::list
requirements:
page: '\d+'
blog_show:
# ...
XML
<!-- config/routes.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog_list" path="/blog/{page}" controller="App\Controller\BlogController::list">
<requirement key="page">\d+</requirement>
</route>
<!-- ... -->
</routes>
PHP
// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;
$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog/{page}', array(
'_controller' => [BlogController::class, 'list'],
), array(
'page' => '\d+'
)));
// ...
return $routes;
\d+
是一個匹配任意長度數字的正則表示式. 現在:
URL | Route | Parameters |
---|---|---|
/blog/2 | blog_list | $page = 2 |
/blog/yay-routing | blog_show | $slug = yay-routing |
如果你願意, 可以在每個佔位符中使用語法 {placeholder_name<requirements>}
. 此功能使配置更簡潔, 但當需求複雜時, 它會降低路由可讀性:
Annotations
// 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($page)
{
// ...
}
}
YAML
# config/routes.yaml
blog_list:
path: /blog/{page<\d+>}
controller: App\Controller\BlogController::list
XML
<!-- config/routes.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog_list" path="/blog/{page<\d+>}"
controller="App\Controller\BlogController::list" />
<!-- ... -->
</routes>
PHP
// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;
$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog/{page<\d+>}', array(
'_controller' => [BlogController::class, 'list'],
)));
// ...
return $routes;
給{佔位符}一個預設值
在前面的例子中, blog_list
的路徑為 /blog/{page}
. 如果使用者訪問 /blog/1
, 則會匹配. 如果使用者訪問 /blog
, 將無法匹配. 只要向路由路徑添加了 {佔位符} , 它就必須有值.
那麼當用戶訪問 /blog
時, 如何讓 blog_list
再次匹配呢? 通過新增一個 預設
值:
Annotations
// 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($page = 1)
{
// ...
}
}
YAML
# config/routes.yaml
blog_list:
path: /blog/{page}
controller: App\Controller\BlogController::list
defaults:
page: 1
requirements:
page: '\d+'
blog_show:
# ...
XML
<!-- config/routes.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog_list" path="/blog/{page}" controller="App\Controller\BlogController::list">
<default key="page">1</default>
<requirement key="page">\d+</requirement>
</route>
<!-- ... -->
</routes>
PHP
// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;
$routes = new RouteCollection();
$routes->add('blog_list', new Route(
'/blog/{page}',
array(
'_controller' => [BlogController::class, 'list'],
'page' => 1,
),
array(
'page' => '\d+'
)
));
// ...
return $routes;
現在, 當用戶訪問 /blog
時, blog_list
路由會匹配, 並且 $page
路由引數會預設取值為 1
.
與{萬用字元}條件一樣, 使用語法 {placeholder_name?default_value}
也可以在每個佔位符中內聯預設值. 此功能與內聯條件相容, 因此你可以在一個佔位符中內聯:
Annotations
// 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($page)
{
// ...
}
}
YAML
# config/routes.yaml
blog_list:
path: /blog/{page<\d+>?1}
controller: App\Controller\BlogController::list
XML
<!-- config/routes.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog_list" path="/blog/{page <\d+>?1}"
controller="App\Controller\BlogController::list" />
<!-- ... -->
</routes>
PHP
// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;
$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog/{page<\d+>?1}', array(
'_controller' => [BlogController::class, 'list'],
)));
// ...
return $routes;
佔位符變數的值若是null
變數, 則需要在萬用字元最後新增?
字元. ( 例如/blog/{page?}
) .
全部路由列表
隨著你應用程式的健壯, 最終會有大量的路由被定義! 要檢視所有內容, 請執行命令:
$ php bin/console debug:router
------------------------------ -------- -------------------------------------
Name Method Path
------------------------------ -------- -------------------------------------
app_lucky_number ANY /lucky/number/{max}
...
------------------------------ -------- -------------------------------------
高階路由示例
請檢視高階示例:
Annotations
// src/Controller/ArticleController.php
// ...
class ArticleController extends AbstractController
{
/**
* @Route(
* "/articles/{_locale}/{year}/{slug}.{_format}",
* defaults={"_format": "html"},
* requirements={
* "_locale": "en|fr",
* "_format": "html|rss",
* "year": "\d+"
* }
* )
*/
public function show($_locale, $year, $slug)
{
}
}
YAML
# config/routes.yaml
article_show:
path: /articles/{_locale}/{year}/{slug}.{_format}
controller: App\Controller\ArticleController::show
defaults:
_format: html
requirements:
_locale: en|fr
_format: html|rss
year: \d+
XML
<!-- config/routes.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="article_show"
path="/articles/{_locale}/{year}/{slug}.{_format}"
controller="App\Controller\ArticleController::show">
<default key="_format">html</default>
<requirement key="_locale">en|fr</requirement>
<requirement key="_format">html|rss</requirement>
<requirement key="year">\d+</requirement>
</route>
</routes>
PHP
// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\ArticleController;
$routes = new RouteCollection();
$routes->add(
'article_show',
new Route('/articles/{_locale}/{year}/{slug}.{_format}', array(
'_controller' => [ArticleController::class, 'show'],
'_format' => 'html',
), array(
'_locale' => 'en|fr',
'_format' => 'html|rss',
'year' => '\d+',
))
);
return $routes;
如你所見, 只有當URL的 {_locale}
部分為 en
或 fr
且 {year}
為數字時, 此路由才會匹配. 示例還展示瞭如何在佔位符之間使用 .
號來替換 /
. 以下URL都可匹配:
- /articles/en/2010/my-post
- /articles/fr/2010/my-post.rss
- /articles/en/2013/my-latest-post.html
_format
路由引數示例突出顯示了
_format
特殊路由引數, 當使用此引數時, 匹配的值將成為Request物件的"請求格式".最後, 請求格式被用作設定返回
Content-Type
之類的事情 ( 例如: 一個JSON請求格式會轉換Content-Type
為application/json
)
特殊路由引數
如你所見, 每個路由引數或預設值最終都可以作為控制器方法的引數. 此外, 還有四個特殊引數: 每個引數在應用程式中具有獨特的功能:
_controller
用於確定路由匹配時執行的控制器
_format
用於設定請求格式 ( 閱讀更多 )
_fragment
用於設定fragment identifier, URL的最後可選部分, 以 #
字元開頭, 用於標識文件的某一部分.
_locale
用於在請求上設定區域 ( 閱讀更多 )
尾部斜槓重定向URL
從歷史上看, URL遵循UNIX約定, 即為路徑新增尾部斜槓 ( 例如 https://example.com/foo/
) , 當刪除斜槓時將作為檔案引用 ( https://example.com/foo ) . 雖然為兩個URL提供不同的內容是可以的, 但現在將兩個URL視為相同的URL並在他們之間重定向是很常見的.
Symfony遵循這個邏輯, 在帶斜槓和不帶斜槓的URL之間重定向 ( 但僅限於GET和HEAD請求 ):
Route path | If the requested URL is /foo | If the requested URL is /foo/ |
---|---|---|
/foo | It matches (200 status response) | It makes a 301 redirect to /foo |
/foo/ | It makes a 301 redirect to /foo/ | It matches (200 status response) |
如果你的應用程式為每個路徑 (/foo
和/foo/
) 定義了不同的路由, 則不會發生自動重定向, 並且始終匹配正確的路由.在Symfony4.1中引入了從
/foo/
到/foo
的自動301重定向. 在之前的Symfony版本中, 會響應404.
控制器命名模式
路由中的控制器格式非常簡單 CONTROLLER_CLASS::METHOD
.
To refer to an action that is implemented as the __invoke() method of a controller class, you do not have to pass the method name, but can just use the fully qualified class name (e.g. AppControllerBlogController).
生成URL
路由系統也可以生成URL. 實際上, 路由是雙向系統: 將URL對映到控制器以及路由返解為URL.
要生成URL, 你需要制定路由的名稱 ( 例如 blog_show
) 以及該路由的路徑中使用的任何萬用字元 ( 例如 slug = my-blog-post
) . 有了這些資訊, 可輕鬆生成任何URL:
class MainController extends AbstractController
{
public function show($slug)
{
// ...
// /blog/my-blog-post
$url = $this->generateUrl(
'blog_show',
array('slug' => 'my-blog-post')
);
}
}
// src/Service/SomeService.php
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class SomeService
{
private $router;
public function __construct(UrlGeneratorInterface $router)
{
$this->router = $router;
}
public function someMethod()
{
$url = $this->router->generate(
'blog_show',
array('slug' => 'my-blog-post')
);
// ...
}
}
使用查詢字串生成URL
generate()
方法採用萬用字元陣列來生成URI. 但是如果你傳遞額外值, 他們將作為查詢字串新增到URI中.
$this->router->generate('blog', array(
'page' => 2,
'category' => 'Symfony',
));
// /blog/2?category=Symfony
生成本地化URL
路由本地化時, Symfony預設使用當前請求區域來生成URL. 為了生成不同語言環境的URL, 你必須在parameters陣列中傳遞 _locale
:
$this->router->generate('about_us', array(
'_locale' => 'nl',
));
// generates: /over-ons
從模板中生成URL
生成絕對URL
預設情況下, 路由將生成相對URL ( 例如 /blog
) . 在控制器中, 將 UrlGeneratorInterface::ABSOLUTE_URL
傳遞給 generateUrl()
方法的第三個引數:
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
$this->generateUrl('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL);
// http://www.example.com/blog/my-blog-post
The host that's used when generating an absolute URL is automatically detected using the current Request object. When generating absolute URLs from outside the web context (for instance in a console command) this doesn't work. See How to Generate URLs from the Console to learn how to solve this problem.
錯誤排除
以下是使用路由時可能會遇到的一些常見錯誤:
Controller "AppControllerBlogController::show()" requires that you provide a value for the "$slug" argument.
當你的控制器方法有一個引數 ( 例如 $slug
) 時會發生這種情況:
public function show($slug)
{
// ..
}
你的路由沒有 {slug}
萬用字元 ( 例如 /blog/show
). 在你的路由路徑中增加 {slug}
: /blog/show/{slug}
或為引數設定一個預設值 ( 例如 $slug = null
)
Some mandatory parameters are missing ("slug") to generate a URL for route "blog_show".
這意味著你正在嘗試生成 blog_show
路由的URL, 但你沒有傳遞 slug 值 (這是必須的, 因為在路由路徑中有一個 {slug} 萬用字元). 要解決此問題, 請在生成路由時傳遞 slug
值:
$this->generateUrl('blog_show', array('slug' => 'slug-value'));
// or, in Twig
// {{ path('blog_show', {'slug': 'slug-value'}) }}