PHPackages                             me-shaon/laravel-resilience - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. me-shaon/laravel-resilience

ActiveLibrary[Testing &amp; Quality](/categories/testing)

me-shaon/laravel-resilience
===========================

Laravel-native resilience testing and fault injection toolkit.

v0.4.0(1mo ago)04↑2900%MITPHPPHP ^8.1CI passing

Since Mar 28Pushed 1mo agoCompare

[ Source](https://github.com/me-shaon/laravel-resilience)[ Packagist](https://packagist.org/packages/me-shaon/laravel-resilience)[ Docs](https://github.com/me-shaon/laravel-resilience)[ GitHub Sponsors](https://github.com/:vendor_name)[ RSS](/packages/me-shaon-laravel-resilience/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (7)Dependencies (13)Versions (7)Used By (0)

Laravel Resilience
==================

[](#laravel-resilience)

Laravel-native resilience testing and fault injection for application-level failure scenarios.

Status
------

[](#status)

The package is in active development. The baseline package bootstrapping, rule-based fault model, container and Laravel-native fault injection, assertion helpers, and scenario runner are now in place.

Compatibility
-------------

[](#compatibility)

- PHP 8.1+
- Laravel 10, 11, 12, and 13

Safety defaults
---------------

[](#safety-defaults)

- `RESILIENCE_ENABLED=true` keeps the package available by default
- `resilience.blocked_environments` defaults to `['production']`
- removing `'production'` from `resilience.blocked_environments` explicitly allows production activation
- scenario runs are only treated as safe by default in `local` and `testing`
- running scenarios in any other environment requires enabling `RESILIENCE_ALLOW_NON_LOCAL_SCENARIOS=true` and passing `--confirm-non-local`
- `php artisan resilience:run ... --dry-run` previews a scenario without activating faults or executing the scenario body

Planned capabilities
--------------------

[](#planned-capabilities)

- deterministic fault injection for container-managed services and Laravel integrations
- resilience-oriented assertions for fallbacks, logs, events, jobs, and duplicate side effects
- discovery tooling that highlights resilience-sensitive touchpoints and suggests practical improvements

Current model
-------------

[](#current-model)

The package currently works around a few simple ideas:

- a `FaultRule` describes the fault behavior we want
- the `Resilience` registry keeps track of active rules and their attempt counts
- the runtime wrapper applies those rules to container-managed services and Laravel-native targets, then restores the original binding when the rule is removed

### Container example

[](#container-example)

```
use MeShaon\LaravelResilience\Facades\Resilience;
use App\Contracts\PaymentGateway;

Resilience::for(PaymentGateway::class)->timeout();

$gateway = app(PaymentGateway::class);

$gateway->charge(500); // throws RuntimeException('Operation timed out.')

Resilience::deactivateAll();
```

In this example, Laravel Resilience wraps the `PaymentGateway` container binding, intercepts the method call, applies the active timeout rule, and then lets you restore the original binding with `deactivateAll()` or `deactivate(...)`.

Laravel-specific helpers are also available for the first integration set:

```
Resilience::http()->timeout();
Resilience::mail()->exception(new RuntimeException('Mail is down.'));
Resilience::cache()->latency(40);
Resilience::queue()->exception(new RuntimeException('Queue is down.'));
Resilience::storage()->latency(40);
```

Those helpers can also target named Laravel drivers when the application code uses them explicitly:

```
Resilience::cache('redis')->latency(40);
Resilience::mail('ses')->exception(new RuntimeException('SES is down.'));
Resilience::queue('redis')->exception(new RuntimeException('Redis queue is down.'));
Resilience::storage('s3')->timeout();
```

That means `Cache::store('redis')`, `Mail::mailer('ses')`, `Queue::connection('redis')`, and `Storage::disk('s3')` can be faulted independently without affecting the default store, mailer, connection, or disk.

Assertion helpers
-----------------

[](#assertion-helpers)

Laravel Resilience includes a small assertion layer to keep resilience tests readable while still working with Laravel's normal testing tools:

```
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use MeShaon\LaravelResilience\Facades\Resilience;

Log::spy();
Event::fake();
Bus::fake();

Resilience::assertFallbackUsed($responseSource, 'cache', 'response source fallback');
Resilience::assertLogWritten('warning', 'Cache fallback used.');
Resilience::assertEventDispatched(App\Events\CacheFallbackTriggered::class, times: 1);
Resilience::assertJobDispatched(App\Jobs\NotifyOps::class, times: 1);
Resilience::assertNoDuplicateSideEffects($writeCount, description: 'inventory write');
```

For degraded but still successful responses, combine your normal Laravel response checks with:

```
Resilience::assertDegradedButSuccessful(
    $response,
    fn ($response) => $response->headers->get('X-Resilience-Degraded') === 'true'
);
```

Scenario runner
---------------

[](#scenario-runner)

The scenario runner lets you define named resilience exercises and run them from Artisan.

A scenario is useful when you want to:

- activate one or more fault rules
- execute a real application workflow while those faults are active
- capture a structured result
- rerun the same resilience experiment by name later

Define scenarios in `config/resilience.php`:

```
'scenarios' => [
    'search-fallback' => App\Resilience\SearchFallbackScenario::class,
],
```

Each scenario class should implement `MeShaon\LaravelResilience\Scenarios\ResilienceScenario`, return the fault rules it wants to activate, and provide a `run()` method.

Example:

```
