From d3306fdc9278d91982cf3acf754a473dd0fa20bc Mon Sep 17 00:00:00 2001 From: azjezz Date: Thu, 25 Mar 2021 15:03:15 +0100 Subject: [PATCH 1/2] add support for symfony/mercure:^0.5 --- .../FrameworkExtension.php | 17 +++- .../Bridge/Mercure/MercureTransport.php | 27 +++-- .../Mercure/MercureTransportFactory.php | 27 +++-- .../Mercure/Tests/MercureTransportTest.php | 99 +++++++++++-------- .../Notifier/Bridge/Mercure/composer.json | 2 +- 5 files changed, 107 insertions(+), 65 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 4b3e4427a0..5b89dd4f3e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -95,6 +95,7 @@ use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory; use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Mercure\HubRegistry; use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory; use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory; use Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory; @@ -2368,11 +2369,17 @@ class FrameworkExtension extends Extension } } - if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages)) { - $container->getDefinition($classToServices[MercureTransportFactory::class]) - ->replaceArgument('$publisherLocator', new ServiceLocatorArgument(new TaggedIteratorArgument('mercure.publisher', null, null, true))); - } elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages)) { - $container->removeDefinition($classToServices[MercureTransportFactory::class]); + if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages)) { + if (ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages)) { + $definition = $container->getDefinition($classToServices[MercureTransportFactory::class]); + if (ContainerBuilder::willBeAvailable('symfony/mercure', HubRegistry::class, $parentPackages)) { + $definition->replaceArgument('$registry', new Reference(HubRegistry::class)); + } else { + $definition->replaceArgument('$registry', new ServiceLocatorArgument(new TaggedIteratorArgument('mercure.publisher', null, null, true))); + } + } else { + $container->removeDefinition($classToServices[MercureTransportFactory::class]); + } } if (isset($config['admin_recipients'])) { diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransport.php b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransport.php index bbdb663f4a..2c9bd5c42d 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransport.php @@ -11,6 +11,10 @@ namespace Symfony\Component\Notifier\Bridge\Mercure; +use Symfony\Component\Mercure\Exception\InvalidArgumentException as MercureInvalidArgumentException; +use Symfony\Component\Mercure\Exception\RuntimeException as MercureRuntimeException; +use Symfony\Component\Mercure\HubInterface; +use Symfony\Component\Mercure\HubRegistry; use Symfony\Component\Mercure\PublisherInterface; use Symfony\Component\Mercure\Update; use Symfony\Component\Notifier\Exception\InvalidArgumentException; @@ -32,21 +36,22 @@ use Symfony\Contracts\HttpClient\HttpClientInterface; */ final class MercureTransport extends AbstractTransport { - private $publisher; - private $publisherId; + private $hub; + private $hubId; private $topics; /** + * @param HubInterface $hub * @param string|string[]|null $topics */ - public function __construct(PublisherInterface $publisher, string $publisherId, $topics = null, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null) + public function __construct($hub, string $hubId, $topics = null, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null) { if (null !== $topics && !\is_array($topics) && !\is_string($topics)) { throw new \TypeError(sprintf('"%s()" expects parameter 3 to be an array of strings, a string or null, "%s" given.', __METHOD__, get_debug_type($topics))); } - $this->publisher = $publisher; - $this->publisherId = $publisherId; + $this->hub = $hub; + $this->hubId = $hubId; $this->topics = $topics ?? 'https://symfony.com/notifier'; parent::__construct($client, $dispatcher); @@ -54,7 +59,7 @@ final class MercureTransport extends AbstractTransport public function __toString(): string { - return sprintf('mercure://%s?%s', $this->publisherId, http_build_query(['topic' => $this->topics])); + return sprintf('mercure://%s?%s', $this->hubId, http_build_query(['topic' => $this->topics])); } public function supports(MessageInterface $message): bool @@ -87,7 +92,11 @@ final class MercureTransport extends AbstractTransport ]), $options->isPrivate(), $options->getId(), $options->getType(), $options->getRetry()); try { - $messageId = ($this->publisher)($update); + if ($this->hub instanceof HubInterface) { + $messageId = $this->hub->publish($update); + } else { + $messageId = ($this->hub)($update); + } $sentMessage = new SentMessage($message, (string) $this); $sentMessage->setMessageId($messageId); @@ -95,9 +104,9 @@ final class MercureTransport extends AbstractTransport return $sentMessage; } catch (HttpExceptionInterface $e) { throw new TransportException('Unable to post the Mercure message: '.$e->getResponse()->getContent(false), $e->getResponse(), $e->getCode(), $e); - } catch (ExceptionInterface $e) { + } catch (ExceptionInterface | MercureRuntimeException $e) { throw new RuntimeException('Unable to post the Mercure message: '.$e->getMessage(), $e->getCode(), $e); - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException | MercureInvalidArgumentException $e) { throw new InvalidArgumentException('Unable to post the Mercure message: '.$e->getMessage(), $e->getCode(), $e); } } diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransportFactory.php index a7411479d1..13fd1aba82 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransportFactory.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Notifier\Bridge\Mercure; use Symfony\Bundle\MercureBundle\MercureBundle; +use Symfony\Component\Mercure\HubRegistry; use Symfony\Component\Mercure\PublisherInterface; use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; @@ -25,16 +26,16 @@ use Symfony\Contracts\Service\ServiceProviderInterface; */ final class MercureTransportFactory extends AbstractTransportFactory { - private $publisherLocator; + private $registry; /** - * @param ServiceProviderInterface $publisherLocator A container that holds {@see PublisherInterface} instances + * @param HubRegistry $registry */ - public function __construct(ServiceProviderInterface $publisherLocator) + public function __construct(HubRegistry $registry) { parent::__construct(); - $this->publisherLocator = $publisherLocator; + $this->registry = $registry; } /** @@ -46,18 +47,24 @@ final class MercureTransportFactory extends AbstractTransportFactory throw new UnsupportedSchemeException($dsn, 'mercure', $this->getSupportedSchemes()); } - $publisherId = $dsn->getHost(); - if (!$this->publisherLocator->has($publisherId)) { + $hubId = $dsn->getHost(); + $topic = $dsn->getOption('topic'); + + if ($this->registry instanceof HubRegistry) { + $hub = $this->registry->getHub($hubId); + + return new MercureTransport($hub, $hubId, $topic); + } + + if (!$this->publisherLocator->has($hubId)) { if (!class_exists(MercureBundle::class) && !$this->publisherLocator->getProvidedServices()) { throw new LogicException('No publishers found. Did you forget to install the MercureBundle? Try running "composer require symfony/mercure-bundle".'); } - throw new LogicException(sprintf('"%s" not found. Did you mean one of: %s?', $publisherId, implode(', ', array_keys($this->publisherLocator->getProvidedServices())))); + throw new LogicException(sprintf('"%s" not found. Did you mean one of: %s?', $hubId, implode(', ', array_keys($this->publisherLocator->getProvidedServices())))); } - $topic = $dsn->getOption('topic'); - - return new MercureTransport($this->publisherLocator->get($publisherId), $publisherId, $topic); + return new MercureTransport($this->publisherLocator->get($hubId), $hubId, $topic); } protected function getSupportedSchemes(): array diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportTest.php index 320b422fc4..79e4a80678 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportTest.php @@ -12,6 +12,10 @@ namespace Symfony\Component\Notifier\Bridge\Mercure\Tests; use Symfony\Component\HttpClient\Exception\TransportException as HttpClientTransportException; +use Symfony\Component\Mercure\Exception\RuntimeException as MercureRuntimeException; +use Symfony\Component\Mercure\HubInterface; +use Symfony\Component\Mercure\Jwt\StaticTokenProvider; +use Symfony\Component\Mercure\MockHub; use Symfony\Component\Mercure\PublisherInterface; use Symfony\Component\Mercure\Update; use Symfony\Component\Notifier\Bridge\Mercure\MercureOptions; @@ -36,11 +40,11 @@ use TypeError; */ final class MercureTransportTest extends TransportTestCase { - public function createTransport(?HttpClientInterface $client = null, ?PublisherInterface $publisher = null, string $publisherId = 'publisherId', $topics = null): TransportInterface + public function createTransport(?HttpClientInterface $client = null, ?HubInterface $hub = null, string $hubId = 'publisherId', $topics = null): TransportInterface { - $publisher = $publisher ?? $this->createMock(PublisherInterface::class); + $hub = $hub ?? $this->createMock(HubInterface::class); - return new MercureTransport($publisher, $publisherId, $topics); + return new MercureTransport($hub, $hubId, $topics); } public function toStringProvider(): iterable @@ -90,87 +94,102 @@ final class MercureTransportTest extends TransportTestCase public function testSendWithTransportFailureThrows() { - $publisher = $this->createMock(PublisherInterface::class); - $publisher->method('__invoke')->willThrowException(new HttpClientTransportException('Cannot connect to mercure')); + $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(): void { + throw new MercureRuntimeException('Cannot connect to mercure'); + }); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Unable to post the Mercure message: Cannot connect to mercure'); - $this->createTransport(null, $publisher)->send(new ChatMessage('subject')); + $this->createTransport(null, $hub)->send(new ChatMessage('subject')); } public function testSendWithWrongResponseThrows() { - $response = $this->createMock(ResponseInterface::class); - $response->method('getContent')->willReturn('Service Unavailable'); + $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(): void { + $response = $this->createMock(ResponseInterface::class); + $response->method('getContent')->willReturn('Service Unavailable'); - $httpException = $this->createMock(ServerExceptionInterface::class); - $httpException->method('getResponse')->willReturn($response); + $httpException = $this->createMock(ServerExceptionInterface::class); + $httpException->method('getResponse')->willReturn($response); - $publisher = $this->createMock(PublisherInterface::class); - $publisher->method('__invoke')->willThrowException($httpException); + throw $httpException; + }); $this->expectException(TransportException::class); $this->expectExceptionMessage('Unable to post the Mercure message: Service Unavailable'); - $this->createTransport(null, $publisher)->send(new ChatMessage('subject')); + $this->createTransport(null, $hub)->send(new ChatMessage('subject')); } public function testSendWithWrongTokenThrows() { - $publisher = $this->createMock(PublisherInterface::class); - $publisher->method('__invoke')->willThrowException(new \InvalidArgumentException('The provided JWT is not valid')); + $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(): void { + throw new \InvalidArgumentException('The provided JWT is not valid'); + }); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Unable to post the Mercure message: The provided JWT is not valid'); - $this->createTransport(null, $publisher)->send(new ChatMessage('subject')); + $this->createTransport(null, $hub)->send(new ChatMessage('subject')); } public function testSendWithMercureOptions() { - $publisher = $this->createMock(PublisherInterface::class); - $publisher - ->expects($this->once()) - ->method('__invoke') - ->with(new Update(['/topic/1', '/topic/2'], '{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', true, 'id', 'type', 1)) - ; + $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(Update $update): string { + $this->assertSame(['/topic/1', '/topic/2'], $update->getTopics()); + $this->assertSame('{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', $update->getData()); + $this->assertSame('id', $update->getId()); + $this->assertSame('type', $update->getType()); + $this->assertSame('1', $update->getRetry()); + $this->assertTrue($update->isPrivate()); - $this->createTransport(null, $publisher)->send(new ChatMessage('subject', new MercureOptions(['/topic/1', '/topic/2'], true, 'id', 'type', 1))); + return 'id'; + }); + + $this->createTransport(null, $hub)->send(new ChatMessage('subject', new MercureOptions(['/topic/1', '/topic/2'], true, 'id', 'type', 1))); } public function testSendWithMercureOptionsButWithoutOptionTopic() { - $publisher = $this->createMock(PublisherInterface::class); - $publisher - ->expects($this->once()) - ->method('__invoke') - ->with(new Update(['https://symfony.com/notifier'], '{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', true, 'id', 'type', 1)) - ; + $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(Update $update): string { + $this->assertSame(['https://symfony.com/notifier'], $update->getTopics()); + $this->assertSame('{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', $update->getData()); + $this->assertSame('id', $update->getId()); + $this->assertSame('type', $update->getType()); + $this->assertSame('1', $update->getRetry()); + $this->assertTrue($update->isPrivate()); - $this->createTransport(null, $publisher)->send(new ChatMessage('subject', new MercureOptions(null, true, 'id', 'type', 1))); + return 'id'; + }); + + $this->createTransport(null, $hub)->send(new ChatMessage('subject', new MercureOptions(null, true, 'id', 'type', 1))); } public function testSendWithoutMercureOptions() { - $publisher = $this->createMock(PublisherInterface::class); - $publisher - ->expects($this->once()) - ->method('__invoke') - ->with(new Update(['https://symfony.com/notifier'], '{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}')) - ; + $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(Update $update): string { + $this->assertSame(['https://symfony.com/notifier'], $update->getTopics()); + $this->assertSame('{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', $update->getData()); - $this->createTransport(null, $publisher)->send(new ChatMessage('subject')); + return 'id'; + }); + + $this->createTransport(null, $hub)->send(new ChatMessage('subject')); } public function testSendSuccessfully() { $messageId = 'urn:uuid:a7045be0-a75d-4d40-8bd2-29fa4e5dd10b'; - $publisher = $this->createMock(PublisherInterface::class); - $publisher->method('__invoke')->willReturn($messageId); + $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(Update $update) use($messageId): string { + $this->assertSame(['https://symfony.com/notifier'], $update->getTopics()); + $this->assertSame('{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', $update->getData()); - $sentMessage = $this->createTransport(null, $publisher)->send(new ChatMessage('subject')); + return $messageId; + }); + + $sentMessage = $this->createTransport(null, $hub)->send(new ChatMessage('subject')); $this->assertSame($messageId, $sentMessage->getMessageId()); } } diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json b/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json index da85334a00..330e4d5d00 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "ext-json": "*", - "symfony/mercure": "^0.4", + "symfony/mercure": "^0.4|^0.5", "symfony/notifier": "^5.3", "symfony/service-contracts": "^1.10|^2" }, From 498f96f1a85bec4ccb3448f47e6548e72f8fc3bf Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Thu, 1 Apr 2021 13:18:02 +0200 Subject: [PATCH 2/2] Drop support of mercure:^0.4 --- composer.json | 2 +- .../FrameworkExtension.php | 19 ++--- .../Bridge/Mercure/MercureTransport.php | 23 ++---- .../Mercure/MercureTransportFactory.php | 25 ++----- .../Notifier/Bridge/Mercure/README.md | 4 +- .../Tests/MercureTransportFactoryTest.php | 70 ++++++------------- .../Mercure/Tests/MercureTransportTest.php | 57 +++++---------- .../Notifier/Bridge/Mercure/composer.json | 2 +- 8 files changed, 58 insertions(+), 144 deletions(-) diff --git a/composer.json b/composer.json index d8afdfa7cc..ca48ba1744 100644 --- a/composer.json +++ b/composer.json @@ -142,7 +142,7 @@ "psr/http-client": "^1.0", "psr/simple-cache": "^1.0", "egulias/email-validator": "^2.1.10|^3.1", - "symfony/mercure-bundle": "^0.2", + "symfony/mercure-bundle": "^0.3", "symfony/phpunit-bridge": "^5.2", "symfony/security-acl": "~2.8|~3.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 5b89dd4f3e..570eded880 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -26,6 +26,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader; use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface; use Symfony\Bundle\FullStack; +use Symfony\Bundle\MercureBundle\MercureBundle; use Symfony\Component\Asset\PackageInterface; use Symfony\Component\BrowserKit\AbstractBrowser; use Symfony\Component\Cache\Adapter\AdapterInterface; @@ -44,8 +45,6 @@ use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; -use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; -use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -2369,17 +2368,11 @@ class FrameworkExtension extends Extension } } - if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages)) { - if (ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages)) { - $definition = $container->getDefinition($classToServices[MercureTransportFactory::class]); - if (ContainerBuilder::willBeAvailable('symfony/mercure', HubRegistry::class, $parentPackages)) { - $definition->replaceArgument('$registry', new Reference(HubRegistry::class)); - } else { - $definition->replaceArgument('$registry', new ServiceLocatorArgument(new TaggedIteratorArgument('mercure.publisher', null, null, true))); - } - } else { - $container->removeDefinition($classToServices[MercureTransportFactory::class]); - } + if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages)) { + $container->getDefinition($classToServices[MercureTransportFactory::class]) + ->replaceArgument('$registry', new Reference(HubRegistry::class)); + } elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages)) { + $container->removeDefinition($classToServices[MercureTransportFactory::class]); } if (isset($config['admin_recipients'])) { diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransport.php b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransport.php index 2c9bd5c42d..74a0edd6b7 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransport.php @@ -11,24 +11,18 @@ namespace Symfony\Component\Notifier\Bridge\Mercure; -use Symfony\Component\Mercure\Exception\InvalidArgumentException as MercureInvalidArgumentException; +use Symfony\Component\Mercure\Exception\InvalidArgumentException; use Symfony\Component\Mercure\Exception\RuntimeException as MercureRuntimeException; use Symfony\Component\Mercure\HubInterface; -use Symfony\Component\Mercure\HubRegistry; -use Symfony\Component\Mercure\PublisherInterface; use Symfony\Component\Mercure\Update; -use Symfony\Component\Notifier\Exception\InvalidArgumentException; use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\RuntimeException; -use Symfony\Component\Notifier\Exception\TransportException; use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Transport\AbstractTransport; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; -use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; -use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; /** @@ -41,10 +35,9 @@ final class MercureTransport extends AbstractTransport private $topics; /** - * @param HubInterface $hub * @param string|string[]|null $topics */ - public function __construct($hub, string $hubId, $topics = null, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null) + public function __construct(HubInterface $hub, string $hubId, $topics = null, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null) { if (null !== $topics && !\is_array($topics) && !\is_string($topics)) { throw new \TypeError(sprintf('"%s()" expects parameter 3 to be an array of strings, a string or null, "%s" given.', __METHOD__, get_debug_type($topics))); @@ -92,22 +85,14 @@ final class MercureTransport extends AbstractTransport ]), $options->isPrivate(), $options->getId(), $options->getType(), $options->getRetry()); try { - if ($this->hub instanceof HubInterface) { - $messageId = $this->hub->publish($update); - } else { - $messageId = ($this->hub)($update); - } + $messageId = $this->hub->publish($update); $sentMessage = new SentMessage($message, (string) $this); $sentMessage->setMessageId($messageId); return $sentMessage; - } catch (HttpExceptionInterface $e) { - throw new TransportException('Unable to post the Mercure message: '.$e->getResponse()->getContent(false), $e->getResponse(), $e->getCode(), $e); - } catch (ExceptionInterface | MercureRuntimeException $e) { + } catch (MercureRuntimeException | InvalidArgumentException $e) { throw new RuntimeException('Unable to post the Mercure message: '.$e->getMessage(), $e->getCode(), $e); - } catch (\InvalidArgumentException | MercureInvalidArgumentException $e) { - throw new InvalidArgumentException('Unable to post the Mercure message: '.$e->getMessage(), $e->getCode(), $e); } } } diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransportFactory.php index 13fd1aba82..5bdabcc58b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransportFactory.php @@ -11,15 +11,13 @@ namespace Symfony\Component\Notifier\Bridge\Mercure; -use Symfony\Bundle\MercureBundle\MercureBundle; +use Symfony\Component\Mercure\Exception\InvalidArgumentException; use Symfony\Component\Mercure\HubRegistry; -use Symfony\Component\Mercure\PublisherInterface; -use Symfony\Component\Notifier\Exception\LogicException; +use Symfony\Component\Notifier\Exception\IncompleteDsnException; use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; use Symfony\Component\Notifier\Transport\AbstractTransportFactory; use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Transport\TransportInterface; -use Symfony\Contracts\Service\ServiceProviderInterface; /** * @author Mathias Arlaud @@ -28,9 +26,6 @@ final class MercureTransportFactory extends AbstractTransportFactory { private $registry; - /** - * @param HubRegistry $registry - */ public function __construct(HubRegistry $registry) { parent::__construct(); @@ -50,21 +45,13 @@ final class MercureTransportFactory extends AbstractTransportFactory $hubId = $dsn->getHost(); $topic = $dsn->getOption('topic'); - if ($this->registry instanceof HubRegistry) { + try { $hub = $this->registry->getHub($hubId); - - return new MercureTransport($hub, $hubId, $topic); + } catch (InvalidArgumentException $exception) { + throw new IncompleteDsnException(sprintf('Hub "%s" not found. Did you mean one of: "%s"?', $hubId, implode('", "', array_keys($this->registry->all())))); } - if (!$this->publisherLocator->has($hubId)) { - if (!class_exists(MercureBundle::class) && !$this->publisherLocator->getProvidedServices()) { - throw new LogicException('No publishers found. Did you forget to install the MercureBundle? Try running "composer require symfony/mercure-bundle".'); - } - - throw new LogicException(sprintf('"%s" not found. Did you mean one of: %s?', $hubId, implode(', ', array_keys($this->publisherLocator->getProvidedServices())))); - } - - return new MercureTransport($this->publisherLocator->get($hubId), $hubId, $topic); + return new MercureTransport($hub, $hubId, $topic); } protected function getSupportedSchemes(): array diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/README.md b/src/Symfony/Component/Notifier/Bridge/Mercure/README.md index 7906b15a99..ab54f6853e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/README.md +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/README.md @@ -7,11 +7,11 @@ DSN example ----------- ``` -MERCURE_DSN=mercure://PUBLISHER_SERVICE_ID?topic=TOPIC +MERCURE_DSN=mercure://HUB_ID?topic=TOPIC ``` where: - - `PUBLISHER_SERVICE_ID` is the Mercure publisher service id + - `HUB_ID` is the Mercure hub id - `TOPIC` is the topic IRI (optional, default: `https://symfony.com/notifier`. Could be either a single topic: `topic=https://foo` or multiple topics: `topic[]=/foo/1&topic[]=https://bar`) Resources diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportFactoryTest.php index fd5a380ff0..37825f4943 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportFactoryTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportFactoryTest.php @@ -11,15 +11,13 @@ namespace Symfony\Component\Notifier\Bridge\Mercure\Tests; -use LogicException; -use Symfony\Bridge\PhpUnit\ClassExistsMock; -use Symfony\Bundle\MercureBundle\MercureBundle; -use Symfony\Component\Mercure\PublisherInterface; +use Symfony\Component\Mercure\HubInterface; +use Symfony\Component\Mercure\HubRegistry; use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; +use Symfony\Component\Notifier\Exception\IncompleteDsnException; use Symfony\Component\Notifier\Test\TransportFactoryTestCase; use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Transport\TransportFactoryInterface; -use Symfony\Contracts\Service\ServiceProviderInterface; /** * @author Mathias Arlaud @@ -28,73 +26,49 @@ final class MercureTransportFactoryTest extends TransportFactoryTestCase { public function createFactory(): TransportFactoryInterface { - $publisherLocator = $this->createMock(ServiceProviderInterface::class); - $publisherLocator->method('has')->willReturn(true); - $publisherLocator->method('get')->willReturn($this->createMock(PublisherInterface::class)); + $hub = $this->createMock(HubInterface::class); + $hubRegistry = new HubRegistry($hub, ['hubId' => $hub]); - return new MercureTransportFactory($publisherLocator); + return new MercureTransportFactory($hubRegistry); } public function supportsProvider(): iterable { - yield [true, 'mercure://publisherId?topic=topic']; - yield [false, 'somethingElse://publisherId?topic=topic']; + yield [true, 'mercure://hubId?topic=topic']; + yield [false, 'somethingElse://hubId?topic=topic']; } public function createProvider(): iterable { yield [ - 'mercure://publisherId?topic=%2Ftopic%2F1', - 'mercure://publisherId?topic=/topic/1', + 'mercure://hubId?topic=%2Ftopic%2F1', + 'mercure://hubId?topic=/topic/1', ]; yield [ - 'mercure://publisherId?topic%5B0%5D=%2Ftopic%2F1&topic%5B1%5D=%2Ftopic%2F2', - 'mercure://publisherId?topic[]=/topic/1&topic[]=/topic/2', + 'mercure://hubId?topic%5B0%5D=%2Ftopic%2F1&topic%5B1%5D=%2Ftopic%2F2', + 'mercure://hubId?topic[]=/topic/1&topic[]=/topic/2', ]; yield [ - 'mercure://publisherId?topic=https%3A%2F%2Fsymfony.com%2Fnotifier', - 'mercure://publisherId', + 'mercure://hubId?topic=https%3A%2F%2Fsymfony.com%2Fnotifier', + 'mercure://hubId', ]; } public function unsupportedSchemeProvider(): iterable { - yield ['somethingElse://publisherId?topic=topic']; + yield ['somethingElse://hubId?topic=topic']; } - public function testCreateWithEmptyServiceProviderAndWithoutMercureBundleThrows() + public function testNotFoundHubThrows() { - ClassExistsMock::register(MercureTransportFactory::class); - ClassExistsMock::withMockedClasses([MercureBundle::class => false]); + $hub = $this->createMock(HubInterface::class); + $hubRegistry = new HubRegistry($hub, ['hubId' => $hub, 'anotherHubId' => $hub]); + $factory = new MercureTransportFactory($hubRegistry); - $publisherLocator = $this->createMock(ServiceProviderInterface::class); - $publisherLocator->method('has')->willReturn(false); - $publisherLocator->method('getProvidedServices')->willReturn([]); - - $factory = new MercureTransportFactory($publisherLocator); - - $this->expectException(LogicException::class); - $this->expectExceptionMessage('No publishers found. Did you forget to install the MercureBundle? Try running "composer require symfony/mercure-bundle".'); - - try { - $factory->create(new Dsn('mercure://publisherId')); - } finally { - ClassExistsMock::withMockedClasses([MercureBundle::class => true]); - } - } - - public function testNotFoundPublisherThrows() - { - $publisherLocator = $this->createMock(ServiceProviderInterface::class); - $publisherLocator->method('has')->willReturn(false); - $publisherLocator->method('getProvidedServices')->willReturn(['fooPublisher' => 'fooFqcn', 'barPublisher' => 'barFqcn']); - - $factory = new MercureTransportFactory($publisherLocator); - - $this->expectException(LogicException::class); - $this->expectExceptionMessage('"publisherId" not found. Did you mean one of: fooPublisher, barPublisher?'); - $factory->create(new Dsn('mercure://publisherId')); + $this->expectException(IncompleteDsnException::class); + $this->expectExceptionMessage('Hub "wrongHubId" not found. Did you mean one of: "hubId", "anotherHubId"?'); + $factory->create(new Dsn('mercure://wrongHubId')); } } diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportTest.php index 79e4a80678..79c38b66cd 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/MercureTransportTest.php @@ -11,28 +11,23 @@ namespace Symfony\Component\Notifier\Bridge\Mercure\Tests; -use Symfony\Component\HttpClient\Exception\TransportException as HttpClientTransportException; +use Symfony\Component\Mercure\Exception\InvalidArgumentException; use Symfony\Component\Mercure\Exception\RuntimeException as MercureRuntimeException; use Symfony\Component\Mercure\HubInterface; use Symfony\Component\Mercure\Jwt\StaticTokenProvider; use Symfony\Component\Mercure\MockHub; -use Symfony\Component\Mercure\PublisherInterface; use Symfony\Component\Mercure\Update; use Symfony\Component\Notifier\Bridge\Mercure\MercureOptions; use Symfony\Component\Notifier\Bridge\Mercure\MercureTransport; -use Symfony\Component\Notifier\Exception\InvalidArgumentException; use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\RuntimeException; -use Symfony\Component\Notifier\Exception\TransportException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\MessageOptionsInterface; use Symfony\Component\Notifier\Message\SmsMessage; use Symfony\Component\Notifier\Test\TransportTestCase; use Symfony\Component\Notifier\Transport\TransportInterface; -use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; -use Symfony\Contracts\HttpClient\ResponseInterface; use TypeError; /** @@ -40,7 +35,7 @@ use TypeError; */ final class MercureTransportTest extends TransportTestCase { - public function createTransport(?HttpClientInterface $client = null, ?HubInterface $hub = null, string $hubId = 'publisherId', $topics = null): TransportInterface + public function createTransport(?HttpClientInterface $client = null, ?HubInterface $hub = null, string $hubId = 'hubId', $topics = null): TransportInterface { $hub = $hub ?? $this->createMock(HubInterface::class); @@ -49,9 +44,9 @@ final class MercureTransportTest extends TransportTestCase public function toStringProvider(): iterable { - yield ['mercure://publisherId?topic=https%3A%2F%2Fsymfony.com%2Fnotifier', $this->createTransport()]; - yield ['mercure://customPublisherId?topic=%2Ftopic', $this->createTransport(null, null, 'customPublisherId', '/topic')]; - yield ['mercure://customPublisherId?topic%5B0%5D=%2Ftopic%2F1&topic%5B1%5D%5B0%5D=%2Ftopic%2F2', $this->createTransport(null, null, 'customPublisherId', ['/topic/1', ['/topic/2']])]; + yield ['mercure://hubId?topic=https%3A%2F%2Fsymfony.com%2Fnotifier', $this->createTransport()]; + yield ['mercure://customHubId?topic=%2Ftopic', $this->createTransport(null, null, 'customHubId', '/topic')]; + yield ['mercure://customHubId?topic%5B0%5D=%2Ftopic%2F1&topic%5B1%5D%5B0%5D=%2Ftopic%2F2', $this->createTransport(null, null, 'customHubId', ['/topic/1', ['/topic/2']])]; } public function supportedMessagesProvider(): iterable @@ -94,7 +89,7 @@ final class MercureTransportTest extends TransportTestCase public function testSendWithTransportFailureThrows() { - $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(): void { + $hub = new MockHub('https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), static function (): void { throw new MercureRuntimeException('Cannot connect to mercure'); }); @@ -104,31 +99,13 @@ final class MercureTransportTest extends TransportTestCase $this->createTransport(null, $hub)->send(new ChatMessage('subject')); } - public function testSendWithWrongResponseThrows() - { - $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(): void { - $response = $this->createMock(ResponseInterface::class); - $response->method('getContent')->willReturn('Service Unavailable'); - - $httpException = $this->createMock(ServerExceptionInterface::class); - $httpException->method('getResponse')->willReturn($response); - - throw $httpException; - }); - - $this->expectException(TransportException::class); - $this->expectExceptionMessage('Unable to post the Mercure message: Service Unavailable'); - - $this->createTransport(null, $hub)->send(new ChatMessage('subject')); - } - public function testSendWithWrongTokenThrows() { - $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(): void { - throw new \InvalidArgumentException('The provided JWT is not valid'); + $hub = new MockHub('https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), static function (): void { + throw new InvalidArgumentException('The provided JWT is not valid'); }); - $this->expectException(InvalidArgumentException::class); + $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Unable to post the Mercure message: The provided JWT is not valid'); $this->createTransport(null, $hub)->send(new ChatMessage('subject')); @@ -136,12 +113,12 @@ final class MercureTransportTest extends TransportTestCase public function testSendWithMercureOptions() { - $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(Update $update): string { + $hub = new MockHub('https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function (Update $update): string { $this->assertSame(['/topic/1', '/topic/2'], $update->getTopics()); $this->assertSame('{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', $update->getData()); $this->assertSame('id', $update->getId()); $this->assertSame('type', $update->getType()); - $this->assertSame('1', $update->getRetry()); + $this->assertSame(1, $update->getRetry()); $this->assertTrue($update->isPrivate()); return 'id'; @@ -152,12 +129,12 @@ final class MercureTransportTest extends TransportTestCase public function testSendWithMercureOptionsButWithoutOptionTopic() { - $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(Update $update): string { + $hub = new MockHub('https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function (Update $update): string { $this->assertSame(['https://symfony.com/notifier'], $update->getTopics()); $this->assertSame('{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', $update->getData()); $this->assertSame('id', $update->getId()); $this->assertSame('type', $update->getType()); - $this->assertSame('1', $update->getRetry()); + $this->assertSame(1, $update->getRetry()); $this->assertTrue($update->isPrivate()); return 'id'; @@ -168,9 +145,10 @@ final class MercureTransportTest extends TransportTestCase public function testSendWithoutMercureOptions() { - $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(Update $update): string { + $hub = new MockHub('https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function (Update $update): string { $this->assertSame(['https://symfony.com/notifier'], $update->getTopics()); $this->assertSame('{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', $update->getData()); + $this->assertFalse($update->isPrivate()); return 'id'; }); @@ -182,10 +160,7 @@ final class MercureTransportTest extends TransportTestCase { $messageId = 'urn:uuid:a7045be0-a75d-4d40-8bd2-29fa4e5dd10b'; - $hub = new MockHub('default', 'https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function(Update $update) use($messageId): string { - $this->assertSame(['https://symfony.com/notifier'], $update->getTopics()); - $this->assertSame('{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', $update->getData()); - + $hub = new MockHub('https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function (Update $update) use ($messageId): string { return $messageId; }); diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json b/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json index 330e4d5d00..4cc5526b37 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "ext-json": "*", - "symfony/mercure": "^0.4|^0.5", + "symfony/mercure": "^0.5.2", "symfony/notifier": "^5.3", "symfony/service-contracts": "^1.10|^2" },