Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c6100bc

Browse filesBrowse files
committed
feature #39342 [Notifier] Add mercure bridge (mtarld)
This PR was merged into the 5.3-dev branch. Discussion ---------- [Notifier] Add mercure bridge | Q | A | ------------- | --- | Branch? | 5.x | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix #36481 | License | MIT | Doc PR | symfony/symfony-docs#14840 Add a Notifier bridge for Mercure. In this PR, Mercure is considered as a chatter (I'm still wondering if it's the most appropriate type). The first approach for the DSN is `mercure://jwtToken@host:port/hubPath?topic=/foo/1&secure=false` with: - `topic` optional (defaults to `null`) - `secure` optional (defaults to `true`) I'm not sure about the current way to deal with http/https. Maybe we can just replace the `mercure` scheme by `http|https`? The notification representation is following [Activity Streams](https://www.w3.org/TR/activitystreams-core/#jsonld) #SymfonyHackday Commits ------- 19c6544 [Notifier] Add mercure bridge
2 parents d91278a + 19c6544 commit c6100bc
Copy full SHA for c6100bc

16 files changed

+677
-0
lines changed

‎composer.json

Copy file name to clipboardExpand all lines: composer.json
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
"psr/http-client": "^1.0",
131131
"psr/simple-cache": "^1.0",
132132
"egulias/email-validator": "^2.1.10",
133+
"symfony/mercure-bundle": "^0.2",
133134
"symfony/phpunit-bridge": "^5.2",
134135
"symfony/security-acl": "~2.8|~3.0",
135136
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader;
2626
use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface;
2727
use Symfony\Bundle\FullStack;
28+
use Symfony\Bundle\MercureBundle\MercureBundle;
2829
use Symfony\Component\Asset\PackageInterface;
2930
use Symfony\Component\BrowserKit\AbstractBrowser;
3031
use Symfony\Component\Cache\Adapter\AdapterInterface;
@@ -43,6 +44,8 @@
4344
use Symfony\Component\Console\Command\Command;
4445
use Symfony\Component\DependencyInjection\Alias;
4546
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
47+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
48+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
4649
use Symfony\Component\DependencyInjection\ChildDefinition;
4750
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
4851
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -112,6 +115,7 @@
112115
use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory;
113116
use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory;
114117
use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory;
118+
use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory;
115119
use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory;
116120
use Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory;
117121
use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory;
@@ -2242,6 +2246,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $
22422246
LinkedInTransportFactory::class => 'notifier.transport_factory.linkedin',
22432247
GatewayApiTransportFactory::class => 'notifier.transport_factory.gatewayapi',
22442248
OctopushTransportFactory::class => 'notifier.transport_factory.octopush',
2249+
MercureTransportFactory::class => 'notifier.transport_factory.mercure',
22452250
];
22462251

22472252
foreach ($classToServices as $class => $service) {
@@ -2250,6 +2255,15 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $
22502255
}
22512256
}
22522257

