Issues (7)

src/MongoCache.php (3 issues)

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
$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 ignore-type  annotation

144
        $this->validateKeys(/** @scrutinizer ignore-type */ $keys);
Loading history...
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 ignore-type  annotation

146
        $items = array_fill_keys(/** @scrutinizer ignore-type */ $keys, $default);
Loading history...
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 ignore-type  annotation

193
        $this->validateKeys(/** @scrutinizer ignore-type */ $keys);
Loading history...
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