feature #39948 [Notifier] [SpotHit] Add the bridge (JamesHemery)

This PR was squashed before being merged into the 5.3-dev branch.

Discussion
----------

[Notifier] [SpotHit] Add the bridge

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| License       | MIT
| Doc PR        | symfony/symfony-docs#14869
| Recipe PR        | symfony/recipes#881

@OskarStark Bridge added :)

Commits
-------

afcca88af2 [Notifier] [SpotHit] Add the bridge
This commit is contained in:
Oskar Stark 2021-02-03 15:45:17 +01:00
commit 3eb8a42036
11 changed files with 348 additions and 0 deletions

View File

@ -126,6 +126,7 @@ use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory as S
use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory;
use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory;
use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory;
use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory;
use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory;
use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory;
use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory;
@ -2236,6 +2237,7 @@ class FrameworkExtension extends Extension
AllMySmsTransportFactory::class => 'notifier.transport_factory.allmysms',
FirebaseTransportFactory::class => 'notifier.transport_factory.firebase',
FreeMobileTransportFactory::class => 'notifier.transport_factory.freemobile',
SpotHitTransportFactory::class => 'notifier.transport_factory.spothit',
OvhCloudTransportFactory::class => 'notifier.transport_factory.ovhcloud',
SinchTransportFactory::class => 'notifier.transport_factory.sinch',
ZulipTransportFactory::class => 'notifier.transport_factory.zulip',

View File

