Symfony系列-路由

漂亮的路由对任何一个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_listblog_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}部分, 并且它只能是enfr. 匹配{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

你可能感兴趣的:(Symfony)