Skip to content

Commit f24ba7f

Browse files
committed
Fixed one escaped mutant in ImportConnector::createExceptionHandler.
Fixed some grammar errors in Readme. Updated Porter graphic with transparent background.
1 parent 624c450 commit f24ba7f

File tree

5 files changed

+44
-46
lines changed

5 files changed

+44
-46
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
!/.github/
44
/composer.lock
55
/*.log
6+
.phpunit.result.cache

README.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@ About this manual
6969

7070
Those wishing to consume a Porter provider create one instance of `Porter` for their application and an instance of `ImportSpecification` for each data import they wish to perform. Those publishing providers must implement `Provider` and `ProviderResource`.
7171

72-
The first half of this manual covers Porter's main API for consuming data services. The second half covers architecture, interface and implementation details for publishing data services. There's an intermission in-between so you'll know where the separation is!
72+
The first half of this manual covers Porter's main API for consuming data services. The second half covers architecture, interface and implementation details for publishing data services. There's an intermission in-between, so you'll know where the separation is!
7373

7474
Text marked as `inline code` denotes literal code, as it would appear in a PHP file. For example, `Porter` refers specifically to the class of the same name within this library, whereas *Porter* refers to this project as a whole.
7575

76-
Porter has a dual API for the synchronous and asynchronous workflow dichotomy. Almost every Porter feature has an asynchronous equivalent. You will need to choose which you prefer to use. Most examples in this manual are for the synchronous API, for brevity and simplicity, but the asynchronous version will invariably be similar. Working synchronously may be easier when getting started but you are encouraged to use the async API if you are able, to reap its benefits.
76+
Porter has a dual API for the synchronous and asynchronous workflow dichotomy. Almost every Porter feature has an asynchronous equivalent. You will need to choose which you prefer to use. Most examples in this manual are for the synchronous API, for brevity and simplicity, but the asynchronous version will invariably be similar. Working synchronously may be easier when getting started, but you are encouraged to use the async API if you are able, to reap its benefits.
7777

7878
Usage
7979
-----
@@ -134,12 +134,12 @@ The following data flow diagram gives a high level overview of Porter's main int
134134

135135
</div>
136136

137-
Our application calls `Porter::import()` with an `ImportSpecification` and receives `PorterRecords` back. Everything else happens internally so we don't need to worry about it unless writing custom providers, resources or connectors.
137+
Our application calls `Porter::import()` with an `ImportSpecification` and receives `PorterRecords` back. Everything else happens internally, so we don't need to worry about it unless writing custom providers, resources or connectors.
138138

139139
Import specifications
140140
---------------------
141141

142-
Import specifications specify *what* to import, *how* it should be [transformed](#transformers) and whether to use [caching](#caching). In synchronous code, create an new instance of `ImportSpecification` and pass a `ProviderResource` that specifies the resource we want to import. In Asynchronous code, create `AsyncImportSpecification` instead.
142+
Import specifications specify *what* to import, *how* it should be [transformed](#transformers) and whether to use [caching](#caching). In synchronous code, create a new instance of `ImportSpecification` and pass a `ProviderResource` that specifies the resource we want to import. In Asynchronous code, create `AsyncImportSpecification` instead.
143143

144144
Options may be configured using the methods below.
145145

@@ -232,14 +232,14 @@ public function transformAsync(AsyncRecordCollection $records, mixed $context):
232232

233233
When `transform()` or `transformAsync()` is called the transformer may iterate each record and change it in any way, including removing or inserting additional records. The record collection must be returned by the method, whether or not changes were made.
234234

235-
Transformers should also implement the `__clone` magic method if the they store any object state, in order to facilitate deep copy when Porter clones the owning `ImportSpecification` during import.
235+
Transformers should also implement the `__clone` magic method if they store any object state, in order to facilitate deep copy when Porter clones the owning `ImportSpecification` during import.
236236

237237
Filtering
238238
---------
239239

240240
Filtering provides a way to remove some records. For each record, if the specified predicate function returns `false` (or a falsy value), the record will be removed, otherwise the record will be kept. The predicate receives the current record as an array as its first parameter and context as its second parameter.
241241

242-
In general we would like to avoid filtering because it is inefficient to import data and then immediately remove some of it, but some immature APIs do not provide a way to reduce the data set on the server, so filtering on the client is the only alternative. Filtering also invalidates the record count reported by some resources, meaning we no longer know how many records are in the collection before iteration.
242+
In general, we would like to avoid filtering because it is inefficient to import data and then immediately remove some of it, but some immature APIs do not provide a way to reduce the data set on the server, so filtering on the client is the only alternative. Filtering also invalidates the record count reported by some resources, meaning we no longer know how many records are in the collection before iteration.
243243

244244
### Example
245245

@@ -330,7 +330,7 @@ Providers must implement the `Provider` interface and supply a valid connector w
330330

331331
#### Implementation example
332332

333-
In the following example we create a provider that only accepts `HttpConnector` instances. We also create a default connector in case one is not supplied. Note it is not always possible to create a default connector and it is perfectly valid to insist the caller supplies a connector.
333+
In the following example we create a provider that only accepts `HttpConnector` instances. We also create a default connector in case one is not supplied. Note it is not always possible to create a default connector, and it is perfectly valid to insist the caller supplies a connector.
334334

335335
```php
336336
final class MyProvider implements Provider
@@ -511,7 +511,7 @@ Everyone is welcome to contribute anything, from [ideas and issues][Issues] to [
511511
License
512512
-------
513513

514-
Porter is published under the open source GNU Lesser General Public License v3.0. However, the original Porter character and artwork is copyright &copy; 2019 [Bilge](https://github.com/Bilge) and may not be reproduced or modified without express written permission.
514+
Porter is published under the open source GNU Lesser General Public License v3.0. However, the original Porter character and artwork is copyright &copy; 2022 [Bilge](https://github.com/Bilge) and may not be reproduced or modified without express written permission.
515515

516516
###### Quick links
517517

docs/images/porter 222x.png

27.3 KB
Loading

test/Integration/Connector/ImportConnectorTest.php

+33-36
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
use ScriptFUSION\Porter\Connector\Connector;
1111
use ScriptFUSION\Porter\Connector\DataSource;
1212
use ScriptFUSION\Porter\Connector\ImportConnector;
13-
use ScriptFUSION\Porter\Connector\Recoverable\RecoverableExceptionHandler;
1413
use ScriptFUSION\Porter\Connector\Recoverable\StatelessRecoverableExceptionHandler;
15-
use ScriptFUSION\Retry\FailingTooHardException;
1614
use ScriptFUSIONTest\FixtureFactory;
1715
use ScriptFUSIONTest\Stubs\TestRecoverableException;
1816
use ScriptFUSIONTest\Stubs\TestRecoverableExceptionHandler;
@@ -64,7 +62,7 @@ public function provideHandlerAndConnector(): \Generator
6462
$connector = FixtureFactory::buildImportConnector(
6563
\Mockery::mock(Connector::class)
6664
->shouldReceive('fetch')
67-
->andReturnUsing($this->createExceptionThrowingClosure())
65+
->andReturnUsing(self::createExceptionThrowingClosure())
6866
->getMock(),
6967
$handler
7068
),
@@ -97,9 +95,7 @@ public function testStatelessExceptionHandlerNotCloned(): void
9795
self::assertSame(
9896
$handler,
9997
\Closure::bind(
100-
function (): RecoverableExceptionHandler {
101-
return $this->userExceptionHandler;
102-
},
98+
fn () => $this->userExceptionHandler,
10399
$connector,
104100
$connector
105101
)()
@@ -118,14 +114,13 @@ public function testExceptionHandlerCannotCancelRetries(): void
118114
->shouldReceive('fetch')
119115
->andThrow(new TestRecoverableException)
120116
->getMock(),
121-
new StatelessRecoverableExceptionHandler(static function () {
122-
return false;
123-
})
117+
new StatelessRecoverableExceptionHandler(fn () => false)
124118
)->fetch($this->source);
125119
}
126120

127121
/**
128-
* Tests that when a user recoverable exception handler returns a promise, the promise is resolved.
122+
* Tests that when a user recoverable exception handler throws an exception, the handler's exception can be
123+
* captured.
129124
*/
130125
public function testAsyncUserRecoverableExceptionHandler(): void
131126
{
@@ -134,20 +129,21 @@ public function testAsyncUserRecoverableExceptionHandler(): void
134129
->shouldReceive('fetchAsync')
135130
->andThrow(new TestRecoverableException)
136131
->getMock(),
137-
self::createAsyncRecoverableExceptionHandler()
132+
new StatelessRecoverableExceptionHandler(
133+
self::createExceptionThrowingClosure($exception = new TestRecoverableException)
134+
)
138135
);
139136

