1. Go to this page and download the library: Download gregpriday/laravel-retry 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/ */
use GregPriday\LaravelRetry\Facades\Retry;
use Illuminate\Support\Facades\Http;
// Simple retry with default settings
$data = Retry::run(function () {
$response = Http::get('https://api.example.com/data');
$response->throw(); // Will automatically retry on network errors & 5xx responses
return $response->json();
})->value();
// With HTTP Client macro (even simpler)
$response = Http::robustRetry(3) // Max 3 attempts total (1 initial + 2 retries)
->get('https://api.example.com/resource');
// If no strategy is explicitly provided, robustRetry uses GuzzleResponseStrategy as the base.
// When options are provided without a strategy, the GuzzleResponseStrategy is wrapped with
// CustomOptionsStrategy to apply those settings.
$result = Retry::maxRetries(5) // Override default max retries
->timeout(10) // Override default timeout per attempt (seconds)
->withStrategy(new ExponentialBackoffStrategy(baseDelay: 0.5)) // Configure strategy with custom base delay
->run(function () {
// Your operation here
})
->value(); // Get result directly or throws the final exception
use GregPriday\LaravelRetry\Facades\Retry;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
$lockKey = 'api-operation-lock';
$result = Retry::maxRetries(3)
->run(function () {
// Set a lock to prevent concurrent operations
Cache::put($lockKey, true, 60);
// Operation that might fail
$response = Http::get('https://api.example.com/data');
$response->throw();
return $response->json();
})
->then(function ($data) {
return ['status' => 'success', 'data' => $data];
})
->catch(function (Throwable $e) {
return ['status' => 'failed', 'error' => $e->getMessage()];
})
->finally(function () use ($lockKey) {
// This always executes regardless of success or failure
// Perfect for cleanup operations
Cache::forget($lockKey);
Log::info('Operation completed, lock released');
})
->value();
$processedData = Retry::maxRetries(3)
->run(function () {
return DB::transaction(function () {
// Complex database operation that might fail
return processSensitiveData();
});
})
->then(function ($data) {
// Process successful result
Log::info('Data processed successfully', ['record_count' => count($data)]);
return $data;
})
->catch(function (QueryException $e) {
// Handle database-specific errors
Log::error('Database error during processing', ['error' => $e->getMessage()]);
return ['error' => 'Database operation failed', 'retry_allowed' => true];
})
->catch(function (Throwable $e) {
// Handle any other exceptions
Log::critical('Unexpected error during processing', ['error' => $e->getMessage()]);
return ['error' => 'Processing failed', 'retry_allowed' => false];
})
->finally(function () {
// Clean up resources, always executed
Cache::forget('processing_lock');
})
->value(); // Get the final result (or throw the exception)
use GregPriday\LaravelRetry\Strategies\ExponentialBackoffStrategy;
use GregPriday\LaravelRetry\Strategies\FixedDelayStrategy;
use GregPriday\LaravelRetry\Strategies\LinearBackoffStrategy;
// With ExponentialBackoffStrategy - Overrides config default for this instance
// Base delay: 1.5s, then ~3s, then ~6s with jitter
Retry::withStrategy(new ExponentialBackoffStrategy(baseDelay: 1.5))
->run($operation);
// With FixedDelayStrategy - Overrides config default for this instance
// Every retry will wait 0.5s (plus jitter if enabled)
Retry::withStrategy(new FixedDelayStrategy(baseDelay: 0.5))
->run($operation);
// With LinearBackoffStrategy (increment: 1.5) - Overrides config default for this instance
// First retry after 1s, then 2.5s, then 4s
Retry::withStrategy(new LinearBackoffStrategy(baseDelay: 1.0, increment: 1.5))
->run($operation);
use Illuminate\Support\Facades\Http;
use GregPriday\LaravelRetry\Strategies\ExponentialBackoffStrategy;
// Simple robust retry with default settings
$response = Http::robustRetry(3) // Max 3 attempts total (1 initial + 2 retries)
->get('https://api.example.com/resource');
// Using a custom retry strategy
$strategy = new ExponentialBackoffStrategy(
multiplier: 1.5,
maxDelay: 60,
withJitter: true
);
$response = Http::withRetryStrategy($strategy, [
'max_attempts' => 5,
'base_delay' => 2.5, // Configures the baseDelay parameter of the underlying strategy
'timeout' => 15,
])
->post('https://api.example.com/submit', ['foo' => 'bar']);
// Custom retry conditions
$response = Http::retryWhen(
function ($attempt, $maxAttempts, $exception, $options) {
return $attempt < $maxAttempts &&
$exception instanceof RequestException &&
$exception->response->status() === 503;
},
[
'max_attempts' => 4,
'timeout' => 10,
'base_delay' => 0.75, // Configures the baseDelay parameter of the underlying strategy
]
)->get('https://api.example.com/data');
use Illuminate\Support\Facades\Http;
// Apply circuit breaker with default parameters
// Uses GuzzleResponseStrategy as the default inner strategy
$response = Http::withCircuitBreaker()
->get('https://api.example.com/endpoint');
// Apply circuit breaker with custom parameters
// The base_delay configures the underlying inner strategy (GuzzleResponseStrategy)
$response = Http::withCircuitBreaker(
failureThreshold: 5, // Open after 5 failures
resetTimeout: 60, // Try again after 60 seconds
[
'max_attempts' => 3,
'base_delay' => 1.0, // Configures the baseDelay of the inner strategy
'timeout' => 5,
]
)->post('https://api.example.com/data', ['key' => 'value']);
use Illuminate\Support\Facades\Http;
// Apply rate limiting with default parameters
$response = Http::withRateLimitHandling()
->get('https://api.example.com/endpoint');
// Apply rate limiting with custom parameters
$response = Http::withRateLimitHandling(
maxAttempts: 100, // Max 100 requests
timeWindow: 60, // Per minute
storageKey: 'api-rate-limit',
[
'max_attempts' => 3,
'base_delay' => 1.0,
]
)->get('https://api.example.com/data');
use Illuminate\Support\Facades\Http;
// Combining features
$response = Http::withToken('api-token')
->withCircuitBreaker(5, 60)
->withHeaders(['Custom-Header' => 'Value'])
->timeout(5)
->get('https://api.example.com/protected-endpoint');
use GregPriday\LaravelRetry\Facades\RetryablePipeline;
use GregPriday\LaravelRetry\Strategies\ExponentialBackoffStrategy;
use GregPriday\LaravelRetry\Contracts\RetryStrategy;
use GregPriday\LaravelRetry\Strategies\FixedDelayStrategy;
// Define pipeline stages with custom retry configurations
class ValidateDataStage
{
// Uses pipeline default settings
public function handle($data, $next)
{
// Validation logic
return $next($data);
}
}
class ProcessDataStage
{
public int $retryCount = 4; // Override pipeline default retries
public RetryStrategy $retryStrategy; // Custom retry strategy for this stage
public function __construct()
{
// Configure baseDelay in the strategy constructor
$this->retryStrategy = new ExponentialBackoffStrategy(
baseDelay: 2.0,
multiplier: 1.5
);
}
public function handle($data, $next)
{
// Process data here
return $next($data);
}
}
class SaveResultsStage
{
public int $retryCount = 1; // Only 1 retry for saving
public int $timeout = 10; // 10-second timeout per attempt
public function handle($data, $next)
{
// Save results logic
return $next($data);
}
}
$result = RetryablePipeline::maxRetries(2)
->withStrategy(new FixedDelayStrategy(baseDelay: 0.5)) // Default strategy for all stages
->send(['initial' => 'data'])
->through([
new ValidateDataStage(), // Uses pipeline defaults (2 retries, FixedDelay)
new ProcessDataStage(), // Uses its own settings (4 retries, ExponentialBackoff)
new SaveResultsStage(), // Uses its own settings (1 retry, 10s timeout)
])
->then(function ($processedData) {
return $processedData;
});
use GregPriday\LaravelRetry\Facades\Retry;
use GregPriday\LaravelRetry\Strategies\ExponentialBackoffStrategy;
Retry::withStrategy(new ExponentialBackoffStrategy())
->run(function () {
// Your operation here
});
// Using the class directly
use GregPriday\LaravelRetry\Strategies\ExponentialBackoffStrategy;
$strategy = new ExponentialBackoffStrategy(
baseDelay: 1.0, // Initial value used for delay calculation (default: 1.0)
multiplier: 2.0, // Delay multiplier
maxDelay: 60, // Maximum delay in seconds
withJitter: true, // Add randomness to prevent thundering herd
jitterPercent: 0.2 // Percentage of jitter (0.2 means ±20%)
);
// Using the factory with options
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('exponential-backoff', [
'baseDelay' => 1.0,
'multiplier' => 2.0,
'maxDelay' => 60,
'withJitter' => true,
'jitterPercent' => 0.2
]);
// Using the class directly
use GregPriday\LaravelRetry\Strategies\LinearBackoffStrategy;
$strategy = new LinearBackoffStrategy(
baseDelay: 1.0, // Base delay in seconds (default: 1.0)
increment: 5, // Add 5 seconds each retry
maxDelay: 30 // Cap at 30 seconds
);
// Using the factory with options
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('linear-backoff', [
'baseDelay' => 1.0,
'increment' => 5,
'maxDelay' => 30
]);
// Using the class directly
use GregPriday\LaravelRetry\Strategies\FibonacciBackoffStrategy;
$strategy = new FibonacciBackoffStrategy(
baseDelay: 1.0, // Base delay in seconds (default: 1.0)
maxDelay: 60, // Maximum delay in seconds
withJitter: true, // Add randomness to prevent thundering herd
jitterPercent: 0.2 // Percentage of jitter (0.2 means ±20%)
);
// Using the factory with options
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('fibonacci-backoff', [
'baseDelay' => 1.0,
'maxDelay' => 60,
'withJitter' => true,
'jitterPercent' => 0.2
]);
// Using the class directly
use GregPriday\LaravelRetry\Strategies\FixedDelayStrategy;
$strategy = new FixedDelayStrategy(
baseDelay: 1.0, // Fixed delay between all retries (default: 1.0)
withJitter: true, // Add randomness
jitterPercent: 0.2 // ±20% jitter
);
// Using the factory with options
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('fixed-delay', [
'baseDelay' => 1.0,
'withJitter' => true,
'jitterPercent' => 0.2
]);
// Using the class directly
use GregPriday\LaravelRetry\Strategies\DecorrelatedJitterStrategy;
$strategy = new DecorrelatedJitterStrategy(
baseDelay: 1.0, // Base delay in seconds (default: 1.0)
maxDelay: 60, // Maximum delay
minFactor: 1.0, // Minimum delay multiplier
maxFactor: 3.0 // Maximum delay multiplier
);
// Using the factory with options
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('decorrelated-jitter', [
'baseDelay' => 1.0,
'maxDelay' => 60,
'minFactor' => 1.0,
'maxFactor' => 3.0
]);
// Using the class directly
use GregPriday\LaravelRetry\Strategies\GuzzleResponseStrategy;
use GregPriday\LaravelRetry\Strategies\ExponentialBackoffStrategy;
$strategy = new GuzzleResponseStrategy(
baseDelay: 1.0, // Base delay in seconds (default: 1.0)
innerStrategy: new ExponentialBackoffStrategy(baseDelay: 0.5), // Optional custom inner strategy
maxDelay: 60
);
// Using the factory with options
// Inner strategy defaults to application default if not specified
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('guzzle-response', [
'baseDelay' => 1.0, // Configures the default inner strategy's base delay
'maxDelay' => 60
// 'innerStrategy' => new CustomInnerStrategy() // Can also provide an instance
]);
// Using the class directly
use GregPriday\LaravelRetry\Strategies\ResponseContentStrategy;
use GregPriday\LaravelRetry\Strategies\ExponentialBackoffStrategy;
$strategy = new ResponseContentStrategy(
innerStrategy: new ExponentialBackoffStrategy(baseDelay: 0.5), // Inner strategy controls delay
retryableContentPatterns: ['/server busy/i', '/try again/i'], // Regex patterns in body
retryableErrorCodes: ['TRY_AGAIN'],
errorCodePaths: ['error.status_code']
);
// Using the factory with options
// Options are typically loaded from config('retry.response_content')
// Inner strategy defaults to the application's default retry strategy
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('response-content');
// Or provide specific options (less common, usually configured globally)
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('response-content', [
'retryableContentPatterns' => ['/custom pattern/'],
'retryableErrorCodes' => ['MY_CODE'],
'errorCodePaths' => ['data.error_status'],
// 'innerStrategy' can also be provided here if needed
]);
// Using the class directly
use GregPriday\LaravelRetry\Strategies\CircuitBreakerStrategy;
use GregPriday\LaravelRetry\Strategies\ExponentialBackoffStrategy;
$strategy = new CircuitBreakerStrategy(
innerStrategy: new ExponentialBackoffStrategy(baseDelay: 2.0), // Inner strategy controls delay
failureThreshold: 3, // Open after 3 failures
resetTimeout: 120, // Stay open for 2 minutes
cacheKey: 'my_service_circuit', // Optional unique key for this circuit
cacheTtl: 1440, // Cache TTL in minutes (default: 1 day)
failOpenOnCacheError: false // Default behavior on cache errors
);
// Using the factory with options
// Creates a circuit breaker using default or named service settings from config
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('circuit-breaker');
// Or with specific options
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('circuit-breaker', [
'failureThreshold' => 5,
'resetTimeout' => 60,
// Inner strategy defaults to application default if not specified
]);
// Using the class directly
use GregPriday\LaravelRetry\Strategies\RateLimitStrategy;
use GregPriday\LaravelRetry\Strategies\FixedDelayStrategy;
$strategy = new RateLimitStrategy(
innerStrategy: new FixedDelayStrategy(baseDelay: 0.5), // Inner strategy controls delay
maxAttempts: 50, // Max attempts allowed
timeWindow: 60, // Within this time window (seconds)
storageKey: 'api-rate-limiter' // Unique key for the rate limiter
);
// Using the factory with options
// Inner strategy defaults to application default if not specified
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('rate-limit', [
'maxAttempts' => 100,
'timeWindow' => 60,
'storageKey' => 'my_api_limit'
// 'innerStrategy' => new CustomInnerStrategy() // Can also provide an instance
]);
// Using the class directly
use GregPriday\LaravelRetry\Strategies\TotalTimeoutStrategy;
use GregPriday\LaravelRetry\Strategies\LinearBackoffStrategy;
$strategy = new TotalTimeoutStrategy(
innerStrategy: new LinearBackoffStrategy(baseDelay: 0.5), // Inner strategy controls delay
totalTimeout: 30.0 // Complete within 30 seconds (float for precision)
);
// Using the factory with options
// totalTimeout loaded from config('retry.total_timeout')
// Inner strategy defaults to the application's default retry strategy
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('total-timeout');
// Or with specific options
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('total-timeout', [
'totalTimeout' => 45.5, // Override total timeout
// 'innerStrategy' => new CustomInnerStrategy() // Can also provide an instance
]);
// Using the class directly
use GregPriday\LaravelRetry\Strategies\CustomOptionsStrategy;
use GregPriday\LaravelRetry\Strategies\ExponentialBackoffStrategy;
$strategy = new CustomOptionsStrategy(
baseDelay: 1.0, // Base delay reference for callbacks
innerStrategy: new ExponentialBackoffStrategy(baseDelay: 0.5), // Base strategy to potentially fall back to
options: ['custom_flag' => true, 'user_id' => 123] // Custom data for callbacks
);
// Override the retry decision logic
$strategy->withShouldRetryCallback(function ($attempt, $maxAttempts, $exception, $options) {
// Only retry if custom_flag is true and within attempts
return $options['custom_flag'] && $attempt < $maxAttempts;
});
// Override the delay calculation logic
$strategy->withDelayCallback(function ($attempt, $baseDelay, $options) {
// Use baseDelay, but maybe double it if custom_flag is set
return $baseDelay * ($options['custom_flag'] ? 2 : 1);
});
// Using the factory with options (callbacks set after instantiation)
$strategy = \GregPriday\LaravelRetry\Factories\StrategyFactory::make('custom-options', [
'baseDelay' => 2.0,
'options' => ['initial_option' => 'value']
// Inner strategy defaults to application default
]);
// $strategy->withShouldRetryCallback(...)
// $strategy->withDelayCallback(...)
// Note: Direct instantiation is generally preferred for this strategy
// since callbacks are fundamental to its functionality
// Using the class directly
use GregPriday\LaravelRetry\Strategies\CallbackRetryStrategy;
use App\Exceptions\CustomTransientException; // Example custom exception
$strategy = new CallbackRetryStrategy(
// Define how delay is calculated (receives attempt, baseDelay, maxAttempts, exception, options)
delayCallback: fn($attempt, $baseDelay) => $baseDelay * ($attempt + 1), // e.g., 1s, 2s, 3s...
// Define when to retry (optional, defaults to checking attempts)
// Receives (attempt, maxAttempts, exception, options)
shouldRetryCallback: fn($attempt, $maxAttempts, $exception) =>
$attempt < $maxAttempts && $exception instanceof CustomTransientException,
baseDelay: 1.0, // Reference delay value for delayCallback (default: 1.0)
options: ['log_retries' => true] // Custom data passed to callbacks (default: [])
);
// Usage:
Retry::withStrategy($strategy)->maxRetries(3)->run(fn() => /* operation */);
// Note: Creating via factory alias is less common as callbacks must be provided in the constructor.
// You would typically instantiate this directly as shown above.
use GregPriday\LaravelRetry\Strategies\CircuitBreakerStrategy;
use GregPriday\LaravelRetry\Strategies\RateLimitStrategy;
use GregPriday\LaravelRetry\Strategies\ExponentialBackoffStrategy;
// Create a strategy that implements circuit breaking, rate limiting, and exponential backoff
$exponentialStrategy = new ExponentialBackoffStrategy(
baseDelay: 0.5,
multiplier: 2.0
);
$rateStrategy = new RateLimitStrategy(
innerStrategy: $exponentialStrategy,
maxAttempts: 50,
timeWindow: 60
);
$strategy = new CircuitBreakerStrategy(
innerStrategy: $rateStrategy,
failureThreshold: 3,
resetTimeout: 120
);
use GregPriday\LaravelRetry\Facades\Retry;
// Using default exception handlers
$result = Retry::run(function () {
// This will automatically retry on common HTTP client exceptions
$response = Http::get('https://api.example.com/data');
$response->throw();
return $response->json();
});
use GregPriday\LaravelRetry\Facades\Retry;
$result = Retry::retryIf(function (Throwable $e, int $attempt) {
// Custom logic to determine if retry is needed
return $e instanceof CustomException && $attempt < 5;
})
->run(function () {
// Your operation here
});
// Or use retryUnless for inverse logic
$result = Retry::retryUnless(function (Throwable $e, int $attempt) {
return $e instanceof PermanentFailureException;
})
->run(function () {
// Your operation here
});
use GregPriday\LaravelRetry\Facades\Retry;
use App\Exceptions\Retry\Handlers\CustomApiHandler;
use Illuminate\Support\Facades\Http;
use Throwable;
// Create a custom handler instance
$handler = new CustomApiHandler();
$result = Retry::retryIf(function (Throwable $e, int $attempt) use ($handler) {
// First check our custom handler to see if exception is generally retryable
$handlerAllowsRetry = $handler->isRetryable($e);
// Then add additional specific conditions for this operation
$isRateLimitError = $e instanceof RequestException && $e->response->status() === 429;
// Only retry rate limit errors for a few attempts
return $handlerAllowsRetry && (!$isRateLimitError || $attempt < 3);
})
->run(function () {
$response = Http::get('https://api.example.com/data');
$response->throw();
return $response->json();
});
namespace App\Exceptions\Retry\Handlers;
use GregPriday\LaravelRetry\Contracts\RetryableExceptionHandler;
use GregPriday\LaravelRetry\Exceptions\Handlers\BaseHandler;
class CustomApiHandler extends BaseHandler implements RetryableExceptionHandler
{
public function isApplicable(): bool
{
// Return true if this handler should be active
return true;
}
public function getExceptions(): array
{
return [
CustomApiException::class,
AnotherCustomException::class,
];
}
public function getPatterns(): array
{
return [
'/rate limit exceeded/i',
'/server temporarily unavailable/i',
];
}
}
'handler_paths' => [
app_path('Exceptions/Retry/Handlers'),
app_path('Services/API/RetryHandlers'),
// Add more paths here
],
namespace App\Exceptions\Retry\Handlers;
use GregPriday\LaravelRetry\Exceptions\Handlers\DatabaseHandler;
class CustomDatabaseHandler extends DatabaseHandler
{
public function isApplicable(): bool
{
// Disable this handler in testing environment
if (app()->environment('testing')) {
return false;
}
return parent::isApplicable();
}
}
use GregPriday\LaravelRetry\Events\RetryingOperationEvent;
use GregPriday\LaravelRetry\Events\OperationSucceededEvent;
use GregPriday\LaravelRetry\Events\OperationFailedEvent;
protected $listen = [
RetryingOperationEvent::class => [
LogRetryAttemptListener::class,
],
OperationSucceededEvent::class => [
LogSuccessListener::class,
],
OperationFailedEvent::class => [
LogFailureListener::class,
NotifyAdminListener::class,
],
];