55use Illuminate \Cache \RetrievesMultipleKeys ;
66use Illuminate \Contracts \Cache \LockProvider ;
77use Illuminate \Contracts \Cache \Store ;
8- use Illuminate \Support \InteractsWithTime ;
8+ use Illuminate \Support \Carbon ;
9+ use MongoDB \BSON \UTCDateTime ;
910use MongoDB \Laravel \Collection ;
1011use MongoDB \Laravel \Connection ;
1112use MongoDB \Operation \FindOneAndUpdate ;
2021
2122final class MongoStore implements LockProvider, Store
2223{
23- use InteractsWithTime;
2424 // Provides "many" and "putMany" in a non-optimized way
2525 use RetrievesMultipleKeys;
2626
@@ -34,7 +34,7 @@ final class MongoStore implements LockProvider, Store
3434 * @param string $prefix Prefix for the name of cache items
3535 * @param Connection|null $lockConnection The MongoDB connection to use for the lock, if different from the cache connection
3636 * @param string $lockCollectionName Name of the collection where locks are stored
37- * @param array{int, int} $lockLottery Probability [chance, total] of pruning expired cache items
37+ * @param array{int, int} $lockLottery Probability [chance, total] of pruning expired cache items. Set to [0, 0] to disable
3838 * @param int $defaultLockTimeoutInSeconds Time-to-live of the locks in seconds
3939 */
4040 public function __construct (
@@ -62,10 +62,9 @@ public function lock($name, $seconds = 0, $owner = null): MongoLock
6262 return new MongoLock (
6363 ($ this ->lockConnection ?? $ this ->connection )->getCollection ($ this ->lockCollectionName ),
6464 $ this ->prefix . $ name ,
65- $ seconds ,
65+ $ seconds ?: $ this -> defaultLockTimeoutInSeconds ,
6666 $ owner ,
6767 $ this ->lockLottery ,
68- $ this ->defaultLockTimeoutInSeconds ,
6968 );
7069 }
7170
@@ -95,7 +94,7 @@ public function put($key, $value, $seconds): bool
9594 [
9695 '$set ' => [
9796 'value ' => $ this ->serialize ($ value ),
98- 'expiration ' => $ this ->currentTime () + $ seconds ,
97+ 'expires_at ' => $ this ->getUTCDateTime ( $ seconds) ,
9998 ],
10099 ],
101100 [
@@ -116,6 +115,8 @@ public function put($key, $value, $seconds): bool
116115 */
117116 public function add ($ key , $ value , $ seconds ): bool
118117 {
118+ $ isExpired = ['$lte ' => ['$expires_at ' , $ this ->getUTCDateTime ()]];
119+
119120 $ result = $ this ->collection ->updateOne (
120121 [
121122 '_id ' => $ this ->prefix . $ key ,
@@ -125,16 +126,16 @@ public function add($key, $value, $seconds): bool
125126 '$set ' => [
126127 'value ' => [
127128 '$cond ' => [
128- 'if ' => [ ' $lte ' => [ ' $expiration ' , $ this -> currentTime ()]] ,
129+ 'if ' => $ isExpired ,
129130 'then ' => $ this ->serialize ($ value ),
130131 'else ' => '$value ' ,
131132 ],
132133 ],
133- 'expiration ' => [
134+ 'expires_at ' => [
134135 '$cond ' => [
135- 'if ' => [ ' $lte ' => [ ' $expiration ' , $ this -> currentTime ()]] ,
136- 'then ' => $ this ->currentTime () + $ seconds ,
137- 'else ' => '$expiration ' ,
136+ 'if ' => $ isExpired ,
137+ 'then ' => $ this ->getUTCDateTime ( $ seconds) ,
138+ 'else ' => '$expires_at ' ,
138139 ],
139140 ],
140141 ],
@@ -156,14 +157,14 @@ public function get($key): mixed
156157 {
157158 $ result = $ this ->collection ->findOne (
158159 ['_id ' => $ this ->prefix . $ key ],
159- ['projection ' => ['value ' => 1 , 'expiration ' => 1 ]],
160+ ['projection ' => ['value ' => 1 , 'expires_at ' => 1 ]],
160161 );
161162
162163 if (! $ result ) {
163164 return null ;
164165 }
165166
166- if ($ result ['expiration ' ] <= $ this ->currentTime ()) {
167+ if ($ result ['expires_at ' ] <= $ this ->getUTCDateTime ()) {
167168 $ this ->forgetIfExpired ($ key );
168169
169170 return null ;
@@ -181,12 +182,9 @@ public function get($key): mixed
181182 #[Override]
182183 public function increment ($ key , $ value = 1 ): int |float |false
183184 {
184- $ this ->forgetIfExpired ($ key );
185-
186185 $ result = $ this ->collection ->findOneAndUpdate (
187186 [
188187 '_id ' => $ this ->prefix . $ key ,
189- 'expiration ' => ['$gte ' => $ this ->currentTime ()],
190188 ],
191189 [
192190 '$inc ' => ['value ' => $ value ],
@@ -200,7 +198,7 @@ public function increment($key, $value = 1): int|float|false
200198 return false ;
201199 }
202200
203- if ($ result ['expiration ' ] <= $ this ->currentTime ()) {
201+ if ($ result ['expires_at ' ] <= $ this ->getUTCDateTime ()) {
204202 $ this ->forgetIfExpired ($ key );
205203
206204 return false ;
@@ -257,7 +255,7 @@ public function forgetIfExpired($key): bool
257255 {
258256 $ result = $ this ->collection ->deleteOne ([
259257 '_id ' => $ this ->prefix . $ key ,
260- 'expiration ' => ['$lte ' => $ this ->currentTime ()],
258+ 'expires_at ' => ['$lte ' => $ this ->getUTCDateTime ()],
261259 ]);
262260
263261 return $ result ->getDeletedCount () > 0 ;
@@ -275,6 +273,17 @@ public function getPrefix(): string
275273 return $ this ->prefix ;
276274 }
277275
276+ /** Creates a TTL index that automatically deletes expired objects. */
277+ public function createTTLIndex (): void
278+ {
279+ $ this ->collection ->createIndex (
280+ // UTCDateTime field that holds the expiration date
281+ ['expires_at ' => 1 ],
282+ // Delay to remove items after expiration
283+ ['expireAfterSeconds ' => 0 ],
284+ );
285+ }
286+
278287 private function serialize ($ value ): string |int |float
279288 {
280289 // Don't serialize numbers, so they can be incremented
@@ -293,4 +302,9 @@ private function unserialize($value): mixed
293302
294303 return unserialize ($ value );
295304 }
305+
306+ private function getUTCDateTime (int $ additionalSeconds = 0 ): UTCDateTime
307+ {
308+ return new UTCDateTime (Carbon::now ()->addSeconds ($ additionalSeconds ));
309+ }
296310}
0 commit comments