Skip to content

Commit 6c4dcd9

Browse files
committed
Moved fetch retries out of connectors into Porter.
1 parent 52fc501 commit 6c4dcd9

File tree

7 files changed

+109
-95
lines changed

7 files changed

+109
-95
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ script:
2121

2222
# Remove mapper and run all non-Mapper tests again.
2323
- composer --dev --no-update-with-dependencies remove scriptfusion/mapper
24-
- '! composer info | awk ''{print $1}'' | grep ^scriptfusion/mapper'
24+
- '! composer info | grep ^scriptfusion/mapper\\b'
2525
- bin/test --exclude-group Mapper
2626

2727
after_success:

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"require": {
1212
"php": "^5.5|^7",
1313
"scriptfusion/static-class": "^1",
14+
"scriptfusion/retry": "^1.1",
1415
"scriptfusion/retry-error-handlers": "^1",
1516
"eloquent/enumeration": "^5",
1617
"psr/cache": "^1",

src/Net/Http/HttpConnector.php

Lines changed: 23 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
use ScriptFUSION\Porter\Connector\CachingConnector;
55
use ScriptFUSION\Porter\Net\UrlBuilder;
66
use ScriptFUSION\Porter\Options\EncapsulatedOptions;
7-
use ScriptFUSION\Retry\ErrorHandler\ExponentialBackoffErrorHandler;
87

98
/**
109
* Fetches data from an HTTP server via the PHP wrapper.
@@ -14,8 +13,6 @@
1413
*/
1514
class HttpConnector extends CachingConnector
1615
{
17-
const DEFAULT_TRIES = 5;
18-
1916
/** @var HttpOptions */
2017
private $options;
2118

@@ -25,9 +22,6 @@ class HttpConnector extends CachingConnector
2522
/** @var string */
2623
private $baseUrl;
2724

28-
/** @var int */
29-
private $tries = self::DEFAULT_TRIES;
30-
3125
public function __construct(HttpOptions $options = null)
3226
{
3327
parent::__construct();
@@ -53,34 +47,32 @@ public function fetchFreshData($source, EncapsulatedOptions $options = null)
5347
throw new \InvalidArgumentException('Options must be an instance of HttpOptions.');
5448
}
5549

56-
return \ScriptFUSION\Retry\retry($this->getTries(), function () use ($source, $options) {
57-
if (false === $response = @file_get_contents(
58-
$this->getOrCreateUrlBuilder()->buildUrl(
59-
$source,
60-
$options ? $options->getQueryParameters() : [],
61-
$this->getBaseUrl()
50+
if (false === $response = @file_get_contents(
51+
$this->getOrCreateUrlBuilder()->buildUrl(
52+
$source,
53+
$options ? $options->getQueryParameters() : [],
54+
$this->getBaseUrl()
55+
),
56+
false,
57+
stream_context_create([
58+
'http' => ['ignore_errors' => true] + array_merge(
59+
$this->options->extractHttpContextOptions(),
60+
$options ? $options->extractHttpContextOptions() : []
6261
),
63-
false,
64-
stream_context_create([
65-
'http' => ['ignore_errors' => true] + array_merge(
66-
$this->options->extractHttpContextOptions(),
67-
$options ? $options->extractHttpContextOptions() : []
68-
),
69-
])
70-
)) {
71-
$error = error_get_last();
72-
throw new HttpConnectionException($error['message'], $error['type']);
73-
}
62+
])
63+
)) {
64+
$error = error_get_last();
65+
throw new HttpConnectionException($error['message'], $error['type']);
66+
}
7467

75-
$code = explode(' ', $http_response_header[0], 3)[1];
76-
if ($code < 200 || $code >= 400) {
77-
throw new HttpServerException(
78-
"HTTP server responded with error: \"$http_response_header[0]\".\n\n$response"
79-
);
80-
}
68+
$code = explode(' ', $http_response_header[0], 3)[1];
69+
if ($code < 200 || $code >= 400) {
70+
throw new HttpServerException(
71+
"HTTP server responded with error: \"$http_response_header[0]\".\n\n$response"
72+
);
73+
}
8174

82-
return $response;
83-
}, new ExponentialBackoffErrorHandler);
75+
return $response;
8476
}
8577

8678
private function getOrCreateUrlBuilder()
@@ -107,28 +99,4 @@ public function setBaseUrl($baseUrl)
10799

108100
return $this;
109101
}
110-
111-
/**
112-
* Gets the maximum number of fetch attempts
113-
*
114-
* @return int
115-
*/
116-
public function getTries()
117-
{
118-
return $this->tries;
119-
}
120-
121-
/**
122-
* Sets the maximum number of fetch attempts.
123-
*
124-
* @param int $tries Maximum fetch attempts.
125-
*
126-
* @return $this
127-
*/
128-
public function setTries($tries)
129-
{
130-
$this->tries = max(1, $tries|0);
131-
132-
return $this;
133-
}
134102
}

src/Net/Soap/SoapConnector.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
use ScriptFUSION\Porter\Connector\CachingConnector;
55
use ScriptFUSION\Porter\Options\EncapsulatedOptions;
66
use ScriptFUSION\Porter\Type\ObjectType;
7-
use ScriptFUSION\Retry\ErrorHandler\ExponentialBackoffErrorHandler;
87

98
/**
109
* Fetches data from a SOAP service.
@@ -33,11 +32,7 @@ public function fetchFreshData($source, EncapsulatedOptions $options = null)
3332

3433
$params = array_merge($this->options->getParameters(), $options ? $options->getParameters() : []);
3534

36-
return ObjectType::toArray(
37-
\ScriptFUSION\Retry\retry(5, function () use ($source, $params) {
38-
return $this->getOrCreateClient()->$source($params);
39-
}, new ExponentialBackoffErrorHandler)
40-
);
35+
return ObjectType::toArray($this->getOrCreateClient()->$source($params));
4136
}
4237

4338
private function getOrCreateClient()

src/Porter.php

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@
2020
use ScriptFUSION\Porter\Provider\ProviderFactory;
2121
use ScriptFUSION\Porter\Provider\Resource\ProviderResource;
2222
use ScriptFUSION\Porter\Specification\ImportSpecification;
23+
use ScriptFUSION\Retry\ErrorHandler\ExponentialBackoffErrorHandler;
2324

2425
/**
2526
* Imports data according to an ImportSpecification.
2627
*/
2728
class Porter
2829
{
30+
const DEFAULT_FETCH_ATTEMPTS = 5;
31+
2932
/**
3033
* @var Provider[]
3134
*/
@@ -46,6 +49,11 @@ class Porter
4649
*/
4750
private $defaultCacheAdvice;
4851

52+
/**
53+
* @var int
54+
*/
55+
private $maxFetchAttempts = self::DEFAULT_FETCH_ATTEMPTS;
56+
4957
public function __construct()
5058
{
5159
$this->defaultCacheAdvice = CacheAdvice::SHOULD_NOT_CACHE();
@@ -127,7 +135,13 @@ private function fetch(ProviderResource $resource, CacheAdvice $cacheAdvice = nu
127135
$provider = $this->getProvider($resource->getProviderClassName(), $resource->getProviderTag());
128136
$this->applyCacheAdvice($provider, $cacheAdvice ?: $this->defaultCacheAdvice);
129137

130-
return $provider->fetch($resource);
138+
return \ScriptFUSION\Retry\retry(
139+
$this->maxFetchAttempts,
140+
function () use ($provider, $resource) {
141+
return $provider->fetch($resource);
142+
},
143+
new ExponentialBackoffErrorHandler
144+
);
131145
}
132146

133147
private function filter(ProviderRecords $records, callable $predicate, $context)
@@ -289,4 +303,28 @@ public function setMapper(CollectionMapper $mapper)
289303

290304
return $this;
291305
}
306+
307+
/**
308+
* Gets the maximum number of fetch attempts per import.
309+
*
310+
* @return int
311+
*/
312+
public function getMaxFetchAttempts()
313+
{
314+
return $this->maxFetchAttempts;
315+
}
316+
317+
/**
318+
* Sets the maximum number of fetch attempts per import.
319+
*
320+
* @param int $attempts Maximum fetch attempts.
321+
*
322+
* @return $this
323+
*/
324+
public function setMaxFetchAttempts($attempts)
325+
{
326+
$this->maxFetchAttempts = max(1, $attempts|0);
327+
328+
return $this;
329+
}
292330
}

test/Functional/Porter/Net/Http/HttpConnectorTest.php

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use ScriptFUSION\Porter\Net\Http\HttpConnector;
77
use ScriptFUSION\Porter\Net\Http\HttpOptions;
88
use ScriptFUSION\Porter\Net\Http\HttpServerException;
9-
use ScriptFUSION\Retry\FailingTooHardException;
9+
use ScriptFUSION\Retry\ErrorHandler\ExponentialBackoffErrorHandler;
1010
use Symfony\Component\Process\Process;
1111

