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

Circuit Breaker Bundle Laravel Package

ksaveras/circuit-breaker-bundle

Symfony bundle implementing the circuit breaker pattern to stop cascading failures. It tracks failing service calls and opens the circuit once a threshold is hit, failing fast until recovery is detected.

View on GitHub
Deep Wiki
Context7

Getting Started

This package (ksaveras/circuit-breaker-bundle) is a Symfony bundle wrapper around a PHP circuit breaker implementation, not a Laravel package. v1.0.0 is its first stable release. For a Laravel codebase you have two realistic options:

  1. Pull in the underlying circuit breaker library directly and wire it yourself.
  2. Install the bundle and ignore the Symfony DI layer — use only the breaker classes.

Either way, budget for a small service provider; there is no extra.laravel auto-discovery.

Requirements: PHP 8.1+ (this is the breaking change in v1.0.0), and the breaker's own dependency was bumped, so pin ^1.0 and re-read the underlying library's changelog before upgrading an existing install.

Where to look first, in order:

  1. composer.json — confirm the exact underlying breaker package and version constraint.
  2. The bundle's DependencyInjection/ extension class — this is the closest thing to documentation for available configuration options and default thresholds.
  3. The test suite — with PHPUnit 10.2 now in use, the tests are the most current record of how the breaker is meant to be constructed and driven.
  4. The underlying library's README for state-machine semantics (closed / open / half-open).

First use case: guard exactly one flaky, high-latency outbound dependency — a payment gateway, SMS provider, or internal service — so that when it degrades you fail fast instead of stacking up 30-second timeouts across every request.

Minimal bootstrap in Laravel:

// app/Providers/CircuitBreakerServiceProvider.php
public function register(): void
{
    $this->app->singleton(PaymentGatewayBreaker::class, fn () => new PaymentGatewayBreaker(
        failureThreshold: 5,
        recoveryTimeout: 30,
        storage: $this->app->make(CacheStorage::class),
    ));
}

Before writing any of this, run a throwaway smoke test that trips the breaker deliberately and confirms it opens and later half-opens. You want to know the failure counts and timing behaviour of this version before it is load-bearing.

Implementation Patterns

Wrap at a choke point, not at call sites. The single most important pattern: put the breaker inside the gateway/client class that owns the outbound call, so every caller is protected and no caller has to remember to wrap.

final class PaymentGateway
{
    public function __construct(
        private HttpClient $http,
        private CircuitBreaker $breaker,
        private LoggerInterface $log,
    ) {}

    public function charge(Charge $charge): Result
    {
        return $this->breaker->call(function () use ($charge) {
            return $this->http->post('/charges', $charge->toArray());
        });
    }
}

Consumers then depend on PaymentGateway and get the breaker for free.

One named breaker per downstream dependency. Never share a single breaker across unrelated services — one flaky SMS provider should not open the circuit on your payment gateway. Either register one singleton per dependency or key the storage by name.

Choose storage deliberately. In-memory state dies at the end of the request. Under PHP-FPM that makes a purely in-memory breaker close to decorative, because each request starts from zero failures. Use a shared store (Redis is the natural fit in Laravel) so state survives across requests and across horizontal pods. Under Octane or in queue workers, in-memory state persists across jobs and can silently accumulate — so shared storage plus an explicit reset policy is the safer default both ways.

Design the fallback before you need it. An open circuit throws. Decide per dependency what happens next:

  • Return a cached/stale value (read paths).
  • Queue the work and reconcile asynchronously (writes that can be deferred).
  • Surface a degraded-but-honest response to the user (payments, auth).

Do not let the breaker exception bubble into a generic 500 — that is worse than the timeout it replaced.

Treat the breaker as an observability feature, not just a resilience one. Log state transitions (closed → open, open → half-open) with the dependency name, and emit a counter you can alert on. A breaker that opens silently is a breaker nobody trusts, and ops will ask you to remove it.

Testing pattern (PHPUnit 10.2): use attributes, not annotations — the docblock @test / @dataProvider style is deprecated in this version.

#[Test]
public function it_opens_after_threshold_and_serves_cached_value(): void
{
    $breaker = $this->breakerWith(failureThreshold: 2);
    // drive two failures, assert third call short-circuits
}

Assert the behaviour at the seam (caller gets fallback, upstream is not hit) rather than poking at internal state. If you do inspect state, inject a fake clock rather than sleeping.

Queue jobs: wrap the job's outbound work in the breaker, but be aware a failed() handler will not fire for a circuit-open rejection — that is a different failure class from a real job failure. Decide whether an open circuit should release the job back onto the queue with a delay or fail it permanently.

Gotchas and Tips

This is a Symfony bundle. Expect a Bundle class, a DI Extension, and YAML configuration. Laravel's container will not resolve any of it. If you install the bundle, treat it as a thin dependency and register the breaker yourself; if you skip the bundle, you lose nothing except the pre-built service definitions.

PHP 8.1 minimum is a real upgrade gate. Check your CI matrix, composer.json require.php, and any Docker base image still on 8.0. Also re-check indirect dependencies that cap PHP versions — this is the most common cause of a failed upgrade on this kind of release.

The breaker bump is the risky line item. "Bump circuit breaker" is listed as a breaking change, which means threshold defaults, state transition rules, or the exception type may have changed. Existing tuning values are not guaranteed to mean the same thing. Re-run your trip/recover tests against v1.0.0 explicitly rather than trusting a green build.

A breaker is not a timeout. It counts failures; it does not prevent a hung request from blocking for 30 seconds. You must set connect and read timeouts on the underlying HTTP client, or the first few failures will each cost you the full timeout before the circuit opens.

Only count dependency failures. 4xx responses, validation errors, and business-logic rejections should not trip the breaker — if they do, a spike in bad user input will take down a healthy integration. Conversely, if your HTTP client throws on 4xx by default, you will need to unwrap those before they reach the breaker.

Half-open needs to be a single probe. If dozens of requests are allowed through the moment the recovery timeout expires, you recreate the outage you were protecting against. Verify how this version arbitrates concurrent half-open traffic.

Give ops a manual override. An artisan command or config kill switch to force-open or force-reset a named breaker saves you at 2am:

// php artisan breaker:reset payment-gateway

Testing traps: wall-clock-dependent recovery timeouts make suites slow and flaky. Inject a clock (or a very short recovery timeout in test config) and never sleep() through a half-open window.

CI change: Travis config was removed in this release. If you were mirroring its pipeline, assume GitHub Actions and verify the test matrix still covers your PHP version.

Keep the breaker off the happy path's critical budget. It is a few microseconds, but wrapping every internal call in a breaker "just in case" adds noise, log volume, and false opens. Reserve it for genuinely remote, genuinely unreliable dependencies.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin