GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Cache::getMultiple()   B
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 26
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 8.439
c 0
b 0
f 0
cc 5
eloc 12
nc 4
nop 2
1
<?php
2
3
/**
4
 * This file is part of the PHPMongo package.
5
 *
6
 * (c) Dmytro Sokil <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sokil\Mongo;
13
14
use Psr\SimpleCache\CacheInterface;
15
use Sokil\Mongo\Cache\Exception\CacheException;
16
use Sokil\Mongo\Cache\Exception\InvalidArgumentException;
17
18
class Cache implements \Countable, CacheInterface
19
{
20
    const FIELD_NAME_VALUE = 'v';
21
    const FIELD_NAME_EXPIRED = 'e';
22
    const FIELD_NAME_TAGS = 't';
23
    
24
    private $collection;
25
26
    /**
27
     * Cache constructor.
28
     * @param Database $database
29
     * @param string $collectionName namespace of cache
30
     */
31
    public function __construct(Database $database, $collectionName)
32
    {
33
        $this->collection = $database
34
            ->map($collectionName, array(
35
                'index' => array(
36
                    // date field
37
                    array(
38
                        'keys' => array(self::FIELD_NAME_EXPIRED => 1),
39
                        'expireAfterSeconds' => 0
40
                    ),
41
                )
42
            ))
43
            ->getCollection($collectionName)
44
            ->disableDocumentPool();
45
    }
46
47
    /**
48
     * @return Cache
49
     */
50
    public function init()
51
    {
52
        $this->collection->initIndexes();
53
        return $this;
54
    }
55
56
    /**
57
     * Persists a set of key => value pairs in the cache, with an optional TTL.
58
     *
59
     * @param array                     $values A list of key => value pairs for a multiple-set operation.
60
     * @param null|int|\DateInterval    $ttl    Optional. The TTL value of this item. If no value is sent and
61
     *                                          the driver supports TTL then the library may set a default value
62
     *                                          for it or let the driver take care of that.
63
     * @param array                     $tags   List of tags
64
     *
65
     * @return bool True on success and false on failure.
66
     *
67
     * @throws \Psr\SimpleCache\InvalidArgumentException
68
     * MUST be thrown if $values is neither an array nor a Traversable,
69
     * or if any of the $values are not a legal value.
70
     */
71
    public function setMultiple($values, $ttl = null, array $tags = array())
72
    {
73
        // prepare expiration
74
        if (!empty($ttl)) {
75 View Code Duplication
            if ($ttl instanceof \DateInterval) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
76
                $ttl = $ttl->s;
77
            } elseif (!is_int($ttl)) {
78
                throw new InvalidArgumentException('Invalid TTL specified');
79
            }
80
81
            $expirationTimestamp = time() + $ttl;
82
        }
83
84
        // prepare persistence
85
        $persistence = $this->collection->getDatabase()->getClient()->createPersistence();
86
87
        // prepare documents to store
88
        foreach ($values as $key => $value) {
89
            // create document
90
            $document = array(
91
                '_id' => $key,
92
                self::FIELD_NAME_VALUE => $value,
93
            );
94
95
            // add expiration
96
            if (!empty($expirationTimestamp)) {
97
                $document[self::FIELD_NAME_EXPIRED] = new \MongoDate($expirationTimestamp);
98
            }
99
100
            // prepare tags
101
            if (!empty($tags)) {
102
                $document[self::FIELD_NAME_TAGS] = $tags;
103
            }
104
105
            // attach document
106
            $persistence->persist($this->collection->createDocument($document));
107
        }
108
109
        try {
110
            $persistence->flush();
111
        } catch (\Exception $e) {
112
            return false;
113
        }
114
115
        return true;
116
    }
117
118
    /**
119
     * Set with expiration on concrete date
120
     *
121
     * @deprecated Use self::set() with calculated ttl
122
     *
123
     * @param int|string $key
124
     * @param mixed $value
125
     * @param int $expirationTime
126
     * @param array $tags
127
     *
128
     * @throws Exception
129
     *
130
     * @return bool
131
     */
132
    public function setDueDate($key, $value, $expirationTime, array $tags = null)
133
    {
134
        return $this->set($key, $value, $expirationTime - time(), $tags);
135
    }
136
    
137
    /**
138
     * Set key that never expired
139
     *
140
     * @deprecated Use self::set() with null in ttl
141
     *
142
     * @param int|string $key
143
     * @param mixed $value
144
     * @param array $tags
145
     *
146
     * @return bool
147
     */
