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

Ganesha Laravel Package

ackintosh/ganesha

Ganesha is a PHP circuit breaker library that prevents cascading failures by stopping calls to failing services and failing fast. It offers configurable thresholds, fallbacks, and storage adapters for Redis, Memcached, and Laravel, making distributed systems more resilient.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package directly; there is no Laravel auto-discovery or publishable Laravel config to rely on:

composer require ackintosh/ganesha

Look first at the package README for the exact Builder API and adapter signatures, then inspect the strategy classes (RateStrategy, CountStrategy) and the storage adapters. In Laravel, the main work is binding a named breaker manager into the container and choosing shared storage such as Redis so state is consistent across PHP-FPM and queue workers.

Minimal Laravel setup:

// config/ganesha.php
return [
    'services' => [
        'exchange_rates' => [
            'redis_connection' => 'ganesha',
            'failure_rate_threshold' => 50,
            'minimum_requests' => 10,
            'time_window' => 60,
            'interval_to_half_open' => 30,
        ],
    ],
];
// app/Services/CircuitBreakerManager.php
namespace App\Services;

use Ackintosh\Ganesha\Builder;
use Ackintosh\Ganesha\Storage\Adapter\Redis as RedisAdapter;

final class CircuitBreakerManager
{
    private array $breakers = [];

    public function breaker(string $service): object
    {
        return $this->breakers[$service] ??= $this->make($service);
    }

    private function make(string $service): object
    {
        $cfg = config("ganesha.services.$service");
        $redis = app('redis')->connection($cfg['redis_connection'] ?? 'default');

        return Builder::withRateStrategy()
            ->adapter(new RedisAdapter($redis->client()))
            ->failureRateThreshold($cfg['failure_rate_threshold'])
            ->minimumRequests($cfg['minimum_requests'])
            ->timeWindow($cfg['time_window'])
            ->intervalToHalfOpen($cfg['interval_to_half_open'])
            ->build();
    }
}
// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->singleton(\App\Services\CircuitBreakerManager::class);
}

First day-to-day use case: wrap one non-critical outbound dependency, such as an exchange-rate API or geocoding service, with a cached or degraded fallback. Start with one service key, one fallback path, and visible logging before rolling the pattern across all integrations.

$breaker = app(\App\Services\CircuitBreakerManager::class)->breaker('exchange_rates');

if (! $breaker->isAvailable('exchange_rates')) {
    return Cache::get('exchange_rates', []);
}

try {
    $response = Http::timeout(2)->get('https://rates.example.com/latest');
} catch (\Illuminate\Http\Client\ConnectionException $e) {
    $breaker->failure('exchange_rates');
    return Cache::get('exchange_rates', []);
}

if ($response->serverError()) {
    $breaker->failure('exchange_rates');
    return Cache::get('exchange_rates', []);
}

$breaker->success('exchange_rates');
Cache::put('exchange_rates', $response->json(), 300);

return $response->json();

Implementation Patterns

Use named breakers per dependency. Treat the service key as part of the contract. Use the same key everywhere for a dependency, e.g. payments, exchange_rates, crm, and avoid deriving it from request payloads unless tenant isolation is genuinely required.

Wrap clients in a small decorator, not controllers. Keep circuit-breaker logic in an application service or client wrapper. Controllers should receive a result or a domain exception, not call success() and failure() directly.

final class ExchangeRateClient
{
    public function __construct(
        private CircuitBreakerManager $breakers,
    ) {}

    public function latest(): array
    {
        $key = 'exchange_rates';
        $breaker = $this->breakers->breaker($key);

        if (! $breaker->isAvailable($key)) {
            return Cache::get($key, []);
        }

        try {
            $response = Http::timeout(2)->get(config('services.rates.url'));
        } catch (ConnectionException $e) {
            $breaker->failure($key);

            return Cache::get($key, []);
        }

        if ($response->serverError()) {
            $breaker->failure($key);

            return Cache::get($key, []);
        }

        $breaker->success($key);

        return $response->json();
    }
}

Integrate with Laravel's HTTP client through a macro or wrapper. A macro keeps call sites small, but a service class is easier to test and less magical. If using a macro, keep the breaker outside the HTTP client's own retry mechanism so one logical call is counted consistently.

