Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Backoff Laravel Package

yriveiro/php-backoff

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require yriveiro/php-backoff
    

    Add to composer.json if not auto-loaded:

    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "Yriveiro\\Backoff\\": "vendor/yriveiro/php-backoff/src/"
        }
    }
    

    Run composer dump-autoload.

  2. First Use Case Retry a failing HTTP request with exponential backoff:

    use Yriveiro\Backoff\Backoff;
    use Yriveiro\Backoff\ExponentialBackoff;
    
    $backoff = new ExponentialBackoff(100, 3); // 100ms base delay, 3 retries
    $attempt = 0;
    
    while ($attempt <= 3) {
        try {
            $response = Http::get('https://api.example.com/data');
            if ($response->successful()) {
                return $response->json();
            }
            throw new \RuntimeException('Request failed');
        } catch (\Exception $e) {
            if ($attempt < 3) {
                $backoff->sleep();
                $attempt++;
            } else {
                throw $e;
            }
        }
    }
    
  3. Where to Look First


Implementation Patterns

Common Workflows

  1. Retryable HTTP Requests Wrap Laravel HTTP client calls in a retry loop:

    public function fetchWithRetry(string $url, int $maxRetries = 3)
    {
        $backoff = new ExponentialBackoff(200, $maxRetries);
        $attempt = 0;
    
        while ($attempt <= $maxRetries) {
            try {
                return Http::retry($maxRetries, $backoff->getDelay($attempt))
                    ->get($url)
                    ->throw();
            } catch (\Exception $e) {
                $attempt++;
                if ($attempt <= $maxRetries) {
                    $backoff->sleep();
                }
            }
        }
        throw new \RuntimeException('Max retries exceeded');
    }
    
  2. Database Operations Retry on deadlocks or transient failures:

    public function retryableTransaction(Closure $callback)
    {
        $backoff = new ExponentialBackoff(500, 3);
        $attempt = 0;
    
        while ($attempt <= 3) {
            try {
                return DB::transaction($callback);
            } catch (\Illuminate\Database\QueryException $e) {
                if (str_contains($e->getMessage(), 'deadlock')) {
                    $attempt++;
                    $backoff->sleep();
                } else {
                    throw $e;
                }
            }
        }
        throw new \RuntimeException('Transaction failed after retries');
    }
    
  3. Custom Backoff Strategies Implement BackoffInterface for non-exponential delays:

    class FixedBackoff implements BackoffInterface
    {
        private $delay;
    
        public function __construct(int $delayMs)
        {
            $this->delay = $delayMs;
        }
    
        public function sleep(): void
        {
            usleep($this->delay * 1000);
        }
    
        public function getDelay(int $attempt): int
        {
            return $this->delay;
        }
    }
    

Integration Tips

  • Laravel Service Providers: Bind the backoff strategy to the container:
    $this->app->singleton(BackoffInterface::class, function ($app) {
        return new ExponentialBackoff(100, 3);
    });
    
  • Middleware: Create middleware for retryable routes:
    public function handle($request, Closure $next)
    {
        $backoff = app(BackoffInterface::class);
        $attempt = 0;
    
        while ($attempt <= 3) {
            try {
                return $next($request);
            } catch (\Exception $e) {
                $attempt++;
                if ($attempt <= 3) {
                    $backoff->sleep();
                } else {
                    throw $e;
                }
            }
        }
    }
    
  • Jobs: Use in queued jobs for idempotent operations:
    public function handle()
    {
        $backoff = new ExponentialBackoff(1000, 5); // 1s base delay, 5 retries
        $attempt = 0;
    
        while ($attempt <= 5) {
            try {
                $this->processIdempotentOperation();
                return;
            } catch (\Exception $e) {
                $attempt++;
                if ($attempt <= 5) {
                    $backoff->sleep();
                }
            }
        }
        throw new \RuntimeException('Job failed after retries');
    }
    

Gotchas and Tips

Pitfalls

  1. Infinite Loops

    • Forgetting to increment $attempt or misconfiguring maxRetries can cause infinite retries.
    • Fix: Always validate $attempt <= maxRetries and throw an exception when exceeded.
  2. Jitter (Thundering Herd)

    • Exponential backoff without jitter can cause synchronized retries in distributed systems.
    • Fix: Add randomness to delays:
      $delay = $backoff->getDelay($attempt) + rand(0, 100);
      
  3. Blocking the Event Loop

    • usleep() blocks the PHP process. Avoid in long-running scripts or async contexts (e.g., Laravel Horizon).
    • Fix: Use sleep() for seconds or async alternatives like ReactPHP for non-blocking delays.
  4. Ignoring Non-Retryable Exceptions

    • Retrying on all exceptions can mask critical failures (e.g., auth errors, invalid data).
    • Fix: Whitelist retryable exceptions:
      $retryable = $e instanceof \Illuminate\Http\Client\ConnectionException ||
                   $e instanceof \PDOException && str_contains($e->getMessage(), 'deadlock');
      
  5. Stateful Operations

    • Retrying operations that modify state (e.g., payments, deletions) can cause duplicates.
    • Fix: Use idempotency keys or transactional outbox patterns.

Debugging

  • Log Delays: Add logging to track retry attempts:
    \Log::debug("Retry attempt {$attempt}/{$maxRetries}. Waiting {$delay}ms.");
    
  • Custom Exceptions: Extend BackoffException for domain-specific errors:
    throw new BackoffException('API rate limit exceeded', $attempt, $maxRetries);
    
  • Backoff Configuration: Validate baseDelay and maxRetries:
    • baseDelay should be ≥ 10ms (too low = thundering herd).
    • maxRetries should align with SLA requirements (e.g., 3–5 for transient issues).

Extension Points

  1. Custom Strategies Implement BackoffInterface for:

    • Linear backoff: Fixed delay increment (e.g., 100ms, 200ms, 300ms).
    • Fibonacci backoff: Avoids exponential explosion (e.g., 1s, 1s, 2s, 3s, 5s).
    • Adaptive backoff: Adjust delays based on failure patterns.
  2. Context-Aware Retries Pass additional context to backoff logic:

    class ContextualBackoff implements BackoffInterface
    {
        public function sleep(array $context): void
        {
            $delay = $this->calculateDelay($context['attempt'], $context['failureType']);
            usleep($delay * 1000);
        }
    }
    
  3. Metrics and Monitoring Track retry statistics:

    $backoff = new ExponentialBackoff(100, 3);
    $backoff->setOnRetry(function ($attempt, $delay) {
        \App\Observers\RetryObserver::record($attempt, $delay);
    });
    
  4. Laravel Integration

    • Macro HTTP Client: Extend Laravel’s HTTP client:
      Http::macro('retryWithBackoff', function ($url, $maxRetries = 3) {
          $backoff = new ExponentialBackoff(200, $maxRetries);
          // ... retry logic
      });
      
    • Event Listeners: Trigger events on retry attempts:
      event(new Retry
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource