Skip to content
This repository was archived by the owner on Jun 23, 2025. It is now read-only.

Commit c579789

Browse files
authored
Add support for failover cache (#23)
- Changes cache TTL default to 15s, the standard default - Add unleash.cache.failover option that will pull the last successful result from the cache - This setting is independent of regular caching - The cache is always stored, to allow enabling the feature during a failure incidence - This change also moves the requests for features out of the constructor - Possible to use in Facades - Still "cached" for the lifetime of the Unleash object
1 parent 75443b6 commit c579789

File tree

5 files changed

+411
-36
lines changed

5 files changed

+411
-36
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"description": "An Unleash client for Laravel",
44
"type": "library",
55
"require": {
6-
"guzzlehttp/guzzle": "^6.3",
6+
"guzzlehttp/guzzle": "^6.3|^7.0",
77
"illuminate/support": "^5.8|^6|^7|^8",
88
"illuminate/http": "^5.8|^6|^7|^8"
99
},

config/unleash.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@
1111
'isEnabled' => env('UNLEASH_ENABLED', true),
1212

1313
// Allow the Unleash API response to be cached.
14+
// Default TTL is 15s
15+
// Failover caching will use the last successful result from Unleash if it down.
16+
// Failover is independent of regular caching.
1417
'cache' => [
15-
'isEnabled' => false,
16-
'ttl' => 3600,
18+
'isEnabled' => env('UNLEASH_CACHE_ENABLED', false),
19+
'ttl' => env('UNLEASH_CACHE_TTL', 15),
20+
'failover' => env('UNLEASH_CACHE_FAILOVER', true),
1721
],
1822

1923
// Mapping of strategies used to guard features on Unleash. The default strategies are already

src/Unleash.php

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,52 @@
22

33
namespace MikeFrancis\LaravelUnleash;
44

5-
use function GuzzleHttp\json_decode;
6-
75
use GuzzleHttp\ClientInterface;
6+
use GuzzleHttp\Exception\InvalidArgumentException;
7+
use GuzzleHttp\Exception\TransferException;
88
use Illuminate\Contracts\Cache\Repository as Cache;
99
use Illuminate\Contracts\Config\Repository as Config;
1010
use Illuminate\Http\Request;
1111
use Illuminate\Support\Arr;
1212
use MikeFrancis\LaravelUnleash\Strategies\Contracts\DynamicStrategy;
1313
use MikeFrancis\LaravelUnleash\Strategies\Contracts\Strategy;
14+
use Symfony\Component\HttpFoundation\Exception\JsonException;
15+
use function GuzzleHttp\json_decode;
1416

1517
class Unleash
1618
{
17-
protected $client;
19+
const DEFAULT_CACHE_TTL = 15;
1820

21+
protected $client;
1922
protected $cache;
20-
2123
protected $config;
22-
2324
protected $request;
24-
25-
protected $features = [];
25+
protected $features;
2626

2727
public function __construct(ClientInterface $client, Cache $cache, Config $config, Request $request)
2828
{
2929
$this->client = $client;
3030
$this->cache = $cache;
3131
$this->config = $config;
3232
$this->request = $request;
33-
34-
if (!$this->config->get('unleash.isEnabled')) {
35-
return;
36-
}
37-
38-
if ($this->config->get('unleash.cache.isEnabled')) {
39-
$this->features = $this->cache->remember(
40-
'unleash',
41-
$this->config->get('unleash.cache.ttl'),
42-
function () {
43-
return $this->fetchFeatures();
44-
}
45-
);
46-
} else {
47-
$this->features = $this->fetchFeatures();
48-
}
4933
}
5034

5135
public function getFeatures(): array
5236
{
53-
return $this->features;
37+
try {
38+
$features = $this->getCachedFeatures();
39+
40+
// Always store the failover cache, in case it is turned on during failure scenarios.
41+
$this->cache->forever('unleash.features.failover', $features);
42+
43+
return $features;
44+
} catch (TransferException | JsonException $e) {
45+
if ($this->config->get('unleash.cache.failover') === true) {
46+
return $this->cache->get('unleash.features.failover', []);
47+
}
48+
}
49+
50+
return [];
5451
}
5552

5653
public function getFeature(string $name)
@@ -109,16 +106,36 @@ public function isFeatureDisabled(string $name, ...$args): bool
109106
return !$this->isFeatureEnabled($name, ...$args);
110107
}
111108

109+
protected function getCachedFeatures(): array
110+
{
111+
if (!$this->config->get('unleash.isEnabled')) {
112+
return [];
113+
}
114+
115+
if ($this->config->get('unleash.cache.isEnabled')) {
116+
return $this->cache->remember(
117+
'unleash',
118+
$this->config->get('unleash.cache.ttl', self::DEFAULT_CACHE_TTL),
119+
function () {
120+
return $this->fetchFeatures();
121+
}
122+
);
123+
}
124+
125+
return $this->features ?? $this->features = $this->fetchFeatures();
126+
}
127+
112128
protected function fetchFeatures(): array
113129
{
114-
try {
115-
$response = $this->client->get($this->getFeaturesApiUrl(), $this->getRequestOptions());
116-
$data = json_decode((string) $response->getBody(), true);
130+
$response = $this->client->get($this->getFeaturesApiUrl(), $this->getRequestOptions());
117131

118-
return $this->formatResponse($data);
119-
} catch (\InvalidArgumentException $e) {
120-
return [];
132+
try {
133+
$data = json_decode((string)$response->getBody(), true, 512, \JSON_BIGINT_AS_STRING);
134+
} catch (InvalidArgumentException $e) {
135+
throw new JsonException('Could not decode unleash response body.', $e->getCode(), $e);
121136
}
137+
138+
return $this->formatResponse($data);
122139
}
123140

124141
protected function getFeaturesApiUrl(): string

tests/Strategies/DynamicStrategyTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,14 @@ protected function getMockConfig(\PHPUnit\Framework\MockObject\MockObject $strat
114114
]
115115
);
116116
$config->expects($this->at(3))
117+
->method('get')
118+
->with('unleash.isEnabled')
119+
->willReturn(true);
120+
$config->expects($this->at(4))
121+
->method('get')
122+
->with('unleash.cache.isEnabled')
123+
->willReturn(false);
124+
$config->expects($this->at(5))
117125
->method('get')
118126
->with('unleash.strategies')
119127
->willReturn(

0 commit comments

Comments
 (0)