步骤 6: 创建控制器

5.0 version
Maintained

创建控制器

我们的留言本项目已经在生产服务器上了,但我们做了点弊。这个项目还没有任何网页。首页只不过是个无聊的 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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
namespace App\Controller;

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

class ConferenceController extends AbstractController
{
    /**
     * @Route("/conference", name="conference")
     */
    public function index()
    {
        return $this->render('conference/index.html.twig', [
            'controller_name' => 'ConferenceController',
        ]);
    }
}

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

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

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

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -8,7 +8,7 @@ use Symfony\Component\Routing\Annotation\Route;
 class ConferenceController extends AbstractController
 {
     /**
-     * @Route("/conference", name="conference")
+     * @Route("/", name="homepage")
      */
     public function index()
     {

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

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

patch_file
 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
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -3,6 +3,7 @@
 namespace App\Controller;

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

 class ConferenceController extends AbstractController
@@ -12,8 +13,13 @@ class ConferenceController extends AbstractController
      */
     public function index()
     {
-        return $this->render('conference/index.html.twig', [
-            'controller_name' => 'ConferenceController',
-        ]);
+        return new Response(<<<EOF
+<html>
+    <body>
+        <img src="/images/under-construction.gif" />
+    </body>
+</html>
+EOF
+        );
     }
 }

刷新浏览器:

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

加一个彩蛋

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

 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
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -3,6 +3,7 @@
 namespace App\Controller;

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

@@ -11,11 +12,17 @@ class ConferenceController extends AbstractController
     /**
      * @Route("/", name="homepage")
      */
-    public function index()
+    public function index(Request $request)
     {
+        $greet = '';
+        if ($name = $request->query->get('hello')) {
+            $greet = sprintf('<h1>Hello %s!</h1>', htmlspecialchars($name));
+        }
+
         return new Response(<<<EOF
 <html>
     <body>
+        $greet
         <img src="/images/under-construction.gif" />
     </body>
 </html>

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

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

注解

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

我们也可以把这个人的名字作为 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
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -9,13 +9,19 @@ use Symfony\Component\Routing\Annotation\Route;
 class ConferenceController extends AbstractController
 {
     /**
-     * @Route("/", name="homepage")
+     * @Route("/hello/{name}", name="homepage")
      */
-    public function index()
+    public function index(string $name = '')
     {
+        $greet = '';
+        if ($name) {
+            $greet = sprintf('<h1>Hello %s!</h1>', htmlspecialchars($name));
+        }
+
         return new Response(<<<EOF
 <html>
     <body>
+        $greet
         <img src="/images/under-construction.gif" />
     </body>
 </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.