4cc3f5bee4
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Generic: Repo Health / Release configuration (push) Successful in 5s
Generic: Repo Health / Scripts governance (push) Successful in 5s
Generic: Repo Health / Release configuration (pull_request) Successful in 6s
Generic: Repo Health / Scripts governance (pull_request) Successful in 6s
Generic: Repo Health / Repository health (push) Successful in 14s
Generic: Repo Health / Repository health (pull_request) Successful in 12s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 44s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 49s
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been skipped
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been skipped
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
- Convert tabs to spaces (3,413 violations) - Fix line endings, trailing whitespace, brace placement - Break lines exceeding 150-char absolute limit - Replace heredoc tab closers with spaces - Fix empty elseif, forbidden function calls - Update phpcs.xml: exclude rules inappropriate for CLI scripts (SideEffects, MissingNamespace, MultipleClasses, HeaderOrder, empty catch blocks) 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');
|
|
}
|
|
}
|