W tym wpisie dowiesz się czym jest i jak można wykorzystać mercure wraz z Symfony. Nie przedstawię Ci tutaj wszystkich możliwości połączenia tych dwóch rzeczy (zresztą jest to niewykonalne) ale będą to dobre podstawy do dalszych działań.
Cały kod wpisu możesz znaleźć [TUTAJ].
Co to mercure
Zapewne nie raz nie dwa spotkałeś się z sytuacją gdy wywołałeś jakąś akcje i po pewnym czasie przyszło powiadomienie np. „Twój post został dodany”. Aby takie powiadomienie wyświetliło się na twoim ekranie potrzebne jest narzędzie (np. cboden/ratchet) i technologia (np. WebSocket). Połączenie tych rzeczy daje nam możliwość wymiany informacji w czasie rzeczywistym między backend i klientem. Do takich właśnie działań służy temat dzisiejszego wpisu tj. mercure.
Technologią odpowiedzialną za wymianę informacji między backendem a klientem odpowiada ich autorski protokół mercure a dokładniej server sent events. SSE to pół dupleksowy (ang. half duplex) strumień zdarzeń, w którym klient subskrybuje zdarzenia a serwer je wysyła. W przeciwieństwie do wcześniej wspomnianego protokołu WebSocket w SSE klient nie może przesyłać żadnych danych do backendu tj. nie mamy full duplex.
Obrazowo jak to wszystko działa najlepiej widać za pomocą schematu w dokumentacji

Mercure hub
Aby wszystko mogło działać poprawnie będziemy potrzebować huba który będzie zarządzał eventami wysyłanymi do klientów/klienta. Samo postawienie huba jest banalnie proste. Cały docker-compose (do użytku tylko deweloperskiego, konfiguracja produkcyjna jest inna) możesz zobaczyć poniżej.
version: '3.7'
services:
mercure:
image: dunglas/mercure
ports:
- "9000:80"
environment:
SERVER_NAME: ':80'
MERCURE_PUBLISHER_JWT_KEY: '!publisher_password!'
MERCURE_SUBSCRIBER_JWT_KEY: '!subscriber_password!'
MERCURE_EXTRA_DIRECTIVES: |
cors_origins http://127.0.0.1:8000
command: /usr/bin/caddy run -config /etc/caddy/Caddyfile.dev
volumes:
- mercure_data:/data
- mercure_config:/config
volumes:
mercure_data:
driver: local
mercure_config:
driver: local
Publikowanie wiadomości
Do publikacji wiadomości będzie Ci również potrzebna nam paczka przygotowana przez symfony symfony/mercure-bundle. Abyśmy mogli przeprowadzić testy i zobaczyć działanie mercure przyda się również jakiś kontroler, command etc. który będzie wysyłał wiadomości na front aplikacji. Przykład takiego kontrolera możesz znaleźć poniżej.
<?php
declare(strict_types=1);
namespace App\Controller;
use App\DTO\MercureDTO;
use App\Form\MercureFormType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/mercure')]
class MercureController extends AbstractController
{
#[Route('/publish', methods: ['GET'])]
public function index(Request $request): Response
{
$form = $this->createForm(MercureFormType::class);
$form->handleRequest($request);
return $this->render('mercure.html.twig', [
'form' => $form->createView(),
]);
}
#[Route('/publish', methods: ['POST'])]
public function publish(Request $request, HubInterface $hub): Response
{
$form = $this->createForm(MercureFormType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var MercureDTO $dto */
$dto = $form->getData();
$update = new Update(
'http://127.0.0.1:8000/product/' . $dto->productId,
json_encode(['status' => $dto->status->value], JSON_THROW_ON_ERROR)
);
$hub->publish($update);
}
return $this->forward(sprintf('%s::index', self::class), [
'request' => $request,
]);
}
}
Jak zapewne zauważyłeś wysyłka eventu jest bardzo prosta. Wystarczy pobrać z kontenera Symfony\Component\Mercure\HubInterface stworzyć obiekt klasy Symfony\Component\Mercure\Update a następnie przekazać go do metody publish. I tak naprawdę to tyle. Wiadomość trafiła do huba a następnie jest przekazywana do subskrybentów klucza który w naszym przypadku to http://127.0.0.1:8000/product/{ID PRODUKTU}.
Odbieranie danych
Tak samo jak w przypadku publikowania wiadomości, mercure jest banalny w odbieraniu ich. Przykład odbierania eventu możesz zobaczyć poniżej.
{% extends 'base.html.twig' %}
{% block body %}
<div id="event-data"></div>
<script>
const eventSource = new EventSource("{{ mercure('http://127.0.0.1:8000/product/' ~ id)|escape('js') }}");
eventSource.onmessage = event => {
const data = JSON.parse(event.data);
console.log(data);
document.getElementById('event-data').innerText = data.status;
}
</script>
{% endblock %}
Celem przykładu było wyświetlenie statusu w konsoli oraz w divie o id event-data. Jak sam widzisz to nic skomplikowanego, wystarczy że do funkcji twigowejmercure(dostarczanej w ramach paczki symfony/mercure-bundle) przekażemy klucz który chcemy subskrybować a rezultat funkcji tj. link do huba przekażemy doEventSource. Cała dalsza magia już odbywa się po stronie JS’a, my tylko musimy dodać obsługę onmessage.
Inne wsparcia
W ramach wprowadzenia do mercure nie będę przedstawiał Ci wszystkich możliwości integracji tego narzędzia z symfony bo sam wpis stałby się zbyt obszerny jak na pierwsze spotkanie ale chciałbym Ci przynajmniej wspomnieć o możliwościach jaka ta integracja daje.
M.in. możemy razem z api platform stworzyć powiadomienia każdorazowo gdy dojdzie do aktualizacji encji. Dzięki temu użytkownik np. administrator może wiedzieć o każdej nowej szansie sprzedaży, możemy aktualizować live dane klienta itp.
Kolejną bardzo fajną według mnie integracją jest możliwość wysyłania notyfikacji z symfony/notifier czyli wysyłanie różnych powiadomień do użytkownika np. o nowej promocji. Gdy odpowiednio skonfigurujemy notifier możemy za pomocą jednej notyfikacji wysłać event z mercure, sms, email itd.
O tym wszystkim możesz przeczytać [TUTAJ] i [TUTAJ].
Podsumowanie
Mam nadzieję że ten wpis pokazał Ci w jak prosty sposób możesz stworzyć komunikację live z twoim użytkownikiem. Poza samym działaniem mercura mam nadzieję że zapewne zauważyłeś jak bardzo mocno symfony wspiera integracje z mercure.
Zachęcam Cię również do przeczytania mojego wpisu na temat chain of responsibility który również może Ci się przydać wdrażając mercure w swoim projekcie [KLIKNIJ TUTAJ].
źródła
- https://symfony.com/doc/current/mercure.html
- https://mercure.rocks/docs
- https://mercure.rocks/docs/getting-started (schemat)
- https://api-platform.com/docs/core/mercure/