Skip to content

Commit 53be1c3

Browse files
committed
Switch to generic HTTP client
Using the Laravel Http facade limits us to Laravel 7+. Since we want to support Laravel 6, we'll use the generic Http client solution for now since it should work in all releases. Add Laravel 6 to GitHub workflow matrix Also update documentation to include developer notes.
1 parent 325b803 commit 53be1c3

File tree

9 files changed

+184
-47
lines changed

9 files changed

+184
-47
lines changed

.github/workflows/run-tests.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ jobs:
99
strategy:
1010
matrix:
1111
php: [7.4, 7.3]
12-
laravel: [8.*]
12+
laravel: [6.*, 8.*]
1313
include:
14+
- laravel: 6.*
15+
testbench: 4.*
1416
- laravel: 8.*
1517
testbench: 6.*
1618

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
.idea
55
.php_cs.cache
66
.phpunit.result.cache
7+
composer.lock

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Introduction
1+
# Laravel SNS Handler
22
This package provides an easy way of adding AWS SNS message handling to your Laravel application as a REST endpoint. The package can automatically confirm subscription requests and dispatches events when a message is received.
33

44
## How to create an SNS topic
@@ -75,3 +75,14 @@ This package adds a route to your application for incoming SNS requests. Note th
7575

7676
**Note: You will only be able to subscribe your endpoint if it can be reached from the AWS SNS service**
7777

78+
## Development
79+
This package is expected to work with supported versions of Laravel, including LTS releases. During development, you should be sure to run tests and validate expected behaviors under different releases. Since we use the `orchestra/testbench` package, you can easily switch between installed Laravel framework releases using `composer`:
80+
81+
```bash
82+
# Laravel 6
83+
composer require --dev orchestra/testbench:^4.0 -W
84+
# Laravel 8
85+
composer require --dev orchestra/testbench:^6.0 -W
86+
```
87+
88+
New releases of Laravel should be added to the GitHub workflow matrix in `.github/workflows/run-tests.yml`.

