步骤 6: 创建控制器

我们的留言本项目已经在生产服务器上了,但我们做了点弊。这个项目还没有任何网页。首页只不过是个无聊的 404 错误页面。让我们来修复它。

当一个 HTTP 请求进来的时候,比如访问首页的时候(http://localhost:8000/),Symfony 试着去找到一条匹配 请求路径路由 (在本例中是请求路径 /)。一条 路由 就是一个请求路径和一个 PHP callable 之间的链接,而这个 PHP callable 是一个函数,它为该请求生成一个 HTTP 应答

这些 callable 称为“控制器”。在 Symfony 里,大部分控制器都以 PHP 类的形式来实现。你可以手工创建这样的一个类,但是因为我们喜欢快速开发,我们来看看 Symfony 如何能帮助我们。

Maker Bundle 来偷偷懒

我们可以用 symfony/maker-bundle 包来轻松生成控制器。

  1. $ symfony composer req maker --dev

因为 maker bundle 只是在开发时用到,不要忘记加上``–dev``选项,避免在生产环境中启用它。

maker bundle 帮你生成很多不同的类。我们在本书中会一直用到它。每一个”生成器“对应一个命令,所有这些命令都是 make 命令命名空间的一部分。

Symfony Console 自带 list 命令,它会列出某个命名空间下的所有可用命令;你用它来查看 maker bundle 提供的所有生成器。

  1. $ symfony console list make

选择配置的格式

在创建项目的第一个控制器之前,我们需要选择配置的格式。Symfony 自带支持YAML、XML、PHP 和注解(annotation)。

对于 和依赖包相关的配置YAML 是最好的选择。这是 config/ 目录里的文件所使用的格式。一种常见的情况是,当你安装一个新的包时,包的 recipe 会添加一个 .yaml 后缀的文件到 config/ 目录下。

对于 和 PHP 代码相关的配置注解 是更好的选择,因为它们就定义在代码旁边。让我用一个例子来解释下。当一个请求进来,需要有一些配置去告诉 Symfony 该请求路径需要被哪个具体的控制器(一个 PHP 类)来处理。当使用 YAML、XML 或者 PHP 这些配置格式的时候,涉及到两个文件(配置文件和 PHP 控制器文件)。当使用注解时,配置是直接写在控制器类里的。

我们需要添加另一个依赖包来管理注解。

  1. $ symfony composer req annotations

你可能会想,当要为某个功能安装一个包时,该如何知道包的名字?大多数时候,你不必知道。在很多情况下,Symfony 会在错误信息中指出要安装的包。举个例子,如果运行 symfony make:controller 命令时还没有安装 annotations 包,那异常信息就会显示,它会提示你所需要安装的包。

生成一个控制器

make:controller 命令来创建你的第一个 控制器

  1. $ symfony console make:controller ConferenceController

这个命令在 src/Controller/ 目录下新建了一个名为 ConferenceController 的类。这个生成的类由一些样本代码组成,你可以去微调它。

src/Controller/ConferenceController.php

  1. namespace App\Controller;
  2. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  3. use Symfony\Component\HttpFoundation\Response;
  4. use Symfony\Component\Routing\Annotation\Route;
  5. class ConferenceController extends AbstractController
  6. {
  7. #[Route('/conference', name: 'conference')]
  8. public function index(): Response
  9. {
  10. return $this->render('conference/index.html.twig', [
  11. 'controller_name' => 'ConferenceController',
  12. ]);
  13. }
  14. }

#[Route('/conference', name: 'conference')] 注解使 index() 方法成为一个控制器(这段配置就在它所配置的代码旁边)。

当你用浏览器访问 /conference 路径时,这个控制器就会被执行,它返回一个应答。

调整一下路由,让它匹配首页的路径:

patch_file

  1. --- a/src/Controller/ConferenceController.php
  2. +++ b/src/Controller/ConferenceController.php
  3. @@ -8,7 +8,7 @@ use Symfony\Component\Routing\Annotation\Route;
  4. class ConferenceController extends AbstractController
  5. {
  6. - #[Route('/conference', name: 'conference')]
  7. + #[Route('/', name: 'homepage')]
  8. public function index(): Response
  9. {
  10. return $this->render('conference/index.html.twig', [

当我们想要在代码里引用首页的地址时,路由的 name 属性会被用到。我们不会硬编码 / 路径,而是会用路由名。

我们来返回一个简单的 HTML 页面,来取代这个默认的页面。

patch_file

  1. --- a/src/Controller/ConferenceController.php
  2. +++ b/src/Controller/ConferenceController.php
  3. @@ -11,8 +11,13 @@ class ConferenceController extends AbstractController
  4. #[Route('/', name: 'homepage')]
  5. public function index(): Response
  6. {
  7. - return $this->render('conference/index.html.twig', [
  8. - 'controller_name' => 'ConferenceController',
  9. - ]);
  10. + return new Response(<<<EOF
  11. +<html>
  12. + <body>
  13. + <img src="/images/under-construction.gif" />
  14. + </body>
  15. +</html>
  16. +EOF
  17. + );
  18. }
  19. }

刷新浏览器:

步骤 6: 创建控制器 - 图1

控制器的主要责任就是针对请求返回一个 HTTP Response 对象。

加一个彩蛋

为了演示应答如何来利用请求中的信息,让我们来加一个小的 彩蛋#In_computing)。当首页路径包含了一个类似 ?hello=Fabien 的查询字符串时,让我们添加一些文字来对字符串中的人致意。

  1. --- a/src/Controller/ConferenceController.php
  2. +++ b/src/Controller/ConferenceController.php
  3. @@ -3,6 +3,7 @@
  4. namespace App\Controller;
  5. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  6. +use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Component\HttpFoundation\Response;
  8. use Symfony\Component\Routing\Annotation\Route;
  9. @@ -11,11 +12,17 @@ class ConferenceController extends AbstractController
  10. #[Route('/', name: 'homepage')]
  11. - public function index(): Response
  12. + public function index(Request $request): Response
  13. {
  14. + $greet = '';
  15. + if ($name = $request->query->get('hello')) {
  16. + $greet = sprintf('<h1>Hello %s!</h1>', htmlspecialchars($name));
  17. + }
  18. +
  19. return new Response(<<<EOF
  20. <html>
  21. <body>
  22. + $greet
  23. <img src="/images/under-construction.gif" />
  24. </body>
  25. </html>

Symfony 通过 Request 对象来暴露请求中的数据。当 Symfony 看到控制器里有 Request 类型的参数,它会被传递给你。我们可以用它来获得查询字符串中的名字,然后添加一个 <h1> 标题。

现在浏览器里打开 / 路径,再打开 /?hello=Fabien 路径,看一下两者的不同。

注解

注意一下对 htmlspecialchars() 函数的调用,是为了防止 XSS 攻击。当我们切换到一个良好的模板引擎后,这些都会自动为我们完成。

我们也可以把这个人的名字作为 URL 的一部分:

  1. --- a/src/Controller/ConferenceController.php
  2. +++ b/src/Controller/ConferenceController.php
  3. @@ -9,13 +9,19 @@ use Symfony\Component\Routing\Annotation\Route;
  4. class ConferenceController extends AbstractController
  5. {
  6. - #[Route('/', name: 'homepage')]
  7. + #[Route('/hello/{name}', name: 'homepage')]
  8. - public function index(): Response
  9. + public function index(string $name = ''): Response
  10. {
  11. + $greet = '';
  12. + if ($name) {
  13. + $greet = sprintf('<h1>Hello %s!</h1>', htmlspecialchars($name));
  14. + }
  15. +
  16. return new Response(<<<EOF
  17. <html>
  18. <body>
  19. + $greet
  20. <img src="/images/under-construction.gif" />
  21. </body>
  22. </html>

路由中的 {name} 部分是动态 路由参数,它类似于通配符。你可以在浏览器先访问 /hello 再访问 /hello/Fabien,看到的结果和之前一样。在控制器参数里用同 的参数,能得到 {name} 参数的 。在本例中,参数名就叫 $name

深入学习


This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.