22
33namespace 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 ;
69use MongoDB \Laravel \Collection ;
710use 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,148 @@ 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+ #[Override]
168+ public function increment ($ key , $ value = 1 ): int |float |false
169+ {
170+ $ this ->forgetIfExpired ($ key );
171+
172+ $ result = $ this ->collection ->findOneAndUpdate (
173+ [
174+ 'key ' => $ this ->prefix . $ key ,
175+ 'expiration ' => ['$gte ' => $ this ->currentTime ()],
176+ ],
177+ [
178+ '$inc ' => ['value ' => $ value ],
179+ ],
180+ [
181+ 'returnDocument ' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER ,
182+ ],
88183 );
184+
185+ if (! $ result ) {
186+ return false ;
187+ }
188+
189+ if ($ result ->expiration <= $ this ->currentTime ()) {
190+ $ this ->forgetIfExpired ($ key );
191+
192+ return false ;
193+ }
194+
195+ return $ result ->value ;
196+ }
197+
198+ /**
199+ * Decrement the value of an item in the cache.
200+ *
201+ * @param string $key
202+ * @param int|float $value
203+ */
204+ #[Override]
205+ public function decrement ($ key , $ value = 1 ): int |float |false
206+ {
207+ return $ this ->increment ($ key , -1 * $ value );
208+ }
209+
210+ /**
211+ * Store an item in the cache indefinitely.
212+ *
213+ * @param string $key
214+ * @param mixed $value
215+ */
216+ #[Override]
217+ public function forever ($ key , $ value ): bool
218+ {
219+ return $ this ->put ($ key , $ value , 315360000 );
220+ }
221+
222+ /**
223+ * Remove an item from the cache.
224+ *
225+ * @param string $key
226+ */
227+ #[Override]
228+ public function forget ($ key ): bool
229+ {
230+ $ result = $ this ->collection ->deleteOne ([
231+ 'key ' => $ this ->prefix . $ key ,
232+ ]);
233+
234+ return $ result ->getDeletedCount () > 0 ;
235+ }
236+
237+ /**
238+ * Remove an item from the cache if it is expired.
239+ *
240+ * @param string $key
241+ */
242+ public function forgetIfExpired ($ key ): bool
243+ {
244+ $ result = $ this ->collection ->deleteOne ([
245+ 'key ' => $ this ->prefix . $ key ,
246+ 'expiration ' => ['$lte ' => $ this ->currentTime ()],
247+ ]);
248+
249+ return $ result ->getDeletedCount () > 0 ;
250+ }
251+
252+ public function flush (): bool
253+ {
254+ $ this ->collection ->deleteMany ([]);
255+
256+ return true ;
257+ }
258+
259+ public function getPrefix ()
260+ {
261+ return $ this ->prefix ;
262+ }
263+
264+ private function serialize ($ value ): string |int |float
265+ {
266+ // Don't serialize numbers, so they can be incremented
267+ if (is_int ($ value ) || is_float ($ value )) {
268+ return $ value ;
269+ }
270+
271+ return serialize ($ value );
272+ }
273+
274+ private function unserialize ($ value ): mixed
275+ {
276+ if (! is_string ($ value ) || ! str_contains ($ value , '; ' )) {
277+ return $ value ;
278+ }
279+
280+ return unserialize ($ value );
89281 }
90282}
0 commit comments