PHPackages                             kissmint3395/aegis - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. kissmint3395/aegis

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

kissmint3395/aegis
==================

A PHP 8.2+ resilience library combining Retry, Circuit Breaker, Timeout, and more into a composable pipeline. Inspired by Resilience4j and .NET Polly.

v0.0.1(2mo ago)10MITPHPPHP ^8.2CI passing

Since Apr 30Pushed 2mo agoCompare

[ Source](https://github.com/kissmint3395/aegis)[ Packagist](https://packagist.org/packages/kissmint3395/aegis)[ RSS](/packages/kissmint3395-aegis/feed)WikiDiscussions master Synced 3w ago

READMEChangelog (1)Dependencies (6)Versions (2)Used By (0)

Aegis
=====

[](#aegis)

[日本語ドキュメント](#%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88)

A PHP 8.2+ resilience library that combines **Retry**, **Circuit Breaker**, **Timeout**, **Rate Limiter**, **Bulkhead**, **Fallback**, **Cache**, and **Hedge** into a single composable pipeline.

Inspired by [Resilience4j](https://resilience4j.readme.io/) and [.NET Polly](https://www.pollydocs.org/) — the PHP ecosystem's missing equivalent.

```
$pipeline = ResiliencePipeline::builder()
    ->circuitBreaker('payment-api', failureThreshold: 5)
    ->retry(maxAttempts: 3, backoff: ExponentialBackoff::withJitter(Duration::milliseconds(100)))
    ->timeout(Duration::seconds(5))
    ->fallback(fn(\Throwable $e) => CachedResponse::last())
    ->build();

$result = $pipeline->execute(fn() => $httpClient->post('/charge', $payload));
```

Why Aegis?
----------

[](#why-aegis)

PHP has several individual resilience libraries, but none that combine them into a composable pipeline:

LibraryRetryCBTimeoutRate LimiterBulkheadFallbackCacheComposablePHP 8.2+`ackintosh/ganesha`✗✅✗✗✗✗✗✗✅`yohang/finite`✗✗✗✗✗✗✗✗✅`cline/retry`✅✗✗✗✗✗✗✗✅**Aegis**✅✅✅✅✅✅✅✅✅Requirements
------------

[](#requirements)

- PHP 8.2+
- `psr/event-dispatcher` ^1.0
- `psr/simple-cache` ^3.0 *(optional, for persistent Circuit Breaker, Rate Limiter, and Bulkhead state)*

Installation
------------

[](#installation)

```
composer require kissmint3395/aegis
```

Usage
-----

[](#usage)

### Retry

[](#retry)

Retry a failing operation with configurable backoff.

```
use Aegis\ResiliencePipeline;
use Aegis\Backoff\ExponentialBackoff;
use Aegis\Duration;

$pipeline = ResiliencePipeline::builder()
    ->retry(
        maxAttempts: 3,
        backoff: ExponentialBackoff::withJitter(Duration::milliseconds(100)),
        retryOn: [\RuntimeException::class],
    )
    ->build();

$result = $pipeline->execute(fn() => $api->fetch());
```

**Backoff strategies:**

```
use Aegis\Backoff\FixedBackoff;
use Aegis\Backoff\ExponentialBackoff;

// Fixed delay
new FixedBackoff(Duration::milliseconds(200))

// Exponential: 100ms → 200ms → 400ms → ...
ExponentialBackoff::create(Duration::milliseconds(100))

// Exponential with jitter: randomised between 50%–100% of each step
ExponentialBackoff::withJitter(Duration::milliseconds(100), maxDelay: Duration::seconds(5))
```

**Conditional retry:**

```
->retry(
    retryIf: fn(\Throwable $e) => $e->getCode() >= 500,
)
```

### Circuit Breaker

[](#circuit-breaker)

Stop cascading failures by blocking calls when a service is unhealthy.

```
use Aegis\ResiliencePipeline;
use Aegis\Duration;

$pipeline = ResiliencePipeline::builder()
    ->circuitBreaker(
        name: 'inventory-api',
        failureThreshold: 5,   // Open after 5 consecutive failures
        successThreshold: 2,   // Close after 2 consecutive successes in HalfOpen
        resetAfter: Duration::seconds(30),
    )
    ->build();
```

**State transitions:**

```
Closed ──(5 failures)──► Open ──(30s elapsed)──► HalfOpen
  ▲                                                  │
  └──────────(2 successes)───────────────────────────┘
                              (1 failure) ──► Open

```

**Persistent state across requests (Redis, APCu, etc.):**

```
use Aegis\Strategy\CircuitBreaker\Storage\Psr16Storage;

$pipeline = ResiliencePipeline::builder()
    ->circuitBreaker('payment-api', storage: new Psr16Storage($redisCache))
    ->build();
```

**Ignore specific exceptions (e.g. validation errors should not trip the circuit):**

```
->circuitBreaker('api', ignoreExceptions: [\InvalidArgumentException::class])
```

### Adaptive Circuit Breaker

[](#adaptive-circuit-breaker)

A sliding-window variant that opens based on the **failure rate** (%) over the last N calls, rather than a consecutive-failure count. Equivalent to Resilience4j's sliding-window circuit breaker.

```
$pipeline = ResiliencePipeline::builder()
    ->adaptiveCircuitBreaker(
        name: 'payment-api',
        windowSize: 20,              // Evaluate the last 20 calls
        failureRateThreshold: 50.0,  // Open if ≥ 50% fail
        minimumCalls: 5,             // Don't open until at least 5 calls recorded
        successThreshold: 2,         // Close after 2 successes in HalfOpen
        resetAfter: Duration::seconds(30),
    )
    ->build();
```

**Persistent state:**

```
use Aegis\Strategy\CircuitBreaker\Storage\AdaptivePsr16Storage;

->adaptiveCircuitBreaker('api', storage: new AdaptivePsr16Storage($redisCache))
```

### Timeout

[](#timeout)

Enforce a maximum execution duration.

```
->timeout(Duration::seconds(5))
```

> **Note:** On Unix systems with the `pcntl` extension, Aegis uses `SIGALRM` for true preemptive interruption. On Windows and environments without `pcntl`, elapsed time is checked after execution — useful for limiting total retry budgets.

### Rate Limiter (Fixed Window)

[](#rate-limiter-fixed-window)

Limit the number of calls within a fixed time window.

```
use Aegis\ResiliencePipeline;
use Aegis\Duration;

$pipeline = ResiliencePipeline::builder()
    ->rateLimit(
        name: 'payment-api',
        limit: 100,                      // Max 100 calls per window
        window: Duration::seconds(60),
    )
    ->build();
```

**Persistent rate limiting across requests (Redis, APCu, etc.):**

```
use Aegis\Strategy\RateLimiter\Storage\Psr16Storage;

$pipeline = ResiliencePipeline::builder()
    ->rateLimit('payment-api', limit: 100, storage: new Psr16Storage($redisCache))
    ->build();
```

> **Note:** The default `InMemoryStorage` is process-scoped. For rate limiting across PHP-FPM workers, use `Psr16Storage` backed by Redis or APCu.

### Rate Limiter (Sliding Window)

[](#rate-limiter-sliding-window)

Eliminates the "boundary burst" problem of fixed-window rate limiting by tracking individual request timestamps.

```
$pipeline = ResiliencePipeline::builder()
    ->slidingRateLimit(
        name: 'payment-api',
        limit: 100,
        window: Duration::seconds(60),
    )
    ->build();
```

**Persistent state:**

```
use Aegis\Strategy\RateLimiter\Storage\SlidingWindowPsr16Storage;

->slidingRateLimit('payment-api', limit: 100, storage: new SlidingWindowPsr16Storage($redisCache))
```

> **Note:** Each entry stores a full timestamp array. For very high-volume endpoints, prefer a Redis ZSET-backed implementation.

### Rate Limiter (Token Bucket)

[](#rate-limiter-token-bucket)

Allows burst traffic up to a `capacity` while enforcing an average rate via continuous token refill. Tokens are added at `refillRate` per `refillPeriod`.

```
$pipeline = ResiliencePipeline::builder()
    ->tokenBucketRateLimit(
        name: 'payment-api',
        capacity: 20,                          // Max burst size
        refillRate: 10,                        // Tokens added per period
        refillPeriod: Duration::seconds(1),    // Refill interval
    )
    ->build();
```

**Persistent state:**

```
use Aegis\Strategy\RateLimiter\Storage\TokenBucketPsr16Storage;

->tokenBucketRateLimit('api', capacity: 20, storage: new TokenBucketPsr16Storage($redisCache))
```

### Bulkhead

[](#bulkhead)

Limit the number of concurrent executions to prevent resource exhaustion.

```
use Aegis\ResiliencePipeline;

$pipeline = ResiliencePipeline::builder()
    ->bulkhead(
        name: 'database',
        maxConcurrent: 10,   // Allow at most 10 concurrent calls
    )
    ->build();
```

**Persistent concurrency tracking across requests (Redis, APCu, etc.):**

```
use Aegis\Strategy\Bulkhead\Storage\Psr16Storage;

$pipeline = ResiliencePipeline::builder()
    ->bulkhead('database', maxConcurrent: 10, storage: new Psr16Storage($redisCache))
    ->build();
```

> **Note:** The default `InMemoryStorage` is process-scoped. For cross-worker concurrency limiting, use `Psr16Storage` backed by Redis or APCu.

### Fallback

[](#fallback)

Return a default value (or call an alternative callable) when all inner strategies fail.

```
$pipeline = ResiliencePipeline::builder()
    ->circuitBreaker('payment-api')
    ->retry(maxAttempts: 3)
    ->fallback(fn(\Throwable $e) => ['status' => 'unavailable'])
    ->build();
```

The fallback handler receives the original `\Throwable`. If the handler itself throws, a `FallbackNotAvailableException` is raised wrapping both the original and fallback exceptions.

```
use Aegis\Exception\FallbackNotAvailableException;

try {
    $result = $pipeline->execute(fn() => $api->fetch());
} catch (FallbackNotAvailableException $e) {
    $original = $e->getOriginalException();
}
```

### Cache

[](#cache)

Cache successful results and serve them on subsequent calls. Optionally serve stale entries when the downstream call fails.

```
use Aegis\Strategy\Cache\Storage\CacheInMemoryStorage;

$storage  = new CacheInMemoryStorage();  // or CachePsr16Storage for persistence

$pipeline = ResiliencePipeline::builder()
    ->cache(
        name: 'product-catalog',
        ttl: Duration::minutes(5),
        staleOnFailure: true,   // Serve expired cache when downstream throws
        storage: $storage,
    )
    ->build();
```

With `staleOnFailure: true`, if the downstream call throws and a (possibly expired) cache entry exists, the stale value is returned instead of propagating the exception.

### Hedge *(experimental)*

[](#hedge-experimental)

Fire additional "hedge" requests after a configurable delay and return the first successful result. Reduces tail latency for operations where some replicas respond faster than others.

```
$pipeline = ResiliencePipeline::builder()
    ->hedge(
        delay: Duration::milliseconds(200),  // Fire hedge after 200ms
        maxHedges: 1,
    )
    ->build();
```

> **Note:** True latency-based hedging requires callables that cooperatively yield via `Fiber::suspend()` (e.g. ReactPHP / Swoole). For synchronous callables, hedges fire immediately after each failure.

### Composing strategies

[](#composing-strategies)

Strategies wrap each other in the order they are added (first = outermost). The recommended order is: **Bulkhead → Rate Limiter → Timeout → Circuit Breaker → Retry → Fallback**.

```
$pipeline = ResiliencePipeline::builder()
    ->bulkhead('svc', maxConcurrent: 10)          // 1. Concurrency gate
    ->rateLimit('svc', limit: 100)                // 2. Rate gate
    ->timeout(Duration::seconds(10))              // 3. Total time budget
    ->circuitBreaker('svc', failureThreshold: 5)  // 4. Block if unhealthy
    ->retry(maxAttempts: 3)                       // 5. Retry transient failures
    ->fallback(fn() => $defaultValue)             // 6. Last-resort fallback
    ->build();
```

### PSR-14 Events

[](#psr-14-events)

Observe what happens inside the pipeline by wiring up a PSR-14 event dispatcher.

```
use Aegis\Event\RetryAttempted;
use Aegis\Event\CircuitOpened;
use Aegis\Event\RateLimitExceeded;
use Aegis\Event\TokenBucketExhausted;

$pipeline = ResiliencePipeline::builder()
    ->withEventDispatcher($dispatcher)
    ->circuitBreaker('api')
    ->retry(maxAttempts: 3)
    ->build();

// Note: listener registration API (listen/addListener/subscribeTo) depends on your PSR-14 implementation.
$dispatcher->listen(RetryAttempted::class, function (RetryAttempted $e) use ($logger): void {
    $logger->warning('Retry attempt', [
        'attempt'   => $e->attempt,
        'max'       => $e->maxAttempts,
        'delay_ms'  => $e->delayMs,
        'error'     => $e->cause->getMessage(),
    ]);
});
```

EventFired when`RetryAttempted`A retry is about to be delayed and re-attempted`CircuitOpened`Circuit transitions Closed → Open`CircuitClosed`Circuit transitions HalfOpen → Closed`CircuitHalfOpened`Circuit transitions Open → HalfOpen`RateLimitExceeded`A call is rejected because the rate limit is reached`BulkheadRejected`A call is rejected because the bulkhead is full`TokenBucketExhausted`A call is rejected because the token bucket is empty### Custom strategies

[](#custom-strategies)

Implement `StrategyInterface` to plug in your own logic.

```
use Aegis\Contract\StrategyInterface;

final class LoggingStrategy implements StrategyInterface
{
    public function __construct(private readonly LoggerInterface $logger) {}

    public function execute(callable $next): mixed
    {
        $start = microtime(true);
        try {
            $result = $next();
            $this->logger->info('OK', ['ms' => (int)((microtime(true) - $start) * 1000)]);
            return $result;
        } catch (\Throwable $e) {
            $this->logger->error($e->getMessage());
            throw $e;
        }
    }
}

$pipeline = ResiliencePipeline::builder()
    ->addStrategy(new LoggingStrategy($logger))
    ->retry(maxAttempts: 3)
    ->build();
```

Exceptions
----------

[](#exceptions)

ExceptionThrown when`RetryExhaustedException`All retry attempts failed. `getPrevious()` returns the last cause.`CircuitOpenException`A call is made while the circuit is Open.`TimeoutExceededException`Execution exceeded the configured duration.`RateLimitExceededException`The rate limit for the window has been reached.`BulkheadFullException`The maximum number of concurrent calls is already reached.`TokenBucketExhaustedException`The token bucket has no tokens available.`FallbackNotAvailableException`Both the primary call and the fallback handler failed.`HedgeExhaustedException`All hedge attempts failed. `getExceptions()` returns every cause.PHPStan integration
-------------------

[](#phpstan-integration)

Aegis ships with a PHPStan rule that catches misuse at analysis time.

**Rule: `retryOn` must contain `Throwable` subclasses**

```
// PHPStan error: "stdClass" does not implement Throwable
new RetryOptions(retryOn: [\stdClass::class]);
```

Install the extension via `phpstan/extension-installer` (automatic) or add manually:

```
# phpstan.neon
includes:
    - vendor/kissmint3395/aegis/phpstan/extension.neon
```

Development
-----------

[](#development)

```
composer install

# Tests
./vendor/bin/phpunit

# Static analysis
./vendor/bin/phpstan analyse
```

Roadmap
-------

[](#roadmap)

- Rate Limiter (Fixed Window, Sliding Window, Token Bucket)
- Bulkhead (concurrency limiting)
- Fallback strategy
- Adaptive Circuit Breaker (sliding-window failure rate)
- Cache strategy
- Hedge strategy (experimental)
- PHPStan 2.x upgrade

License
-------

[](#license)

MIT

---

日本語ドキュメント
---------

[](#日本語ドキュメント)

[↑ English](#aegis)

PHP 8.2+ 向けのレジリエンスライブラリです。**リトライ**・**サーキットブレーカー**・**タイムアウト**・**レートリミッター**・**バルクヘッド**・**フォールバック**・**キャッシュ**・**ヘッジ**を単一のコンポーザブルなパイプラインとして組み合わせられます。

[Resilience4j](https://resilience4j.readme.io/)（Java）や[.NET Polly](https://www.pollydocs.org/) に相当するものが PHP エコシステムに存在しなかったため作成しました。

```
$pipeline = ResiliencePipeline::builder()
    ->circuitBreaker('payment-api', failureThreshold: 5)
    ->retry(maxAttempts: 3, backoff: ExponentialBackoff::withJitter(Duration::milliseconds(100)))
    ->timeout(Duration::seconds(5))
    ->fallback(fn(\Throwable $e) => CachedResponse::last())
    ->build();

$result = $pipeline->execute(fn() => $httpClient->post('/charge', $payload));
```

### なぜ Aegis？

[](#なぜ-aegis)

PHP には個別のレジリエンスライブラリが存在しますが、それらをパイプラインとして合成できるものはありませんでした。

ライブラリリトライCBタイムアウトレートリミッターバルクヘッドフォールバックキャッシュ合成可能PHP 8.2+`ackintosh/ganesha`✗✅✗✗✗✗✗✗✅`yohang/finite`✗✗✗✗✗✗✗✗✅`cline/retry`✅✗✗✗✗✗✗✗✅**Aegis**✅✅✅✅✅✅✅✅✅### 要件

[](#要件)

- PHP 8.2+
- `psr/event-dispatcher` ^1.0
- `psr/simple-cache` ^3.0 *（任意。サーキットブレーカー・レートリミッター・バルクヘッドの状態を永続化する場合）*

### インストール

[](#インストール)

```
composer require kissmint3395/aegis
```

### 使い方

[](#使い方)

#### リトライ

[](#リトライ)

失敗した処理をバックオフ戦略付きで再試行します。

```
use Aegis\ResiliencePipeline;
use Aegis\Backoff\ExponentialBackoff;
use Aegis\Duration;

$pipeline = ResiliencePipeline::builder()
    ->retry(
        maxAttempts: 3,
        backoff: ExponentialBackoff::withJitter(Duration::milliseconds(100)),
        retryOn: [\RuntimeException::class],
    )
    ->build();

$result = $pipeline->execute(fn() => $api->fetch());
```

**バックオフ戦略一覧:**

```
use Aegis\Backoff\FixedBackoff;
use Aegis\Backoff\ExponentialBackoff;

// 固定遅延
new FixedBackoff(Duration::milliseconds(200))

// 指数バックオフ: 100ms → 200ms → 400ms → ...
ExponentialBackoff::create(Duration::milliseconds(100))

// ジッター付き指数バックオフ: 各ステップの 50〜100% をランダムで選択
ExponentialBackoff::withJitter(Duration::milliseconds(100), maxDelay: Duration::seconds(5))
```

**条件付きリトライ:**

```
->retry(
    retryIf: fn(\Throwable $e) => $e->getCode() >= 500,
)
```

#### サーキットブレーカー

[](#サーキットブレーカー)

サービスが不健全なときに呼び出しをブロックし、カスケード障害を防ぎます。

```
use Aegis\ResiliencePipeline;
use Aegis\Duration;

$pipeline = ResiliencePipeline::builder()
    ->circuitBreaker(
        name: 'inventory-api',
        failureThreshold: 5,
        successThreshold: 2,
        resetAfter: Duration::seconds(30),
    )
    ->build();
```

**状態遷移:**

```
Closed ──（5回失敗）──► Open ──（30秒経過）──► HalfOpen
  ▲                                                │
  └──────────（2回成功）────────────────────────────┘
                            （1回失敗）──► Open

```

**リクエスト間で状態を永続化（Redis・APCu など）:**

```
use Aegis\Strategy\CircuitBreaker\Storage\Psr16Storage;

$pipeline = ResiliencePipeline::builder()
    ->circuitBreaker('payment-api', storage: new Psr16Storage($redisCache))
    ->build();
```

#### アダプティブ サーキットブレーカー

[](#アダプティブ-サーキットブレーカー)

直近 N 件の\*\*失敗率（%）\*\*に基づいてサーキットを開く、スライディングウィンドウ型のサーキットブレーカーです。

```
$pipeline = ResiliencePipeline::builder()
    ->adaptiveCircuitBreaker(
        name: 'payment-api',
        windowSize: 20,              // 直近 20 件を評価
        failureRateThreshold: 50.0,  // 50% 以上失敗でオープン
        minimumCalls: 5,             // 5 件以上記録されるまでは評価しない
        successThreshold: 2,
        resetAfter: Duration::seconds(30),
    )
    ->build();
```

#### タイムアウト

[](#タイムアウト)

処理の最大実行時間を設定します。

```
->timeout(Duration::seconds(5))
```

> **注意:** `pcntl` 拡張が利用可能な Unix 環境では `SIGALRM` によるプリエンプティブな割り込みを行います。Windows や `pcntl` が使えない環境では、実行後に経過時間をチェックする方式にフォールバックします。

#### レートリミッター（固定ウィンドウ）

[](#レートリミッター固定ウィンドウ)

固定ウィンドウ内の呼び出し回数を制限します。

```
$pipeline = ResiliencePipeline::builder()
    ->rateLimit(
        name: 'payment-api',
        limit: 100,
        window: Duration::seconds(60),
    )
    ->build();
```

#### レートリミッター（スライディングウィンドウ）

[](#レートリミッタースライディングウィンドウ)

固定ウィンドウの「境界バースト問題」を解消します。個々のリクエストタイムスタンプを記録して正確にカウントします。

```
$pipeline = ResiliencePipeline::builder()
    ->slidingRateLimit(
        name: 'payment-api',
        limit: 100,
        window: Duration::seconds(60),
    )
    ->build();
```

#### レートリミッター（トークンバケット）

[](#レートリミッタートークンバケット)

最大 `capacity` バーストを許容しつつ、`refillRate / refillPeriod` の平均レートを連続補充で維持します。

```
$pipeline = ResiliencePipeline::builder()
    ->tokenBucketRateLimit(
        name: 'payment-api',
        capacity: 20,
        refillRate: 10,
        refillPeriod: Duration::seconds(1),
    )
    ->build();
```

#### バルクヘッド

[](#バルクヘッド)

同時実行数を制限してリソース枯渇を防ぎます。

```
$pipeline = ResiliencePipeline::builder()
    ->bulkhead(
        name: 'database',
        maxConcurrent: 10,
    )
    ->build();
```

#### フォールバック

[](#フォールバック)

全戦略が失敗したとき、代替値を返すか代替処理を実行します。

```
$pipeline = ResiliencePipeline::builder()
    ->circuitBreaker('payment-api')
    ->retry(maxAttempts: 3)
    ->fallback(fn(\Throwable $e) => ['status' => 'unavailable'])
    ->build();
```

フォールバックハンドラー自体が例外をスローした場合は `FallbackNotAvailableException` が発生します。

#### キャッシュ

[](#キャッシュ)

成功したレスポンスをキャッシュし、次回以降は呼び出しをスキップします。`staleOnFailure: true` を指定すると、ダウンストリームが失敗した際に期限切れのキャッシュを返します。

```
use Aegis\Strategy\Cache\Storage\CacheInMemoryStorage;

$pipeline = ResiliencePipeline::builder()
    ->cache(
        name: 'product-catalog',
        ttl: Duration::minutes(5),
        staleOnFailure: true,
        storage: new CacheInMemoryStorage(),
    )
    ->build();
```

#### ヘッジ（実験的）

[](#ヘッジ実験的)

一定時間内に応答がない場合、追加のリクエストを並行して投げ、最初に成功したものを採用します（テールレイテンシー対策）。

```
$pipeline = ResiliencePipeline::builder()
    ->hedge(
        delay: Duration::milliseconds(200),
        maxHedges: 1,
    )
    ->build();
```

> **注意:** 真のレイテンシーベースのヘッジには、`Fiber::suspend()` を使用した協調型非同期（ReactPHP / Swoole など）が必要です。同期的な callable の場合、ヘッジは各失敗後に即座に発火します。

#### 戦略の合成

[](#戦略の合成)

戦略は追加した順に外側から適用されます（最初に追加 = 最も外側）。 推奨順序は **Bulkhead → Rate Limiter → Timeout → Circuit Breaker → Retry → Fallback** です。

```
$pipeline = ResiliencePipeline::builder()
    ->bulkhead('svc', maxConcurrent: 10)
    ->rateLimit('svc', limit: 100)
    ->timeout(Duration::seconds(10))
    ->circuitBreaker('svc', failureThreshold: 5)
    ->retry(maxAttempts: 3)
    ->fallback(fn() => $defaultValue)
    ->build();
```

#### PSR-14 イベント

[](#psr-14-イベント)

PSR-14 のイベントディスパッチャーを接続して、パイプライン内部の動作を観測できます。

```
$pipeline = ResiliencePipeline::builder()
    ->withEventDispatcher($dispatcher)
    ->circuitBreaker('api')
    ->retry(maxAttempts: 3)
    ->build();
```

イベント発火タイミング`RetryAttempted`リトライ待機前`CircuitOpened`Closed → Open に遷移したとき`CircuitClosed`HalfOpen → Closed に遷移したとき`CircuitHalfOpened`Open → HalfOpen に遷移したとき`RateLimitExceeded`レート制限に達して呼び出しを拒否したとき`BulkheadRejected`バルクヘッドが満杯で呼び出しを拒否したとき`TokenBucketExhausted`トークンバケットが空で呼び出しを拒否したとき#### カスタム戦略

[](#カスタム戦略)

`StrategyInterface` を実装して独自の戦略を追加できます。

```
use Aegis\Contract\StrategyInterface;

final class LoggingStrategy implements StrategyInterface
{
    public function __construct(private readonly LoggerInterface $logger) {}

    public function execute(callable $next): mixed
    {
        $start = microtime(true);
        try {
            $result = $next();
            $this->logger->info('成功', ['ms' => (int)((microtime(true) - $start) * 1000)]);
            return $result;
        } catch (\Throwable $e) {
            $this->logger->error($e->getMessage());
            throw $e;
        }
    }
}

$pipeline = ResiliencePipeline::builder()
    ->addStrategy(new LoggingStrategy($logger))
    ->retry(maxAttempts: 3)
    ->build();
```

### 例外一覧

[](#例外一覧)

例外発生タイミング`RetryExhaustedException`全リトライが失敗。`getPrevious()` で最後の原因を取得可能`CircuitOpenException`サーキットがオープン状態のときに呼び出しを行った`TimeoutExceededException`設定した時間内に処理が完了しなかった`RateLimitExceededException`ウィンドウ内のレート制限に達した`BulkheadFullException`最大同時実行数に達している`TokenBucketExhaustedException`トークンバケットにトークンがない`FallbackNotAvailableException`本処理とフォールバックの両方が失敗した`HedgeExhaustedException`全ヘッジ試行が失敗。`getExceptions()` で全原因を取得可能### PHPStan 連携

[](#phpstan-連携)

Aegis には静的解析時に誤用を検出する PHPStan ルールが同梱されています。

```
// PHPStan エラー: "stdClass" は Throwable を実装していない
new RetryOptions(retryOn: [\stdClass::class]);
```

`phpstan/extension-installer` 経由で自動的に有効化されます。手動で追加する場合:

```
# phpstan.neon
includes:
    - vendor/kissmint3395/aegis/phpstan/extension.neon
```

### 開発

[](#開発)

```
composer install

# テスト実行
./vendor/bin/phpunit

# 静的解析
./vendor/bin/phpstan analyse
```

### ロードマップ

[](#ロードマップ)

- レートリミッター（固定ウィンドウ・スライディングウィンドウ・トークンバケット）
- バルクヘッド（同時実行数制限）
- フォールバック戦略
- アダプティブ サーキットブレーカー（失敗率ベース）
- キャッシュ戦略
- ヘッジ戦略（実験的）
- PHPStan 2.x 対応

###  Health Score

33

—

LowBetter than 72% of packages

Maintenance84

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity36

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

86d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/955392?v=4)[kissmint3395](/maintainers/kissmint3395)[@kissmint3395](https://github.com/kissmint3395)

---

Top Contributors

[![kissmint3395](https://avatars.githubusercontent.com/u/955392?v=4)](https://github.com/kissmint3395 "kissmint3395 (9 commits)")

---

Tags

timeoutretrycircuit breakerfault toleranceresilience

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/kissmint3395-aegis/health.svg)

```
[![Health](https://phpackages.com/badges/kissmint3395-aegis/health.svg)](https://phpackages.com/packages/kissmint3395-aegis)
```

###  Alternatives

[illuminate/contracts

The Illuminate Contracts package.

706130.3M14.1k](/packages/illuminate-contracts)[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.6M600](/packages/shopware-core)[civicrm/civicrm-core

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

751291.4k46](/packages/civicrm-civicrm-core)[flow-php/etl

PHP ETL - Extract Transform Load - Abstraction

378604.0k107](/packages/flow-php-etl)[shopware/app-php-sdk

Shopware App SDK for PHP

15109.8k3](/packages/shopware-app-php-sdk)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
