路由
對(duì)于任何嚴(yán)謹(jǐn)?shù)膚eb應(yīng)用程序而言美觀的URL是絕對(duì)必須的。這意味著index.php?article_id=57
這類丑陋的URL要被/read/intro-to-symfony
取代。
擁有靈活性是更加重要的。你將頁(yè)面的URL從/blog
改為/news
時(shí)需要做些什么?你需要跟蹤和更新多少鏈接以做出改變?如果你使用Symfony的路由,這就很容易了。
Symfony路由器允許你定義創(chuàng)造性的url,映射到應(yīng)用程序的不同區(qū)域。在本章結(jié)束時(shí),你將可以做到:
創(chuàng)建復(fù)雜的路由,它們將映射到控制器
在模板和控制器中生成URL
從Bundle中(也可以從其它地方)加載路由資源
對(duì)路由除錯(cuò)
路由樣例 ?
一個(gè)路由,是指一個(gè)URL路徑(path)到一個(gè)控制器(controller)的映射。例如,你想匹配一些URL:/blog/my-post
和 /blog/all-about-symfony
,并且發(fā)送路由到一個(gè)“能夠查詢和渲染那一篇博文”的控制器上。路由簡(jiǎn)單的很:
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,把相關(guān)參數(shù)或通配符用slug
表示并傳入。對(duì)于/blog/my-blog-post
這樣的URL,slug
變量得到my-blog-post
的值,并供你控制器使用。美觀blog_show
是一個(gè)內(nèi)部名稱,他沒(méi)什么實(shí)際的意義就是一個(gè)唯一的標(biāo)識(shí)。以后,你可用他來(lái)生成一些URL。
如果你不想去使用注釋方式,因?yàn)槟悴幌矚g他們,或者因?yàn)槟悴幌M蕾囉赟ensioFrameworkExtraBundle,你也可以使用YAML,XML或者PHP。在這些格式中,_controller
參數(shù)是一個(gè)特殊的鍵,它告訴symfony路由指定的URL應(yīng)該執(zhí)行哪個(gè)控制器。_controller
字符串稱為邏輯名。它遵循規(guī)則指向一個(gè)特定的php類和方法,AppBundle\Controller\BlogController::showAction
方法。
恭喜!你剛剛創(chuàng)建了一個(gè)路由并把它連接到控制器?,F(xiàn)在,當(dāng)你訪問(wèn)/blog/my-post
,showAction
控制器將被執(zhí)行并且$slug
變量就等于my-post
。
Symfony路由的目標(biāo):將請(qǐng)求的URL映射到控制器。遵循這一目標(biāo),你將學(xué)習(xí)到各式各樣的技巧,甚至使映射大多數(shù)復(fù)雜的URL變得簡(jiǎn)單。
路由:深入了解 ?
當(dāng)一個(gè)請(qǐng)求發(fā)送到你的應(yīng)用程序,它包含一個(gè)確切的“資源”的客戶端請(qǐng)求地址。該地址被稱為URL(或URI),它可以是/contact
、/blog/read-me
或其它任何東西。下面是一個(gè)HTTP請(qǐng)求的例子:
GET /blog/my-blog-post
symfony路由系統(tǒng)的目的是解析url,并確定調(diào)用哪個(gè)控制器。整個(gè)過(guò)程是這樣的:
由Symfony的前端控制器(如
app.php
)來(lái)處理請(qǐng)求。symfony的核心(Kernel內(nèi)核)要求路由器來(lái)檢查請(qǐng)求。
路由將輸入的URL匹配到一個(gè)特定的路由,并返回路由信息,其中包括要執(zhí)行的控制器信息。
Symfony內(nèi)核執(zhí)行控制器并最終返回
Response
對(duì)象。
路由是將一個(gè)輸入U(xiǎn)RL轉(zhuǎn)換成特定的工具來(lái)執(zhí)行控制器。
創(chuàng)建路由 ?
Symfony從一個(gè)單一的路由配置文件中加載所有的路由到你的應(yīng)用程序。美觀路由配置文件通常是app/config/routing.yml
,但你也可以通過(guò)應(yīng)用程序配置文件將該文件放置在任何地方(包括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', ),));
盡管所有的路由都可以從一個(gè)文件加載,但是通常的做法是包含額外的路由資源。為此,你要把外部的路由文件配置到主要路由文件中。具體信息可查看本章:包含外部路由資源。
基本的路由配置 ?
定義一個(gè)路由是容易的,一個(gè)典型的應(yīng)用程序也應(yīng)該有很多的路由。一個(gè)基本的路由包含兩個(gè)部分:path
匹配和defaults
數(shù)組:
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;
該路由匹配首頁(yè)(/
)并將它映射到AppBundle:Main:homepage
控制器。_controller
字符串被Symfony轉(zhuǎn)換成PHP函數(shù)去執(zhí)行。美觀過(guò)程在本章(控制器命名模式)中被簡(jiǎn)短提及。
帶參數(shù)的路由 ?
路由系統(tǒng)支持很多有趣的路由寫法。許多的路由都可以包含一個(gè)或者多個(gè)“參數(shù)或通配符”占位符:
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}
占位符將自動(dòng)匹配到控制器中。換句話說(shuō),如果該URL是/blog/hello-world
,控制器中$slug
變量的值就是hello-world
。這可以用來(lái),匹配博客文章標(biāo)題的字符串。
然而這種方式路由將不會(huì)匹配/blog
這樣的URL,因?yàn)槟J(rèn)情況下,所有的占位符都是必填的。當(dāng)然這也是可以變通的,可以在defaults
數(shù)組中添加占位符(參數(shù))的值來(lái)實(shí)現(xiàn)。
添加{通配符}的條件 ?
快速瀏覽一下已經(jīng)創(chuàng)建的路由:
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;
你能發(fā)現(xiàn)問(wèn)題嗎??jī)蓚€(gè)路由都匹配類似/blog/*
的URL。Symfony路由總是選擇它第一個(gè)匹配的(blog)路由。換句話說(shuō),該blog_show
路由永遠(yuǎn)被匹配。相反,像一個(gè)/blog/my-blog-post
的URL會(huì)匹配第一個(gè)(blog
)路由,并返回一個(gè)my-blog-post
的值給{page}
參數(shù)。
URL | Route | Parameters |
---|---|---|
/blog/2 | blog_list | $page = 2 |
/blog/my-blog-post | blog_show | $slug = my-blog-post |
給{通配符}一個(gè)默認(rèn)值 ?
高級(jí)的路由樣例 ?
在Symfony中你可以通過(guò)創(chuàng)建一個(gè)強(qiáng)大的路由結(jié)構(gòu)來(lái)實(shí)現(xiàn)你所需的一切。下面是一個(gè)示例來(lái)展示路由系統(tǒng)是如何的靈活:
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}
為(en
或者fr
)和 {year}
是數(shù)字的。該路由還向你展示了你可以使用一個(gè)句號(hào)來(lái)分割兩個(gè)占位符。上面路由匹配的URL如下:
/articles/en/2010/my-post
/articles/fr/2010/my-post.rss
/articles/en/2013/my-latest-post.html
有時(shí),你想要令路由中的某些部分成為“全局配置”。symfony可以利用服務(wù)容器參數(shù)來(lái)做到這一點(diǎn)。參考如何在路由中使用服務(wù)容器的參數(shù)以了解更多。
特殊的路由參數(shù) ?
正如你所看到的,每個(gè)路由參數(shù)或默認(rèn)值都可以作為控制器方法的參數(shù)。此外,有三個(gè)參數(shù)是特殊的:在你的應(yīng)用程序中每個(gè)都是給你的應(yīng)用增加一個(gè)獨(dú)特的功能:
_controller
- 正如你所看到的,這個(gè)參數(shù)是用來(lái)決定“當(dāng)路由匹配時(shí)”要執(zhí)行哪個(gè)控制器的。
_format
- 用于設(shè)置請(qǐng)求格式(request format。了解詳情)。
_locale
- 用于設(shè)置請(qǐng)求的locale (了解詳情).
控制器命名模式 ?
如果你使用YAML,XML或PHP的路由配置,那么每個(gè)路由都必須有一個(gè)_controller
參數(shù),用于指示當(dāng)路匹配時(shí)應(yīng)執(zhí)行哪個(gè)控制器。這個(gè)參數(shù)使用一個(gè)簡(jiǎn)單的字符串pattern,叫做控制器邏輯名(logical controller name),Symfony用它來(lái)映射一個(gè)特定的PHP方法或類。此pattern有三個(gè)部分,用冒號(hào)隔開(kāi):
bundle:controller:action
假設(shè),一個(gè)_controller
值是一個(gè)AppBundle:Blog:show
那么意味著:
Bundle | Controller Class | Method 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類名及方法來(lái)指定這個(gè)類:AppBundle\Controller\BlogController::showAction
。但如果你遵循一些簡(jiǎn)單的命名約定,邏輯名將更加簡(jiǎn)潔也更加靈活。
除了使用邏輯名和FQCN類名之外,Symfony也支持第三種指定控制器的方式。這種方式只使用一個(gè)冒號(hào)分隔(如service_name:indexAction
),并將控制器作為一個(gè)服務(wù)來(lái)引用(參見(jiàn)如何把控制器定義為服務(wù))。
路由參數(shù)和控制器參數(shù) ?
路由參數(shù)(如{slug}
是非常重要的,因?yàn)樗▊儯┒急挥米骺刂破鞣椒ǖ膮?shù):
public function showAction($slug){ // ...}
現(xiàn)實(shí)中,defaults
集將參數(shù)值一起合并成一個(gè)表單數(shù)組。該數(shù)組中的每個(gè)鍵都被做為控制器的參數(shù)。
換句話說(shuō),對(duì)于控制器方法的每個(gè)參數(shù),Symfony2都會(huì)根據(jù)該名稱來(lái)查找路由參數(shù),并將其值指向到控制器作為參數(shù)。在上面的高級(jí)示例當(dāng)中,下列變量的任何組合(以任意方式)都被用作showAction()
方法的參數(shù):
$_locale
$year
$title
$_format
$_controller
$_route
占位符和defaults
集被合并在一起,就就算是$_controller
變量也是可用的。更多細(xì)節(jié)的討論,請(qǐng)參見(jiàn)作為 控制器–把路由參數(shù)傳入控制器。
你也可以使用指定的$_route
變量,它的值是被匹配的路由名。
你甚至可以在你的路由中定義額外的信息并在你的控制器中訪問(wèn)它。關(guān)于更多信息請(qǐng)閱讀 如何從路由向控制器傳遞額外的信息
生成URL ?
路由系統(tǒng)也用于生成URL。在現(xiàn)實(shí)中,路由是一個(gè)雙向系統(tǒng):映射URL到控制器+參數(shù)以及映射路由+參數(shù)返回URL。match()
和generate()
方法構(gòu)成了這個(gè)雙向系統(tǒng)。使用之前的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
要生成一個(gè)URL,你需要指定路由的名稱(如blog_show
)以及任意的通配符(如slug = my-blog-post
)。有個(gè)這些信息,任何URL就可以很容易的生成了:
class MainController extends Controller{ public function showAction($slug) { // ... $url = $this->generateUrl( 'blog_show', array('slug' => 'my-blog-post') ); }}
在控制器中你沒(méi)有繼承symfony的父類Controller,那么你不可以使用generateUrl()
快捷方法,但你可以使用router的generate()
服務(wù)方法:
$url = $this->container->get('router')->generate( 'blog_show', array('slug' => 'my-blog-post'));
在即將到來(lái)的部分中,你將學(xué)會(huì)如何在模板中生成URL地址。
如果你的應(yīng)用程序前端使用的是ajax請(qǐng)求,你可能希望根據(jù)你的路由配置,在JavaScript中生成URL。通過(guò)使用FOSJsRoutingBundle,你就可以做到:
var url = Routing.generate( 'blog_show', {"slug": 'my-blog-post'});
更多信息請(qǐng)閱讀這個(gè)bundle文檔。
生成帶有Query Strings的URL ?
這個(gè)generate
方法采用通配符數(shù)組來(lái)生成URL。但是如果在其中添加了額外的鍵值對(duì),他們將會(huì)被添加成Query Strings來(lái)生成一個(gè)新的URL:
$this->get('router')->generate('blog', array( 'page' => 2, 'category' => 'Symfony'));// /blog/2?category=Symfony
在模板里生成URL ?
在應(yīng)用程序頁(yè)面之間進(jìn)行連接時(shí),最常見(jiàn)的地方就是從模板中生成URL。這樣做其實(shí)和以前一樣,但是使用的是一個(gè)模板助手函數(shù):
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>
生成絕對(duì)的URL ?
默認(rèn)情況下,路由器會(huì)產(chǎn)生相對(duì)的URL(如/blog
)。在控制器中,很簡(jiǎn)單的把generateUrl()
方法的第三參數(shù)設(shè)置成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()
函數(shù)(生成一個(gè)絕對(duì)的URL),而不是path()
函數(shù)(生成一個(gè)相對(duì)的URL)。在php中,需要要在generateUrl()中傳入U(xiǎn)rlGeneratorInterface::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>
當(dāng)生成一個(gè)絕對(duì)URL鏈接時(shí),所使用的主機(jī)自動(dòng)檢測(cè)當(dāng)前使用的
Request
對(duì)象。當(dāng)生成從web環(huán)境外的絕對(duì)URL(例如一個(gè)控制臺(tái)命令)這是行不通的。請(qǐng)參見(jiàn) 如何從控制臺(tái)生成URL 來(lái)學(xué)習(xí)如何解決這個(gè)問(wèn)題。
總結(jié) ?
路由是一個(gè)將傳入的請(qǐng)求之URL映射到用來(lái)處理該請(qǐng)求的控制器函數(shù)的系統(tǒng)。它允許你指定一個(gè)美觀的URL,并使應(yīng)用程序的功能與URL“脫鉤”。路由是一個(gè)雙向的機(jī)制,意味著它也可以用來(lái)生成URL。
Keep Going! ?
路由,核對(duì)完畢!現(xiàn)在,去解封控制器的力量。