漂亮的URL是任何嚴謹的Web應用程序所必須的. 這意味着像 index.php?article_id=57
這樣醜陋的URL要被 /read/intro-to-symfony
所取代.javascript
具備靈活性更加劇要. 若是你須要將 /blog
更改成 /news
, 須要作些什麼? 你須要搜索並更新多少連接才能作出這種改動? 若是你使用的是Symfony的路由, 更改將是很簡單的.php
路由是從URL到控制器的映射, 假如你想要一個路由徹底匹配 /blog
和另外更多可匹配任何像 /blog/my-post
和 /blog/all-about-symfony
URL的動態路由.html
路由能夠在YAML, XML和PHP. 全部格式都提供相同的功能和性能, 所以可選擇你喜歡的格式. 若是你選擇PHP annotations, 請在你的應用程序中運行一次此命令以添加對它們的支持:java
$ composer require annotations
如今你能夠配置路由:web
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' // ... } }
YAMLjson
# config/routes.yaml blog_list: path: /blog controller: App\Controller\BlogController::list blog_show: path: /blog/{slug} controller: App\Controller\BlogController::show
XMLapi
<!-- 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;
感謝這兩條路由:app
/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就會自動使用它.
路由能夠本地化地爲每一個區域提供惟一的路徑. 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/{page}
將匹配 /blog/*
;/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;
要了解其餘路由條件 ( 如HTTP方法, 主機名和動態表達式 ) 請參閱 How to Define Route Requirements
在前面的例子中, 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均可匹配:
_format
路由參數示例突出顯示了
_format
特殊路由參數, 當使用此參數時, 匹配的值將成爲Request對象的"請求格式".最後, 請求格式被用做設置返回
Content-Type
之類的事情 ( 例如: 一個JSON請求格式會轉換Content-Type
爲application/json
)
如你所見, 每一個路由參數或默認值最終均可以做爲控制器方法的參數. 此外, 還有四個特殊參數: 每一個參數在應用程序中具備獨特的功能:
_controller
用於肯定路由匹配時執行的控制器
_format
用於設置請求格式 ( 閱讀更多 )
_fragment
用於設置fragment identifier, URL的最後可選部分, 以 #
字符開頭, 用於標識文檔的某一部分.
_locale
用於在請求上設置區域 ( 閱讀更多 )
從歷史上看, 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, 你須要制定路由的名稱 ( 例如 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') ); } }
若是須要從服務生成URL, 注入 UrlGeneratorInterface 服務.
// 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') ); // ... } }
generate()
方法採用通配符數組來生成URI. 可是若是你傳遞額外值, 他們將做爲查詢字符串添加到URI中.
$this->router->generate('blog', array( 'page' => 2, 'category' => 'Symfony', )); // /blog/2?category=Symfony
路由本地化時, Symfony默認使用當前請求區域來生成URL. 爲了生成不一樣語言環境的URL, 你必須在parameters數組中傳遞 _locale
:
$this->router->generate('about_us', array( '_locale' => 'nl', )); // generates: /over-ons
要在Twig中生成URL: 請參閱模板章節. 若是你須要在JavaScript中生成URL, 請參閱 How to Generate Routing URLs in JavaScript
默認狀況下, 路由將生成相對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'}) }}