divineomega / attempt
Attempt to run a function, retrying if needed
Fund package maintenance!
Requires
- php: >=7.1
Requires (Dev)
- phpunit/phpunit: ^7.5||^9.6||^10.5||^11.5||^12.5
Replaces
- divineomega/attempt: v3.1.0
This package is auto-updated.
Last update: 2026-07-18 05:31:20 UTC
README
Retry a PHP callable when an operation fails.
Installation
composer require jord-jd/attempt
The package supports PHP 7.1 through current PHP 8.x releases.
Usage
$result = attempt(function () { return fetchFromAnUnreliableApi(); }) ->maxAttempts(5) ->withGap(2) ->now();
maxAttempts(5) means at most five total executions, including the first one.
Use maxAttempts(0), the default, for no attempt limit. The last exception is
available as the previous exception on MaxAttemptsExceeded.
Exponential or custom backoff
$result = attempt($operation) ->maxAttempts(5) ->withExponentialBackoff(1, 2.0, 30) ->now();
Successive delays are 1, 2, 4, and 8 seconds, with later retries capped at 30
seconds. For complete control, return a non-negative integer number of seconds
from withBackoff():
use Exception; $result = attempt($operation) ->withBackoff(function (int $failedAttempt, Exception $exception): int { return min(60, $failedAttempt * 5); }) ->now();
Calling withGap() after withBackoff() switches back to a fixed delay.
Retry selected exceptions
Do not retry permanent failures:
$result = attempt($operation) ->retryIf(function (Exception $exception, int $failedAttempt): bool { return $exception instanceof TemporaryApiException; }) ->maxAttempts(5) ->now();
When the predicate returns false, the original exception is rethrown.
Observe retries
attempt($operation) ->onRetry(function (Exception $exception, int $failedAttempt, int $delay): void { logger()->warning('Operation failed; retrying', [ 'attempt' => $failedAttempt, 'delay' => $delay, 'exception' => $exception, ]); }) ->now();
The callback runs only when another execution will actually be attempted.
Time limits and scheduled starts
// Retry until this instant. No execution is started at or after the deadline. attempt($operation) ->until(new DateTimeImmutable('+30 seconds')) ->withGap(5) ->now(); // Wait until this instant, then begin. A past time starts immediately. attempt($operation) ->at(new DateTimeImmutable('+5 seconds'));
If the next configured delay would cross the deadline, Attempt waits only until
the deadline and throws DateTimeExceeded without starting a late execution.
All configuration methods can be chained. Negative attempt limits or gaps and
invalid exponential-backoff values throw InvalidArgumentException immediately.