步骤 12: 监听事件

5.0 version
Maintained

监听事件

当前的布局缺少一个导航的页头,这个页头可以用来返回首页,或者从一个会议跳到下一个会议。

添加网站页头

任何会展示在所有页面的内容,比如页头,都是基础布局的一部分:

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -6,6 +6,15 @@
         {% block stylesheets %}{% endblock %}
     </head>
     <body>
+        <header>
+            <h1><a href="{{ path('homepage') }}">Guestbook</a></h1>
+            <ul>
+            {% for conference in conferences %}
+                <li><a href="{{ path('conference', { id: conference.id }) }}">{{ conference }}</a></li>
+            {% endfor %}
+            </ul>
+            <hr />
+        </header>
         {% block body %}{% endblock %}
         {% block javascripts %}{% endblock %}
     </body>

把这段代码添加到布局里,这意味着所有继承布局的模板都要定义一个名为 conferences 的变量,这个变量由控制器创建并传递给模板。

由于我们只有两个控制器,你可能会像下面这样做:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -32,9 +32,10 @@ class ConferenceController extends AbstractController
     /**
      * @Route("/conference/{slug}", name="conference")
      */
-    public function show(Conference $conference, CommentRepository $commentRepository)
+    public function show(Conference $conference, CommentRepository $commentRepository, ConferenceRepository $conferenceRepository)
     {
         return new Response($this->twig->render('conference/show.html.twig', [
+            'conferences' => $conferenceRepository->findAll(),
             'conference' => $conference,
             'comments' => $commentRepository->findBy(['conference' => $conference], ['createdAt' => 'DESC']),
         ]));

想象一下你必须更新几十个控制器,而且新的控制器里也要这样做。这不太现实。肯定有一个更好的方式。

Twig 支持全局变量。全局变量 在所有渲染的模板中可用。你可以在一个配置文件中定义它们,但这只适用于值不变的全局变量。若要添加一个 Twig 全局变量来引用所有会议,我们需要创建一个 监听器

认识 Symfony 的事件

Symfony 自带一个事件分发组件。分发器在某些特定的时刻 分发 某些 事件监听器 则监听这些事件。 监听器 就是接入框架内部的钩子。

例如,一些事件允许你和 HTTP 请求的生命周期进行交互。在处理一个请求的过程中,分发器会分发一些事件,这可以发生在请求对象被创建的时候,或是在控制器即将执行的时候,也可以在应答对象就绪并准备发送的时候,还可以在一个异常抛出的时候。监听器 可以监听一个或多个事件,并且根据事件上下文执行一些逻辑。

事件是一些定义良好的扩展点,它使得这个框架适应于更多的场景,也更具扩展性。很多 Symfony 组件,比如 Security 组件、Messenger 组件、Workflow 组件和 Mailer 组件大量使用事件。

另一个自带的事件和监听器的实际案例就是命令的生命周期:你可以创建一个监听器,让它在 任何 命令执行之前运行一些代码。

任何包和 bundle 可以分发自己的事件,这使得它们的代码更具扩展性。

为了避免写一个配置文件来描述监听器要监听哪些事件,可以创建一个 订阅器。一个订阅器也是一个监听器,但它有一个静态的 getSubscribedEvents() 方法,该方法返回配置信息。这个机制使得订阅器可以自动注册到 Symfony 的事件分发器。

实现一个订阅器

你对于使用 Maker Bundle 来生成代码应该牢记于心了,现在用它来生成一个订阅器:

1
$ symfony console make:subscriber TwigEventSubscriber

这个命令会问你要监听哪个事件。选择``SymfonyComponentHttpKernelEventControllerEvent``事件,该事件在控制器被调用前那一刻被分发。这是注入``conferences``全局变量的最好时机,这样当控制器渲染模板时,Twig就可以使用它。按照下面的代码来更新你的订阅器:

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
28
29
--- a/src/EventSubscriber/TwigEventSubscriber.php
+++ b/src/EventSubscriber/TwigEventSubscriber.php
@@ -2,14 +2,25 @@

 namespace App\EventSubscriber;

+use App\Repository\ConferenceRepository;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 use Symfony\Component\HttpKernel\Event\ControllerEvent;
+use Twig\Environment;

 class TwigEventSubscriber implements EventSubscriberInterface
 {
+    private $twig;
+    private $conferenceRepository;
+
+    public function __construct(Environment $twig, ConferenceRepository $conferenceRepository)
+    {
+        $this->twig = $twig;
+        $this->conferenceRepository = $conferenceRepository;
+    }
+
     public function onControllerEvent(ControllerEvent $event)
     {
-        // ...
+        $this->twig->addGlobal('conferences', $this->conferenceRepository->findAll());
     }

     public static function getSubscribedEvents()

现在,你可以添加任意多的控制器:conferences 变量在 Twig 里总是可用。

注解

在以后的步骤中,我们会谈到一个性能好很多的替代方案。

按照年份和城市对会议进行排序

将会议按照年份排序可以让浏览变得更容易。我们可以创建一个定制方法来获取会议并对它们排序,但这里我们不用这个方式,而是去覆盖 findAll() 方法的默认实现,这样可以确保程序的所有地方都应用这个排序。

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
--- a/src/Repository/ConferenceRepository.php
+++ b/src/Repository/ConferenceRepository.php
@@ -19,6 +19,11 @@ class ConferenceRepository extends ServiceEntityRepository
         parent::__construct($registry, Conference::class);
     }

+    public function findAll()
+    {
+        return $this->findBy([], ['year' => 'ASC', 'city' => 'ASC']);
+    }
+
     // /**
     //  * @return Conference[] Returns an array of Conference objects
     //  */

在本步骤的结尾,网站看上去应该像这样:


  • « Previous 步骤 11: 对代码进行分支
  • Next » 步骤 13: 管理 Doctrine 对象的生命周期

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