wal3fo / laravel-sentinel
Production-ready circuit breaker package for Laravel queue jobs and service calls.
Requires
- php: ^8.1
- illuminate/cache: ^10.0|^11.0|^12.0|^13.0
- illuminate/config: ^10.0|^11.0|^12.0|^13.0
- illuminate/console: ^10.0|^11.0|^12.0|^13.0
- illuminate/contracts: ^10.0|^11.0|^12.0|^13.0
- illuminate/events: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- phpunit/phpunit: ^10.0|^11.0
README
Production-ready circuit breaker for Laravel queues and service calls, built to stop cascading failures with closed, open, and half-open states.
Introduction
When a dependency starts failing (mail API, payment gateway, CRM, webhook target), retries alone can overload workers and amplify outages. A circuit breaker prevents that by temporarily short-circuiting failing paths, then probing recovery in a controlled way.
Wal3fo Laravel Sentinel is designed for Laravel queue-heavy applications where resilience and predictable failure handling matter. Use it when:
- jobs call unstable or rate-limited external services
- multiple workers can overwhelm a failing dependency
- you need controlled backoff and safe recovery behavior
- you want observable state transitions and operational commands
Features
- Queue middleware for automatic circuit checks and job release behavior
- Full circuit lifecycle:
closed,open,half_open - Cache-backed state and counters (Redis strongly recommended)
- Per-service configuration overrides
- Pluggable failure classification via contract
- Artisan commands for status and manual control
- Transition events for monitoring and alerting
- Laravel
10,11,12, and13support
Installation
composer require wal3fo/laravel-sentinel
Publish the package configuration:
php artisan vendor:publish --tag=sentinel-config
Tip
Use Redis as the cache backend for distributed queue workers and reliable lock behavior.
Configuration
Sentinel is configured in config/sentinel.php.
Global defaults are defined under defaults, then selectively overridden per service under services.
<?php return [ 'defaults' => [ 'threshold' => 50.0, 'min_requests' => 10, 'cooldown_seconds' => 60, 'release_delay_seconds' => 30, 'half_open_probe_limit' => 1, ], 'services' => [ 'mail' => [ 'threshold' => 40.0, 'min_requests' => 20, 'cooldown_seconds' => 120, 'release_delay_seconds' => 20, 'half_open_probe_limit' => 1, ], 'crm' => [ 'threshold' => 30.0, 'min_requests' => 8, 'cooldown_seconds' => 45, 'release_delay_seconds' => 15, ], ], ];
Key Options
threshold: failure-rate percentage that opens the circuitmin_requests: minimum attempts required before threshold evaluationcooldown_seconds: time to keep the circuit open before half-open probe windowrelease_delay_seconds: queue job release delay when circuit is not allowing execution
Per-Service Overrides
Any service key in services inherits from defaults and can override only what it needs.
Basic Usage
Queue Job Middleware
<?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Wal3fo\LaravelSentinel\Middleware\CircuitBreakerMiddleware; final class SendEmailJob implements ShouldQueue { use Dispatchable; use InteractsWithQueue; use Queueable; use SerializesModels; public function middleware(): array { return [ new CircuitBreakerMiddleware('mail'), ]; } public function handle(): void { // Call external provider here. } }
Multiple services in one job:
public function middleware(): array { return [ new CircuitBreakerMiddleware(['mail', 'crm']), ]; }
Attribute Usage
When you prefer declarative service mapping, use the package attribute and instantiate middleware without arguments.
<?php namespace App\Jobs; use Illuminate\Contracts\Queue\ShouldQueue; use Wal3fo\LaravelSentinel\Attributes\UseCircuitBreaker; use Wal3fo\LaravelSentinel\Middleware\CircuitBreakerMiddleware; #[UseCircuitBreaker(['mail'])] final class SendNotificationJob implements ShouldQueue { public function middleware(): array { return [new CircuitBreakerMiddleware()]; } public function handle(): void { // Perform work. } }
How It Works
Sentinel tracks attempts and failures per service in cache.
closed: requests/jobs flow normally while failures are recorded.open: oncemin_requestsis met andfailure_rate >= threshold, executions are short-circuited.half_open: aftercooldown_seconds, Sentinel allows limited probes (half_open_probe_limit).- On probe success, the circuit closes and counters reset.
- On probe failure, the circuit re-opens and cooldown restarts.
Failure rate is calculated as:
$$ ext{failure_rate} = \frac{\text{failures}}{\text{attempts}} \times 100 $$
When a queue middleware check finds a circuit open (or half-open without an available probe slot), the job is released using the configured release_delay_seconds.
Failure Classification
By default, Sentinel uses:
Wal3fo\LaravelSentinel\Services\DefaultFailureClassifier
The default classifier ignores configured business exceptions and counts outage-like exceptions.
To customize behavior, provide your own classifier implementing:
Wal3fo\LaravelSentinel\Contracts\FailureClassifier
<?php namespace App\Support; use Throwable; use Wal3fo\LaravelSentinel\Contracts\FailureClassifier; final class ApiFailureClassifier implements FailureClassifier { public function shouldCount(Throwable $throwable, string $service, array $serviceConfig = []): bool { return ! $throwable instanceof \DomainException; } }
Register it in configuration:
'failure_classifier' => [ 'class' => App\Support\ApiFailureClassifier::class, ],
Artisan Commands
php artisan sentinel:statusShows status for all configured services.php artisan sentinel:status mailShows detailed status for a single service.php artisan sentinel:open mailForces a service circuit toopen.php artisan sentinel:close mailForces a service circuit toclosed.php artisan sentinel:resetResets all configured service circuits and counters.php artisan sentinel:reset mailResets one service circuit and counters.
Events
Sentinel dispatches events on state changes:
Wal3fo\LaravelSentinel\Events\CircuitOpenedFired when a circuit transitions toopen.Wal3fo\LaravelSentinel\Events\CircuitHalfOpenedFired when cooldown expires and circuit entershalf_open.Wal3fo\LaravelSentinel\Events\CircuitClosedFired when recovery succeeds and circuit returns toclosed.
Each event extends Wal3fo\LaravelSentinel\Events\CircuitStateChanged and includes service, state, and status data.
Best Practices
- Apply circuit breakers to network or infrastructure-dependent operations, not pure business validation logic.
- Count failures that indicate dependency outage/timeouts; ignore business/domain exceptions where appropriate.
- Use Redis for cache and locks in multi-worker production environments.
- Set
min_requestshigh enough to avoid opening on low-volume noise. - Tune
thresholdper dependency based on real-world error patterns and SLOs.
Testing
Run the test suite:
composer test
The package includes coverage for:
- middleware behavior and release flow when circuits are open
- state transitions (
closed→open→half_open→closed) - half-open probe limits
- failure classification rules
- event dispatching
- artisan command behavior
Compatibility
- Laravel:
10,11,12,13 - PHP:
^8.1(effective minimum follows the selected Laravel version)
Contributing
Contributions are welcome.
- Fork the repository.
- Create a feature branch.
- Add tests for behavioral changes.
- Run
composer test. - Open a pull request with a clear description.
License
Released under the MIT License.