Skip to content

Commit 76fd8a3

Browse files
committed
Make MongoStore independant from DatabaseStore
1 parent 2b109ef commit 76fd8a3

File tree

2 files changed

+245
-60
lines changed

2 files changed

+245
-60
lines changed

src/Cache/MongoStore.php

+228-41
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,127 @@
22

33
namespace MongoDB\Laravel\Cache;
44

5-
use Illuminate\Cache\DatabaseStore;
5+
use Illuminate\Cache\RetrievesMultipleKeys;
6+
use Illuminate\Contracts\Cache\LockProvider;
7+
use Illuminate\Contracts\Cache\Store;
8+
use Illuminate\Support\InteractsWithTime;
69
use MongoDB\Laravel\Collection;
710
use MongoDB\Laravel\Connection;
11+
use MongoDB\Operation\FindOneAndUpdate;
12+
use Override;
813

9-
use function assert;
14+
use function is_float;
15+
use function is_int;
16+
use function is_string;
17+
use function serialize;
18+
use function str_contains;
19+
use function unserialize;
1020

11-
class MongoStore extends DatabaseStore
21+
final class MongoStore implements LockProvider, Store
1222
{
23+
use InteractsWithTime;
24+
// Provides "many" and "putMany" in a non-optimized way
25+
use RetrievesMultipleKeys;
26+
27+
private Collection $collection;
28+
1329
public function __construct(
14-
Connection $connection,
15-
string $table,
16-
string $prefix = '',
17-
string $lockTable = 'cache_locks',
18-
array $lockLottery = [2, 100],
19-
int $defaultLockTimeoutInSeconds = 86400,
30+
private Connection $connection,
31+
private string $collectionName,
32+
private string $prefix = '',
33+
private ?Connection $lockConnection = null,
34+
private string $lockCollectionName = 'cache_locks',
35+
private array $lockLottery = [2, 100],
36+
private int $defaultLockTimeoutInSeconds = 86400,
2037
) {
21-
parent::__construct($connection, $table, $prefix, $lockTable, $lockLottery, $defaultLockTimeoutInSeconds);
38+
$this->collection = $this->connection->getCollection($this->collectionName);
39+
}
40+
41+
/**
42+
* Get a lock instance.
43+
*
44+
* @param string $name
45+
* @param int $seconds
46+
* @param string|null $owner
47+
*/
48+
#[Override]
49+
public function lock($name, $seconds = 0, $owner = null): MongoLock
50+
{
51+
return new MongoLock(
52+
($this->lockConnection ?? $this->connection)->getCollection($this->lockCollectionName),
53+
$this->prefix . $name,
54+
$seconds,
55+
$owner,
56+
$this->lockLottery,
57+
$this->defaultLockTimeoutInSeconds,
58+
);
59+
}
60+
61+
/**
62+
* Restore a lock instance using the owner identifier.
63+
*/
64+
#[Override]
65+
public function restoreLock($name, $owner): MongoLock
66+
{
67+
return $this->lock($name, 0, $owner);
2268
}
2369

70+
/**
71+
* Store an item in the cache for a given number of seconds.
72+
*
73+
* @param string $key
74+
* @param mixed $value
75+
* @param int $seconds
76+
*/
77+
#[Override]
2478
public function put($key, $value, $seconds): bool
2579
{
26-
$key = $this->prefix . $key;
27-
$value = $this->serialize($value);
28-
$expiration = $this->getTime() + $seconds;
29-
$collection = $this->table()->raw(null);
30-
assert($collection instanceof Collection);
31-
32-
$result = $collection->updateOne(
33-
['key' => ['$eq' => $key]],
34-
['$set' => ['value' => $value, 'expiration' => $expiration]],
35-
['upsert' => true],
80+
$result = $this->collection->updateOne(
81+
[
82+
'key' => $this->prefix . $key,
83+
],
84+
[
85+
'$set' => [
86+
'value' => $this->serialize($value),
87+
'expiration' => $this->currentTime() + $seconds,
88+
],
89+
],
90+
[
91+
'upsert' => true,
92+
93+
],
3694
);
3795

3896
return $result->getUpsertedCount() > 0 || $result->getModifiedCount() > 0;
3997
}
4098

99+
/**
100+
* Store an item in the cache if the key doesn't exist.
101+
*
102+
* @param string $key
103+
* @param mixed $value
104+
* @param int $seconds
105+
*/
41106
public function add($key, $value, $seconds): bool
42107
{
43-
$key = $this->prefix . $key;
44-
$value = $this->serialize($value);
45-
$expiration = $this->getTime() + $seconds;
46-
$collection = $this->table()->raw(null);
47-
assert($collection instanceof Collection);
48-
49-
$result = $collection->updateOne(
50-
['key' => ['$eq' => $key]],
108+
$result = $this->collection->updateOne(
109+
[
110+
'key' => $this->prefix . $key,
111+
],
51112
[
52113
[
53114
'$set' => [
54115
'value' => [
55116
'$cond' => [
56-
'if' => ['$lte' => ['$expiration', $this->getTime()]],
57-
'then' => $value,
117+
'if' => ['$lte' => ['$expiration', $this->currentTime()]],
118+
'then' => $this->serialize($value),
58119
'else' => '$value',
59120
],
60121
],
61122
'expiration' => [
62123
'$cond' => [
63-
'if' => ['$lte' => ['$expiration', $this->getTime()]],
64-
'then' => $expiration,
124+
'if' => ['$lte' => ['$expiration', $this->currentTime()]],
125+
'then' => $this->currentTime() + $seconds,
65126
'else' => '$expiration',
66127
],
67128
],
@@ -74,17 +135,143 @@ public function add($key, $value, $seconds): bool
74135
return $result->getUpsertedCount() > 0 || $result->getModifiedCount() > 0;
75136
}
76137

77-
public function lock($name, $seconds = 0, $owner = null)
138+
/**
139+
* Retrieve an item from the cache by key.
140+
*
141+
* @param string $key
142+
*/
143+
#[Override]
144+
public function get($key): mixed
78145
{
79-
assert($this->connection instanceof Connection);
146+
$result = $this->collection->findOne(
147+
[
148+
'key' => $this->prefix . $key,
149+
],
150+
);
80151

81-
return new MongoLock(
82-
($this->lockConnection ?? $this->connection)->getCollection($this->lockTable),
83-
$this->prefix . $name,
84-
$seconds,
85-
$owner,
86-
$this->lockLottery,
87-
$this->defaultLockTimeoutInSeconds,
152+
if ($result->expiration <= $this->currentTime()) {
153+
$this->forgetIfExpired($key);
154+
155+
return null;
156+
}
157+
158+
return $this->unserialize($result->value);
159+
}
160+
161+
/**
162+
* Increment the value of an item in the cache.
163+
*
164+
* @param string $key
165+
* @param int|float $value
166+
*
167+
* @return int|float
168+
*/
169+
#[Override]
170+
public function increment($key, $value = 1): int|float
171+
{
172+
$this->forgetIfExpired($key);
173+
174+
$result = $this->collection->findOneAndUpdate(
175+
[
176+
'key' => $this->prefix . $key,
177+
'expiration' => ['$gte' => $this->currentTime()],
178+
],
179+
[
180+
'$inc' => ['value' => $value],
181+
],
182+
[
183+
'upsert' => true,
184+
'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
185+
],
88186
);
187+
188+
return $result ? $result->value : false;
189+
}
190+
191+
/**
192+
* Decrement the value of an item in the cache.
193+
*
194+
* @param string $key
195+
* @param int|float $value
196+
*
197+
* @return int|float
198+
*/
199+
#[Override]
200+
public function decrement($key, $value = 1)
201+
{
202+
return $this->increment($key, -1 * $value);
203+
}
204+
205+
/**
206+
* Store an item in the cache indefinitely.
207+
*
208+
* @param string $key
209+
* @param mixed $value
210+
*/
211+
#[Override]
212+
public function forever($key, $value): bool
213+
{
214+
return $this->put($key, $value, 315360000);
215+
}
216+
217+
/**
218+
* Remove an item from the cache.
219+
*
220+
* @param string $key
221+
*/
222+
#[Override]
223+
public function forget($key): bool
224+
{
225+
$result = $this->collection->deleteOne([
226+
'key' => $this->prefix . $key,
227+
]);
228+
229+
return $result->getDeletedCount() > 0;
230+
}
231+
232+
/**
233+
* Remove an item from the cache if it is expired.
234+
*
235+
* @param string $key
236+
*/
237+
public function forgetIfExpired($key): bool
238+
{
239+
$result = $this->collection->deleteOne([
240+
'key' => $this->prefix . $key,
241+
'expiration' => ['$lte' => $this->currentTime()],
242+
]);
243+
244+
return $result->getDeletedCount() > 0;
245+
}
246+
247+
public function flush(): bool
248+
{
249+
$this->collection->deleteMany([]);
250+
251+
return true;
252+
}
253+
254+
public function getPrefix()
255+
{
256+
return $this->prefix;
257+
}
258+
259+
private function serialize($value): string|int
260+
{
261+
// Don't serialize numbers, so they can be incremented
262+
if (is_int($value) || is_float($value)) {
263+
return $value;
264+
}
265+
266+
return serialize($value);
267+
}
268+
269+
private function unserialize($value): string|int
270+
{
271+
if (! is_string($value) || ! str_contains($value, ';')) {
272+
return $value;
273+
}
274+
275+
return unserialize($value);
89276
}
90277
}

src/MongoDBServiceProvider.php

+17-19
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88
use Illuminate\Cache\Repository;
99
use Illuminate\Foundation\Application;
1010
use Illuminate\Support\ServiceProvider;
11-
use InvalidArgumentException;
1211
use MongoDB\Laravel\Cache\MongoStore;
1312
use MongoDB\Laravel\Eloquent\Model;
1413
use MongoDB\Laravel\Queue\MongoConnector;
1514

15+
use function assert;
16+
1617
class MongoDBServiceProvider extends ServiceProvider
1718
{
1819
/**
@@ -39,33 +40,30 @@ public function register()
3940
});
4041
});
4142

42-
// Add connector for queue support.
43-
$this->app->resolving('queue', function ($queue) {
44-
$queue->addConnector('mongodb', function () {
45-
return new MongoConnector($this->app['db']);
46-
});
47-
});
48-
49-
// Add cache store.
43+
// Add cache and lock drivers.
5044
$this->app->resolving('cache', function (CacheManager $cache) {
5145
$cache->extend('mongodb', function (Application $app, array $config): Repository {
52-
$connection = $app['db']->connection($config['connection'] ?? null);
46+
// The closure is bound to the CacheManager
47+
assert($this instanceof CacheManager);
5348

5449
$store = new MongoStore(
55-
$connection,
56-
$config['collection'] ?? $config['table'] ?? throw new InvalidArgumentException('Missing "collection" name for MongoDB cache'),
50+
$app['db']->connection($config['connection'] ?? null),
51+
$config['collection'] ?? 'cache',
5752
$this->getPrefix($config),
58-
$config['lock_table'] ?? 'cache_locks',
53+
$app['db']->connection($config['lock_connection'] ?? $config['connection'] ?? null),
54+
($config['lock_collection'] ?? $config['collection'] ?? 'cache') . '_locks',
5955
$config['lock_lottery'] ?? [2, 100],
6056
$config['lock_timeout'] ?? 86400,
6157
);
6258

63-
return $this->repository(
64-
$store->setLockConnection(
65-
$app['db']->connection($config['lock_connection'] ?? $config['connection'] ?? null),
66-
),
67-
$config,
68-
);
59+
return $this->repository($store, $config);
60+
});
61+
});
62+
63+
// Add connector for queue support.
64+
$this->app->resolving('queue', function ($queue) {
65+
$queue->addConnector('mongodb', function () {
66+
return new MongoConnector($this->app['db']);
6967
});
7068
});
7169
}

0 commit comments

Comments
 (0)