eventsauce/backoff
Small PHP library with a BackOffStrategy interface and ready-made retry delays (exponential, Fibonacci, linear). Configure initial delay, max tries, max delay, and growth base. Call backOff($tries, $throwable) inside retry loops to pause between attempts.
Pros:
BackOffStrategy interface allows for strategy injection, making it easy to swap implementations (e.g., exponential, Fibonacci) without modifying core logic.App\Exceptions\Handler). This fits well with Laravel’s event-driven and middleware-heavy architecture.run(function () { ... })).Cons:
goto, which is unconventional in PHP/Laravel and may violate team coding standards. The BackOffRunner mitigates this but isn’t always applicable (e.g., async jobs, middleware).Illuminate\Bus\PendingDispatchException or third-party packages (e.g., spatie/circuit-breaker) could fill this gap.Illuminate\Queue\ShouldQueue). The BackOffRunner can wrap job logic in handle() methods.Illuminate\Support\Facades\Http) for retrying API calls with jittered delays.DB::transaction()).dispatchSync()).BackOffRunner for non-critical operations (e.g., background jobs).maxTries and initialDelay for HTTP requests.Log facade) and track metrics (e.g., max_retries_reached events).maxTries: -1 risks unbounded delays. Avoid in production; use maxTries conservatively (e.g., 3–5).GET requests) unless necessary.FullJitter for high-contention scenarios (e.g., shared resources).HalfJitter or ScatteredJitter for finer control.Sentry or Laravel Debugbar?maxTries is exhausted? (e.g., log, notify, fail silently)HttpTests or QueueTests for integration tests?BackOffStrategy into controllers, services, or jobs.BackOffRunner for async retries.Http::retry() or custom middleware.Bus::dispatch()).schedule:run).// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->bind(BackOffStrategy::class, function () {
return new ExponentialBackOffStrategy(
initialDelayMs: 100,
maxTries: 3,
jitter: new FullJitter()
);
});
}
BackOffRunner to minimize code changes.// app/Jobs/ProcessPayment.php
public function handle()
{
$runner = new BackOffRunner(
new ExponentialBackOffStrategy(100, 3),
PaymentFailedException::class
);
$runner->run(fn() => $this->chargeCustomer());
}
BackOffStrategy.// app/Services/PaymentService.php
public function __construct(
private PaymentGateway $gateway,
private BackOffStrategy $backOff
) {}
public function charge(float $amount): void
{
$tries = 0;
start:
try {
$this->gateway->charge($amount);
} catch (PaymentFailedException $e) {
$this->backOff->backOff(++$tries, $e);
if ($tries < 3) goto start;
throw $e;
}
}
RetryableService).config/backoff.php).'strategies' => [
'payments' => [
'strategy' => ExponentialBackOffStrategy::class,
'initial_delay' => 100, // ms
'max_tries' => 3,
'jitter' => FullJitter::class,
],
'api_calls' => [
'strategy' => FibonacciBackOffStrategy::class,
'initial_delay' => 500,
'max_tries' => 5,
'jitter' => HalfJitter::class,
],
],
failed_jobs table).How can I help you explore Laravel packages today?