Symfony2学习笔记之系统路由详解

Bailador 是对 Perl Dancer Web 开发框架的模仿。安装方法:

本文详细讲述了Symfony2的系统路由。分享给大家供大家参考,具体如下:

panda install Bailador# orzef install Bailador

漂亮的URL绝对是一个严肃的web应用程序必须做到的,这种方式使index.php?article_id=57这类的丑陋URL被隐藏,由更受欢迎的像
/read/intro-to-symfony 来替代。

我们来创建一个脚本 first.pl,打印 “hello world”:

拥有灵活性更为重要,如果你要改变一个页面的URL,比如从/blog 到 /new
怎么办?

use v6;use Bailador;get '/' => sub { "hello world"}baile;

有多少链接需要你找出来并更新呢?
如果你使用Symfony的router,这种改变将变得很简单。

运行:perl6 first.pl 它会启动一个小型的 Web
服务器,你可以在3000端口上访问它:

Symfony2
router让你定义更具创造力的URL,你可以map你的应用程序的不同区域。

$ perl6 first.plEntering the development dance floor: http://0.0.0.0:3000[2016-05-05T12:57:31Z] Started HTTP server.

创建复杂的路由并map到controllers并可以在模板和controllers内部生成URLs

在 Bailador 中,我们需要把 HTTP
请求方法和服务器上的路径映射给一个匿名子例程,
这个子例程会返回它里面的内容。在这个例子中,我们把我们告诉它的网站根路径的
get HTTP 请求映射为返回字符串 hello
world
。如果你启动这个程序并用浏览器打开
你就会看到这个文本。

从bundles(或者其他任何地方)加载路由资源

我们还可以映射其它路径:

调试你的路由

get '/about' => sub { "关于我"}

路由活动

这会把 url 映射为返回 「关于我」。

一个路径是一个从URL 模式到一个controller的绑定。

路径中的一部分可以是以冒号开头的占位符:

比如假设你想匹配任何像 /blog/my-post 或者
/blog/all-about-symfony的路径并把它们发送到一个controller在那里可以查找并渲染blog实体。

get '/hello/:name' => sub  { "Hello $name!"};

该路径很简单:

:name 部分能匹配除了斜线 /
之外的任何字符串,并且它所匹配到的值会被赋值给匿名子例程中的 $name
变量。

YAML格式:

这样的占位符你可以拥有多个,并且占位符的实际名字是什么无关紧要。占位符所捕获到的值会按照它们出现在
url 中的顺序赋值给函数的参数。

# app/config/routing.yml
blog_show:
pattern: /blog/{slug}
defaults: {_controller: AcmeBlogBundle:Blog:show }
get '/hello/:first/:family' => sub ($fname, $lname) { "Hello $fname! And hi $lname"};

XML格式:

在这个例子中,无论 :first 占位符捕获到的是什么,它都会被赋值给
$fname 参数,无论 :family 捕获到的是什么,它都会被赋值给
:$lname。例如 url 会生成如下响应:

<!-- 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" pattern="/blog/{slug}">
<default key="_controller">AcmeBlogBundle:Blog:show</default>
</route>
</routes>
Hello Foo! And hi Bar!

PHP代码格式:

当然,让占位符的名字和参数的名字相同可能会让代码更易读。这是第二个脚本的完整版本:

// app/config/routing.php
use SymfonyComponentRoutingRouteCollection;
use SymfonyComponentRoutingRoute;
$collection = new RouteCollection();
$collection->add('blog_show', new Route('/blog/{slug}', array(
     '_controller' => 'AcmeBlogBundle:Blog:show',
)));
use v6;use Bailador;get '/' => sub { "hello world"}get '/hello/:first/:family' => sub ($fname, $lname) { "Hello $fname! And hi $lname"};baile;