148
    public function setNeverExpired($key, $value, array $tags = null)
149
    {
150
        return $this->set($key, $value, null, $tags);
151
    }
152
    
153
    /**
154
     * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
155
     *
156
     * @param string                    $key    The key of the item to store.
157
     * @param mixed                     $value  The value of the item to store, must be serializable.
158
     * @param null|int|\DateInterval    $ttl    Optional. The TTL value of this item. If no value is sent and
159
     *                                          the driver supports TTL then the library may set a default value
160
     *                                          for it or let the driver take care of that.
161
     * @param array                     $tags   List of tags
162
     *
163
     * @return bool True on success and false on failure.
164
     *
165
     * @throws InvalidArgumentException
166
     * @throws CacheException
167
     */
168
    public function set($key, $value, $ttl = null, array $tags = null)
169
    {
170
        // create document
171
        $document = array(
172
            '_id' => $key,
173
            self::FIELD_NAME_VALUE => $value,
174
        );
175
176
        // prepare expiration
177
        if (!empty($ttl)) {
178 View Code Duplication
            if ($ttl instanceof \DateInterval) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
179
                $ttl = $ttl->s;
180
            } elseif (!is_int($ttl)) {
181
                throw new InvalidArgumentException('Invalid TTL specified');
182
            }
183
184
            $expirationTimestamp = time() + $ttl;
185
            $document[self::FIELD_NAME_EXPIRED] = new \MongoDate((int) $expirationTimestamp);
186
        }
187
188
        // prepare tags
189
        if (!empty($tags)) {
190
            $document[self::FIELD_NAME_TAGS] = $tags;
191
        }
192
193
        // create document
194
        $result = $this
195
            ->collection
196
            ->getMongoCollection()
197
            ->update(
198
                array(
199
                    '_id' => $key,
200
                ),
201
                $document,
202
                array(
203
                    'upsert' => true,
204
                )
205
            );
206
207
        // check result
208
        return (double) 1 === $result['ok'];
209
    }
210
211
        /**
212
     * @param array $keys
213
     * @param mixed|null $default
214
     *
215
     * @return array
216
     */
217
    public function getMultiple($keys, $default = null)
218
    {
219
        // Prepare defaults
220
        $values = array_fill_keys($keys, $default);
221
222
        // Get document
223
        $documents = $this->collection->getDocuments($keys);
224
        if (empty($documents)) {
225
            return $values;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $values; (array) is incompatible with the return type declared by the interface Psr\SimpleCache\CacheInterface::getMultiple of type Psr\SimpleCache\iterable.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
226
        }
227
228
        // Mongo deletes document not exactly when set in field
229
        // Required date checking
230
        // Expiration may be empty for keys which never expired
231
        foreach ($documents as $document) {
232
            /** @var \MongoDate $expiredAt */
233
            $expiredAt = $document->get(self::FIELD_NAME_EXPIRED);
234
            if (empty($expiredAt) || $expiredAt->sec >= time()) {
235
                $values[$document->getId()] = $document->get(self::FIELD_NAME_VALUE);
236
            } else {
237
                $values[$document->getId()] = $default;
238
            }
239
        }
240
241
        return $values;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $values; (array) is incompatible with the return type declared by the interface Psr\SimpleCache\CacheInterface::getMultiple of type Psr\SimpleCache\iterable.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
242
    }
243
244
    /**
245
     * Get value by key
246
     *
247
     * @param string $key
248
     * @param mixed $default
249
     *
250
     * @return mixed
251
     */
252
    public function get($key, $default = null)
253
    {
254
        // Get document
255
        $document = $this->collection->getDocument($key);
256
        if (!$document) {
257
            return $default;
258
        }
259
260
        // Mongo deletes document not exactly when set in field
261
        // Required date checking
262
        // Expiration may be empty for keys which never expired
263
        $expiredAt = $document->get(self::FIELD_NAME_EXPIRED);
264
        if (!empty($expiredAt) && $expiredAt->sec < time()) {
265
            return $default;
266
        }
267
268
        // Return value
269
        return $document->get(self::FIELD_NAME_VALUE);
270
    }
271
272
273
    /**
274
     * Clear all cache
275
     *
276
     * @return bool
277
     */
278
    public function clear()
279
    {
280
        try {
281
            $this->collection->delete();
282
        } catch (\Exception $e) {
283
            return false;
284
        }
285
286
        return true;
287
    }
