Skip to content

hugronaphor/circuit-breaker

Repository files navigation

Circuit Breaker for PHP

A storage-agnostic Circuit Breaker implementation for PHP with optional Symfony integration.

Protect your application from cascading failures when external services (APIs, payment gateways, email services) go down.

Installation

This package is not yet published on Packagist. Add the repository to your composer.json:

{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/hugronaphor/circuit-breaker"
        }
    ]
}

Then require the package:

composer require hugronaphor/circuit-breaker:dev-main

Optional Dependencies

# For Doctrine storage (database persistence)
composer require doctrine/orm

# For DynamoDB storage (AWS Lambda)
composer require async-aws/dynamo-db

Quick Start

use Hugronaphor\CircuitBreaker\CircuitBreaker;
use Hugronaphor\CircuitBreaker\Storage\InMemoryStorage;

$storage = new InMemoryStorage();
$circuitBreaker = new CircuitBreaker(
    storage: $storage,
    serviceName: 'payment_api',
    failureThreshold: 3,        // Open circuit after 3 failures
    recoveryTimeoutSeconds: 60, // Try again after 60 seconds
);

$result = $circuitBreaker->call(
    operation: fn() => $this->paymentGateway->charge($amount),
    fallback: fn() => $this->queuePaymentForRetry($amount),
);

Using the Factory

use Hugronaphor\CircuitBreaker\CircuitBreakerFactory;
use Hugronaphor\CircuitBreaker\Storage\InMemoryStorage;

$factory = new CircuitBreakerFactory(
    storage: new InMemoryStorage(),
    defaultFailureThreshold: 3,
    defaultRecoveryTimeoutSeconds: 60,
);

$paymentCircuit = $factory->create('payment_api');
$emailCircuit = $factory->create('email_service', failureThreshold: 5);

How It Works

     ┌──────────┐      Failures reach       ┌──────────┐
     │  CLOSED  │ ─────── threshold ──────► │   OPEN   │
     │ (normal) │                           │ (reject) │
     └──────────┘                           └──────────┘
          ▲                                      │
          │                               Recovery timeout
     Test succeeds                            expires
          │                                      ▼
          └─────────────────────────────── ┌──────────┐
                       Test fails ────────►│HALF-OPEN │
                       (back to OPEN)      │  (test)  │
                                           └──────────┘
State Behavior
CLOSED Normal operation. Requests pass through. Failures are counted.
OPEN Circuit is tripped. All requests immediately use fallback.
HALF-OPEN After recovery timeout, one test request is allowed through.

Symfony Integration

1. Register the Bundle

// config/bundles.php
return [
    Hugronaphor\CircuitBreaker\Symfony\HugronaphorCircuitBreakerBundle::class => ['all' => true],
];

2. Configure

# config/packages/hugronaphor_circuit_breaker.yaml
hugronaphor_circuit_breaker:
    storage: doctrine  # or 'in_memory', 'dynamodb', or custom service ID
    default_failure_threshold: 3
    default_recovery_timeout: 60

3. Doctrine Mapping (if using Doctrine storage)

# config/packages/doctrine.yaml
doctrine:
    orm:
        mappings:
            HugronaphorCircuitBreaker:
                type: attribute
                dir: '%kernel.project_dir%/vendor/hugronaphor/circuit-breaker/src/Bridge/Doctrine/Entity'
                prefix: 'Hugronaphor\CircuitBreaker\Bridge\Doctrine\Entity'
                alias: HugronaphorCircuitBreaker
                is_bundle: false

Then run migrations:

php bin/console make:migration
php bin/console doctrine:migrations:migrate

4. Use in Services

use Hugronaphor\CircuitBreaker\CircuitBreakerFactory;

class ExchangeRateService
{
    public function __construct(
        private readonly CircuitBreakerFactory $circuitBreakerFactory,
    ) {}

    public function getRate(string $from, string $to): float
    {
        return $this->circuitBreakerFactory->create('exchange_rate_api')
            ->call(
                operation: fn() => $this->apiClient->fetchRate($from, $to),
                fallback: fn() => $this->getCachedRate($from, $to),
            );
    }
}

Storage Backends

Backend Use Case Config
InMemoryStorage Testing, single-process apps storage: in_memory
DoctrineCircuitBreakerStorage Multi-process apps, traditional hosting storage: doctrine
DynamoDbCircuitBreakerStorage AWS Lambda, serverless storage: dynamodb

Custom storage can be implemented via CircuitBreakerStorageInterface.

Troubleshooting

Circuit Opens Too Quickly

Increase failureThreshold:

$factory->create('flaky_api', failureThreshold: 10);

Circuit Never Recovers

Decrease recoveryTimeoutSeconds or manually reset:

$circuitBreaker->reset();

State Not Shared Between Lambda Invocations

Switch from InMemoryStorage to DynamoDbStorage or DoctrineStorage.

The Doctrine of Migration Doesn't See Entity

Ensure the mapping is added to doctrine.yaml. For multiple entity managers, place it under doctrine.orm.entity_managers.<manager_name>.mappings.

Documentation

For detailed documentation including:

  • Full API reference
  • All storage backend configurations
  • Real-world examples
  • Best practices
  • Monitoring & observability

See docs/llms.txt.

License

MIT

About

Storage-agnostic Circuit Breaker implementation for PHP with Symfony integration

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages