爱自由 2019-06-29
漂亮的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
<!-- config/routes.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_list" controller="App\Controller\BlogController::list" path="/blog" > <!-- settings --> </route> <route id="blog_show" controller="App\Controller\BlogController::show" path="/blog/{slug}"> <!-- settings --> </route> </routes>
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就会自动使用它.
路由可以本地化地为每个区域提供唯一的路径. 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
<!-- config/routes.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="about_us" controller="App\Controller\CompanyController::about"> <path locale="nl">/over-ons</path> <path locale="en">/about-us</path> </route> </routes>
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/{page}
将匹配 /blog/*
;/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
<!-- config/routes.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_list" path="/blog/{page}" controller="App\Controller\BlogController::list"> <requirement key="page">\d+</requirement> </route> <!-- ... --> </routes>
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<requirements>}
. 此功能使配置更简洁, 但当需求复杂时, 它会降低路由可读性:
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
<!-- config/routes.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_list" path="/blog/{page<\d+>}" controller="App\Controller\BlogController::list" /> <!-- ... --> </routes>
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
<!-- config/routes.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_list" path="/blog/{page}" controller="App\Controller\BlogController::list"> <default key="page">1</default> <requirement key="page">\d+</requirement> </route> <!-- ... --> </routes>
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
<!-- config/routes.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_list" path="/blog/{page <\d+>?1}" controller="App\Controller\BlogController::list" /> <!-- ... --> </routes>
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
<!-- config/routes.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="article_show" path="/articles/{_locale}/{year}/{slug}.{_format}" controller="App\Controller\ArticleController::show"> <default key="_format">html</default> <requirement key="_locale">en|fr</requirement> <requirement key="_format">html|rss</requirement> <requirement key="year">\d+</requirement> </route> </routes>
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都可匹配:
_format
路由参数示例突出显示了 _format
特殊路由参数, 当使用此参数时, 匹配的值将成为Request对象的"请求格式".
最后, 请求格式被用作设置返回 Content-Type
之类的事情 ( 例如: 一个JSON请求格式会转换 Content-Type
为 application/json
)
如你所见, 每个路由参数或默认值最终都可以作为控制器方法的参数. 此外, 还有四个特殊参数: 每个参数在应用程序中具有独特的功能:
_controller
用于确定路由匹配时执行的控制器
_format
用于设置请求格式 ( 阅读更多 )
_fragment
用于设置fragment identifier, URL的最后可选部分, 以 #
字符开头, 用于标识文档的某一部分.
_locale
用于在请求上设置区域 ( 阅读更多 )
从历史上看, 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
.
路由系统也可以生成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') ); // ... } }
generate()
方法采用通配符数组来生成URI. 但是如果你传递额外值, 他们将作为查询字符串添加到URI中.
$this->router->generate('blog', array( 'page' => 2, 'category' => 'Symfony', )); // /blog/2?category=Symfony
路由本地化时, Symfony默认使用当前请求区域来生成URL. 为了生成不同语言环境的URL, 你必须在parameters数组中传递 _locale
:
$this->router->generate('about_us', array( '_locale' => 'nl', )); // generates: /over-ons
要在Twig中生成URL: 请参阅模板章节. 如果你需要在JavaScript中生成URL, 请参阅 How to Generate Routing URLs in JavaScript
默认情况下, 路由将生成相对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-postThe 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
)
这意味着你正在尝试生成 blog_show
路由的URL, 但你没有传递 slug 值 (这是必须的, 因为在路由路径中有一个 {slug} 通配符). 要解决此问题, 请在生成路由时传递 slug
值:
$this->generateUrl('blog_show', array('slug' => 'slug-value')); // or, in Twig // {{ path('blog_show', {'slug': 'slug-value'}) }}