Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Circuit Breaker Laravel Package

ejsmont-artur/php-circuit-breaker

Laravel-friendly PHP circuit breaker implementation to add resiliency to external service calls. Supports configurable failure thresholds, timeouts and recovery, helping prevent cascading failures when APIs or dependencies are down or slow.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require ejsmont-artur/php-circuit-breaker
    

    Add to composer.json if using a custom namespace:

    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "CircuitBreaker\\": "vendor/ejsmont-artur/php-circuit-breaker/src/"
        }
    }
    

    Run composer dump-autoload.

  2. Basic Usage

    use CircuitBreaker\CircuitBreaker;
    use CircuitBreaker\CircuitBreakerFactory;
    
    $factory = new CircuitBreakerFactory();
    $breaker = $factory->createCircuitBreaker(
        'myService',
        3, // maxFailures
        1000, // resetTimeoutMs
        1000 // failureThresholdDurationMs
    );
    
    $result = $breaker->execute(function() {
        return $this->callExternalService();
    });
    
  3. First Use Case: Wrapping External API Calls

    $breaker = $factory->createCircuitBreaker('apiService', 3, 5000, 2000);
    $response = $breaker->execute(function() {
        return Http::get('https://external-api.com/data');
    });
    

Implementation Patterns

Common Workflows

  1. Dependency Injection (Laravel)

    // In a service provider
    $this->app->singleton('circuitBreaker', function ($app) {
        $factory = new CircuitBreakerFactory();
        return $factory->createCircuitBreaker(
            'database',
            5,
            10000,
            3000
        );
    });
    
    // In a controller/service
    $breaker = app('circuitBreaker');
    
  2. Dynamic Circuit Breaker Configuration Use a config file (config/circuit-breakers.php) to define breakers:

    return [
        'services' => [
            'api' => [
                'max_failures' => 3,
                'reset_timeout_ms' => 10000,
                'failure_threshold_duration_ms' => 5000,
            ],
            'database' => [
                'max_failures' => 5,
                'reset_timeout_ms' => 15000,
                'failure_threshold_duration_ms' => 8000,
            ],
        ],
    ];
    

    Load dynamically:

    $config = config('circuit-breakers.services');
    $factory = new CircuitBreakerFactory();
    $breaker = $factory->createCircuitBreaker(
        'api',
        $config['api']['max_failures'],
        $config['api']['reset_timeout_ms'],
        $config['api']['failure_threshold_duration_ms']
    );
    
  3. Logging Failures Extend the CircuitBreaker class to log failures:

    use CircuitBreaker\CircuitBreaker;
    use Psr\Log\LoggerInterface;
    
    class LoggingCircuitBreaker extends CircuitBreaker
    {
        protected $logger;
    
        public function __construct(
            string $name,
            int $maxFailures,
            int $resetTimeoutMs,
            int $failureThresholdDurationMs,
            LoggerInterface $logger
        ) {
            parent::__construct($name, $maxFailures, $resetTimeoutMs, $failureThresholdDurationMs);
            $this->logger = $logger;
        }
    
        protected function onFailure()
        {
            $this->logger->warning("Circuit breaker '{$this->name}' failed");
        }
    }
    
  4. Retry Logic with Exponential Backoff Combine with a retry library (e.g., php-retry) for sophisticated retries:

    use Retry\Retry;
    
    $retry = Retry::withMaxAttempts(3)
        ->withDelay(100)
        ->withMultiplier(2)
        ->attempt(function () use ($breaker) {
            return $breaker->execute(function () {
                return $this->callExternalService();
            });
        });
    

Integration Tips

  1. Middleware for API Routes Create middleware to wrap API calls:

    namespace App\Http\Middleware;
    
    use Closure;
    use CircuitBreaker\CircuitBreakerFactory;
    
    class CircuitBreakerMiddleware
    {
        protected $factory;
    
        public function __construct(CircuitBreakerFactory $factory)
        {
            $this->factory = $factory;
        }
    
        public function handle($request, Closure $next)
        {
            $breaker = $this->factory->createCircuitBreaker(
                'api',
                config('circuit-breakers.api.max_failures'),
                config('circuit-breakers.api.reset_timeout_ms'),
                config('circuit-breakers.api.failure_threshold_duration_ms')
            );
    
            return $breaker->execute(function () use ($next) {
                return $next($request);
            });
        }
    }
    

    Register in app/Http/Kernel.php:

    protected $middleware = [
        \App\Http\Middleware\CircuitBreakerMiddleware::class,
    ];
    
  2. Queue Workers Use circuit breakers in queue jobs to prevent cascading failures:

    use CircuitBreaker\CircuitBreakerFactory;
    use Illuminate\Bus\Queueable;
    use Illuminate\Queue\SerializesModels;
    use Illuminate\Queue\InteractsWithQueue;
    
    class ProcessPayment implements ShouldQueue
    {
        use Queueable, SerializesModels, InteractsWithQueue;
    
        protected $breaker;
    
        public function __construct(CircuitBreakerFactory $factory)
        {
            $this->breaker = $factory->createCircuitBreaker(
                'paymentService',
                3,
                10000,
                5000
            );
        }
    
        public function handle()
        {
            $this->breaker->execute(function () {
                // Process payment logic
            });
        }
    }
    
  3. Testing Mock the circuit breaker in tests:

    $mockBreaker = Mockery::mock('CircuitBreaker\CircuitBreaker');
    $mockBreaker->shouldReceive('execute')
        ->once()
        ->andReturn('mocked_result');
    
    $this->app->instance('circuitBreaker', $mockBreaker);
    

Gotchas and Tips

Pitfalls

  1. State Management

    • Circuit breakers are not stateless by default. If using multiple instances (e.g., in a load-balanced environment), ensure state is shared (e.g., via Redis).
    • Fix: Use a shared storage backend:
      $factory = new CircuitBreakerFactory();
      $breaker = $factory->createCircuitBreakerWithStorage(
          'redis',
          'myService',
          3,
          10000,
          5000,
          new Redis()
      );
      
  2. Timeout Handling

    • The resetTimeoutMs is not a delay before tripping. It’s the time the breaker stays open after tripping.
    • Tip: Set failureThresholdDurationMs to control how quickly the breaker trips after failures.
  3. Exception Handling

    • The breaker only catches exceptions thrown by the closure. Ensure your closure propagates all relevant exceptions.
    • Example:
      $breaker->execute(function () {
          try {
              return $this->callExternalService();
          } catch (\Exception $e) {
              // Ensure exceptions bubble up
              throw $e;
          }
      });
      
  4. Thread Safety

    • The package is not thread-safe. Avoid using the same breaker instance across multiple threads/processes.
    • Fix: Create a new breaker instance per request/thread.
  5. Configuration Overrides

    • Hardcoded values in the breaker constructor cannot be changed dynamically. Recreate the breaker if thresholds need adjustment.
    • Tip: Use a factory method or dependency injection to manage dynamic changes.

Debugging Tips

  1. Check Breaker State Add a helper method to inspect the breaker:

    public function getStatus(): string
    {
        return $this->isOpen() ? 'OPEN' : 'CLOSED';
    }
    

    Log status during debugging:

    \Log::debug("Circuit breaker status: {$breaker->getStatus()}");
    
  2. Simulate Failures Force a failure to test the breaker:

    $breaker->forceFailure(); // Simulate a failure
    $result = $breaker->execute(function () {
        return "This will fail";
    });
    // $result should be null (or a fallback value)
    
  3. Monitor Reset Time Use isOpen() and getTimeToReset() to debug timing:

    if ($breaker->isOpen()) {
        $resetTime = $breaker->getTimeToReset();
        \Log::info("Breaker will reset in {$resetTime}ms");
    }
    

Extension Points

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
daikazu/eloquent-salesforce-objects
unseen-codes/chat
romalytar/yammi-jobs-monitoring-laravel
kisame76/filament-db-table-state
nqxcode/laravel-lucene-search
dpfx/laravel-livewire-wizards
workos/workos-php-laravel
sofa/laravel-global-scope
nawasara/auth-primitives
adhocrat-io/arkhe-main
make-dev/orca-harpoon
itsemon245/lamet
baks-dev/dashboard
amoifr/pickle-panther-bundle
make-dev/orca
dmstr/symfony-system-resources-bundle
dmstr/symfony-job-queue-bundle
dmstr/openapi-json-schema-bundle
dmstr/keycloak-security-bundle
dmstr/doctrine-audit-log-bundle