8000 [Notifier] Add Brevo bridge (formerly Sendinblue) · symfony/symfony@731f9b0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 731f9b0

Browse files
PEtanguyfabpot
authored andcommitted
[Notifier] Add Brevo bridge (formerly Sendinblue)
1 parent 1fa7636 commit 731f9b0

21 files changed

+406
-2
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2721,6 +2721,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $
27212721
NotifierBridge\AllMySms\AllMySmsTransportFactory::class => 'notifier.transport_factory.all-my-sms',
27222722
NotifierBridge\AmazonSns\AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns',
27232723
NotifierBridge\Bandwidth\BandwidthTransportFactory::class => 'notifier.transport_factory.bandwidth',
2724+
NotifierBridge\Brevo\BrevoTransportFactory::class => 'notifier.transport_factory.brevo',
27242725
NotifierBridge\Chatwork\ChatworkTransportFactory::class => 'notifier.transport_factory.chatwork',
27252726
NotifierBridge\Clickatell\ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell',
27262727
NotifierBridge\ClickSend\ClickSendTransportFactory::class => 'notifier.transport_factory.click-send',

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
->abstract()
2323
->args([service('event_dispatcher'), service('http_client')->ignoreOnInvalid()])
2424

25+
->set('notifier.transport_factory.brevo', Bridge\Brevo\BrevoTransportFactory::class)
26+
->parent('notifier.transport_factory.abstract')
27+
->tag('texter.transport_factory')
28+
2529
->set('notifier.transport_factory.slack', Bridge\Slack\SlackTransportFactory::class)
2630
->parent('notifier.transport_factory.abstract')
2731
->tag('chatter.transport_factory')
@@ -279,11 +283,11 @@
279283
->set('notifier.transport_factory.simple-textin', Bridge\SimpleTextin\SimpleTextinTransportFactory::class)
280284
->parent('notifier.transport_factory.abstract')
281285
->tag('texter.transport_factory')
282-
286+
283287
->set('notifier.transport_factory.click-send', Bridge\ClickSend\ClickSendTransportFactory::class)
284288
->parent('notifier.transport_factory.abstract')
285289
->tag('texter.transport_factory')
286-
290+
287291
->set('notifier.transport_factory.smsmode', Bridge\Smsmode\SmsmodeTransportFactory::class)
288292
->parent('notifier.transport_factory.abstract')
289293
->tag('texter.transport_factory')
Lines 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
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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\Brevo;
13+
14+
use Symfony\Component\Notifier\Exception\TransportException;
15+
use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException;
16+
use Symfony\Component\Notifier\Message\MessageInterface;
17+
use Symfony\Component\Notifier\Message\SentMessage;
18+
use Symfony\Component\Notifier\Message\SmsMessage;
19+
use Symfony\Component\Notifier\Transport\AbstractTransport;
20+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
21+
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
22+
use Symfony\Contracts\HttpClient\HttpClientInterface;
23+
24+
/**
25+
* @author Pierre Tanguy
26+
*/
27+
final class BrevoTransport extends AbstractTransport
28+
{
29+
protected const HOST = 'api.brevo.com';
30+
31+
public function __construct(
32+
#[\SensitiveParameter] private readonly string $apiKey,
33+
private readonly string $sender,
34+
HttpClientInterface $client = null,
35+
EventDispatcherInterface $dispatcher = null
36+
) {
37+
parent::__construct($client, $dispatcher);
38+
}
39+
40+
public function __toString(): string
41+
{
42+
return sprintf('brevo://%s?sender=%s', $this->getEndpoint(), $this->sender);
43+
}
44+
45+
public function supports(MessageInterface $message): bool
46+
{
47+
return $message instanceof SmsMessage;
48+
}
49+
50+
protected function doSend(MessageInterface $message): SentMessage
51+
{
52+
if (!$message instanceof SmsMessage) {
53+
throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message);
54+
}
55+
56+
$sender = $message->getFrom() ?: $this->sender;
57+
58+
$response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/v3/transactionalSMS/sms', [
59+
'json' => [
60+
'sender' => $sender,
61+
'recipient' => $message->getPhone(),
62+
'content' => $message->getSubject(),
63+
],
64+
'headers' => [
65+
'api-key' => $this->apiKey,
66+
],
67+
]);
68+
69+
try {
70+
$statusCode = $response->getStatusCode();
71+
} catch (TransportExceptionInterface $e) {
72+
throw new TransportException('Could not reach the remote Brevo server.', $response, 0, $e);
73+
}
74+
75+
if (201 !== $statusCode) {
76+
$error = $response->toArray(false);
77+
78+
throw new TransportException('Unable to send the SMS: '.$error['message'], $response);
79+
}
80+
81+
$success = $response->toArray(false);
82+
83+
$sentMessage = new SentMessage($message, (string) $this);
84+
$sentMessage->setMessageId($success['messageId']);
85+
86+
return $sentMessage;
87+
}
88+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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\Brevo;
13+
14+
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
15+
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
16+
use Symfony\Component\Notifier\Transport\Dsn;
17+
18+
/**
19+
* @author Pierre Tanguy
20+
*/
21+
final class BrevoTransportFactory extends AbstractTransportFactory
22+
{
23+
public function create(Dsn $dsn): BrevoTransport
24+
{
25+
$scheme = $dsn->getScheme();
26+
27+
if ('brevo' !== $scheme) {
28+
throw new UnsupportedSchemeException($dsn, 'brevo', $this->getSupportedSchemes());
29+
}
30+
31+
$apiKey = $this->getUser($dsn);
32+
$sender = $dsn->getRequiredOption('sender');
33+
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
34+
$port = $dsn->getPort();
35+
36+
return (new BrevoTransport($apiKey, $sender, $this->client, $this->dispatcher))->setHost($host)->setPort($port);
37+
}
38+
39+
protected function getSupportedSchemes(): array
40+
{
41+
return ['brevo'];
42+
}
43+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
6.4
5+
---
6+
7+
* Add the bridge
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2023-present 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.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Brevo Notifier
2+
===============
3+
4+
Provides [Brevo](https://brevo.com) integration for Symfony Notifier.
5+
This bridge was created following the rebranding of Sendinblue.
6+
7+
DSN example
8+
-----------
9+
10+
```
11+
BREVO_DSN=brevo://API_KEY@default?sender=SENDER
12+
```
13+
14+
where:
15+
- `API_KEY` is your api key from your Brevo account
16+
- `SENDER` is your sender's phone number
17+
18+
See more info at https://developers.brevo.com/reference/sendtransacsms
19+
20+
Resources
21+
---------
22+
23+
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
24+
* [Report issues](https://github.com/symfony/symfony/issues) and
25+
[send Pull Requests](https://github.com/symfony/symfony/pulls)
26+
in the [main Symfony repository](https://github.com/symfony/symfony)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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\Brevo\Tests;
13+
14+
use Symfony\Component\Notifier\Bridge\Brevo\BrevoTransportFactory;
15+
use Symfony\Component\Notifier\Test\TransportFactoryTestCase;
16+
17+
final class BrevoTransportFactoryTest extends TransportFactoryTestCase
18+
{
19+
public function createFactory(): BrevoTransportFactory
20+
{
21+
return new BrevoTransportFactory();
22+
}
23+
24+
public static function createProvider(): iterable
25+
{
26+
yield [
27+
'brevo://host.test?sender=0611223344',
28+
'brevo://apiKey@host.test?sender=0611223344',
29+
];
30+
}
31+
32+
public static function supportsProvider(): iterable
33+
{
34+
yield [true, 'brevo://apiKey@default?sender=0611223344'];
35+
yield [false, 'somethingElse://apiKey@default?sender=0611223344'];
36+
}
37+
38+
public static function incompleteDsnProvider(): iterable
39+
{
40+
yield 'missing api_key' => ['brevo://default?sender=0611223344'];
41+
}
42+
43+
public static function missingRequiredOptionProvider(): iterable
44+
{
45+
yield 'missing option: sender' => ['brevo://apiKey@host.test'];
46+
}
47+
48+
public static function unsupportedSchemeProvider(): iterable
49+
{
50+
yield ['somethingElse://apiKey@default?sender=0611223344'];
51+
yield ['somethingElse://apiKey@host']; // missing "sender" option
52+
}
53+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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\Brevo\Tests;
13+
14+
use Symfony\Component\HttpClient\MockHttpClient;
15+
use Symfony\Component\Notifier\Bridge\Brevo\BrevoTransport;
16+
use Symfony\Component\Notifier\Exception\TransportException;
17+
use Symfony\Component\Notifier\Message\ChatMessage;
18+
use Symfony\Component\Notifier\Message\SmsMessage;
19+
use Symfony\Component\Notifier\Test\TransportTestCase;
20+
use Symfony\Component\Notifier\Tests\Transport\DummyMessage;
21+
use Symfony\Contracts\HttpClient\HttpClientInterface;
22+
use Symfony\Contracts\HttpClient\ResponseInterface;
23+
24+
final class BrevoTransportTest extends TransportTestCase
25+
{
26+
public static function createTransport(HttpClientInterface $client = null): BrevoTransport
27+
{
28+
return (new BrevoTransport('api-key', '0611223344', $client ?? new MockHttpClient()))->setHost('host.test');
29+
}
30+
31+
public static function toStringProvider(): iterable
32+
{
33+
yield ['brevo://host.test?sender=0611223344', self::createTransport()];
34+
}
35+
36+
public static function supportedMessagesProvider(): iterable
37+
{
38+
yield [new SmsMessage('0611223344', 'Hello!')];
39+
}
40+
41+
public static function unsupportedMessagesProvider(): iterable
42+
{
43+
yield [new ChatMessage('Hello!')];
44+
yield [new DummyMessage()];
45+
}
46+
47+
public function testSendWithErrorResponseThrowsTransportException()
48+
{
49+
$response = $this->createMock(ResponseInterface::class);
50+
$response->expects($this->exactly(2))
51+
->method('getStatusCode')
52+
->willReturn(400);
53+
$response->expects($this->once())
54+
->method('getContent')
55+
->willReturn(json_encode(['code' => 400, 'message' => 'bad request']));
56+
57+
$client = new MockHttpClient(static fn (): ResponseInterface => $response);
58+
59+
$transport = self::createTransport($client);
60+
61+
$this->expectException(TransportException::class);
62+
$this->expectExceptionMessage('Unable to send the SMS: bad request');
63+
64+
$transport->send(new SmsMessage('phone', 'testMessage'));
65+
}
66+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "symfony/brevo-notifier",
3+
"type": "symfony-notifier-bridge",
4+
"description": "Symfony Brevo Notifier Bridge",
5+
"keywords": ["brevo", "notifier"],
6+
"homepage": "https://symfony.com",
7+
"license": "MIT",
8+
"authors": [
9+
{
10+
"name": "Pierre Tanguy",
11+
"homepage": "https://github.com/petanguy"
12+
},
13+
{
14+
"name": "Symfony Community",
15+
"homepage": "https://symfony.com/contributors"
16+
}
17+
],
18+
"require": {
19+
"php": ">=8.1",
20+
"symfony/http-client": "^5.4|^6.0",
21+
"symfony/notifier": "^6.4"
22+
},
23+
"require-dev": {
24+
"symfony/event-dispatcher": "^5.4|^6.0"
25+
},
26+
"autoload": {
27+
"psr-4": {"Symfony\\Component\\Notifier\\Bridge\\Brevo\\": ""},
28+
"exclude-from-classmap": [
29+
"/Tests/"
< 4EB7 /code>
30+
]
31+
},
32+
"minimum-stability": "dev"
33+
}
34+

0 commit comments

Comments
 (0)
0