1799401db5
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
- Add Copyright + FILE INFORMATION headers to 11 PHP enterprise classes - Add FILE INFORMATION blocks to 9 PHP files with incomplete headers - Add headers to 2 test files - Add markdown comment headers to 27 index/README files - Add headers to 5 root markdown files - Add FILE INFORMATION to 4 files with existing but incomplete headers All files now conform to moko-platform file header standard. Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
148 lines
4.5 KiB
PHP
148 lines
4.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Retry Helper - Retry execution with exponential backoff
|
|
*
|
|
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* FILE INFORMATION
|
|
* DEFGROUP: MokoStandards.Enterprise.Recovery
|
|
* INGROUP: MokoStandards.Enterprise
|
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
* PATH: /lib/Enterprise/RetryHelper.php
|
|
* BRIEF: Retry helper with exponential backoff
|
|
*
|
|
* @package MokoStandards\Enterprise
|
|
* @version 04.00.04
|
|
* @author MokoStandards Team
|
|
* @license GPL-3.0-or-later
|
|
*/
|
|
|
|
namespace MokoEnterprise;
|
|
|
|
use Exception;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Retry execution helper with exponential backoff.
|
|
*
|
|
* Features:
|
|
* - Configurable retry attempts
|
|
* - Exponential backoff strategy
|
|
* - Exception filtering
|
|
* - Retry and failure callbacks
|
|
*
|
|
* Example:
|
|
* ```php
|
|
* $retry = new RetryHelper(maxRetries: 3, backoffBase: 2.0);
|
|
* $result = $retry->execute(function() {
|
|
* // Your code that might fail
|
|
* return $api->call();
|
|
* });
|
|
* ```
|
|
*/
|
|
class RetryHelper
|
|
{
|
|
private int $maxRetries;
|
|
private float $backoffBase;
|
|
/** @var array<class-string<Throwable>> */
|
|
private array $retryableExceptions;
|
|
/** @var callable|null */
|
|
private $onRetry;
|
|
/** @var callable|null */
|
|
private $onFailure;
|
|
|
|
/**
|
|
* Initialize retry helper.
|
|
*
|
|
* @param int $maxRetries Maximum number of retry attempts
|
|
* @param float $backoffBase Base for exponential backoff (seconds)
|
|
* @param array<class-string<Throwable>> $retryableExceptions Exceptions to catch and retry
|
|
* @param callable|null $onRetry Callback function called on each retry
|
|
* @param callable|null $onFailure Callback function called on final failure
|
|
*/
|
|
public function __construct(
|
|
int $maxRetries = 3,
|
|
float $backoffBase = 2.0,
|
|
array $retryableExceptions = [Exception::class],
|
|
?callable $onRetry = null,
|
|
?callable $onFailure = null
|
|
) {
|
|
$this->maxRetries = $maxRetries;
|
|
$this->backoffBase = $backoffBase;
|
|
$this->retryableExceptions = $retryableExceptions;
|
|
$this->onRetry = $onRetry;
|
|
$this->onFailure = $onFailure;
|
|
}
|
|
|
|
/**
|
|
* Execute callable with retry logic.
|
|
*
|
|
* @param callable $callable Function to execute
|
|
* @return mixed Result of callable
|
|
* @throws Throwable If all retries exhausted
|
|
*/
|
|
public function execute(callable $callable): mixed
|
|
{
|
|
$lastException = null;
|
|
|
|
for ($attempt = 0; $attempt < $this->maxRetries; $attempt++) {
|
|
try {
|
|
$result = $callable();
|
|
|
|
if ($attempt > 0) {
|
|
error_log("Function succeeded on attempt " . ($attempt + 1));
|
|
}
|
|
|
|
return $result;
|
|
} catch (Throwable $e) {
|
|
// Check if this exception is retryable
|
|
$shouldRetry = false;
|
|
foreach ($this->retryableExceptions as $exceptionClass) {
|
|
if ($e instanceof $exceptionClass) {
|
|
$shouldRetry = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$shouldRetry) {
|
|
throw $e;
|
|
}
|
|
|
|
$lastException = $e;
|
|
|
|
if ($attempt < $this->maxRetries - 1) {
|
|
// Calculate backoff time
|
|
$backoffTime = $this->backoffBase ** $attempt;
|
|
error_log(
|
|
"Function failed on attempt " . ($attempt + 1) . "/{$this->maxRetries}: {$e->getMessage()}. " .
|
|
"Retrying in {$backoffTime}s..."
|
|
);
|
|
|
|
// Call retry callback if provided
|
|
if ($this->onRetry !== null) {
|
|
($this->onRetry)($attempt, $e, $backoffTime);
|
|
}
|
|
|
|
// Sleep for backoff time (convert to microseconds)
|
|
usleep((int) ($backoffTime * 1000000));
|
|
} else {
|
|
error_log("Function failed after {$this->maxRetries} attempts: {$e->getMessage()}");
|
|
|
|
// Call failure callback if provided
|
|
if ($this->onFailure !== null) {
|
|
($this->onFailure)($this->maxRetries, $e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// All retries exhausted
|
|
throw $lastException ?? new RecoveryError('All retries exhausted');
|
|
}
|
|
}
|