漂亮的路由对任何一个WEB应用而言都是刚需. 这意味着我们要抛弃类似 index.php?article_id=57 这样丑陋的URL, 而使用像 /read/intro-to-symfony 语义化的url.
灵活性也是非常重要的. 如果你需要把你页面所有的/blog变成/news, 你要查找多少次, 替换多少次? 如果你使用Symfony的路由器, 那改个URL什么的就变得非常非常简单.
Symfony路由器可以让你定义出各种个性化的URL, 映射到你应用的各个地方. 了解完这个章节后, 你会:
- 创建复杂的路由映射到控制器 - 在控制器和模板中生成URL - 从包中加载路由资源(或者说任何地方) - 调试路由一条路由就是把URL路径映射到控制器. 例如. 你想匹配类似 /blog/my-post 或 /blog/all-about-symfony 的路径. 并且把它们分发到通过查找获取到的控制器. 并且渲染一个blog实体. 要实现这个功能, 非常简单:
译者注: 路由定义有四种方法 通过注释定义路由 通过yaml配置文件定义路由 通过xml配置文件定义路由 通过php配置文件定义路由
推荐使用yaml的方式, 但是为了方便, 我们在下面统一使用注释方式进行讲解.
// src/AppBundle/Controller/BlogController.php namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class BlogController extends Controller { /** * Matches /blog exactly * * @Route("/blog", name="blog_list") */ public function listAction() { // ... } /** * Matches /blog/* * * @Route("/blog/{slug}", name="blog_show") */ public function showAction($slug) { // $slug will equal the dynamic part of the URL // e.g. at /blog/yay-routing, then $slug='yay-routing' // ... } }这两条路由的作用是:
如果用户访问 /blog, 将会匹配第一条路由, 并执行listAction().
如果用户访问 /blog/*, 第二条路由将会被匹配到, showAction()方法被执行. 因为路由是 /blog/{slug}, $slug变量会被传给showAction. 例如, /blog/yay-routing, $slug 会等于 yay-routing.
不管什么时候, 路由当中的{placeholder}总是相当于一个通配符: 它匹配任何字符. 你的控制器现在可以有一个参数叫$placeholder (路由当中的通配符必须和方法中的参数名相同)
每条路由都会有个内部名字: blog_list 和 blog_show. 当然, 你可以随意命名. 每个命名必须是唯一的. 稍晚, 我们再来探讨怎么用它来生成URL.
这就是Symfony路由器的目的: 把URL映射到控制器. 随着时间的推移, 你会了解到所有的方法, 可以让你轻而易举地创建更复杂的路由.
想象一下这样的场景, blog_list路由会包含一个分页列表, 它的路由类似于/blog/2和/blog/3来对应第二页和第三页. 如果你把它改成 /blog/{page}, 会有以下的问题:
blog_list: /blog/{page}会匹配/blog/*;blog_show: /blog/{slug} 也会匹配 /blog/*;当两条路由规则匹配同一个URL时, 第一个路由会被优先使用. 不幸的是, 这意味着 /blog/yay-routing 会匹配 blog_list, 这肯定不行!
为了解决这样的问题, 对 {page}通配符添加一个约束, 让它只匹配数字:
// src/AppBundle/Controller/BlogController.php namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class BlogController extends Controller { /** * @Route("/blog/{page}", name="blog_list", requirements={"page": "\d+"}) */ public function listAction($page) { // ... } /** * @Route("/blog/{slug}", name="blog_show") */ public function showAction($slug) { // ... } }\d+是正则表达式, 它匹配一个或多个数字. 现在路由又可以正常地工作了.
在上面的例子中, blog_list匹配/blog/{page}这样的路径. 如果用户访问/blog/1, 路由会匹配. 但是如果用户访问的是 /blog, 它是不会匹配的. 一旦你添加了 {placeholder}到路由中, 它必须要匹配一个值.
那我们要怎么样让/blog_list匹配/blog这样的路由呢? 添加一个默认值就可以了:
// src/AppBundle/Controller/BlogController.php namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class BlogController extends Controller { /** * @Route("/blog/{page}", name="blog_list", requirements={"page": "\d+"}) */ public function listAction($page = 1) { // ... } }现在, 当用户再访问/page的时候, /blog_list路由就会匹配$page, 并给它一个默认值为1.
把上面所有的内容都理解之后, 来看看下面这个高级一点儿的例子:
// 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) { } }就像你上面看到的一样, 这条路由将会匹配 URL中的 {_locale}部分, 并且它只能是en或fr. 匹配{year}, 只能为数字. 这条路由也向我们展示了怎么在占位符之间用.来代替/. 它也会匹配下面的URLS:
/articles/en/2010/my-post
/articles/fr/2010/my-post.rss
/articles/en/2013/my-latest-post.html
如果你使用YAML, XML 或者php作为路由的配置文件, 那么每一条路由都必须包含 _controller参数, 它来决定当路由匹配的时候应该运行哪一个控制器. 这个参数使用一个简单的字符串, 叫做逻辑控制器名, Symfony会将它映射到指定的方法和类. 这个部分分为三部分, 通过:号分隔.
bundle:controller:action
包名:控制器:方法
比如, AppBundle:Blog:show 的意思是, 包名为AppBundle, 控制器为Blog, 方法为show.
控制器的内容是这样的:
// src/AppBundle/Controller/BlogController.php namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class BlogController extends Controller { public function showAction($slug) { // ... } }注意Symfony会在类名后加上Controller (Blog=>BlogController), 方法名后加上Action(show => showAction).
你也可以使用完全限定的类名和方法名来指向这个控制器: AppBundle\Controller\BlogController::showAction. 但是如果你喜欢简单方便, 逻辑名可以更简单,更灵活.
除了使用逻辑名和完全限定类名, Symfony也支持第三种方式来指向控制器. 这种方法只需要一个冒号 ( service_name:indexAction), 可以把控制器作为一个服务使用.
Symfony在一个配置文件中加载所有的路由: app/config/routing.yml. 但是在这个文件中, 你可以加载其它的路由文件. 实际上, Symfony默认从AppBundle中的Controller/目录中加载了注释方式的路由.
# app/config/routing.yml app: resource: "@AppBundle/Controller/" type: annotation更多的关于加载路由的细节, 我们会在其它章节中进行讨论.
路由系统当然也可以生成路由. 实际上, 路由是一个双向系统: 把URL映射到控制器, 把控制器变成URL.
为了生成一条URL, 你需要指定路由的名字 (blog_show) 和需要的通配符. 示例:
class MainController extends Controller { public function showAction($slug) { // ... // /blog/my-blog-post $url = $this->generateUrl( 'blog_show', array('slug' => 'my-blog-post') ); } }