[Notifier] Add mercure bridge

This commit is contained in:
Mathias Arlaud 2020-12-05 22:14:52 +01:00
parent 29b88afeb7
commit 19c6544f42
16 changed files with 677 additions and 0 deletions

View File

@ -130,6 +130,7 @@
"psr/http-client": "^1.0",
"psr/simple-cache": "^1.0",
"egulias/email-validator": "^2.1.10",
"symfony/mercure-bundle": "^0.2",
"symfony/phpunit-bridge": "^5.2",
"symfony/security-acl": "~2.8|~3.0",
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",

View File

@ -25,6 +25,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;
@ -43,6 +44,8 @@ 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;
@ -112,6 +115,7 @@ use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory;
use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory;
use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory;
use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory;
use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory;
use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory;
use Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory;
use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory;
@ -2242,6 +2246,7 @@ class FrameworkExtension extends Extension
LinkedInTransportFactory::class => 'notifier.transport_factory.linkedin',
GatewayApiTransportFactory::class => 'notifier.transport_factory.gatewayapi',
OctopushTransportFactory::class => 'notifier.transport_factory.octopush',
MercureTransportFactory::class => 'notifier.transport_factory.mercure',
];
foreach ($classToServices as $class => $service) {
@ -2250,6 +2255,15 @@ class FrameworkExtension extends Extension
}
}
if (class_exists(MercureTransportFactory::class)) {
if (!class_exists(MercureBundle::class)) {
throw new \LogicException('The MercureBundle is not registered in your application. Try running "composer require symfony/mercure-bundle".');
}
$container->getDefinition($classToServices[MercureTransportFactory::class])
->replaceArgument('$publisherLocator', new ServiceLocatorArgument(new TaggedIteratorArgument('mercure.publisher', null, null, true)));
}
if (isset($config['admin_recipients'])) {
$notifier = $container->getDefinition('notifier');
foreach ($config['admin_recipients'] as $i => $recipient) {

View File

@ -22,6 +22,7 @@ use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory;
use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory;
use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory;
use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory;
use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory;
use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory;
use Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory;
use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory;
@ -135,6 +136,10 @@ return static function (ContainerConfigurator $container) {
->parent('notifier.transport_factory.abstract')
->tag('texter.transport_factory')
->set('notifier.transport_factory.mercure', MercureTransportFactory::class)
->parent('notifier.transport_factory.abstract')
->tag('chatter.transport_factory')
->set('notifier.transport_factory.null', NullTransportFactory::class)
->parent('notifier.transport_factory.abstract')
->tag('chatter.transport_factory')

View File

@ -0,0 +1,4 @@
/Tests export-ignore
/phpunit.xml.dist export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore

View File

@ -0,0 +1,7 @@
CHANGELOG
=========
5.3
---
* Add the bridge

View File

@ -0,0 +1,19 @@
Copyright (c) 2021 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Notifier\Bridge\Mercure;
use Symfony\Component\Notifier\Message\MessageOptionsInterface;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
*/
final class MercureOptions implements MessageOptionsInterface
{
private $topics;
private $private;
private $id;
private $type;
private $retry;
/**
* @param string|string[]|null $topics
*/
public function __construct($topics = null, bool $private = false, ?string $id = null, ?string $type = null, ?int $retry = null)
{
if (null !== $topics && !\is_array($topics) && !\is_string($topics)) {
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)));
}
$this->topics = null !== $topics ? (array) $topics : null;
$this->private = $private;
$this->id = $id;
$this->type = $type;
$this->retry = $retry;
}
/**
* @return string[]|null
*/
public function getTopics(): ?array
{
return $this->topics;
}
public function isPrivate(): bool
{
return $this->private;
}
public function getId(): ?string
{
return $this->id;
}
public function getType(): ?string
{
return $this->type;
}
public function getRetry(): ?int
{
return $this->retry;
}
public function toArray(): array
{
return [
'topics' => $this->topics,
'private' => $this->private,
'id' => $this->id,
'type' => $this->type,
'retry' => $this->retry,
];
}
public function getRecipientId(): ?string
{
return null;
}
}

View File

