跳到内容

路由

编辑此页

当您的应用程序收到请求时,它会调用控制器操作来生成响应。路由配置定义了对于每个传入的 URL 运行哪个操作。它还提供了其他有用的功能,例如生成 SEO 友好的 URL(例如,/read/intro-to-symfony 而不是 index.php?article_id=57)。

创建路由

路由可以在 YAML、XML、PHP 或使用属性进行配置。所有格式都提供相同的功能和性能,因此请选择您喜欢的格式。Symfony 推荐使用属性,因为它将路由和控制器放在同一位置很方便。

使用属性创建路由

PHP 属性允许在与这些路由关联的控制器代码旁边定义路由。属性在 PHP 8 及更高版本中是原生的,因此您可以立即使用它们。

在使用它们之前,您需要在项目中添加一些配置。如果您的项目使用Symfony Flex,则已为您创建此文件。否则,请手动创建以下文件

1
2
3
4
5
6
7
8
9
10
# config/routes/attributes.yaml
controllers:
    resource:
        path: ../../src/Controller/
        namespace: App\Controller
    type: attribute

kernel:
    resource: App\Kernel
    type: attribute

此配置告诉 Symfony 查找在 App\Controller 命名空间中声明并在遵循 PSR-4 标准的 src/Controller/ 目录中存储的类上定义为属性的路由。内核也可以充当控制器,这对于将 Symfony 用作微框架的小型应用程序尤其有用。

假设您想在应用程序中为 /blog URL 定义路由。为此,请创建一个控制器类,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog', name: 'blog_list')]
    public function list(): Response
    {
        // ...
    }
}

此配置定义了一个名为 blog_list 的路由,当用户请求 /blog URL 时匹配。当发生匹配时,应用程序运行 BlogController 类的 list() 方法。

注意

匹配路由时,不考虑 URL 的查询字符串。在此示例中,诸如 /blog?foo=bar/blog?foo=bar&bar=foo 之类的 URL 也将匹配 blog_list 路由。

警告

如果您在同一文件中定义多个 PHP 类,Symfony 仅加载第一个类的路由,而忽略所有其他路由。

路由名称(blog_list)现在并不重要,但稍后在生成 URL时,它将至关重要。您只需要记住,每个路由名称在应用程序中都必须是唯一的。

在 YAML、XML 或 PHP 文件中创建路由

除了在控制器类中定义路由之外,您还可以在单独的 YAML、XML 或 PHP 文件中定义它们。主要优点是它们不需要任何额外的依赖项。主要缺点是,在检查某些控制器操作的路由时,您必须使用多个文件。

以下示例展示了如何在 YAML/XML/PHP 中定义一个名为 blog_list 的路由,该路由将 /blog URL 与 BlogControllerlist() 操作关联起来

1
2
3
4
5
6
7
8
9
# config/routes.yaml
blog_list:
    path: /blog
    # the controller value has the format 'controller_class::method_name'
    controller: App\Controller\BlogController::list

    # if the action is implemented as the __invoke() method of the
    # controller class, you can skip the '::method_name' part:
    # controller: App\Controller\BlogController

注意

默认情况下,Symfony 加载以 YAML 和 PHP 格式定义的路由。如果您以 XML 格式定义路由,则需要更新 src/Kernel.php 文件

匹配 HTTP 方法

默认情况下,路由匹配任何 HTTP 谓词(GETPOSTPUT 等)。使用 methods 选项来限制每个路由应响应的谓词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Controller/BlogApiController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogApiController extends AbstractController
{
    #[Route('/api/posts/{id}', methods: ['GET', 'HEAD'])]
    public function show(int $id): Response
    {
        // ... return a JSON response with the post
    }

    #[Route('/api/posts/{id}', methods: ['PUT'])]
    public function edit(int $id): Response
    {
        // ... edit a post
    }
}

提示

HTML 表单仅支持 GETPOST 方法。如果您从 HTML 表单中使用不同的方法调用路由,请添加一个名为 _method 的隐藏字段,其中包含要使用的方法(例如,<input type="hidden" name="_method" value="PUT">)。如果您使用Symfony 表单创建表单,并且framework.http_method_override 选项为 true,则会自动为您完成此操作。

匹配表达式

如果您需要某些路由根据某些任意匹配逻辑进行匹配,请使用 condition 选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class DefaultController extends AbstractController
{
    #[Route(
        '/contact',
        name: 'contact',
        condition: "context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'",
        // expressions can also include config parameters:
        // condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'"
    )]
    public function contact(): Response
    {
        // ...
    }

    #[Route(
        '/posts/{id}',
        name: 'post_show',
        // expressions can retrieve route parameter values using the "params" variable
        condition: "params['id'] < 1000"
    )]
    public function showPost(int $id): Response
    {
        // ... return a JSON response with the post
    }
}

condition 选项的值是使用任何有效的表达式语言语法的表达式,并且可以使用 Symfony 创建的以下任何变量

context
RequestContext 的实例,它保存有关正在匹配的路由的最基本信息。
request
表示当前请求的Symfony Request 对象。
params
当前路由的匹配路由参数数组。

您还可以使用以下函数

