Layout hiện tại đang thiếu một đầu điều hướng (navigation header) để quay lại trang chủ hoặc đổi từ conference này sang conference kế tiếp.
Thêm một Website Header
Tất cả các thứ hiện trên tất cả các trang như header là một phần của Layout chính (base layout):
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -14,6 +14,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>
Thêm code này vào layout có nghĩa là tất cả các template sử dụng base layout cần phải định nghĩa biến conferences,
Các biến này cần được tạo và đưa vào controller của chúng.
Ở đây chúng ta có 2 controllers, bên bạn có thể làm như sau (lưu ý không áp dụng thay đổi ngay, vì phân tiếp sẽ có cách tốt hơn)
--- 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::PAGINATOR_PER_PAGE,
Với 2 controllers thì thực hiện khá đơn giản, nhưng mà nếu có vài chục controllers, nếu làm tương tự thì không thực tiễn lắm, và có một cách tốt hơn.
Twig có biến global, - biến này sẵn có ở tất cả các rendered templates. Bạn có thể định nghĩa biến ở file cấu hình, nhưng nó chỉ thực hiện với giá trị tĩnh (static value), để thêm tất cả các confernces là một biến Twig global, chúng ta cần tạo một listener.
Tìm hiểu Symfony Events
Symfony xây dựng sẵn (built-in) Event Dispatcher Component. A dispatcher phát các events (sự kiện) ở thời gian cụ thể listeners có thể nghe thấy. Listeners là các hooks vào bên trong framework.
Chẳng hạn, một vài events (sự kiện) cho phép bạn tương tác với vòng đời (lifecycle) của một HTTP requests. Trong quá trình xử lý một request, the dispatcher phát đi các sự kiện (events),
khi một controller sẵn sàng thực thi,
khi một response sẵn sàng gửi đi,
hay một exception vừa được ném.
Một listener có thể nghe một hay nhiều sự kiện (events) và thực thi vài logic dựa trên bối cảnh sự kiện (event context)
Các sự kiện là điểm mở rộng được định nghĩa chuẩn làm cho framework thêm phần mở rộng và generic. Nhiều Symfony Components như Security, Messenger, Workflow, or Mailer sử dụng rất sâu.
Một ví dụ về events và listeners là vòng đời của một lệnh: bạn có thể tạo một listener để thực thi code trước khi bấy kỳ một lệnh nào chạy.
Another built-in example of events and listeners in action is the lifecycle of a command: you can create a listener to execute code before any command is run.
Any package or bundle can also dispatch their own events to make their code extensible.
Bất kỳ package hay bundle đều có thể phát sự kiện (events) riêng của chúng để làm cho code mở rộng.
Tạo một subscribe để tránh việc tạo file cấu hình - file này mô tả các sự kiện mà một listener muốn nghe. Như vậy một subscriber là một listener vớí static method getSubscribedEvents()
trả lại cấu hình của listener. Điều này cho phép các subscriber được đăng ký trong Symfony dispatcher một cách tự động.
Implementing a Subscriber
Sử dụng maker bundle để tạo một subcriber, đặt tên là TwigEventSubscriber
$ symfony console make:subscriber TwigEventSubscriber
Lệnh này hỏi bạn sự kiện (event) bạn muốn nghe, chọn Symfony\Component\HttpKernel\Event\ControllerEvent
event, sự kiện này được phát đi ngay trước khi a cotroller được gọi. Đây là thời điểm tốt nhất để đưa biến Twig global conferences
global để Twig có truy cập vào khi mà controller render templates. Cập nhật subscriber như dưới đây:
--- 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
Giờ bạn có thể thể thêm bao nhiều controllers tuỳ thích: Biến conferences
luôn sẵn có trong Twig.
We will talk about a much better alternative performance-wise in a later step.
Về vấn đề hiệu năng tốt hơn - performance-wise, sẽ được đề cấp trong bước tiếp theo.
Sorting Conferences by Year and City
Ordering the conference list by year may facilitate browsing. We could create a custom method to retrieve and sort all conferences, but instead, we are going to override the default implementation of the findAll()
method to be sure that sorting applies everywhere:
--- 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']);
+ }
+
public function save(Conference $entity, bool $flush = false): void
{
$this->getEntityManager()->persist($entity);
At the end of this step, the website should look like the following: