Weave 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

Tool Circuitbreaker Bundle Laravel Package

sfynx-project/tool-circuitbreaker-bundle

Symfony bundle implementing the circuit breaker pattern to protect PHP applications from cascading failures. It monitors unreliable service calls, opens the circuit after repeated errors to fail fast, and supports controlled recovery when the service is healthy again.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: despite the description, this is a Symfony bundle, not a Laravel package. There is no CircuitBreakerServiceProvider, no facade, and no config/circuitbreaker.php. It targets Symfony 2/3-era DI and was last released in January 2017. Before writing any code, decide which of these two paths you're on:

  1. Adapt the handlers — pull the circuit breaker handler classes/interfaces out of the bundle (or vendor them into app/Support/CircuitBreaker) and wire them yourself. This is the realistic path for Laravel.
  2. Skip the package, keep the pattern — use a maintained library or a ~100-line hand-rolled breaker with a Redis-backed state store. For most teams this is cheaper than resurrecting a 2017 Symfony bundle.

If you do proceed with adaptation:

composer require sfynx-project/tool-circuitbreaker-bundle

Then check composer.json and the DependencyInjection/ directory in vendor/sfynx-project/tool-circuitbreaker-bundle to see the actual handler class names and constructor signatures — the README is thin, so the DI extension is your real documentation. Note the bundle will try to register itself via Symfony's Bundle class; you will not use that class at all in Laravel.

First use case: wrap a single flaky outbound dependency — a payment gateway, a shipping-rate API, an SMS provider — behind a breaker, with a cached/fallback response when the breaker is open.

// app/Providers/CircuitBreakerServiceProvider.php
public function register(): void
{
    $this->app->singleton(PaymentGatewayBreaker::class, function ($app) {
        return new PaymentGatewayBreaker(
            store: $app->make(BreakerStore::class),   // Redis-backed
            threshold: 5,
            resetTimeout: 60,                        // seconds
        );
    });
}

Then in a job or service:

if (! $breaker->isAvailable()) {
    return $this->cachedRates(); // fallback path
}

try {
    $result = $client->rates($payload);
    $breaker->success();
    return $result;
} catch (ConnectionException $e) {
    $breaker->failure();
    throw $e;
}

Start with one dependency and one breaker. Resist wrapping every HTTP call on day one.

Implementation Patterns

Wrap at the boundary, not in controllers. Put the breaker inside a dedicated client/gateway class (App\Services\Payments\PaymentGatewayClient) so controllers and jobs never see breaker state. This makes it trivial to swap in a fake in tests.

Guzzle / Laravel HTTP middleware. The cleanest day-to-day integration is a middleware that calls success() on 2xx–4xx and failure() on 5xx/timeouts:

Http::withMiddleware(function (callable $handler) use ($breaker) {
    return function ($request, $options) use ($handler, $breaker) {
        if (! $breaker->isAvailable()) {
            throw new BreakerOpenException();
        }
        return $handler($request, $options)->then(
            function ($response) use ($breaker) { $breaker->success(); return $response; },
            function ($reason) use ($breaker) { $breaker->failure(); return rejection($reason); }
        );
    };
})->get('https://rates.example.com/...');

Key your state per dependency, and store it centrally. PHP-FPM gives you a fresh process per request, so in-memory counters are useless. Use Redis or the database with a key like breaker:{service}:{environment}. Do not reuse one breaker across unrelated services — a shipping outage should not open the breaker for payments.

Combine with retries and timeouts, in this order: timeout → retry (bounded, with jitter) → breaker → fallback. A breaker whose wrapped call can hang for 30s is worse than no breaker.

Expose state for ops. Add a /health/dependencies endpoint returning each breaker's state and counters, and a console command:

// php artisan breaker:reset payments

Fallbacks should be explicit types. Return a CachedRates value object or a RatesUnavailable result rather than returning null — otherwise every caller needs null checks and the open state leaks everywhere.

Test by forcing state. Give your breaker class forceOpen() / forceClosed() methods (or inject a fake that implements the same interface) so feature tests don't depend on real failure counts.

Gotchas and Tips

  • Abandoned. Last release January 2017; expect Symfony 2/3 DI idioms, missing PHP 8 return types, and each()/create_function-era assumptions. Run composer why-not php 8.2 and expect friction. Treat any code you reuse as a fork you now own.
  • License is NOASSERTION. That is not an open-source license grant you can rely on. Get legal sign-off before shipping it in a commercial product; the pattern itself is public knowledge and carries no such risk.
  • Symfony bundle registration will fire. If you composer require it into a Laravel app, nothing auto-registers it (good), but if you're in a mixed/legacy setup, the bundle's DI extension may attempt container compilation you don't want.
  • Two stars, zero dependents. No upstream fixes, no community answers, no CVEs tracked. Budget for maintenance as if you wrote it.
  • State not shared = breaker that never opens. The classic bug: counters in request-scoped memory, so every request starts at zero failures. Verify from two separate processes that a forced failure increments the same key.
  • Half-open concurrency. When the reset timeout elapses, only one request should probe the dependency. Without a lock, a traffic spike sends hundreds of probes at a recovering service and re-opens the breaker immediately.
  • Threshold units are ambiguous in old bundles — check whether the reset timeout is seconds or milliseconds, and whether threshold counts consecutive failures or total failures in a window. Misreading this is the most common source of "why is it always open?" tickets.
  • Don't breaker everything. Local DB calls, cache reads, and same-VPC services have different failure modes; a breaker there mostly adds latency. Reserve it for cross-network, third-party, or known-fragile dependencies.
  • Log every state transition at warning with the service name and the triggering exception. Without this, an open breaker looks identical to a logic bug in production.
  • Fallback caching needs a TTL. A permanent cached fallback hides an outage indefinitely — cap it and alert when the breaker has been open longer than the TTL.
  • If you're on Laravel 10+/PHP 8.2, seriously evaluate maintained alternatives before adopting this package: a Symfony HttpClient-based breaker, a Guzzle middleware like webignition/http-retry paired with a small stateful breaker, or a hand-rolled class in app/Support. You can implement the whole pattern in ~120 lines plus Redis, and you keep the upgrade path.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager