@@ -159,20 +159,19 @@ service, including controllers::
159159 namespace App\Controller;
160160
161161 use Symfony\Component\HttpFoundation\Response;
162- use Symfony\Component\Mercure\PublisherInterface ;
162+ use Symfony\Component\Mercure\HubInterface ;
163163 use Symfony\Component\Mercure\Update;
164164
165165 class PublishController
166166 {
167- public function __invoke(PublisherInterface $publisher ): Response
167+ public function __invoke(HubInterface $hub ): Response
168168 {
169169 $update = new Update(
170170 'http://example.com/books/1',
171171 json_encode(['status' => 'OutOfStock'])
172172 );
173173
174- // The Publisher service is an invokable object
175- $publisher($update);
174+ $hub->publish($update);
176175
177176 return new Response('published!');
178177 }
@@ -297,17 +296,14 @@ by using the ``AbstractController::addLink`` helper method::
297296 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
298297 use Symfony\Component\HttpFoundation\JsonResponse;
299298 use Symfony\Component\HttpFoundation\Request;
300- use Symfony\Component\WebLink\Link ;
299+ use Symfony\Component\Mercure\Discovery ;
301300
302301 class DiscoverController extends AbstractController
303302 {
304- public function __invoke(Request $request): JsonResponse
303+ public function __invoke(Request $request, Discovery $discovery ): JsonResponse
305304 {
306- // This parameter is automatically created by the MercureBundle
307- $hubUrl = $this->getParameter('mercure.default_hub');
308-
309305 // Link: <http://localhost:3000/.well-known/mercure>; rel="mercure"
310- $this ->addLink($request, new Link('mercure', $hubUrl) );
306+ $discovery ->addLink($request);
311307
312308 return $this->json([
313309 '@id' => '/books/1',
@@ -346,13 +342,13 @@ of the ``Update`` constructor to ``true``::
346342 // src/Controller/Publish.php
347343 namespace App\Controller;
348344
345+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
349346 use Symfony\Component\HttpFoundation\Response;
350- use Symfony\Component\Mercure\PublisherInterface;
351347 use Symfony\Component\Mercure\Update;
352348
353- class PublishController
349+ class PublishController extends AbstractController
354350 {
355- public function __invoke(PublisherInterface $publisher ): Response
351+ public function __invoke(HubInterface $hub ): Response
356352 {
357353 $update = new Update(
358354 'http://example.com/books/1',
@@ -362,7 +358,7 @@ of the ``Update`` constructor to ``true``::
362358
363359 // Publisher's JWT must contain this topic, a URI template it matches or * in mercure.publish or you'll get a 401
364360 // Subscriber's JWT must contain this topic, a URI template it matches or * in mercure.subscribe to receive the update
365- $publisher ($update);
361+ $hub->publish ($update);
366362
367363 return new Response('private update published!');
368364 }
@@ -412,44 +408,72 @@ To generate the JWT, we'll use the ``lcobucci/jwt`` library. Install it:
412408
413409 $ composer require lcobucci/jwt
414410
411+ And add your JWT secret to the configuration as follow ::
412+
413+ .. configuration-block ::
414+
415+ .. code-block :: yaml
416+
417+ # config/packages/mercure.yaml
418+ mercure :
419+ hubs :
420+ default :
421+ url : https://mercure-hub.example.com/.well-known/mercure
422+ jwt :
423+ secret : ' !ChangeMe!'
424+
425+ .. code-block :: xml
426+
427+ <!-- config/packages/mercure.xml -->
428+ <?xml version =" 1.0" encoding =" UTF-8" ?>
429+ <config >
430+ <hub
431+ name =" default"
432+ url =" https://mercure-hub.example.com/.well-known/mercure"
433+ >
434+ <jwt secret =" !ChangeMe!" />
435+ </hub >
436+ </config >
437+
438+ .. code-block :: php
439+
440+ // config/packages/mercure.php
441+ $container->loadFromExtension('mercure', [
442+ 'hubs' => [
443+ 'default' => [
444+ 'url' => 'https://mercure-hub.example.com/.well-known/mercure',
445+ 'jwt' => [
446+ 'secret' => '!ChangeMe!',
447+ ]
448+ ],
449+ ],
450+ ]);
451+
415452 And here is the controller::
416453
417454 // src/Controller/DiscoverController.php
418455 namespace App\Controller;
419456
420- use Lcobucci\JWT\Configuration;
421- use Lcobucci\JWT\Signer\Hmac\Sha256;
422- use Lcobucci\JWT\Signer\Key;
423457 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
424- use Symfony\Component\HttpFoundation\Cookie;
425458 use Symfony\Component\HttpFoundation\Request;
426459 use Symfony\Component\HttpFoundation\Response;
427- use Symfony\Component\WebLink\Link;
460+ use Symfony\Component\Mercure\Authorization;
461+ use Symfony\Component\Mercure\Discovery;
428462
429463 class DiscoverController extends AbstractController
430464 {
431- public function __invoke(Request $request): Response
465+ public function __invoke(Request $request, Discovery $discovery, Authorization $authorization ): Response
432466 {
433- $hubUrl = $this->getParameter('mercure.default_hub');
434- $this->addLink($request, new Link('mercure', $hubUrl));
435-
436- $key = Key\InMemory::plainText('mercure_secret_key'); // don't forget to set this parameter! Test value: !ChangeMe!
437- $configuration = Configuration::forSymmetricSigner(new Sha256(), $key);
438-
439- $token = $configuration->builder()
440- ->withClaim('mercure', ['subscribe' => ["http://example.com/books/1"]]) // can also be a URI template, or *
441- ->getToken($configuration->signer(), $configuration->signingKey())
442- ->toString();
443-
444- $response = $this->json(['@id' => '/demo/books/1', 'availability' => 'https://schema.org/InStock']);
445- $cookie = Cookie::create('mercureAuthorization')
446- ->withValue($token)
447- ->withPath('/.well-known/mercure')
448- ->withSecure(true)
449- ->withHttpOnly(true)
450- ->withSameSite('strict')
451- ;
452- $response->headers->setCookie($cookie);
467+ $discovery->addLink($request);
468+
469+ $response = new JsonResponse([
470+ '@id' => '/demo/books/1',
471+ 'availability' => 'https://schema.org/InStock'
472+ ]);
473+
474+ $response->headers->setCookie(
475+ $authorization->createCookie($request, ["http://example.com/books/1"])
476+ );
453477
454478 return $response;
455479 }
@@ -464,15 +488,17 @@ Programmatically Generating The JWT Used to Publish
464488---------------------------------------------------
465489
466490Instead of directly storing a JWT in the configuration,
467- you can create a service that will return the token used by
468- the ``Publisher `` object::
491+ you can create a token provider that will return the token used by
492+ the ``HubInterface `` object::
469493
470- // src/Mercure/MyJwtProvider .php
494+ // src/Mercure/MyTokenProvider .php
471495 namespace App\Mercure;
472496
473- final class MyJwtProvider
497+ use Symfony\Component\Mercure\JWT\TokenProviderInterface;
498+
499+ final class MyTokenProvider implements TokenProviderInterface
474500 {
475- public function __invoke (): string
501+ public function getToken (): string
476502 {
477503 return 'the-JWT';
478504 }
@@ -489,7 +515,8 @@ Then, reference this service in the bundle configuration:
489515 hubs :
490516 default :
491517 url : https://mercure-hub.example.com/.well-known/mercure
492- jwt_provider : App\Mercure\MyJwtProvider
518+ jwt :
519+ provider : App\Mercure\MyTokenProvider
493520
494521 .. code-block :: xml
495522
@@ -499,8 +526,9 @@ Then, reference this service in the bundle configuration:
499526 <hub
500527 name =" default"
501528 url =" https://mercure-hub.example.com/.well-known/mercure"
502- jwt-provider =" App\Mercure\MyJwtProvider"
503- />
529+ >
530+ <jwt provider =" App\Mercure\MyTokenProvider" />
531+ </hub >
504532 </config >
505533
506534 .. code-block :: php
@@ -512,7 +540,9 @@ Then, reference this service in the bundle configuration:
512540 'hubs' => [
513541 'default' => [
514542 'url' => 'https://mercure-hub.example.com/.well-known/mercure',
515- 'jwt_provider' => MyJwtProvider::class,
543+ 'jwt' => [
544+ 'provider' => MyJwtProvider::class,
545+ ]
516546 ],
517547 ],
518548 ]);
@@ -573,29 +603,59 @@ its Mercure support.
573603Testing
574604--------
575605
576- During functional testing there is no need to send updates to Mercure. They will
577- be handled by a stub publisher::
606+ During unit testing there is not need to send updates to Mercure.
578607
579- // tests/Functional/Fixtures/PublisherStub.php
580- namespace App\Tests\Functional\Fixtures;
608+ You can instead make use of the `MockHub `::
609+
610+ // tests/Functional/.php
611+ namespace App\Tests\Unit\Controller;
581612
582- use Symfony\Component\Mercure\PublisherInterface;
613+ use App\Controller\MessageController;
614+ use Symfony\Component\Mercure\HubInterface;
615+ use Symfony\Component\Mercure\JWT\StaticTokenProvider;
616+ use Symfony\Component\Mercure\MockHub;
583617 use Symfony\Component\Mercure\Update;
584618
585- class PublisherStub implements PublisherInterface
619+ class MessageControllerTest extends TestCase
586620 {
587621 public function __invoke(Update $update): string
588622 {
589- return '';
623+ $hub = new MockHub('default', 'https://internal/.well-known/mercure', new StaticTokenProvider('foo'), function(Update $update): string {
624+ // $this->assertTrue($update->isPrivate());
625+
626+ return 'id';
627+ });
628+
629+ $controller = new MessageController($hub);
630+
631+ ...
632+ }
633+ }
634+
635+ During functional testing you can instead decorate the Hub::
636+
637+ // tests/Functional/Fixtures/HubStub.php
638+ namespace App\Tests\Functional\Fixtures;
639+
640+ use Symfony\Component\Mercure\HubInterface;
641+ use Symfony\Component\Mercure\Update;
642+
643+ class HubStub implements HubInterface
644+ {
645+ public function publish(Update $update): string
646+ {
647+ return 'id';
590648 }
649+
650+ // implement rest of HubInterface methods here
591651 }
592652
593- PublisherStub decorates the default publisher service so no updates are actually
594- sent. Here is the PublisherStub implementation::
653+ HubStub decorates the default hub service so no updates are actually
654+ sent. Here is the HubStub implementation::
595655
596656 # config/services_test.yaml
597- App\Tests\Functional\Fixtures\PublisherStub :
598- decorates: mercure.hub.default.publisher
657+ App\Tests\Functional\Fixtures\HubStub :
658+ decorates: mercure.hub.default
599659
600660
601661Debugging
0 commit comments