diff --git a/src/ToggleManager.php b/src/ToggleManager.php index 9d4fa6f..629716c 100644 --- a/src/ToggleManager.php +++ b/src/ToggleManager.php @@ -10,6 +10,7 @@ use OffloadProject\Toggle\Contracts\Driver; use OffloadProject\Toggle\Exceptions\ToggleNotFoundException; use RuntimeException; +use Throwable; class ToggleManager { @@ -33,11 +34,20 @@ public function active(string|BackedEnum $name): bool $key = $this->normalizeKey($name); if ($this->cacheEnabled()) { - return $this->getCache()->remember( - $this->cacheKey($key), - $this->cacheTtl(), - fn () => $this->resolve($key) - ); + try { + return $this->getCache()->remember( + $this->cacheKey($key), + $this->cacheTtl(), + fn () => $this->resolve($key) + ); + } catch (Throwable $exception) { + // Rethrow toggle-specific exceptions so that genuine bugs are not masked. + if ($exception instanceof ToggleNotFoundException) { + throw $exception; + } + // Other exceptions (QueryException, PDOException, etc.) indicate cache + // unavailability - fall through to resolve without cache + } } return $this->resolve($key); @@ -113,7 +123,12 @@ public function forgetCache(string|BackedEnum $name): bool { $key = $this->normalizeKey($name); - return $this->getCache()->forget($this->cacheKey($key)); + try { + return $this->getCache()->forget($this->cacheKey($key)); + } catch (Throwable) { + // Cache unavailable + return false; + } } /** @@ -121,14 +136,21 @@ public function forgetCache(string|BackedEnum $name): bool */ public function flushCache(): bool { - $cache = $this->getCache(); + // Get toggles from driver first (let driver exceptions propagate) + $toggles = $this->getDriver()->all(); - // Clear all known toggles from cache - foreach ($this->getDriver()->all() as $name => $value) { - $cache->forget($this->cacheKey($name)); - } + try { + $cache = $this->getCache(); - return true; + foreach ($toggles as $name => $value) { + $cache->forget($this->cacheKey($name)); + } + + return true; + } catch (Throwable) { + // Cache unavailable + return false; + } } /** diff --git a/tests/Feature/CacheTest.php b/tests/Feature/CacheTest.php index c805876..e8cb1a9 100644 --- a/tests/Feature/CacheTest.php +++ b/tests/Feature/CacheTest.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Illuminate\Database\QueryException; use Illuminate\Support\Facades\Cache; use OffloadProject\Toggle\Facades\Toggle; @@ -39,3 +40,38 @@ expect(Cache::has('toggle:test-flag'))->toBeFalse(); expect(Cache::has('toggle:disabled-flag'))->toBeFalse(); }); + +it('gracefully handles cache unavailability during active check', function () { + // Verify caching is enabled for this test + expect(config('toggle.cache.enabled'))->toBeTrue(); + + // Mock cache to throw a QueryException (simulating database not existing) + $exception = new QueryException('sqlite', 'select * from cache', [], new Exception('Database does not exist')); + Cache::shouldReceive('store') + ->atLeast()->once() + ->andThrow($exception); + + // Should still resolve the toggle without cache + expect(Toggle::active('test-flag'))->toBeTrue(); + expect(Toggle::active('disabled-flag'))->toBeFalse(); +}); + +it('gracefully handles cache unavailability during forgetCache', function () { + $exception = new QueryException('sqlite', 'select * from cache', [], new Exception('Database does not exist')); + Cache::shouldReceive('store') + ->once() + ->andThrow($exception); + + // Should return false but not throw + expect(Toggle::forgetCache('test-flag'))->toBeFalse(); +}); + +it('gracefully handles cache unavailability during flushCache', function () { + $exception = new QueryException('sqlite', 'select * from cache', [], new Exception('Database does not exist')); + Cache::shouldReceive('store') + ->once() + ->andThrow($exception); + + // Should return false but not throw + expect(Toggle::flushCache())->toBeFalse(); +});