@@ -159,20 +159,19 @@ service, including controllers::
159
159
namespace App\Controller;
160
160
161
161
use Symfony\Component\HttpFoundation\Response;
162
- use Symfony\Component\Mercure\PublisherInterface ;
162
+ use Symfony\Component\Mercure\HubInterface ;
163
163
use Symfony\Component\Mercure\Update;
164
164
165
165
class PublishController
166
166
{
167
- public function __invoke(PublisherInterface $publisher ): Response
167
+ public function __invoke(HubInterface $hub ): Response
168
168
{
169
169
$update = new Update(
170
170
'http://example.com/books/1',
171
171
json_encode(['status' => 'OutOfStock'])
172
172
);
173
173
174
- // The Publisher service is an invokable object
175
- $publisher($update);
174
+ $hub->publish($update);
176
175
177
176
return new Response('published!');
178
177
}
@@ -297,17 +296,14 @@ by using the ``AbstractController::addLink`` helper method::
297
296
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
298
297
use Symfony\Component\HttpFoundation\JsonResponse;
299
298
use Symfony\Component\HttpFoundation\Request;
300
- use Symfony\Component\WebLink\Link ;
299
+ use Symfony\Component\Mercure\Discovery ;
301
300
302
301
class DiscoverController extends AbstractController
303
302
{
304
- public function __invoke(Request $request): JsonResponse
303
+ public function __invoke(Request $request, Discovery $discovery ): JsonResponse
305
304
{
306
- // This parameter is automatically created by the MercureBundle
307
- $hubUrl = $this->getParameter('mercure.default_hub');
308
-
309
305
// Link: <http://localhost:3000/.well-known/mercure>; rel="mercure"
310
- $this ->addLink($request, new Link('mercure', $hubUrl) );
306
+ $discovery ->addLink($request);
311
307
312
308
return $this->json([
313
309
'@id' => '/books/1',
@@ -346,13 +342,13 @@ of the ``Update`` constructor to ``true``::
346
342
// src/Controller/Publish.php
347
343
namespace App\Controller;
348
344
345
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
349
346
use Symfony\Component\HttpFoundation\Response;
350
- use Symfony\Component\Mercure\PublisherInterface;
351
347
use Symfony\Component\Mercure\Update;
352
348
353
- class PublishController
349
+ class PublishController extends AbstractController
354
350
{
355
- public function __invoke(PublisherInterface $publisher ): Response
351
+ public function __invoke(HubInterface $hub ): Response
356
352
{
357
353
$update = new Update(
358
354
'http://example.com/books/1',
@@ -362,7 +358,7 @@ of the ``Update`` constructor to ``true``::
362
358
363
359
// Publisher's JWT must contain this topic, a URI template it matches or * in mercure.publish or you'll get a 401
364
360
// 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);
366
362
367
363
return new Response('private update published!');
368
364
}
@@ -412,44 +408,73 @@ To generate the JWT, we'll use the ``lcobucci/jwt`` library. Install it:
412
408
413
409
$ composer require lcobucci/jwt
414
410
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
+
442
+ $container->loadFromExtension('mercure', [
443
+ 'hubs' => [
444
+ 'default' => [
445
+ 'url' => 'https://mercure-hub.example.com/.well-known/mercure',
446
+ 'jwt' => [
447
+ 'secret' => '!ChangeMe!',
448
+ ]
449
+ ],
450
+ ],
451
+ ]);
452
+
415
453
And here is the controller::
416
454
417
455
// src/Controller/DiscoverController.php
418
456
namespace App\Controller;
419
457
420
- use Lcobucci\JWT\Configuration;
421
- use Lcobucci\JWT\Signer\Hmac\Sha256;
422
- use Lcobucci\JWT\Signer\Key;
423
458
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
424
- use Symfony\Component\HttpFoundation\Cookie;
425
459
use Symfony\Component\HttpFoundation\Request;
426
460
use Symfony\Component\HttpFoundation\Response;
427
- use Symfony\Component\WebLink\Link;
461
+ use Symfony\Component\Mercure\Authorization;
462
+ use Symfony\Component\Mercure\Discovery;
428
463
429
464
class DiscoverController extends AbstractController
430
465
{
431
- public function __invoke(Request $request): Response
466
+ public function __invoke(Request $request, Discovery $discovery, Authorization $authorization ): Response
432
467
{
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);
468
+ $discovery->addLink($request);
469
+
470
+ $response = new JsonResponse([
471
+ '@id' => '/demo/books/1',
472
+ 'availability' => 'https://schema.org/InStock'
473
+ ]);
474
+
475
+ $response->headers->setCookie(
476
+ $authorization->createCookie($request, ["http://example.com/books/1"])
477
+ );
453
478
454
479
return $response;
455
480
}
@@ -464,15 +489,17 @@ Programmatically Generating The JWT Used to Publish
464
489
---------------------------------------------------
465
490
466
491
Instead 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::
492
+ you can create a token provider that will return the token used by
493
+ the ``HubInterface `` object::
469
494
470
- // src/Mercure/MyJwtProvider .php
495
+ // src/Mercure/MyTokenProvider .php
471
496
namespace App\Mercure;
472
497
473
- final class MyJwtProvider
498
+ use Symfony\Component\Mercure\JWT\TokenProviderInterface;
499
+
500
+ final class MyTokenProvider implements TokenProviderInterface
474
501
{
475
- public function __invoke (): string
502
+ public function getToken (): string
476
503
{
477
504
return 'the-JWT';
478
505
}
@@ -489,7 +516,8 @@ Then, reference this service in the bundle configuration:
489
516
hubs :
490
517
default :
491
518
url : https://mercure-hub.example.com/.well-known/mercure
492
- jwt_provider : App\Mercure\MyJwtProvider
519
+ jwt :
520
+ provider : App\Mercure\MyTokenProvider
493
521
494
522
.. code-block :: xml
495
523
@@ -499,8 +527,9 @@ Then, reference this service in the bundle configuration:
499
527
<hub
500
528
name =" default"
501
529
url =" https://mercure-hub.example.com/.well-known/mercure"
502
- jwt-provider =" App\Mercure\MyJwtProvider"
503
- />
530
+ >
531
+ <jwt provider =" App\Mercure\MyTokenProvider" />
532
+ </hub >
504
533
</config >
505
534
506
535
.. code-block :: php
@@ -512,7 +541,9 @@ Then, reference this service in the bundle configuration:
512
541
'hubs' => [
513
542
'default' => [
514
543
'url' => 'https://mercure-hub.example.com/.well-known/mercure',
515
- 'jwt_provider' => MyJwtProvider::class,
544
+ 'jwt' => [
545
+ 'provider' => MyJwtProvider::class,
546
+ ]
516
547
],
517
548
],
518
549
]);
@@ -573,29 +604,59 @@ its Mercure support.
573
604
Testing
574
605
--------
575
606
576
- During functional testing there is no need to send updates to Mercure. They will
577
- be handled by a stub publisher::
607
+ During unit testing there is not need to send updates to Mercure.
578
608
579
- // tests/Functional/Fixtures/PublisherStub.php
580
- namespace App\Tests\Functional\Fixtures;
609
+ You can instead make use of the `MockHub `::
610
+
611
+ // tests/Functional/.php
612
+ namespace App\Tests\Unit\Controller;
581
613
582
- use Symfony\Component\Mercure\PublisherInterface;
614
+ use App\Controller\MessageController;
615
+ use Symfony\Component\Mercure\JWT\StaticTokenProvider;
616
+ use Symfony\Component\Mercure\MockHub;
617
+ use Symfony\Component\Mercure\HubInterface;
583
618
use Symfony\Component\Mercure\Update;
584
619
585
- class PublisherStub implements PublisherInterface
620
+ class MessageControllerTest extends TestCase
586
621
{
587
622
public function __invoke(Update $update): string
588
623
{
589
- return '';
624
+ $hub = new MockHub('default', 'https://internal/.well-known/mercure', new StaticTokenProvider('foo'), function(Update $update): string {
625
+ // $this->assertTrue($update->isPrivate());
626
+
627
+ return 'id';
628
+ });
629
+
630
+ $controller = new MessageController($hub);
631
+
632
+ ...
633
+ }
634
+ }
635
+
636
+ During functional testing you can instead decorate the Hub::
637
+
638
+ // tests/Functional/Fixtures/HubStub.php
639
+ namespace App\Tests\Functional\Fixtures;
640
+
641
+ use Symfony\Component\Mercure\HubInterface;
642
+ use Symfony\Component\Mercure\Update;
643
+
644
+ class HubStub implements HubInterface
645
+ {
646
+ public function publish(Update $update): string
647
+ {
648
+ return 'id';
590
649
}
650
+
651
+ // implement rest of HubInterface methods here
591
652
}
592
653
593
- PublisherStub decorates the default publisher service so no updates are actually
594
- sent. Here is the PublisherStub implementation::
654
+ HubStub decorates the default hub service so no updates are actually
655
+ sent. Here is the HubStub implementation::
595
656
596
657
# config/services_test.yaml
597
- App\Tests\Functional\Fixtures\PublisherStub :
598
- decorates: mercure.hub.default.publisher
658
+ App\Tests\Functional\Fixtures\HubStub :
659
+ decorates: mercure.hub.default
599
660
600
661
601
662
Debugging
0 commit comments