140137
try {
141138
$connector->fetchAsync($this->asyncSource);
142-
} catch (FailingTooHardException $exception) {
143-
// This is fine.
139+
} catch (\Exception $e) {
140+
self::assertSame($exception, $e);
144141
}
145-
146-
self::assertTrue(isset($exception));
147142
}
148143

149144
/**
150-
* Tests that when a resource recoverable exception handler returns a promise, the promise is resolved.
145+
* Tests that when a resource recoverable exception handler throws an exception, the handler's exception can be
146+
* captured.
151147
*/
152148
public function testAsyncResourceRecoverableExceptionHandler(): void
153149
{
@@ -158,20 +154,20 @@ public function testAsyncResourceRecoverableExceptionHandler(): void
158154
->getMock()
159155
);
160156

161-
$connector->setRecoverableExceptionHandler(self::createAsyncRecoverableExceptionHandler());
157+
$connector->setRecoverableExceptionHandler(new StatelessRecoverableExceptionHandler(
158+
self::createExceptionThrowingClosure($exception = new TestRecoverableException)
159+
));
162160

163161
try {
164162
$connector->fetchAsync($this->asyncSource);
165-
} catch (FailingTooHardException $exception) {
166-
// This is fine.
163+
} catch (\Exception $e) {
164+
self::assertSame($exception, $e);
167165
}
168-
169-
self::assertTrue(isset($exception));
170166
}
171167

172168
/**
173-
* Tests that when user and resource recoverable exception handlers both return promises, both promises are
174-
* resolved.
169+
* Tests that when user and resource recoverable exception handlers are both set, both handlers are invoked,
170+
* resource handler first and user handler second.
175171
*/
176172
public function testAsyncUserAndResourceRecoverableExceptionHandlers(): void
177173
{
@@ -180,36 +176,37 @@ public function testAsyncUserAndResourceRecoverableExceptionHandlers(): void
180176
->shouldReceive('fetchAsync')
181177
->andThrow(new TestRecoverableException)
182178
->getMock(),
183-
self::createAsyncRecoverableExceptionHandler()
179+
new StatelessRecoverableExceptionHandler(self::createExceptionThrowingClosure($e2 = new \Exception))
184180
);
185181

186-
$connector->setRecoverableExceptionHandler(self::createAsyncRecoverableExceptionHandler());
182+
$connector->setRecoverableExceptionHandler(new StatelessRecoverableExceptionHandler(
183+
self::createExceptionThrowingClosure($e1 = new \Exception())
184+
));
187185

188186
try {
189187
$connector->fetchAsync($this->asyncSource);
190-
} catch (FailingTooHardException $exception) {
191-
// This is fine.
188+
} catch (\Exception $exception) {
189+
self::assertSame($e1, $exception);
192190
}
193191

194-
self::assertTrue(isset($exception));
192+
try {
193+
$connector->fetchAsync($this->asyncSource);
194+
} catch (\Exception $exception) {
195+
self::assertSame($e2, $exception);
196+
}
195197
}
196198

197199
/**
198200
* Creates a closure that only throws an exception on the first invocation.
199201
*/
200-
private static function createExceptionThrowingClosure(): \Closure
202+
private static function createExceptionThrowingClosure(\Exception $exception = null): \Closure
201203
{
202-
return static function (): void {
204+
return static function () use ($exception): void {
203205
static $invocationCount;
204206

205207
if (!$invocationCount++) {
206-
throw new TestRecoverableException;
208+
throw $exception ?? new TestRecoverableException;
207209
}
208210
};
209211
}
210-
211-
private static function createAsyncRecoverableExceptionHandler(): RecoverableExceptionHandler
212-
{
213-
return new StatelessRecoverableExceptionHandler(fn () => null);
214-
}
215212
}

test/MockFactory.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
namespace ScriptFUSIONTest;
55

6-
use Amp\Future;
76
use Mockery\MockInterface;
87
use ScriptFUSION\Async\Throttle\Throttle;
98
use ScriptFUSION\Porter\Connector\AsyncConnector;
@@ -17,6 +16,7 @@
1716
use ScriptFUSION\Porter\Provider\Resource\ProviderResource;
1817
use ScriptFUSION\Porter\Provider\Resource\SingleRecordResource;
1918
use ScriptFUSION\StaticClass;
19+
use function Amp\async;
2020
use function Amp\delay;
2121

2222
final class MockFactory
@@ -92,7 +92,7 @@ public static function mockThrottle(): Throttle|MockInterface
9292
{
9393
return \Mockery::mock(Throttle::class)
9494
->allows('watch')
95-
->andReturnUsing(fn (\Closure $closure, mixed ...$args) => $closure($args))
95+
->andReturnUsing(fn (\Closure $closure, mixed ...$args) => async($closure, ...$args))
9696
->byDefault()
9797
->getMock()
9898
;

0 commit comments

Comments
 (0)