# Retry Framework

Unified retry system for the PHP framework providing consistent retry logic across all components.

## Overview

The Retry Framework consolidates retry logic from various parts of the system (Database, HttpClient, Cache, etc.) into a single, configurable, and observable system.

## Quick Start

### Basic Usage

```php
use App\Framework\Retry\RetryManager;

// Simple retry with exponential backoff
$retryManager = RetryManager::create($clock)
    ->exponentialBackoff(maxAttempts: 3, initialDelayMs: 100);

$result = $retryManager->execute(function() {
    // Your operation that might fail
    return $this->unreliableApiCall();
});

// Get the result (throws exception if all retries failed)
$data = $result->getResult();
```

### Fluent API Examples

```php
// Linear delay strategy
$result = RetryManager::create($clock)
    ->linearDelay(maxAttempts: 5, delayMs: 500)
    ->execute($operation);

// Fixed retry (no delay)
$result = RetryManager::create($clock)
    ->fixedRetry(maxAttempts: 2)
    ->execute($operation);

// Custom exponential backoff
$result = RetryManager::create($clock)
    ->exponentialBackoff(
        maxAttempts: 4,
        initialDelayMs: 50,
        multiplier: 3.0
    )
    ->execute($operation);
```

### Pre-configured Scenarios

```php
// Database operations
$result = $retryManager->executeDatabaseOperation(function() {
    return $this->database->query('SELECT * FROM users');
});

// HTTP requests
$result = $retryManager->executeHttpRequest(function() {
    return $this->httpClient->get('https://api.example.com/data');
});

// Cache operations
$result = $retryManager->executeCacheOperation(function() {
    return $this->cache->get('expensive-computation');
});
```

## Strategies

### ExponentialBackoffStrategy

Doubles the delay between retries: 100ms → 200ms → 400ms → 800ms

```php
use App\Framework\Retry\Strategies\ExponentialBackoffStrategy;

// Custom strategy
$strategy = new ExponentialBackoffStrategy(
    maxAttempts: 3,
    initialDelay: Duration::fromMilliseconds(100),
    multiplier: 2.0,
    maxDelay: Duration::fromSeconds(10),
    useJitter: true
);

$retryManager = RetryManager::create($clock)->withStrategy($strategy);
```

**Pre-configured factories:**
- `ExponentialBackoffStrategy::forDatabase()` - Optimized for database operations
- `ExponentialBackoffStrategy::forHttpClient()` - Optimized for HTTP requests
- `ExponentialBackoffStrategy::forCache()` - Optimized for cache operations

### LinearDelayStrategy

Constant delay between retries: 500ms → 500ms → 500ms

```php
use App\Framework\Retry\Strategies\LinearDelayStrategy;

$strategy = LinearDelayStrategy::medium(3); // 500ms delay, 3 attempts
$retryManager = RetryManager::create($clock)->withStrategy($strategy);
```

**Pre-configured factories:**
- `LinearDelayStrategy::fast()` - 100ms delay
- `LinearDelayStrategy::medium()` - 500ms delay  
- `LinearDelayStrategy::slow()` - 2s delay

### FixedRetryStrategy

No delay between retries (immediate retry).

```php
use App\Framework\Retry\Strategies\FixedRetryStrategy;

$strategy = FixedRetryStrategy::quick(2); // 2 attempts, no delay
$retryManager = RetryManager::create($clock)->withStrategy($strategy);
```

## Event System Integration

The retry system emits events for monitoring and observability:

```php
// Enable events
$retryManager = RetryManager::create($clock)
    ->withEventDispatcher($eventDispatcher)
    ->withContext(['service' => 'user-api']);

// Events are automatically dispatched:
// - RetryAttemptEvent: For each attempt
// - RetrySucceededEvent: When operation succeeds
// - RetryFailedEvent: When all retries are exhausted
```

### Event Handlers