@ -0,0 +1,100 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Notifier\Bridge\Mercure;
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\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\HttpClientInterface;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
*/
final class MercureTransport extends AbstractTransport
{
private $publisher;
private $publisherId;
private $topics;
/**
* @param string|string[]|null $topics
*/
public function __construct(PublisherInterface $publisher, string $publisherId, $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->topics = $topics ?? 'https://symfony.com/notifier';
parent::__construct($client, $dispatcher);
}
public function __toString(): string
{
return sprintf('mercure://%s?%s', $this->publisherId, http_build_query(['topic' => $this->topics]));
}
public function supports(MessageInterface $message): bool
{
return $message instanceof ChatMessage && (null === $message->getOptions() || $message->getOptions() instanceof MercureOptions);
}
/**
* @see https://symfony.com/doc/current/mercure.html#publishing
*/
protected function doSend(MessageInterface $message): SentMessage
{
if (!$message instanceof ChatMessage) {
throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message);
}
if (($options = $message->getOptions()) && !$options instanceof MercureOptions) {
throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" for options.', __CLASS__, MercureOptions::class));
}
if (null === $options) {
$options = new MercureOptions($this->topics);
}
// @see https://www.w3.org/TR/activitystreams-core/#jsonld
$update = new Update($options->getTopics() ?? $this->topics, json_encode([
'@context' => 'https://www.w3.org/ns/activitystreams',
'type' => 'Announce',
'summary' => $message->getSubject(),
]), $options->isPrivate(), $options->getId(), $options->getType(), $options->getRetry());
try {
$messageId = ($this->publisher)($update);
$sentMessage = new SentMessage($message, (string) $this);
$sentMessage->setMessageId($messageId);
return $sentMessage;
} catch (ExceptionInterface $e) {
throw new TransportException(sprintf('Unable to post the Mercure message: "%s".', $e->getResponse()->getContent(false)), $e->getResponse(), $e->getCode(), $e);
} catch (\InvalidArgumentException $e) {
throw new InvalidArgumentException(sprintf('Unable to post the Mercure message: "%s".', $e->getMessage()), $e->getCode(), $e);
}
}
}

View File

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Notifier\Bridge\Mercure;
use Symfony\Component\Mercure\PublisherInterface;
use Symfony\Component\Notifier\Exception\LogicException;
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 <mathias.arlaud@gmail.com>
*/
final class MercureTransportFactory extends AbstractTransportFactory
{
private $publisherLocator;
/**
* @param ServiceProviderInterface $publisherLocator A container that holds {@see PublisherInterface} instances
*/
public function __construct(ServiceProviderInterface $publisherLocator)
{
parent::__construct();
$this->publisherLocator = $publisherLocator;
}
/**
* @return MercureTransport
*/
public function create(Dsn $dsn): TransportInterface
{
if ('mercure' !== $dsn->getScheme()) {
throw new UnsupportedSchemeException($dsn, 'mercure', $this->getSupportedSchemes());
}
$publisherId = $dsn->getHost();
if (!$this->publisherLocator->has($publisherId)) {
throw new LogicException(sprintf('"%s" not found. Did you mean one of: %s?', $publisherId, implode(', ', array_keys($this->publisherLocator->getProvidedServices()))));
}
$topic = $dsn->getOption('topic');
return new MercureTransport($this->publisherLocator->get($publisherId), $publisherId, $topic);
}
protected function getSupportedSchemes(): array
{
return ['mercure'];
}
}

View File

@ -0,0 +1,23 @@
Mercure Notifier
================
Provides [Mercure](https://github.com/symfony/mercure) integration for Symfony Notifier.
DSN example
-----------
```
MERCURE_DSN=mercure://PUBLISHER_SERVICE_ID?topic=TOPIC
```
where:
- `PUBLISHER_SERVICE_ID` is the Mercure publisher service 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
---------
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View File

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Notifier\Bridge\Mercure\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Notifier\Bridge\Mercure\MercureOptions;
use TypeError;
final class MercureOptionsTest extends TestCase
{
public function testConstructWithDefaults()
{
$this->assertSame((new MercureOptions())->toArray(), [
'topics' => null,
'private' => false,
'id' => null,
'type' => null,
'retry' => null,
]);
}
public function testConstructWithParameters()
{
$options = (new MercureOptions('/topic/1', true, 'id', 'type', 1));
$this->assertSame($options->toArray(), [
'topics' => ['/topic/1'],
'private' => true,
'id' => 'id',
'type' => 'type',
'retry' => 1,
]);
}
public function testConstructWithWrongTopicsThrows()
{
$this->expectException(TypeError::class);
new MercureOptions(1);
}
}

View File

@ -0,0 +1,77 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Notifier\Bridge\Mercure\Tests;
use LogicException;
use Symfony\Component\Mercure\PublisherInterface;
use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory;
use Symfony\Component\Notifier\Tests\TransportFactoryTestCase;
use Symfony\Component\Notifier\Transport\Dsn;
use Symfony\Component\Notifier\Transport\TransportFactoryInterface;
use Symfony\Contracts\Service\ServiceProviderInterface;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
*/
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));
return new MercureTransportFactory($publisherLocator);
}
public function supportsProvider(): iterable
{
yield [true, 'mercure://publisherId?topic=topic'];
yield [false, 'somethingElse://publisherId?topic=topic'];
}
public function createProvider(): iterable
{
yield [
'mercure://publisherId?topic=%2Ftopic%2F1',
'mercure://publisherId?topic=/topic/1',
];
yield [
'mercure://publisherId?topic%5B0%5D=%2Ftopic%2F1&topic%5B1%5D=%2Ftopic%2F2',
'mercure://publisherId?topic[]=/topic/1&topic[]=/topic/2',
];
yield [
'mercure://publisherId?topic=https%3A%2F%2Fsymfony.com%2Fnotifier',
'mercure://publisherId',
];
}
public function unsupportedSchemeProvider(): iterable
{
yield ['somethingElse://publisherId?topic=topic'];
}
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'));
}
}

