Passed
Pull Request — master (#251)
by Gabriel
10:21
created

ExtMongoDBCache::doFlush()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.2559

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 10
ccs 3
cts 5
cp 0.6
crap 2.2559
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
6
namespace Doctrine\Common\Cache;
7
8
use MongoDB\BSON\Binary;
9
use MongoDB\BSON\UTCDateTime;
10
use MongoDB\Collection;
11
use MongoDB\Database;
12
use MongoDB\Driver\Exception\Exception;
13
use MongoDB\Model\BSONDocument;
14
use function serialize;
15
use function time;
16
use function unserialize;
17
18
/**
19
 * MongoDB cache provider for ext-mongodb
20
 *
21
 * @internal Do not use - will be removed in 2.0. Use MongoDBCache instead
22
 */
23
class ExtMongoDBCache extends CacheProvider
24
{
25
    /** @var Database */
26
    private $database;
27
28
    /** @var Collection */
29
    private $collection;
30
31
    /** @var bool */
32
    private $expirationIndexCreated = false;
33
34
    /**
35
     * This provider will default to the write concern and read preference
36
     * options set on the Database instance (or inherited from MongoDB or
37
     * Client). Using an unacknowledged write concern (< 1) may make the return
38
     * values of delete() and save() unreliable. Reading from secondaries may
39
     * make contain() and fetch() unreliable.
40
     *
41
     * @see http://www.php.net/manual/en/mongo.readpreferences.php
42
     * @see http://www.php.net/manual/en/mongo.writeconcerns.php
43
     */
44 78
    public function __construct(Collection $collection)
45
    {
46
        // Ensure there is no typemap set - we want to use our own
47 78
        $this->collection = $collection->withOptions(['typeMap' => null]);
48 78
        $this->database   = new Database($collection->getManager(), $collection->getDatabaseName());
49 78
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54 76
    protected function doFetch($id)
55
    {
56 76
        $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::DATA_FIELD, MongoDBCache::EXPIRATION_FIELD]);
57
58 76
        if ($document === null) {
59 76
            return false;
60
        }
61
62 65
        if ($this->isExpired($document)) {
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type array; however, parameter $document of Doctrine\Common\Cache\ExtMongoDBCache::isExpired() does only seem to accept MongoDB\Model\BSONDocument, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

62
        if ($this->isExpired(/** @scrutinizer ignore-type */ $document)) {
Loading history...
63
            $this->createExpirationIndex();
64
            $this->doDelete($id);
65
            return false;
66
        }
67
68 65
        return unserialize($document[MongoDBCache::DATA_FIELD]->getData());
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74 73
    protected function doContains($id)
75
    {
76 73
        $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::EXPIRATION_FIELD]);
77
78 73
        if ($document === null) {
79 52
            return false;
80
        }
81
82 69
        if ($this->isExpired($document)) {
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type array; however, parameter $document of Doctrine\Common\Cache\ExtMongoDBCache::isExpired() does only seem to accept MongoDB\Model\BSONDocument, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

82
        if ($this->isExpired(/** @scrutinizer ignore-type */ $document)) {
Loading history...
83 1
            $this->createExpirationIndex();
84 1
            $this->doDelete($id);
85 1
            return false;
86
        }
87
88 69
        return true;
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 74
    protected function doSave($id, $data, $lifeTime = 0)
95
    {
96
        try {
97 74
            $this->collection->updateOne(
98 74
                ['_id' => $id],
99
                [
100 74
                    '$set' => [
101 74
                        MongoDBCache::EXPIRATION_FIELD => ($lifeTime > 0 ? new UTCDateTime((time() + $lifeTime) * 1000): null),
102 74
                        MongoDBCache::DATA_FIELD => new Binary(serialize($data), Binary::TYPE_GENERIC),
103
                    ],
104
                ],
105 74
                ['upsert' => true]
106
            );
107
        } catch (Exception $e) {
108
            return false;
109
        }
110
111 74
        return true;
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117 46
    protected function doDelete($id)
118
    {
119
        try {
120 46
            $this->collection->deleteOne(['_id' => $id]);
121
        } catch (Exception $e) {
122
            return false;
123
        }
124
125 46
        return true;
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131 2
    protected function doFlush()
132
    {
133
        try {
134
            // Use remove() in lieu of drop() to maintain any collection indexes
135 2
            $this->collection->deleteMany([]);
136
        } catch (Exception $e) {
137
            return false;
138
        }
139
140 2
        return true;
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146 1
    protected function doGetStats()
147
    {
148 1
        $uptime      = null;
149 1
        $memoryUsage = null;
150
151
        try {
152 1
            $serverStatus = $this->database->command([
153 1
                'serverStatus' => 1,
154
                'locks' => 0,
155
                'metrics' => 0,
156
                'recordStats' => 0,
157
                'repl' => 0,
158 1
            ])->toArray()[0];
159 1
            $uptime       = $serverStatus['uptime'] ?? null;
160
        } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
161
        }
162
163
        try {
164 1
            $collStats   = $this->database->command(['collStats' => $this->collection->getCollectionName()])->toArray()[0];
165
            $memoryUsage = $collStats['size'] ?? null;
166 1
        } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
167
        }
168
169
        return [
170 1
            Cache::STATS_HITS => null,
171
            Cache::STATS_MISSES => null,
172 1
            Cache::STATS_UPTIME => $uptime,
173 1
            Cache::STATS_MEMORY_USAGE => $memoryUsage,
174
            Cache::STATS_MEMORY_AVAILABLE  => null,
175
        ];
176
    }
177
178
    /**
179
     * Check if the document is expired.
180
     */
181 70
    private function isExpired(BSONDocument $document) : bool
182
    {
183 70
        return isset($document[MongoDBCache::EXPIRATION_FIELD]) &&
184 70
            $document[MongoDBCache::EXPIRATION_FIELD] instanceof UTCDateTime &&
185 70
            $document[MongoDBCache::EXPIRATION_FIELD]->toDateTime() < new \DateTime();
186
    }
187
188 1
    private function createExpirationIndex() : void
189
    {
190 1
        if ($this->expirationIndexCreated) {
191
            return;
192
        }
193
194 1
        $this->collection->createIndex([MongoDBCache::EXPIRATION_FIELD => 1], ['background' => true, 'expireAfterSeconds' => 0]);
195 1
    }
196
}
197