PHP code example of code-distortion / backoff

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);

// backoff algorithms - in seconds
Backoff::fixed(2)                         // 2, 2, 2, 2, 2…
Backoff::linear(5)                        // 5, 10, 15, 20, 25…
Backoff::linear(5, 10)                    // 5, 15, 25, 35, 45…
Backoff::exponential(1)                   // 1, 2, 4, 8, 16…
Backoff::exponential(1, 1.5)              // 1, 1.5, 2.25, 3.375, 5.0625…
Backoff::polynomial(1)                    // 1, 4, 9, 16, 25…
Backoff::polynomial(1, 1.5)               // 1, 2.8284271247462, 5.1961524227066, 8, 11.180339887499…
Backoff::fibonacci(1)                     // 1, 1, 2, 3, 5…
Backoff::decorrelated(1)                  // 1.6147780669, 2.9651922732, 5.7128698436, 10.3225378844, 2.3890401166…
Backoff::random(2, 5)                     // 2.7361497528, 2.8163467878, 4.6468904857, 3.3016198676, 3.3810068137…
Backoff::sequence([1, 2, 3, 5, 10])       // 1, 2, 3, 5, 10
Backoff::sequence([1, 2, 3, 5, 10], true) // 1, 2, 3, 5, 10, 10, 10, 10, 10…
Backoff::callback($callback)              // $callback(1, $prev), $callback(2, $prev), $callback(3, $prev)…
Backoff::custom($backoffAlgorithm)        // delay managed by a custom backoff algorithm class

// backoff algorithms - in milliseconds
Backoff::fixedMs(2)                         // 2, 2, 2, 2, 2…
Backoff::linearMs(5)                        // 5, 10, 15, 20, 25…
Backoff::linearMs(5, 10)                    // 5, 15, 25, 35, 45…
Backoff::exponentialMs(1)                   // 1, 2, 4, 8, 16…
Backoff::exponentialMs(1, 1.5)              // 1, 1.5, 2.25, 3.375, 5.0625…
Backoff::polynomialMs(1)                    // 1, 4, 9, 16, 25…
Backoff::polynomialMs(1, 1.5)               // 1, 2.8284271247462, 5.1961524227066, 8, 11.180339887499…
Backoff::fibonacciMs(1)                     // 1, 1, 2, 3, 5…
Backoff::decorrelatedMs(1)                  // 1.6147780669, 2.9651922732, 5.7128698436, 10.3225378844, 2.3890401166…
Backoff::randomMs(2, 5)                     // 2.7361497528, 2.8163467878, 4.6468904857, 3.3016198676, 3.3810068137…
Backoff::sequenceMs([1, 2, 3, 5, 10])       // 1, 2, 3, 5, 10
Backoff::sequenceMs([1, 2, 3, 5, 10], true) // 1, 2, 3, 5, 10, 10, 10, 10, 10…
Backoff::callbackMs($callback)              // $callback(1, $prev), $callback(2, $prev), $callback(3, $prev)…
Backoff::customMs($backoffAlgorithm)        // delay managed by a custom backoff algorithm class

// backoff algorithms - in microseconds
Backoff::fixedUs(2)                         // 2, 2, 2, 2, 2…
Backoff::linearUs(5)                        // 5, 10, 15, 20, 25…
Backoff::linearUs(5, 10)                    // 5, 15, 25, 35, 45…
Backoff::exponentialUs(1)                   // 1, 2, 4, 8, 16…
Backoff::exponentialUs(1, 1.5)              // 1, 1.5, 2.25, 3.375, 5.0625…
Backoff::polynomialUs(1)                    // 1, 4, 9, 16, 25…
Backoff::polynomialUs(1, 1.5)               // 1, 2.8284271247462, 5.1961524227066, 8, 11.180339887499…
Backoff::fibonacciUs(1)                     // 1, 1, 2, 3, 5…
Backoff::decorrelatedUs(1)                  // 1.6147780669, 2.9651922732, 5.7128698436, 10.3225378844, 2.3890401166…
Backoff::randomUs(2, 5)                     // 2.7361497528, 2.8163467878, 4.6468904857, 3.3016198676, 3.3810068137…
Backoff::sequenceUs([1, 2, 3, 5, 10])       // 1, 2, 3, 5, 10
Backoff::sequenceUs([1, 2, 3, 5, 10], true) // 1, 2, 3, 5, 10, 10, 10, 10, 10…
Backoff::callbackUs($callback)              // $callback(1, $prev), $callback(2, $prev), $callback(3, $prev)…
Backoff::customUs($backoffAlgorithm)        // delay managed by a custom backoff algorithm class

// utility backoff algorithms
Backoff::noop() // 0, 0, 0, 0, 0…
Backoff::none() // (1 attempt, no retries)

// 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);

$result = Backoff::exponential(1)->maxDelay(30)->maxAttempts(10)->attempt($action, $default);

// Backoff::fixed($delay)

Backoff::fixed(2)->attempt($action); // 2, 2, 2, 2, 2…

Backoff::fixedMs(2)->attempt($action); // in milliseconds
Backoff::fixedUs(2)->attempt($action); // in microseconds

// Backoff::linear($initialDelay, $delayIncrease = null)

Backoff::linear(5)->attempt($action);     // 5, 10, 15, 20, 25…
Backoff::linear(5, 10)->attempt($action); // 5, 15, 25, 35, 45…