1212
final class HttpConnectorTest extends \PHPUnit_Framework_TestCase
@@ -41,46 +41,22 @@ public function testConnectionToLocalWebserver()
4141

4242
public function testConnectionTimeout()
4343
{
44-
try {
45-
$this->fetch($this->connector->setTries(1));
46-
} catch (FailingTooHardException $exception) {
47-
self::assertInstanceOf(HttpConnectionException::class, $exception->getPrevious());
48-
49-
return;
50-
}
44+
$this->setExpectedException(HttpConnectionException::class);
5145

52-
self::fail('Expected exception was not thrown.');
46+
$this->fetch();
5347
}
5448

5549
public function testErrorResponse()
5650
{
5751
$server = $this->startServer('404');
52+
53+
$this->setExpectedExceptionRegExp(HttpServerException::class, '[^foo\z]m');
54+
5855
try {
5956
$this->fetch();
60-
} catch (FailingTooHardException $exception) {
61-
self::assertInstanceOf(HttpServerException::class, $exception->getPrevious(), $exception->getMessage());
62-
self::assertStringEndsWith('foo', $exception->getPrevious()->getMessage());
63-
64-
return;
6557
} finally {
6658
$this->stopServer($server);
6759
}
68-
69-
self::fail('Expected exception was not thrown.');
70-
}
71-
72-
public function testOneTry()
73-
{
74-
$this->setExpectedException(FailingTooHardException::class, '1');
75-
76-
$this->fetch($this->connector->setTries(1));
77-
}
78-
79-
public function testDefaultTries()
80-
{
81-
$this->setExpectedException(FailingTooHardException::class, (string)HttpConnector::DEFAULT_TRIES);
82-
83-
$this->fetch();
8460
}
8561

8662
/**
@@ -91,11 +67,30 @@ public function testDefaultTries()
9167
private function startServer($script)
9268
{
9369
$server = (
94-
// Prevent forking on some Unix systems.
95-
new Process(sprintf('%sphp -S %s %s.php', file_exists('/bin/sh') ? 'exec ' : '', self::HOST, $script))
70+
new Process(sprintf(
71+
'%sphp -S %s %s.php',
72+
// Prevent forking on some Unix systems.
73+
file_exists('/bin/sh') ? 'exec ' : '',
74+
self::HOST,
75+
$script
76+
))
9677
)->setWorkingDirectory(self::$dir);
9778
$server->start();
9879

80+
// Wait for server to spawn.
81+
\ScriptFUSION\Retry\retry(5, function () {
82+
$this->fetch();
83+
}, function (\Exception $exception) {
84+
static $handler;
85+
$handler = $handler ?: new ExponentialBackoffErrorHandler;
86+
87+
if (!$exception instanceof HttpConnectionException) {
88+
return false;
89+
}
90+
91+
return $handler();
92+
});
93+
9994
return $server;
10095
}
10196

test/Integration/Porter/PorterTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use ScriptFUSION\Porter\ProviderNotFoundException;
2323
use ScriptFUSION\Porter\Specification\ImportSpecification;
2424
use ScriptFUSION\Porter\Specification\StaticDataImportSpecification;
25+
use ScriptFUSION\Retry\FailingTooHardException;
2526
use ScriptFUSIONTest\MockFactory;
2627

2728
final class PorterTest extends \PHPUnit_Framework_TestCase
@@ -210,6 +211,22 @@ public function testImportTaggedResource()
210211
self::assertSame($output, $records->current());
211212
}
212213

214+
public function testOneTry()
215+
{
216+
$this->setExpectedException(FailingTooHardException::class, '1');
217+
218+
$this->provider->shouldReceive('fetch')->once()->andThrow(\Exception::class);
219+
$this->porter->setMaxFetchAttempts(1)->import($this->specification);
220+
}
221+
222+
public function testDefaultTries()
223+
{
224+
$this->setExpectedException(FailingTooHardException::class, (string)Porter::DEFAULT_FETCH_ATTEMPTS);
225+
226+
$this->provider->shouldReceive('fetch')->times(Porter::DEFAULT_FETCH_ATTEMPTS)->andThrow(\Exception::class);
227+
$this->porter->import($this->specification);
228+
}
229+
213230
#endregion
214231

215232
#region Import one

0 commit comments

Comments
 (0)