イベントをリッスンする
現在のレイアウトは、ホームページへ戻ったり、個々のカンファレンスページへ移動するナビゲーションヘッダーがありません。
Webサイトのヘッダーを追加する
全てのWebページに表示されるヘッダーなどのパーツはベースレイアウト内にあった方が良いですね:
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
@@ -12,6 +12,15 @@
{% 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つしかコントローラーがないので、次のようにしたくなるかもしれません(この変更はコードに適用しないでください。より良い方法をこの後すぐに学ぶからです):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -21,12 +21,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 $this->render('conference/show.html.twig', [
+ 'conferences' => $conferenceRepository->findAll(),
'conference' => $conference,
'comments' => $paginator,
'previous' => $offset - CommentRepository::COMMENTS_PER_PAGE,
コントローラーがたくさんあったとするとどうしましょうか?そしてコントローラーを追加する度に同じことをしますか?しかし、それはあまり良いプラクティスではないので、もっと良い方法でした方が良いですね。
Twig はグローバル変数を使うことができます。 グローバル変数 は、全てのテンプレートで使うことができます。静的な値である必要がありますが、設定ファイルで定義することができます。全てのカンファレンスをTwig のグローバル変数として追加するために、リスナーを追加をしましょう。
Symfony Events を使ってみる
Symfony は Event Dispatcher コンポーネントがビルトインされています。ディスパッチャーは、リスナー が、リッスンできる指定のタイミングで、決められた イベント をディスパッチします。リスナーはフレームワーク内部へのフックです。
例えば、 HTTPリクエストのライフサイクルにフックされたイベントもあります。リクエストを処理する際の、リクエストが作成された時、コントローラーが実行される時、レスポンスを返す準備ができた時などに、ディスパッチャーは、イベントをディスパッチします。 リスナー は、イベントをリッスンすることで、イベントのコンテキストに基づいたロジックを処理することができます。
フレームワークをより汎用に拡張しやすいようにイベントは設計されています。Security, Message, Workflow や Mailer などの Symfony コンポーネントは至るところでイベントを使用しています。
他のビルトインされたイベントとリスナーの例は、コマンドのライフサイクルです: どんな コマンド実行前にでもリスナーを作成し、コードを実行させることができます。
パッケージやバンドルはコードを拡張しやすいように自らのイベントをディスパッチすることもできます。
どのイベントをリッスンするかを説明する設定ファイルを作らなくても良いように サブスクライバー を作りましょう。サブスクライバーは、 static メソッドの getSubscribedEvents()
で設定を返すことができるリスナーです。こうやってSymfony ディスパッチャーを自動的に登録することができます。
サブスクライバーを実装する
Maker バンドルを使ってサブスクライバーを生成しましょう:
1
$ symfony console make:subscriber TwigEventSubscriber
このコマンドで、リッスンしたいイベントを答えます。コントローラーが呼ばれる直前にディスパッチされる Symfony
イベントを選んでください。Twigがグローバル変数 conference
を参照するのは、コントローラーがテンプレートをレンダーする際ですので、ここが良いタイミングです:
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): void
{
- // ...
+ $this->twig->addGlobal('conferences', $this->conferenceRepository->findAll());
}
public static function getSubscribedEvents(): array
これで新しくコントローラーを追加しても、 Twig 内 conference
変数が使えるようになりました。
Note
後のステップでよりパフォーマンスを考慮した方法を説明します。
年と都市でカンファレンスをソートする
年でカンファレンスのリストをソートすることで見やすくなるかもしれません。カンファレンスの一覧を取得してソートするカスタムメソッドを作成することもできますが、 findAll()
メソッドのデフォルトの実装をオーバーライドして、全ての場所でソートされた状態で取得できるようにしてみましょう:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
--- a/src/Repository/ConferenceRepository.php
+++ b/src/Repository/ConferenceRepository.php
@@ -21,6 +21,11 @@ class ConferenceRepository extends ServiceEntityRepository
parent::__construct($registry, Conference::class);
}
+ public function findAll(): array
+ {
+ return $this->findBy([], ['year' => 'ASC', 'city' => 'ASC']);
+ }
+
// /**
// * @return Conference[] Returns an array of Conference objects
// */
最後のステップとして、Webサイトが次のようになっているか見てみてください: