Download the PHP package harris21/laravel-fuse without Composer

On this page you can find all versions of the php package harris21/laravel-fuse. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel-fuse

Circuit breaker for Laravel queue jobs

Protect your queue workers from cascading failures when external services go down.


The Problem

When Stripe goes down at 11 PM, your queue workers don't know. They keep trying to charge customers. Each job waits 30 seconds for a timeout. Then retries. Waits again. Your entire queue system freezes.

Without Fuse: 10,000 jobs × 30-second timeouts = 25+ hours to clear the queue.

With Fuse: Circuit opens after 5 failures. Queue clears in 10 seconds. Automatic recovery when the service returns.


Features


How It Works

CLOSED — Normal operations. All requests pass through. Failures are tracked in the background.

OPEN — Protection mode. After the failure threshold is exceeded, the circuit trips. Jobs fail instantly (1ms, not 30s) and are delayed for automatic retry. No API calls are made.

HALF-OPEN — Testing recovery. After the timeout period, one probe request tests if the service recovered. Success closes the circuit. Failure reopens it. If you need to, you may customize the probe request.

If a cache call fails while Fuse decides whether a job may run, for example on a timeout, the middleware reports the error and releases the job with its release delay, so that error does not count toward the job's maxExceptions. If the kill switch can't be read, Fuse reports that too and goes by fuse.enabled. On the sync driver, where a release would drop the job, the error is thrown as before.


Installation

Publish the configuration:


Quick Start

Add the middleware to your job:

That's it. Your job is now protected.


Attributes

Instead of building the middleware array yourself, declare protection with attributes:

Jobs that talk to multiple services can stack them:

Both release: and window: can be set per attribute. window: overrides the failure-tracking window (in seconds) for that service, taking precedence over config:

If you have other middleware to include, use merge() to prepend the circuit breakers:


Configuration


Peak Hours

Configure different thresholds for business hours when every transaction matters:

During peak hours (9 AM - 5 PM), the circuit uses the higher threshold to maximize successful transactions. Outside peak hours, it uses the lower threshold for earlier protection.

Without peak_hours_threshold, a service uses its threshold (or default_threshold when it sets none) at every hour of the day.

A peak window can cross midnight: 'peak_hours_start' => 22 with 'peak_hours_end' => 6 covers 22:00 to 06:59. Both hours are inclusive, so an end of 17 lasts until 17:59.


Tracking Window

Failures are counted in fixed time windows (tumbling buckets), and the circuit only evaluates the failure rate once a window has gathered min_requests attempts. This matters for low-volume services: if a service doesn't see min_requests attempts within a single window, the failure rate is never checked and the circuit can't trip. The default 60-second window fits busy services — quieter ones need a wider window so enough attempts accumulate.

Widen the window so enough samples accumulate before the bucket rolls over:

Set it globally with default_window, per-service as above, or inline on the attribute (#[UseCircuitBreaker(service: 'reports', window: 600)]). Counters auto-expire after twice the window, so there's still nothing to clean up.

Trade-offs to keep in mind: a longer window reacts more slowly and keeps the circuit eligible to stay open longer, and because buckets are tumbling, a failure burst that straddles a boundary is split across two windows — so worst-case detection can lag by up to one window. If you ever need smoother behaviour, a future weighted "current + previous bucket" counter could remove that boundary effect.


Intelligent Failure Classification

Not all errors indicate a service is down. Fuse only counts real outages:

Error Type Counted as Failure? Reason
500, 502, 503 Yes Server errors indicate service problems
Connection timeout Yes Service is unreachable
Connection refused Yes Service is unreachable
429 Too Many Requests No Service is healthy, just rate limiting
401 Unauthorized No Your API key is wrong, not a service issue
403 Forbidden No Permission issue, not a service outage
400 Bad Request Yes Could indicate API issues
404 Not Found Yes Could indicate API changes

This prevents false positives. A rate limit doesn't mean Stripe is down - it means you're sending too many requests.


Custom Failure Classification

The default behavior works well for most APIs, but some services deviate from HTTP standards. For example, Stripe returns 500 for idempotency errors that are actually client-side issues — not outages.

You can override the failure classification logic per service by setting the failure_classifier option in your service config:

Extending the Default Classifier

The easiest approach is to extend DefaultFailureClassifier and override specific cases:

Implementing the Interface from Scratch

For full control, implement FailureClassifier directly:

When no failure_classifier is configured, Fuse uses DefaultFailureClassifier which preserves the behavior described in the table above.

Rate-limited jobs and Retry-After

The classifier keeps 429s from tripping the circuit, but that is all it does. The exception itself still reaches the worker, so a rate-limited job counts one of its maxExceptions and retries on its normal backoff (zero unless you set one). Three 429s in a row will fail a job that did nothing wrong, against an API that was up the whole time.

Fuse leaves this alone on purpose. When to retry a rate-limited job, and how long to wait, is retry logic; the circuit breaker's only stake in a 429 is to not mistake it for an outage. Handle the retry side with a small job middleware that reads the delay the API already gave you:

Because the middleware releases instead of rethrowing, the 429 no longer counts toward maxExceptions either. Note that Retry-After can also be an HTTP date rather than seconds; the cast falls back to 30 in that case, so parse the date form if your API sends it.

List it before the circuit breaker so Fuse still sees and classifies the exception:

The order matters. Placed after CircuitBreakerMiddleware, the swallowed 429 looks like a success to the breaker, and a half-open circuit would close on a rate-limited probe.

If you would rather stop the whole service when it is rate limited, a custom classifier that counts 429s does that: enough of them trip the circuit, and every job for that service is released without an HTTP call until the timeout expires. The delay is your release config rather than the header value, and the circuit reports it as an outage, so reach for this only when sustained throttling should be treated as one.


Recovery strategies

By default, when a circuit is half-open, Fuse lets a single probe request through to test whether the service has recovered. If it succeeds, the circuit closes and traffic returns to full speed; if it fails, the circuit reopens. You don't need to configure anything for this — it's how Fuse works out of the box.

If you would like more control over how a recovered service is tested, you may provide your own recovery strategy by implementing the RecoveryStrategy contract. This is entirely optional.

For example, you might warm a slow service back up gradually instead of returning to full speed on the first success:

Then reference your class via the recovery_strategy option on your service configuration. It is resolved through the container, just like failure_classifier:

Use $breaker->key('your-suffix') to namespace any cache keys your strategy needs.

Where does the probe job go?

After my talk at Laravel Live Denmark 2026, three or four people asked the same thing: when the circuit goes half-open and the test job runs, where does that job go afterwards? Back to the top of the queue where it was, or to the bottom?

The bottom, and the same is true for every job released while the circuit is open. release() is the only portable way Laravel gives a job middleware to put a job back, and on Redis and the database driver it always lands at the tail. Redis moves it through the delayed set and pushes it onto the end of the list; the database driver deletes the row and inserts a new one with a higher id. SQS just hides the message for the delay, so it keeps its slot there, but SQS standard queues don't promise order to begin with.

This matters most for the probe. Say jobs A, B and C were queued in that order when the circuit opened. After the timeout, A runs as the probe and fails. Laravel's worker releases A with the job's backoff (default 0), A gets popped again a moment later, the circuit is open now, and Fuse releases it with the normal delay. B and C were released the moment the probe slot was taken, so they are already ahead of A in the delayed set. Which job probes next depends on which one comes due first after the timeout, and it is rarely A. Measured with three workers and six jobs on Redis: the probes across four cycles were jobs 5, 6, 4 and 5, and job 3 was the first one through when the service came back.

If you want the oldest held job to remain the recovery probe, switch the service to the OldestJobProbe strategy:

It works like this. While the circuit is open, every held job passes through the middleware once per release interval, so within one interval Fuse has seen the whole backlog. It keeps the job with the oldest dispatch time (the createdAt stamp Laravel writes into the payload, which survives every release). When the circuit goes half-open, only that job is allowed to run; the rest are released as usual. If the probe fails, the same job is the probe next time. It stays the probe until it succeeds, or until Laravel fails it for good via maxExceptions, at which point the next oldest job takes over.

Every admitted probe also holds a cache lock. default_probe_lock_ttl is 65 seconds, and probe_lock_ttl overrides it per service. If the job declares a positive $timeout, Fuse uses at least $timeout + 5. Set the value higher than any worker timeout or probe runtime, and use a finite timeout for probe jobs.

What this gives you, precisely:

What it does not give you:

While open, fuse:status and the status page show the current probe candidate. The candidate can change as Fuse sees older held jobs. Once half-open, the same field shows the elected probe. JSON uses probe_candidate; the table uses the Probe column.

One cost to be aware of: if the elected job is sitting in its release delay when the timeout expires, recovery waits for it to come round, up to release seconds. That's why the strategy is opt-in.


Events

Fuse dispatches Laravel events on every state transition:

Listening to Events

Event Properties

CircuitBreakerOpened:

CircuitBreakerHalfOpen:

CircuitBreakerClosed:

Wiring Events to Monitoring

Fuse only emits events — it deliberately doesn't log, page, or export metrics itself, so nothing about your monitoring stack needs to be known by the package. Point a listener at whichever tool you already use.

Slack (or any webhook-based alert):

Prometheus (via whichever client you've wired into your app, e.g. promphp/prometheus_client_php):

Map both methods to their respective events in your EventServiceProvider (or listener attributes), same as any other multi-event listener.

Laravel Pulse (custom recorder — check your installed Pulse version's docs for the exact recorder signature):

Register it in config/pulse.php under recorders, and it'll show up alongside your other Pulse cards.


Real-World Scenarios

Stripe goes down at 11 PM

What actually happens, second by second:

  1. 11:00:00 PM — Stripe starts returning 503s. Jobs still run at full speed; each one waits out a real timeout before failing, and every failure is tallied in the current window.
  2. 11:00:04 PM — The 5th failure lands and the failure rate crosses 50%. The circuit trips: CircuitBreakerOpened fires with the failure rate, attempts, and failure count for your listeners to log or alert on.
  3. 11:00:04 PM onward — Every new ChargeCustomer job fails in the middleware in about 1ms, no HTTP call made, and is released back onto the queue with a 20s delay (release: 20). Your workers stay free to process jobs for other services instead of blocking on Stripe timeouts.
  4. 11:00:34 PM — 30 seconds after opening, the next job to run is let through as a single probe. The circuit is now half-open; CircuitBreakerHalfOpen fires. Every other released job stays parked — the recovery strategy only ever admits one probe at a time (see Recovery strategies).
  5. Two outcomes:
    • Stripe recovered: the probe succeeds, the circuit closes, CircuitBreakerClosed fires, and the backlog of released jobs drains at full speed as their delays expire.
    • Stripe is still down: the probe fails, the circuit reopens immediately (no need to re-accumulate 5 failures), and the cycle repeats from step 4 after another 30s.

Without Fuse, all of those queued jobs would each burn a full HTTP timeout retrying Stripe directly. With it, the queue stops hammering a dead endpoint within one failure window and resumes automatically the moment Stripe answers again.

Isolating one customer's broken webhook endpoint

A common queue pattern is delivering outbound webhooks to URLs your customers configure themselves — you don't control their uptime, and one customer's misconfigured or down endpoint shouldn't slow down deliveries to everyone else.

Fuse's circuits are just keyed by the string you pass in, so naming the circuit per-recipient gets you per-recipient isolation for free — no extra config needed, since an unconfigured circuit name simply falls back to your default_* settings:

If endpoint #42 starts timing out, only the webhook:42 circuit trips. Deliveries to endpoints #7, #118, and everyone else keep flowing at full speed through the same queue, same workers, same job class — they're each tracked under their own service name and never see endpoint 42's failures. Once endpoint 42 recovers, its circuit closes independently, exactly like the Stripe scenario above.

Caveat: fuse:status, fuse:open/fuse:close, and the status page dashboard all only know about service names listed under fuse.services in your config — they can't discover or display dynamically-named circuits like webhook:42. If you need CLI/dashboard visibility into a specific high-traffic endpoint, add it to config/fuse.php explicitly; otherwise, inspect it programmatically with (new CircuitBreaker("webhook:{$id}"))->getStats().


Status Page

Fuse includes a real-time monitoring dashboard that shows the state of all your circuit breakers.

Enable the Status Page

Add to your .env:

The status page is available at /fuse (configurable via FUSE_STATUS_PAGE_PREFIX).

Authorization

Access is controlled by a viewFuse gate. By default, only the local environment is allowed. Override it in your AppServiceProvider:

If you set status_page.middleware, your middleware replaces this gate check. Add 'can:viewFuse' to the list to keep it. The enabled setting applies either way.

Configuration

What It Shows


Artisan Commands

Fuse includes CLI commands for inspecting and manually controlling circuit breakers.

Check circuit status

Outputs a table with the current state, failure rate, request counts, and threshold for each circuit.

Reset a circuit

Resets the circuit to CLOSED state and clears all stats for the current window.

Manually open a circuit

Forces the circuit OPEN immediately. Useful when you know a service is down and want to protect your queue before failures accumulate. The circuit will recover automatically after the configured timeout.

Manually close a circuit

Forces the circuit CLOSED immediately. Useful when a service has recovered but the circuit hasn't timed out yet.


Fallback Strategies

When the circuit opens, your application needs a plan. Here are common strategies:

Return cached data — Show last known prices, cached shipping rates, or stale product info. Slightly stale data beats an error page.

Use a fallback service — Switch to a backup payment provider, or show "payment pending" and queue it for later.

Queue for later — Fuse already does this with release(). For synchronous requests, dispatch a job to retry when the circuit closes.

Graceful degradation — Hide the feature entirely. Can't load recommendations? Don't show that section. The page still works.


Direct Usage

Use the circuit breaker directly outside of jobs:

Check Circuit State


Requirements


Credits

Built by Harris Raftopoulos for Laracon India 2026.

YouTube: @harrisrafto

Video walkthrough: Watch on YouTube

Based on the circuit breaker pattern from Michael Nygard's Release It! and popularized by Martin Fowler.


License

MIT


All versions of laravel-fuse with dependencies

PHP Build Version
Package Version
Requires php Version ^8.3
illuminate/cache Version ^11.0|^12.0|^13.0
illuminate/contracts Version ^11.0|^12.0|^13.0
illuminate/support Version ^11.0|^12.0|^13.0
spatie/laravel-package-tools Version ^1.16
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package harris21/laravel-fuse contains the following files

Loading the files please wait ...