env(string $name)
使用环境变量处理器返回变量的值
service(string $alias)

返回路由条件服务。

首先,将 #[AsRoutingConditionService] 属性或 routing.condition_service 标签添加到您想要在路由条件中使用的服务

1
2
3
4
5
6
7
8
9
10
11
use Symfony\Bundle\FrameworkBundle\Routing\Attribute\AsRoutingConditionService;
use Symfony\Component\HttpFoundation\Request;

#[AsRoutingConditionService(alias: 'route_checker')]
class RouteChecker
{
    public function check(Request $request): bool
    {
        // ...
    }
}

然后,使用 service() 函数在条件内引用该服务

1
2
3
4
// Controller (using an alias):
#[Route(condition: "service('route_checker').check(request)")]
// Or without alias:
#[Route(condition: "service('App\\\Service\\\RouteChecker').check(request)")]

在幕后,表达式被编译为原始 PHP。因此,使用 condition 键不会造成额外的开销,超出底层 PHP 执行所需的时间。

警告

生成 URL 时,考虑条件(这将在本文后面解释)。

调试路由

随着应用程序的增长,您最终将拥有很多路由。Symfony 包含一些命令来帮助您调试路由问题。首先,debug:router 命令以 Symfony 评估它们的相同顺序列出您的所有应用程序路由

1
2
3
4
5
6
7
8
9
10
11
12
$ php bin/console debug:router

----------------  -------  -------  -----  --------------------------------------------
Name              Method   Scheme   Host   Path
----------------  -------  -------  -----  --------------------------------------------
homepage          ANY      ANY      ANY    /
contact           GET      ANY      ANY    /contact
contact_process   POST     ANY      ANY    /contact
article_show      ANY      ANY      ANY    /articles/{_locale}/{year}/{title}.{_format}
blog              ANY      ANY      ANY    /blog/{page}
blog_show         ANY      ANY      ANY    /blog/{slug}
----------------  -------  -------  -----  --------------------------------------------

将某些路由的名称(或部分名称)传递给此参数以打印路由详细信息

1
2
3
4
5
6
7
8
9
10
11
$ php bin/console debug:router app_lucky_number

+-------------+---------------------------------------------------------+
| Property    | Value                                                   |
+-------------+---------------------------------------------------------+
| Route Name  | app_lucky_number                                        |
| Path        | /lucky/number/{max}                                     |
| ...         | ...                                                     |
| Options     | compiler_class: Symfony\Component\Routing\RouteCompiler |
|             | utf8: true                                              |
+-------------+---------------------------------------------------------+

提示

使用 --show-aliases 选项显示给定路由的所有可用别名。

另一个命令称为 router:match,它显示哪个路由将匹配给定的 URL。它有助于找出为什么某些 URL 没有执行您期望的控制器操作

1
2
3
$ php bin/console router:match /lucky/number/8

  [OK] Route "app_lucky_number" matches

路由参数

前面的示例定义了 URL 永远不会更改的路由(例如 /blog)。但是,通常定义路由,其中某些部分是可变的。例如,用于显示某些博客文章的 URL 可能会包含标题或 slug(例如 /blog/my-first-post/blog/all-about-symfony)。

在 Symfony 路由中,可变部分用 { } 包裹。例如,用于显示博客文章内容的路由定义为 /blog/{slug}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    // ...

    #[Route('/blog/{slug}', name: 'blog_show')]
    public function show(string $slug): Response
    {
        // $slug will equal the dynamic part of the URL
        // e.g. at /blog/yay-routing, then $slug='yay-routing'

        // ...
    }
}

变量部分的名称(本例中为 {slug})用于创建一个 PHP 变量,其中存储该路由内容并传递给控制器。如果用户访问 /blog/my-first-post URL,Symfony 将执行 BlogController 类中的 show() 方法,并将 $slug = 'my-first-post' 参数传递给 show() 方法。

路由可以定义任意数量的参数,但每个参数在每个路由上只能使用一次(例如 /blog/posts-about-{category}/page/{pageNumber})。

参数验证

假设您的应用程序具有 blog_show 路由(URL:/blog/{slug})和 blog_list 路由(URL:/blog/{page})。鉴于路由参数接受任何值,因此无法区分这两个路由。

如果用户请求 /blog/my-first-post,则两个路由都将匹配,并且 Symfony 将使用首先定义的路由。要解决此问题,请使用 requirements 选项向 {page} 参数添加一些验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])]
    public function list(int $page): Response
    {
        // ...
    }

    #[Route('/blog/{slug}', name: 'blog_show')]
    public function show($slug): Response
    {
        // ...
    }
}

requirements 选项定义了PHP 正则表达式,路由参数必须匹配这些表达式,整个路由才能匹配。在此示例中,\d+ 是一个正则表达式,它匹配任意长度的数字。现在

URL 路由 参数
/blog/2 blog_list $page = 2
/blog/my-first-post blog_show $slug = my-first-post

提示

Requirement 枚举包含常用正则表达式常量的集合,例如数字、日期和 UUID,这些常量可以用作路由参数要求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Requirement\Requirement;