View File

@ -0,0 +1,163 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Notifier\Bridge\Mercure\Tests;
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\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\Tests\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;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
*/
final class MercureTransportTest extends TransportTestCase
{
public function createTransport(?HttpClientInterface $client = null, ?PublisherInterface $publisher = null, string $publisherId = 'publisherId', $topics = null): TransportInterface
{
$publisher = $publisher ?? $this->createMock(PublisherInterface::class);
return new MercureTransport($publisher, $publisherId, $topics);
}
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']])];
}
public function supportedMessagesProvider(): iterable
{
yield [new ChatMessage('Hello!')];
}
public function unsupportedMessagesProvider(): iterable
{
yield [new SmsMessage('0611223344', 'Hello!')];
yield [$this->createMock(MessageInterface::class)];
}
public function testCanSetCustomPort()
{
$this->markTestSkipped("Mercure transport doesn't use a regular HTTP Dsn");
}
public function testCanSetCustomHost()
{
$this->markTestSkipped("Mercure transport doesn't use a regular HTTP Dsn");
}
public function testCanSetCustomHostAndPort()
{
$this->markTestSkipped("Mercure transport doesn't use a regular HTTP Dsn");
}
public function testConstructWithWrongTopicsThrows()
{
$this->expectException(TypeError::class);
$this->createTransport(null, null, 'publisherId', 1);
}
public function testSendWithNonMercureOptionsThrows()
{
$this->expectException(LogicException::class);
$this->createTransport()->send(new ChatMessage('testMessage', $this->createMock(MessageOptionsInterface::class)));
}
public function testSendWithWrongResponseThrows()
{
$response = $this->createMock(ResponseInterface::class);
$response->method('getContent')->willReturn('Service Unavailable');
$httpException = $this->createMock(ServerExceptionInterface::class);
$httpException->method('getResponse')->willReturn($response);
$publisher = $this->createMock(PublisherInterface::class);
$publisher->method('__invoke')->willThrowException($httpException);
$this->expectException(TransportException::class);
$this->expectExceptionMessage('Unable to post the Mercure message: "Service Unavailable".');
$this->createTransport(null, $publisher)->send(new ChatMessage('subject'));
}
public function testSendWithWrongTokenThrows()
{
$publisher = $this->createMock(PublisherInterface::class);
$publisher->method('__invoke')->willThrowException(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'));
}
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))
;
$this->createTransport(null, $publisher)->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))
;
$this->createTransport(null, $publisher)->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"}'))
;
$this->createTransport(null, $publisher)->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);
$sentMessage = $this->createTransport(null, $publisher)->send(new ChatMessage('subject'));
$this->assertSame($messageId, $sentMessage->getMessageId());
}
}

View File

@ -0,0 +1,32 @@
{
"name": "symfony/mercure-notifier",
"type": "symfony-bridge",
"description": "Symfony Mercure Notifier Bridge",
"keywords": ["mercure", "notifier"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Mathias Arlaud",
"email": "mathias.arlaud@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.2.5",
"ext-json": "*",
"symfony/mercure": "^0.4",
"symfony/notifier": "^5.3",
"symfony/service-contracts": "^1.10|^2"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Mercure\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev"
}

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Symfony Mercure Notifier Bridge Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Resources</directory>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -100,6 +100,10 @@ class UnsupportedSchemeException extends LogicException
'class' => Bridge\Octopush\OctopushTransportFactory::class,
'package' => 'symfony/octopush-notifier',
],
'mercure' => [
'class' => Bridge\Mercure\MercureTransportFactory::class,
'package' => 'symfony/mercure-notifier',
],
];
/**