Backoff::linearMs(5)->attempt($action); // in milliseconds
Backoff::linearUs(5)->attempt($action); // in microseconds

// Backoff::exponential($initialDelay, $factor = 2)

Backoff::exponential(1)->attempt($action);      // 1, 2, 4, 8, 16…
Backoff::exponential(1, 1.5)->attempt($action); // 1, 1.5, 2.25, 3.375, 5.0625…

Backoff::exponentialMs(1)->attempt($action); // in milliseconds
Backoff::exponentialUs(1)->attempt($action); // in microseconds

// Backoff::polynomial($initialDelay, $power = 2)

Backoff::polynomial(1)->attempt($action);      // 1, 4, 9, 16, 25…
Backoff::polynomial(1, 1.5)->attempt($action); // 1, 2.8284271247462, 5.1961524227066, 8, 11.180339887499…

Backoff::polynomialMs(1)->attempt($action); // in milliseconds
Backoff::polynomialUs(1)->attempt($action); // in microseconds

// Backoff::fibonacci($initialDelay, $on); // 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89…
Backoff::fibonacci(5)->attempt($action); // 5, 5, 10, 15, 25, 40, 65, 105, 170, 275…

Backoff::fibonacciMs(1)->attempt($action); // in milliseconds
Backoff::fibonacciUs(1)->attempt($action); // in microseconds

Backoff::fibonacci(1, false)->attempt($action); // 1, 2, 3, 5, 8, 13, 21, 34, 55, 89…
Backoff::fibonacci(5, false)->attempt($action); // 5, 10, 15, 25, 40, 65, 105, 170, 275, 445…

// Backoff::random($baseDelay, $multiplier = 3)

Backoff::decorrelated(1)->attempt($action); // 2.6501523185, 7.4707976956, 12.3241439061, 25.1076970005, 46.598982162…
Backoff::decorrelated(1, 2)->attempt($action); // 1.6147780669, 2.9651922732, 5.7128698436, 10.3225378844, 2.3890401166…

Backoff::decorrelatedMs(1)->attempt($action); // in milliseconds
Backoff::decorrelatedUs(1)->attempt($action); // in microseconds

// Backoff::random($min, $max)

Backoff::random(2, 5)->attempt($action); // 2.7361497528, 2.8163467878, 4.6468904857, 3.3016198676, 3.3810068137…

Backoff::randomMs(2, 5)->attempt($action); // in milliseconds
Backoff::randomUs(2, 5)->attempt($action); // in microseconds

// Backoff::sequence($delays, $continuation = null)

Backoff::sequence([1, 1.25, 1.5, 2, 3])->attempt($action);       // 1, 1.25, 1.5, 2, 3
Backoff::sequence([1, 1.25, 1.5, 2, 3], true)->attempt($action); // 1, 1.25, 1.5, 2, 3, 3, 3, 3, 3…

Backoff::sequenceMs([1, 1.25, 1.5, 2, 3])->attempt($action); // in milliseconds
Backoff::sequenceUs([1, 1.25, 1.5, 2, 3])->attempt($action); // in microseconds

// $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::noop()->attempt($action); // 0, 0, 0, 0, 0…

Backoff::none()->attempt($action); // (no retries)

Backoff::exponential(1)
    ->maxAttempts(5) // <<<
    ->attempt($action);

Backoff::exponential(10)
    ->maxDelay(200) // <<<
    ->attempt($action);

Backoff::exponential(10)
    ->maxAttempts(5)
    ->immediateFirstRetry() // <<< 0, 10, 20, 40, 80…
    ->attempt($action);

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);

Backoff::exponential(1)
    ->noJitter() // <<<
    ->attempt($action);

Backoff::exponential(1)
    ->retryAllExceptions() // <<<
    ->attempt($action);

Backoff::exponential(1)
    ->retryAllExceptions($default) // <<<
    ->attempt($action);

Backoff::exponential(1)
    ->retryExceptions(MyException::class, $default) // <<<
    ->attempt($action);

Backoff::exponential(1)
    ->retryExceptions([MyException1::class, MyException2::class], $default1) // <<<
    ->retryExceptions(MyException3::class, $default2) // <<<
    ->attempt($action);

$callback = fn(Throwable $e, AttemptLog $log): bool => …; // your logic here

Backoff::exponential(1)
    ->retryExceptions($callback, $default) // <<<
    ->attempt($action);

Backoff::exponential(1)
    ->retryExceptions(false) // <<<
    ->attempt($action);
// or
Backoff::exponential(1)
    ->dontRetryExceptions() // <<<
    ->attempt($action);

Backoff::exponential(1)
    ->dontRetryExceptions($default) // <<<
    ->attempt($action);

Backoff::exponential(1)
    ->retryWhen($match, $strict = false, $default = null) // <<<
    ->attempt($action);

$callback = fn(mixed $result, AttemptLog $log): bool => …; // your logic here

Backoff::exponential(1)
    ->retryWhen($callback, false, $default = null) // <<<
    ->attempt($action);

Backoff::exponential(1)
    ->retryUntil($match, $strict = false) // <<<
    ->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::exponential(1)
    ->maxAttempts(10)
    // 0, 0, 0, 0, 0… delays when running tests
    ->onlyDelayWhen(!$runningTests) // <<<
    ->attempt($action);

$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
>