288
289
    /**
290
     * Delete an item from the cache by its unique key.
291
     *
292
     * @param string $key The unique cache key of the item to delete.
293
     *
294
     * @return bool True if the item was successfully removed. False if there was an error.
295
     *
296
     * @throws \Psr\SimpleCache\InvalidArgumentException
297
     * MUST be thrown if the $key string is not a legal value.
298
     */
299
    public function delete($key)
300
    {
301
        if (empty($key) || !is_string($key)) {
302
            throw new InvalidArgumentException('Key must be string');
303
        }
304
305
        try {
306
            $this->collection->batchDelete(array(
307
                '_id' => $key,
308
            ));
309
        } catch (\Exception $e) {
310
            return false;
311
        }
312
313
        return true;
314
    }
315
316
    /**
317
     * Deletes multiple cache items in a single operation.
318
     *
319
     * @param array $keys A list of string-based keys to be deleted.
320
     *
321
     * @return bool True if the items were successfully removed. False if there was an error.
322
     *
323
     * @throws \Psr\SimpleCache\InvalidArgumentException
324
     * MUST be thrown if $keys is neither an array nor a Traversable,
325
     * or if any of the $keys are not a legal value.
326
     */
327
    public function deleteMultiple($keys)
328
    {
329
        try {
330
            $this->collection->batchDelete(
331
                function(Expression $e) use($keys) {
332
                    $e->whereIn('_id', $keys);
333
                }
334
            );
335
        } catch (\Exception $e) {
336
            return false;
337
        }
338
339
        return true;
340
    }
341
    
342
    /**
343
     * Delete documents by tag
344
     */
345
    public function deleteMatchingTag($tag)
346
    {
347
        $this->collection->batchDelete(function (\Sokil\Mongo\Expression $e) use ($tag) {
348
            return $e->where(Cache::FIELD_NAME_TAGS, $tag);
349
        });
350
        
351
        return $this;
352
    }
353
    
354
    /**
355
     * Delete documents by tag
356
     */
357
    public function deleteNotMatchingTag($tag)
358
    {
359
        $this->collection->batchDelete(function (\Sokil\Mongo\Expression $e) use ($tag) {
360
            return $e->whereNotEqual(Cache::FIELD_NAME_TAGS, $tag);
361
        });
362
        
363
        return $this;
364
    }
365
    
366
    /**
367
     * Delete documents by tag
368
     * Document deletes if it contains all passed tags
369
     */
370
    public function deleteMatchingAllTags(array $tags)
371
    {
372
        $this->collection->batchDelete(function (\Sokil\Mongo\Expression $e) use ($tags) {
373
            return $e->whereAll(Cache::FIELD_NAME_TAGS, $tags);
374
        });
375
        
376
        return $this;
377
    }
378
    
379
    /**
380
     * Delete documents by tag
381
     * Document deletes if it not contains all passed tags
382
     */
383
    public function deleteMatchingNoneOfTags(array $tags)
384
    {
385
        $this->collection->batchDelete(function (\Sokil\Mongo\Expression $e) use ($tags) {
386
            return $e->whereNoneOf(Cache::FIELD_NAME_TAGS, $tags);
387
        });
388
        
389
        return $this;
390
    }
391
    
392
    /**
393
     * Delete documents by tag
394
     * Document deletes if it contains any of passed tags
395
     */
396
    public function deleteMatchingAnyTag(array $tags)
397
    {
398
        $this->collection->batchDelete(function (\Sokil\Mongo\Expression $e) use ($tags) {
399
            return $e->whereIn(Cache::FIELD_NAME_TAGS, $tags);
400
        });
401
        
402
        return $this;
403
    }
404
    
405
    /**
406
     * Delete documents by tag
407
     * Document deletes if it contains any of passed tags
408
     */
409
    public function deleteNotMatchingAnyTag(array $tags)
410
    {
411
        $this->collection->batchDelete(function (\Sokil\Mongo\Expression $e) use ($tags) {
412
            return $e->whereNotIn(Cache::FIELD_NAME_TAGS, $tags);
413
        });
414
        
415
        return $this;
416
    }
417
418
    /**
419
     * Get total count of documents in cache
420
     *
421
     * @return int
422
     */
423
    public function count()
424
    {
425
        return $this->collection->count();
426
    }
427
428
    /**
429
     * Check if cache has key
430
     *
431
     * @param string $key
432
     * @return bool
433
     */
434
    public function has($key)
435
    {
436
        return (bool)$this->get($key);
437
    }
438
}
439