路由


對於任何嚴謹的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。

如果你不想去使用註解方式,因為你不喜歡他們,或因為你不希望依賴SensioFrameworkExtraBundle,你也可以使用YAML,XML或PHP。在這些格式中,

_controller參數是一個特殊的鍵,它告訴symfony路由指定的URL應該執行哪個控制器。 _controller字串稱為邏輯名稱。它遵循規則指向一個特定的php類別和方法,AppBundle\Controller\BlogController::showAction方法。

恭喜!你剛剛創建了一個路由並把它連接到控制器。現在,當你造訪

/blog/my-postshowAction控制器將被執行並且$slug變數就等於my-post

Symfony路由的目標:將請求的URL對應到控制器。遵循這一目標,你將學習到各式各樣的技巧,甚至使映射大多數複雜的URL變得簡單。

路由:深入了解 

當一個請求發送到你的應用程序,它包含一個確切的「資源」的客戶端請求地址。這個位址被稱為URL(或URI),它可以是/contact/blog/read-me或其它任何東西。以下是一個HTTP請求的範例:

GET /blog/my-blog-post

symfony路由系統的目的是解析url,並決定要呼叫哪個控制器。整個過程是這樣的:

  1. 由Symfony的前端控制器(如app.php)來處理請求。

  2. symfony的核心(Kernel核心)要求路由器來檢查請求。

  3. 路由將輸入的URL配對到一個特定的路由,並傳迴路由訊息,其中包括要執行的控制器資訊。

  4. Symfony核心執行控制器並最終返回Response物件。

1466599518_84162_47822_request-flow-1.png

路由是將一個輸入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}參數。

#

給{通配符}一個預設值 

進階的路由範例 

在Symfony中你可以透過創造一個強大的路由結構來實現你所需的一切。以下是一個範例來展示路由系統是如何的靈活:

Annotations:// src/AppBundle/Controller/ArticleController.php // ...class ArticleController extends Controller{
    /**
     * @Route(
     *     "/articles/{_locale}/{year}/{title}.{_format}",
     *     defaults={"_format": "html"},
     *     requirements={
     *         "_locale": "en|fr",
     *         "_format": "html|rss",
     *         "year": "\d+"
     *     }
     * )
     */
    public function showAction($_locale, $year, $title)
    {
    }}
YAML:# app/config/routing.ymlarticle_show:
  path:     /articles/{_locale}/{year}/{title}.{_format}
  defaults: { _controller: AppBundle:Article:show, _format: html }
  requirements:
      _locale:  en|fr
      _format:  html|rss
      year:     \d+
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="article_show"        path="/articles/{_locale}/{year}/{title}.{_format}">         <default key="_controller">AppBundle:Article:show</default>
        <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:// app/config/routing.phpuse Symfony\Component\Routing\RouteCollection;use Symfony\Component\Routing\Route; $collection = new RouteCollection();$collection->add(
    'article_show',
    new Route('/articles/{_locale}/{year}/{title}.{_format}', array(
        '_controller' => 'AppBundle:Article:show',
        '_format'     => 'html',
    ), array(
        '_locale' => 'en|fr',
        '_format' => 'html|rss',
        'year'    => '\d+',
    ))); return $collection;

正如你所看到的,美觀路由只匹配一部分URL也就是滿足{_locale}為( enfr)和{year}是數字的。路由也向你展示了你可以使用一個句號來分割兩個佔位符。上面路由匹配的URL如下:

  • /articles/en/2010/my-post

  • /articles/fr/2010/my-post.rss

  • /articles/en/2013/my-latest-post.html

特殊的_format路由參數

這個範例也突顯了特殊的_format路由參數。當使用這個參數時,匹配值將成為Request物件的「request format」(請求格式)。

最終,請求格式被用在「設定回應的Content-Type」這種地方(如一個json請求格式將轉換成application/ jsonContent-Type)。它也可以在控制器中使用,根據不同的_format值去渲染不同的模板。 _format參數是一種非常強大的方式,把相同的內容以不同格式來渲染(譯註:即輸出)。

在symfony3.0之前的版本中,可以覆寫request(物件)中的格式參數(_format),透過新增名為「_format」的query參數即可(例:/foo/bar?_format=json)。濫用這種行為被認為是很不好的實踐,而且還會令你的程式在升級到symfony3時「特別複雜」。

有時,你會想要讓路由中的某些部分成為「全域設定」。 symfony可以利用服務容器參數來做到這一點。參考如何在路由中使用服務容器的參數以了解更多。

#

特殊的路由參數 ¶

#如你所看到的,每個路由參數或預設值都可以作為控制器方法的參數。此外,有三個參數是特殊的:在你的應用程式中每個都是為你的應用程式增加一個獨特的功能:

  • ##_controller
  • #如你所看到的,這個參數是用來決定「當路由匹配時」要執行哪個控制器的。
  • _format
  • 用於設定請求格式(request format。了解詳情)。
  • _locale
  • 用於設定請求的locale (了解詳情).
控制器命名模式 

如果你使用YAML,XML或PHP的路由配置,那麼每個路由都必須有一個

_controller參數,用於指示當路匹配時應執行哪個控制器。這個參數使用一個簡單的字串pattern,叫做控制器邏輯名稱(logical controller name),Symfony用它來映射一個特定的PHP方法或類別。此pattern有三個部分,用冒號隔開:

##bundle:controller:action

假設,一個
_controller

值是一個AppBundle:Blog:show那麼意味著:

URLRouteParameters
/blog/2#blog_list$page = 2
/blog/my-blog-post##blog_show#$slug = my-blog-post
##BundleController ClassMethod Name#AppBundle
##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! ¶

路由,核對完畢!現在,去解封控制器的力量。