PHPackages                             stechstudio/backoff - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. stechstudio/backoff

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

stechstudio/backoff
===================

PHP library providing retry functionality with multiple backoff strategies and jitter support

1.6(1y ago)2044.6M—5.1%2618MITPHPCI passing

Since Oct 20Pushed 1y ago6 watchersCompare

[ Source](https://github.com/stechstudio/Backoff)[ Packagist](https://packagist.org/packages/stechstudio/backoff)[ RSS](/packages/stechstudio-backoff/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (7)Dependencies (1)Versions (14)Used By (18)

PHP Backoff
===========

[](#php-backoff)

[![Latest Version on Packagist](https://camo.githubusercontent.com/27e3b9f88f7211ad8ecbc9caff6b73f542094a703b837289769fed5df092defc/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f737465636873747564696f2f6261636b6f66662e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/stechstudio/backoff)[![Tests](https://camo.githubusercontent.com/9c368de246480c2b195f892495721111028554ecaacaa524077e9f786dc4acd4/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f737465636873747564696f2f6261636b6f66662f72756e2d74657374732e796d6c3f7374796c653d666c61742d737175617265)](https://camo.githubusercontent.com/9c368de246480c2b195f892495721111028554ecaacaa524077e9f786dc4acd4/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f737465636873747564696f2f6261636b6f66662f72756e2d74657374732e796d6c3f7374796c653d666c61742d737175617265)[![Software License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE.md)[![Total Downloads](https://camo.githubusercontent.com/43a207228879acc4cc1ab7e193f35eac27811a18af1795c47c8e5e8977d33597/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f737465636873747564696f2f6261636b6f66662e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/stechstudio/backoff)

Easily wrap your code with retry functionality. This library provides:

1. 4 backoff strategies (plus the ability to use your own)
2. Optional jitter / randomness to spread out retries and minimize collisions
3. Wait time cap
4. Callbacks for custom retry logic or error handling

Installation
------------

[](#installation)

```
composer require stechstudio/backoff

```

Defaults
--------

[](#defaults)

This library provides sane defaults so you can hopefully just jump in for most of your use cases.

By default the backoff is quadratic with a 100ms base time (`attempt^2 * 100`), a max of 5 retries, and no jitter.

Quickstart
----------

[](#quickstart)

The simplest way to use Backoff is with the global `backoff` helper function:

```
$result = backoff(function() {
    return doSomeWorkThatMightFail();
});
```

If successful `$result` will contain the result of the closure. If max attempts are exceeded the inner exception is re-thrown.

You can of course provide other options via the helper method if needed.

Method parameters are `$callback`, `$maxAttempts`, `$strategy`, `$waitCap`, `$useJitter`.

Backoff class usage
-------------------

[](#backoff-class-usage)

The Backoff class constructor parameters are `$maxAttempts`, `$strategy`, `$waitCap`, `$useJitter`.

```
$backoff = new Backoff(10, 'exponential', 10000, true);
$result = $backoff->run(function() {
    return doSomeWorkThatMightFail();
});
```

Or if you are injecting the Backoff class with a dependency container, you can set it up with setters after the fact. Note that setters are chainable.

```
// Assuming a fresh instance of $backoff was handed to you
$result = $backoff
    ->setStrategy('constant')
    ->setMaxAttempts(10)
    ->enableJitter()
    ->run(function() {
        return doSomeWorkThatMightFail();
    });
```

Changing defaults
-----------------

[](#changing-defaults)

If you find you want different defaults, you can modify them via static class properties:

```
Backoff::$defaultMaxAttempts = 10;
Backoff::$defaultStrategy = 'exponential';
Backoff::$defaultJitterEnabled = true;
```

You might want to do this somewhere in your application bootstrap for example. These defaults will be used anytime you create an instance of the Backoff class or use the `backoff()` helper function.

Strategies
----------

[](#strategies)

There are four built-in strategies available: constant, linear, polynomial, and exponential.

The default base time for all strategies is 100 milliseconds.

### Constant

[](#constant)

```
$strategy = new ConstantStrategy(500);
```

This strategy will sleep for 500 milliseconds on each retry loop.

### Linear

[](#linear)

```
$strategy = new LinearStrategy(200);
```

This strategy will sleep for `attempt * baseTime`, providing linear backoff starting at 200 milliseconds.

### Polynomial

[](#polynomial)

```
$strategy = new PolynomialStrategy(100, 3);
```

This strategy will sleep for `(attempt^degree) * baseTime`, so in this case `(attempt^3) * 100`.

The default degree if none provided is 2, effectively quadratic time.

### Exponential

[](#exponential)

```
$strategy = new ExponentialStrategy(100);
```

This strategy will sleep for `(2^attempt) * baseTime`.

Specifying strategy
-------------------

[](#specifying-strategy)

In our earlier code examples we specified the strategy as a string:

```
backoff(function() {
    ...
}, 10, 'constant');

// OR

$backoff = new Backoff(10, 'constant');
```

This would use the `ConstantStrategy` with defaults, effectively giving you a 100 millisecond sleep time.

You can create the strategy instance yourself in order to modify these defaults:

```
backoff(function() {
    ...
}, 10, new LinearStrategy(500));

// OR

$backoff = new Backoff(10, new LinearStrategy(500));
```

You can also pass in an integer as the strategy, will translates to a ConstantStrategy with the integer as the base time in milliseconds:

```
backoff(function() {
    ...
}, 10, 1000);

// OR

$backoff = new Backoff(10, 1000);
```

Finally, you can pass in a closure as the strategy if you wish. This closure should receive an integer `attempt` and return a sleep time in milliseconds.

```
backoff(function() {
    ...
}, 10, function($attempt) {
    return (100 * $attempt) + 5000;
});

// OR

$backoff = new Backoff(10);
$backoff->setStrategy(function($attempt) {
    return (100 * $attempt) + 5000;
});
```

Wait cap
--------

[](#wait-cap)

You may want to use a fast growing backoff time (like exponential) but then also set a max wait time so that it levels out after a while.

This cap can be provided as the fourth argument to the `backoff` helper function, or using the `setWaitCap()` method on the Backoff class.

Jitter
------

[](#jitter)

If you have a lot of clients starting a job at the same time and encountering failures, any of the above backoff strategies could mean the workers continue to collide at each retry.

The solution for this is to add randomness. See here for a good explanation:

You can enable jitter by passing `true` in as the fifth argument to the `backoff` helper function, or by using the `enableJitter()` method on the Backoff class.

We use the "FullJitter" approach outlined in the above article, where a random number between 0 and the sleep time provided by your selected strategy is used.

Custom retry decider
--------------------

[](#custom-retry-decider)

By default Backoff will retry if an exception is encountered, and if it has not yet hit max retries.

You may provide your own retry decider for more advanced use cases. Perhaps you want to retry based on time rather than number of retries, or perhaps there are scenarios where you would want retry even when an exception was not encountered.

Provide the decider as a callback, or an instance of a class with an `__invoke` method. Backoff will hand it four parameters: the current attempt, max attempts, the last result received, and the exception if one was encountered. Your decider needs to return true or false.

```
$backoff->setDecider(function($attempt, $maxAttempts, $result, $exception = null) {
    return someCustomLogic();
});
```

Error handler callback
----------------------

[](#error-handler-callback)

You can provide a custom error handler to be notified anytime an exception occurs, even if we have yet to reach max attempts. This is a useful place to do logging for example.

```
$backoff->setErrorHandler(function($exception, $attempt, $maxAttempts) {
    Log::error("On run $attempt we hit a problem: " . $exception->getMessage());
});
```

###  Health Score

56

—

FairBetter than 98% of packages

Maintenance46

Moderate activity, may be stable

Popularity61

Solid adoption and visibility

Community34

Small or concentrated contributor base

Maturity70

Established project with proven stability

 Bus Factor1

Top contributor holds 74.4% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~257 days

Recently: every ~99 days

Total

13

Last Release

405d ago

Major Versions

0.4 → 1.0.02018-06-05

### Community

Maintainers

![](https://www.gravatar.com/avatar/315be5f111b5501a41b99a0205c9c85915335391168a0ed10316546a1a38bbd8?d=identicon)[jszobody](/maintainers/jszobody)

---

Top Contributors

[![jszobody](https://avatars.githubusercontent.com/u/203749?v=4)](https://github.com/jszobody "jszobody (32 commits)")[![bubba-h57](https://avatars.githubusercontent.com/u/603630?v=4)](https://github.com/bubba-h57 "bubba-h57 (2 commits)")[![Lewiscowles1986](https://avatars.githubusercontent.com/u/2605791?v=4)](https://github.com/Lewiscowles1986 "Lewiscowles1986 (2 commits)")[![kler](https://avatars.githubusercontent.com/u/966132?v=4)](https://github.com/kler "kler (1 commits)")[![Prizephitah](https://avatars.githubusercontent.com/u/786674?v=4)](https://github.com/Prizephitah "Prizephitah (1 commits)")[![VincentLanglet](https://avatars.githubusercontent.com/u/9052536?v=4)](https://github.com/VincentLanglet "VincentLanglet (1 commits)")[![alexjeen](https://avatars.githubusercontent.com/u/1567497?v=4)](https://github.com/alexjeen "alexjeen (1 commits)")[![yamadashy](https://avatars.githubusercontent.com/u/5019072?v=4)](https://github.com/yamadashy "yamadashy (1 commits)")[![askurihin](https://avatars.githubusercontent.com/u/37978981?v=4)](https://github.com/askurihin "askurihin (1 commits)")[![dbellettini](https://avatars.githubusercontent.com/u/325358?v=4)](https://github.com/dbellettini "dbellettini (1 commits)")

---

Tags

backoffjitterphpretry

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/stechstudio-backoff/health.svg)

```
[![Health](https://phpackages.com/badges/stechstudio-backoff/health.svg)](https://phpackages.com/packages/stechstudio-backoff)
```

###  Alternatives

[bref/dev-server

Local development server for serverless web apps

3612.5k](/packages/bref-dev-server)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