Http::macro('withCircuitBreaker', function (
    string $service,
    callable $request,
    callable $fallback
) {
    $breaker = app(CircuitBreakerManager::class)->breaker($service);

    if (! $breaker->isAvailable($service)) {
        return $fallback();
    }

    try {
        $response = $request();
    } catch (ConnectionException $e) {
        $breaker->failure($service);
        throw $e;
    }

    if ($response->serverError()) {
        $breaker->failure($service);
    } else {
        $breaker->success($service);
    }

    return $response;
});

Classify failures deliberately. Count connection failures, timeouts, DNS errors, and dependency 5xx responses. Do not count validation errors, authorization failures, or client-side 4xx responses as dependency failures. A breaker that opens because users submit bad data is worse than no breaker.

Use fallbacks that are safe and idempotent. Good fallbacks include cached read data, a queued write, a degraded response, or a clear domain exception. Do not make the fallback call the same failing dependency. For writes, record intent in a queue or outbox and process it later rather than pretending the write succeeded.

Use it in jobs and commands, but avoid retry storms. For outbound calls inside queued jobs, check availability before work and release/backoff when the breaker is open. Do not stack aggressive job retries, HTTP retries, and breaker failures without deciding what counts as one logical attempt.

Log and measure transitions. If the installed version exposes events, subscribe in a service provider. Otherwise, wrap the manager or client and emit metrics for open-state hits, failures, successes, and fallbacks. Track per-service breaker state, not just application error rates.

Test with an in-memory adapter and a reset between tests. Use the package's in-memory adapter where available, or a dedicated Redis database/prefix. Bind a fake CircuitBreakerManager in feature tests and assert that fallback paths behave when isAvailable() is false. Avoid flushing a shared Redis instance from tests.

Gotchas and Tips

  • In-memory storage is not production storage. PHP-FPM requests and queue workers do not share process memory. Use Redis or another shared adapter in production, especially with Laravel Octane, where a long-lived singleton can make in-memory state leak across requests.

  • Redis connection details matter. The package adapter may expect a raw Redis client, not Laravel's connection wrapper. For phpredis, $redis->connection()->client() is often the right object; confirm the adapter signature for your installed version. If using Predis, verify which adapter/client type is supported.

  • Verify key prefixing. Passing Laravel's raw Redis client may bypass Laravel's Redis prefix. Use a dedicated Redis connection or database for Ganesha so breaker keys do not collide with cache/session keys and are not wiped by unrelated cache operations.

  • Handle storage failure explicitly. If Redis is unavailable, decide whether the application should fail open (allow the call) or fail closed (block it). The breaker should not become a new single point of failure. Wrap isAvailable() and adapter access if necessary, log the storage failure, and use a documented policy.

  • Tune thresholds against real traffic. minimumRequests that is too high means the breaker never opens; timeWindow that is too short makes it flap; intervalToHalfOpen that is too long extends outages unnecessarily. Start conservative, observe, and tune per dependency rather than using one global setting.

  • Do not use a circuit breaker as a retry mechanism or rate limiter. It records dependency health; it does not replace timeouts, retries with backoff, queues, or rate limiting. Always set HTTP timeouts; a breaker cannot help if the underlying call hangs forever.

  • Keep the service key stable. A typo such as exchange_rates vs exchange-rate creates independent breaker state. Centralize keys in constants or config and use them from both the check and the success/failure calls.

  • Avoid high-cardinality keys. Per-user or per-request breaker keys create unbounded Redis keys and meaningless thresholds. Use tenant or region keys only when the dependency genuinely fails independently for those groups.

  • Record success and failure on every completed path. Do not call failure() for exceptions that indicate the dependency is healthy, such as a 422 from a downstream API. Do not call success() after a connection exception. Use explicit branches around the protected call.

  • Do not swallow exceptions by default. Returning a fallback is fine for reads; for writes, throw a domain exception or queue the work. Silent fallbacks hide data loss and make incident diagnosis harder.

  • Watch config caching and singleton caching. Config values are safe to cache, but do not instantiate Redis clients or breaker objects inside config files. If your manager memoizes breakers by service key, reset it in tests when config changes.

  • Extension points are mostly the adapter and your wrapper. Use a custom storage adapter if Redis/Memcached/MongoDB do not fit your infrastructure. Use the strategy classes the package provides rather than copying breaker state logic into application code. If package events are unavailable in your version, emit your own metrics around isAvailable(), success(), and failure().

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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums