步骤 12: 监听事件
当前的布局缺少一个导航的页头,这个页头可以用来返回首页,或者从一个会议跳到下一个会议。
添加网站页头
任何会展示在所有页面的内容,比如页头,都是基础布局的一部分:
patch_file
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -8,6 +8,15 @@
{% block javascripts %}{% 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 %}
</body>
</html>
把这段代码添加到布局里,这意味着所有继承布局的模板都要定义一个名为 conferences
的变量,这个变量由控制器创建并传递给模板。
由于我们只有 2 个控制器,你 可能 会要做下面的操作(不要照这样去改你的代码,我们很快会学习一种更好的方式):
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -29,12 +29,13 @@ class ConferenceController extends AbstractController
}
#[Route('/conference/{id}', name: 'conference')]
- public function show(Request $request, Conference $conference, CommentRepository $commentRepository): Response
+ public function show(Request $request, Conference $conference, CommentRepository $commentRepository, ConferenceRepository $conferenceRepository): Response
{
$offset = max(0, $request->query->getInt('offset', 0));
$paginator = $commentRepository->getCommentPaginator($conference, $offset);
return new Response($this->twig->render('conference/show.html.twig', [
+ 'conferences' => $conferenceRepository->findAll(),
'conference' => $conference,
'comments' => $paginator,
'previous' => $offset - CommentRepository::PAGINATOR_PER_PAGE,
想象一下你必须更新几十个控制器,而且新的控制器里也要这样做。这不太现实。肯定有一个更好的方式。
Twig 支持全局变量。全局变量 在所有渲染的模板中可用。你可以在一个配置文件中定义它们,但这只适用于值不变的全局变量。若要添加一个 Twig 全局变量来引用所有会议,我们需要创建一个 监听器。
认识 Symfony 的事件
Symfony 自带一个事件分发组件。分发器在某些特定的时刻 分发 某些 事件, 监听器 则监听这些事件。 监听器 就是接入框架内部的钩子。
例如,一些事件允许你和 HTTP 请求的生命周期进行交互。在处理一个请求的过程中,分发器会分发一些事件,这可以发生在请求对象被创建的时候,或是在控制器即将执行的时候,也可以在应答对象就绪并准备发送的时候,还可以在一个异常抛出的时候。监听器 可以监听一个或多个事件,并且根据事件上下文执行一些逻辑。
事件是一些定义良好的扩展点,它使得这个框架适应于更多的场景,也更具扩展性。很多 Symfony 组件,比如 Security 组件、Messenger 组件、Workflow 组件和 Mailer 组件大量使用事件。
另一个自带的事件和监听器的实际案例就是命令的生命周期:你可以创建一个监听器,让它在 任何 命令执行之前运行一些代码。
任何包和 bundle 可以分发自己的事件,这使得它们的代码更具扩展性。
为了避免写一个配置文件来描述监听器要监听哪些事件,可以创建一个 订阅器。一个订阅器也是一个监听器,但它有一个静态的 getSubscribedEvents()
方法,该方法返回配置信息。这个机制使得订阅器可以自动注册到 Symfony 的事件分发器。
实现一个订阅器
你对于使用 Maker Bundle 来生成代码应该牢记于心了,现在用它来生成一个订阅器:
$ symfony console make:subscriber TwigEventSubscriber
这个命令会问你要监听哪个事件。选择``SymfonyComponentHttpKernelEventControllerEvent``事件,该事件在控制器被调用前那一刻被分发。这是注入``conferences``全局变量的最好时机,这样当控制器渲染模板时,Twig就可以使用它。按照下面的代码来更新你的订阅器:
patch_file
--- 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
--- 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
// */
在本步骤的结尾,网站看上去应该像这样:
深入学习
- Symfony 应用中的 请求-应答流;
- Symfony 自带的 HTTP 事件;
- Symfony 自带的 Console 事件。
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.