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 Laravel Package

eljam/circuit-breaker

eljam/circuit-breaker adds the circuit breaker pattern to Laravel: it tracks failures from flaky services, opens the circuit to fail fast instead of cascading, and closes it again once the service recovers. Configurable thresholds, fallbacks, and custom handlers prevent downtime from spreading.

Deep Wiki
Context7

Getting Started

Install with Composer:

composer require eljam/circuit-breaker

The package has no framework coupling. Its only hard dependency of note is symfony/event-dispatcher (used to emit lifecycle events), and it ships a MemoryStorage so you can be running in a couple of minutes.

Minimal wiring:

use Eljam\CircuitBreaker\CircuitBreaker;
use Eljam\CircuitBreaker\Storage\MemoryStorage;
use Symfony\Component\EventDispatcher\EventDispatcher;

$breaker = new CircuitBreaker(new EventDispatcher(), new MemoryStorage());

$circuit = $breaker->getCircuit('payments-api'); // name, and optionally threshold/timeout

if ($circuit->isAvailable()) {
    try {
        $response = $client->charge($payload);
        $circuit->success();
        return $response;
    } catch (\Throwable $e) {
        $circuit->failure();
        throw $e;
    }
}

throw new \RuntimeException('payments-api circuit is open');

Where to look first, in this order:

  1. Eljam\CircuitBreaker\Circuit — the state machine (closed / open / half-open), the failure counter, threshold and timeout.
  2. Eljam\CircuitBreaker\Storage\StorageInterface — this is the piece you will almost certainly replace to make the breaker useful in production.
  3. Eljam\CircuitBreaker\CircuitEvents — the event names (FAILURE, SUCCESS, OPEN, CLOSE, HALF_OPEN) you'll subscribe to for logging and metrics.

First realistic use case: put the breaker around one flaky outbound dependency — a payments gateway, a geocoding API, a legacy SOAP service — where repeated timeouts are dragging down your request latency. Ship it with default threshold/timeout and an event listener that logs transitions. Don't wire it across the whole app on day one.

Implementation Patterns

The guard / success / failure triad. Every call site is structurally identical: check isAvailable(), do the work, report success() or failure(). Wrap it once so it isn't copy-pasted:

function guarded(Circuit $circuit, callable $fn, ?callable $fallback = null) {
    if (!$circuit->isAvailable()) {
        return $fallback ? $fallback() : throw new CircuitOpenException($circuit->getName());
    }
    try {
        $result = $fn();
        $circuit->success();
        return $result;
    } catch (\Throwable $e) {
        $circuit->failure();
        throw $e;
    }
}

Decorate the client, not every caller. For Guzzle or your own HTTP wrapper, a middleware or a thin decorator class is the cleanest integration: the circuit name is derived from the host or a config constant, and every request through that client is automatically guarded. Do the same for a repository or an internal service client. Callers stay unaware the breaker exists.

Name circuits per dependency, not per endpoint. payments-api, geo-api, legacy-crm. A circuit per endpoint fragments the failure signal and you'll never reach the threshold; a circuit per host gives you the "this dependency is down" signal you actually want. If a single endpoint is dramatically more fragile, give it a child circuit like payments-api.refund.

Persistent storage is the whole point. MemoryStorage resets on every request, so under PHP-FPM it can never meaningfully open. Before shipping, implement StorageInterface on top of Redis, APCu, or your DB, keyed by circuit name, storing state, failure count, and last failure timestamp. A per-request breaker is theater.

Subscribe to events for observability. Listen on CircuitEvents::FAILURE, OPEN, CLOSE, HALF_OPEN. Log the transition once (not per request), increment a metric, and alert on OPEN for critical dependencies. Most of the day-to-day value of this package shows up here — the breaker doubles as a dependency-health signal for free.

Half-open is your probe. Once the timeout elapses the circuit goes half-open and the next call is the probe. Your guarded helper must still call failure() on a failed probe, or you'll wedge the circuit permanently in half-open. With N PHP workers waking up at once you may get N probes; if that matters, use a lock or a "first caller wins" flag in your storage.

Fallbacks live at the call site. Cached/stale data for reads, a queued job for writes, a friendly empty state for UI. The breaker tells you when; the call site knows what.

Tune two knobs per circuit. Threshold (consecutive failures before opening) and timeout (cooldown before half-open). Start at the defaults and adjust per dependency — a payment gateway deserves a lower threshold than an analytics ping.

Gotchas and Tips

  • It's a 2017 package and effectively unmaintained. Verify composer constraints against your PHP version and pin it. The library is small; if you hit a wall, fork/vendor the handful of classes into your own namespace and own them — usually cheaper than waiting for an upstream fix.
  • The breaker can't see your exceptions. failure() is entirely manual. The number-one bug is catching an exception, returning a fallback, and forgetting to report the failure — the circuit never opens and you've built a no-op.
  • Define what "failure" means. A 404 from the dependency is not a circuit failure; a timeout, connection reset, or 5xx is. Classify before calling failure() or you'll open circuits on ordinary client errors.
  • Check-then-act is racy. isAvailable() followed by the call is not atomic; several workers can all pass while the circuit is opening. Usually fine (you're limiting damage, not enforcing a hard quota) — just don't expect strict semantics.
  • success() resets the failure counter. That's the intended "consecutive failures" model. If you need a rate-based breaker (failures per time window), this package won't give it to you out of the box; extend Circuit or keep the window counters in your storage.
  • You must supply an event dispatcher, even with zero listeners. new EventDispatcher() is fine — and prefer a dedicated instance over your app's global dispatcher so these events don't leak into unrelated listeners.
  • Storage is the real extension point. Atomicity, key naming, TTLs, and multi-region all live in your StorageInterface implementation. Make the open/half-open transition atomic (Redis SET NX / a Lua script / a transaction) and set a TTL slightly longer than the timeout so abandoned circuits clean themselves up.
  • Verify method names against the source. The README is thin and docblocks are sparse; the classes are tiny, so read Circuit and CircuitBreaker before spreading usage across the codebase.
  • Debugging: when chasing "why did this open / not open", log the circuit name and current state right before the guard check. MemoryStorage makes tests deterministic; still add one integration test against your real persistent storage.
  • Don't breaker everything. Wrapping local, fast, in-process calls adds noise and false positives. This is for remote dependencies with real failure modes — and it pairs well with retries (retry first, then report the failure) and hard timeouts.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource