Skip to content

Commit eac6697

Browse files
t1gorMinishlink
authored andcommitted
Return results as a \Generator (#185)
* WIP: initial concept of returning report objects * WIP: add a test * Docs update Change results handling example * Attempt to fix tests #1 * Change flush() signature for easier mocking * Unqualify iterable * Remove ignored vagrant file
1 parent 3727db3 commit eac6697

8 files changed

+248
-96
lines changed

.github/CONTRIBUTING.md

+18-5
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,27 @@
22
WebPush is an open source library.
33
Feel free to contribute by submitting a pull request or creating (and solving) issues!
44

5-
## Running Tests
5+
## Installing a mock push service
6+
7+
Before running tests, you'll need to install the [web-push testing service](https://www.npmjs.com/package/web-push-testing-service):
8+
9+
```bash
10+
npm install web-push-testing-service -g
11+
```
12+
13+
NOTE: You might need to make sure command `web-push-testing-service` runs OK on cli. In my case on OSX, I needed to add a bash alias after install:
614

7-
First, you will need to create your own configuration file by copying
8-
phpunit.dist.xml to phpunit.xml and filling in the fields you need for
15+
```~/.bash_profile
16+
alias web-push-testing-service='/usr/local/Cellar/node/7.4.0/bin/web-push-testing-service'
17+
```
18+
19+
After that, please create your own configuration file by copying
20+
`phpunit.dist.xml` to phpunit.xml and filling in the fields you need for
921
testing (i.e. STANDARD_ENDPOINT, GCM_API_KEY etc.).
1022

11-
Then, download [phpunit](https://phpunit.de/) and test with one of the
12-
following commands:
23+
## Running Tests
24+
25+
Then, download [phpunit](https://phpunit.de/) and test with one of the following commands:
1326

1427
**For All Tests**
1528
`php phpunit.phar`

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@
22
composer.lock
33
phpunit.xml
44

5+
# web-push-testing-service logs
6+
cli.log
7+
module.log
8+
.vagrant
9+
Vagrantfile # temp, may be?

.travis.yml

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ sudo: required
88
cache:
99
directories:
1010
- ~/.selenium-assistant
11+
- node_modules # cache modules too
12+
- $HOME/.composer/cache/files # and composer packages
1113

1214
matrix:
1315
allow_failures:

README.md

+19-3
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,26 @@ foreach ($notifications as $notification) {
6868
$notification['payload'] // optional (defaults null)
6969
);
7070
}
71-
$webPush->flush();
7271

73-
// send one notification and flush directly
74-
$webPush->sendNotification(
72+
/**
73+
* Check sent results
74+
* @var MessageSentReport $report
75+
*/
76+
foreach ($webPush->flush() as $report) {
77+
$endpoint = $report->getRequest()->getUri()->__toString();
78+
79+
if ($report->isSuccess()) {
80+
echo "[v] Message sent successfully for subscription {$endpoint}.";
81+
} else {
82+
echo "[x] Message failed to sent for subscription {$endpoint}: {$report->getReason()}";
83+
}
84+
}
85+
86+
/**
87+
* send one notification and flush directly
88+
* @var \Generator<MessageSentReport> $sent
89+
*/
90+
$sent = $webPush->sendNotification(
7591
$notifications[0]['subscription'],
7692
$notifications[0]['payload'], // optional (defaults null)
7793
true // optional (defaults false)

src/MessageSentReport.php

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
/**
3+
* @author Igor Timoshenkov [[email protected]]
4+
* @started: 03.09.2018 9:21
5+
*/
6+
7+
namespace Minishlink\WebPush;
8+
9+
use Psr\Http\Message\RequestInterface;
10+
use Psr\Http\Message\ResponseInterface;
11+
12+
/**
13+
* Standardized response from sending a message
14+
*/
15+
class MessageSentReport {
16+
17+
/**
18+
* @var boolean
19+
*/
20+
protected $success;
21+
22+
/**
23+
* @var RequestInterface
24+
*/
25+
protected $request;
26+
27+
/**
28+
* @var ResponseInterface
29+
*/
30+
protected $response;
31+
32+
/**
33+
* @var string
34+
*/
35+
protected $reason;
36+
37+
/**
38+
* @param RequestInterface $request
39+
* @param ResponseInterface $response
40+
* @param bool $success
41+
* @param string $reason
42+
*/
43+
public function __construct(?RequestInterface $request = null, ?ResponseInterface $response = null, bool $success = true, $reason = 'OK') {
44+
$this->success = $success;
45+
$this->request = $request;
46+
$this->response = $response;
47+
$this->reason = $reason;
48+
}
49+
50+
/**
51+
* @return bool
52+
*/
53+
public function isSuccess(): bool {
54+
return $this->success;
55+
}
56+
57+
/**
58+
* @param bool $success
59+
*
60+
* @return MessageSentReport
61+
*/
62+
public function setSuccess(bool $success): MessageSentReport {
63+
$this->success = $success;
64+
return $this;
65+
}
66+
67+
/**
68+
* @return RequestInterface
69+
*/
70+
public function getRequest(): RequestInterface {
71+
return $this->request;
72+
}
73+
74+
/**
75+
* @param RequestInterface $request
76+
*
77+
* @return MessageSentReport
78+
*/
79+
public function setRequest(RequestInterface $request): MessageSentReport {
80+
$this->request = $request;
81+
return $this;
82+
}
83+
84+
/**
85+
* @return ResponseInterface
86+
*/
87+
public function getResponse(): ResponseInterface {
88+
return $this->response;
89+
}
90+
91+
/**
92+
* @param ResponseInterface $response
93+
*
94+
* @return MessageSentReport
95+
*/
96+
public function setResponse(ResponseInterface $response): MessageSentReport {
97+
$this->response = $response;
98+
return $this;
99+
}
100+
101+
/**
102+
* @return string
103+
*/
104+
public function getEndpoint(): string {
105+
return $this->request->getUri()->__toString();
106+
}
107+
108+
/**
109+
* @return bool
110+
*/
111+
public function isSubscriptionExpired(): bool {
112+
return \in_array($this->response->getStatusCode(), [404, 410], true);
113+
}
114+
115+
/**
116+
* @return string
117+
*/
118+
public function getReason(): string {
119+
return $this->reason;
120+
}
121+
122+
/**
123+
* @param string $reason
124+
*
125+
* @return MessageSentReport
126+
*/
127+
public function setReason(string $reason): MessageSentReport {
128+
$this->reason = $reason;
129+
return $this;
130+
}
131+
}

src/WebPush.php

+35-75
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use GuzzleHttp\Client;
1818
use GuzzleHttp\Exception\RequestException;
1919
use GuzzleHttp\Psr7\Request;
20-
use GuzzleHttp\Promise;
20+
use Psr\Http\Message\ResponseInterface;
2121

2222
class WebPush
2323
{
@@ -121,93 +121,53 @@ public function sendNotification(Subscription $subscription, ?string $payload =
121121

122122
$this->notifications[] = new Notification($subscription, $payload, $options, $auth);
123123

124-
if ($flush) {
125-
$res = $this->flush();
126-
127-
// if there has been a problem with at least one notification
128-
if (is_array($res)) {
129-
// if there was only one notification, return the information directly
130-
if (count($res) === 1) {
131-
return $res[0];
132-
}
133-
134-
return $res;
135-
}
136-
137-
return true;
138-
}
139-
140-
return true;
124+
return false !== $flush ? $this->flush() : true;
141125
}
142126

143-
/**
144-
* Flush notifications. Triggers the requests.
145-
*
146-
* @param null|int $batchSize Defaults the value defined in defaultOptions during instantiation (which defaults to 1000).
147-
*
148-
* @return array|bool If there are no errors, return true.
149-
* If there were no notifications in the queue, return false.
150-
* Else return an array of information for each notification sent (success, statusCode, headers, content)
151-
*
152-
* @throws \ErrorException
153-
*/
154-
public function flush(?int $batchSize = null)
127+
/**
128+
* Flush notifications. Triggers the requests.
129+
*
130+
* @param null|int $batchSize Defaults the value defined in defaultOptions during instantiation (which defaults to 1000).
131+
*
132+
* @return iterable
133+
* @throws \ErrorException
134+
*/
135+
public function flush(?int $batchSize = null) : iterable
155136
{
156137
if (empty($this->notifications)) {
157-
return false;
138+
yield from [];
158139
}
159140

160141
if (null === $batchSize) {
161142
$batchSize = $this->defaultOptions['batchSize'];
162143
}
163144

164145
$batches = array_chunk($this->notifications, $batchSize);
165-
$return = [];
166-
$completeSuccess = true;
167-
foreach ($batches as $batch) {
168-
// for each endpoint server type
169-
$requests = $this->prepare($batch);
170-
$promises = [];
171-
foreach ($requests as $request) {
172-
$promises[] = $this->client->sendAsync($request);
173-
}
174-
$results = Promise\settle($promises)->wait();
175-
176-
foreach ($results as $result) {
177-
if ($result['state'] === "rejected") {
178-
/** @var RequestException $reason **/
179-
$reason = $result['reason'];
180-
181-
$error = [
182-
'success' => false,
183-
'endpoint' => $reason->getRequest()->getUri(),
184-
'message' => $reason->getMessage(),
185-
];
186-
187-
$response = $reason->getResponse();
188-
if ($response !== null) {
189-
$statusCode = $response->getStatusCode();
190-
$error['statusCode'] = $statusCode;
191-
$error['reasonPhrase'] = $response->getReasonPhrase();
192-
$error['expired'] = in_array($statusCode, [404, 410]);
193-
$error['content'] = $response->getBody();
194-
$error['headers'] = $response->getHeaders();
195-
}
196146

197-
$return[] = $error;
198-
$completeSuccess = false;
199-
} else {
200-
$return[] = [
201-
'success' => true,
202-
];
203-
}
204-
}
205-
}
206-
207-
// reset queue
208-
$this->notifications = null;
147+
// reset queue
148+
$this->notifications = [];
209149

210-
return $completeSuccess ? true : $return;
150+
foreach ($batches as $batch) {
151+
// for each endpoint server type
152+
$requests = $this->prepare($batch);
153+
154+
foreach ($requests as $request) {
155+
$result = null;
156+
157+
$this->client->sendAsync($request)
158+
->then(function ($response) use ($request, &$result) {
159+
/** @var ResponseInterface $response * */
160+
$result = new MessageSentReport($request, $response);
161+
})
162+
->otherwise(function ($reason) use (&$result) {
163+
/** @var RequestException $reason **/
164+
$result = new MessageSentReport($reason->getRequest(), $reason->getResponse(), false, $reason->getMessage());
165+
})
166+
->wait(false);
167+
168+
yield $result;
169+
}
170+
}
211171
}
212172

213173
/**

tests/PushServiceTest.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,12 @@ protected function createClosureTest($browserId, $browserVersion, $options)
178178

179179
try {
180180
$sendResp = $this->webPush->sendNotification($subscription, $payload, true);
181-
$this->assertTrue($sendResp);
181+
$this->assertInstanceOf(\Generator::class, $sendResp);
182+
183+
/** @var \Minishlink\WebPush\MessageSentReport $report */
184+
foreach ($sendResp as $report) {
185+
$this->assertTrue($report->isSuccess());
186+
}
182187

183188
$dataString = json_encode([
184189
'testSuiteId' => self::$testSuiteId,

0 commit comments

Comments
 (0)