2258+
if (class_exists(MercureTransportFactory::class)) {
2259+
if (!class_exists(MercureBundle::class)) {
2260+
throw new \LogicException('The MercureBundle is not registered in your application. Try running "composer require symfony/mercure-bundle".');
2261+
}
2262+
2263+
$container->getDefinition($classToServices[MercureTransportFactory::class])
2264+
->replaceArgument('$publisherLocator', new ServiceLocatorArgument(new TaggedIteratorArgument('mercure.publisher', null, null, true)));
2265+
}
2266+
22532267
if (isset($config['admin_recipients'])) {
22542268
$notifier = $container->getDefinition('notifier');
22552269
foreach ($config['admin_recipients'] as $i => $recipient) {

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory;
2323
use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory;
2424
use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory;
25+
use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory;
2526
use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory;
2627
use Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory;
2728
use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory;
@@ -135,6 +136,10 @@
135136
->parent('notifier.transport_factory.abstract')
136137
->tag('texter.transport_factory')
137138

139+
->set('notifier.transport_factory.mercure', MercureTransportFactory::class)
140+
->parent('notifier.transport_factory.abstract')
141+
->tag('chatter.transport_factory')
142+
138143
->set('notifier.transport_factory.null', NullTransportFactory::class)
139144
->parent('notifier.transport_factory.abstract')
140145
->tag('chatter.transport_factory')
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/Tests export-ignore
2+
/phpunit.xml.dist export-ignore
3+
/.gitattributes export-ignore
4+
/.gitignore export-ignore
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
5.3
5+
---
6+
7+
* Add the bridge
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2021 Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.
+86Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Notifier\Bridge\Mercure;
13+
14+
use Symfony\Component\Notifier\Message\MessageOptionsInterface;
15+
16+
/**
17+
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
18+
*/
19+
final class MercureOptions implements MessageOptionsInterface
20+
{
21+
private $topics;
22+
private $private;
23+
private $id;
24+
private $type;
25+
private $retry;
26+
27+
/**
28+
* @param string|string[]|null $topics
29+
*/
30+
public function __construct($topics = null, bool $private = false, ?string $id = null, ?string $type = null, ?int $retry = null)
31+
{
32+
if (null !== $topics && !\is_array($topics) && !\is_string($topics)) {
33+
throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an array of strings, a string or null, "%s" given.', __METHOD__, get_debug_type($topics)));
34+
}
35+
36+
$this->topics = null !== $topics ? (array) $topics : null;
37+
$this->private = $private;
38+
$this->id = $id;
39+
$this->type = $type;
40+
$this->retry = $retry;
41+
}
42+
43+
/**
44+
* @return string[]|null
45+
*/
46+
public function getTopics(): ?array
47+
{
48+
return $this->topics;
49+
}
50+
51+
public function isPrivate(): bool
52+
{
53+
return $this->private;
54+
}
55+
56+
public function getId(): ?string
57+
{
58+
return $this->id;
59+
}
60+
61+
public function getType(): ?string
62+
{
63+
return $this->type;
64+
}
65+
66+
public function getRetry(): ?int
67+
{
68+
return $this->retry;
69+
}
70+
71+
public function toArray(): array
72+
{
73+
return [
74+
'topics' => $this->topics,
75+
'private' => $this->private,
76+
'id' => $this->id,
77+
'type' => $this->type,
78+
'retry' => $this->retry,
79+
];
80+
}
81+
82+
public function getRecipientId(): ?string
83+
{
84+
return null;
85+
}
86+
}
+100Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Notifier\Bridge\Mercure;
13+
14+
use Symfony\Component\Mercure\PublisherInterface;
15+
use Symfony\Component\Mercure\Update;
16+
use Symfony\Component\Notifier\Exception\InvalidArgumentException;
17+
use Symfony\Component\Notifier\Exception\LogicException;
18+
use Symfony\Component\Notifier\Exception\TransportException;
19+
use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException;
20+
use Symfony\Component\Notifier\Message\ChatMessage;
21+
use Symfony\Component\Notifier\Message\MessageInterface;
22+
use Symfony\Component\Notifier\Message\SentMessage;
23+
use Symfony\Component\Notifier\Transport\AbstractTransport;
24+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
25+
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
26+
use Symfony\Contracts\HttpClient\HttpClientInterface;
27+
28+
/**
29+
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
30+
*/
31+
final class MercureTransport extends AbstractTransport
32+
{
33+
private $publisher;
34+
private $publisherId;
35+
private $topics;
36+
37+
/**
38+
* @param string|string[]|null $topics
39+
*/
40+
public function __construct(PublisherInterface $publisher, string $publisherId, $topics = null, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null)
41+
{
42+
if (null !== $topics && !\is_array($topics) && !\is_string($topics)) {
43+
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)));
44+
}
45+
46+
$this->publisher = $publisher;
47+
$this->publisherId = $publisherId;
48+
$this->topics = $topics ?? 'https://symfony.com/notifier';
49+
50+
parent::__construct($client, $dispatcher);
51+
}
52+
53+
public function __toString(): string
54+
{
55+
return sprintf('mercure://%s?%s', $this->publisherId, http_build_query(['topic' => $this->topics]));
56+
}
57+
58+
public function supports(MessageInterface $message): bool
59+
{
60+
return $message instanceof ChatMessage && (null === $message->getOptions() || $message->getOptions() instanceof MercureOptions);
61+
}
62+
63+
/**
64+
* @see https://symfony.com/doc/current/mercure.html#publishing
65+
*/
66+
protected function doSend(MessageInterface $message): SentMessage
67+
{
68+
if (!$message instanceof ChatMessage) {
69+
throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message);
70+
}
71+
72+
if (($options = $message->getOptions()) && !$options instanceof MercureOptions) {
73+
throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" for options.', __CLASS__, MercureOptions::class));
74+
}
75+
76+
if (null === $options) {
77+
$options = new MercureOptions($this->topics);
78+
}
79+
80+
// @see https://www.w3.org/TR/activitystreams-core/#jsonld
81+
$update = new Update($options->getTopics() ?? $this->topics, json_encode([
82+
'@context' => 'https://www.w3.org/ns/activitystreams',
83+
'type' => 'Announce',
84+
'summary' => $message->getSubject(),
85+
]), $options->isPrivate(), $options->getId(), $options->getType(), $options->getRetry());
86+
87+
try {
88+
$messageId = ($this->publisher)($update);
89+
90+
$sentMessage = new SentMessage($message, (string) $this);
91+
$sentMessage->setMessageId($messageId);
92+
93+
return $sentMessage;
94+
} catch (ExceptionInterface $e) {
95+
throw new TransportException(sprintf('Unable to post the Mercure message: "%s".', $e->getResponse()->getContent(false)), $e->getResponse(), $e->getCode(), $e);
96+
} catch (\InvalidArgumentException $e) {
97+
throw new InvalidArgumentException(sprintf('Unable to post the Mercure message: "%s".', $e->getMessage()), $e->getCode(), $e);
98+
}
99+
}
100+
}
+62Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Notifier\Bridge\Mercure;
13+
14+
use Symfony\Component\Mercure\PublisherInterface;
15+
use Symfony\Component\Notifier\Exception\LogicException;
16+
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
17+
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
18+
use Symfony\Component\Notifier\Transport\Dsn;
19+
use Symfony\Component\Notifier\Transport\TransportInterface;
20+
use Symfony\Contracts\Service\ServiceProviderInterface;
21+
22+
/**
23+
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
24+
*/
25+
final class MercureTransportFactory extends AbstractTransportFactory
26+
{
27+
private $publisherLocator;
28+
29+
/**
30+
* @param ServiceProviderInterface $publisherLocator A container that holds {@see PublisherInterface} instances
31+
*/
32+
public function __construct(ServiceProviderInterface $publisherLocator)
33+
{
34+
parent::__construct();
35+
36+
$this->publisherLocator = $publisherLocator;
37+
}
38+
39+
/**
40+
* @return MercureTransport
41+
*/
42+
public function create(Dsn $dsn): TransportInterface
43+
{
44+
if ('mercure' !== $dsn->getScheme()) {
45+
throw new UnsupportedSchemeException($dsn, 'mercure', $this->getSupportedSchemes());
46+
}
47+
48+
$publisherId = $dsn->getHost();
49+
if (!$this->publisherLocator->has($publisherId)) {
50+
throw new LogicException(sprintf('"%s" not found. Did you mean one of: %s?', $publisherId, implode(', ', array_keys($this->publisherLocator->getProvidedServices()))));
51+
}
52+
53+
$topic = $dsn->getOption('topic');
54+
55+
return new MercureTransport($this->publisherLocator->get($publisherId), $publisherId, $topic);
56+
}
57+
58+
protected function getSupportedSchemes(): array
59+
{
60+
return ['mercure'];
61+
}
62+
}
+23Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Mercure Notifier
2+
================
3+
4+
Provides [Mercure](https://github.com/symfony/mercure) integration for Symfony Notifier.
5+
6+
DSN example
7+
-----------
8+
9+
```
10+
MERCURE_DSN=mercure://PUBLISHER_SERVICE_ID?topic=TOPIC
11+
```
12+
13+
where:
14+
- `PUBLISHER_SERVICE_ID` is the Mercure publisher service id
15+
- `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`)
16+
17+
Resources
18+
---------
19+
20+
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
21+
* [Report issues](https://github.com/symfony/symfony/issues) and
22+
[send Pull Requests](https://github.com/symfony/symfony/pulls)
23+
in the [main Symfony repository](https://github.com/symfony/symfony)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.