漂亮的路由对任何一个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.
为了生成一条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')
);
}
}
通过Query String生成URL
$this->get('router')->generate('blog', array(
'page' => 2,
'category' => 'Symfony'
));
// /blog/2?category=Symfony
生成绝对路径的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