class BlogController extends AbstractController
{
    #[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => Requirement::DIGITS])]
    public function list(int $page): Response
    {
        // ...
    }
}

提示

路由要求(以及路由路径)可以包含配置参数,这对于定义一次复杂的正则表达式并在多个路由中重用它们很有用。

提示

参数还支持PCRE Unicode 属性,这些属性是匹配通用字符类型的转义序列。例如,\p{Lu} 匹配任何语言中的任何大写字符,\p{Greek} 匹配任何希腊字符,等等。

注意

在路由参数中使用正则表达式时,您可以将 utf8 路由选项设置为 true,以使任何 . 字符匹配任何 UTF-8 字符,而不仅仅是单个字节。

如果您愿意,可以使用语法 {parameter_name<requirements>} 在每个参数中内联要求。此功能使配置更加简洁,但当要求复杂时,可能会降低路由的可读性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog/{page<\d+>}', name: 'blog_list')]
    public function list(int $page): Response
    {
        // ...
    }
}

可选参数

在前面的示例中,blog_list 的 URL 是 /blog/{page}。如果用户访问 /blog/1,它将匹配。但是,如果他们访问 /blog,它将匹配。一旦您向路由添加参数,它就必须有一个值。

您可以通过为 {page} 参数添加默认值,使 blog_list 再次在用户访问 /blog 时匹配。当使用属性时,默认值在控制器操作的参数中定义。在其他配置格式中,它们使用 defaults 选项定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])]
    public function list(int $page = 1): Response
    {
        // ...
    }
}

现在,当用户访问 /blog 时,blog_list 路由将匹配,并且 $page 将默认为值 1

警告

您可以有多个可选参数(例如 /blog/{slug}/{page}),但在可选参数之后的所有内容都必须是可选的。例如,/{page}/blog 是一个有效的路径,但 page 将始终是必需的(即 /blog 将不匹配此路由)。

如果您想始终在生成的 URL 中包含一些默认值(例如,为了强制生成 /blog/1 而不是上例中的 /blog),请在参数名称前添加 ! 字符:/blog/{!page}

与要求一样,默认值也可以使用语法 {parameter_name?default_value} 在每个参数中内联。此功能与内联要求兼容,因此您可以在单个参数中内联两者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog/{page<\d+>?1}', name: 'blog_list')]
    public function list(int $page): Response
    {
        // ...
    }
}

提示

要为任何参数指定 null 默认值,请在 ? 字符后不添加任何内容(例如 /blog/{page?})。如果这样做,请不要忘记更新相关控制器参数的类型,以允许传递 null 值(例如,将 int $page 替换为 ?int $page)。

优先级参数

Symfony 按照路由定义的顺序评估路由。如果路由的路径匹配许多不同的模式,则可能会阻止其他路由被匹配。在 YAML 和 XML 中,您可以上下移动配置文件中的路由定义来控制其优先级。在定义为 PHP 属性的路由中,这很难做到,因此您可以在这些路由中设置可选的 priority 参数来控制其优先级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    /**
     * This route has a greedy pattern and is defined first.
     */
    #[Route('/blog/{slug}', name: 'blog_show')]
    public function show(string $slug): Response
    {
        // ...
    }

    /**
     * This route could not be matched without defining a higher priority than 0.
     */
    #[Route('/blog/list', name: 'blog_list', priority: 2)]
    public function list(): Response
    {
        // ...
    }
}

priority 参数需要一个整数值。优先级较高的路由在优先级较低的路由之前排序。未定义时的默认值为 0

参数转换

常见的路由需求是将存储在某些参数中的值(例如,充当用户 ID 的整数)转换为另一个值(例如,表示用户的对象)。此功能称为“参数转换器”。

现在,保留之前的路由配置,但更改控制器操作的参数。不要使用 string $slug,而是添加 BlogPost $post

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Controller/BlogController.php
namespace App\Controller;

use App\Entity\BlogPost;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    // ...

    #[Route('/blog/{slug}', name: 'blog_show')]
    public function show(BlogPost $post): Response
    {
        // $post is the object whose slug matches the routing parameter

        // ...
    }
}

如果您的控制器参数包含对象的类型提示(本例中为 BlogPost),则“参数转换器”会发出数据库请求,以使用请求参数(本例中为 slug)查找对象。如果未找到对象,Symfony 会自动生成 404 响应。

查看Doctrine 参数转换文档,了解可用于自定义用于从路由参数中获取对象的数据库查询的 #[MapEntity] 属性。

Backed Enum 参数

您可以使用 PHP backed enumerations(backed 枚举) 作为路由参数,因为 Symfony 会自动将它们转换为标量值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Controller/OrderController.php
namespace App\Controller;

use App\Enum\OrderStatusEnum;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class OrderController extends AbstractController
{
    #[Route('/orders/list/{status}', name: 'list_orders_by_status')]
    public function list(OrderStatusEnum $status = OrderStatusEnum::Paid): Response
    {
        // ...
    }
}

特殊参数

除了您自己的参数之外,路由还可以包含由 Symfony 创建的以下任何特殊参数

