Skip to content

Commit

Permalink
Create transport
Browse files Browse the repository at this point in the history
  • Loading branch information
parijke committed May 19, 2024
1 parent 6617de8 commit 8b926fa
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CHANGELOG
=========

6.4
---

* Add the bridge
94 changes: 94 additions & 0 deletions SpryngTransport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Notifier\Bridge\Spryng;

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\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
* @author Paul Rijke
*/
final class SpryngTransport extends AbstractTransport
{
protected const HOST = 'rest.spryngsms.com';

public function __construct(
#[\SensitiveParameter] private readonly string $apiKey,
private readonly string $sender,
private readonly int|string $route,
?HttpClientInterface $client = null,
?EventDispatcherInterface $dispatcher = null
) {
parent::__construct($client, $dispatcher);
}

public function __toString(): string
{
return sprintf('spryng://%s?sender=%s&route=%s', $this->getEndpoint(), $this->sender, $this->route);
}

public function supports(MessageInterface $message): bool
{
return $message instanceof SmsMessage;
}

protected function doSend(MessageInterface $message): SentMessage
{
if (!$message instanceof SmsMessage) {
throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message);
}

$sender = $message->getFrom() ?: $this->sender;

$response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/v1/messages', [
'json' => [
'sender' => $sender,
'encoding' => 'auto',
'originator' => $sender,
'recipients' => [$message->getPhone()],
'body' => $message->getSubject(),
'route' => $this->route ?? 'business',
],
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => "Bearer {$this->apiKey}",
],
]);

try {
$statusCode = $response->getStatusCode();
} catch (TransportExceptionInterface $e) {
throw new TransportException('Could not reach the remote Spryng server.', $response, 0, $e);
}

if (201 !== $statusCode) {
$error = $response->toArray(false);

throw new TransportException('Unable to send the SMS: '.($error['message'] ?? $response->getContent(false)), $response);
}

$success = $response->toArray(false);

$sentMessage = new SentMessage($message, (string) $this);
$sentMessage->setMessageId($success['messageId']);

return $sentMessage;
}
}
44 changes: 44 additions & 0 deletions SpryngTransportFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Notifier\Bridge\Spryng;

use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;

/**
* @author Paul Rijke
*/
final class SpryngTransportFactory extends AbstractTransportFactory
{
public function create(Dsn $dsn): SpryngTransport
{
$scheme = $dsn->getScheme();

if ('spryng' !== $scheme) {
throw new UnsupportedSchemeException($dsn, 'spryng', $this->getSupportedSchemes());
}

$apiKey = $this->getUser($dsn);
$sender = $dsn->getRequiredOption('sender');
$route = $dsn->getOption('route') ?? 'business';
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();

return (new SpryngTransport($apiKey, $sender, $route, $this->client, $this->dispatcher))->setHost($host)->setPort($port);
}

protected function getSupportedSchemes(): array
{
return ['spryng'];
}
}
53 changes: 53 additions & 0 deletions Tests/SpryngTransportFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Notifier\Bridge\Spryng\Tests;

use Symfony\Component\Notifier\Bridge\Spryng\SpryngTransportFactory;
use Symfony\Component\Notifier\Test\TransportFactoryTestCase;

final class SpryngTransportFactoryTest extends TransportFactoryTestCase
{
public function createFactory(): SpryngTransportFactory
{
return new SpryngTransportFactory();
}

public static function createProvider(): iterable
{
yield [
'spryng://host.test?sender=0611223344&route=1234',
'spryng://[email protected]?sender=0611223344&route=1234',
];
}

public static function supportsProvider(): iterable
{
yield [true, 'spryng://apiKey@default?sender=0611223344'];
yield [false, 'somethingElse://apiKey@default?sender=0611223344'];
}

public static function incompleteDsnProvider(): iterable
{
yield 'missing api_key' => ['spryng://default?sender=0611223344'];
}

public static function missingRequiredOptionProvider(): iterable
{
yield 'missing option: sender' => ['spryng://[email protected]'];
}

public static function unsupportedSchemeProvider(): iterable
{
yield ['somethingElse://apiKey@default?sender=0611223344'];
yield ['somethingElse://apiKey@host']; // missing "sender" option
}
}
67 changes: 67 additions & 0 deletions Tests/SpryngTransportTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Notifier\Bridge\Spryng\Tests;

use PHPUnit\Framework\MockObject\Generator\MockClass;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\Notifier\Bridge\Spryng\SpryngTransport;
use Symfony\Component\Notifier\Exception\TransportException;
use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Message\SmsMessage;
use Symfony\Component\Notifier\Test\TransportTestCase;
use Symfony\Component\Notifier\Tests\Transport\DummyMessage;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

final class SpryngTransportTest extends TransportTestCase
{
public static function createTransport(?HttpClientInterface $client = null): SpryngTransport
{
return (new SpryngTransport('api-key', '0611223344', 'business', $client ?? new MockHttpClient()))->setHost('host.test');
}

public static function toStringProvider(): iterable
{
yield ['spryng://host.test?sender=0611223344&route=business', self::createTransport()];
}

public static function supportedMessagesProvider(): iterable
{
yield [new SmsMessage('0611223344', 'Hello!')];
}

public static function unsupportedMessagesProvider(): iterable
{
yield [new ChatMessage('Hello!')];
// yield [new DummyMessage()];
}

public function testSendWithErrorResponseThrowsTransportException()
{
$response = $this->createMock(ResponseInterface::class);
$response->expects($this->exactly(2))
->method('getStatusCode')
->willReturn(400);
$response->expects($this->once())
->method('getContent')
->willReturn(json_encode(['code' => 400, 'message' => 'bad request']));

$client = new MockHttpClient(static fn (): ResponseInterface => $response);

$transport = self::createTransport($client);

$this->expectException(TransportException::class);
$this->expectExceptionMessage('Unable to send the SMS: bad request');

$transport->send(new SmsMessage('phone', 'testMessage'));
}
}
36 changes: 36 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "symfony/spryng-notifier",
"type": "symfony-notifier-bridge",
"description": "Symfony Spryng Notifier Bridge",
"keywords": ["spryng", "notifier"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Paul Rijke",
"email": "[email protected]",
"homepage": "https://github.com/parijke"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=8.2",
"symfony/http-client": "^6.4|^7.0",
"symfony/notifier": "^6.4|^7.0"
},
"require-dev": {
"symfony/event-dispatcher": "^5.4|^6.0|^7.0",
"symfony/phpunit-bridge": "7.1.x-dev",
"phpunit/phpunit": "^11.2@dev"
},
"autoload": {
"psr-4": {"Symfony\\Component\\Notifier\\Bridge\\Spryng\\": ""},
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev"
}

0 comments on commit 8b926fa

Please sign in to comment.