blog_show路径定义了一个URL模式,它像/blog/*
这里的通配符被命名为slug。对于URL/blog/my-blog-post,slug变量会得到值
my-blog-post。
_controller参数是一个特定的键,它告诉Symfogy当一个URL匹配这个路径时哪个controller将要被执行。
_controller字符串被称为逻辑名。它的值会按照特定的模式来指定具体的PHP类和方法。

我们来看看怎么从用户那儿接收输入并把输入回显给用户。

// src/Acme/BlogBundle/Controller/BlogController.php
namespace AcmeBlogBundleController;
use SymfonyBundleFrameworkBundleControllerController;
class BlogController extends Controller
{
  public function showAction($slug)
  {
    $blog = // use the $slug variable to query the database
    return $this->render('AcmeBlogBundle:Blog:show.html.twig', array(
      'blog' => $blog,
    ));
  }
}

对于这,我们必须创建两个路由因为现在 Bailador 还不能处理 GET 参数。

现在当你再访问/blog/my-post 时,showAction
controller将被执行并且$slug变量的值为my-post

# echo_post.p6use v6;use Bailador;get '/' => sub { '<form method="POST" action="/echo"><input name="text"><input type="submit"></form>';}post '/echo' => sub { my $text = request.params<text> // ''; my $html = 'You said (in a POST request) '; $html ~= $text; return $html;}baile;

Symfogy2 的路由器目标:映射一个请求的URL到controller。

图片 1img

路由:内部的秘密

我们能看到怎么创建一个路由来处理 POST 请求。

当一个请求发送到应用程序时,它包含一个客户端想要获取资源的地址。这个地址叫做URL或者URI。可能是/contact,/blog/read-me或者其它样式。

第一个路由 get ‘/’ => { 会发送一个 GET
请求并且它会返回一个包含在这个脚本中的 HTML
片段。(我知道,我们很快就会使用模板了) 那个 HTML
片段包含了一个带有单个文本框的表单和一个提交按钮。这个表单有一个通向
/echo URL 的 action,并且表单拥有
method=”POST”。这意味着,当用户点击提交按钮时,浏览器会发送回 POST
请求。

GET /blog/my-blog-post

第二个路由 post ‘/echo’ => sub { 会处理 /echo 路径的 POST
请求。

Symfony2
路由系统的目标是解析这些URL并决定哪个controller应该被执行来回复该请求。

Bailador 提供的 request 函数以
Bailador::Request的形式返回代表当前请求的对象。

整个路由过程可以分为:

request 函数有几个方法,其中一个是 params
方法,它返回一个散列,其中散列的键是参数的名字(在我们这个例子中是
text),值是提交的值。

1.请求被Symfony2的前端控制器(app.php)处理。
2.Symfony2核心(kernel)要求路由器检查请求。
3.路由器匹配接收到的URL到一个特定的路径并返回有关信息,包括应该被执行的controller。
4.Symfony2核心执行该controller,该controller最终会返回一个Response对象。

我们把那个值保存在 $text 变量中,并且我们使用 ‘//’ defined-or
操作符来设置变量的值为空,在用户没有提供任何值的情况下。然后我们连接用户提供的值组成
“html”
字符串。最后发送回那个字符串,我们这个小小的回显服务器就能工作啦。

路由器层就是一个把接收到的URL转换为要执行的特定controller的工具。

图片 2img

创建路由

use v6;use Bailador;get '/' => sub { '<form method="GET" action="/echo"><input name="text"><input type="submit"></form>';}get '/echo' => sub { return 'You said (in a GET request) ' ~ (request.params<text> // '');}baile;

Symfony会从一个单独的路由配置文件中加载你应用程序的所有路由。该文件通常为
app/config/routing.yml。 它可以被配置成包括XML或者PHP文件等文件。

图片 3img

YAML格式:

在这个例子中,我省略了临时变量 $text
$html,在之前的例子中它们也不是必要的。当我们使用 GET
方法请求后,提交后回在浏览器的 URL 地址栏中拼接上我们的 text
字段和字段的值。

# app/config/config.yml
framework:
  # ...
  router:    { resource: "%kernel.root_dir%/config/routing.yml" }

在下面这个模板中,它把数据接收到变量 $h
中,之后使用这个变量来展示版本号和当前时间 –
从纪元开始的秒数。bailador/code_in_module/views/index.tt

XML格式:

% my  = @_;<!DOCTYPE html><html> <head> <title>Bailador App</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> </head> <body> <h1>Bailador App</h1> <div> Version <%= $h<version> %> Current time: <%= $h<date> %> </div> </body></html>
<!-- app/config/config.xml -->
<framework:config ...>
  <!-- ... -->
  <framework:router resource="%kernel.root_dir%/config/routing.xml" />
</framework:config>

这个文件把所有代码包含在类中:

PHP代码格式:

unit class Demo;
// app/config/config.php
$container->loadFromExtension('framework', array(
  // ...
  'router'    => array('resource' => '%kernel.root_dir%/config/routing.php'),
));

为了拥有特定领域语言,它加载了 Bailador 以让我们定义路由更容易。

基础路由配置

use Bailador;

定义一个路由很简单,通常一个应用程序拥有很多路由。一个基础路由是由两部分组成:pattern部分和defaults数组部分。
比如:

最重要的是它包含了路由。

YAML格式:

unit class Demo;use Bailador; my $version = '0.01';get '/' => sub { template 'index.tt', { version => $version, date => time }}

use Bailador;Bailador::import();use lib callframe.file.IO.dirname ~ '/lib';use Demo;baile;
_welcome:
  pattern:  /
  defaults: { _controller: AcmeDemoBundle:Main:homepage }

最有意思的应该是这段代码:

XML格式:

use lib callframe.file.IO.dirname ~ '/lib';
<?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" pattern="/">
    <default key="_controller">AcmeDemoBundle:Main:homepage</default>
  </route>
</routes>

它计算这个工程的根目录 – 假设 app.pl 文件在根目录中 – 然后把 /lib
子目录添加到 perl 将要查找额外模块的地方。这会在 lib 子目录下加载
Demo.pm 文件。

PHP代码格式:

图片 4img

use SymfonyComponentRoutingRouteCollection;
use SymfonyComponentRoutingRoute;
$collection = new RouteCollection();
$collection->add('_welcome', new Route('/', array(
  '_controller' => 'AcmeDemoBundle:Main:homepage',
)));
return $collection;

该路由匹配首页(/)并映射到AcmeDemoBundle:Main:homepage
controller。_controller字符串被Symfony2翻译成一个相应的PHP函数并被执行。

带占位符路由

当然,路由系统支持更多有趣的路由。许多路由会包含一个或者多个被命名的通配符占位符。

YAML格式:

blog_show:
  pattern:  /blog/{slug}
  defaults: { _controller: AcmeBlogBundle:Blog:show }

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" pattern="/blog/{slug}">
    <default key="_controller">AcmeBlogBundle:Blog:show</default>
  </route>
</routes>

PHP代码格式:

use SymfonyComponentRoutingRouteCollection;
use SymfonyComponentRoutingRoute;
$collection = new RouteCollection();
$collection->add('blog_show', new Route('/blog/{slug}', array(
  '_controller' => 'AcmeBlogBundle:Blog:show',
)));
return $collection;

该模式将匹配任何类似/blog/*形式的URL。匹配占位符{slug}的值将会在controller中被使用。换句话说,如果URL是/blog/hello-world,则$slug变量值是hello-world,
该值将能在controller中被使用。该模式不会匹配像/blog,
因为默认情况下所有的占位符都是必须的。
当然可以通过在defaults数组中给这些占位符赋来改变它。

必需和可选占位符

我们来添加一个新的路由,显示所有可用的blog列表。

YAML格式:

blog:
  pattern:  /blog
  defaults: { _controller: AcmeBlogBundle:Blog:index }

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" pattern="/blog">
    <default key="_controller">AcmeBlogBundle:Blog:index</default>
  </route>
</routes>

PHP代码格式:

use SymfonyComponentRoutingRouteCollection;
use SymfonyComponentRoutingRoute;
$collection = new RouteCollection();
$collection->add('blog', new Route('/blog', array(
  '_controller' => 'AcmeBlogBundle:Blog:index',
)));
return $collection;

到目前为止,我们的路由都是非常简单的路由模式。它们包含的非占位符将会被精确匹配。

如果你想该路由能够支持分页,比如让/blog/2
显示第二页的blog,那就需要为之前的路由添加一个新的{page}占位符。

YAML格式:

blog:
  pattern:  /blog/{page}
  defaults: { _controller: AcmeBlogBundle:Blog:index }

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" pattern="/blog/{page}">
    <default key="_controller">AcmeBlogBundle:Blog:index</default>
  </route>
</routes>

PHP代码格式:

use SymfonyComponentRoutingRouteCollection;
use SymfonyComponentRoutingRoute;
$collection = new RouteCollection();
$collection->add('blog', new Route('/blog/{page}', array(
  '_controller' => 'AcmeBlogBundle:Blog:index',
)));
return $collection;

跟之前的{slug}占位符一样{page}占位符将会在你的controller内部可用,它的值可以用于表示要显示的blog值的页码。但是要清楚,因为占位符默认情况下都是必需的,该路由也将不再匹配之前的/blog
URL,这时候你如果还像看第一页的话,就必须通过/blog/1
URL来访问了。要解决该问题,可以在该路由的defaults数组中指定{page}的默认值。

YAML格式:

blog:
  pattern:  /blog/{page}
  defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }

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" pattern="/blog/{page}">
    <default key="_controller">AcmeBlogBundle:Blog:index</default>
    <default key="page">1</default>
  </route>
</routes>

PHP代码格式:

use SymfonyComponentRoutingRouteCollection;
use SymfonyComponentRoutingRoute;
$collection = new RouteCollection();
$collection->add('blog', new Route('/blog/{page}', array(
  '_controller' => 'AcmeBlogBundle:Blog:index',
  'page' => 1,
)));
return $collection;

通过添加page到defaults键, {page}占位符就不再是必需的。这时候
/blog将会被匹配并且page参数被设置为1,URL /blog/2 也会被匹配。

添加要求约束

看看下面这些路由:

YAML格式:

blog:
  pattern:  /blog/{page}
  defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
blog_show:
  pattern:  /blog/{slug}
  defaults: { _controller: AcmeBlogBundle:Blog:show }

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" pattern="/blog/{page}">
    <default key="_controller">AcmeBlogBundle:Blog:index</default>
    <default key="page">1</default>
  </route>
  <route id="blog_show" pattern="/blog/{slug}">
    <default key="_controller">AcmeBlogBundle:Blog:show</default>
  </route>
</routes>

PHP代码格式:

use SymfonyComponentRoutingRouteCollection;
use SymfonyComponentRoutingRoute;
$collection = new RouteCollection();
$collection->add('blog', new Route('/blog/{page}', array(
  '_controller' => 'AcmeBlogBundle:Blog:index',
  'page' => 1,
)));
$collection->add('blog_show', new Route('/blog/{show}', array(
  '_controller' => 'AcmeBlogBundle:Blog:show',
)));
return $collection;

你发现问题了吗?注意这两个路由都能匹配像/blog/*
类型的URL。Symfony只会选择第一个与之匹配的路由。

换句话说,blog_show将永远不会被像/blog/* 类型的URL匹配。而像
/blog/my-blog-post这样的URL也会被blog路由匹配,并且page变量会获得my-blog-post这样的值。

这肯定不可以,那么怎么办呢?答案是给路由添加约束要求requirements。

在blog路由中占位符{page}理想状态下只匹配整数值。幸运的是正则表达可以很容易的满足这一要求。

YAML格式:

blog:
  pattern:  /blog/{page}
  defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
  requirements:
    page: d+

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" pattern="/blog/{page}">
    <default key="_controller">AcmeBlogBundle:Blog:index</default>
    <default key="page">1</default>
    <requirement key="page">d+</requirement>
  </route>
</routes>

PHP代码格式:

use SymfonyComponentRoutingRouteCollection;
use SymfonyComponentRoutingRoute;
$collection = new RouteCollection();
$collection->add('blog', new Route('/blog/{page}', array(
  '_controller' => 'AcmeBlogBundle:Blog:index',
  'page' => 1,
), array(
  'page' => 'd+',
)));
return $collection;

这里 d+
约束是一个正则表达式,它指定了{page}只接受整数。这样像/blog/my-blog-post就不再被匹配了。这时候,它才会被blog_show路由匹配。因为参数的约束都是正则表达式,所以其复杂程度和灵活性都有你来决定了。
假设home页使用两种语言则可以这样配置路由:

YAML格式:

homepage:
  pattern:  /{culture}
  defaults: { _controller: AcmeDemoBundle:Main:homepage, culture: en }
  requirements:
    culture: en|fr

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="homepage" pattern="/{culture}">
    <default key="_controller">AcmeDemoBundle:Main:homepage</default>
    <default key="culture">en</default>
    <requirement key="culture">en|fr</requirement>
  </route>
</routes>

PHP代码格式:

use SymfonyComponentRoutingRouteCollection;
use SymfonyComponentRoutingRoute;
$collection = new RouteCollection();
$collection->add('homepage', new Route('/{culture}', array(
  '_controller' => 'AcmeDemoBundle:Main:homepage',
  'culture' => 'en',
), array(
  'culture' => 'en|fr',
)));
return $collection;

添加HTTP 方法约束

除了URL,你还可以匹配请求的方法(GET,HEAD,POST,PUT,DELETE等)。假设你有一个联系表单有两个controller,一个用于显示表单(使用GET请求)一个用于处理提交的表单(POST请求)。它的配置如下:

YAML格式:

contact:
  pattern: /contact
  defaults: { _controller: AcmeDemoBundle:Main:contact }
  requirements:
    _method: GET
contact_process:
  pattern: /contact
  defaults: { _controller: AcmeDemoBundle:Main:contactProcess }
  requirements:
    _method: POST

发表评论

电子邮件地址不会被公开。 必填项已用*标注