_controller
此参数用于确定在路由匹配时执行哪个控制器和动作。
_format
匹配的值用于设置 Request 对象的“请求格式”。这用于设置响应的 Content-Type,例如,json 格式转换为 application/jsonContent-Type
_fragment
用于设置片段标识符,它是 URL 的可选最后部分,以 # 字符开头,用于标识文档的一部分。
_locale
用于在请求上设置 locale(区域设置)

您可以将这些属性(_fragment 除外)包含在单个路由和路由导入中。Symfony 定义了一些具有相同名称的特殊属性(下划线除外),以便您可以更轻松地定义它们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Controller/ArticleController.php
namespace App\Controller;

// ...
class ArticleController extends AbstractController
{
    #[Route(
        path: '/articles/{_locale}/search.{_format}',
        locale: 'en',
        format: 'html',
        requirements: [
            '_locale' => 'en|fr',
            '_format' => 'html|xml',
        ],
    )]
    public function search(): Response
    {
    }
}

额外参数

在路由的 defaults 选项中,您可以选择定义未包含在路由配置中的参数。这对于将额外的参数传递给路由的控制器非常有用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog/{page}', name: 'blog_index', defaults: ['page' => 1, 'title' => 'Hello world!'])]
    public function index(int $page, string $title): Response
    {
        // ...
    }
}

路由参数中的斜线字符

路由参数可以包含除 / 斜杠字符之外的任何值,因为该字符用于分隔 URL 的不同部分。例如,如果 /share/{token} 路由中的 token 值包含 / 字符,则此路由将不匹配。

一个可能的解决方案是更改参数要求以使其更宽松

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class DefaultController extends AbstractController
{
    #[Route('/share/{token}', name: 'share', requirements: ['token' => '.+'])]
    public function share($token): Response
    {
        // ...
    }
}

注意

如果路由定义了多个参数,并且您将此宽松的正则表达式应用于所有参数,则可能会得到意外的结果。例如,如果路由定义为 /share/{path}/{token},并且 pathtoken 都接受 /,则 token 将仅获得最后一部分,其余部分由 path 匹配。

注意

如果路由包含特殊的 {_format} 参数,则不应为允许斜杠的参数使用 .+ 要求。例如,如果模式为 /share/{token}.{_format} 并且 {token} 允许任何字符,则 /share/foo/bar.json URL 将把 foo/bar.json 视为 token,并且格式将为空。这可以通过将 .+ 要求替换为 [^.]+ 来解决,以允许除点以外的任何字符。

路由别名

路由别名允许您为同一路由设置多个名称

1
2
3
# config/routes.yaml
new_route_name:
    alias: original_route_name

在此示例中,original_route_namenew_route_name 路由都可以在应用程序中使用,并将产生相同的结果。

弃用路由别名

如果某些路由别名不再使用(因为它已过时或您决定不再维护它),您可以弃用其定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
new_route_name:
    alias: original_route_name

    # this outputs the following generic deprecation message:
    # Since acme/package 1.2: The "new_route_name" route alias is deprecated. You should stop using it, as it will be removed in the future.
    deprecated:
        package: 'acme/package'
        version: '1.2'

    # you can also define a custom deprecation message (%alias_id% placeholder is available)
    deprecated:
        package: 'acme/package'
        version: '1.2'
        message: 'The "%alias_id%" route alias is deprecated. Do not use it anymore.'

在此示例中,每次使用 new_route_name 别名时,都会触发弃用警告,建议您停止使用该别名。

该消息实际上是一个消息模板,它将 %alias_id% 占位符的出现替换为路由别名名称。您必须在模板中至少出现一次 %alias_id% 占位符。

路由组和前缀

一组路由通常会共享一些选项(例如,所有与博客相关的路由都以 /blog 开头)。这就是 Symfony 包含共享路由配置功能的原因。

当将路由定义为属性时,请将公共配置放在控制器类的 #[Route] 属性中。在其他路由格式中,导入路由时使用选项定义公共配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/blog', requirements: ['_locale' => 'en|es|fr'], name: 'blog_')]
class BlogController extends AbstractController
{
    #[Route('/{_locale}', name: 'index')]
    public function index(): Response
    {
        // ...
    }

    #[Route('/{_locale}/posts/{slug}', name: 'show')]
    public function show(string $slug): Response
    {
        // ...
    }
}

警告

exclude 选项仅在 resource 值是 glob 字符串时才有效。如果您使用常规字符串(例如 '../src/Controller'),则 exclude 值将被忽略。

在此示例中,index() 动作的路由将被称为 blog_index,其 URL 将为 /blog/{_locale}show() 动作的路由将被称为 blog_show,其 URL 将为 /blog/{_locale}/posts/{slug}。这两个路由还将验证 _locale 参数是否与类属性中定义的正则表达式匹配。

注意

如果任何前缀路由定义了空路径,Symfony 会向其添加尾部斜杠。在前面的示例中,以 /blog 为前缀的空路径将导致 /blog/ URL。如果您想避免此行为,请将 trailing_slash_on_root 选项设置为 false(使用 PHP 属性时,此选项不可用)

1
2
3
4
5
6
7
# config/routes/attributes.yaml
controllers:
    resource: '../../src/Controller/'
    type:     attribute
    prefix:   '/blog'
    trailing_slash_on_root: false
    # ...

