1. Go to this page and download the library: Download code-distortion/backoff library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
code-distortion / backoff example snippets
// let Backoff manage the delays and trigger retries for you
$action = fn() => …; // do some work
$result = Backoff::exponential(2)->maxAttempts(10)->maxDelay(30)->attempt($action);
// the usual case
$action = fn() => …; // do some work
$result = Backoff::exponential(2)->maxAttempts(10)->maxDelay(30)->attempt($action);
// selection of examples
$result = Backoff::exponential(1)->attempt($action, $default);
Backoff::polynomial(1)->attempt($action);
Backoff::sequence([1, 2, 3, 5, 10])->attempt($action);
Backoff::exponential(1)->equalJitter()->immediateFirstRetry()->attempt($action);
Backoff::exponential(1)->retryExceptions(MyException::class)->attempt($action);
Backoff::exponential(1)->retryWhen(false)->attempt($action);
Backoff::exponential(1)->retryUntil(true)->attempt($action);
Backoff::exponential(1)->failureCallback($failed)->attempt($action);
// max-attempts (default = no limit)
->maxAttempts(10) // the maximum number of attempts allowed
->maxAttempts(null) // remove the limit, or
->noMaxAttempts() // remove the limit, or
->noAttemptLimit() // alias for noMaxAttempts()
// max-delay - the maximum delay to wait between each attempt (default = no limit)
->maxDelay(30) // set the max-delay, in the current unit-of-measure
->maxDelay(null) // remove the limit, or
->noMaxDelay() // remove the limit, or
->noDelayLimit() // alias for noMaxDelay()
// choose the type of jitter to apply to the delay (default = full jitter)
->fullJitter() // apply full jitter, between 0% and 100% of the base-delay (applied by default)
->equalJitter() // apply equal jitter, between 50% and 100% of the base-delay
->jitterRange(0.75, 1.25) // apply jitter between $min and $max (e.g. 0.75 = 75%, 1.25 = 125%) of the base-delay
->jitterCallback($callback) // specify a callback that applies the jitter
->customJitter($jitter) // jitter managed by a custom jitter class
->noJitter() // disable jitter - the base-delay will be used as-is
// insert an initial retry that happens straight away
// before the backoff algorithm starts generating delays (default = off)
->immediateFirstRetry() // insert an immediate retry
->immediateFirstRetry(false) // don't insert an immediate retry, or
->noImmediateFirstRetry() // don't insert an immediate retry
// turn off delays or retries altogether - may be useful when running tests (default = enabled)
->onlyDelayWhen(!$runningTests) // enable or disable delays (disabled means delays are 0)
->onlyRetryWhen(!$runningTests) // enable or disable retries (disabled means only 1 attempt will be made)
// retry based on EXCEPTIONS…
// retry when any exception occurs (this is the default setting)
// along with $default which is returned if all attempts fail
// $default may be a callable that returns the default value
// if $default is omitted, the final exception will be rethrown
->retryAllExceptions()
->retryAllExceptions($default)
// retry when these particular exceptions occur
// (you can specify multiple types of exceptions by passing
// them as an array, or by calling this multiple times)
->retryExceptions(MyException::class)
->retryExceptions(MyException::class, $default)
// you can also specify a callback that chooses whether to retry or not
// (return true to retry, false to end)
// $callback(Throwable $e, AttemptLog $log): bool
->retryExceptions($callback);
->retryExceptions($callback, $default);
// or choose to NOT retry when exceptions occur
// if $default is omitted, any exceptions will be rethrown
->retryExceptions(false) // or
->dontRetryExceptions()
->retryExceptions(false, $default) // or
->dontRetryExceptions($default)
// retry based on the return VALUE…
// (by default, retries won't happen based on the return value)
// retry WHEN a particular value is returned,
// along with $default which is returned if all attempts fail
// $default may be a callable that returns the default value
// if $default is omitted, the final value returned by $action is returned
// (you can check for different values by calling this multiple times)
->retryWhen($match, $strict = false)
->retryWhen($match, $strict, $default)
// you can also specify a callback that chooses whether to retry or not
// (return true to retry, false to end)
// $callback(mixed $result, AttemptLog $log): bool
->retryWhen($callback)
->retryWhen($callback, false, $default) // strict doesn't matter when using a callback
// retry UNTIL this value is returned
// (you can check for different values by calling this multiple times)
->retryUntil($match, $strict = false)
// you can also pass a callback that chooses whether to retry or not
// (unlike ->retryWhen(…), here you return false to retry, true to end)
// $callback(mixed $result, AttemptLog $log): bool
->retryUntil($callback)
// (you can specify multiple callbacks at a time by passing
// them as an array, or by calling these methods multiple times)
// called when any exception occurs
// $callback(Throwable $e, AttemptLog $log, bool $willRetry): void
->exceptionCallback($callback)
// called when an "invalid" value is returned
// $callback(mixed $result, AttemptLog $log, bool $willRetry): void
->invalidResultCallback($callback)
// called after an attempt succeeds
// $callback(AttemptLog[] $logs): void
->successCallback($callback)
// called after all attempts fail, including when no
// attempts occur, and when an exception is thrown
// $callback(AttemptLog[] $logs): void
->failureCallback($callback)
// called afterwards regardless of the outcome, including
// when no attempts occur, and when an exception is thrown
// $callback(AttemptLog[] $logs): void
->finallyCallback($callback)
->attempt($action); // run your callback and retry it when needed
->attempt($action, $default); // run your callback, retry it when needed, and return $default if all attempts fail
// $default may be a callable that returns the default value
use CodeDistortion\Backoff\Backoff;
$action = fn() => …; // do some work
$result = Backoff::exponential(1)->maxDelay(30)->maxAttempts(10)->attempt($action);
// $callback = function (int $retryNumber, int|float|null $prevBaseDelay): int|float|null …
Backoff::callback($callback)->attempt($action); // $callback(1, $prev), $callback(2, $prev), $callback(3, $prev)…
Backoff::callbackMs($callback)->attempt($action); // in milliseconds
Backoff::callbackUs($callback)->attempt($action); // in microseconds
// MyBackoffAlgorithm.php
use CodeDistortion\Backoff\Interfaces\BackoffAlgorithmInterface;
use CodeDistortion\Backoff\Support\BaseBackoffAlgorithm;
class MyBackoffAlgorithm extends BaseBackoffAlgorithm implements BackoffAlgorithmInterface
{
/** @var boolean Whether jitter may be applied to the delays calculated by this algorithm. */
protected bool $jitterMayBeApplied = true;
public function __construct(
// e.g. private int|float $initialDelay,
// … and any other parameters you need
) {
}
public function calculateBaseDelay(int $retryNumber, int|float|null $prevBaseDelay): int|float|null
{
return …; // your logic here
}
}
$algorithm = new MyBackoffAlgorithm(…);
Backoff::custom($algorithm)->attempt($action);
Backoff::customMs($algorithm)->attempt($action); // in milliseconds
Backoff::customUs($algorithm)->attempt($action); // in microseconds
Backoff::exponential(1)
->fullJitter() // <<< between 0% and 100%
->attempt($action);
Backoff::exponential(1)
->equalJitter() // <<< between 50% and 100%
->attempt($action);
Backoff::exponential(1)
->jitterRange(0.75, 1.25) // <<< between 75% and 125%
->attempt($action);
// $callback = function (int|float $delay, int $retryNumber): int|float …
$callback = fn(int|float $delay, int $retryNumber): int|float => …; // your logic here
Backoff::exponential(1)
->jitterCallback($callback) // <<<
->attempt($action);
// MyJitter.php
use CodeDistortion\Backoff\Interfaces\JitterInterface;
use CodeDistortion\Backoff\Support\BaseJitter;
class MyJitter extends BaseJitter implements JitterInterface
{
public function __construct(
// … any configuration parameters you need
) {
}
public function apply(int|float $delay, int $retryNumber): int|float
{
return …; // your logic here
}
}
$jitter = new MyJitter(…);
Backoff::exponential(1)
->customJitter($jitter) // <<<
->attempt($action);
$callback = fn(mixed $result, AttemptLog $log): bool => …; // your logic here
Backoff::exponential(1)
->retryUntil($callback) // <<<
->attempt($action);
$callback = fn(…) => …; // do something here
// the callback can accept these parameters:
// $e - called '$e' - the exception that was thrown
// $exception - called '$exception' - the exception that was thrown
// Throwable $e - of type 'Throwable', or any exception type you'd like to catch in particular
// $willRetry - called '$willRetry' - true if a retry will be made, false if not
// AttemptLog $log - of type 'AttemptLog' - the current AttemptLog object
// $log - called '$log' - the current AttemptLog object
// $logs - called '$logs' - an array of AttemptLog objects
Backoff::exponential(1)
->exceptionCallback($callback) // <<<
->attempt($action);
> $callback1 = fn(MyException1 $e) => …; // will be called when MyException1 is thrown
> $callback2 = fn(MyException2 $e) => …; // will be called when MyException2 is thrown
>
> Backoff::exponential(1)
> ->exceptionCallback($callback1, $callback2) // <<<
> ->attempt($action);
>
$callback = fn(…) => …; // do something here
// the callback can accept these parameters:
// $result - called '$result' - the result that was returned
// $willRetry - called '$willRetry' - true if a retry will be made, false if not
// AttemptLog $log - of type 'AttemptLog' - the current AttemptLog object
// $log - called '$log' - the current AttemptLog object
// $logs - called '$logs' - an array of AttemptLog objects
Backoff::exponential(1)
->invalidResultCallback($callback) // <<<
->attempt($action);
$callback = fn(…) => …; // do something here
// the callback can accept these parameters:
// $result - called '$result' - the result that was returned
// AttemptLog $log - of type 'AttemptLog' - the current AttemptLog object
// $log - called '$log' - the current AttemptLog object
// $logs - called '$logs' - an array of AttemptLog objects
Backoff::exponential(1)
->successCallback($callback) // <<<
->attempt($action);
$callback = fn(…) => …; // do something here
// the callback can accept these parameters:
// AttemptLog $log - of type 'AttemptLog' - the current AttemptLog object
// $log - called '$log' - the current AttemptLog object
// $logs - called '$logs' - an array of AttemptLog objects
Backoff::exponential(1)
->failureCallback($callback) // <<<
->attempt($action);
$callback = fn(…) => …; // do something here
// the callback can accept these parameters:
// AttemptLog $log - of type 'AttemptLog' - the current AttemptLog object
// $log - called '$log' - the current AttemptLog object
// $logs - called '$logs' - an array of AttemptLog objects
Backoff::exponential(1)
->finallyCallback($callback) // <<<
->attempt($action);
$log->attemptNumber(); // the attempt being made (1, 2, 3…)
$log->retryNumber(); // the retry being made (0, 1, 2…)
// the maximum possible attempts
// (returns null for unlimited attempts)
// note: it's possible for a backoff algorithm to return null
// so the attempts finish early. This won't be reflected here
$log->maxAttempts();
$log->firstAttemptOccurredAt(); // when the first attempt started
$log->thisAttemptOccurredAt(); // when the current attempt started
// the time spent on this attempt
// (will be null until known)
$log->workingTime(); // in the current unit-of-measure
$log->workingTimeInSeconds(); // in seconds
$log->workingTimeInMs(); // in milliseconds
$log->workingTimeInUs(); // in microseconds
// the overall time spent attempting the action (so far)
// (sum of all working time since the first attempt)
// (will be null until known)
$log->overallWorkingTime(); // in the current unit-of-measure
$log->overallWorkingTimeInSeconds(); // in seconds
$log->overallWorkingTimeInMs(); // in milliseconds
$log->overallWorkingTimeInUs(); // in microseconds
// the delay that was applied before this attempt
// (will be null for the first attempt)
$log->prevDelay(); // in the current unit-of-measure
$log->prevDelayInSeconds(); // in seconds
$log->prevDelayInMs(); // in milliseconds
$log->prevDelayInUs(); // in microseconds
// the delay that will be used before the next attempt
// (will be null if there are no more attempts left)
$log->nextDelay(); // in the current unit-of-measure
$log->nextDelayInSeconds(); // in seconds
$log->nextDelayInMs(); // in milliseconds
$log->nextDelayInUs(); // in microseconds
// the overall delay so far (sum of all delays since the first attempt)
$log->overallDelay(); // in the current unit-of-measure
$log->overallDelayInSeconds(); // in seconds
$log->overallDelayInMs(); // in milliseconds
$log->overallDelayInUs(); // in microseconds
// the unit-of-measure used
// these are values from CodeDistortion\Backoff\Settings::UNIT_XXX
$log->unitType();
$runningTests = …;
$backoff = Backoff::exponential(1)
->maxAttempts(10)
// no reties when running tests
->onlyRetryWhen(!$runningTests) // <<<
->attempt($action);
use CodeDistortion\Backoff\Backoff;
// choose a backoff algorithm and configure it as needed
$backoff = Backoff::exponential(1)->maxDelay(30)->maxAttempts(10);
// then use it in your loop
do {
$success = …; // do some work
} while ((!$success) && ($backoff->step())); // <<<
$maxAttempts = …; // possibly 0
// specify that $backoff->step() will be called at the entrance to your loop
$backoff = Backoff::exponential(1)->maxDelay(30)->maxAttempts($maxAttempts)->runsAtStartOfLoop(); // <<<
$success = false;
while ((!$success) && ($backoff->step())) { // <<<
$success = …; // do some work
};
$backoff = Backoff::exponential(1)->maxDelay(30)->maxAttempts(10)->runsAtStartOfLoop();
$success = false;
while ((!$success) && ($backoff->step())) {
try {
$success = …; // do some work
} catch (MyException $e) {
// handle the exception
}
};
$backoff = Backoff::exponential(1)->maxDelay(30)->maxAttempts(10)->runsAtStartOfLoop();
$success = false;
while ((!$success) && ($backoff->step(false))) { // <<<
// there won't be a first delay because of ->runsAtStartOfLoop()
$backoff->sleep(); // <<<
$success = …; // do some work
};
$backoff = Backoff::exponential(1)->maxDelay(30)->maxAttempts(10)->runsAtStartOfLoop();
$success = false;
while ((!$success) && ($backoff->step(false))) { // <<<
// there won't be a first delay because of ->runsAtStartOfLoop()
// remember the ->getDelayInXXX() methods may return a float
if ($delay = (int) $backoff->getDelayInUs()) { // <<<
// note that usleep() might not support delays larger than 1 second
// https://www.php.net/usleep
usleep($delay);
}
$success = …; // do some work
};
$backoff = Backoff::exponential(1);
do {
$backoff->startOfAttempt(); // <<<
$success = …; // do some work
$backoff->endOfAttempt(); // <<<
$log = $backoff->currentLog(); // returns the current AttemptLog
// … perform some logging here based upon $log
} while ((!$success) && ($backoff->step()));
$logs = $backoff->logs(); // returns all the AttemptLogs in an array
// tell backoff where you'll call ->step() (default = at the end of the loop)
->runsAtStartOfLoop() // specify that $backoff->step() will be called at the entrance to your loop
->runsAtStartOfLoop(false) // specify that $backoff->step() will be called at the end of your loop (default), or
->runsAtEndOfLoop() // specify that $backoff->step() will be called at the end of your loop (default)
// trigger the backoff logic - placed in the structure of your loop
->step(); // calculate the delay and perform the sleep, returns false when the attempts are exhausted
// if you'd like to separate the sleep from ->step()
->step(false); // calculate delay without sleeping, returns false when the attempts are exhausted
->sleep(); // sleep for the delay calculated by ->step(false)
// if you'd like to perform the sleep yourself, call ->step(false) and then retrieve the delay
->getDelay(); // get the delay in the current unit-of-measure (note: may contain decimals)
->getDelayInSeconds(); // get the delay in seconds (note: may contain decimals)
->getDelayInMs(); // get the delay in milliseconds (note: may contain decimals)
->getDelayInUs(); // get the delay in microseconds (note: may contain decimals)
->getUnitType(); // get the unit-of-measure being used (from CodeDistortion\Backoff\Settings::UNIT_XXX)
// querying the state of the backoff
->currentAttemptNumber(); // get the current attempt number
->isFirstAttempt(); // check if the first attempt is currently being made
->isLastAttempt(); // check if the last attempt is currently being made (however it may run indefinitely)
->canContinue(); // check if the more attempts can be made - this is the same as what ->step() returns
->hasStopped(); // check if the attempts have been exhausted - this is the opposite to ->canContinue()
// working with logs
->startOfAttempt(); // start the attempt, so the log is built
->endOfAttempt(); // end the attempt, so the log is built
->currentLog(); // get the AttemptLog for the current attempt
->logs(); // get all the AttemptLogs (so far)
// and finally
->reset(); // reset the backoff to its initial state, ready to be re-used
// generate delays in the current unit-of-measure
$backoff->simulate(1); // generate a single delay (e.g. for retry 1)
$backoff->simulate(10, 20); // generate a sequence of delays, returned as an array (e.g. for retries 10 - 20)
// generate delays in seconds (note: may contain decimals)
$backoff->simulateInSeconds(1);
$backoff->simulateInSeconds(1, 20);
// generate delays in milliseconds (note: may contain decimals)
$backoff->simulateInMs(1);
$backoff->simulateInMs(1, 20);
// generate delays in microseconds (note: may contain decimals)
$backoff->simulateInUs(1);
$backoff->simulateInUs(1, 20);
// these are values from CodeDistortion\Backoff\Settings::UNIT_XXX
$backoff->getUnitType();
> $first = $backoff->simulate(1, 20);
> $second = $backoff->simulate(1, 20);
> // $second will be the same as $first
> $third = $backoff->reset()->simulate(1, 20);
> // however $third will be different
>
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.