路由
漂亮的URL是任何严谨的Web应用程序所必须的. 这意味着像 index.php?article_id=57
这样丑陋的URL要被 /read/intro-to-symfony
所取代.
具有灵活性更加重要. 如果你需要将 /blog
更改为 /news
, 需要做些什么? 你需要搜索并更新多少链接才能做出这种改动? 如果你使用的是Symfony的路由, 更改将是很简单的.
创建路由
路由是从URL到控制器的映射, 假如你想要一个路由完全匹配 /blog
和另外更多可匹配任何像 /blog/my-post
和 /blog/all-about-symfony
URL的动态路由.
路由可以在YAML, XML和PHP. 所有格式都提供相同的功能和性能, 因此可选择你喜欢的格式. 如果你选择PHP annotations, 请在你的应用程序中运行一次此命令以添加对它们的支持:
$ composer require annotations
现在你可以配置路由:
Annotations
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
/**
* Matches /blog exactly
*
* @Route("/blog", name="blog_list")
*/
public function list()
{
// ...
}
/**
* Matches /blog/*
*
* @Route("/blog/{slug}", name="blog_show")
*/
public function show($slug)
{
// $slug will equal the dynamic part of the URL
// e.g. at /blog/yay-routing, then $slug='yay-routing'
// ...
}
}
YAML
# config/routes.yaml
blog_list:
path: /blog
controller: App\Controller\BlogController::list
blog_show:
path: /blog/{slug}
controller: App\Controller\BlogController::show
XML
PHP
// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;
$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog', array(
'_controller' => [BlogController::class, 'list']
)));
$routes->add('blog_show', new Route('/blog/{slug}', array(
'_controller' => [BlogController::class, 'show']
)));
return $routes;
感谢这两条路由:
- 如果用户访问
/blog
, 匹配第一条路由配置并且list()
将被执行; - 如果用户访问
/blog/*
, 匹配第二条路由配置并且show()
将被执行. 因为路由路径是/blog/{slug}
, 所以$slug
变量传递给该值匹配的show()
. 例如, 如果用户访问/blog/yay-routing
, 那么$slug
将等于yay-routing
.
每当路由路径中有 {placeholder}
时, 该部分就成为通配符: 它将匹配任意值. 你的控制器现在也有一个名为 $placeholder
的参数 ( 通配符和参数名称必须匹配 ).
每个路由还有一个内部名称: blog_list
和 blog_show
. 这些可以是任意内容 ( 只要每个都是唯一的 ) 并且需要无任何特别含义. 稍后你将使用它们来生成URL.
其他格式的路由每个方法上面的 @Route 称为 annotation. 如果你更愿意使用YAML, XML或PHP配置路由, 那没问题! 只需创建一个新的路由文件 ( 例如
routes.xml
) , Symfony就会自动使用它.
本地化路由(i18n)
路由可以本地化地为每个区域提供唯一的路径. Symfony提供了一种简便的方式来声明本地化路由而无重复.
Annotations
// src/Controller/CompanyController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class CompanyController extends AbstractController
{
/**
* @Route({
* "nl": "/over-ons",
* "en": "/about-us"
* }, name="about_us")
*/
public function about()
{
// ...
}
}
YAML
# config/routes.yaml
about_us:
path:
nl: /over-ons
en: /about-us
controller: App\Controller\CompanyController::about
XML
/over-ons
/about-us
PHP
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;
return function (RoutingConfigurator $routes) {
$routes->add('about_us', ['nl' => '/over-ons', 'en' => '/about-us'])
->controller('App\Controller\CompanyController::about');
};
当本地化路由匹配时, Symfony会自动识别请求期间应使用哪个区域的路由设置. 以这种方式定义路由避免了对路由重复注册的需要, 最小化了由定义不一致引起的任何错误的风险.
为所有路由添加前缀是国际化应用程序的一个常见需求. 这样可以通过为每个语言环境定义不同的路径前缀来完成 ( 如果愿意, 可以为默认语言设置一个空前缀 ):
YAML
# config/routes/annotations.yaml
controllers:
resource: '../../src/Controller/'
type: annotation
prefix:
en: '' # don't prefix URLs for English, the default locale
nl: '/nl'
添加 {通配符} 条件
想象一下, blog_list
路由将包含一个博客主题的分页列表, 其中包含 /blog/2
和 /blog/3
等第2页和第3页的URL. 如果你将路径修改为 /blog/{page}
, 你将会遇到一个问题:
- blog_list:
/blog/{page}
将匹配/blog/*
; - blog_show:
/blog/{slug}
将仍然匹配/blog/*
;
当两条路由匹配相同的URL时, 加载的第一条路由将胜利. 不幸的是, 这意味着 /blog/yay-routing
将匹配 blog_list
.
要解决此问题, 添加一个 {page}
通配符用来只匹配数字:
Annotations
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
/**
* @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"})
*/
public function list($page)
{
// ...
}
/**
* @Route("/blog/{slug}", name="blog_show")
*/
public function show($slug)
{
// ...
}
}
YAML
# config/routes.yaml
blog_list:
path: /blog/{page}
controller: App\Controller\BlogController::list
requirements:
page: '\d+'
blog_show:
# ...
XML
\d+
PHP
// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;
$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog/{page}', array(
'_controller' => [BlogController::class, 'list'],
), array(
'page' => '\d+'
)));
// ...
return $routes;
\d+
是一个匹配任意长度数字的正则表达式. 现在:
URL | Route | Parameters |
---|---|---|
/blog/2 | blog_list | $page = 2 |
/blog/yay-routing | blog_show | $slug = yay-routing |
如果你愿意, 可以在每个占位符中使用语法 {placeholder_name
. 此功能使配置更简洁, 但当需求复杂时, 它会降低路由可读性:
Annotations
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
/**
* @Route("/blog/{page<\d+>}", name="blog_list")
*/
public function list($page)
{
// ...
}
}
YAML
# config/routes.yaml
blog_list:
path: /blog/{page<\d+>}
controller: App\Controller\BlogController::list
XML
PHP
// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;
$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog/{page<\d+>}', array(
'_controller' => [BlogController::class, 'list'],
)));
// ...
return $routes;
要了解其他路由条件 ( 如HTTP方法, 主机名和动态表达式 ) 请参阅 How to Define Route Requirements
给{占位符}一个默认值
在前面的例子中, blog_list
的路径为 /blog/{page}
. 如果用户访问 /blog/1
, 则会匹配. 如果用户访问 /blog
, 将无法匹配. 只要向路由路径添加了 {占位符} , 它就必须有值.
那么当用户访问 /blog
时, 如何让 blog_list
再次匹配呢? 通过添加一个 默认
值:
Annotations
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
/**
* @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"})
*/
public function list($page = 1)
{
// ...
}
}
YAML
# config/routes.yaml
blog_list:
path: /blog/{page}
controller: App\Controller\BlogController::list
defaults:
page: 1
requirements:
page: '\d+'
blog_show:
# ...
XML
1
\d+
PHP
// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;
$routes = new RouteCollection();
$routes->add('blog_list', new Route(
'/blog/{page}',
array(
'_controller' => [BlogController::class, 'list'],
'page' => 1,
),
array(
'page' => '\d+'
)
));
// ...
return $routes;
现在, 当用户访问 /blog
时, blog_list
路由会匹配, 并且 $page
路由参数会默认取值为 1
.
与{通配符}条件一样, 使用语法 {placeholder_name?default_value}
也可以在每个占位符中内联默认值. 此功能与内联条件兼容, 因此你可以在一个占位符中内联:
Annotations
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
/**
* @Route("/blog/{page<\d+>?1}", name="blog_list")
*/
public function list($page)
{
// ...
}
}
YAML
# config/routes.yaml
blog_list:
path: /blog/{page<\d+>?1}
controller: App\Controller\BlogController::list
XML
PHP
// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;
$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog/{page<\d+>?1}', array(
'_controller' => [BlogController::class, 'list'],
)));
// ...
return $routes;
占位符变量的值若是null
变量, 则需要在通配符最后添加?
字符. ( 例如/blog/{page?}
) .
全部路由列表
随着你应用程序的健壮, 最终会有大量的路由被定义! 要查看所有内容, 请运行命令:
$ php bin/console debug:router
------------------------------ -------- -------------------------------------
Name Method Path
------------------------------ -------- -------------------------------------
app_lucky_number ANY /lucky/number/{max}
...
------------------------------ -------- -------------------------------------
高级路由示例
请查看高级示例:
Annotations
// src/Controller/ArticleController.php
// ...
class ArticleController extends AbstractController
{
/**
* @Route(
* "/articles/{_locale}/{year}/{slug}.{_format}",
* defaults={"_format": "html"},
* requirements={
* "_locale": "en|fr",
* "_format": "html|rss",
* "year": "\d+"
* }
* )
*/
public function show($_locale, $year, $slug)
{
}
}
YAML
# config/routes.yaml
article_show:
path: /articles/{_locale}/{year}/{slug}.{_format}
controller: App\Controller\ArticleController::show
defaults:
_format: html
requirements:
_locale: en|fr
_format: html|rss
year: \d+
XML
html
en|fr
html|rss
\d+
PHP
// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\ArticleController;
$routes = new RouteCollection();
$routes->add(
'article_show',
new Route('/articles/{_locale}/{year}/{slug}.{_format}', array(
'_controller' => [ArticleController::class, 'show'],
'_format' => 'html',
), array(
'_locale' => 'en|fr',
'_format' => 'html|rss',
'year' => '\d+',
))
);
return $routes;
如你所见, 只有当URL的 {_locale}
部分为 en
或 fr
且 {year}
为数字时, 此路由才会匹配. 示例还展示了如何在占位符之间使用 .
号来替换 /
. 以下URL都可匹配:
- /articles/en/2010/my-post
- /articles/fr/2010/my-post.rss
- /articles/en/2013/my-latest-post.html
_format
路由参数示例突出显示了
_format
特殊路由参数, 当使用此参数时, 匹配的值将成为Request对象的"请求格式".最后, 请求格式被用作设置返回
Content-Type
之类的事情 ( 例如: 一个JSON请求格式会转换Content-Type
为application/json
)
特殊路由参数
如你所见, 每个路由参数或默认值最终都可以作为控制器方法的参数. 此外, 还有四个特殊参数: 每个参数在应用程序中具有独特的功能:
_controller
用于确定路由匹配时执行的控制器
_format
用于设置请求格式 ( 阅读更多 )
_fragment
用于设置fragment identifier, URL的最后可选部分, 以 #
字符开头, 用于标识文档的某一部分.
_locale
用于在请求上设置区域 ( 阅读更多 )
尾部斜杠重定向URL
从历史上看, URL遵循UNIX约定, 即为路径添加尾部斜杠 ( 例如 https://example.com/foo/
) , 当删除斜杠时将作为文件引用 ( https://example.com/foo ) . 虽然为两个URL提供不同的内容是可以的, 但现在将两个URL视为相同的URL并在他们之间重定向是很常见的.
Symfony遵循这个逻辑, 在带斜杠和不带斜杠的URL之间重定向 ( 但仅限于GET和HEAD请求 ):
Route path | If the requested URL is /foo | If the requested URL is /foo/ |
---|---|---|
/foo | It matches (200 status response) | It makes a 301 redirect to /foo |
/foo/ | It makes a 301 redirect to /foo/ | It matches (200 status response) |
如果你的应用程序为每个路径 (/foo
和/foo/
) 定义了不同的路由, 则不会发生自动重定向, 并且始终匹配正确的路由.在Symfony4.1中引入了从
/foo/
到/foo
的自动301重定向. 在之前的Symfony版本中, 会响应404.
控制器命名模式
路由中的控制器格式非常简单 CONTROLLER_CLASS::METHOD
.
To refer to an action that is implemented as the __invoke() method of a controller class, you do not have to pass the method name, but can just use the fully qualified class name (e.g. AppControllerBlogController).
生成URL
路由系统也可以生成URL. 实际上, 路由是双向系统: 将URL映射到控制器以及路由返解为URL.
要生成URL, 你需要制定路由的名称 ( 例如 blog_show
) 以及该路由的路径中使用的任何通配符 ( 例如 slug = my-blog-post
) . 有了这些信息, 可轻松生成任何URL:
class MainController extends AbstractController
{
public function show($slug)
{
// ...
// /blog/my-blog-post
$url = $this->generateUrl(
'blog_show',
array('slug' => 'my-blog-post')
);
}
}
如果需要从服务生成URL, 注入 UrlGeneratorInterface 服务.
// src/Service/SomeService.php
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class SomeService
{
private $router;
public function __construct(UrlGeneratorInterface $router)
{
$this->router = $router;
}
public function someMethod()
{
$url = $this->router->generate(
'blog_show',
array('slug' => 'my-blog-post')
);
// ...
}
}
使用查询字符串生成URL
generate()
方法采用通配符数组来生成URI. 但是如果你传递额外值, 他们将作为查询字符串添加到URI中.
$this->router->generate('blog', array(
'page' => 2,
'category' => 'Symfony',
));
// /blog/2?category=Symfony
生成本地化URL
路由本地化时, Symfony默认使用当前请求区域来生成URL. 为了生成不同语言环境的URL, 你必须在parameters数组中传递 _locale
:
$this->router->generate('about_us', array(
'_locale' => 'nl',
));
// generates: /over-ons
从模板中生成URL
要在Twig中生成URL: 请参阅模板章节. 如果你需要在JavaScript中生成URL, 请参阅 How to Generate Routing URLs in JavaScript
生成绝对URL
默认情况下, 路由将生成相对URL ( 例如 /blog
) . 在控制器中, 将 UrlGeneratorInterface::ABSOLUTE_URL
传递给 generateUrl()
方法的第三个参数:
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
The host that's used when generating an absolute URL is automatically detected using the current Request object. When generating absolute URLs from outside the web context (for instance in a console command) this doesn't work. See How to Generate URLs from the Console to learn how to solve this problem.
错误排除
以下是使用路由时可能会遇到的一些常见错误:
Controller "AppControllerBlogController::show()" requires that you provide a value for the "$slug" argument.
当你的控制器方法有一个参数 ( 例如 $slug
) 时会发生这种情况:
public function show($slug)
{
// ..
}
你的路由没有 {slug}
通配符 ( 例如 /blog/show
). 在你的路由路径中增加 {slug}
: /blog/show/{slug}
或为参数设置一个默认值 ( 例如 $slug = null
)
Some mandatory parameters are missing ("slug") to generate a URL for route "blog_show".
这意味着你正在尝试生成 blog_show
路由的URL, 但你没有传递 slug 值 (这是必须的, 因为在路由路径中有一个 {slug} 通配符). 要解决此问题, 请在生成路由时传递 slug
值:
$this->generateUrl('blog_show', array('slug' => 'slug-value'));
// or, in Twig
// {{ path('blog_show', {'slug': 'slug-value'}) }}