另请参阅

Symfony 可以从不同的来源导入路由,您甚至可以创建自己的路由加载器。

获取路由名称和参数

Symfony 创建的 Request 对象将所有路由配置(例如名称和参数)存储在“请求属性”中。您可以通过 Request 对象在控制器中获取此信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog', name: 'blog_list')]
    public function list(Request $request): Response
    {
        $routeName = $request->attributes->get('_route');
        $routeParameters = $request->attributes->get('_route_params');

        // use this to get all the available attributes (not only routing ones):
        $allAttributes = $request->attributes->all();

        // ...
    }
}

在服务中,您可以通过注入 RequestStack 服务来获取此信息。在模板中,使用 Twig 全局 app 变量来获取当前路由名称 (app.current_route) 及其参数 (app.current_route_parameters)。

特殊路由

Symfony 定义了一些特殊的控制器,用于从路由配置中渲染模板并重定向到其他路由,因此您无需创建控制器动作。

直接从路由渲染模板

阅读关于 Symfony 模板主文章中关于从路由渲染模板的部分。

直接从路由重定向到 URL 和路由

使用 RedirectController 重定向到其他路由和 URL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# config/routes.yaml
doc_shortcut:
    path: /doc
    controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController
    defaults:
        route: 'doc_page'
        # optionally you can define some arguments passed to the route
        page: 'index'
        version: 'current'
        # redirections are temporary by default (code 302) but you can make them permanent (code 301)
        permanent: true
        # add this to keep the original query string parameters when redirecting
        keepQueryParams: true
        # add this to keep the HTTP method when redirecting. The redirect status changes
        # * for temporary redirects, it uses the 307 status code instead of 302
        # * for permanent redirects, it uses the 308 status code instead of 301
        keepRequestMethod: true
        # add this to remove all original route attributes when redirecting
        ignoreAttributes: true
        # or specify which attributes to ignore:
        # ignoreAttributes: ['offset', 'limit']

legacy_doc:
    path: /legacy/doc
    controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController
    defaults:
        # this value can be an absolute path or an absolute URL
        path: 'https://legacy.example.com/doc'
        permanent: true

提示

Symfony 还提供了一些实用程序,用于在控制器内部重定向

重定向带有尾部斜杠的 URL

从历史上看,URL 遵循 UNIX 约定,为目录添加尾部斜杠(例如 https://example.com/foo/),并删除它们以引用文件(https://example.com/foo)。尽管为两个 URL 提供不同的内容是可以的,但现在通常将两个 URL 视为相同的 URL 并在它们之间重定向。

Symfony 遵循此逻辑在带有和不带有尾部斜杠的 URL 之间重定向(但仅适用于 GETHEAD 请求)

路由 URL 如果请求的 URL 是 /foo 如果请求的 URL 是 /foo/
/foo 它匹配(200 状态响应) 它进行 301 重定向到 /foo
/foo/ 它进行 301 重定向到 /foo/ 它匹配(200 状态响应)

子域名路由

路由可以配置 host 选项,以要求传入请求的 HTTP host 与某些特定值匹配。在以下示例中,两个路由都匹配相同的路径 (/),但其中一个仅响应特定的主机名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Controller/MainController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class MainController extends AbstractController
{
    #[Route('/', name: 'mobile_homepage', host: 'm.example.com')]
    public function mobileHomepage(): Response
    {
        // ...
    }

    #[Route('/', name: 'homepage')]
    public function homepage(): Response
    {
        // ...
    }
}

host 选项的值可以包含参数(这在多租户应用程序中很有用),并且这些参数也可以使用 requirements 进行验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/Controller/MainController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class MainController extends AbstractController
{
    #[Route(
        '/',
        name: 'mobile_homepage',
        host: '{subdomain}.example.com',
        defaults: ['subdomain' => 'm'],
        requirements: ['subdomain' => 'm|mobile'],
    )]
    public function mobileHomepage(): Response
    {
        // ...
    }

    #[Route('/', name: 'homepage')]
    public function homepage(): Response
    {
        // ...
    }
}

在上面的示例中,subdomain 参数定义了一个默认值,因为否则每次使用这些路由生成 URL 时都需要包含一个子域值。

提示

导入路由时,您还可以设置 host 选项,以使所有路由都要求该主机名。

注意

使用子域路由时,您必须在功能测试中设置 Host HTTP 标头,否则路由将不匹配

1
2
3
4
5
6
7
8
9
$crawler = $client->request(
    'GET',
    '/',
    [],
    [],
    ['HTTP_HOST' => 'm.example.com']
    // or get the value from some configuration parameter:
    // ['HTTP_HOST' => 'm.'.$client->getContainer()->getParameter('domain')]
);

提示

您还可以在 host 选项中使用内联默认值和要求格式:{subdomain<m|mobile>?m}.example.com

本地化路由 (i18n)

如果您的应用程序被翻译成多种语言,则每个路由可以为每个翻译区域设置定义不同的 URL。这避免了重复路由的需求,这也减少了潜在的错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Controller/CompanyController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class CompanyController extends AbstractController
{
    #[Route(path: [
        'en' => '/about-us',
        'nl' => '/over-ons'
    ], name: 'about_us')]
    public function about(): Response
    {
        // ...
    }
}

