入门
安装和设置 Symfony 框架
技术要求
在创建您的第一个 Symfony 应用程序之前,您必须:
- 安装 PHP 8.2 或更高版本以及这些 PHP 扩展(在大多数 PHP 8 安装中默认安装并启用): Ctype 、 iconv 、 PCRE 、 Session 、 SimpleXML和Tokenizer ;
- 安装 Composer ,用于安装 PHP 包。
另外,安装 Symfony CLI 。这是可选的,但它为您提供了一个名为symfony有用二进制文件,它提供了在本地开发和运行 Symfony 应用程序所需的所有工具。
symfony二进制文件还提供了一个工具来检查您的计算机是否满足所有要求。打开控制台终端并运行以下命令:
symfony check:requirements
创建 Symfony 应用程序
打开控制台终端并运行以下任意命令来创建新的 Symfony 应用程序:
symfony new my_project_directory --version="7.1.*" --webapp
symfony new my_project_directory --version="7.1.*"
这两个命令之间的唯一区别是默认安装的软件包数量。 --webapp选项会安装额外的软件包,为您提供构建 Web 应用程序所需的一切。
如果您不使用 Symfony 二进制文件,请运行以下命令以使用 Composer 创建新的 Symfony 应用程序:
composer create-project symfony/skeleton:"7.1.*" my_project_directory
cd my_project_directory
composer require webapp
composer create-project symfony/skeleton:"7.1.*" my_project_directory
无论您运行哪个命令来创建 Symfony 应用程序。所有这些都将创建一个新的my_project_directory/目录,将一些依赖项下载到其中,甚至生成入门所需的基本目录和文件。换句话说,您的新应用程序已准备就绪!
设置现有的 Symfony 项目
除了创建新的 Symfony 项目之外,您还将处理其他开发人员已经创建的项目。在这种情况下,您只需要获取项目代码并使用 Composer 安装依赖项即可。假设您的团队使用 Git,请使用以下命令设置您的项目:
cd projects/
git clone ...
cd my-project/
composer install
您可能还需要自定义.env 文件并执行一些其他特定于项目的任务(例如创建数据库)。当第一次使用现有的 Symfony 应用程序时,运行此命令可能会很有用,该命令显示有关项目的信息:
php bin/console about
运行 Symfony 应用程序
在生产中,您应该安装 Nginx 或 Apache 等 Web 服务器并将其配置为运行 Symfony 。如果您不使用 Symfony 本地 Web 服务器进行开发,也可以使用此方法。
然而,对于本地开发,运行 Symfony 最方便的方法是使用symfony二进制文件提供的本地 Web 服务器。该本地服务器除其他外还提供对 HTTP/2、并发请求、TLS/SSL 和自动生成安全证书的支持。
打开控制台终端,进入新项目目录并启动本地 Web 服务器,如下所示:
cd my-project/
symfony server:start
打开浏览器并导航到http://localhost:8000/ 。如果一切正常,您将看到一个欢迎页面。稍后,当您完成工作时,通过从终端按Ctrl+C来停止服务器。
Symfony Docker 集成
如果您想将 Docker 与 Symfony 结合使用,请参阅将 Docker 与 Symfony 结合使用。
安装软件包
开发 Symfony 应用程序时的常见做法是安装提供即用型功能的包(Symfony 称之为捆绑包)。软件包在使用之前通常需要进行一些设置(编辑一些文件以启用捆绑包,创建一些文件以添加一些初始配置等)
大多数情况下,此设置可以自动化,这就是 Symfony 包含Symfony Flex 的原因,它是一个用于简化 Symfony 应用程序中包的安装/删除的工具。从技术上讲,Symfony Flex 是一个 Composer 插件,在创建新的 Symfony 应用程序时默认安装,并自动执行 Symfony 应用程序的最常见任务。
Symfony Flex 修改了require 、 update和remove Composer 命令的行为以提供高级功能。考虑以下示例:
cd my-project/
composer require logger
如果您在不使用 Flex 的 Symfony 应用程序中运行该命令,您将看到 Composer 错误,说明logger不是有效的包名称。但是,如果应用程序安装了 Symfony Flex,则该命令将安装并启用使用官方 Symfony 记录器所需的所有包。
这是可能的,因为许多 Symfony 包/捆绑包定义了“配方” ,这是一组用于将包安装到 Symfony 应用程序中并启用包的自动指令。 Flex 会跟踪它安装在symfony.lock文件中的配方,该文件必须提交到您的代码存储库。
Symfony Flex 配方由社区贡献,它们存储在两个公共存储库中:
- 主配方存储库是高质量和维护包的精选配方列表。默认情况下,Symfony Flex 仅在此存储库中查找
- Contrib 配方存储库,包含社区创建的所有配方。它们都保证可以工作,但它们的相关包可能无法维护。 Symfony Flex 在安装任何这些配方之前都会征求您的许可。
阅读Symfony Recipes 文档,了解有关如何为您自己的包创建菜谱的所有内容。
Packs
有时,单个功能需要安装多个包和捆绑包。 Symfony 没有单独安装它们,而是提供了packs ,它们是包含多个依赖项的 Composer 元包。
例如,要在应用程序中添加调试功能,您可以运行composer require --dev debug命令。这将安装symfony/debug-pack ,进而安装多个软件包,如symfony/debug-bundle 、 symfony/monolog-bundle 、 symfony/var-dumper等。
您不会在您的composer.json中看到symfony/debug-pack依赖项,因为Flex会自动解压该包。这意味着它只添加真正的包作为依赖项(例如,您将在require-dev中看到一个新的symfony/var-dumper )。
检查安全漏洞
安装Symfony CLI时创建的symfony二进制文件提供了一个命令来检查项目的依赖项是否包含任何已知的安全漏洞:
symfony check:security
良好的安全实践是定期执行此命令,以便能够尽快更新或替换受损的依赖项。安全检查是通过获取公共PHP安全建议数据库在本地完成的,因此您的composer.lock文件不会在网络上发送。
如果您的任何依赖项受到已知安全漏洞的影响,则check:security命令将以非零退出代码终止。通过这种方式,您可以将其添加到项目构建过程和持续集成工作流程中,以使它们在存在漏洞时失败。
Symfony LTS 版本
根据Symfony的发布流程,“长期支持”(或简称LTS)版本每两年发布一次。查看Symfony 版本以了解最新的 LTS 版本。
默认情况下,创建新 Symfony 应用程序的命令使用最新的稳定版本。如果您想使用 LTS 版本,请添加--version选项:
symfony new my_project_directory --version=lts
symfony new my_project_directory --version=next
symfony new my_project_directory --version="6.4.*"
lts和next快捷键仅在使用 Symfony 创建新项目时可用。如果你使用Composer,你需要告诉确切的版本:
composer create-project symfony/skeleton:"6.4.*" my_project_directory
Symfony 演示应用程序
Symfony 演示应用程序是一个功能齐全的应用程序,展示了开发 Symfony 应用程序的推荐方法。对于 Symfony 新手来说,它是一个很棒的学习工具,它的代码包含大量注释和有用的注释。
运行以下命令来创建一个基于 Symfony Demo 应用程序的新项目:
symfony new my_project_directory --demo
在 Symfony 中创建您的首页
创建新页面(无论是 HTML 页面还是 JSON 端点)是一个两步过程:
- 创建控制器:控制器是您编写的用于构建页面的 PHP 函数。您获取传入的请求信息并使用它来创建 Symfony Response对象,该对象可以保存 HTML 内容、JSON 字符串甚至图像或 PDF 等二进制文件;
- 创建路由:路由是页面的 URL(例如/about )并指向控制器。
创建页面:路由和控制器
假设您要创建一个页面 - /lucky/number - 生成一个幸运(好吧,随机)数字并打印它。为此,请创建一个“控制器”类并在其中创建一个“控制器”方法:
<?php
// src/Controller/LuckyController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
class LuckyController
{
public function number(): Response
{
$number = random_int(0, 100);
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>'
);
}
}
现在您需要将此控制器函数与公共 URL(例如/lucky/number )相关联,以便在用户浏览到它时调用number()方法。此关联是使用#[Route]属性定义的(在 PHP 中,属性用于将元数据添加到代码中):
// src/Controller/LuckyController.php
// ...
+ use Symfony\Component\Routing\Attribute\Route;
class LuckyController
{
+ #[Route('/lucky/number')]
public function number(): Response
{
// this looks exactly the same
}
}
就是这样!如果您使用Symfony Web 服务器,请访问: http://localhost:8000/lucky/number进行尝试
如果您看到幸运数字被打印回给您,那么恭喜您!但在你跑去玩彩票之前,先看看它是如何运作的。还记得创建页面的两个步骤吗?
- 创建一个控制器和一个方法:这是一个构建页面并最终返回Response对象的函数。您将在控制器自己的部分中了解有关控制器的更多信息,包括如何返回 JSON 响应;
- 创建路由:在config/routes.yaml中,路由定义页面的 URL( path )以及要调用的controller 。您将在其自己的部分中了解有关路由的更多信息,包括如何创建可变URL。
bin/console 命令
您的项目内部已经有一个强大的调试工具: bin/console命令。尝试运行它:
php bin/console
您应该会看到一个命令列表,这些命令可以为您提供调试信息、帮助生成代码、生成数据库迁移等等。当您安装更多软件包时,您将看到更多命令。 要获取系统中所有路由的列表,请使用debug:router命令:
php bin/console debug:router
您应该在列表中看到您的app_lucky_number路线:
---------------- ------- ------- ----- --------------
Name Method Scheme Host Path
---------------- ------- ------- ----- --------------
app_lucky_number ANY ANY ANY /lucky/number
---------------- ------- ------- ----- --------------
除了app_lucky_number之外,您还将看到调试路由——下一节将详细介绍调试路由。
当您继续学习时,您将了解更多命令!
Web 调试工具栏:梦想调试工具
Symfony令人惊叹的功能之一是 Web 调试工具栏:在开发时,该工具栏会在页面底部显示大量调试信息。这一切都通过名为symfony/profiler-pack的Symfony 包开箱即用。
您将在页面底部看到一个黑条。您将在此过程中了解有关它所包含的所有信息的更多信息,但请随意尝试:将鼠标悬停在不同的图标上并单击不同的图标以获取有关路由、性能、日志记录等的信息。
渲染模板
如果您从控制器返回 HTML,您可能需要呈现一个模板。幸运的是,Symfony 附带了Twig :一种最小、强大且实际上非常有趣的模板语言。
安装 twig 包:
composer require twig
确保LuckyController扩展了 Symfony 的AbstractController基类:
// src/Controller/LuckyController.php
// ...
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
- class LuckyController
+ class LuckyController extends AbstractController
{
// ...
}
现在,使用方便的render()方法来渲染模板。向其传递一个number变量,以便您可以在 Twig 中使用它:
// src/Controller/LuckyController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
// ...
class LuckyController extends AbstractController
{
#[Route('/lucky/number')]
public function number(): Response
{
$number = random_int(0, 100);
return $this->render('lucky/number.html.twig', [
'number' => $number,
]);
}
}
模板文件位于templates/目录中,该目录是在您安装 Twig 时自动创建的。创建一个新的templates/lucky目录,其中包含新的number.html.twig文件:
{# templates/lucky/number.html.twig #}
<h1>Your lucky number is {{ number }}</h1>
{{ number }}语法用于在 Twig 中打印变量。刷新您的浏览器即可获得新的幸运数字!
现在您可能想知道 Web 调试工具栏去了哪里:那是因为当前模板中没有
路由
当您的应用程序收到请求时,它会调用控制器操作来生成响应。路由配置定义对每个传入 URL 运行哪个操作。它还提供其他有用的功能,例如生成 SEO 友好的 URL(例如/read/intro-to-symfony而不是index.php?article_id=57 )。
创建路由
路由可以在 YAML、XML、PHP 中或使用属性进行配置。所有格式都提供相同的功能和性能,因此请选择您最喜欢的格式。Symfony 推荐 attributes,因为把 route 和 controller 放在同一个地方很方便。
创建路由作为属性
PHP 属性允许在与这些路由关联的控制器代码旁边定义路由。属性在 PHP 8 及更高版本中是原生的,因此您可以立即使用它们。
在使用它们之前,您需要向项目添加一些配置。如果你的项目使用 Symfony Flex,那么这个文件已经为你创建了。否则,请手动创建以下文件:
# 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 定义路由。为此,请创建一个如下所示的 controller 类:
// 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() 方法。
路由名称 (blog_list) 目前并不重要,但在以后生成 URL 时将是必不可少的。您只需记住,每个路由名称在应用程序中必须是唯一的。
在 YAML、XML 或 PHP 文件中创建路由
您可以在单独的 YAML、XML 或 PHP 文件中定义路由,而不是在控制器类中定义路由。主要优点是它们不需要任何额外的依赖。主要缺点是,在检查某些控制器动作的路由时,您必须处理多个文件。
以下示例展示了如何在 YAML/XML/PHP 中定义一个名为 blog_list 的路由,该路由将 /blog URL 与 BlogController 的 list() 操作相关联:
# 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
匹配 HTTP 方法
默认情况下,路由匹配任何 HTTP 动词(GET、POST、PUT 等)使用 methods 选项来限制每个路由应响应的动词:
// 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
}
}
匹配表达式
如果您需要基于某些任意匹配逻辑进行匹配的某些路由,请使用 condition 选项:
// 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) 使用 Environment Variable Processors 返回变量的值
-
service(string $alias) 返回路由条件服务。 首先,将 #[AsRoutingConditionService] 属性或 routing.condition_service 标记添加到要在路由条件中使用的服务: ``` 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() 函数在 conditions 中引用该服务: ``` // 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 执行时间的额外开销。
调试路由
随着应用程序的增长,您最终将拥有大量路由。Symfony 包含一些命令来帮助您调试路由问题。首先,debug:router 命令按照 Symfony 评估它们的顺序列出所有应用程序路由:
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}
---------------- ------- ------- ----- --------------------------------------------
将某个路由的名称(或名称的一部分)传递给此参数以打印路由详细信息:
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 |
+-------------+---------------------------------------------------------+
另一个命令称为 router:match,它显示哪个路由将匹配给定的 URL。找出某些 URL 未执行您期望的控制器操作的原因非常有用:
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}:
// 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} 参数添加一些验证:
// 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 Route 路线 Parameters 参数
/blog/2 blog_list $page = 2
/blog/my-first-post blog_show $slug = my-first-post $slug = 我的首发
如果您愿意,可以使用语法 {parameter_name} .此功能使配置更简洁,但在需求复杂时,它可能会降低路由的可读性:
// 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 时再次blog_list匹配。使用属性时,默认值在 controller 动作的参数中定义。在其他配置格式中,它们使用 defaults 选项定义:
// 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。
如果要始终在生成的 URL 中包含一些默认值(例如,在上一个示例中强制生成 /blog/1 而不是 /blog),请在参数名称前添加 !字符:/blog/{!page}
与 requirements 一样,也可以使用语法 {parameter_name?default_value} 在每个参数中内联默认值 。此功能与内联要求兼容,因此您可以在单个参数中内联两者:
// 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
{
// ...
}
}
优先级参数
Symfony 按照路由定义的顺序来评估路由。如果路由的路径与许多不同的模式匹配,则可能会阻止其他路由匹配。在 YAML 和 XML 中,您可以在配置文件中上下移动路由定义以控制其优先级。在定义为 PHP 属性的路由中,这要困难得多,因此你可以在这些路由中设置可选的 priority 参数来控制它们的优先级:
// 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 的整数)转换为另一个值(例如,代表用户的对象)。此功能称为 “param converter”。
现在,保留之前的路由配置,但更改 controller 操作的参数。添加 BlogPost $post,而不是字符串 $slug
// 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),则“param converter”会发出数据库请求,以使用请求参数(在本例中为 slug)查找对象。如果未找到对象,Symfony 会自动生成 404 响应。
查看 Doctrine param conversion 文档,了解 #[MapEntity] 属性,该属性可用于自定义用于从 route 参数获取对象的数据库查询。
支持的枚举参数
你可以使用 PHP 支持的枚举作为路由参数,因为 Symfony 会自动将它们转换为它们的标量值。
// 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/json 的 Content-Type)。
- _fragment 用于设置片段标识符,该标识符是 URL 的可选最后一部分,以 # 字符开头,用于标识文档的一部分。
- _locale 用于设置请求的区域设置。
您可以在单个路由和路由导入中包含这些属性(_fragment 除外)。Symfony 定义了一些同名的特殊属性(除了前导下划线),这样你就可以更容易地定义它们了:
// 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 选项中,您可以选择定义路由配置中未包含的参数。这对于将额外的参数传递给路由的控制器很有用:
// 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 值包含 / 字符,则此路由将不匹配。
一种可能的解决方案是将 parameter requirements 更改为更宽松:
// 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
{
// ...
}
}
路由别名
Route alias 允许您为同一路由设置多个名称:
# config/routes.yaml
new_route_name:
alias: original_route_name
在此示例中,original_route_name 和 new_route_name 路由都可以在应用程序中使用,并且将产生相同的结果。
弃用路由别名
如果某个路由别名不应该再使用(因为它已过时或您决定不再维护它),您可以弃用其定义:
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 包含共享路由配置的功能。
当定义路由为属性时,将通用配置放在 controller 类的 #[Route] 属性中。在其他路由格式中,在导入路由时使用选项定义通用配置。
// 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
{
// ...
}
}
在此示例中,index() 操作的路由将调用 blog_index,其 URL 将为 /blog/{_locale}。show() 操作的路由将blog_show调用,其 URL 将为 /blog/{_locale}/posts/{slug}。这两个路由还将验证 _locale 参数是否与 class 属性中定义的正则表达式匹配。
获取路由名称和参数
Symfony 创建的 Request 对象将所有路由配置(比如 name 和 parameters)都存储在 “request attributes” 中。您可以通过 Request 对象在控制器中获取此信息:
// 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();
// ...
}
}
在 services 中,您可以通过注入 RequestStack 服务来获取此信息。在模板中,使用 Twig 全局 app 变量获取当前路由名称 (app.current_route) 及其参数 (app.current_route_parameters)。
特殊路由
Symfony 定义了一些特殊的控制器来渲染模板并从路由配置中重定向到其他路由,因此你不必创建控制器动作。
直接从路由渲染模板
请阅读 Symfony 模板 主文章 中关于从 route 渲染模板的部分。
直接从路由重定向到 URL 和路由
使用 RedirectController 重定向到其他路由和 URL:
# 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
重定向带有尾部斜杠的 URL
从历史上看,URL 遵循 UNIX 约定,为目录添加尾部斜杠(例如 https://example.com/foo/)并删除它们以引用文件 (https://example.com/foo)。尽管为两个 URL 提供不同的内容是可以的,但现在将两个 URL 视为同一 URL 并在它们之间重定向是很常见的。
Symfony 遵循这个逻辑在有和没有尾部斜杠的 URL 之间重定向(但仅限于 GET 和 HEAD 请求):
子域路由
路由可以配置 host 选项,以要求传入请求的 HTTP 主机与某个特定值匹配。在以下示例中,两个路由都匹配相同的路径 (/),但其中一个路由仅响应特定的主机名:
// 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 选项的值可以包含参数(这在多租户应用程序中很有用),并且这些参数也可以根据需要进行验证:
// 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 时,您都需要包含一个 subdomain 值。
本地化路由 (i18n)
如果您的应用程序被翻译成多种语言,则每个路由可以为每个翻译区域设置定义不同的 URL。这避免了复制路由的需要,这也减少了潜在的错误:
// 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
{
// ...
}
}
当匹配到本地化的路由时,Symfony 在整个请求过程中会自动使用相同的 locale。
国际化应用程序的一个常见要求是为所有路由添加 locale 前缀。这可以通过为每个 locale 定义不同的前缀来完成(如果您愿意,还可以为默认 locale 设置一个空前缀):
# config/routes/attributes.yaml
controllers:
resource: '../../src/Controller/'
type: attribute
prefix:
en: '' # don't prefix URLs for English, the default locale
nl: '/nl'
另一个常见要求是根据区域设置将网站托管在不同的域上。这可以通过为每个 locale 定义不同的主机来完成。
# config/routes/attributes.yaml
controllers:
resource: '../../src/Controller/'
type: attribute
host:
en: 'www.example.com'
nl: 'www.example.nl'
无状态路由
有时,当应该缓存 HTTP 响应时,确保这种情况可能发生是很重要的。但是,每当在请求期间启动会话时,Symfony 都会将响应转换为私有的不可缓存响应。
有关详细信息,请参阅 HTTP 缓存。
路由可以配置一个无状态的布尔选项,以声明在匹配请求时不应使用该会话:
// 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 参数报告它:
- enabled:将引发 UnexpectedSessionUsageException 异常
- disabled:将记录警告
它将帮助您了解并希望修复应用程序中的意外行为。
生成 URL
路由系统是双向的:
- 它们将 URL 与控制器相关联(如前几节所述);
- 它们为给定路由生成 URL。
通过从路由生成 URL,您可以不在 HTML 模板中手动写入 <a href=“...”>
值。此外,如果某些路由的 URL 发生变化,您只需更新路由配置,所有链接都会更新。
要生成 URL,您需要指定路由的名称(例如 blog_show)和路由定义的参数值(例如 slug = my-blog-post)。
因此,每个路由都有一个内部名称,该名称在应用程序中必须是唯一的。如果你没有使用 name 选项显式设置路由名称,Symfony 会根据控制器和动作自动生成一个名称。
如果目标类有一个添加路由的 __invoke() 方法,并且目标类恰好添加了一条路由,那么 Symfony 就会基于 FQCN 声明路由别名。Symfony 还会自动为每个只定义一个路由的方法添加一个别名。请考虑以下类:
// 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 .
在 Controller 中生成 URL
如果你的控制器是从 AbstractController 继承而来的,请使用 generateUrl() 辅助函数:
// 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']);
// ...
}
}
如果你的控制器不是从 AbstractController 扩展而来的,你需要在你的控制器中获取服务,并按照下一节的说明进行操作。
在 Services 中生成 URL
将路由器 Symfony 服务注入到你自己的服务中,并使用它的 generate() 方法。当使用服务自动装配时,您只需在服务构造函数中添加一个参数,并使用 UrlGeneratorInterface 类对其进行类型提示:
// src/Service/SomeService.php
namespace App\Service;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class SomeService
{
public function __construct(
private UrlGeneratorInterface $router,
) {
}
public function someMethod(): void
{
// ...
// generate a URL with no route arguments
$signUpPage = $this->router->generate('sign_up');
// generate a URL with route arguments
$userProfilePage = $this->router->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->router->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->router->generate('sign_up', ['_locale' => 'nl']);
}
}
在模板中生成 URL
请阅读 Symfony 模板主条目中关于在页面之间创建链接的部分。
在 JavaScript 中生成 URL
如果您的 JavaScript 代码包含在 Twig 模板中,则可以使用 path() 和 url() Twig 函数生成 URL 并将其存储在 JavaScript 变量中。需要 escape() 过滤器来转义任何非 JavaScript 安全的值:
<script>
const route = "{{ path('blog_show', {slug: 'my-blog-post'})|escape('js') }}";
</script>
如果您需要动态生成 URL 或者使用纯 JavaScript 代码,则此解决方案不起作用。在这些情况下,请考虑使用 FOSJsRoutingBundle。
在命令中生成 URL
在命令中生成 URL 的工作方式与在服务中生成 URL 相同。唯一的区别是命令不在 HTTP 上下文中执行。因此,如果您生成绝对 URL,您将获得 http://localhost/ 作为主机名,而不是您的真实主机名。
解决方案是配置 default_uri 选项来定义命令在生成 URL 时使用的 “request context”:
# config/packages/routing.yaml
framework:
router:
# ...
default_uri: 'https://example.org/my/path/'
现在,在命令中生成 URL 时,您将获得预期的结果:
// 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']);
// ...
}
}
检查路由是否存在
在高度动态的应用程序中,可能需要先检查路由是否存在,然后再使用它来生成 URL。在这些情况下,请不要使用 getRouteCollection() 方法,因为这会重新生成路由缓存并减慢应用程序的速度。
相反,请尝试生成 URL 并捕获路由不存在时引发的 RouteNotFoundException:
use Symfony\Component\Routing\Exception\RouteNotFoundException;
// ...
try {
$url = $this->router->generate($routeName, $routeParameters);
} catch (RouteNotFoundException $e) {
// the route is not defined...
}
在生成的 URL 上强制使用 HTTPS
默认情况下,生成的 URL 使用与当前请求相同的 HTTP 方案。在没有 HTTP 请求的控制台命令中,URL 默认使用 http。你可以通过每个命令(通过路由器的 getContext() 方法)或使用以下配置参数全局更改它:
# config/services.yaml
parameters:
router.request_context.scheme: 'https'
asset.request_context.secure: true
在控制台命令之外,使用 schemes 选项显式定义每个路由的方案:
// 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
{
// ...
}
}
为登录路由生成的 URL 将始终使用 HTTPS。这意味着,当使用 path() Twig 函数生成 URL 时,如果原始请求的 HTTP 方案与路由使用的方案不同,则可能会得到绝对 URL 而不是相对 URL:
{# 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 方案。
如果要强制一组路由使用 HTTPS,可以在导入路由时定义默认方案。以下示例在定义为属性的所有路由上强制使用 HTTPS:
# config/routes/attributes.yaml
controllers:
resource: '../../src/Controller/'
type: attribute
schemes: [https]
对 URI 进行签名
签名 URI 是包含依赖于 URI 内容的哈希值的 URI。这样,您以后可以通过重新计算签名 URI 的哈希值并将其与 URI 中包含的哈希值进行比较来检查签名 URI 的完整性。
Symfony 提供了一个通过 UriSigner 服务对 URI 进行签名的工具,你可以将其注入到你的服务或控制器中:
// 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 参数定义过期日期/时间:
// 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'
}
}
故障排除
以下是您在使用路由时可能会看到的一些常见错误:
Controller "App\\Controller\\BlogController::show()" requires that you
provide a value for the "$slug" argument.
当你的控制器方法有一个参数(例如 $slug)时,就会发生这种情况:
public function show(string $slug): Response
{
// ...
}
但是你的路由路径没有 {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', ['slug' => 'slug-value']);
or, in Twig: 或者,在 Twig 中:
{{ path('blog_show', {slug: 'slug-value'}) }}
控制器
控制器是您创建的 PHP 函数,它从 Request 对象中读取信息并创建并返回 Response 对象。响应可以是 HTML 页面、JSON、XML、文件下载、重定向、404 错误或其他任何内容。控制器运行应用程序呈现页面内容所需的任意逻辑。
基础控制器
虽然控制器可以是任何 PHP 可调用的(函数、对象上的方法或 Closure),但控制器通常是控制器类中的一个方法:
// src/Controller/LuckyController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class LuckyController
{
#[Route('/lucky/number/{max}', name: 'app_lucky_number')]
public function number(int $max): Response
{
$number = random_int(0, $max);
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>'
);
}
}
控制器是 number() 方法,它位于控制器类 LuckyController 中。
这个控制器非常简单:
- 第 2 行:Symfony 利用 PHP 的命名空间功能来命名空间整个控制器类。
- 第 4 行:Symfony 再次利用了 PHP 的命名空间功能:use 关键字导入了控制器必须返回的 Response 类。