Skip to content

Commit d439ff5

Browse files
taylorotwellStyleCIBottimacdonald
authored
Failover cache (#57430)
* working on failover cache * write failover cache * update wording * Apply fixes from StyleCI * update config file * Update src/Illuminate/Cache/FailoverStore.php Co-authored-by: Tim MacDonald <hello@timacdonald.me> * Apply suggestion from @timacdonald Co-authored-by: Tim MacDonald <hello@timacdonald.me> --------- Co-authored-by: StyleCI Bot <bot@styleci.io> Co-authored-by: Tim MacDonald <hello@timacdonald.me>
1 parent 4fd46f7 commit d439ff5

File tree

4 files changed

+248
-1
lines changed

4 files changed

+248
-1
lines changed

config/cache.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
| same cache driver to group types of items stored in your caches.
2828
|
2929
| Supported drivers: "array", "database", "file", "memcached",
30-
| "redis", "dynamodb", "octane", "null"
30+
| "redis", "dynamodb", "octane",
31+
| "failover", "null"
3132
|
3233
*/
3334

@@ -95,6 +96,14 @@
9596
'driver' => 'octane',
9697
],
9798

99+
'failover' => [
100+
'driver' => 'failover',
101+
'stores' => [
102+
'database',
103+
'array',
104+
],
105+
],
106+
98107
],
99108

