路由
對於任何嚴謹的web應用程式而言美觀的URL是絕對必須的。這意味著index.php?article_id=57
這類醜陋的URL要被/read/intro-to-symfony
取代。
擁有彈性是更重要的。你將頁面的URL從/blog
改為/news
時需要做些什麼?你需要追蹤和更新多少連結以做出改變?如果你使用Symfony的路由,這就很容易了。
Symfony路由器讓你可以定義創造性的url,映射到應用程式的不同區域。在本章結束時,你將可以做到:
建立複雜的路由,它們將會對應到控制器
- ##在模板和控制器中產生URL
- 從Bundle(也可以從其它地方)載入路由資源
- 對路由調試
¶
一個路由,是指一個URL路徑(path)到一個控制器(controller)的對應。例如,你想匹配一些URL:/blog/my-post 和
/blog/all-about-symfony,並且發送路由到一個「能夠查詢和渲染那一篇博文”的控制器上。路由簡單的很:
Annotations:// src/AppBundle/Controller/BlogController.phpnamespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class BlogController extends Controller{ /** * @Route("/blog/{slug}", name="blog_show") */ public function showAction($slug) { // ... }}
YAML:# app/config/routing.ymlblog_show: path: /blog/{slug} defaults: { _controller: AppBundle:Blog:show }
XML:<!-- app/config/routing.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_show" path="/blog/{slug}"> <default key="_controller">AppBundle:Blog:show</default> </route></routes>
PHP:// app/config/routing.phpuse Symfony\Component\Routing\RouteCollection;use Symfony\Component\Routing\Route; $collection = new RouteCollection();$collection->add('blog_show', new Route('/blog/{slug}', array( '_controller' => 'AppBundle:Blog:show',))); return $collection;定義
blog_show路由模式,用來符合像
/blog/*的URL,把相關參數或通配符用
slug 表示並傳入。對於
/blog/my-blog-post這樣的URL,
slug變數得到
my-blog-post的值,並供你控制器使用。美觀
blog_show是一個內部名稱,他沒什麼實際的意義就是一個唯一的識別。以後,你可用他來產生一些URL。
_controller參數是一個特殊的鍵,它告訴symfony路由指定的URL應該執行哪個控制器。
_controller字串稱為
邏輯名稱。它遵循規則指向一個特定的php類別和方法,AppBundle\Controller\BlogController::showAction方法。
/blog/my-post,
showAction控制器將被執行並且
$slug變數就等於
my-post。
路由:深入了解 ¶
當一個請求發送到你的應用程序,它包含一個確切的「資源」的客戶端請求地址。這個位址被稱為URL(或URI),它可以是/contact
、/blog/read-me
或其它任何東西。以下是一個HTTP請求的範例:
GET /blog/my-blog-post
symfony路由系統的目的是解析url,並決定要呼叫哪個控制器。整個過程是這樣的:
由Symfony的前端控制器(如
app.php
)來處理請求。symfony的核心(Kernel核心)要求路由器來檢查請求。
路由將輸入的URL配對到一個特定的路由,並傳迴路由訊息,其中包括要執行的控制器資訊。
Symfony核心執行控制器並最終返回
Response
物件。
路由是將一個輸入URL轉換成特定的工具來執行控制器。
建立路由 ¶
Symfony從單一的路由設定檔載入所有的路由到你的應用程式。美觀路由設定檔通常是app/config/routing.yml
,但你也可以透過應用程式設定檔將該檔案放置在任何地方(包括xml或php格式的設定檔)。
YAML:# app/config/config.yml framework: # ... router: { resource: '%kernel.root_dir%/config/routing.yml' }
XML:<!-- app/config/config.xml --><?xml version="1.0" encoding="UTF-8" ?><container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <framework:config> <!-- ... --> <framework:router resource="%kernel.root_dir%/config/routing.xml" /> </framework:config></container>
PHP:// app/config/config.php$container->loadFromExtension('framework', array( // ... 'router' => array( 'resource' => '%kernel.root_dir%/config/routing.php', ),));
儘管所有的路由都可以從一個檔案加載,但是通常的做法是包含額外的路由資源。為此,你要把外部的路由檔案設定到主要路由檔案。具體資訊可查看本章:包含外部路由資源。
基本的路由設定 ¶
定義一個路由是容易的,一個典型的應用程式也應該有很多的路由。一個基本的路由包含兩個部分:path
匹配和defaults
數組:
Annotations:// src/AppBundle/Controller/MainController.php // ...class MainController extends Controller{ /** * @Route("/") */ public function homepageAction() { // ... }}
YAML:# app/config/routing.yml_welcome: path: / defaults: { _controller: AppBundle:Main:homepage }
XML:<!-- app/config/routing.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="_welcome" path="/"> <default key="_controller">AppBundle:Main:homepage</default> </route> </routes>
PHP:// app/config/routing.phpuse Symfony\Component\Routing\RouteCollection;use Symfony\Component\Routing\Route; $collection = new RouteCollection();$collection->add('_welcome', new Route('/', array( '_controller' => 'AppBundle:Main:homepage',))); return $collection;
該路由匹配首頁(/
)並將它對應到AppBundle:Main:homepage
控制器。 _controller
字串被Symfony轉換成PHP函數去執行。美觀過程在本章(控制器命名模式)中被簡短提及。
有參數的路由 ¶
路由系統支援許多有趣的路由寫法。許多的路由都可以包含一個或多個“參數或通配符”佔位符:
Annotations:// src/AppBundle/Controller/BlogController.php // ...class BlogController extends Controller{ /** * @Route("/blog/{slug}") */ public function showAction($slug) { // ... }}
YAML:# app/config/routing.ymlblog_show: path: /blog/{slug} defaults: { _controller: AppBundle:Blog:show }
XML:<!-- app/config/routing.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_show" path="/blog/{slug}"> <default key="_controller">AppBundle:Blog:show</default> </route></routes>
PHP:// app/config/routing.phpuse Symfony\Component\Routing\RouteCollection;use Symfony\Component\Routing\Route; $collection = new RouteCollection();$collection->add('blog_show', new Route('/blog/{slug}', array( '_controller' => 'AppBundle:Blog:show',))); return $collection;
美觀路徑將匹配任何/blog/*
的URL。更棒的是,美觀{slug}
佔位符會自動配對到控制器中。換句話說,如果該URL是/blog/hello-world
,控制器中$slug
變數的值就是hello-world
。這可以用來,匹配部落格文章標題的字串。
然而這種方式路由將不會符合/blog
這樣的URL,因為預設情況下,所有的佔位符都是必填的。當然這也是可以變通的,可以在defaults
陣列中加入佔位符(參數)的值來實現。
新增{通配符}的條件 ¶
快速瀏覽一下已經建立的路由:
Annotations:// src/AppBundle/Controller/BlogController.php // ...class BlogController extends Controller{ /** * @Route("/blog/{page}", defaults={"page" = 1}) */ public function indexAction($page) { // ... } /** * @Route("/blog/{slug}") */ public function showAction($slug) { // ... }}
YAML:# app/config/routing.ymlblog: path: /blog/{page} defaults: { _controller: AppBundle:Blog:index, page: 1 }blog_show: path: /blog/{slug} defaults: { _controller: AppBundle:Blog:show }
XML:<!-- app/config/routing.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" path="/blog/{page}"> <default key="_controller">AppBundle:Blog:index</default> <default key="page">1</default> </route> <route id="blog_show" path="/blog/{slug}"> <default key="_controller">AppBundle:Blog:show</default> </route></routes>
PHP:// app/config/routing.phpuse Symfony\Component\Routing\RouteCollection;use Symfony\Component\Routing\Route; $collection = new RouteCollection();$collection->add('blog', new Route('/blog/{page}', array( '_controller' => 'AppBundle:Blog:index', 'page' => 1,))); $collection->add('blog_show', new Route('/blog/{show}', array( '_controller' => 'AppBundle:Blog:show',))); return $collection;
你能發現問題嗎?兩個路由都符合類似/blog/*
的URL。 Symfony路由總是選擇它第一個符合的(blog)路由。換句話說,該blog_show
路由永遠被比對。相反,像一個/blog/my-blog-post
的URL會匹配第一個(blog
)路由,並回傳一個my-blog-post
的值給{page}
參數。
URL | Route | Parameters |
---|---|---|
/blog/2 | #blog_list | $page = 2 |
/blog/my-blog-post | ##blog_show
| #$slug = my-blog-post
|
Method Name | ||
---|---|---|
##BlogController | showAction |
|
這個控制器可能是這樣的:
// src/AppBundle/Controller/BlogController.phpnamespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class BlogController extends Controller{ public function showAction($slug) { // ... }}
注意,Symfony在Blog
上添加了字串Controller
作為類別名稱(Blog
=>BlogController
),加入字串Action
作為方法名稱(show
=>showAction
#)。
你也可以使用它的FQCN類別名稱及方法來指定這個類別:AppBundle\Controller\BlogController::showAction
。但如果你遵循一些簡單的命名約定,邏輯名將會更簡潔、更有彈性。
除了使用邏輯名稱和FQCN類別名稱之外,Symfony也支援第三種指定控制器的方式。這種方式只使用一個冒號分隔(如service_name:indexAction
),並將控制器作為一個服務來引用(請參閱如何定義控制器為服務)。
路由參數和控制器參數 ¶
路由參數(如{slug}
是非常重要的,因為它(們)都被用作控制器方法的參數:
public function showAction($slug){ // ...}
現實中,defaults
集將參數值一起合併成一個表單數組。該數組中的每個鍵都被做為控制器的參數。
換句話說,對於控制器方法的每個參數,Symfony2都會根據該名稱來查找路由參數,並將其值指向到控制器作為參數。在上面的高級範例當中,下列變數的任何組合(以任意方式)都被用作showAction()
方法的參數:
$_locale
$year
$title
#$_format
- ##$_controller
- $_route
defaults集合併在一起,就算是
$_controller 變數也是可用的。更多細節的討論,請參考作為控制器–把路由參數傳入控制器。
$ _route變量,它的值是被匹配的路由名稱。
你甚至可以在你的路由中定義額外的資訊並在你的控制器中存取它。關於更多資訊請閱讀 如何從路由傳遞到控制器額外的資訊
產生URL ¶
#路由系統也用於產生URL。在現實中,路由是一個雙向系統:映射URL到控制器 參數以及映射路由 參數返回URL。 match()
和generate()
方法構成了這個雙向系統。使用之前的blog_show
的範例:
$params = $this->get('router')->match('/blog/my-blog-post'); // array( // 'slug' => 'my-blog-post', // '_controller' => 'AppBundle:Blog:show', // ) $uri = $this->get('router')->generate('blog_show', array( 'slug' => 'my-blog-post')); // /blog/my-blog-post
要產生一個URL,你需要指定路由的名稱(如blog_show
)以及任意的通配符(如 slug = my-blog-post
)。有個這些訊息,任何URL就可以很容易的生成了:
class MainController extends Controller{ public function showAction($slug) { // ... $url = $this->generateUrl( 'blog_show', array('slug' => 'my-blog-post') ); }}
在控制器中你沒有繼承symfony的父類Controller,那麼你不可以使用generateUrl()
快捷方法,但你可以使用router的generate()
服務方法:
$url = $this->container->get('router')->generate( 'blog_show', array('slug' => 'my-blog-post'));
在即將到來的部分中,你將學會如何在模板中產生URL位址。
如果你的應用程式前端使用的是ajax請求,你可能想要根據你的路由配置,在JavaScript中產生URL。透過使用FOSJsRoutingBundle,你就可以做到:
var url = Routing.generate( 'blog_show', {"slug": 'my-blog-post'});
更多資訊請閱讀這個bundle文件。
產生帶有Query Strings的URL ¶
#這個generate
方法採用萬用字元陣列來產生URL。但如果在其中加入了額外的鍵值對,他們將會被加成Query Strings來產生一個新的URL:
$this->get('router')->generate('blog', array( 'page' => 2, 'category' => 'Symfony'));// /blog/2?category=Symfony
在模板裡產生URL ¶
##在應用程式頁面之間進行連接時,最常見的地方就是從模板中產生URL。這樣做其實和以前一樣,但是使用的是一個模板助手函數:
Twig:<a href="{{ path('blog_show', {'slug': 'my-blog-post'}) }}"> Read this blog post. </a>
php:<a href="<?php echo $view['router']->path('blog_show', array( 'slug' => 'my-blog-post',)) ?>"> Read this blog post. </a>
產生絕對的URL ¶
預設情況下,路由器會產生相對的URL (如/blog
)。在控制器中,很簡單的把generateUrl()
方法的第三個參數設定成UrlGeneratorInterface::ABSOLUTE_URL
即可。
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
在模板引擎Twig中,要使用url()
函數(產生一個絕對的URL),而不是path()
函數(產生一個相對的URL)。在php中,需要要在generateUrl()中傳入UrlGeneratorInterface::ABSOLUTE_URL:
Twig:<a href="{{ url('blog_show', {'slug': 'my-blog-post'}) }}"> Read this blog post. </a>
php:<a href="<?php echo $view['router']->url('blog_show', array( 'slug' => 'my-blog-post',)) ?>"> Read this blog post. </a>
当生成一个绝对URL链接时,所使用的主机自动检测当前使用的
Request
对象。当生成从web环境外的绝对URL(例如一个控制台命令)这是行不通的。请参见 如何从控制台生成URL 来学习如何解决这个问题。
總結 ¶
路由是一個將傳入的請求之URL對應到用來處理該請求的控制器函數的系統。它允許你指定一個美觀的URL,並使應用程式的功能與URL「脫鉤」。路由是一個雙向的機制,這意味著它也可以用來產生URL。
Keep Going! ¶
路由,核對完畢!現在,去解封控制器的力量。