philiprehberger/php-retry

Composable retry utility with exponential backoff, jitter, and exception filtering

Maintainers

Package info

github.com/philiprehberger/php-retry

pkg:composer/philiprehberger/php-retry

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.1.1 2026-03-17 20:06 UTC

This package is auto-updated.

Last update: 2026-03-17 20:07:16 UTC


README

Tests Latest Version on Packagist License

Composable retry utility with exponential backoff, jitter, and exception filtering.

Requirements

Requirement Version
PHP ^8.2

Installation

composer require philiprehberger/php-retry

Usage

Basic retry

use PhilipRehberger\Retry\Retry;

$result = Retry::times(3)->run(function () {
    return file_get_contents('https://api.example.com/data');
});

echo $result->value;       // The response body
echo $result->attempts;    // Number of attempts made
echo $result->totalTimeMs; // Total time spent in milliseconds

Exponential backoff with jitter

$result = Retry::times(5)
    ->backoff(baseMs: 100, maxMs: 5000)
    ->jitter()
    ->run(fn () => $httpClient->get('/unstable-endpoint'));

Linear backoff

$result = Retry::times(3)
    ->linear(delayMs: 200)
    ->run(fn () => $api->call());

Constant delay

$result = Retry::times(4)
    ->constant(delayMs: 500)
    ->run(fn () => $service->fetch());

Exception filtering

Only retry on specific exceptions:

$result = Retry::times(5)
    ->onlyIf(fn (\Throwable $e) => $e instanceof ConnectionException)
    ->run(fn () => $db->query($sql));

Exclude specific exceptions from retrying:

$result = Retry::times(5)
    ->except(ValidationException::class, AuthenticationException::class)
    ->run(fn () => $api->submit($data));

Conditional Retry

Use shouldRetry() to provide a predicate that receives the exception and attempt number:

$retry = Retry::times(5)
    ->shouldRetry(function (\Throwable $e, int $attempt): bool {
        // Stop retrying after attempt 3 for rate-limit errors
        if ($e instanceof RateLimitException && $attempt >= 3) {
            return false;
        }
        return true;
    })
    ->run(fn () => $api->request());

Use retryOnlyOn() to retry only for specific exception types:

$result = Retry::times(5)
    ->retryOnlyOn(ConnectionException::class, TimeoutException::class)
    ->run(fn () => $httpClient->get('/endpoint'));

Retrieve the total number of attempts after execution with getAttempts():

$retry = Retry::times(5);
$result = $retry->run(fn () => $service->call());

echo $retry->getAttempts(); // e.g. 3

Time budget

Stop retrying after a total time budget is exceeded:

$result = Retry::times(100)
    ->constant(delayMs: 50)
    ->maxDuration(ms: 2000)
    ->run(fn () => $service->call());

Retry forever (with safety)

$result = Retry::forever()
    ->backoff(baseMs: 100, maxMs: 30000)
    ->jitter()
    ->maxDuration(ms: 60000)
    ->run(fn () => $queue->consume());

Callbacks

$result = Retry::times(5)
    ->backoff(baseMs: 100)
    ->beforeRetry(function (int $attempt, \Throwable $e) {
        logger()->warning("Retry attempt {$attempt}: {$e->getMessage()}");
    })
    ->afterRetry(function (int $attempt, ?\Throwable $e) {
        if ($e === null) {
            logger()->info("Succeeded on attempt {$attempt}");
        }
    })
    ->run(fn () => $api->request());

API

Method Description
Retry::times(int $maxAttempts) Create a retry builder with a maximum number of attempts
Retry::forever() Create a retry builder that retries indefinitely
->backoff(bool $exponential, int $baseMs, int $maxMs) Configure exponential backoff
->linear(int $delayMs) Configure linear backoff
->constant(int $delayMs) Configure constant delay
->jitter(bool $enabled) Enable or disable jitter
->onlyIf(callable $predicate) Only retry when predicate returns true
->except(string ...$exceptionClasses) Exclude specific exception types from retrying
->shouldRetry(callable $predicate) Predicate receiving exception and attempt number; return false to stop
->retryOnlyOn(string ...$exceptionClasses) Only retry for the given exception types (sugar for shouldRetry)
->getAttempts() Get the total number of attempts made after execution
->maxDuration(int $ms) Set maximum total duration for all attempts
->beforeRetry(callable $callback) Callback invoked before each retry
->afterRetry(callable $callback) Callback invoked after each attempt
->run(callable $operation) Execute the operation with retry logic

Development

composer install
vendor/bin/phpunit
vendor/bin/pint --test
vendor/bin/phpstan analyse

License

MIT