@@ -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,72 @@ 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
+ <
B41A
div class="diff-text-inner">
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
+
415
452
And here is the controller::
416
453
417
454
// src/Controller/DiscoverController.php
418
455
namespace App\Controller;
419
456
420
- use Lcobucci\JWT\Configuration;
421
- use Lcobucci\JWT\Signer\Hmac\Sha256;
422
- use Lcobucci\JWT\Signer\Key;
423
457
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
424
- use Symfony\Component\HttpFoundation\Cookie;
425
458
use Symfony\Component\HttpFoundation\Request;
426
459
use Symfony\Component\HttpFoundation\Response;
427
- use Symfony\Component\WebLink\Link;
460
+ use Symfony\Component\Mercure\Authorization;
461
+ use Symfony\Component\Mercure\Discovery;
428
462
429
463
class DiscoverController extends AbstractController
430
464
{
431
- public function __invoke(Request $request): Response
465
+ public function __invoke(Request $request, Discovery $discovery, Authorization $authorization ): Response
432
466
{
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
+ );
453
477
454
478
return $response;
455
479
}
@@ -464,15 +488,17 @@ Programmatically Generating The JWT Used to Publish
464
488
---------------------------------------------------
465
489
466
490
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::
491
+ you can create a token provider that will return the token used by
492
+ the ``HubInterface `` ob
EF5E
ject::
469
493
470
- // src/Mercure/MyJwtProvider .php
494
+ // src/Mercure/MyTokenProvider .php
471
495
namespace App\Mercure;
472
496
473
- final class MyJwtProvider
497
+ use Symfony\Component\Mercure\JWT\TokenProviderInterface;
498
+
499
+ final class MyTokenProvider implements TokenProviderInterface
474
500
{
475
- public function __invoke (): string
501
+ public function getToken (): string
476
502
{
477
503
return 'the-JWT';
478
504
}
@@ -489,7 +515,8 @@ Then, reference this service in the bundle configuration:
489
515
hubs :
490
516
default :
491
517
url : https://mercure-hub.example.com/.well-known/mercure
492
- jwt_provider : App\Mercure\MyJwtProvider
518
+ jwt :
519
+ provider : App\Mercure\MyTokenProvider
493
520
494
521
.. code-block :: xml
495
522
@@ -499,8 +526,9 @@ Then, reference this service in the bundle configuration:
499
526
<hub
500
527
name =" default"
501
528
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 >
504
532
</config >
505
533
506
534
.. code-block :: php
@@ -512,7 +540,9 @@ Then, reference this service in the bundle configuration:
512
540
'hubs' => [
513
541
'default' => [
514
542
'url' => 'https://mercure-hub.example.com/.well-known/mercure',
515
- 'jwt_provider' => MyJwtProvider::class,
543
+ 'jwt' => [
544
+ 'provider' => MyJwtProvider::class,
545
+ ]
516
546
],
517
547
],
518
548
]);
@@ -573,29 +603,59 @@ its Mercure support.
573
603
Testing
574
604
--------
575
605
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.
578
607
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;
581
612
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;
583
617
use Symfony\Component\Mercure\Update;
584
618
585
- class PublisherStub implements PublisherInterface
619
+ class MessageControllerTest extends TestCase
586
620
{
587
621
public function __invoke(Update $update): string
588
622
{
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';
590
648
}
649
+
650
+ // implement rest of HubInterface methods here
591
651
}
592
652
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::
595
655
596
656
# 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
599
659
600
660
601
661
Debugging
0 commit comments