Skip to content

Commit 5f5b41b

Browse files
committed
Merge branch '4.4' into 5.2
* 4.4: update mercure documentation
2 parents d564173 + 8081837 commit 5f5b41b

File tree

1 file changed

+117
-62
lines changed

1 file changed

+117
-62
lines changed

mercure.rst

+117-62
Original file line numberDiff line numberDiff line change
@@ -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
}
@@ -406,50 +402,73 @@ This cookie will be automatically sent by the web browser when connecting to the
406402
Then, the Hub will verify the validity of the provided JWT, and extract the topic selectors
407403
from it.
408404

409-
To generate the JWT, we'll use the ``lcobucci/jwt`` library. Install it:
405+
add your JWT secret to the configuration as follow ::
410406

411-
.. code-block:: terminal
407+
.. configuration-block::
408+
409+
.. code-block:: yaml
410+
411+
# config/packages/mercure.yaml
412+
mercure:
413+
hubs:
414+
default:
415+
url: https://mercure-hub.example.com/.well-known/mercure
416+
jwt:
417+
secret: '!ChangeMe!'
412418
413-
$ composer require lcobucci/jwt
419+
.. code-block:: xml
420+
421+
<!-- config/packages/mercure.xml -->
422+
<?xml version="1.0" encoding="UTF-8" ?>
423+
<config>
424+
<hub
425+
name="default"
426+
url="https://mercure-hub.example.com/.well-known/mercure"
427+
>
428+
<jwt secret="!ChangeMe!"/>
429+
</hub>
430+
</config>
431+
432+
.. code-block:: php
433+
434+
// config/packages/mercure.php
435+
$container->loadFromExtension('mercure', [
436+
'hubs' => [
437+
'default' => [
438+
'url' => 'https://mercure-hub.example.com/.well-known/mercure',
439+
'jwt' => [
440+
'secret' => '!ChangeMe!',
441+
]
442+
],
443+
],
444+
]);
414445
415446
And here is the controller::
416447

417448
// src/Controller/DiscoverController.php
418449
namespace App\Controller;
419450

420-
use Lcobucci\JWT\Configuration;
421-
use Lcobucci\JWT\Signer\Hmac\Sha256;
422-
use Lcobucci\JWT\Signer\Key;
423451
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
424452
use Symfony\Component\HttpFoundation\Cookie;
425453
use Symfony\Component\HttpFoundation\Request;
426454
use Symfony\Component\HttpFoundation\Response;
427-
use Symfony\Component\WebLink\Link;
455+
use Symfony\Component\Mercure\Authorization;
456+
use Symfony\Component\Mercure\Discovery;
428457

429458
class DiscoverController extends AbstractController
430459
{
431-
public function __invoke(Request $request): Response
460+
public function __invoke(Request $request, Discovery $discovery, Authorization $authorization): Response
432461
{
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);
462+
$discovery->addLink($request);
463+
464+
$response = new JsonResponse([
465+
'@id' => '/demo/books/1',
466+
'availability' => 'https://schema.org/InStock'
467+
]);
468+
469+
$response->headers->setCookie(
470+
$authorization->createCookie($request, ["http://example.com/books/1"])
471+
);
453472

454473
return $response;
455474
}
@@ -464,15 +483,17 @@ Programmatically Generating The JWT Used to Publish
464483
---------------------------------------------------
465484

466485
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::
486+
you can create a token provider that will return the token used by
487+
the ``HubInterface`` object::
469488

470-
// src/Mercure/MyJwtProvider.php
489+
// src/Mercure/MyTokenProvider.php
471490
namespace App\Mercure;
472491

473-
final class MyJwtProvider
492+
use Symfony\Component\Mercure\JWT\TokenProviderInterface;
493+
494+
final class MyTokenProvider implements TokenProviderInterface
474495
{
475-
public function __invoke(): string
496+
public function getToken(): string
476497
{
477498
return 'the-JWT';
478499
}
@@ -489,7 +510,8 @@ Then, reference this service in the bundle configuration:
489510
hubs:
490511
default:
491512
url: https://mercure-hub.example.com/.well-known/mercure
492-
jwt_provider: App\Mercure\MyJwtProvider
513+
jwt:
514+
provider: App\Mercure\MyTokenProvider
493515
494516
.. code-block:: xml
495517
@@ -499,8 +521,9 @@ Then, reference this service in the bundle configuration:
499521
<hub
500522
name="default"
501523
url="https://mercure-hub.example.com/.well-known/mercure"
502-
jwt-provider="App\Mercure\MyJwtProvider"
503-
/>
524+
>
525+
<jwt provider="App\Mercure\MyTokenProvider"/>
526+
</hub>
504527
</config>
505528
506529
.. code-block:: php
@@ -512,7 +535,9 @@ Then, reference this service in the bundle configuration:
512535
'hubs' => [
513536
'default' => [
514537
'url' => 'https://mercure-hub.example.com/.well-known/mercure',
515-
'jwt_provider' => MyJwtProvider::class,
538+
'jwt' => [
539+
'provider' => MyJwtProvider::class,
540+
]
516541
],
517542
],
518543
]);
@@ -573,29 +598,59 @@ its Mercure support.
573598
Testing
574599
--------
575600

576-
During functional testing there is no need to send updates to Mercure. They will
577-
be handled by a stub publisher::
601+
During unit testing there is not need to send updates to Mercure.
602+
603+
You can instead make use of the `MockHub`::
578604

579-
// tests/Functional/Fixtures/PublisherStub.php
605+
// tests/Functional/.php
606+
namespace App\Tests\Unit\Controller;
607+
608+
use App\Controller\MessageController;
609+
use Symfony\Component\Mercure\HubInterface;
610+
use Symfony\Component\Mercure\JWT\StaticTokenProvider;
611+
use Symfony\Component\Mercure\MockHub;
612+
use Symfony\Component\Mercure\Update;
613+
614+
class MessageControllerTest extends TestCase
615+
{
616+
public function testPublishing()
617+
{
618+
$hub = new MockHub('default', 'https://internal/.well-known/mercure', new StaticTokenProvider('foo'), function(Update $update): string {
619+
// $this->assertTrue($update->isPrivate());
620+
621+
return 'id';
622+
});
623+
624+
$controller = new MessageController($hub);
625+
626+
...
627+
}
628+
}
629+
630+
During functional testing you can instead decorate the Hub::
631+
632+
// tests/Functional/Fixtures/HubStub.php
580633
namespace App\Tests\Functional\Fixtures;
581634

582-
use Symfony\Component\Mercure\PublisherInterface;
635+
use Symfony\Component\Mercure\HubInterface;
583636
use Symfony\Component\Mercure\Update;
584637

585-
class PublisherStub implements PublisherInterface
638+
class HubStub implements HubInterface
586639
{
587-
public function __invoke(Update $update): string
640+
public function publish(Update $update): string
588641
{
589-
return '';
642+
return 'id';
590643
}
644+
645+
// implement rest of HubInterface methods here
591646
}
592647

593-
PublisherStub decorates the default publisher service so no updates are actually
594-
sent. Here is the PublisherStub implementation::
648+
HubStub decorates the default hub service so no updates are actually
649+
sent. Here is the HubStub implementation::
595650

596651
# config/services_test.yaml
597-
App\Tests\Functional\Fixtures\PublisherStub:
598-
decorates: mercure.hub.default.publisher
652+
App\Tests\Functional\Fixtures\HubStub:
653+
decorates: mercure.hub.default
599654

600655

601656
Debugging

0 commit comments

Comments
 (0)