注意

当为本地化路由使用 PHP 属性时,您必须使用 path 命名参数来指定路径数组。

当匹配本地化路由时,Symfony 会在整个请求期间自动使用相同的区域设置。

提示

当应用程序使用完整的“语言 + 地区”区域设置(例如 fr_FRfr_BE)时,如果所有相关区域设置中的 URL 相同,则路由可以仅使用语言部分(例如 fr)以避免重复相同的 URL。

国际化应用程序的常见要求是以区域设置作为所有路由的前缀。这可以通过为每个区域设置定义不同的前缀来完成(如果您愿意,可以为默认区域设置设置空前缀)

1
2
3
4
5
6
7
# config/routes/attributes.yaml
controllers:
    resource: '../../src/Controller/'
    type: attribute
    prefix:
        en: '' # don't prefix URLs for English, the default locale
        nl: '/nl'

注意

如果正在导入的路由在其自身定义中包含特殊的_locale 参数,则 Symfony 将仅为该区域设置导入它,而不会为其他配置的区域设置前缀导入它。

例如,如果路由在其定义中包含 locale: 'en' 并且正在使用 en (prefix: empty) 和 nl (prefix: /nl) 区域设置导入,则该路由将仅在 en 区域设置中可用,而不在 nl 中可用。

另一个常见的需求是根据区域设置在不同的域上托管网站。这可以通过为每个区域设置定义不同的主机来完成。

1
2
3
4
5
6
7
# config/routes/attributes.yaml
controllers:
    resource: '../../src/Controller/'
    type: attribute
    host:
        en: 'www.example.com'
        nl: 'www.example.nl'

无状态路由

有时,当 HTTP 响应应该被缓存时,确保可以发生这种情况很重要。但是,每当在请求期间启动会话时,Symfony 都会将响应转换为私有的不可缓存的响应。

有关详细信息,请参阅HTTP 缓存

路由可以配置 stateless 布尔选项,以声明在匹配请求时不应使用会话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Controller/MainController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;

class MainController extends AbstractController
{
    #[Route('/', name: 'homepage', stateless: true)]
    public function homepage(): Response
    {
        // ...
    }
}

现在,如果使用了会话,应用程序将根据您的 kernel.debug 参数报告它

它将帮助您理解并希望修复应用程序中意外的行为。

生成 URL

路由系统是双向的

  1. 它们将 URL 与控制器关联(如前几节所述);
  2. 它们为给定的路由生成 URL。

从路由生成 URL 允许您不必在 HTML 模板中手动编写 <a href="..."> 值。此外,如果某些路由的 URL 发生更改,您只需更新路由配置,所有链接都将更新。

要生成 URL,您需要指定路由的名称(例如 blog_show)和路由定义的参数值(例如 slug = my-blog-post)。

因此,每个路由都有一个内部名称,该名称在应用程序中必须是唯一的。如果您没有使用 name 选项显式设置路由名称,则 Symfony 会根据控制器和动作生成一个自动名称。

如果目标类具有添加路由的 __invoke() 方法并且如果目标类恰好添加了一个路由,则 Symfony 会根据 FQCN 声明路由别名。Symfony 还会为每个仅定义一个路由的方法自动添加别名。考虑以下类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Controller/MainController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;

final class MainController extends AbstractController
{
    #[Route('/', name: 'homepage')]
    public function homepage(): Response
    {
        // ...
    }
}

Symfony 将添加一个名为 App\Controller\MainController::homepage 的路由别名。

在控制器中生成 URL

