1 | <?php |
||||
2 | |||||
3 | namespace SubjectivePHP\Psr\SimpleCache; |
||||
4 | |||||
5 | use DateInterval; |
||||
6 | use MongoDB\BSON\UTCDateTime; |
||||
7 | use MongoDB\Collection; |
||||
8 | use Psr\SimpleCache\CacheInterface; |
||||
9 | use SubjectivePHP\Psr\SimpleCache\Serializer\NullSerializer; |
||||
10 | use SubjectivePHP\Psr\SimpleCache\Serializer\SerializerInterface; |
||||
11 | |||||
12 | /** |
||||
13 | * A PSR-16 implementation which stores data in a MongoDB collection. |
||||
14 | */ |
||||
15 | final class MongoCache implements CacheInterface |
||||
16 | { |
||||
17 | use KeyValidatorTrait; |
||||
18 | use TTLValidatorTrait; |
||||
19 | |||||
20 | /** |
||||
21 | * MongoDB collection containing the cached responses. |
||||
22 | * |
||||
23 | * @var Collection |
||||
24 | */ |
||||
25 | private $collection; |
||||
26 | |||||
27 | /** |
||||
28 | * The object responsible for serializing data to and from Mongo documents. |
||||
29 | * |
||||
30 | * @var SerializerInterface |
||||
31 | */ |
||||
32 | private $serializer; |
||||
33 | |||||
34 | /** |
||||
35 | * Array of settings to use with find commands. |
||||
36 | * |
||||
37 | * @var array |
||||
38 | */ |
||||
39 | private static $findSettings = [ |
||||
40 | 'typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array'], |
||||
41 | 'projection' => ['expires' => false], |
||||
42 | ]; |
||||
43 | |||||
44 | /** |
||||
45 | * Construct a new instance of MongoCache. |
||||
46 | * |
||||
47 | * @param Collection $collection The collection containing the cached data. |
||||
48 | * @param SerializerInterface $serializer A concrete serializer for converting data to and from BSON serializable |
||||
49 | * data. |
||||
50 | */ |
||||
51 | public function __construct(Collection $collection, SerializerInterface $serializer = null) |
||||
52 | { |
||||
53 | $this->collection = $collection; |
||||
54 | $this->serializer = $serializer ?? new NullSerializer(); |
||||
55 | } |
||||
56 | |||||
57 | /** |
||||
58 | * Fetches a value from the cache. |
||||
59 | * |
||||
60 | * @param string $key The unique key of this item in the cache. |
||||
61 | * @param mixed $default Default value to return if the key does not exist. |
||||
62 | * |
||||
63 | * @return mixed The value of the item from the cache, or $default in case of cache miss. |
||||
64 | * |
||||
65 | * @throws InvalidArgumentException Thrown if the $key string is not a legal value. |
||||
66 | */ |
||||
67 | public function get($key, $default = null)//@codingStandardsIgnoreLine Interface does not define type-hints or return |
||||
68 | { |
||||
69 | $this->validateKey($key); |
||||
70 | $cached = $this->collection->findOne(['_id' => $key], self::$findSettings); |
||||
71 | if ($cached === null) { |
||||
72 | return $default; |
||||
73 | } |
||||
74 | |||||
75 | return $this->serializer->unserialize($cached['data']); |
||||
76 | } |
||||
77 | |||||
78 | /** |
||||
79 | * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. |
||||
80 | * |
||||
81 | * @param string $key The key of the item to store. |
||||
82 | * @param mixed $value The value of the item to store, must be serializable. |
||||
83 | * @param null|integer|DateInterval $ttl Optional. The TTL value of this item. If no value is sent and |
||||
84 | * the driver supports TTL then the library may set a default value |
||||
85 | * for it or let the driver take care of that. |
||||
86 | * |
||||
87 | * @return boolean True on success and false on failure. |
||||
88 | * |
||||
89 | * @throws InvalidArgumentException Thrown if the $key string is not a legal value. |
||||
90 | */ |
||||
91 | public function set($key, $value, $ttl = null)//@codingStandardsIgnoreLine Interface does not define type-hints or return |
||||
92 | { |
||||
93 | $this->validateKey($key); |
||||
94 | return $this->updateCache($key, $this->serializer->serialize($value), $this->getExpires($ttl)); |
||||
95 | } |
||||
96 | |||||
97 | /** |
||||
98 | * Delete an item from the cache by its unique key. |
||||
99 | * |
||||
100 | * @param string $key The unique cache key of the item to delete. |
||||
101 | * |
||||
102 | * @return boolean True if the item was successfully removed. False if there was an error. |
||||
103 | * |
||||
104 | * @throws InvalidArgumentException Thrown if the $key string is not a legal value. |
||||
105 | */ |
||||
106 | public function delete($key)//@codingStandardsIgnoreLine Interface does not define type-hints or return |
||||
107 | { |
||||
108 | $this->validateKey($key); |
||||
109 | try { |
||||
110 | $this->collection->deleteOne(['_id' => $key]); |
||||
111 | return true; |
||||
112 | } catch (\Exception $e) { |
||||
113 | return false; |
||||
114 | } |
||||
115 | } |
||||
116 | |||||
117 | /** |
||||
118 | * Wipes clean the entire cache's keys. |
||||
119 | * |
||||
120 | * @return boolean True on success and false on failure. |
||||
121 | */ |
||||
122 | public function clear()//@codingStandardsIgnoreLine Interface does not define type-hints or return |
||||
123 | { |
||||
124 | try { |
||||
125 | $this->collection->deleteMany([]); |
||||
126 | return true; |
||||
127 | } catch (\Exception $e) { |
||||
128 | return false; |
||||
129 | } |
||||
130 | } |
||||
131 | |||||
132 | /** |
||||
133 | * Obtains multiple cache items by their unique keys. |
||||
134 | * |
||||
135 | * @param iterable $keys A list of keys that can obtained in a single operation. |
||||
136 | * @param mixed $default Default value to return for keys that do not exist. |
||||
137 | * |
||||
138 | * @return array List of key => value pairs. Cache keys that do not exist or are stale will have $default as value. |
||||
139 | * |
||||
140 | * @throws InvalidArgumentException Thrown if the $key string is not a legal value. |
||||
141 | */ |
||||
142 | public function getMultiple($keys, $default = null)//@codingStandardsIgnoreLine Interface does not define type-hints or return |
||||
143 | { |
||||
144 | $this->validateKeys($keys); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
145 | |||||
146 | $items = array_fill_keys($keys, $default); |
||||
0 ignored issues
–
show
$keys of type iterable is incompatible with the type array expected by parameter $keys of array_fill_keys() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
147 | $cached = $this->collection->find(['_id' => ['$in' => $keys]], self::$findSettings); |
||||
148 | foreach ($cached as $item) { |
||||
149 | $items[$item['_id']] = $this->serializer->unserialize($item['data']); |
||||
150 | } |
||||
151 | |||||
152 | return $items; |
||||
153 | } |
||||
154 | |||||
155 | /** |
||||
156 | * Persists a set of key => value pairs in the cache, with an optional TTL. |
||||
157 | * |
||||
158 | * @param iterable $values A list of key => value pairs for a multiple-set operation. |
||||
159 | * @param null|integer|DateInterval $ttl Optional. The TTL value of this item. If no value is sent and |
||||
160 | * the driver supports TTL then the library may set a default value |
||||
161 | * for it or let the driver take care of that. |
||||
162 | * |
||||
163 | * @return boolean True on success and false on failure. |
||||
164 | * |
||||
165 | * @throws InvalidArgumentException Thrown if $values is neither an array nor a Traversable, |
||||
166 | * or if any of the $values are not a legal value. |
||||
167 | */ |
||||
168 | public function setMultiple($values, $ttl = null)//@codingStandardsIgnoreLine Interface does not define type-hints or return |
||||
169 | { |
||||
170 | $expires = $this->getExpires($ttl); |
||||
171 | foreach ($values as $key => $value) { |
||||
172 | $this->validateKey($key); |
||||
173 | if (!$this->updateCache($key, $this->serializer->serialize($value), $expires)) { |
||||
174 | return false; |
||||
175 | } |
||||
176 | } |
||||
177 | |||||
178 | return true; |
||||
179 | } |
||||
180 | |||||
181 | /** |
||||
182 | * Deletes multiple cache items in a single operation. |
||||
183 | * |
||||
184 | * @param iterable $keys A list of string-based keys to be deleted. |
||||
185 | * |
||||
186 | * @return boolean True if the items were successfully removed. False if there was an error. |
||||
187 | * |
||||
188 | * @throws InvalidArgumentException Thrown if $keys is neither an array nor a Traversable, |
||||
189 | * or if any of the $keys are not a legal value. |
||||
190 | */ |
||||
191 | public function deleteMultiple($keys)//@codingStandardsIgnoreLine Interface does not define type-hints |
||||
192 | { |
||||
193 | $this->validateKeys($keys); |
||||
0 ignored issues
–
show
$keys of type iterable is incompatible with the type array expected by parameter $keys of SubjectivePHP\Psr\Simple...goCache::validateKeys() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
194 | |||||
195 | try { |
||||
196 | $this->collection->deleteMany(['_id' => ['$in' => $keys]]); |
||||
197 | return true; |
||||
198 | } catch (\Exception $e) { |
||||
199 | return false; |
||||
200 | } |
||||
201 | } |
||||
202 | |||||
203 | /** |
||||
204 | * Determines whether an item is present in the cache. |
||||
205 | * |
||||
206 | * NOTE: It is recommended that has() is only to be used for cache warming type purposes |
||||
207 | * and not to be used within your live applications operations for get/set, as this method |
||||
208 | * is subject to a race condition where your has() will return true and immediately after, |
||||
209 | * another script can remove it making the state of your app out of date. |
||||
210 | * |
||||
211 | * @param string $key The cache item key. |
||||
212 | * |
||||
213 | * @return boolean |
||||
214 | * |
||||
215 | * @throws InvalidArgumentException Thrown if the $key string is not a legal value. |
||||
216 | */ |
||||
217 | public function has($key) //@codingStandardsIgnoreLine Interface does not define type-hints |
||||
218 | { |
||||
219 | $this->validateKey($key); |
||||
220 | return $this->collection->count(['_id' => $key]) === 1; |
||||
221 | } |
||||
222 | |||||
223 | /** |
||||
224 | * Upserts a PSR-7 response in the cache. |
||||
225 | * |
||||
226 | * @param string $key The key of the response to store. |
||||
227 | * @param array $value The data to store. |
||||
228 | * @param UTCDateTime $expires The expire date of the cache item. |
||||
229 | * |
||||
230 | * @return boolean |
||||
231 | */ |
||||
232 | private function updateCache(string $key, array $value, UTCDateTime $expires) : bool |
||||
233 | { |
||||
234 | $document = ['_id' => $key, 'expires' => $expires, 'data' => $value]; |
||||
235 | try { |
||||
236 | $this->collection->updateOne(['_id' => $key], ['$set' => $document], ['upsert' => true]); |
||||
237 | return true; |
||||
238 | } catch (\Exception $e) { |
||||
239 | return false; |
||||
240 | } |
||||
241 | } |
||||
242 | |||||
243 | /** |
||||
244 | * Converts the given time to live value to a UTCDateTime instance; |
||||
245 | * |
||||
246 | * @param mixed $ttl The time-to-live value to validate. |
||||
247 | * |
||||
248 | * @return UTCDateTime |
||||
249 | */ |
||||
250 | private function getExpires($ttl) : UTCDateTime |
||||
251 | { |
||||
252 | $this->validateTTL($ttl); |
||||
253 | |||||
254 | $ttl = $ttl ?: 86400; |
||||
255 | |||||
256 | if ($ttl instanceof \DateInterval) { |
||||
257 | return new UTCDateTime((new \DateTime('now'))->add($ttl)->getTimestamp() * 1000); |
||||
258 | } |
||||
259 | |||||
260 | return new UTCDateTime((time() + $ttl) * 1000); |
||||
261 | } |
||||
262 | } |
||||
263 |