@ -34,6 +34,7 @@ use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory;
use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory;
use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory;
use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory;
use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory;
use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory;
use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory;
use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory;
@ -90,6 +91,10 @@ return static function (ContainerConfigurator $container) {
->parent('notifier.transport_factory.abstract')
->tag('texter.transport_factory')
->set('notifier.transport_factory.spothit', SpotHitTransportFactory::class)
->parent('notifier.transport_factory.abstract')
->tag('texter.transport_factory')
->set('notifier.transport_factory.ovhcloud', OvhCloudTransportFactory::class)
->parent('notifier.transport_factory.abstract')
->tag('texter.transport_factory')

View File

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

View File

@ -0,0 +1,23 @@
Spot-Hit Notifier
=================
Provides [Spot-Hit](https://www.spot-hit.fr/) integration for Symfony Notifier.
#### DSN example
```
SPOTHIT_DSN=spothit://TOKEN@default?from=FROM
```
where:
- `TOKEN` is your Spot-Hit API key
- `FROM` is the custom sender (3-11 letters, default is a 5 digits phone number)
Resources
---------
* [Spot-Hit API doc](https://www.spot-hit.fr/documentation-api).
* [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,96 @@
<?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\SpotHit;
use Symfony\Component\Notifier\Exception\TransportException;
use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException;
use Symfony\Component\Notifier\Message\MessageInterface;
use Symfony\Component\Notifier\Message\SentMessage;
use Symfony\Component\Notifier\Message\SmsMessage;
use Symfony\Component\Notifier\Transport\AbstractTransport;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* @author James Hemery <james@yieldstudio.fr>
*/
final class SpotHitTransport extends AbstractTransport
{
protected const HOST = 'spot-hit.fr';
private $token;
private $from;
public function __construct(string $token, ?string $from = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)
{
$this->token = $token;
$this->from = $from;
parent::__construct($client, $dispatcher);
}
public function __toString(): string
{
if (!$this->from) {
return sprintf('spothit://%s', $this->getEndpoint());
}
return sprintf('spothit://%s?from=%s', $this->getEndpoint(), $this->from);
}
public function supports(MessageInterface $message): bool
{
return $message instanceof SmsMessage;
}
/**
* @param MessageInterface|SmsMessage $message
*
* @throws TransportExceptionInterface
* @throws ClientExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ServerExceptionInterface
*/
protected function doSend(MessageInterface $message): SentMessage
{
if (!$this->supports($message)) {
throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message);
}
$endpoint = sprintf('https://www.%s/api/envoyer/sms', $this->getEndpoint());
$response = $this->client->request('POST', $endpoint, [
'body' => [
'key' => $this->token,
'destinataires' => $message->getPhone(),
'type' => 'premium',
'message' => $message->getSubject(),
'expediteur' => $this->from,
],
]);
$data = json_decode($response->getContent(), true);
if (!$data['resultat']) {
$errors = \is_array($data['erreurs']) ? implode(',', $data['erreurs']) : $data['erreurs'];
throw new TransportException(sprintf('[HTTP %d] Unable to send the SMS: error(s) "%s".', $response->getStatusCode(), $errors), $response);
}
$sentMessage = new SentMessage($message, (string) $this);
$sentMessage->setMessageId($data['id']);
return $sentMessage;
}
}

View File

@ -0,0 +1,47 @@
<?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\SpotHit;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
use Symfony\Component\Notifier\Transport\TransportInterface;
/**
* @author James Hemery <james@yieldstudio.fr>
*/
final class SpotHitTransportFactory extends AbstractTransportFactory
{
/**
* @return SpotHitTransport
*/
public function create(Dsn $dsn): TransportInterface
{
$scheme = $dsn->getScheme();
if ('spothit' !== $scheme) {
throw new UnsupportedSchemeException($dsn, 'spothit', $this->getSupportedSchemes());
}
$token = $this->getUser($dsn);
$from = $dsn->getOption('from');
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();
return (new SpotHitTransport($token, $from, $this->client, $this->dispatcher))->setHost($host)->setPort($port);
}
protected function getSupportedSchemes(): array
{
return ['spothit'];
}
}

View File

@ -0,0 +1,51 @@
<?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\SpotHit\Tests;
use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory;
use Symfony\Component\Notifier\Tests\TransportFactoryTestCase;
use Symfony\Component\Notifier\Transport\TransportFactoryInterface;
final class SpotHitTransportFactoryTest extends TransportFactoryTestCase
{
/**
* @return SpotHitTransportFactory
*/
public function createFactory(): TransportFactoryInterface
{
return new SpotHitTransportFactory();
}
public function createProvider(): iterable
{
yield [
'spothit://spot-hit.fr',
'spothit://api_token@default',
];
yield [
'spothit://spot-hit.fr?from=MyCompany',
'spothit://api_token@default?from=MyCompany',
];
}
public function supportsProvider(): iterable
{
yield [true, 'spothit://api_token@default?from=MyCompany'];
yield [false, 'somethingElse://api_token@default?from=MyCompany'];
}
public function unsupportedSchemeProvider(): iterable
{
yield ['foobar://api_token@default?from=MyCompany'];
yield ['foobar://api_token@default'];
}
}

View File

@ -0,0 +1,48 @@
<?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\SpotHit\Tests;
use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransport;
use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Message\MessageInterface;
use Symfony\Component\Notifier\Message\SmsMessage;
use Symfony\Component\Notifier\Tests\TransportTestCase;
use Symfony\Component\Notifier\Transport\TransportInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
final class SpotHitTransportTest extends TransportTestCase
{
/**
* @return SpotHitTransport
*/
public function createTransport(?HttpClientInterface $client = null): TransportInterface
{
return (new SpotHitTransport('api_token', 'MyCompany', $client ?: $this->createMock(HttpClientInterface::class)))->setHost('host.test');
}
public function toStringProvider(): iterable
{
yield ['spothit://host.test?from=MyCompany', $this->createTransport()];
}
public function supportedMessagesProvider(): iterable
{
yield [new SmsMessage('0611223344', 'Hello!')];
yield [new SmsMessage('+33611223344', 'Hello!')];
}
public function unsupportedMessagesProvider(): iterable
{
yield [new ChatMessage('Hello!')];
yield [$this->createMock(MessageInterface::class)];
}
}

View File

@ -0,0 +1,34 @@
{
"name": "symfony/spothit-notifier",
"type": "symfony-bridge",
"description": "Symfony Spot-Hit Notifier Bridge",
"keywords": ["sms", "spot-hit", "notifier", "symfony"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "James Hemery",
"homepage": "https://github.com/JamesHemery"
},
{
"name": "Yield Studio",
"homepage": "https://github.com/YieldStudio"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.2.5",
"symfony/http-client": "^4.3|^5.1",
"symfony/notifier": "^5.3"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Notifier\\Bridge\\SpotHit\\": "" },
"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 Spot-Hit Notifier 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

@ -68,6 +68,10 @@ class UnsupportedSchemeException extends LogicException
'class' => Bridge\FreeMobile\FreeMobileTransportFactory::class,
'package' => 'symfony/free-mobile-notifier',
],
'spothit' => [
'class' => Bridge\SpotHit\SpotHitTransportFactory::class,
'package' => 'symfony/spothit-notifier',
],
'ovhcloud' => [
'class' => Bridge\OvhCloud\OvhCloudTransportFactory::class,
'package' => 'symfony/ovh-cloud-notifier',