如果您的控制器从AbstractController 扩展,请使用 generateUrl() 助手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class BlogController extends AbstractController
{
    #[Route('/blog', name: 'blog_list')]
    public function list(): Response
    {
        // generate a URL with no route arguments
        $signUpPage = $this->generateUrl('sign_up');

        // generate a URL with route arguments
        $userProfilePage = $this->generateUrl('user_profile', [
            'username' => $user->getUserIdentifier(),
        ]);

        // generated URLs are "absolute paths" by default. Pass a third optional
        // argument to generate different URLs (e.g. an "absolute URL")
        $signUpPage = $this->generateUrl('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);

        // when a route is localized, Symfony uses by default the current request locale
        // pass a different '_locale' value if you want to set the locale explicitly
        $signUpPageInDutch = $this->generateUrl('sign_up', ['_locale' => 'nl']);

        // ...
    }
}

注意

如果您将一些未包含在路由定义中的参数传递给 generateUrl() 方法,它们将作为查询字符串包含在生成的 URL 中

1
2
3
$this->generateUrl('blog', ['page' => 2, 'category' => 'Symfony']);
// the 'blog' route only defines the 'page' parameter; the generated URL is:
// /blog/2?category=Symfony

警告

当对象用作占位符时,它们会被转换为字符串,但当用作额外参数时,它们不会被转换。因此,如果您将对象(例如 Uuid)作为额外参数的值传递,则需要显式将其转换为字符串

1
$this->generateUrl('blog', ['uuid' => (string) $entity->getUuid()]);

如果您的控制器未从 AbstractController 扩展,您需要在控制器中获取服务,并按照下一节的说明进行操作。

在服务中生成 URL

router Symfony 服务注入到您自己的服务中,并使用其 generate() 方法。当使用服务自动装配时,您只需要在服务构造函数中添加一个参数,并使用 UrlGeneratorInterface 类进行类型提示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// src/Service/SomeService.php
namespace App\Service;

use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class SomeService
{
    public function __construct(
        private UrlGeneratorInterface $urlGenerator,
    ) {
    }

    public function someMethod(): void
    {
        // ...

        // generate a URL with no route arguments
        $signUpPage = $this->urlGenerator->generate('sign_up');

        // generate a URL with route arguments
        $userProfilePage = $this->urlGenerator->generate('user_profile', [
            'username' => $user->getUserIdentifier(),
        ]);

        // generated URLs are "absolute paths" by default. Pass a third optional
        // argument to generate different URLs (e.g. an "absolute URL")
        $signUpPage = $this->urlGenerator->generate('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);

        // when a route is localized, Symfony uses by default the current request locale
        // pass a different '_locale' value if you want to set the locale explicitly
        $signUpPageInDutch = $this->urlGenerator->generate('sign_up', ['_locale' => 'nl']);
    }
}

在模板中生成 URL

阅读关于 Symfony 模板主文章中关于创建页面之间链接的部分。

在 JavaScript 中生成 URL

如果您的 JavaScript 代码包含在 Twig 模板中,则可以使用 path()url() Twig 函数生成 URL 并将其存储在 JavaScript 变量中。需要 escape() 过滤器来转义任何非 JavaScript 安全的值

1
2
3
<script>
    const route = "{{ path('blog_show', {slug: 'my-blog-post'})|escape('js') }}";
</script>

如果您需要动态生成 URL 或者您正在使用纯 JavaScript 代码,则此解决方案不起作用。在这些情况下,请考虑使用 FOSJsRoutingBundle

在命令中生成 URL

在命令中生成 URL 的工作方式与在服务中生成 URL相同。唯一的区别是命令不在 HTTP 上下文中执行。因此,如果您生成绝对 URL,您将获得 http://127.0.0.1/ 作为主机名,而不是您的真实主机名。

解决方案是配置 default_uri 选项,以定义命令生成 URL 时使用的“请求上下文”

1
2
3
4
5
# config/packages/routing.yaml
framework:
    router:
        # ...
        default_uri: 'https://example.org/my/path/'

现在,在命令中生成 URL 时,您将获得预期的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/Command/SomeCommand.php
namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
// ...

class SomeCommand extends Command
{
    public function __construct(private UrlGeneratorInterface $urlGenerator)
    {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // generate a URL with no route arguments
        $signUpPage = $this->urlGenerator->generate('sign_up');

        // generate a URL with route arguments
        $userProfilePage = $this->urlGenerator->generate('user_profile', [
            'username' => $user->getUserIdentifier(),
        ]);

        // by default, generated URLs are "absolute paths". Pass a third optional
        // argument to generate different URIs (e.g. an "absolute URL")
        $signUpPage = $this->urlGenerator->generate('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);

        // when a route is localized, Symfony uses by default the current request locale
        // pass a different '_locale' value if you want to set the locale explicitly
        $signUpPageInDutch = $this->urlGenerator->generate('sign_up', ['_locale' => 'nl']);

        // ...
    }
}

注意

默认情况下,为 Web 资源生成的 URL 使用相同的 default_uri 值,但您可以使用 asset.request_context.base_pathasset.request_context.secure 容器参数来更改它。

检查路由是否存在

在高度动态的应用程序中,可能需要在使用路由生成 URL 之前检查路由是否存在。在这些情况下,请勿使用 getRouteCollection() 方法,因为这会重新生成路由缓存并减慢应用程序的速度。

相反,尝试生成 URL 并捕获当路由不存在时抛出的 RouteNotFoundException

1
2
3
4
5
6
7
8
9
use Symfony\Component\Routing\Exception\RouteNotFoundException;

// ...

try {
    $url = $this->router->generate($routeName, $routeParameters);
} catch (RouteNotFoundException $e) {
    // the route is not defined...
}

强制在生成的 URL 上使用 HTTPS

注意

如果您的服务器在终止 SSL 的代理后面运行,请确保配置 Symfony 以在代理后面工作

scheme 的配置仅用于非 HTTP 请求。schemes 选项与不正确的代理配置一起使用将导致重定向循环。

默认情况下,生成的 URL 使用与当前请求相同的 HTTP scheme。在没有 HTTP 请求的控制台命令中,URL 默认使用 http。您可以按命令(通过路由器的 getContext() 方法)或使用以下配置参数全局更改此设置

1
2
3
4
# config/services.yaml
parameters:
    router.request_context.scheme: 'https'
    asset.request_context.secure: true

在控制台命令之外,使用 schemes 选项显式定义每个路由的 scheme

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Controller/SecurityController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class SecurityController extends AbstractController
{
    #[Route('/login', name: 'login', schemes: ['https'])]
    public function login(): Response
    {
        // ...
    }
}

login 路由生成的 URL 将始终使用 HTTPS。这意味着当使用 path() Twig 函数生成 URL 时,如果原始请求的 HTTP scheme 与路由使用的 scheme 不同,您可能会获得绝对 URL 而不是相对 URL

1
2
3
4
5
6
{# if the current scheme is HTTPS, generates a relative URL: /login #}
{{ path('login') }}

{# if the current scheme is HTTP, generates an absolute URL to change
   the scheme: https://example.com/login #}
{{ path('login') }}

scheme 要求也适用于传入请求。如果您尝试使用 HTTP 访问 /login URL,您将自动重定向到相同的 URL,但使用 HTTPS scheme。

如果您想强制一组路由使用 HTTPS,您可以在导入它们时定义默认 scheme。以下示例强制将 HTTPS 应用于所有定义为属性的路由

1
2
3
4
5
# config/routes/attributes.yaml
controllers:
    resource: '../../src/Controller/'
    type: attribute
    schemes: [https]

签名 URI

签名 URI 是包含哈希值的 URI,该哈希值取决于 URI 的内容。这样,您稍后可以通过重新计算其哈希值并将其与 URI 中包含的哈希值进行比较来检查签名 URI 的完整性。

Symfony 通过 UriSigner 服务提供了一个实用程序来签名 URI,您可以将其注入到您的服务或控制器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// src/Service/SomeService.php
namespace App\Service;

use Symfony\Component\HttpFoundation\UriSigner;

class SomeService
{
    public function __construct(
        private UriSigner $uriSigner,
    ) {
    }

    public function someMethod(): void
    {
        // ...

        // generate a URL yourself or get it somehow...
        $url = 'https://example.com/foo/bar?sort=desc';

        // sign the URL (it adds a query parameter called '_hash')
        $signedUrl = $this->uriSigner->sign($url);
        // $url = 'https://example.com/foo/bar?sort=desc&_hash=e4a21b9'

        // check the URL signature
        $uriSignatureIsValid = $this->uriSigner->check($signedUrl);
        // $uriSignatureIsValid = true

        // if you have access to the current Request object, you can use this
        // other method to pass the entire Request object instead of the URI:
        $uriSignatureIsValid = $this->uriSigner->checkRequest($request);
    }
}

出于安全原因,通常使签名 URI 在一段时间后过期(例如,在将它们用于重置用户凭据时)。默认情况下,签名 URI 不会过期,但您可以使用 sign()$expiration 参数定义过期日期/时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// src/Service/SomeService.php
namespace App\Service;

use Symfony\Component\HttpFoundation\UriSigner;

class SomeService
{
    public function __construct(
        private UriSigner $uriSigner,
    ) {
    }

    public function someMethod(): void
    {
        // ...

        // generate a URL yourself or get it somehow...
        $url = 'https://example.com/foo/bar?sort=desc';

        // sign the URL with an explicit expiration date
        $signedUrl = $this->uriSigner->sign($url, new \DateTimeImmutable('2050-01-01'));
        // $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=2524608000&_hash=e4a21b9'

        // if you pass a \DateInterval, it will be added from now to get the expiration date
        $signedUrl = $this->uriSigner->sign($url, new \DateInterval('PT10S'));  // valid for 10 seconds from now
        // $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=1712414278&_hash=e4a21b9'

        // you can also use a timestamp in seconds
        $signedUrl = $this->uriSigner->sign($url, 4070908800); // timestamp for the date 2099-01-01
        // $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=4070908800&_hash=e4a21b9'
    }
}

注意

过期日期/时间作为时间戳通过 _expiration 查询参数包含在签名 URI 中。

7.1

为签名 URI 添加过期日期的功能在 Symfony 7.1 中引入。

注意

生成的 URI 哈希可能包含 /+ 字符,这可能会导致某些客户端出现问题。如果您遇到此问题,请使用以下内容替换它们:strtr($hash, ['/' => '_', '+' => '-'])

故障排除

以下是在使用路由时可能会看到的一些常见错误

1
2
Controller "App\\Controller\\BlogController::show()" requires that you
provide a value for the "$slug" argument.

当您的控制器方法具有参数(例如 $slug)时,会发生这种情况

1
2
3
4
public function show(string $slug): Response
{
    // ...
}

但您的路由路径没有 {slug} 参数(例如,它是 /blog/show)。将 {slug} 添加到您的路由路径:/blog/show/{slug},或为参数指定默认值(即 $slug = null)。

1
2
Some mandatory parameters are missing ("slug") to generate a URL for route
"blog_show".

这意味着您正在尝试为 blog_show 路由生成 URL,但您没有传递 slug 值(这是必需的,因为它在路由路径中具有 {slug} 参数)。要解决此问题,请在生成路由时传递 slug

1
$this->generateUrl('blog_show', ['slug' => 'slug-value']);

或者,在 Twig 中

1
{{ path('blog_show', {slug: 'slug-value'}) }}
这项工作,包括代码示例,根据 Creative Commons BY-SA 3.0 许可获得许可。
TOC
    版本