Skip to content

Commit

Permalink
Remove configurator getters and add shortcut for retrying Doctrine tr…
Browse files Browse the repository at this point in the history
…ansactions exceptions
  • Loading branch information
Tobion committed Sep 19, 2019
1 parent dd58655 commit 8e73980
Show file tree
Hide file tree
Showing 6 changed files with 26 additions and 111 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ $callableThatMightFail = function (int $arg1, int $arg2): int {

// Allows you to call the callable with parameters and retry its execution in case an exception is thrown.
// You can access the return value of the callable (3 in this case).
$returnValue = Retry::call($callableThatMightFail, 1, 2);
$returnValue = Retry::configure()->call($callableThatMightFail, 1, 2);

// By default:
// - The callable is retried twice (i.e. max three executions). If it still fails, the last error is rethrown.
// - Retries have a 300 milliseconds delay between them.
// - Retries have a no delay between them.
// - Every \Throwable will trigger the retry logic, i.e. both \Exception and \Error.
// You can adjust the retry logic like this:
$retryingCallable = Retry::configure()
->setMaxRetries(5)
->setDelayInMs(100)
->setRetryableExceptions(\RuntimeException::class) // other failures like \TypeError will not be retried
->maxRetries(5)
->delayInMs(100)
->retryOnSpecificExceptions(\RuntimeException::class) // other failures like \TypeError will not be retried
->decorate($callableThatMightFail)
;
$returnValue = $retryingCallable(1, 2);
Expand Down
22 changes: 9 additions & 13 deletions src/Retry.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Tobion\Retry;

use Doctrine\DBAL\Exception\RetryableException;

/**
* Simple retry entry point providing shortcuts.
*
Expand All @@ -18,23 +20,17 @@ public static function configure(): RetryConfigurator
}

/**
* Returns a callable that decorates the given operation that should be retried on failure.
*/
public static function decorate(callable $callable): RetryingCallable
{
return (new RetryConfigurator())->decorate($callable);
}

/**
* Executes the passed callable and its arguments with the default retry behavior.
*
* @see RetryConfigurator
* Executes the passed callable and its arguments with the a preconfigured retry behavior suitable for Doctrine database transactions.
*
* @return mixed The return value of the passed callable
*/
public static function call(callable $callable, ...$arguments)
public static function onDoctrineExceptionWith2Retries300MsDelay(callable $callable, ...$arguments)
{
return (new RetryConfigurator())->call($callable, ...$arguments);
return self::configure()
->maxRetries(2)
->delayInMs(300)
->retryOnSpecificExceptions(RetryableException::class)
->call($callable, ...$arguments);
}

/**
Expand Down
48 changes: 9 additions & 39 deletions src/RetryConfigurator.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*
* @author Tobias Schultze <http://tobion.de>
*/
class RetryConfigurator
final class RetryConfigurator
{
/**
* @var string[]
Expand All @@ -27,20 +27,18 @@ class RetryConfigurator
/**
* @var int
*/
private $delayInMs;
private $delayInMs = 0;

/**
* Configures the retry logic. By default:
*
* - The callable is retried twice (i.e. max three executions). If it still fails, the last error is rethrown.
* - Retries have a 300 milliseconds delay between them.
* - Every \Throwable will trigger the retry logic, i.e. both exceptions and errors.
* - Retries have a no delay between them.
* - Every \Throwable will trigger the retry logic, i.e. both \Exception and \Error.
*/
public function __construct(int $maxRetries = 2, int $delayInMs = 300, string $exceptionToRetry = \Throwable::class)
public function __construct(int $maxRetries = 2)
{
$this->maxRetries = $maxRetries;
$this->delayInMs = $delayInMs;
$this->setRetryableExceptions($exceptionToRetry);
}

/**
Expand All @@ -53,7 +51,7 @@ public function __construct(int $maxRetries = 2, int $delayInMs = 300, string $e
*
* @return $this
*/
public function setRetryableExceptions(string $exceptionClass, string ...$moreExceptionClasses): self
public function retryOnSpecificExceptions(string $exceptionClass, string ...$moreExceptionClasses): self
{
array_unshift($moreExceptionClasses, $exceptionClass);
$this->retryableExceptionClasses = $moreExceptionClasses;
Expand All @@ -66,7 +64,7 @@ public function setRetryableExceptions(string $exceptionClass, string ...$moreEx
*
* @return $this
*/
public function setMaxRetries(int $maxRetries): self
public function maxRetries(int $maxRetries): self
{
$this->maxRetries = $maxRetries;

Expand All @@ -80,41 +78,13 @@ public function setMaxRetries(int $maxRetries): self
*
* @return $this
*/
public function setDelayInMs(int $milliseconds): self
public function delayInMs(int $milliseconds): self
{
$this->delayInMs = $milliseconds;

return $this;
}

/**
* Returns the exception classes/interfaces to catch and retry on.
*
* If empty, every exception will trigger the retry logic.
*
* @return string[]
*/
public function getRetryableExceptions(): array
{
return $this->retryableExceptionClasses;
}

/**
* Returns the maximum number of retries.
*/
public function getMaxRetries(): int
{
return $this->maxRetries;
}

/**
* Returns the delay between retries in milliseconds.
*/
public function getDelayInMs(): int
{
return $this->delayInMs;
}

/**
* Returns a callable that decorates the given operation that should be retried on failure.
*/
Expand All @@ -123,7 +93,7 @@ public function decorate(callable $operation): RetryingCallable
$handlers = [];

// we can skip the handler in this default case
if ([\Throwable::class] !== $this->retryableExceptionClasses) {
if ([] !== $this->retryableExceptionClasses && [\Throwable::class] !== $this->retryableExceptionClasses) {
$handlers[] = new RethrowNonRetryableExceptions(...$this->retryableExceptionClasses);
}

Expand Down
41 changes: 0 additions & 41 deletions tests/RetryConfiguratorTest.php

This file was deleted.

6 changes: 3 additions & 3 deletions tests/RetryFunctionalityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function testRetrySucceeds(): void
public function testRetrySucceedsWithTwoRetryableExceptions(): void
{
$retry = (new RetryConfigurator(4))
->setRetryableExceptions(TestExceptionToRetry::class,TestDifferentException::class)
->retryOnSpecificExceptions(TestExceptionToRetry::class,TestDifferentException::class)
->decorate([new TestExamplesToRetry(4), 'useTwoExceptions'])
;

Expand All @@ -65,7 +65,7 @@ public function testRetryFailsAfterMaxRetries(): void

public function testNoRetryWhenNotRetryableError(): void
{
$retry = (new RetryConfigurator(10, 300, TestExceptionToRetry::class))->decorate(
$retry = (new RetryConfigurator(10))->retryOnSpecificExceptions(TestExceptionToRetry::class)->decorate(
[new TestExamplesToRetry(), 'retryableErrorFollowedByOtherError']
);

Expand All @@ -82,7 +82,7 @@ public function testRetryDelays(): void
{
$start = microtime(true);

$returnValue = (new RetryConfigurator(2, 500))->call($this->getCallable(2));
$returnValue = (new RetryConfigurator(2))->delayInMs(500)->call($this->getCallable(2));

$elapsedTimeInMs = (microtime(true) - $start) * 1000;

Expand Down
10 changes: 0 additions & 10 deletions tests/RetryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,4 @@ public function testConfigure(): void
{
$this->assertInstanceOf(RetryConfigurator::class, Retry::configure());
}

public function testDecorate(): void
{
$this->assertInstanceOf(RetryingCallable::class, Retry::decorate(function () { return 42; }));
}

public function testCallPassesOnArgumentsAndReturnsCallableReturnValue(): void
{
$this->assertSame(42, Retry::call(function (int $arg1, int $arg2) { return $arg1 + $arg2; }, 40, 2));
}
}

0 comments on commit 8e73980

Please sign in to comment.