syastrebov/circuit-breaker

Circuit breaker wrapper for microservices and api calls.

Installs: 96

Dependents: 1

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

pkg:composer/syastrebov/circuit-breaker

v0.0.2 2026-02-01 16:44 UTC

This package is not auto-updated.

Last update: 2026-02-01 16:50:13 UTC


README

PHP implementation of circuit breaker wrapper for microservices and api calls.

Install

composer require syastrebov/circuit-breaker

Usage

Simple usage:

use CircuitBreaker\CircuitBreaker;
use CircuitBreaker\Providers\RedisProvider;

$redis = new \Redis();
$redis->connect('redis');

$circuit = new CircuitBreaker(new RedisProvider($redis));
$response = $circuit->run(
    $name,
    function () {
        // call your api
        return '{"response": "data"}';
    }
);

// {"response": "data"}
echo $response;

Use fallback:

use CircuitBreaker\CircuitBreaker;
use CircuitBreaker\Providers\RedisProvider;

$redis = new \Redis();
$redis->connect('redis');

$circuit = new CircuitBreaker(new RedisProvider($redis));
$response = $circuit->run(
    $name,
    // action
    function () {
        throw new \RuntimeException('unable to fetch data');
    },
    // fallback
    function () {
        // call your api
        return '{"response": "cached data"}';
    }
);

// {"response": "cached data"}
echo $response;

Use exception:

use CircuitBreaker\CircuitBreaker;
use CircuitBreaker\Providers\RedisProvider;
use CircuitBreaker\Exceptions;

$redis = new \Redis();
$redis->connect('redis');

$circuit = new CircuitBreaker(new RedisProvider($redis));

try {
    $response = $circuit->run(
        $name,
        // action
        function () {
            throw new \RuntimeException('unable to fetch data');
        }
    );
} catch (UnableToProcessException $e) {
    // handle exception
}

Config

use CircuitBreaker\CircuitBreaker;
use CircuitBreaker\Providers\RedisProvider;
use CircuitBreaker\CircuitBreakerConfig;

$redis = new \Redis();
$redis->connect('redis');

$circuit = new CircuitBreaker(new RedisProvider($redis), new CircuitBreakerConfig(
    // Number of attempts within run() action
    retries: 5,
    // Number of failed attempts to change state to 'OPEN'
    closedThreshold: 2,
    // Number of succeed attempts to change state to 'CLOSED'
    halfOpenThreshold: 2,
    // Delay between retries within run() action in microseconds
    retryInterval: 1000,
    // TTL of OPEN state in seconds
    openTimeout: 60,
    // If true and no fallback defined returns NULL otherwise throws UnableToProcessException 
    fallbackOrNull: true
));

Supported Drivers

Redis:

use CircuitBreaker\CircuitBreaker;
use CircuitBreaker\Providers\RedisProvider;

$redis = new \Redis();
$redis->connect('redis');

$circuit = new CircuitBreaker(new RedisProvider($redis));

Redis cluster:

use CircuitBreaker\CircuitBreaker;
use CircuitBreaker\Providers\RedisProvider;

$redis = new \RedisCluster(
        'my cluster',
        [
            'redis-node-1:6379',
            'redis-node-2:6379',
            'redis-node-3:6379',
        ],
        1.5,
        1.5,
        true
    )
);

$circuit = new CircuitBreaker(new RedisProvider($redis));

Memcached:

use CircuitBreaker\CircuitBreaker;
use CircuitBreaker\Providers\MemcachedProvider;

$memcached = new \Memcached();
$memcached->addServer('memcached', 11211);

$circuit = new CircuitBreaker(new MemcachedProvider($memcached));

MySQL:

use CircuitBreaker\CircuitBreaker;
use CircuitBreaker\Providers\DatabaseProvider;

$table = 'circuit_breaker';

$pdo = new \PDO("mysql:host=mysql;dbname=database", 'user', 'password');
$pdo->prepare("
    CREATE TABLE IF NOT EXISTS $table (
        name VARCHAR(255) NOT NULL UNIQUE,
        state ENUM('closed', 'open', 'half_open'),
        state_timestamp INT,
        half_open_attempts INT,
        failed_attempts INT
    );
")->execute();

$provider = new DatabaseProvider($pdo, $table);

PostgreSQL:

use CircuitBreaker\CircuitBreaker;
use CircuitBreaker\Providers\DatabaseProvider;

$table = 'circuit_breaker';

$pdo = new \PDO("pgsql:host=postgres;dbname=database", 'user', 'password');
$pdo->prepare("
    DO $$ 
    BEGIN 
        IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'state_enum') THEN 
            CREATE TYPE state_enum AS ENUM ('closed', 'open', 'half_open'); 
        END IF; 
    END $$;
")->execute();
$pdo->prepare("    
    CREATE TABLE IF NOT EXISTS $table (
        name VARCHAR(255) UNIQUE NOT NULL,
        state state_enum NULL,
        state_timestamp INT,
        half_open_attempts INT,
        failed_attempts INT
    );
")->execute();

$provider = new DatabaseProvider($pdo, $table);

SQLite:

use CircuitBreaker\CircuitBreaker;
use CircuitBreaker\Providers\DatabaseProvider;

$table = 'circuit_breaker';
$databaseFile = __DIR__ . '/database.sqlite';

$pdo = new \PDO("sqlite:$databaseFile");
$pdo->prepare("    
    CREATE TABLE IF NOT EXISTS $table (
        name VARCHAR(255) UNIQUE,
        state TEXT CHECK(state IN ('open', 'half_open', 'closed')),
        state_timestamp INTEGER,
        half_open_attempts INTEGER,
        failed_attempts INTEGER
    );
")->execute();

$provider = new DatabaseProvider($pdo, $table);

Run tests

docker compose up -d
docker exec -t circuit-breaker-php vendor/bin/phpunit