```php
use App\Framework\Core\Events\OnEvent;
use App\Framework\Retry\Events\RetryFailedEvent;

class RetryMonitoring 
{
    #[OnEvent]
    public function onRetryFailed(RetryFailedEvent $event): void
    {
        $this->logger->error('Retry operation failed', [
            'attempts' => $event->getAttemptCount(),
            'duration_ms' => $event->getDurationMs(),
            'operation_type' => $event->getOperationType(),
            'exception' => $event->getLastException()?->getMessage()
        ]);
    }
}
```

## Metrics and Monitoring

The system includes built-in metrics collection:

```php
use App\Framework\Retry\Metrics\RetryMetrics;

// RetryMetrics automatically collects data via events
$metrics = $container->get(RetryMetrics::class);

// Get statistics
$stats = $metrics->getStats();
echo "Success rate: " . $metrics->getSuccessRate() * 100 . "%\n";
echo "Retry rate: " . $metrics->getRetryRate() * 100 . "%\n";
echo "Avg attempts: " . $metrics->getAverageAttemptsPerOperation() . "\n";

// Most problematic operations
$mostRetried = $metrics->getMostRetriedOperations();
$commonExceptions = $metrics->getMostCommonExceptions();
```

## Migration from Legacy Middleware

### Database RetryMiddleware

**Before:**
```php
use App\Framework\Database\Middleware\RetryMiddleware;

$middleware = new RetryMiddleware($timer, maxRetries: 3, retryDelayMs: 100);
```

**After:**
```php
use App\Framework\Database\Middleware\UnifiedRetryMiddleware;

$middleware = new UnifiedRetryMiddleware($clock, $eventDispatcher);
```

### HttpClient RetryMiddleware

**Before:**
```php
use App\Framework\HttpClient\Middleware\RetryMiddleware;

$middleware = new RetryMiddleware($timer, maxRetries: 3, baseDelay: 1.0);
```

**After:**
```php
use App\Framework\HttpClient\Middleware\UnifiedRetryMiddleware;

$middleware = UnifiedRetryMiddleware::forApi($clock, $eventDispatcher);
```

## RetryableOperation Interface

For more complex scenarios, implement the `RetryableOperation` interface:

```php
use App\Framework\Retry\RetryableOperation;

class DatabaseBackupOperation implements RetryableOperation
{
    public function execute(): mixed
    {
        return $this->performBackup();
    }

    public function canRetry(Throwable $exception): bool
    {
        // Don't retry on authentication errors
        return !($exception instanceof AuthenticationException);
    }

    public function prepareRetry(int $attempt, Throwable $lastException): void
    {
        // Clean up before retry
        $this->cleanup();
    }
}

// Usage
$operation = new DatabaseBackupOperation();
$result = $retryManager->executeOperation($operation);
```

## Configuration

The retry system integrates with the framework's dependency injection:

```php
// In your service provider or initializer
$container->bind(RetryManager::class, function($container) {
    return RetryManager::create($container->get(Clock::class))
        ->withEventDispatcher($container->get(EventDispatcherInterface::class));
});
```

## Best Practices

1. **Choose appropriate strategies**: Use exponential backoff for external services, linear delay for predictable services
2. **Set reasonable limits**: Don't retry indefinitely, set max attempts and timeouts
3. **Monitor and alert**: Use the event system to monitor retry patterns and failures
4. **Consider circuit breakers**: For external services, combine with circuit breaker pattern
5. **Add context**: Use `withContext()` to add meaningful metadata for debugging

## Error Handling

```php
$result = $retryManager->execute($operation);

if ($result->wasSuccessful()) {
    $data = $result->getResult();
    echo "Success after {$result->getAttemptCount()} attempts\n";
} else {
    echo "Failed after {$result->getAttemptCount()} attempts\n";
    echo "Total duration: {$result->getTotalDuration()->toSeconds()}s\n";
    throw $result->lastException;
}
```

## Performance Considerations

- **Jitter**: Exponential backoff includes jitter by default to prevent thundering herd
- **Memory**: Retry history is kept in memory for the duration of the operation
- **Events**: Event dispatching adds minimal overhead; disable if not needed
- **Strategies**: Fixed retry has the lowest overhead, exponential backoff the highest