100109
/*

src/Illuminate/Cache/CacheManager.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,21 @@ protected function newDynamodbClient(array $config)
245245
return new DynamoDbClient($dynamoConfig);
246246
}
247247

248+
/**
249+
* Create an instance of the failover cache driver.
250+
*
251+
* @param array $config
252+
* @return \Illuminate\Cache\Repository
253+
*/
254+
protected function createFailoverDriver(array $config)
255+
{
256+
return $this->repository(new FailoverStore(
257+
$this,
258+
$this->app->make(DispatcherContract::class),
259+
$config['stores']
260+
));
261+
}
262+
248263
/**
249264
* Create an instance of the file cache driver.
250265
*
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Illuminate\Cache\Events;
4+
5+
use Throwable;
6+
7+
class CacheFailedOver
8+
{
9+
/**
10+
* Create a new event instance.
11+
*
12+
* @param string $storeName The name of the cache store that failed.
13+
*/
14+
public function __construct(
15+
public ?string $storeName,
16+
public Throwable $exception,
17+
) {
18+
}
19+
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
<?php
2+
3+
namespace Illuminate\Cache;
4+
5+
use Illuminate\Cache\Events\CacheFailedOver;
6+
use Illuminate\Contracts\Cache\LockProvider;
7+
use Illuminate\Contracts\Events\Dispatcher;
8+
use RuntimeException;
9+
use Throwable;
10+
11+
class FailoverStore extends TaggableStore implements LockProvider
12+
{
13+
/**
14+
* Create a new failover store.
15+
*/
16+
public function __construct(
17+
protected CacheManager $cache,
18+
protected Dispatcher $events,
19+
protected array $stores)
20+
{
21+
}
22+
23+
/**
24+
* Retrieve an item from the cache by key.
25+
*
26+
* @param string $key
27+
* @return mixed
28+
*/
29+
public function get($key)
30+
{
31+
return $this->attemptOnAllStores(__FUNCTION__, func_get_args());
32+
}
33+
34+
/**
35+
* Retrieve multiple items from the cache by key.
36+
*
37+
* Items not found in the cache will have a null value.
38+
*
39+
* @return array
40+
*/
41+
public function many(array $keys)
42+
{
43+
return $this->attemptOnAllStores(__FUNCTION__, func_get_args());
44+
}
45+
46+
/**
47+
* Store an item in the cache for a given number of seconds.
48+
*
49+
* @param string $key
50+
* @param mixed $value
51+
* @param int $seconds
52+
* @return bool
53+
*/
54+
public function put($key, $value, $seconds)
55+
{
56+
return $this->attemptOnAllStores(__FUNCTION__, func_get_args());
57+
}
58+
59+
/**
60+
* Store multiple items in the cache for a given number of seconds.
61+
*
62+
* @param int $seconds
63+
* @return bool
64+
*/
65+
public function putMany(array $values, $seconds)
66+
{
67+
return $this->attemptOnAllStores(__FUNCTION__, func_get_args());
68+
}
69+
70+
/**
71+
* Store an item in the cache if the key doesn't exist.
72+
*
73+
* @param string $key
74+
* @param mixed $value
75+
* @param int $seconds
76+
* @return bool
77+
*/
78+
public function add($key, $value, $seconds)
79+
{
80+
return $this->attemptOnAllStores(__FUNCTION__, func_get_args());
81+
}
82+
83+
/**
84+
* Increment the value of an item in the cache.
85+
*
86+
* @param string $key
87+
* @param mixed $value
88+
* @return int|false
89+
*/
90+
public function increment($key, $value = 1)
91+
{
92+
return $this->attemptOnAllStores(__FUNCTION__, func_get_args());
93+
}
94+
95+
/**
96+
* Decrement the value of an item in the cache.
97+
*
98+
* @param string $key
99+
* @param mixed $value
100+
* @return int|false
101+
*/
102+
public function decrement($key, $value = 1)
103+
{
104+
return $this->attemptOnAllStores(__FUNCTION__, func_get_args());
105+
}
106+
107+
/**
108+
* Store an item in the cache indefinitely.
109+
*
110+
* @param string $key
111+
* @param mixed $value
112+
* @return bool
113+
*/
114+
public function forever($key, $value)
115+
{
116+
return $this->attemptOnAllStores(__FUNCTION__, func_get_args());
117+
}
118+
119+
/**
120+
* Get a lock instance.
121+
*
122+
* @param string $name
123+
* @param int $seconds
124+
* @param string|null $owner
125+
* @return \Illuminate\Contracts\Cache\Lock
126+
*/
127+
public function lock($name, $seconds = 0, $owner = null)
128+
{
129+
return $this->attemptOnAllStores(__FUNCTION__, func_get_args());
130+
}
131+
132+
/**
133+
* Restore a lock instance using the owner identifier.
134+
*
135+
* @param string $name
136+
* @param string $owner
137+
* @return \Illuminate\Contracts\Cache\Lock
138+
*/
139+
public function restoreLock($name, $owner)
140+
{
141+
return $this->attemptOnAllStores(__FUNCTION__, func_get_args());
142+
}
143+
144+
/**
145+
* Remove an item from the cache.
146+
*
147+
* @param string $key
148+
* @return bool
149+
*/
150+
public function forget($key)
151+
{
152+
return $this->attemptOnAllStores(__FUNCTION__, func_get_args());
153+
}
154+
155+
/**
156+
* Remove all items from the cache.
157+
*
158+
* @return bool
159+
*/
160+
public function flush()
161+
{
162+
return $this->attemptOnAllStores(__FUNCTION__, func_get_args());
163+
}
164+
165+
/**
166+
* Get the cache key prefix.
167+
*
168+
* @return string
169+
*/
170+
public function getPrefix()
171+
{
172+
return $this->attemptOnAllStores(__FUNCTION__, func_get_args());
173+
}
174+
175+
/**
176+
* Attempt the given method on all stores.
177+
*/
178+
protected function attemptOnAllStores(string $method, array $arguments)
179+
{
180+
$lastException = null;
181+
182+
foreach ($this->stores as $store) {
183+
try {
184+
return $this->store($store)->{$method}(...$arguments);
185+
} catch (Throwable $e) {
186+
$lastException = $e;
187+
188+
$this->events->dispatch(new CacheFailedOver($store, $e));
189+
}
190+
}
191+
192+
throw $lastException ?? new RuntimeException('All failover cache stores failed.');
193+
}
194+
195+
/**
196+
* Get the cache store for the given store name.
197+
*
198+
* @return \Illuminate\Contracts\Cache\Store
199+
*/
200+
protected function store(string $store)
201+
{
202+
return $this->cache->store($store)->getStore();
203+
}
204+
}

0 commit comments

Comments
 (0)