composer.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616
"require": {
1717
"php": ">=7.3",
1818
"ext-json": "*",
19-
"aws/aws-php-sns-message-validator": "^1.6"
19+
"aws/aws-php-sns-message-validator": "^1.6",
20+
"php-http/discovery": "^1.14",
21+
"psr/http-factory": "^1.0",
22+
"psr/http-client": "^1.0"
2023
},
2124
"require-dev" : {
2225
"roave/security-advisories": "dev-latest",
23-
"guzzlehttp/guzzle": "^7.2",
24-
"orchestra/testbench": "^6.0"
26+
"orchestra/testbench": "^6.0",
27+
"http-interop/http-factory-guzzle": "^1.2",
28+
"php-http/guzzle6-adapter": "^2.0"
2529
},
2630
"autoload": {
2731
"psr-4": {

src/Listeners/SnsConfirmationRequestListener.php

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,67 @@
33

44
namespace Nipwaayoni\SnsHandler\Listeners;
55

6-
use Illuminate\Support\Facades\Http;
6+
use Http\Discovery\HttpClientDiscovery;
7+
use Http\Discovery\Psr17FactoryDiscovery;
78
use Illuminate\Support\Facades\Log;
89
use Nipwaayoni\SnsHandler\Events\SnsConfirmationRequestReceived;
910
use Nipwaayoni\SnsHandler\SnsConfirmSubscriptionException;
1011
use Nipwaayoni\SnsHandler\SnsException;
12+
use Nipwaayoni\SnsHandler\SnsMessage;
13+
use Psr\Http\Client\ClientExceptionInterface;
14+
use Psr\Http\Client\ClientInterface;
15+
use Psr\Http\Message\RequestFactoryInterface;
16+
use Psr\Http\Message\ResponseInterface;
1117

1218
class SnsConfirmationRequestListener
1319
{
20+
/**
21+
* @var ClientInterface
22+
*/
23+
private $client;
24+
25+
/**
26+
* @var RequestFactoryInterface
27+
*/
28+
private $requestFactory;
29+
30+
/**
31+
* @param ClientInterface|null $client
32+
* @param RequestFactoryInterface|null $requestFactory
33+
*/
34+
public function __construct(
35+
ClientInterface $client = null,
36+
RequestFactoryInterface $requestFactory = null
37+
) {
38+
$this->client = $client ?? HttpClientDiscovery::find();
39+
$this->requestFactory = $requestFactory ?? Psr17FactoryDiscovery::findRequestFactory();
40+
}
41+
1442
public function handle(SnsConfirmationRequestReceived $event)
1543
{
1644
$message = $event->message();
17-
//TODO Make this work with Laravel 6, as the Http facade was introduced in Laravel 7
45+
1846
$response = $this->getResponse($message);
19-
if ($response->successful()) {
20-
$info = sprintf('Subscription confirmation for %s succeeded with status %s', $message->topicArn(), $response->status());
21-
Log::info($info);
22-
return;
23-
}
24-
$error = sprintf('Subscription confirmation for %s failed with status %s', $message->topicArn(), $response->status());
25-
Log::error($error);
26-
throw new SnsConfirmSubscriptionException($error);
47+
48+
Log::info(sprintf('Subscription confirmation for %s succeeded with status %s', $message->topicArn(), $response->getStatusCode()));
2749
}
2850

2951
/**
30-
* @param \Nipwaayoni\SnsHandler\SnsMessage $message
31-
* @return \Illuminate\Http\Client\Response
32-
* @throws \Nipwaayoni\SnsHandler\SnsException
52+
* @param SnsMessage $message
53+
* @return ResponseInterface
54+
* @throws SnsConfirmSubscriptionException
55+
* @throws SnsException
3356
*/
34-
private function getResponse(\Nipwaayoni\SnsHandler\SnsMessage $message): \Illuminate\Http\Client\Response
57+
private function getResponse(SnsMessage $message): ResponseInterface
3558
{
36-
if (class_exists(Http::class)) {
37-
return Http::get($message->subscribeUrl());
59+
try {
60+
return $this->client->sendRequest(
61+
$this->requestFactory->createRequest('GET', $message->subscribeUrl())
62+
);
63+
} catch (ClientExceptionInterface $e) {
64+
throw new SnsConfirmSubscriptionException(
65+
sprintf('Subscription confirmation for %s failed with status %s', $message->topicArn(), $e->getCode())
66+
);
3867
}
39-
throw new SnsException("Unable to determine HTTP method");
4068
}
4169
}

tests/HttpTransaction.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
4+
namespace Nipwaayoni\Tests\SnsHandler;
5+
6+
use Psr\Http\Message\RequestInterface;
7+
use Psr\Http\Message\ResponseInterface;
8+
9+
class HttpTransaction
10+
{
11+
/**
12+
* @var array
13+
*/
14+
private $transaction;
15+
16+
public function __construct(array $transaction)
17+
{
18+
$this->transaction = $transaction;
19+
}
20+
21+
public function request(): RequestInterface
22+
{
23+
return $this->transaction['request'];
24+
}
25+
26+
public function response(): ResponseInterface
27+
{
28+
return $this->transaction['response'];
29+
}
30+
}

tests/HttpTransactionContainer.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
4+
namespace Nipwaayoni\Tests\SnsHandler;
5+
6+
class HttpTransactionContainer implements \ArrayAccess, \Countable
7+
{
8+
/**
9+
* @var array
10+
*/
11+
private $container = [];
12+
13+
public function offsetExists($offset): bool
14+
{
15+
return isset($this->container[$offset]);
16+
}
17+
18+
public function offsetGet($offset): HttpTransaction
19+
{
20+
if (!isset($this->container[$offset])) {
21+
throw new \Exception('Undefined transaction offset');
22+
}
23+
24+
return new HttpTransaction($this->container[$offset]);
25+
}
26+
27+
public function offsetSet($offset, $value): void
28+
{
29+
if (is_null($offset)) {
30+
$this->container[] = $value;
31+
} else {
32+
$this->container[$offset] = $value;
33+
}
34+
}
35+
36+
public function offsetUnset($offset): void
37+
{
38+
unset($this->container[$offset]);
39+
}
40+
41+
public function count(): int
42+
{
43+
return count($this->container);
44+
}
45+
}

tests/SnsHttpTestHelperTrait.php

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,41 @@
33

44
namespace Nipwaayoni\Tests\SnsHandler;
55

6-
use Illuminate\Http\Client\Request;
7-
use Illuminate\Support\Facades\Http;
8-
use Nipwaayoni\SnsHandler\SnsException;
6+
use GuzzleHttp\Handler\MockHandler;
7+
use GuzzleHttp\HandlerStack;
8+
use GuzzleHttp\Middleware;
9+
use GuzzleHttp\Psr7\Response;
10+
use Http\Client\HttpClient;
911

1012
trait SnsHttpTestHelperTrait
1113
{
12-
public function httpExpects(array $content = null): void
14+
/** @var HttpClient */
15+
private $client;
16+
17+
/** @var HttpTransactionContainer */
18+
private $container;
19+
20+
public function httpExpects(Response ...$responses): void
1321
{
14-
if (class_exists(Http::class)) {
15-
Http::fake($content);
16-
return;
17-
}
18-
throw new SnsException("Unable to determine HTTP method");
22+
$this->container = new HttpTransactionContainer();
23+
24+
$history = Middleware::history($this->container);
25+
26+
$mock = new MockHandler($responses);
27+
28+
$handlerStack = HandlerStack::create($mock);
29+
$handlerStack->push($history);
30+
31+
$client = new \GuzzleHttp\Client(['handler' => $handlerStack]);
32+
$this->client = new \Http\Adapter\Guzzle6\Client($client);
1933
}
2034

2135
public function httpAssertSent(callable $function): void
2236
{
23-
if (class_exists(Http::class)) {
24-
Http::assertSent($function);
25-
return;
26-
}
27-
throw new SnsException("Unable to determine HTTP method");
37+
$this->assertCount(1, $this->container);
38+
39+
$request = $this->container[0]->request();
40+
41+
$function($request);
2842
}
2943
}

tests/Unit/SnsConfirmationRequestListenerTest.php

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
namespace Nipwaayoni\Tests\SnsHandler\Unit;
55

66
use Aws\Sns\Message;
7-
use Illuminate\Http\Client\Request;
7+
use GuzzleHttp\Psr7\Response;
88
use Illuminate\Support\Facades\Event;
9-
use Illuminate\Support\Facades\Http;
109
use Illuminate\Support\Facades\Log;
1110
use Nipwaayoni\SnsHandler\Events\SnsConfirmationRequestReceived;
1211
use Nipwaayoni\SnsHandler\Listeners\SnsConfirmationRequestListener;
@@ -29,8 +28,6 @@ public function setUp(): void
2928
parent::setUp();
3029

3130
Event::fake();
32-
33-
$this->listener = new SnsConfirmationRequestListener();
3431
}
3532

3633
public function testThrowsExceptionIfConfirmSubscriptionFails(): void
@@ -40,36 +37,41 @@ public function testThrowsExceptionIfConfirmSubscriptionFails(): void
4037
'SubscribeURL' => 'https://aws.amazon.com/subscribe/123',
4138
]));
4239

43-
$this->httpExpects([
44-
'https://aws.amazon.com/subscribe/123' => Http::response([], 404, [])
45-
]);
40+
$this->httpExpects(
41+
new Response(404)
42+
);
43+
44+
$this->listener = new SnsConfirmationRequestListener($this->client);
4645

4746
$event = new SnsConfirmationRequestReceived(new SnsMessage($message));
4847

4948
$this->expectException(SnsConfirmSubscriptionException::class);
5049
$this->expectExceptionMessage('Subscription confirmation for arn:aws:sns:us-west-2:123456789012:MyTopic failed with status 404');
51-
Log::shouldReceive('error')->once();
5250

5351
$this->listener->handle($event);
5452
}
5553

5654
public function testConfirmsSubscriptionUsingSubscribeUrl(): void
5755
{
58-
$this->httpExpects(['https://aws.amazon.com/subscribe/123' => Http::response([], 200, [])]);
56+
$this->httpExpects(
57+
new Response(200)
58+
);
5959

6060
$message = Message::fromJsonString($this->makeSnsMessageJson([
6161
'Type' => SnsMessage::SUBSCRIBE_TYPE,
6262
'SubscribeURL' => 'https://aws.amazon.com/subscribe/123',
6363
]));
6464

65+
$this->listener = new SnsConfirmationRequestListener($this->client);
66+
6567
$event = new SnsConfirmationRequestReceived(new SnsMessage($message));
6668

6769
Log::shouldReceive('info')->once();
6870

6971
$this->listener->handle($event);
7072

71-
$this->httpAssertSent(function (Request $request) {
72-
return $request->url() === 'https://aws.amazon.com/subscribe/123';
73+
$this->httpAssertSent(function (\Psr\Http\Message\RequestInterface $request) {
74+
$this->assertEquals('https://aws.amazon.com/subscribe/123', $request->getUri());
7375
});
7476
}
7577
}

0 commit comments

Comments
 (0)