漂亮的URLs是任何嚴謹的網頁應用程式都需要的,這代表比起index.php?article_id=57這樣不美觀的URLs,更喜歡像是/read/intro-to-symfony這個樣子。
保有彈性也是很重要的一點,如果你像要將一個頁面URL從/blog變成/news該怎麼做?為了這個更動,有多少連結要追溯、更新?如果你是用Symfony的路由,這些問題都能輕鬆解決。
建立路由
路由是將URL對應到控制器(controller),假設我們希望有個路由可以對應到/blog,其他更動態的路由可以對應到更多URL,如/blog/my-post或/blog/all-about-symfony。路由的設定可以寫在YAML、XML和PHP,所有的檔案類型都能提供相同的特性和效能,所以只要選一個喜歡的使用即可,如果是用PHP註解式宣告(PHP annotations),請執行一次這個指令讓系統支援這個方式。
composer require annotations現在可以設定路由:
// 省略程式碼由於這兩個路由:
- 如果使用者前往/blog,與第一個路由匹配並執行函式list()。
- 如果使用者前往/blog/*,與第二個路由相符並執行函式show(),有個參數$slug會傳到show()去對應。比方說,如果使用者前往/blog/yay-routing,所以$slug等於yay-routing。
每個路由還有一個內部名稱(internal name):blog_list和blog_show},名稱可以自行決定(只要不重複),現在還沒有用到,之後可以用來產生URL。
本地化路由 (i18n)
路由可以提供每個地區(locale)一個獨一無二的路徑來達到本地化,Symfony提供一個方便的方法去宣告本地化路由,省去許多重複性的工作。// 省略程式碼當需求(request)與一個本地化路由相符時,Symfony會自動知道要要使用哪一個地區,這樣的宣告方式也同時降低重複註冊路由的需求,也就是降低因定義不一致產生錯誤的風險。
對於一個國際化的應用,在所有路由前加上地區當作前綴是很常見的需求,每一個地區定義不同的前綴就能達成(如果有需要的話,預設地區可以設定空白前綴字):
// 省略程式碼
新增通配符{wildcard}的條件
想像一下有個blog_list的路由是分成很多頁的部落格貼文,路徑可能像是blog/2和blog/3,如果將路由的路徑設定成blog/{page},你可能會有疑問:- blog_list: blog/{page}與blog/*相符
- blog_show: blog/{slug}也與blog/*相符
為了解決這個問題,我們新增一個條件(requirement),讓{page}通配符只能是數字(number, digits):
// 省略程式碼\d+是一個通常表示式(regular expression),能與任何長度的數字配對,整理一下現在的情況:
URL | Route | Parameters |
---|---|---|
/blog/2 | blog_list | $page = 2 |
/blog/yay-routing | blog_show | $slug = yay-routing |
如果你願意的話,條件可以和佔位符(placeholder)寫在同一行,句法會像這樣{placeholder_name
// 省略程式碼想要認識更多路由條件 - 像是HTTP method、主機名稱(hostname)和動態表示式(dynamic expressions) - 請看如何設定路由條件。
設定占用符{placeholders}預設值
在前面的例子中,blog_list設定的路徑是/blog/{page},如果使用這訪問/blog/1,將會符合;當如果是/blog,將不會配對成功。只要在路徑設定一個{placeholder},就必須有值。所以如何再一次將讓blog_list與/blog配對呢?可以透過設定預設值:
// 省略程式碼現在,當使用者前往/blog時,會對應到路由blog_list,此時$page為預設值1。
如果你希望每次產生URL時都需要預設值(等同前面的舉例,強制讓/blog變成/blog/1),在佔位符前面加上字元!:/blog/{!page}。
和路由條件一樣,預設值和佔位符也能將定義寫在同一行,句法為{placehodler_name?defulat_value},這個功能與路由條件能夠同時使用,所以你可以把預設值、路由條件兩者與佔位符用一行定義:
// 省略程式碼
列出所有路由
隨著你的專案開發,最終你會有一堆路由,如果要全部檢視請執行:php bin/console debug:router ------------------------------ -------- ------------------------------------- Name Method Path ------------------------------ -------- ------------------------------------- app_lucky_number ANY /lucky/number/{max} ... ------------------------------ -------- -------------------------------------
進階路由範例
綜合上述,請看下面這個進階範例:// 省略程式碼如同你看到的,這個路由只會在{_locale}這個部分是en或fr,而且{year}是數字時,才會匹配。這個路由也顯示在佔位符之間如何使用點(dot),而非使用斜槓(slash),符合這個陸游的URL像是:
- /articles/en/2010/my-post
- /articles/fr/2010/my-post.rss
- /articles/en/2013/my-latest-post.html
特殊的路由參數
就像你看到的,每個路由參數或預設值會成為控制器函式可取得的變數,除此之外,有四個特別的參數:每一個都能在你的應用裡發揮獨有的功能:_controller
如同所示,當路由配對成功時,決定要執行哪一個控制器。
_format
用來設定請求(request)的格式(閱讀更多)
_fragment
用來設定片段標識符,這是一個自選的功能,在URL的最後加上以#字元開頭的字串,來辨識整份文件的某個段落。
_locale
用來設定請求(request)的地區(閱讀更多)
重新導向尾端斜槓(slashes)的URL
從古到今,URL遵照UNIX協定,如果當作目錄就在尾端加上斜槓(e.g. https://example.com/foo/),視為文件的話會刪掉最後的斜槓來進行導向(e.g. https://example.com/foo,雖然兩個URL可以提供不通的內容,但將兩者視為相同的URL並在它們之間重新轉向是很常見的。Symfony遵循有和沒有尾端斜槓URL之間的重新導向這樣的邏輯(但只適用GET和HEAD請求):
Route path | If the requested URL is /foo | If the requested URL is /foo/ |
/foo | It matches (200status response) | It makes a 301 redirect to /foo |
/foo/ | It makes a 301 redirect to /foo/ | It matches (200status response) |
控制器命名方式
路由內controller格式為CONTROLLER_CLASS::METHOD。產生URLs
路由系統也可以產生URL,實際上,路由是雙向系統:由URL對應到控制器、路由對應到URL。要生成URL,你需要指定路徑會使用到的路由的名稱(e.g blog_show)和任何通配符(slug = my-blog-post),有了這些資訊,控制器就能產生URL:
class MainController extends AbstractController { public function show($slug) { // ... // /blog/my-blog-post $url = $this->generateUrl( 'blog_show', ['slug' => 'my-blog-post'] ); } }如果你需要從服務(service)產生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', ['slug' => 'my-blog-post'] ); // ... } }
產生帶有查詢字串的URL
generate()的函式會使用整個通配符陣列(an array of wildcard value)來闡產生URL,但如果傳送了額外的值,會當作查詢字串加到URL。$this->router->generate('blog', [ 'page' => 2, 'category' => 'Symfony', ]); // /blog/2?category=Symfony
產生本地化URLs
當路由是本地的(localized),Symfony會用現在的請求地區(request locale)來生成URL,如果要生成不同地區的URL,就必須傳_locale到參數陣列內。$this->router->generate('about_us', [ '_locale' => 'nl', ]); // generates: /over-ons
在模板(Template)裡產生URLs
在Twig產生URLs,可以參考關於模板的文章:連結到頁面(Linking to Pages),假設你也想要在JavaScript裡產生連結,請看:如何在JavaScript產生路由URLs(How to Generate Routing URLs in JavaSript)。產生絕對路徑(Absolute URLs)
一般情況下,路由會產生相應的URLs(e.g. /blog),從控制器,將UrlGeneratorInterface::ABSOLUTE_URL當作generateUrl()的第三個參數:use Symfony\Component\Routing\Generator\UrlGeneratorInterface; $this->generateUrl('blog_show', ['slug' => 'my-blog-post'], UrlGeneratorInterface::ABSOLUTE_URL); // http://www.example.com/blog/my-blog-post
Troubleshooting
當你在寫路由時,你可能看過這些常見的錯誤:這發生在你的控制器函式需要一個參數(e.g.$slug):
public function show($slug) { // .. }但是你的路由裡沒有{slug}這個通配符(e.g. /blog/show),在路由新增{slug}:/blog/show/{slug}或將參數定一個預設值(i.e. $slug = null)。
這代表你正試圖讓blog_show路由產生一個沒有slug值的URL(但slug值卻是必要的,因為路徑中有個通配符{slug}),為了修正這個問題,產生路由時請傳slug值:
$this->generateUrl('blog_show', ['slug' => 'slug-value']); // or, in Twig // {{ path('blog_show', {'slug': 'slug-value'}) }}
繼續前進!
路由沒問題了,現在一起揭開控制器(原文)的強大吧!文章原文:
https://symfony.com/doc/4.2/setup.html
系列文章:
- Symfony4.2入門教學【第一篇】安裝
- Symfony4.2入門教學【第二篇】建立第一個頁面(Routes)
- Symfony4.2入門教學【第四篇】控制器(Controller)
- Symfony4.2入門教學【第五篇】模板(Templates)
- Symfony4.2入門教學【第六篇】設定(Configuration)